001/* ===========================================================
002 * Orson Charts : a 3D chart library for the Java(tm) platform
003 * ===========================================================
004 * 
005 * (C)opyright 2013-2016, by Object Refinery Limited.  All rights reserved.
006 * 
007 * http://www.object-refinery.com/orsoncharts/index.html
008 * 
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as published by
011 * the Free Software Foundation, either version 3 of the License, or
012 * (at your option) any later version.
013 *
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
021 * 
022 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
023 * Other names may be trademarks of their respective owners.]
024 * 
025 * If you do not wish to be bound by the terms of the GPL, an alternative
026 * commercial license can be purchased.  For details, please see visit the
027 * Orson Charts home page:
028 * 
029 * http://www.object-refinery.com/orsoncharts/index.html
030 * 
031 */
032
033package com.orsoncharts.util;
034
035import java.awt.geom.Dimension2D;
036import java.awt.geom.Point2D;
037import java.awt.geom.Rectangle2D;
038import java.io.Serializable;
039
040/**
041 * A specification for the alignment and fitting of one rectangle (the source 
042 * rectangle) with reference to another (the target rectangle).  Instances of
043 * this class are immutable.
044 * <br><br>
045 * One application for this is to specify how the background image for a chart 
046 * should be aligned and scaled.
047 * <br><br>
048 * NOTE: This class is serializable, but the serialization format is subject 
049 * to change in future releases and should not be relied upon for persisting 
050 * instances of this class.
051 */
052@SuppressWarnings("serial")
053public class Fit2D implements Serializable {
054
055    /** 
056     * Aligns a source rectangle to the center of a target rectangle, without
057     * resizing it.
058     * 
059     * @since 1.1
060     */
061    public static final Fit2D CENTER_NO_SCALING = new Fit2D(Anchor2D.CENTER, 
062            Scale2D.NONE);
063 
064    /** 
065     * Fits a source rectangle to the top left of a target rectangle, without
066     * resizing it.
067     * 
068     * @since 1.1
069     */
070    public static final Fit2D TOP_LEFT_NO_SCALING 
071            = new Fit2D(Anchor2D.TOP_LEFT, Scale2D.NONE);
072
073    /** 
074     * Fits a source rectangle to the top center of a target rectangle, without
075     * resizing it.
076     * 
077     * @since 1.1
078     */
079    public static final Fit2D TOP_CENTER_NO_SCALING 
080            = new Fit2D(Anchor2D.TOP_CENTER, Scale2D.NONE);
081
082    /** 
083     * Fits a source rectangle to the top right of a target rectangle, without
084     * resizing it.
085     * 
086     * @since 1.1
087     */
088    public static final Fit2D TOP_RIGHT_NO_SCALING 
089            = new Fit2D(Anchor2D.TOP_RIGHT, Scale2D.NONE);
090
091    /** 
092     * Fits a source rectangle to the center left of a target rectangle, 
093     * without resizing it.
094     * 
095     * @since 1.1
096     */
097    public static final Fit2D CENTER_LEFT_NO_SCALING 
098            = new Fit2D(Anchor2D.CENTER_LEFT, Scale2D.NONE);
099
100    /** 
101     * Fits a source rectangle to the center right of a target rectangle, 
102     * without resizing it.
103     * 
104     * @since 1.1
105     */
106    public static final Fit2D CENTER_RIGHT_NO_SCALING 
107            = new Fit2D(Anchor2D.CENTER_RIGHT, Scale2D.NONE);
108
109    /** 
110     * Fits a source rectangle to the bottom left of a target rectangle, 
111     * without resizing it.
112     * 
113     * @since 1.1
114     */
115    public static final Fit2D BOTTOM_LEFT_NO_SCALING 
116            = new Fit2D(Anchor2D.BOTTOM_LEFT, Scale2D.NONE);
117
118    /** 
119     * Fits a source rectangle to the bottom center of a target rectangle, 
120     * without resizing it.
121     * 
122     * @since 1.1
123     */
124    public static final Fit2D BOTTOM_CENTER_NO_SCALING 
125            = new Fit2D(Anchor2D.BOTTOM_CENTER, Scale2D.NONE);
126
127    /** 
128     * Fits a source rectangle to the bottom right of a target rectangle, 
129     * without resizing it.
130     * 
131     * @since 1.1
132     */
133    public static final Fit2D BOTTOM_RIGHT_NO_SCALING 
134            = new Fit2D(Anchor2D.BOTTOM_RIGHT, Scale2D.NONE);
135    
136    /**
137     * Returns a fitter for the specified reference point.
138     * 
139     * @param refPt  the reference point ({@code null} not permitted).
140     * 
141     * @return A fitter.
142     * 
143     * @since 1.1
144     */
145    public static Fit2D getNoScalingFitter(RefPt2D refPt) {
146        switch (refPt) {
147            case TOP_LEFT : return Fit2D.TOP_LEFT_NO_SCALING;
148            case TOP_CENTER : return Fit2D.TOP_CENTER_NO_SCALING;
149            case TOP_RIGHT : return Fit2D.TOP_RIGHT_NO_SCALING;
150            case CENTER_LEFT : return Fit2D.CENTER_LEFT_NO_SCALING;
151            case CENTER : return Fit2D.CENTER_NO_SCALING;
152            case CENTER_RIGHT : return Fit2D.CENTER_RIGHT_NO_SCALING;
153            case BOTTOM_LEFT : return Fit2D.BOTTOM_LEFT_NO_SCALING;
154            case BOTTOM_CENTER : return Fit2D.BOTTOM_CENTER_NO_SCALING;
155            case BOTTOM_RIGHT : return Fit2D.BOTTOM_RIGHT_NO_SCALING;
156        }
157        throw new IllegalStateException("RefPt2D not recognised : " + refPt); 
158    }
159    
160    /**
161     * Scale the source rectangle to fit the target rectangle.
162     * 
163     * @since 1.1
164     */
165    public static final Fit2D SCALE_TO_FIT_TARGET 
166            = new Fit2D(Anchor2D.CENTER, Scale2D.SCALE_BOTH);
167
168    /** The anchor point for alignment. */
169    private Anchor2D anchor;
170    
171    /** The scaling to apply. */
172    private Scale2D scale;
173    
174    /**
175     * Creates a new instance.
176     * 
177     * @param anchor  the anchor point ({@code null} not permitted).
178     * @param scale  the scaling ({@code null} not permitted).
179     */
180    public Fit2D(Anchor2D anchor, Scale2D scale) {
181        ArgChecks.nullNotPermitted(anchor, "anchor");
182        ArgChecks.nullNotPermitted(scale, "scale");
183        this.anchor = anchor;
184        this.scale = scale;
185    }
186    
187    /**
188     * Returns the anchor.
189     * 
190     * @return The anchor (never {@code null}).
191     * 
192     * @since 1.1
193     */
194    public Anchor2D getAnchor() {
195        return this.anchor;
196    }
197    
198    /**
199     * Returns the scaling.
200     * 
201     * @return The scaling (never {@code null}).
202     * 
203     * @since 1.1
204     */
205    public Scale2D getScale() {
206        return this.scale;
207    }
208    
209    /**
210     * Fits a rectangle of the specified dimension to the target rectangle,
211     * aligning and scaling according to the attributes of this instance.
212     * 
213     * @param srcDim  the dimensions of the source rectangle ({@code null}
214     *     not permitted).
215     * @param target  the target rectangle ({@code null} not permitted).
216     * 
217     * @return The bounds of the fitted rectangle (never {@code null}). 
218     */
219    public Rectangle2D fit(Dimension2D srcDim, Rectangle2D target) {
220        Rectangle2D result = new Rectangle2D.Double();
221        if (this.scale == Scale2D.SCALE_BOTH) {
222            result.setFrame(target);
223            return result;
224        }
225        double width = srcDim.getWidth();
226        if (this.scale == Scale2D.SCALE_HORIZONTAL) {
227            width = target.getWidth();
228            if (!this.anchor.getRefPt().isHorizontalCenter()) {
229                width -= 2 * this.anchor.getOffset().getDX();
230            }
231        }
232        double height = srcDim.getHeight();
233        if (this.scale == Scale2D.SCALE_VERTICAL) {
234            height = target.getHeight();
235            if (!this.anchor.getRefPt().isVerticalCenter()) {
236                height -= 2 * this.anchor.getOffset().getDY();
237            }
238        }
239        Point2D pt = this.anchor.getAnchorPoint(target);
240        double x = Double.NaN; 
241        if (this.anchor.getRefPt().isLeft()) {
242            x = pt.getX();
243        } else if (this.anchor.getRefPt().isHorizontalCenter()) {
244            x = target.getCenterX() - width / 2;
245        } else if (this.anchor.getRefPt().isRight()) {
246            x = pt.getX() - width;
247        }
248        double y = Double.NaN;
249        if (this.anchor.getRefPt().isTop()) {
250            y = pt.getY();
251        } else if (this.anchor.getRefPt().isVerticalCenter()) {
252            y = target.getCenterY() - height / 2;
253        } else if (this.anchor.getRefPt().isBottom()) {
254            y = pt.getY() - height;
255        }
256        result.setRect(x, y, width, height);
257        return result;
258    }
259    
260    /**
261     * Tests this instance for equality with an arbitrary object.
262     * 
263     * @param obj  the object ({@code null} permitted).
264     * 
265     * @return A boolean. 
266     */
267    @Override
268    public boolean equals(Object obj) {
269        if (obj == this) {
270            return true;
271        }
272        if (!(obj instanceof Fit2D)) {
273            return false;
274        }
275        Fit2D that = (Fit2D) obj;
276        if (!this.anchor.equals(that.anchor)) {
277            return false;
278        }
279        if (!this.scale.equals(that.scale)) {
280            return false;
281        }
282        return true;
283    }
284}