/** * Licensed to The Apereo Foundation under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * * The Apereo Foundation licenses this file to you under the Educational * Community License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License * at: * * http://opensource.org/licenses/ecl2.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * */ package org.opencastproject.composer.layout; import static org.opencastproject.util.data.Monadics.mlist; import org.opencastproject.util.data.Function; import org.opencastproject.util.data.Tuple; import java.util.List; public final class LayoutManager { private LayoutManager() { } /** * Compose two shapes on a canvas. * It is guaranteed that shapes to not extend the underlying canvas. * * @param canvas * the dimension of the target canvas * @param upper * the dimension of the upper (z-axis) source shape * @param lower * the dimension of the lower (z-axis) source shape * @param spec * the layout specification */ public static TwoShapeLayout twoShapeLayout(Dimension canvas, Dimension upper, Dimension lower, TwoShapeLayouts.TwoShapeLayoutSpec spec) { return new TwoShapeLayout(canvas, calcLayout(canvas, upper, spec.getUpper()), calcLayout(canvas, lower, spec.getLower())); } private static Layout calcLayout(Dimension canvas, Dimension shape, HorizontalCoverageLayoutSpec posSpec) { final Dimension slice = new Dimension(limitMin(canvas.getWidth() * posSpec.getHorizontalCoverage(), 0), canvas.getHeight()); final Dimension scaled = scaleToFit(slice, shape); final AnchorOffset dist = posSpec.getAnchorOffset(); final Offset anchorOfReference = offset(dist.getReferenceAnchor(), canvas); final Offset anchorOfReferring = offset(dist.getReferringAnchor(), scaled); return new Layout( scaled, new Offset(limitMin(anchorOfReference.getX() + dist.getOffset().getX() - anchorOfReferring.getX(), 0), limitMin(anchorOfReference.getY() + dist.getOffset().getY() - anchorOfReferring.getY(), 0))); } private static Layout calcLayout(Dimension canvas, Dimension shape, AbsolutePositionLayoutSpec posSpec) { final AnchorOffset dist = posSpec.getAnchorOffset(); final Offset anchorOfReference = offset(dist.getReferenceAnchor(), canvas); final Offset anchorOfReferring = offset(dist.getReferringAnchor(), shape); return new Layout( shape, new Offset(limitMin(anchorOfReference.getX() + dist.getOffset().getX() - anchorOfReferring.getX(), 0), limitMin(anchorOfReference.getY() + dist.getOffset().getY() - anchorOfReferring.getY(), 0))); } /** * Compose a list of shapes on a canvas. * * @param canvas * the dimension of the target canvas * @param shapes * A list of shapes sorted in z-order with the first shape in the list being the lowermost one. * The list consists of the dimension of the source shape tupled with a layout specification. */ public static MultiShapeLayout multiShapeLayout(final Dimension canvas, final List<Tuple<Dimension, HorizontalCoverageLayoutSpec>> shapes) { return new MultiShapeLayout( canvas, mlist(shapes).map(new Function<Tuple<Dimension, HorizontalCoverageLayoutSpec>, Layout>() { @Override public Layout apply(Tuple<Dimension, HorizontalCoverageLayoutSpec> a) { return calcLayout(canvas, a.getA(), a.getB()); } }).value()); } /** * Compose a list of shapes on a canvas. * * @param canvas * the dimension of the target canvas * @param shapes * A list of shapes sorted in z-order with the first shape in the list being the lowermost one. * The list consists of the dimension of the source shape tupled with a layout specification. */ public static MultiShapeLayout absoluteMultiShapeLayout( final Dimension canvas, final List<Tuple<Dimension, AbsolutePositionLayoutSpec>> shapes) { return new MultiShapeLayout( canvas, mlist(shapes).map(new Function<Tuple<Dimension, AbsolutePositionLayoutSpec>, Layout>() { @Override public Layout apply(Tuple<Dimension, AbsolutePositionLayoutSpec> a) { return calcLayout(canvas, a.getA(), a.getB()); } }).value()); } public static int limitMax(double v, int max) { return (int) Math.min(Math.round(v), max); } public static int limitMin(double v, int min) { return (int) Math.max(Math.round(v), min); } /** Test if <code>shape</code> fits into <code>into</code>. */ public static boolean fits(Dimension into, Dimension shape) { return shape.getHeight() <= into.getHeight() && shape.getWidth() <= into.getHeight(); } /** Calculate the area of a dimension. */ public static int area(Dimension a) { return a.getWidth() * a.getHeight(); } /** Return the dimension with the bigger area. */ public static Dimension max(Dimension a, Dimension b) { return area(a) > area(b) ? a : b; } /** Get the aspect ratio of a dimension. */ public static double aspectRatio(Dimension a) { return d(a.getWidth()) / d(a.getHeight()); } /** Test if layouts <code>a</code> and <code>b</code> overlap. */ public static boolean overlap(Layout a, Layout b) { return (between(left(a), right(a), left(b)) || between(left(a), right(a), right(b))) && (between(top(a), bottom(a), top(b)) || between(top(a), bottom(a), bottom(b))); } /** Get the X coordinate of the left bound of the layout. */ public static int left(Layout a) { return a.getOffset().getX(); } /** Get the X coordinate of the right bound of the layout. */ public static int right(Layout a) { return a.getOffset().getX() + a.getDimension().getWidth(); } /** Get the Y coordinate of the top bound of the layout. */ public static int top(Layout a) { return a.getOffset().getY(); } /** Get the Y coordinate of the bottom bound of the layout. */ public static int bottom(Layout a) { return a.getOffset().getY() + a.getDimension().getHeight(); } /** Calculate the offset of an anchor point for a given shape relative to its upper left corner. */ public static Offset offset(Anchor a, Dimension dim) { return new Offset(limitMax(a.getLeft() * d(dim.getWidth()), dim.getWidth()), limitMax(a.getTop() * d(dim.getHeight()), dim.getHeight())); } /** * Scale <code>shape</code> by <code>scale</code> and ensure that any rounding errors are limited so that * the resulting dimension does not exceed <code>limit</code>. */ public static Dimension scale(Dimension limit, Dimension shape, double scale) { return Dimension.dimension( limitMax(d(shape.getWidth()) * scale, limit.getWidth()), limitMax(d(shape.getHeight()) * scale, limit.getHeight())); } /** Scale <code>d</code> to fit into <code>canvas</code> . */ public static Dimension scaleToFit(Dimension canvas, Dimension d) { final double scaleToWidth = d(canvas.getWidth()) / d(d.getWidth()); if (d.getHeight() * scaleToWidth > canvas.getHeight()) { final double scaleToHeight = d(canvas.getHeight()) / d(d.getHeight()); return scale(canvas, d, scaleToHeight); } else { return scale(canvas, d, scaleToWidth); } } /** a <= x <= b */ public static boolean between(int a, int b, int x) { return a <= x && x <= b; } private static double d(int v) { return v; } }