View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.composer.layout;
23  
24  import static org.opencastproject.util.data.Monadics.mlist;
25  
26  import org.opencastproject.util.data.Function;
27  import org.opencastproject.util.data.Tuple;
28  
29  import java.util.List;
30  
31  public final class LayoutManager {
32    private LayoutManager() {
33    }
34  
35    /**
36     * Compose two shapes on a canvas.
37     * It is guaranteed that shapes to not extend the underlying canvas.
38     *
39     * @param canvas
40     *         the dimension of the target canvas
41     * @param upper
42     *         the dimension of the upper (z-axis) source shape
43     * @param lower
44     *         the dimension of the lower (z-axis) source shape
45     * @param spec
46     *         the layout specification
47     */
48    public static TwoShapeLayout twoShapeLayout(Dimension canvas,
49                                                Dimension upper,
50                                                Dimension lower,
51                                                TwoShapeLayouts.TwoShapeLayoutSpec spec) {
52      return new TwoShapeLayout(canvas,
53                                calcLayout(canvas, upper, spec.getUpper()),
54                                calcLayout(canvas, lower, spec.getLower()));
55    }
56  
57    private static Layout calcLayout(Dimension canvas,
58                                     Dimension shape,
59                                     HorizontalCoverageLayoutSpec posSpec) {
60      final Dimension slice = new Dimension(limitMin(canvas.getWidth() * posSpec.getHorizontalCoverage(), 0),
61                                            canvas.getHeight());
62      final Dimension scaled = scaleToFit(slice, shape);
63      final AnchorOffset dist = posSpec.getAnchorOffset();
64      final Offset anchorOfReference = offset(dist.getReferenceAnchor(), canvas);
65      final Offset anchorOfReferring = offset(dist.getReferringAnchor(), scaled);
66      return new Layout(
67              scaled,
68              new Offset(limitMin(anchorOfReference.getX() + dist.getOffset().getX() - anchorOfReferring.getX(), 0),
69                         limitMin(anchorOfReference.getY() + dist.getOffset().getY() - anchorOfReferring.getY(), 0)));
70    }
71  
72    private static Layout calcLayout(Dimension canvas,
73                                     Dimension shape,
74                                     AbsolutePositionLayoutSpec posSpec) {
75      final AnchorOffset dist = posSpec.getAnchorOffset();
76      final Offset anchorOfReference = offset(dist.getReferenceAnchor(), canvas);
77      final Offset anchorOfReferring = offset(dist.getReferringAnchor(), shape);
78      return new Layout(
79              shape,
80              new Offset(limitMin(anchorOfReference.getX() + dist.getOffset().getX() - anchorOfReferring.getX(), 0),
81                         limitMin(anchorOfReference.getY() + dist.getOffset().getY() - anchorOfReferring.getY(), 0)));
82    }
83  
84    /**
85     * Compose a list of shapes on a canvas.
86     *
87     * @param canvas
88     *         the dimension of the target canvas
89     * @param shapes
90     *         A list of shapes sorted in z-order with the first shape in the list being the lowermost one.
91     *         The list consists of the dimension of the source shape tupled with a layout specification.
92     */
93    public static MultiShapeLayout multiShapeLayout(final Dimension canvas,
94                                                    final List<Tuple<Dimension, HorizontalCoverageLayoutSpec>> shapes) {
95      return new MultiShapeLayout(
96              canvas,
97              mlist(shapes).map(new Function<Tuple<Dimension, HorizontalCoverageLayoutSpec>, Layout>() {
98                @Override public Layout apply(Tuple<Dimension, HorizontalCoverageLayoutSpec> a) {
99                  return calcLayout(canvas, a.getA(), a.getB());
100               }
101             }).value());
102   }
103 
104   /**
105    * Compose a list of shapes on a canvas.
106    *
107    * @param canvas
108    *         the dimension of the target canvas
109    * @param shapes
110    *         A list of shapes sorted in z-order with the first shape in the list being the lowermost one.
111    *         The list consists of the dimension of the source shape tupled with a layout specification.
112    */
113   public static MultiShapeLayout absoluteMultiShapeLayout(
114           final Dimension canvas,
115           final List<Tuple<Dimension, AbsolutePositionLayoutSpec>> shapes) {
116     return new MultiShapeLayout(
117             canvas,
118             mlist(shapes).map(new Function<Tuple<Dimension, AbsolutePositionLayoutSpec>, Layout>() {
119               @Override public Layout apply(Tuple<Dimension, AbsolutePositionLayoutSpec> a) {
120                 return calcLayout(canvas, a.getA(), a.getB());
121               }
122             }).value());
123   }
124 
125   public static int limitMax(double v, int max) {
126     return (int) Math.min(Math.round(v), max);
127   }
128 
129   public static int limitMin(double v, int min) {
130     return (int) Math.max(Math.round(v), min);
131   }
132 
133   /** Test if <code>shape</code> fits into <code>into</code>. */
134   public static boolean fits(Dimension into, Dimension shape) {
135     return shape.getHeight() <= into.getHeight() && shape.getWidth() <= into.getHeight();
136   }
137 
138   /** Calculate the area of a dimension. */
139   public static int area(Dimension a) {
140     return a.getWidth() * a.getHeight();
141   }
142 
143   /** Return the dimension with the bigger area. */
144   public static Dimension max(Dimension a, Dimension b) {
145     return area(a) > area(b) ? a : b;
146   }
147 
148   /** Get the aspect ratio of a dimension. */
149   public static double aspectRatio(Dimension a) {
150     return d(a.getWidth()) / d(a.getHeight());
151   }
152 
153   /** Test if layouts <code>a</code> and <code>b</code> overlap. */
154   public static boolean overlap(Layout a, Layout b) {
155     return (between(left(a), right(a), left(b)) || between(left(a), right(a), right(b)))
156             && (between(top(a), bottom(a), top(b)) || between(top(a), bottom(a), bottom(b)));
157   }
158 
159   /** Get the X coordinate of the left bound of the layout. */
160   public static int left(Layout a) {
161     return a.getOffset().getX();
162   }
163 
164   /** Get the X coordinate of the right bound of the layout. */
165   public static int right(Layout a) {
166     return a.getOffset().getX() + a.getDimension().getWidth();
167   }
168 
169   /** Get the Y coordinate of the top bound of the layout. */
170   public static int top(Layout a) {
171     return a.getOffset().getY();
172   }
173 
174   /** Get the Y coordinate of the bottom bound of the layout. */
175   public static int bottom(Layout a) {
176     return a.getOffset().getY() + a.getDimension().getHeight();
177   }
178 
179   /** Calculate the offset of an anchor point for a given shape relative to its upper left corner. */
180   public static Offset offset(Anchor a, Dimension dim) {
181     return new Offset(limitMax(a.getLeft() * d(dim.getWidth()), dim.getWidth()),
182                       limitMax(a.getTop() * d(dim.getHeight()), dim.getHeight()));
183   }
184 
185   /**
186    * Scale <code>shape</code> by <code>scale</code> and ensure that any rounding errors are limited so that
187    * the resulting dimension does not exceed <code>limit</code>.
188    */
189   public static Dimension scale(Dimension limit, Dimension shape, double scale) {
190     return Dimension.dimension(
191             limitMax(d(shape.getWidth()) * scale, limit.getWidth()),
192             limitMax(d(shape.getHeight()) * scale, limit.getHeight()));
193   }
194 
195   /** Scale <code>d</code> to fit into <code>canvas</code> . */
196   public static Dimension scaleToFit(Dimension canvas, Dimension d) {
197     final double scaleToWidth = d(canvas.getWidth()) / d(d.getWidth());
198     if (d.getHeight() * scaleToWidth > canvas.getHeight()) {
199       final double scaleToHeight = d(canvas.getHeight()) / d(d.getHeight());
200       return scale(canvas, d, scaleToHeight);
201     } else {
202       return scale(canvas, d, scaleToWidth);
203     }
204   }
205 
206   /** a &lt;= x &lt;= b */
207   public static boolean between(int a, int b, int x) {
208     return a <= x && x <= b;
209   }
210 
211   private static double d(int v) {
212     return v;
213   }
214 }