diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 0f36010d9bd..86a0d30c592 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -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& aSpecifiedWidth, + const Maybe& 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 object’s + // 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 +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 specifiedWidth; + if (aIntrinsicSize.width.GetUnit() == eStyleUnit_Coord) { + specifiedWidth.emplace(aIntrinsicSize.width.GetCoordValue()); + } + + Maybe specifiedHeight; + if (aIntrinsicSize.height.GetUnit() == eStyleUnit_Coord) { + specifiedHeight.emplace(aIntrinsicSize.height.GetCoordValue()); + } + + Maybe 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 fitType; + + Maybe 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 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, diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index f84b782a2ab..e582776655e 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -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