Bug 624647 part 1: Add utility method nsLayoutUtils::ComputeObjectDestRect() to handle object-fit/object-position. r=seth

This commit is contained in:
Daniel Holbert 2014-11-14 16:45:23 -08:00
parent 6c33817dd5
commit 779bb4b2dd
2 changed files with 240 additions and 0 deletions

View File

@ -9,6 +9,7 @@
#include "mozilla/ArrayUtils.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "nsCharTraits.h"
@ -3464,6 +3465,218 @@ nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
return resultRect;
}
enum ObjectDimensionType { eWidth, eHeight };
static nscoord
ComputeMissingDimension(const nsSize& aDefaultObjectSize,
const nsSize& aIntrinsicRatio,
const Maybe<nscoord>& aSpecifiedWidth,
const Maybe<nscoord>& aSpecifiedHeight,
ObjectDimensionType aDimensionToCompute)
{
// The "default sizing algorithm" computes the missing dimension as follows:
// (source: http://dev.w3.org/csswg/css-images-3/#default-sizing )
// 1. "If the object has an intrinsic aspect ratio, the missing dimension of
// the concrete object size is calculated using the intrinsic aspect
// ratio and the present dimension."
if (aIntrinsicRatio.width > 0 && aIntrinsicRatio.height > 0) {
// Fill in the missing dimension using the intrinsic aspect ratio.
nscoord knownDimensionSize;
float ratio;
if (aDimensionToCompute == eWidth) {
knownDimensionSize = *aSpecifiedHeight;
ratio = aIntrinsicRatio.width / aIntrinsicRatio.height;
} else {
knownDimensionSize = *aSpecifiedWidth;
ratio = aIntrinsicRatio.height / aIntrinsicRatio.width;
}
return NSCoordSaturatingNonnegativeMultiply(knownDimensionSize, ratio);
}
// 2. "Otherwise, if the missing dimension is present in the objects
// intrinsic dimensions, [...]"
// NOTE: *Skipping* this case, because we already know it's not true -- we're
// in this function because the missing dimension is *not* present in
// the object's intrinsic dimensions.
// 3. "Otherwise, the missing dimension of the concrete object size is taken
// from the default object size. "
return (aDimensionToCompute == eWidth) ?
aDefaultObjectSize.width : aDefaultObjectSize.height;
}
/*
* This computes & returns the concrete object size of replaced content, if
* that content were to be rendered with "object-fit: none". (Or, if the
* element has neither an intrinsic height nor width, this method returns an
* empty Maybe<> object.)
*
* As specced...
* http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none
* ..we use "the default sizing algorithm with no specified size,
* and a default object size equal to the replaced element's used width and
* height."
*
* The default sizing algorithm is described here:
* http://dev.w3.org/csswg/css-images-3/#default-sizing
* Quotes in the function-impl are taken from that ^ spec-text.
*
* Per its final bulleted section: since there's no specified size,
* we run the default sizing algorithm using the object's intrinsic size in
* place of the specified size. But if the object has neither an intrinsic
* height nor an intrinsic width, then we instead return without populating our
* outparam, and we let the caller figure out the size (using a contain
* constraint).
*/
static Maybe<nsSize>
MaybeComputeObjectFitNoneSize(const nsSize& aDefaultObjectSize,
const IntrinsicSize& aIntrinsicSize,
const nsSize& aIntrinsicRatio)
{
// "If the object has an intrinsic height or width, its size is resolved as
// if its intrinsic dimensions were given as the specified size."
//
// So, first we check if we have an intrinsic height and/or width:
Maybe<nscoord> specifiedWidth;
if (aIntrinsicSize.width.GetUnit() == eStyleUnit_Coord) {
specifiedWidth.emplace(aIntrinsicSize.width.GetCoordValue());
}
Maybe<nscoord> specifiedHeight;
if (aIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) {
specifiedHeight.emplace(aIntrinsicSize.height.GetCoordValue());
}
Maybe<nsSize> noneSize; // (the value we'll return)
if (specifiedWidth || specifiedHeight) {
// We have at least one specified dimension; use whichever dimension is
// specified, and compute the other one using our intrinsic ratio, or (if
// no valid ratio) using the default object size.
noneSize.emplace();
noneSize->width = specifiedWidth ?
*specifiedWidth :
ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
specifiedWidth, specifiedHeight,
eWidth);
noneSize->height = specifiedHeight ?
*specifiedHeight :
ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
specifiedWidth, specifiedHeight,
eHeight);
}
// [else:] "Otherwise [if there's neither an intrinsic height nor width], its
// size is resolved as a contain constraint against the default object size."
// We'll let our caller do that, to share code & avoid redundant
// computations; so, we return w/out populating noneSize.
return noneSize;
}
// Computes the concrete object size to render into, as described at
// http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution
static nsSize
ComputeConcreteObjectSize(const nsSize& aConstraintSize,
const IntrinsicSize& aIntrinsicSize,
const nsSize& aIntrinsicRatio,
uint8_t aObjectFit)
{
// Handle default behavior (filling the container) w/ fast early return.
// (Also: if there's no valid intrinsic ratio, then we have the "fill"
// behavior & just use the constraint size.)
if (MOZ_LIKELY(aObjectFit == NS_STYLE_OBJECT_FIT_FILL) ||
aIntrinsicRatio.width == 0 ||
aIntrinsicRatio.height == 0) {
return aConstraintSize;
}
// The type of constraint to compute (cover/contain), if needed:
Maybe<nsImageRenderer::FitType> fitType;
Maybe<nsSize> noneSize;
if (aObjectFit == NS_STYLE_OBJECT_FIT_NONE ||
aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) {
noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize,
aIntrinsicRatio);
if (!noneSize || aObjectFit == NS_STYLE_OBJECT_FIT_SCALE_DOWN) {
// Need to compute a 'CONTAIN' constraint (either for the 'none' size
// itself, or for comparison w/ the 'none' size to resolve 'scale-down'.)
fitType.emplace(nsImageRenderer::CONTAIN);
}
} else if (aObjectFit == NS_STYLE_OBJECT_FIT_COVER) {
fitType.emplace(nsImageRenderer::COVER);
} else if (aObjectFit == NS_STYLE_OBJECT_FIT_CONTAIN) {
fitType.emplace(nsImageRenderer::CONTAIN);
}
Maybe<nsSize> constrainedSize;
if (fitType) {
constrainedSize.emplace(
nsImageRenderer::ComputeConstrainedSize(aConstraintSize,
aIntrinsicRatio,
*fitType));
}
// Now, we should have all the sizing information that we need.
switch (aObjectFit) {
// skipping NS_STYLE_OBJECT_FIT_FILL; we handled it w/ early-return.
case NS_STYLE_OBJECT_FIT_CONTAIN:
case NS_STYLE_OBJECT_FIT_COVER:
MOZ_ASSERT(constrainedSize);
return *constrainedSize;
case NS_STYLE_OBJECT_FIT_NONE:
if (noneSize) {
return *noneSize;
}
MOZ_ASSERT(constrainedSize);
return *constrainedSize;
case NS_STYLE_OBJECT_FIT_SCALE_DOWN:
MOZ_ASSERT(constrainedSize);
if (noneSize) {
constrainedSize->width =
std::min(constrainedSize->width, noneSize->width);
constrainedSize->height =
std::min(constrainedSize->height, noneSize->height);
}
return *constrainedSize;
default:
MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'");
return aConstraintSize; // fall back to (default) 'fill' behavior
}
}
/* static */ nsRect
nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect,
const IntrinsicSize& aIntrinsicSize,
const nsSize& aIntrinsicRatio,
const nsStylePosition* aStylePos)
{
// Step 1: Figure out our "concrete object size"
// (the size of the region we'll actually draw our image's pixels into).
nsSize concreteObjectSize =
ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize,
aIntrinsicRatio, aStylePos->mObjectFit);
// Step 2: Figure out how to align that region in the element's content-box.
nsPoint imageTopLeftPt, imageAnchorPt;
nsImageRenderer::ComputeObjectAnchorPoint(aStylePos->mObjectPosition,
aConstraintRect.Size(),
concreteObjectSize,
&imageTopLeftPt, &imageAnchorPt);
// Right now, we're with respect to aConstraintRect's top-left point. We add
// that point here, to convert to the same broader coordinate space that
// aConstraintRect is in.
imageTopLeftPt += aConstraintRect.TopLeft();
imageAnchorPt += aConstraintRect.TopLeft();
// XXXdholbert Per bug 1098417, we should be returning imageAnchorPt here,
// and our caller should make sure it's pixel-aligned.
return nsRect(imageTopLeftPt, concreteObjectSize);
}
nsresult
nsLayoutUtils::GetFontMetricsForFrame(const nsIFrame* aFrame,
nsFontMetrics** aFontMetrics,

View File

@ -117,6 +117,7 @@ class nsLayoutUtils
typedef mozilla::dom::DOMRectList DOMRectList;
typedef mozilla::layers::Layer Layer;
typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
typedef mozilla::IntrinsicSize IntrinsicSize;
typedef mozilla::gfx::SourceSurface SourceSurface;
typedef mozilla::gfx::Color Color;
typedef mozilla::gfx::DrawTarget DrawTarget;
@ -1079,6 +1080,32 @@ public:
nsIFrame* aFrame,
uint32_t aFlags = 0);
/**
* Computes the destination rect that a given replaced element should render
* into, based on its CSS 'object-fit' and 'object-position' properties.
*
* @param aConstraintRect The constraint rect that we have at our disposal,
* which would e.g. be exactly filled by the image
* if we had "object-fit: fill".
* @param aIntrinsicSize The replaced content's intrinsic size, as reported
* by nsIFrame::GetIntrinsicSize().
* @param aIntrinsicRatio The replaced content's intrinsic ratio, as reported
* by nsIFrame::GetIntrinsicRatio().
* @param aStylePos The nsStylePosition struct that contains the 'object-fit'
* and 'object-position' values that we should rely on.
* (This should usually be the nsStylePosition for the
* replaced element in question, but not always. For
* example, a <video>'s poster-image has a dedicated
* anonymous element & child-frame, but we should still use
* the <video>'s 'object-fit' and 'object-position' values.)
* @return The nsRect into which we should render the replaced content (using
* the same coordinate space as the passed-in aConstraintRect).
*/
static nsRect ComputeObjectDestRect(const nsRect& aConstraintRect,
const IntrinsicSize& aIntrinsicSize,
const nsSize& aIntrinsicRatio,
const nsStylePosition* aStylePos);
/**
* Get the font metrics corresponding to the frame's style data.
* @param aFrame the frame