001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 */
028
029package org.jfree.chart.api;
030
031import java.awt.geom.Rectangle2D;
032import java.io.Serializable;
033import org.jfree.chart.internal.Args;
034
035/**
036 * Represents the insets for a rectangle, specified in absolute or relative 
037 * terms. This class is immutable.
038 */
039public class RectangleInsets implements Serializable {
040
041    /** For serialization. */
042    private static final long serialVersionUID = 1902273207559319996L;
043    
044    /**
045     * A useful constant representing zero insets.
046     */
047    public static final RectangleInsets ZERO_INSETS = new RectangleInsets(
048        UnitType.ABSOLUTE, 0.0, 0.0, 0.0, 0.0);
049    
050    /** Absolute or relative units. */
051    private UnitType unitType;
052    
053    /** The top insets. */
054    private double top;
055    
056    /** The left insets. */
057    private double left;
058    
059    /** The bottom insets. */
060    private double bottom;
061    
062    /** The right insets. */
063    private double right;
064    
065    /**
066     * Creates a new instance with all insets initialised to {@code 1.0}.
067     */
068    public RectangleInsets() {
069        this(1.0, 1.0, 1.0, 1.0);
070    }
071    
072    /**
073     * Creates a new instance with the specified insets (as 'absolute' units).
074     * 
075     * @param top  the top insets.
076     * @param left  the left insets.
077     * @param bottom  the bottom insets.
078     * @param right  the right insets.
079     */
080    public RectangleInsets(double top, double left, double bottom, 
081            double right) {
082        this(UnitType.ABSOLUTE, top, left, bottom, right);   
083    }
084    
085    /**
086     * Creates a new instance.
087     * 
088     * @param unitType  absolute or relative units ({@code null} not permitted).
089     * @param top  the top insets.
090     * @param left  the left insets.
091     * @param bottom  the bottom insets.
092     * @param right  the right insets.
093     */
094    public RectangleInsets(UnitType unitType, double top, double left, 
095            double bottom, double right) {
096        Args.nullNotPermitted(unitType, "unitType");
097        this.unitType = unitType;
098        this.top = top;
099        this.bottom = bottom;
100        this.left = left;
101        this.right = right;
102    }
103    
104    /**
105     * Returns the unit type (absolute or relative).  This specifies whether 
106     * the insets are measured as Java2D units or percentages.
107     * 
108     * @return The unit type (never {@code null}).
109     */
110    public UnitType getUnitType() {
111        return this.unitType;
112    }
113  
114    /**
115     * Returns the top insets.
116     * 
117     * @return The top insets.
118     */
119    public double getTop() {
120        return this.top;
121    }
122    
123    /**
124     * Returns the bottom insets.
125     * 
126     * @return The bottom insets.
127     */
128    public double getBottom() {
129        return this.bottom;
130    }
131    
132    /**
133     * Returns the left insets.
134     * 
135     * @return The left insets.
136     */
137    public double getLeft() {
138        return this.left;
139    }
140    
141    /**
142     * Returns the right insets.
143     * 
144     * @return The right insets.
145     */
146    public double getRight() {
147        return this.right;
148    }
149    
150    /**
151     * Tests this instance for equality with an arbitrary object.
152     * 
153     * @param obj  the object ({@code null} permitted).
154     * 
155     * @return A boolean.
156     */
157    @Override
158    public boolean equals(Object obj) {
159        if (obj == this) {
160            return true;   
161        }
162        if (!(obj instanceof RectangleInsets)) {
163                return false;
164        }
165        final RectangleInsets that = (RectangleInsets) obj;
166        if (that.unitType != this.unitType) {
167            return false;   
168        }
169        if (this.left != that.left) {
170            return false;   
171        }
172        if (this.right != that.right) {
173            return false;   
174        }
175        if (this.top != that.top) {
176            return false;   
177        }
178        if (this.bottom != that.bottom) {
179            return false;   
180        }
181        return true;   
182    }
183
184    /**
185     * Returns a hash code for the object.
186     * 
187     * @return A hash code.
188     */
189    @Override
190    public int hashCode() {
191        int result;
192        long temp;
193        result = (this.unitType != null ? this.unitType.hashCode() : 0);
194        temp = this.top != +0.0d ? Double.doubleToLongBits(this.top) : 0L;
195        result = 29 * result + (int) (temp ^ (temp >>> 32));
196        temp = this.bottom != +0.0d ? Double.doubleToLongBits(this.bottom) : 0L;
197        result = 29 * result + (int) (temp ^ (temp >>> 32));
198        temp = this.left != +0.0d ? Double.doubleToLongBits(this.left) : 0L;
199        result = 29 * result + (int) (temp ^ (temp >>> 32));
200        temp = this.right != +0.0d ? Double.doubleToLongBits(this.right) : 0L;
201        result = 29 * result + (int) (temp ^ (temp >>> 32));
202        return result;
203    }
204
205    /**
206     * Returns a textual representation of this instance, useful for debugging
207     * purposes.
208     * 
209     * @return A string representing this instance.
210     */
211    @Override
212    public String toString() {
213        return "RectangleInsets[t=" + this.top + ",l=" + this.left
214                + ",b=" + this.bottom + ",r=" + this.right + "]";
215    }
216    
217    /**
218     * Creates an adjusted rectangle using the supplied rectangle, the insets
219     * specified by this instance, and the horizontal and vertical 
220     * adjustment types.
221     * 
222     * @param base  the base rectangle ({@code null} not permitted).
223     * @param horizontal  the horizontal adjustment type ({@code null} not
224     *                    permitted).
225     * @param vertical  the vertical adjustment type ({@code null} not 
226     *                  permitted).
227     * 
228     * @return The inset rectangle.
229     */
230    public Rectangle2D createAdjustedRectangle(Rectangle2D base,
231            LengthAdjustmentType horizontal, LengthAdjustmentType vertical) {
232        Args.nullNotPermitted(base, "base");
233        double x = base.getX();
234        double y = base.getY();
235        double w = base.getWidth();
236        double h = base.getHeight();
237        if (horizontal == LengthAdjustmentType.EXPAND) {
238            final double leftOutset = calculateLeftOutset(w);
239            x = x - leftOutset;
240            w = w + leftOutset + calculateRightOutset(w);
241        }
242        else if (horizontal == LengthAdjustmentType.CONTRACT) {
243            final double leftMargin = calculateLeftInset(w);
244            x = x + leftMargin;
245            w = w - leftMargin - calculateRightInset(w);
246        }
247        if (vertical == LengthAdjustmentType.EXPAND) {
248            final double topMargin = calculateTopOutset(h);
249            y = y - topMargin;
250            h = h + topMargin + calculateBottomOutset(h);
251        }
252        else if (vertical == LengthAdjustmentType.CONTRACT) {
253            final double topMargin = calculateTopInset(h);
254            y = y + topMargin;
255            h = h - topMargin - calculateBottomInset(h);
256        }
257        return new Rectangle2D.Double(x, y, w, h);
258    }
259    
260    /**
261     * Creates an 'inset' rectangle.
262     * 
263     * @param base  the base rectangle ({@code null} not permitted).
264     * 
265     * @return The inset rectangle.
266     */
267    public Rectangle2D createInsetRectangle(Rectangle2D base) {
268        return createInsetRectangle(base, true, true);
269    }
270    
271    /**
272     * Creates an 'inset' rectangle.
273     * 
274     * @param base  the base rectangle ({@code null} not permitted).
275     * @param horizontal  apply horizontal insets?
276     * @param vertical  apply vertical insets?
277     * 
278     * @return The inset rectangle.
279     */
280    public Rectangle2D createInsetRectangle(Rectangle2D base,
281            boolean horizontal, boolean vertical) {
282        Args.nullNotPermitted(base, "base");
283        double topMargin = 0.0;
284        double bottomMargin = 0.0;
285        if (vertical) {
286            topMargin = calculateTopInset(base.getHeight());
287            bottomMargin = calculateBottomInset(base.getHeight());
288        }
289        double leftMargin = 0.0;
290        double rightMargin = 0.0;
291        if (horizontal) {
292            leftMargin = calculateLeftInset(base.getWidth());
293            rightMargin = calculateRightInset(base.getWidth());
294        }
295        return new Rectangle2D.Double(base.getX() + leftMargin, 
296                base.getY() + topMargin,
297                base.getWidth() - leftMargin - rightMargin,
298                base.getHeight() - topMargin - bottomMargin);
299    }
300    
301    /**
302     * Creates an outset rectangle.
303     * 
304     * @param base  the base rectangle ({@code null} not permitted).
305     * 
306     * @return An outset rectangle.
307     */
308    public Rectangle2D createOutsetRectangle(Rectangle2D base) {
309        return createOutsetRectangle(base, true, true);
310    }
311    
312    /**
313     * Creates an outset rectangle.
314     * 
315     * @param base  the base rectangle ({@code null} not permitted).
316     * @param horizontal  apply horizontal insets?
317     * @param vertical  apply vertical insets? 
318     * 
319     * @return An outset rectangle.
320     */
321    public Rectangle2D createOutsetRectangle(Rectangle2D base,
322            boolean horizontal, boolean vertical) {
323        Args.nullNotPermitted(base, "base");
324        double topMargin = 0.0;
325        double bottomMargin = 0.0;
326        if (vertical) {
327            topMargin = calculateTopOutset(base.getHeight());
328            bottomMargin = calculateBottomOutset(base.getHeight());
329        }
330        double leftMargin = 0.0;
331        double rightMargin = 0.0;
332        if (horizontal) {
333            leftMargin = calculateLeftOutset(base.getWidth());
334            rightMargin = calculateRightOutset(base.getWidth());
335        }
336        return new Rectangle2D.Double(base.getX() - leftMargin, 
337                base.getY() - topMargin,
338                base.getWidth() + leftMargin + rightMargin,
339                base.getHeight() + topMargin + bottomMargin);
340    }
341    
342    /**
343     * Returns the top margin.
344     * 
345     * @param height  the height of the base rectangle.
346     * 
347     * @return The top margin (in Java2D units).
348     */
349    public double calculateTopInset(double height) {
350        double result = this.top;
351        if (this.unitType == UnitType.RELATIVE) {
352            result = (this.top * height);
353        }
354        return result;
355    }
356    
357    /**
358     * Returns the top margin.
359     * 
360     * @param height  the height of the base rectangle.
361     * 
362     * @return The top margin (in Java2D units).
363     */
364    public double calculateTopOutset(double height) {
365        double result = this.top;
366        if (this.unitType == UnitType.RELATIVE) {
367            result = (height / (1 - this.top - this.bottom)) * this.top;
368        }
369        return result;
370    }
371    
372    /**
373     * Returns the bottom margin.
374     * 
375     * @param height  the height of the base rectangle.
376     * 
377     * @return The bottom margin (in Java2D units).
378     */
379    public double calculateBottomInset(double height) {
380        double result = this.bottom;
381        if (this.unitType == UnitType.RELATIVE) {
382            result = (this.bottom * height);
383        }
384        return result;
385    }
386
387    /**
388     * Returns the bottom margin.
389     * 
390     * @param height  the height of the base rectangle.
391     * 
392     * @return The bottom margin (in Java2D units).
393     */
394    public double calculateBottomOutset(double height) {
395        double result = this.bottom;
396        if (this.unitType == UnitType.RELATIVE) {
397            result = (height / (1 - this.top - this.bottom)) * this.bottom;
398        }
399        return result;
400    }
401
402    /**
403     * Returns the left margin.
404     * 
405     * @param width  the width of the base rectangle.
406     * 
407     * @return The left margin (in Java2D units).
408     */
409    public double calculateLeftInset(double width) {
410        double result = this.left;
411        if (this.unitType == UnitType.RELATIVE) {
412            result = (this.left * width);
413        }
414        return result;
415    }
416    
417    /**
418     * Returns the left margin.
419     * 
420     * @param width  the width of the base rectangle.
421     * 
422     * @return The left margin (in Java2D units).
423     */
424    public double calculateLeftOutset(double width) {
425        double result = this.left;
426        if (this.unitType == UnitType.RELATIVE) {
427            result = (width / (1 - this.left - this.right)) * this.left;
428        }
429        return result;
430    }
431    
432    /**
433     * Returns the right margin.
434     * 
435     * @param width  the width of the base rectangle.
436     * 
437     * @return The right margin (in Java2D units).
438     */
439    public double calculateRightInset(double width) {
440        double result = this.right;
441        if (this.unitType == UnitType.RELATIVE) {
442            result = (this.right * width);
443        }
444        return result;
445    }
446    
447    /**
448     * Returns the right margin.
449     * 
450     * @param width  the width of the base rectangle.
451     * 
452     * @return The right margin (in Java2D units).
453     */
454    public double calculateRightOutset(double width) {
455        double result = this.right;
456        if (this.unitType == UnitType.RELATIVE) {
457            result = (width / (1 - this.left - this.right)) * this.right;
458        }
459        return result;
460    }
461    
462    /**
463     * Trims the given width to allow for the insets.
464     * 
465     * @param width  the width.
466     * 
467     * @return The trimmed width.
468     */
469    public double trimWidth(double width) {
470        return width - calculateLeftInset(width) - calculateRightInset(width);   
471    }
472    
473    /**
474     * Extends the given width to allow for the insets.
475     * 
476     * @param width  the width.
477     * 
478     * @return The extended width.
479     */
480    public double extendWidth(double width) {
481        return width + calculateLeftOutset(width) + calculateRightOutset(width);   
482    }
483
484    /**
485     * Trims the given height to allow for the insets.
486     * 
487     * @param height  the height.
488     * 
489     * @return The trimmed height.
490     */
491    public double trimHeight(double height) {
492        return height - calculateTopInset(height) 
493                - calculateBottomInset(height);   
494    }
495    
496    /**
497     * Extends the given height to allow for the insets.
498     * 
499     * @param height  the height.
500     * 
501     * @return The extended height.
502     */
503    public double extendHeight(double height) {
504        return height + calculateTopOutset(height) 
505                + calculateBottomOutset(height);   
506    }
507
508    /**
509     * Shrinks the given rectangle by the amount of these insets.
510     * 
511     * @param area  the area ({@code null} not permitted).
512     */
513    public void trim(Rectangle2D area) {
514        double w = area.getWidth();
515        double h = area.getHeight();
516        double l = calculateLeftInset(w);
517        double r = calculateRightInset(w);
518        double t = calculateTopInset(h);
519        double b = calculateBottomInset(h);
520        area.setRect(area.getX() + l, area.getY() + t, w - l - r, h - t - b);    
521    }
522    
523}
524