    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}