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 org.opencastproject.util.data.Tuple;
25  
26  import java.util.List;
27  
28  public final class LayoutManager {
29    private LayoutManager() {
30    }
31  
32    /**
33     * Compose two shapes on a canvas.
34     * It is guaranteed that shapes to not extend the underlying canvas.
35     *
36     * @param canvas
37     *         the dimension of the target canvas
38     * @param upper
39     *         the dimension of the upper (z-axis) source shape
40     * @param lower
41     *         the dimension of the lower (z-axis) source shape
42     * @param spec
43     *         the layout specification
44     */
45    public static TwoShapeLayout twoShapeLayout(Dimension canvas,
46                                                Dimension upper,
47                                                Dimension lower,
48                                                TwoShapeLayouts.TwoShapeLayoutSpec spec) {
49      return new TwoShapeLayout(canvas,
50                                calcLayout(canvas, upper, spec.getUpper()),
51                                calcLayout(canvas, lower, spec.getLower()));
52    }
53  
54    private static Layout calcLayout(Dimension canvas,
55                                     Dimension shape,
56                                     HorizontalCoverageLayoutSpec posSpec) {
57      final Dimension slice = new Dimension(limitMin(canvas.getWidth() * posSpec.getHorizontalCoverage(), 0),
58                                            canvas.getHeight());
59      final Dimension scaled = scaleToFit(slice, shape);
60      final AnchorOffset dist = posSpec.getAnchorOffset();
61      final Offset anchorOfReference = offset(dist.getReferenceAnchor(), canvas);
62      final Offset anchorOfReferring = offset(dist.getReferringAnchor(), scaled);
63      return new Layout(
64              scaled,
65              new Offset(limitMin(anchorOfReference.getX() + dist.getOffset().getX() - anchorOfReferring.getX(), 0),
66                         limitMin(anchorOfReference.getY() + dist.getOffset().getY() - anchorOfReferring.getY(), 0)));
67    }
68  
69    private static Layout calcLayout(Dimension canvas,
70                                     Dimension shape,
71                                     AbsolutePositionLayoutSpec posSpec) {
72      final AnchorOffset dist = posSpec.getAnchorOffset();
73      final Offset anchorOfReference = offset(dist.getReferenceAnchor(), canvas);
74      final Offset anchorOfReferring = offset(dist.getReferringAnchor(), shape);
75      return new Layout(
76              shape,
77              new Offset(limitMin(anchorOfReference.getX() + dist.getOffset().getX() - anchorOfReferring.getX(), 0),
78                         limitMin(anchorOfReference.getY() + dist.getOffset().getY() - anchorOfReferring.getY(), 0)));
79    }
80  
81    /**
82     * Compose a list of shapes on a canvas.
83     *
84     * @param canvas
85     *         the dimension of the target canvas
86     * @param shapes
87     *         A list of shapes sorted in z-order with the first shape in the list being the lowermost one.
88     *         The list consists of the dimension of the source shape tupled with a layout specification.
89     */
90    public static MultiShapeLayout multiShapeLayout(final Dimension canvas,
91                                                    final List<Tuple<Dimension, HorizontalCoverageLayoutSpec>> shapes) {
92      List<Layout> layouts = shapes.stream()
93          .map(a -> calcLayout(canvas, a.getA(), a.getB()))
94          .toList();
95  
96      return new MultiShapeLayout(canvas, layouts);
97    }
98  
99    /**
100    * Compose a list of shapes on a canvas.
101    *
102    * @param canvas
103    *         the dimension of the target canvas
104    * @param shapes
105    *         A list of shapes sorted in z-order with the first shape in the list being the lowermost one.
106    *         The list consists of the dimension of the source shape tupled with a layout specification.
107    */
108   public static MultiShapeLayout absoluteMultiShapeLayout(
109           final Dimension canvas,
110           final List<Tuple<Dimension, AbsolutePositionLayoutSpec>> shapes) {
111     List<Layout> layouts = shapes.stream()
112         .map(a -> calcLayout(canvas, a.getA(), a.getB()))
113         .toList();
114 
115     return new MultiShapeLayout(canvas, layouts);
116   }
117 
118   public static int limitMax(double v, int max) {
119     return (int) Math.min(Math.round(v), max);
120   }
121 
122   public static int limitMin(double v, int min) {
123     return (int) Math.max(Math.round(v), min);
124   }
125 
126   /** Test if <code>shape</code> fits into <code>into</code>. */
127   public static boolean fits(Dimension into, Dimension shape) {
128     return shape.getHeight() <= into.getHeight() && shape.getWidth() <= into.getHeight();
129   }
130 
131   /** Calculate the area of a dimension. */
132   public static int area(Dimension a) {
133     return a.getWidth() * a.getHeight();
134   }
135 
136   /** Return the dimension with the bigger area. */
137   public static Dimension max(Dimension a, Dimension b) {
138     return area(a) > area(b) ? a : b;
139   }
140 
141   /** Get the aspect ratio of a dimension. */
142   public static double aspectRatio(Dimension a) {
143     return d(a.getWidth()) / d(a.getHeight());
144   }
145 
146   /** Test if layouts <code>a</code> and <code>b</code> overlap. */
147   public static boolean overlap(Layout a, Layout b) {
148     return (between(left(a), right(a), left(b)) || between(left(a), right(a), right(b)))
149             && (between(top(a), bottom(a), top(b)) || between(top(a), bottom(a), bottom(b)));
150   }
151 
152   /** Get the X coordinate of the left bound of the layout. */
153   public static int left(Layout a) {
154     return a.getOffset().getX();
155   }
156 
157   /** Get the X coordinate of the right bound of the layout. */
158   public static int right(Layout a) {
159     return a.getOffset().getX() + a.getDimension().getWidth();
160   }
161 
162   /** Get the Y coordinate of the top bound of the layout. */
163   public static int top(Layout a) {
164     return a.getOffset().getY();
165   }
166 
167   /** Get the Y coordinate of the bottom bound of the layout. */
168   public static int bottom(Layout a) {
169     return a.getOffset().getY() + a.getDimension().getHeight();
170   }
171 
172   /** Calculate the offset of an anchor point for a given shape relative to its upper left corner. */
173   public static Offset offset(Anchor a, Dimension dim) {
174     return new Offset(limitMax(a.getLeft() * d(dim.getWidth()), dim.getWidth()),
175                       limitMax(a.getTop() * d(dim.getHeight()), dim.getHeight()));
176   }
177 
178   /**
179    * Scale <code>shape</code> by <code>scale</code> and ensure that any rounding errors are limited so that
180    * the resulting dimension does not exceed <code>limit</code>.
181    */
182   public static Dimension scale(Dimension limit, Dimension shape, double scale) {
183     return Dimension.dimension(
184             limitMax(d(shape.getWidth()) * scale, limit.getWidth()),
185             limitMax(d(shape.getHeight()) * scale, limit.getHeight()));
186   }
187 
188   /** Scale <code>d</code> to fit into <code>canvas</code> . */
189   public static Dimension scaleToFit(Dimension canvas, Dimension d) {
190     final double scaleToWidth = d(canvas.getWidth()) / d(d.getWidth());
191     if (d.getHeight() * scaleToWidth > canvas.getHeight()) {
192       final double scaleToHeight = d(canvas.getHeight()) / d(d.getHeight());
193       return scale(canvas, d, scaleToHeight);
194     } else {
195       return scale(canvas, d, scaleToWidth);
196     }
197   }
198 
199   /** a &lt;= x &lt;= b */
200   public static boolean between(int a, int b, int x) {
201     return a <= x && x <= b;
202   }
203 
204   private static double d(int v) {
205     return v;
206   }
207 }