All Downloads are FREE. Search and download functionalities are using the official Maven repository.

echopointng.image.TextImageReference Maven / Gradle / Ivy

Go to download

Echo2 bundled with Echo2_Extras, Echo2_FileTransfer and echopointing and various improvements/bugfixes

There is a newer version: 2.0.4
Show newest version
package echopointng.image;
/* 
 * This file is part of the Echo Point Project.  This project is a collection
 * of Components that have extended the Echo Web Application Framework.
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 */

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.font.GraphicAttribute;
import java.awt.font.ImageGraphicAttribute;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.Map;

import nextapp.echo2.app.Alignment;
import nextapp.echo2.app.Color;
import nextapp.echo2.app.Extent;
import nextapp.echo2.app.Font;
import nextapp.echo2.app.Insets;
import echopointng.util.ColorKit;
import echopointng.util.FontKit;

/**
 * The TextImageReference class is an ImageReference
 * implementation that can draw text onto a background image.  This makes the 
 * class perfect for "dynamic" button icons since a "button-like" background 
 * image can be used and then button text can be dynamically drawn on it.
 * 

* If the text to be rendered wont fit within the background image (plus its Insets) * then the background image can be "scaled" to fit the text. The scaling options * are : *

    *
  • SCALE_NONE - no scaling is performed
  • *
  • SCALE_FAST - a fast scaling algorithm is used. Not always the best look but its fast
  • *
  • SCALE_SMOOTH - a better looking scaling algorithm is used at the cost of speed
  • *
  • SCALE_SPLICE_V_THEN_H - this will splice the image at its x,y, first in the vertical direction * and then in the horizontal direction.
  • *
  • SCALE_SPLICE_H_THEN_V - this will splice the image at its x,y, first in the horizontal direction * and then in the vertical direction.
  • *
*

* The last two scaling options are very useful for "button-like" background images. This is because * they will not scale the "outer" edges of the image but rather "splice and fill". This means that * "button-like shadows and outlines" will not be lost or scaled unecesarily. *

* TextImageReference will also "break" strings on carriage return boundaries, to allow * for multi line text. This may result in the need to "auto-resize" the background image. *

* The text is drawn at a specific {x,y} position. If you use a contructor without * the {x,y} parameters, then the text is automatically centred within the * height and width of the backgroundImage. You can use the horizontal and * vertical alignment properties to control where the text is placed in relation * to the {x,y} position. The {x,y} position will be the base line of the text. *

* AttributedString's are used to render the image text. These * contain formatting instructions for specific characters. While the * formatting information uses java.awt.* visual classes, a series of static * helper methods have been provided to help create java.text.AttributedStrings * using Echo color/font objects. You can also use the ColorKit * and FontKit classes. *

* AttributedStrings have a great feature whereby the Unicode * replacement character u'FFFC' can be substituted with another visual * element. This is supported via the replacementImages and the * replacementImageAlignment properties. The images in the replacementImages * will be substituted, in a round robin fashion, for each Unicode replacement * character in the AttributedString. *

* This class by default does not keep the encoded output image bytes in memory. * You can change this by setKeptInMemory(true). *

* You should think about loading the background images via * ImageKit.loadCachedImage(...) so that these static * images are cached even if the dynamic TextImageReference * is not. * * @see java.text.AttributedString * @see java.awt.font.TextAttribute * @see java.awt.Image * @see java.awt.Image#SCALE_FAST * @see java.awt.Image#SCALE_SMOOTH * * @author Brad Baker */ public class TextImageReference extends EncodedImageReference implements Serializable { /** Dont scale the background image to the text size */ public final static int SCALE_NONE = 0; /** Choose a scaling algorithm to optimize speed more than smoothness of the scaled image */ public final static int SCALE_FAST = 4; /** Choose a scaling algorithm to optimize smoothness of the scaled image more than speed */ public final static int SCALE_SMOOTH = 8; /** Choose a scaling algorithm that splices vertically then horizontally */ public final static int SCALE_SPLICE_V_THEN_H = 16; /** Choose a scaling algorithm that splices horizontally then vertically */ public final static int SCALE_SPLICE_H_THEN_V = 32; /** The default scaling option is SCALE_SPLICE_V_THEN_H */ public final static int SCALE_DEFAULT = SCALE_SPLICE_V_THEN_H; /** The default insets between the text and the outer edge of the background image */ public final static Insets DEFAULT_INSETS = new Insets(3); private transient AttributedString attributedString; private transient Image backgroundImage; private transient Image[] replacementImages; private boolean bestQuality = true; private boolean autoCenterAtConstruction = false; private int horizontalAlignment = Alignment.CENTER; private int replacementImageAlignment = Alignment.CENTER; private int textAngle = 0; private int verticalAlignment = Alignment.CENTER; private int x = Integer.MAX_VALUE; private int y = Integer.MAX_VALUE; private int origImageW = -1; private int origImageH = -1; private int scaleOption = SCALE_DEFAULT; private Insets insets = DEFAULT_INSETS; private TextImageDrawer drawer = new TextImageDrawer(); /** * Constructs a TextImageReference that will be drawn centered within the * background image. * * @param string - the attributed string to draw. Can be null. * @param backgroundImage - the background image to draw on. Must not be null. */ public TextImageReference(AttributedString string, Image backgroundImage) { this(string,backgroundImage,SCALE_DEFAULT); } /** * Constructs a TextImageReference that will be drawn centered within the * background image and scalled according to the scaling option. * * @param string - the attributed string to draw. Can be null. * @param backgroundImage - the background image to draw on. Must not be null. * @param scaleOption - the scaling option to use */ public TextImageReference(AttributedString string, Image backgroundImage, int scaleOption) { this(string, backgroundImage, scaleOption, new GifEncoder()); } /** * Constructs a TextImageReference that will be drawn centered within the * background image and scalled according to the scaling option. * * @param string - the attributed string to draw. Can be null. * @param backgroundImage - the background image to draw on. Must not be null. * @param scaleOption - the scaling option to use * @param encoder - the Imageencoder to use for this image */ public TextImageReference(AttributedString string, Image backgroundImage, int scaleOption, ImageEncoder encoder) { constructTextImageRefererence(Integer.MAX_VALUE, Integer.MAX_VALUE, string, backgroundImage, scaleOption, encoder); } /** * Constructs a TextImageReference that will be drawn centered within the * background image, which is loaded from a file. * * @param string - the attributed string to draw. Can be null. * @param imageFileName - the background image file to draw on. Must not be null. * @param encoder = the ImageEncoder to use */ public TextImageReference(AttributedString string, String imageFileName,ImageEncoder encoder) { this(string,ImageKit.loadImage(imageFileName),SCALE_DEFAULT,encoder); } /** * Constructs a TextImageReference that will be drawn centered within the * background image, which is loaded from a file. * * @param string - the attributed string to draw. Can be null. * @param imageFileName - the background image file to draw on. Must not be null. */ public TextImageReference(AttributedString string, String imageFileName) { this(string,ImageKit.loadImage(imageFileName)); } /** * Constructs a TextImageReference that will be centered within the * background image, which is loaded from an URL. * * @param string - the attributed string to draw. Can be null. * @param imageURL - the background image URL to draw on. Must not be null. */ public TextImageReference(AttributedString string, URL imageURL) { this(string,ImageKit.loadImage(imageURL)); } /** * Constructs a TextImageReference that will be centered within the * background image, which is loaded from an URL. * * @param string - the attributed string to draw. Can be null. * @param imageURL - the background image URL to draw on. Must not be null. * @param encoder = the ImageEncoder to use */ public TextImageReference(AttributedString string, URL imageURL, ImageEncoder encoder) { this(string,ImageKit.loadImage(imageURL), SCALE_DEFAULT,encoder); } /** * Constructs a TextImageReference that will be drawn at the x,y points * within the background image. * * @param x - where to draw the text in the x direction * @param y - where to draw the text in the y direction * @param string - the attributed string to draw. Can be null. * @param backgroundImage - the background image to draw on. Must not be null. */ public TextImageReference(int x, int y, AttributedString string, Image backgroundImage) { this(x, y, string, backgroundImage, new GifEncoder()); } /** * Constructs a TextImageReference that will be drawn at the x,y points * within the background image. * * @param x - where to draw the text in the x direction * @param y - where to draw the text in the y direction * @param string - the attributed string to draw. Can be null. * @param backgroundImage - the background image to draw on. Must not be null. * @param encoder = the ImageEncoder to use */ public TextImageReference(int x, int y, AttributedString string, Image backgroundImage, ImageEncoder encoder) { constructTextImageRefererence(x, y, string, backgroundImage, SCALE_DEFAULT, encoder); } /** * Constructs a TextImageReference that will be drawn at the x,y points * within the background image, which is loaded from a file. * * @param x - where to draw the text in the x direction * @param y - where to draw the text in the y direction * @param string - the attributed string to draw. Can be null. * @param imageFileName - the background image to draw on. Must not be null. */ public TextImageReference(int x, int y, AttributedString string, String imageFileName) { this(x,y,string,ImageKit.loadImage(imageFileName)); } /** * Constructs a TextImageReference that will be drawn at the x,y points * within the background image, which is loaded from a file. * * @param x - where to draw the text in the x direction * @param y - where to draw the text in the y direction * @param string - the attributed string to draw. Can be null. * @param imageFileName - the background image to draw on. Must not be null. * @param encoder = the ImageEncoder to use */ public TextImageReference(int x, int y, AttributedString string, String imageFileName, ImageEncoder encoder) { this(x,y,string,ImageKit.loadImage(imageFileName),encoder); } /** * Constructs a TextImageReference that will be drawn at the x,y points * within the background image, which is loaded from an URL. * * @param x - where to draw the text in the x direction * @param y - where to draw the text in the y direction * @param string - the attributed string to draw. Can be null. * @param imageURL - the background image to draw on. Must not be null. */ public TextImageReference(int x, int y, AttributedString string, URL imageURL) { this(x,y,string,ImageKit.loadImage(imageURL)); } /** * Constructs a TextImageReference that will be drawn at the x,y points * within the background image, which is loaded from an URL. * * @param x - where to draw the text in the x direction * @param y - where to draw the text in the y direction * @param string - the attributed string to draw. Can be null. * @param imageURL - the background image to draw on. Must not be null. * @param encoder = the ImageEncoder to use */ public TextImageReference(int x, int y, AttributedString string, URL imageURL, ImageEncoder encoder) { this(x,y,string,ImageKit.loadImage(imageURL),encoder); } /** Constructs the object */ private void constructTextImageRefererence(int x, int y, AttributedString string, Image backgroundImage, int scalingOption, ImageEncoder encoder) { if (backgroundImage == null) throw new IllegalArgumentException("The backgroundImage must be non null"); if (!ImageKit.waitForImage(backgroundImage)) throw new IllegalStateException("The backgroundImage could not be loaded"); this.origImageW = backgroundImage.getWidth(ImageKit.imageObserver); this.origImageH = backgroundImage.getHeight(ImageKit.imageObserver); this.scaleOption = scalingOption; this.attributedString = string; this.backgroundImage = backgroundImage; if (x == Integer.MAX_VALUE && y == Integer.MAX_VALUE) this.autoCenterAtConstruction = true; if (encoder == null) { // we use GIF encoding by default! encoder = new GifEncoder(); } super.setEncoder(encoder); // // initially I thought that keeping the encoded results in memory would // be needed because the encoder would be called all the time. However // the CacheableService facility of Echo is excellent and hence render // will only be called if the image has changed. So we dont keep it // in memory. super.setKeptInMemory(false); // // okay workout background image size, and maybe scale it! drawer.reconstructBackgroundImage(x,y); } /** * Called to paint the text of the TextImageReference. The * background image is already present when this method is called *

* Subclasses are expected to draw the AttributedString, respecting * the other parameters that are in force such as textAngle *

* This method returns a Rectangle2D that outlines the boundaries * of any text painted, whcih may be larger than the actual * drawing space. * * @param g the Graphics2D * @param x the x co-ord to paint the text * @param y the y co-ord to paint the text * @return returns a boundary Rectangle enclosing all text painted */ protected Rectangle2D paintText(Graphics2D g, int x, int y) { return drawer.paintText(g,x,y,this.attributedString); } /** * This methods returns an array of TextLayouts that will be used to * draw the attributed string. In this default version of the method, * the TextLayouts are broken down on carriage return boundaries. These * TextLayouts can then be used to determine the height and width * of the attributed string. *

* This is called to determine the dimensions of the current text. This * is used to decide whether to "scale" the background image * if the text inside it is bigger then these plus the insets. This * method should return the same dimensions that are used * in the paintText() method. *

* * @param g - the Graphics 2D * @param as - the attributed string * @return an array of TextLayout to be used for painting */ protected TextLayout[] getTextLayouts(Graphics2D g, AttributedString as) { ACI subcit; ACI cit = new ACI(attributedString.getIterator()); FontRenderContext frc = g.getFontRenderContext(); replaceAttributedImages(g, frc, this.attributedString); // run through the string and find all the \n chars indexes. // We start a new line from that position when drawing ArrayList textLayouts = new ArrayList(); int current = cit.getBeginIndex(); int end = cit.getEndIndex(); int previous = current; boolean lastCharWasCR = false; for (char c = cit.first(); c != CharacterIterator.DONE; c = cit.next()) { if (c == '\n') { if (! lastCharWasCR) { subcit = new ACI(as.getIterator(null,previous,current)); textLayouts.add(new TextLayout(subcit.getIterator(), frc)); previous = current+1; } else { previous++; } lastCharWasCR = true; } else { lastCharWasCR = false; } current++; } if (previous < end) { subcit = new ACI(as.getIterator(null,previous,end)); textLayouts.add(new TextLayout(subcit.getIterator(), frc)); } return (TextLayout[]) textLayouts.toArray(new TextLayout[textLayouts.size()]); } /** * Replaces all Unicode replacement characters u'FFFC' with the images from * getReplacementImages() array in a round robin fashion. */ protected void replaceAttributedImages(Graphics2D g, FontRenderContext frc, AttributedString as) { if (as == null) return; StringBuffer buf = new StringBuffer(); CharacterIterator cit = as.getIterator(); for (char c = cit.first(); c != CharacterIterator.DONE; c = cit.next()) { buf.append(c); } String s = buf.toString(); int ii = 0; Image[] images = replacementImages; if (images != null && images.length > 0) { // the maximum height int maxTextHeight = 0; for (int i = 0; i < replacementImages.length; i++) { maxTextHeight = Math.max(maxTextHeight, images[i].getHeight(ImageKit.imageObserver)); } TextLayout tl = new TextLayout(attributedString.getIterator(), frc); Rectangle rectText = tl.getBounds().getBounds(); maxTextHeight = Math.max(maxTextHeight, rectText.height); int pos = s.indexOf("\ufffc"); while (pos >= 0) { ImageKit.waitForImage(images[ii]); //int imgW = images[ii].getWidth(ImageKit.imageObserver); int imgH = images[ii].getHeight(ImageKit.imageObserver); int gX = 0; int gY = 0; int attrAlignment = GraphicAttribute.TOP_ALIGNMENT; if (replacementImageAlignment == Alignment.TOP) { attrAlignment = GraphicAttribute.ROMAN_BASELINE; gY += rectText.height - tl.getDescent(); } else if (replacementImageAlignment == Alignment.CENTER) { attrAlignment = GraphicAttribute.ROMAN_BASELINE; if (imgH < maxTextHeight) { gY += (rectText.height - tl.getDescent()) / 2 + (imgH / 2); } else { gY += (rectText.height / 2) + (imgH / 2); } } else if (replacementImageAlignment == Alignment.BOTTOM) { attrAlignment = GraphicAttribute.ROMAN_BASELINE; gY += imgH; } ImageGraphicAttribute imageGraphic = new ImageGraphicAttribute(images[ii], attrAlignment, gX, gY); as.addAttribute(TextAttribute.CHAR_REPLACEMENT, imageGraphic, pos, pos + 1); // wrap around the replacement images array. ii++; if (ii >= images.length) ii = 0; pos = s.indexOf("\ufffc", pos + 1); } } } /** * We override getImage() so that the resultant image * is "painted" every time it is needed. This is a * computation vs memory tradeoff. * * @see EncodedImageReference#getImage() */ public Image getImage() { Image paintedImage = drawer.paintImage(); return paintedImage; } /** * Returns the backgroundImage in use * * @return Image - the backgroundImage in use */ public Image getBackgroundImage() { return backgroundImage; } /** * This returns the height of the backgroundImage */ public Extent getHeight() { return new Extent(backgroundImage.getHeight(ImageKit.imageObserver)); } /** * Returns the horizontal alignment of the text relative to the x,y * drawing point. * * @return The horizontal alignment of the text relative to the x,y * drawing point, * one of the following values: *

    *
  • Alignment.LEFT
  • *
  • Alignment.CENTER (the default)
  • *
  • Alignment.RIGHT
  • *
*/ public int getHorizontalAlignment() { return horizontalAlignment; } /** * Returns the alignment of the replacement images within * the AttributedString. This can be * Alignment.TOP, Alignment.CENTER or Alignment.BOTTOM * * @return the replacement image alignment */ public int getReplacementImageAlignment() { return replacementImageAlignment; } /** * Returns the array of replacement images that will be substituted for * the Unicode replacement character 'u'FFFC'. These are replaced in a * round-robin fashion, by circling around the array. This can be a * null array. * * @return the array of replacement images */ public Image[] getReplacementImages() { return replacementImages; } /** * Returns the AttributedString in use. * * @return AttributedString - the AttributedString in use. */ public AttributedString getString() { return attributedString; } /** * Returns the current textAngle. * * @return int - the current textAngle. */ public int getTextAngle() { return textAngle; } /** * Returns the vertical alignment of the text relative to the x,y * drawing point. * * @return The vertical alignment of the text relative to the x,y * drawing point, * one of the following values: *
    *
  • Alignment.TOP
  • *
  • Alignment.CENTER (the default)
  • *
  • Alignment.BOTTOM
  • *
*/ public int getVerticalAlignment() { return verticalAlignment; } /** * This returns the width of the backgroundImage. */ public Extent getWidth() { return new Extent(backgroundImage.getWidth(ImageKit.imageObserver)); } /** * The x position where the etxt will be drawn. * * @return int - The x position where the etxt will be drawn. */ public int getX() { return x; } /** * The y position where the etxt will be drawn. * * @return int - The y position where the etxt will be drawn. */ public int getY() { return y; } /** * Returns true if the image produce is of the best quality. * @return boolean - true if the image produce is of the best quality. */ public boolean isBestQuality() { return bestQuality; } /** * Sets the background image to be used * * @param image - the background image to be used */ public void setBackgroundImage(Image image) { if (image == null) throw new IllegalArgumentException("The background image must be non null"); Image oldImage = backgroundImage; backgroundImage = image; if (!oldImage.equals(image)) { fireImageChange(); } } /** * Sets the background image by loading from an image * file. * * @param imageFileName - the background image file to be used */ public void setBackgroundImage(String imageFileName) { Image image = ImageKit.loadImage(imageFileName); setBackgroundImage(image); } /** * Sets the background image by loading from an image * URL. * * @param imageURL - the background image URL to be used */ public void setBackgroundImage(URL imageURL) { Image image = ImageKit.loadImage(imageURL); setBackgroundImage(image); } /** * Set this to true if you want the best quality images to be drawn. * This setting affects the rendering hints used during image drawing. * * @param b */ public void setBestQuality(boolean b) { boolean oldValue = bestQuality; bestQuality = b; if (oldValue != bestQuality) { fireImageChange(); } } /** * Sets the horizontal alignment of the text relative to the x,y * drawing point. * * @param i - The horizontal alignment of the text relative to the x,y * drawing point, * one of the following values: *
    *
  • Alignment.LEFT
  • *
  • Alignment.CENTER (the default)
  • *
  • Alignment.RIGHT
  • *
*/ public void setHorizontalAlignment(int i) { if (i != Alignment.LEFT && i != Alignment.CENTER && i != Alignment.RIGHT) throw new IllegalArgumentException("setHorizontalAlignment must be one of Alignment.LEFT, Alignment.CENTER, or Alignment.RIGHT"); int oldValue = horizontalAlignment; horizontalAlignment = i; if (oldValue != horizontalAlignment) { fireImageChange(); } } /** * Sets the alignment of the replacement images within * the AttributedString. This can be * Alignment.TOP, Alignment.CENTER or Alignment.BOTTOM * * @param i */ public void setReplacementImageAlignment(int i) { int oldValue = replacementImageAlignment; replacementImageAlignment = i; if (oldValue != replacementImageAlignment) { fireImageChange(); } } /** * Sets the array of replacement images that will be substituted for * the Unicode replacement character u'FFFC'. These are replaced in a * round-robin fashion, for each Unicode replacement character in the * AttribuedtString. This can be a null array and hence no replacement * will be done. * @param images * @see AttributedString for more details */ public void setReplacementImages(Image[] images) { Image[] oldValue = replacementImages; replacementImages = images; if (oldValue != replacementImages) { fireImageChange(); } } /** * This is the AttributedString to be drawn onto the * image. This can be null. * * @param string */ public void setString(AttributedString string) { AttributedString oldValue = this.attributedString; this.attributedString = string; if (oldValue != null && !oldValue.equals(this.attributedString)) { fireImageChange(); } } /** * Sets the text angle in degrees to be used. * * @param i int - the text angle to use., */ public void setTextAngle(int i) { int oldValue = textAngle; textAngle = i; if (oldValue != textAngle) { fireImageChange(); } } /** * Sets the vertical alignment of the text relative to the x,y * drawing point. * * @param i - The vertical alignment of the text relative to the x,y * drawing point, * one of the following values: *
    *
  • Alignment.TOP
  • *
  • Alignment.CENTER (the default)
  • *
  • Alignment.BOTTOM
  • *
*/ public void setVerticalAlignment(int i) { if (i != Alignment.TOP && i != Alignment.CENTER && i != Alignment.BOTTOM) throw new IllegalArgumentException("setHorizontalAlignment must be one of Alignment.TOP, Alignment.CENTER, or Alignment.BOTTOM"); int oldValue = verticalAlignment; verticalAlignment = i; if (oldValue != verticalAlignment) { fireImageChange(); } } /** * Sets the X position to draw the text on the image. If this is set to * Integer.MAX_VALUE then the text will be centered within the * background image. * * @param i */ public void setX(int i) { int oldValue = x; x = i; if (oldValue != x) { fireImageChange(); } } /** * Sets the y position for drawing the text on the image. If this is set to * Integer.MAX_VALUE then the text will be centered within the * background image. * * @param i */ public void setY(int i) { int oldValue = y; y = i; if (oldValue != y) { this.autoCenterAtConstruction = false; fireImageChange(); } } /** * @see java.io.Serializable */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.backgroundImage = ImageKit.readSerializedImage(in); this.attributedString = ImageKit.readSerializedAttributedString(in); int replaceImagesLen = in.readInt(); replacementImages = new Image[replaceImagesLen]; for (int i = 0; i < replaceImagesLen; i++) { replacementImages[i] = ImageKit.readSerializedImage(in); } fireImageChange(); } /** * @see java.io.Serializable */ private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); ImageKit.writeSerializedImage(out, backgroundImage); ImageKit.writeSerializedAttributedString(out, attributedString); int replaceImagesLen = (replacementImages == null ? 0 : replacementImages.length); out.writeInt(replaceImagesLen); for (int i = 0; i < replaceImagesLen; i++) { ImageKit.writeSerializedImage(out, replacementImages[i]); } } /** * Returns an AttributedString with no color or font information * * @param s * @return AttributedString */ public static AttributedString getAttributedString(String s) { return getAttributedString(s, null, null); } /** * Returns an AttributedString with the specified forground color * and font based on nextapp.echo.* visual objects. * * @param s - must not be null * @param foreground - can be null * @param font - can be null * @return AttributedString */ public static AttributedString getAttributedString(String s, Color foreground, Font font) { AttributedString as = new AttributedString(s); // // you can add attributes to a zero lengthed string so dont try! if (s == null || s.length() == 0) return as; if (foreground != null) { as.addAttribute(TextAttribute.FOREGROUND, ColorKit.makeAwtColor(foreground, null)); } if (font != null) { as.addAttribute(TextAttribute.FONT, FontKit.makeAwtFont(font, null)); } return as; } /** * Returns the scaling option in effect. * @return the scaling option in effect. */ public int getScaleOption() { return scaleOption; } /** * Sets the scaling option that will be used to draw the background * image. If the text of the TextImageReference is to large for the * background image, then the image will be scaled up to the requireed * size, using this scaling option. * * @param scalingOption the new scaling option */ public void setScaleOption(int scalingOption) { int oldValue = this.scaleOption; this.scaleOption = scalingOption; if ( oldValue != scalingOption) { fireImageChange(); } } /** * Returns the Insets in use * @return the Insets in use */ public Insets getInsets() { return insets; } /** * Sets the Insets between the text and the background image outer edge. * @param insets the Insets between the text and the background image outer edge. */ public void setInsets(Insets insets) { Insets oldValue = this.insets; this.insets = insets; if (oldValue != insets) { fireImageChange(); } } /** * Called when the image properties have changed and the background * image might have to be scaled. */ private void fireImageChange() { drawer.reconstructBackgroundImage(this.x,this.y); update(); } /** * @see java.lang.Object#toString() */ public String toString() { StringBuffer sb = new StringBuffer(); if (this.attributedString != null) { sb.append(new ACI(this.attributedString.getIterator()).toString()); sb.append(" origW:"); sb.append(origImageW); sb.append(" origH:"); sb.append(origImageH); return sb.toString(); } else { return "null"; } } /** * TextImageDrawer holds all the drawing code, just * to help us organise this java source file. */ private class TextImageDrawer implements Serializable { /** * This creates a "tall" attributed string version of the * specified string. It does this by replacing all * standard ASCI characters with M and y so that any * scaling will occur as if the string contained a * M and a y. The reason this is important is that * images heights will be consistent accross different * text in the images. */ private AttributedString createTallVersion(AttributedString as) { if (as == null) return as; AttributedCharacterIterator cit; cit = as.getIterator(); for (char c = cit.first(); c != CharacterIterator.DONE; c = cit.next()) { if (c == 'M' || c == 'y') return as; } int count = 0; StringBuffer buf = new StringBuffer(); cit = as.getIterator(); for (char c = cit.first(); c != CharacterIterator.DONE; c = cit.next()) { if (Character.isLetter(c)) { c = (count++ % 2 == 0 ? 'y' : 'M'); } buf.append(c); } AttributedString newAS = new AttributedString(buf.toString()); count = 0; cit = as.getIterator(); for (char c = cit.first(); c != CharacterIterator.DONE; c = cit.next()) { Map attributes = cit.getAttributes(); newAS.addAttributes(attributes,count,count+1); count++; } return newAS; } /** * Calculates the height of a text layout */ private double calcTextLayoutHeight(TextLayout textLayout) { double height = 0; height += textLayout.getAscent(); height += textLayout.getDescent(); height += textLayout.getLeading(); return height; } /** * Gets the bounds of the image with respect to the Insets */ Rectangle2D getPreferredBoundsRect(int imageW, int imageH) { int insetL = (getInsets() == null ? 0 : getInsets().getLeft().getValue()); int insetT = (getInsets() == null ? 0 : getInsets().getTop().getValue()); int insetR = (getInsets() == null ? 0 : getInsets().getRight().getValue()); int insetB = (getInsets() == null ? 0 : getInsets().getBottom().getValue()); Rectangle2D preferredTB = new Rectangle2D.Float(insetL,insetT,imageW-insetR-insetL,imageH-insetB-insetT); return preferredTB; } /** * Returns the total dimensions of a list of TextLayouts. * * @return Dimension - the text dimensions */ private Dimension getTextDimensions(TextLayout[] textLayouts) { double height = 0; double width = 0; double w = 0; for (int i = 0; i < textLayouts.length; i++) { w = textLayouts[i].getBounds().getWidth(); if (w > width) width = w; height += calcTextLayoutHeight(textLayouts[i]); } return new Dimension((int) width, (int) height); } /** * Paints a small cross hair at a point with a little marker to to * indicate which way postive x and postiive y are. */ void _paintCrossHairs(Graphics2D g, float x1, float y1, java.awt.Color clr) { int x = (int) x1; int y = (int) y1; int delta = 15; g.setPaint(clr); g.drawLine(x, y, x, y - delta); g.drawLine(x, y, x + delta, y); g.drawLine(x, y, x, y + delta); g.drawLine(x, y, x - delta, y); g.drawLine(x, y, x + (delta / 2), y + (delta / 2)); } /** * Draws a text layout around a point, aligning the text box * around that point according to the given alignment values and * rotating the text are the given angle. *

* This returns a bounding Rectangle of where to text was painted. */ private Rectangle2D paintRotatedTextCentered(Graphics2D g, TextLayout tl, float rx, float ry, int textAngle, int hTextAlignment, int vTextAlignment) { Rectangle rect = tl.getBounds().getBounds(); // debug - help us visually to see the {x,y} position //_paintCrossHairs(g, rx, ry, java.awt.Color.RED); // save the current transform AffineTransform atPrev = g.getTransform(); // text is draw from the base line so adjust to its // logical bounding box, which is what we want centered float tx = rx; float ty = ry; if (hTextAlignment == Alignment.CENTER) tx = rx - (rect.width / 2); if (hTextAlignment == Alignment.RIGHT) tx = rx - (rect.width); if (vTextAlignment == Alignment.CENTER) ty = ry + (rect.height / 2) - (tl.getDescent() / 2); if (vTextAlignment == Alignment.TOP) ty = ry + (rect.height - tl.getDescent()); // and make a new one around of point of interest AffineTransform at = new AffineTransform(); at.setToIdentity(); at.rotate(Math.toRadians(textAngle), rx, ry); at.translate(tx, ty); g.setTransform(at); float dx = 0; float dy = 0; tl.draw(g, dx, dy); // debug - this draws a box around the text // helps with visualising centering //g.setColor(java.awt.Color.black); //g.draw(rect); g.setTransform(atPrev); Rectangle2D bounds = new Rectangle2D.Double(); bounds.setRect(rect.getX()+tx, rect.getY()+ty, rect.getWidth(), rect.getHeight()); return bounds; } /** Called to contruct the actual background image and do any stretching as necessary */ private void reconstructBackgroundImage(int inX, int inY) { if (!ImageKit.waitForImage(backgroundImage)) throw new IllegalStateException("The backgroundImage could not be scaled"); int imageW = backgroundImage.getWidth(ImageKit.imageObserver); int imageH = backgroundImage.getHeight(ImageKit.imageObserver); // // work out if the painted text will be larger then the // background image plus its insets. If so we might have to scale the // background image to fit. We "draw" from the // if (getScaleOption() != SCALE_NONE) { BufferedImage bufImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bufImage.createGraphics(); int insetL = (getInsets() == null ? 0 : getInsets().getLeft().getValue()); int insetT = (getInsets() == null ? 0 : getInsets().getTop().getValue()); int insetR = (getInsets() == null ? 0 : getInsets().getRight().getValue()); int insetB = (getInsets() == null ? 0 : getInsets().getBottom().getValue()); Rectangle2D preferredTB = new Rectangle2D.Float(insetL,insetT,imageW-insetR-insetL,imageH-insetB-insetT); int paintX = (autoCenterAtConstruction || inX == Integer.MAX_VALUE) ? imageW / 2 : inX; int paintY = (autoCenterAtConstruction || inY == Integer.MAX_VALUE) ? imageH / 2 : inY; AttributedString tallAS = createTallVersion(attributedString); Rectangle2D actualTB = paintText(g, paintX, paintY, tallAS); // // if the painted text does not fit in the actual background image arae // then lets scale it if (! preferredTB.contains(actualTB)) { int useScalingOption = getScaleOption(); int newW = (int) (actualTB.getWidth()+insetL + insetR); int newH = (int) (actualTB.getHeight()+insetT + insetB); // // and scale it newW = Math.max(imageW,newW); newH = Math.max(imageH,newH); int spliceX = (autoCenterAtConstruction || inX == Integer.MAX_VALUE) ? imageW / 2 : inX; int spliceY = (autoCenterAtConstruction || inY == Integer.MAX_VALUE) ? imageH / 2 : inY; if (useScalingOption == SCALE_SPLICE_H_THEN_V) { backgroundImage = ImageKit.enlargeImageSpliceHoriz(backgroundImage,spliceY,newH); backgroundImage = ImageKit.enlargeImageSpliceVert(backgroundImage,spliceX,newW); } else if (useScalingOption == SCALE_SPLICE_V_THEN_H) { backgroundImage = ImageKit.enlargeImageSpliceVert(backgroundImage,spliceX,newW); backgroundImage = ImageKit.enlargeImageSpliceHoriz(backgroundImage,spliceY,newH); } else { backgroundImage = backgroundImage.getScaledInstance(newW,newH,useScalingOption); } imageW = newW; imageH = newH; } } x = (autoCenterAtConstruction || inX == Integer.MAX_VALUE) ? imageW / 2 : inX; y = (autoCenterAtConstruction || inY == Integer.MAX_VALUE) ? imageH / 2 : inY; } /** Called when the image may need to be painted */ private Image paintImage() { ImageKit.waitForImage(backgroundImage); int imageW = backgroundImage.getWidth(ImageKit.imageObserver); int imageH = backgroundImage.getHeight(ImageKit.imageObserver); BufferedImage bufImage = new BufferedImage(imageW, imageH, BufferedImage.TYPE_INT_ARGB); Graphics2D g = bufImage.createGraphics(); if (bestQuality) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); } else { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); } /// draw the background image on to the graphics area! g.drawImage(backgroundImage, 0, 0, imageW, imageH, ImageKit.imageObserver); // debug our drawing surface //g.setColor(java.awt.Color.ORANGE); //g.drawRect(0, 0, imageW-1, imageH-1); // call the protected paintText method to allow // others to do the text painting paintText(g, x, y, attributedString); // debug - draw our preferred text boundaries //g.setColor(java.awt.Color.green); //g.draw(getPreferredBoundsRect(imageW,imageH)); g.dispose(); return bufImage; } private Rectangle2D paintText(Graphics2D g, int x, int y, AttributedString attributedString) { if (attributedString == null) return new Rectangle(); ACI cit = new ACI(attributedString.getIterator()); int start = cit.getBeginIndex(); int limit = cit.getEndIndex(); if (start == limit) return new Rectangle(); FontRenderContext frc = g.getFontRenderContext(); // insert our replacement images replaceAttributedImages(g, frc, attributedString); TextLayout[] textLayouts = getTextLayouts(g,attributedString); Dimension textDimensions = getTextDimensions(textLayouts); float textHeight = (float) textDimensions.getHeight(); float textWidth = (float) textDimensions.getWidth(); if (textWidth <= 0 || textHeight <= 0) return new Rectangle(); float fx = x; float fy = y; float fydraw = fy; // debug - help us visually to see the {x,y} position //_paintCrossHairs(g, fx, fy, java.awt.Color.RED); // // if we are centered vertically, then we move up to // 1/2 the height of the total logical text box. // if (verticalAlignment == Alignment.CENTER) fy = fy - (textHeight / 2.0f); Rectangle2D textBounds = null; for (int i = 0; i < textLayouts.length; i++) { fydraw = fy; // // again if we are centered vertically, we need to move // down 1/2 the height of the single text box if (verticalAlignment == Alignment.CENTER) fydraw += calcTextLayoutHeight(textLayouts[i]) / 2.0; Rectangle2D singleTextBounds = paintRotatedTextCentered(g, textLayouts[i], fx, fydraw, textAngle, horizontalAlignment, verticalAlignment); if (textBounds == null) textBounds = singleTextBounds; else textBounds = textBounds.createUnion(singleTextBounds); fy += calcTextLayoutHeight(textLayouts[i]); } // debug - help us visually to see the text boundaries //g.setColor(java.awt.Color.orange); //g.draw(textBounds); return textBounds; } }; /** * An AttributedCharacterIteraror delegate. Used to make debugging easier * in Eclipse! */ private class ACI implements Serializable { AttributedCharacterIterator cit; private ACI(AttributedCharacterIterator cit) { this.cit = cit; } /** * @see java.lang.Object#toString() */ public String toString() { StringBuffer buf = new StringBuffer(); for (char c = cit.first(); c != CharacterIterator.DONE; c = cit.next()) { buf.append(c); } return buf.toString(); } public AttributedCharacterIterator getIterator() { return cit; } protected Object clone() throws CloneNotSupportedException { return cit.clone(); } public char current() { return cit.current(); } public boolean equals(Object obj) { return cit.equals(obj); } public char first() { return cit.first(); } public int getBeginIndex() { return cit.getBeginIndex(); } public int getEndIndex() { return cit.getEndIndex(); } public int getIndex() { return cit.getIndex(); } public char last() { return cit.last(); } public char next() { return cit.next(); } public char previous() { return cit.previous(); } public char setIndex(int position) { return cit.setIndex(position); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy