001/* ===========================================================
002 * Orson Charts : a 3D chart library for the Java(tm) platform
003 * ===========================================================
004 * 
005 * (C)opyright 2013-2022, by David Gilbert.  All rights reserved.
006 * 
007 * https://github.com/jfree/orson-charts
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 org.jfree.chart3d.graphics2d;
034
035import org.jfree.chart3d.internal.Args;
036import java.awt.geom.Dimension2D;
037import java.awt.geom.Point2D;
038import java.awt.geom.Rectangle2D;
039import java.io.Serializable;
040
041/**
042 * A specification for the alignment and fitting of one rectangle (the source 
043 * rectangle) with reference to another (the target rectangle).  Instances of
044 * this class are immutable.
045 * <br><br>
046 * One application for this is to specify how the background image for a chart 
047 * should be aligned and scaled.
048 * <br><br>
049 * NOTE: This class is serializable, but the serialization format is subject 
050 * to change in future releases and should not be relied upon for persisting 
051 * instances of this class.
052 */
053@SuppressWarnings("serial")
054public class Fit2D implements Serializable {
055
056    /** 
057     * Aligns a source rectangle to the center of a target rectangle, without
058     * resizing it.
059     * 
060     * @since 1.1
061     */
062    public static final Fit2D CENTER_NO_SCALING = new Fit2D(Anchor2D.CENTER, 
063            Scale2D.NONE);
064 
065    /** 
066     * Fits a source rectangle to the top left of a target rectangle, without
067     * resizing it.
068     * 
069     * @since 1.1
070     */
071    public static final Fit2D TOP_LEFT_NO_SCALING 
072            = new Fit2D(Anchor2D.TOP_LEFT, Scale2D.NONE);
073
074    /** 
075     * Fits a source rectangle to the top center of a target rectangle, without
076     * resizing it.
077     * 
078     * @since 1.1
079     */
080    public static final Fit2D TOP_CENTER_NO_SCALING 
081            = new Fit2D(Anchor2D.TOP_CENTER, Scale2D.NONE);
082
083    /** 
084     * Fits a source rectangle to the top right of a target rectangle, without
085     * resizing it.
086     * 
087     * @since 1.1
088     */
089    public static final Fit2D TOP_RIGHT_NO_SCALING 
090            = new Fit2D(Anchor2D.TOP_RIGHT, Scale2D.NONE);
091
092    /** 
093     * Fits a source rectangle to the center left of a target rectangle, 
094     * without resizing it.
095     * 
096     * @since 1.1
097     */
098    public static final Fit2D CENTER_LEFT_NO_SCALING 
099            = new Fit2D(Anchor2D.CENTER_LEFT, Scale2D.NONE);
100
101    /** 
102     * Fits a source rectangle to the center right of a target rectangle, 
103     * without resizing it.
104     * 
105     * @since 1.1
106     */
107    public static final Fit2D CENTER_RIGHT_NO_SCALING 
108            = new Fit2D(Anchor2D.CENTER_RIGHT, Scale2D.NONE);
109
110    /** 
111     * Fits a source rectangle to the bottom left of a target rectangle, 
112     * without resizing it.
113     * 
114     * @since 1.1
115     */
116    public static final Fit2D BOTTOM_LEFT_NO_SCALING 
117            = new Fit2D(Anchor2D.BOTTOM_LEFT, Scale2D.NONE);
118
119    /** 
120     * Fits a source rectangle to the bottom center of a target rectangle, 
121     * without resizing it.
122     * 
123     * @since 1.1
124     */
125    public static final Fit2D BOTTOM_CENTER_NO_SCALING 
126            = new Fit2D(Anchor2D.BOTTOM_CENTER, Scale2D.NONE);
127
128    /** 
129     * Fits a source rectangle to the bottom right of a target rectangle, 
130     * without resizing it.
131     * 
132     * @since 1.1
133     */
134    public static final Fit2D BOTTOM_RIGHT_NO_SCALING 
135            = new Fit2D(Anchor2D.BOTTOM_RIGHT, Scale2D.NONE);
136    
137    /**
138     * Returns a fitter for the specified reference point.
139     * 
140     * @param refPt  the reference point ({@code null} not permitted).
141     * 
142     * @return A fitter.
143     * 
144     * @since 1.1
145     */
146    public static Fit2D getNoScalingFitter(RefPt2D refPt) {
147        switch (refPt) {
148            case TOP_LEFT : return Fit2D.TOP_LEFT_NO_SCALING;
149            case TOP_CENTER : return Fit2D.TOP_CENTER_NO_SCALING;
150            case TOP_RIGHT : return Fit2D.TOP_RIGHT_NO_SCALING;
151            case CENTER_LEFT : return Fit2D.CENTER_LEFT_NO_SCALING;
152            case CENTER : return Fit2D.CENTER_NO_SCALING;
153            case CENTER_RIGHT : return Fit2D.CENTER_RIGHT_NO_SCALING;
154            case BOTTOM_LEFT : return Fit2D.BOTTOM_LEFT_NO_SCALING;
155            case BOTTOM_CENTER : return Fit2D.BOTTOM_CENTER_NO_SCALING;
156            case BOTTOM_RIGHT : return Fit2D.BOTTOM_RIGHT_NO_SCALING;
157        }
158        throw new IllegalStateException("RefPt2D not recognised : " + refPt); 
159    }
160    
161    /**
162     * Scale the source rectangle to fit the target rectangle.
163     * 
164     * @since 1.1
165     */
166    public static final Fit2D SCALE_TO_FIT_TARGET 
167            = new Fit2D(Anchor2D.CENTER, Scale2D.SCALE_BOTH);
168
169    /** The anchor point for alignment. */
170    private final Anchor2D anchor;
171    
172    /** The scaling to apply. */
173    private final Scale2D scale;
174    
175    /**
176     * Creates a new instance.
177     * 
178     * @param anchor  the anchor point ({@code null} not permitted).
179     * @param scale  the scaling ({@code null} not permitted).
180     */
181    public Fit2D(Anchor2D anchor, Scale2D scale) {
182        Args.nullNotPermitted(anchor, "anchor");
183        Args.nullNotPermitted(scale, "scale");
184        this.anchor = anchor;
185        this.scale = scale;
186    }
187    
188    /**
189     * Returns the anchor.
190     * 
191     * @return The anchor (never {@code null}).
192     * 
193     * @since 1.1
194     */
195    public Anchor2D getAnchor() {
196        return this.anchor;
197    }
198    
199    /**
200     * Returns the scaling.
201     * 
202     * @return The scaling (never {@code null}).
203     * 
204     * @since 1.1
205     */
206    public Scale2D getScale() {
207        return this.scale;
208    }
209    
210    /**
211     * Fits a rectangle of the specified dimension to the target rectangle,
212     * aligning and scaling according to the attributes of this instance.
213     * 
214     * @param srcDim  the dimensions of the source rectangle ({@code null}
215     *     not permitted).
216     * @param target  the target rectangle ({@code null} not permitted).
217     * 
218     * @return The bounds of the fitted rectangle (never {@code null}). 
219     */
220    public Rectangle2D fit(Dimension2D srcDim, Rectangle2D target) {
221        Rectangle2D result = new Rectangle2D.Double();
222        if (this.scale == Scale2D.SCALE_BOTH) {
223            result.setFrame(target);
224            return result;
225        }
226        double width = srcDim.getWidth();
227        if (this.scale == Scale2D.SCALE_HORIZONTAL) {
228            width = target.getWidth();
229            if (!this.anchor.getRefPt().isHorizontalCenter()) {
230                width -= 2 * this.anchor.getOffset().getDX();
231            }
232        }
233        double height = srcDim.getHeight();
234        if (this.scale == Scale2D.SCALE_VERTICAL) {
235            height = target.getHeight();
236            if (!this.anchor.getRefPt().isVerticalCenter()) {
237                height -= 2 * this.anchor.getOffset().getDY();
238            }
239        }
240        Point2D pt = this.anchor.getAnchorPoint(target);
241        double x = Double.NaN; 
242        if (this.anchor.getRefPt().isLeft()) {
243            x = pt.getX();
244        } else if (this.anchor.getRefPt().isHorizontalCenter()) {
245            x = target.getCenterX() - width / 2;
246        } else if (this.anchor.getRefPt().isRight()) {
247            x = pt.getX() - width;
248        }
249        double y = Double.NaN;
250        if (this.anchor.getRefPt().isTop()) {
251            y = pt.getY();
252        } else if (this.anchor.getRefPt().isVerticalCenter()) {
253            y = target.getCenterY() - height / 2;
254        } else if (this.anchor.getRefPt().isBottom()) {
255            y = pt.getY() - height;
256        }
257        result.setRect(x, y, width, height);
258        return result;
259    }
260    
261    /**
262     * Tests this instance for equality with an arbitrary object.
263     * 
264     * @param obj  the object ({@code null} permitted).
265     * 
266     * @return A boolean. 
267     */
268    @Override
269    public boolean equals(Object obj) {
270        if (obj == this) {
271            return true;
272        }
273        if (!(obj instanceof Fit2D)) {
274            return false;
275        }
276        Fit2D that = (Fit2D) obj;
277        if (!this.anchor.equals(that.anchor)) {
278            return false;
279        }
280        if (!this.scale.equals(that.scale)) {
281            return false;
282        }
283        return true;
284    }
285}