blob: 21eba9ac7fa8d548c2c2b13b2d0f262a33f4a6d5 [file] [log] [blame]
// Copyright 2014 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0
//
// 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.
#include "cobalt/layout/replaced_box.h"
#include <algorithm>
#include <memory>
#include "base/bind.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/cssom/filter_function_list_value.h"
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/cssom/map_to_mesh_function.h"
#include "cobalt/layout/container_box.h"
#include "cobalt/layout/letterboxed_image.h"
#include "cobalt/layout/used_style.h"
#include "cobalt/layout/white_space_processing.h"
#include "cobalt/loader/mesh/mesh_projection.h"
#include "cobalt/math/transform_2d.h"
#include "cobalt/math/vector2d_f.h"
#include "cobalt/render_tree/brush.h"
#include "cobalt/render_tree/color_rgba.h"
#include "cobalt/render_tree/filter_node.h"
#include "cobalt/render_tree/image_node.h"
#include "cobalt/render_tree/lottie_node.h"
#include "cobalt/render_tree/map_to_mesh_filter.h"
#include "cobalt/render_tree/punch_through_video_node.h"
#include "cobalt/render_tree/rect_node.h"
#include "cobalt/render_tree/resource_provider.h"
namespace cobalt {
namespace layout {
using render_tree::CompositionNode;
using render_tree::FilterNode;
using render_tree::ImageNode;
using render_tree::LottieNode;
using render_tree::MapToMeshFilter;
using render_tree::Node;
using render_tree::PunchThroughVideoNode;
using render_tree::RectNode;
using render_tree::SolidColorBrush;
using render_tree::animations::AnimateNode;
namespace {
// Used when intrinsic ratio cannot be determined,
// as per https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width.
const float kFallbackIntrinsicRatio = 2.0f;
// Becomes a used value of "width" if it cannot be determined by any other
// means, as per https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width.
const float kFallbackWidth = 300.0f;
const char* kEquirectangularMeshURL =
"h5vcc-embedded://equirectangular_40_40.msh";
const char* kWarningInvalidMeshUrl =
"Mesh specification invalid in map-to-mesh filter: "
"must be either a valid URL or 'equirectangular'.";
const char* kWarningLoadingMeshFailed =
"Could not load mesh specified by map-to-mesh filter.";
// Convert the parsed keyword value for a stereo mode into a stereo mode enum
// value.
render_tree::StereoMode ReadStereoMode(
const scoped_refptr<cssom::KeywordValue>& keyword_value) {
cssom::KeywordValue::Value value = keyword_value->value();
if (value == cssom::KeywordValue::kMonoscopic) {
return render_tree::kMono;
} else if (value == cssom::KeywordValue::kStereoscopicLeftRight) {
return render_tree::kLeftRight;
} else if (value == cssom::KeywordValue::kStereoscopicTopBottom) {
return render_tree::kTopBottom;
} else {
LOG(DFATAL) << "Stereo mode has an invalid non-NULL value, defaulting to "
<< "monoscopic";
return render_tree::kMono;
}
}
} // namespace
ReplacedBox::ReplacedBox(
const scoped_refptr<cssom::CSSComputedStyleDeclaration>&
css_computed_style_declaration,
const ReplaceImageCB& replace_image_cb, const SetBoundsCB& set_bounds_cb,
const scoped_refptr<Paragraph>& paragraph, int32 text_position,
const base::Optional<LayoutUnit>& maybe_intrinsic_width,
const base::Optional<LayoutUnit>& maybe_intrinsic_height,
const base::Optional<float>& maybe_intrinsic_ratio,
UsedStyleProvider* used_style_provider,
base::Optional<ReplacedBoxMode> replaced_box_mode,
const math::SizeF& content_size,
base::Optional<render_tree::LottieAnimation::LottieProperties>
lottie_properties,
LayoutStatTracker* layout_stat_tracker)
: Box(css_computed_style_declaration, used_style_provider,
layout_stat_tracker),
maybe_intrinsic_width_(maybe_intrinsic_width),
maybe_intrinsic_height_(maybe_intrinsic_height),
// Like Chromium, we assume that an element must always have an intrinsic
// ratio, although technically it's a spec violation. For details see
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width.
intrinsic_ratio_(maybe_intrinsic_ratio.value_or(kFallbackIntrinsicRatio)),
replace_image_cb_(replace_image_cb),
set_bounds_cb_(set_bounds_cb),
paragraph_(paragraph),
text_position_(text_position),
replaced_box_mode_(replaced_box_mode),
content_size_(content_size),
lottie_properties_(lottie_properties) {}
WrapResult ReplacedBox::TryWrapAt(WrapAtPolicy wrap_at_policy,
WrapOpportunityPolicy wrap_opportunity_policy,
bool is_line_existence_justified,
LayoutUnit available_width,
bool should_collapse_trailing_white_space) {
// NOTE: This logic must stay in sync with
// InlineLevelBlockContainerBox::TryWrapAt().
DCHECK(!IsAbsolutelyPositioned());
// Wrapping is not allowed until the line's existence is justified, meaning
// that wrapping cannot occur before the box. Given that this box cannot be
// split, no wrappable point is available.
if (!is_line_existence_justified) {
return kWrapResultNoWrap;
}
// Atomic inline elements participate in the inline formatting context as a
// single opaque box. Therefore, the parent's style should be used, as the
// internals of the atomic inline element have no impact on the formatting of
// the line.
// https://www.w3.org/TR/CSS21/visuren.html#inline-boxes
if (!parent()) {
return kWrapResultNoWrap;
}
bool style_allows_break_word = parent()->computed_style()->overflow_wrap() ==
cssom::KeywordValue::GetBreakWord();
if (!ShouldProcessWrapOpportunityPolicy(wrap_opportunity_policy,
style_allows_break_word)) {
return kWrapResultNoWrap;
}
// Even when the style prevents wrapping, wrapping can still occur before the
// box if the line's existence has already been justified and whitespace
// precedes it.
if (!DoesAllowTextWrapping(parent()->computed_style()->white_space())) {
if (text_position_ > 0 &&
paragraph_->IsCollapsibleWhiteSpace(text_position_ - 1)) {
return kWrapResultWrapBefore;
} else {
return kWrapResultNoWrap;
}
}
Paragraph::BreakPolicy break_policy =
Paragraph::GetBreakPolicyFromWrapOpportunityPolicy(
wrap_opportunity_policy, style_allows_break_word);
return paragraph_->IsBreakPosition(text_position_, break_policy)
? kWrapResultWrapBefore
: kWrapResultNoWrap;
}
void ReplacedBox::SplitBidiLevelRuns() {}
bool ReplacedBox::TrySplitAtSecondBidiLevelRun() { return false; }
base::Optional<int> ReplacedBox::GetBidiLevel() const {
return paragraph_->GetBidiLevel(text_position_);
}
void ReplacedBox::SetShouldCollapseLeadingWhiteSpace(
bool should_collapse_leading_white_space) {
// Do nothing.
}
void ReplacedBox::SetShouldCollapseTrailingWhiteSpace(
bool should_collapse_trailing_white_space) {
// Do nothing.
}
bool ReplacedBox::HasLeadingWhiteSpace() const { return false; }
bool ReplacedBox::HasTrailingWhiteSpace() const { return false; }
bool ReplacedBox::IsCollapsed() const { return false; }
bool ReplacedBox::JustifiesLineExistence() const { return true; }
bool ReplacedBox::AffectsBaselineInBlockFormattingContext() const {
return false;
}
LayoutUnit ReplacedBox::GetBaselineOffsetFromTopMarginEdge() const {
return GetMarginBoxHeight();
}
namespace {
void AddLetterboxedImageToRenderTree(
const LetterboxDimensions& dimensions,
const scoped_refptr<render_tree::Image>& image,
CompositionNode::Builder* composition_node_builder) {
if (dimensions.image_rect) {
ImageNode::Builder image_builder(image, *dimensions.image_rect);
composition_node_builder->AddChild(new ImageNode(image_builder));
}
}
void AddLetterboxedPunchThroughVideoNodeToRenderTree(
const LetterboxDimensions& dimensions,
const ReplacedBox::SetBoundsCB& set_bounds_cb,
CompositionNode::Builder* border_node_builder) {
if (dimensions.image_rect) {
PunchThroughVideoNode::Builder builder(*(dimensions.image_rect),
set_bounds_cb);
border_node_builder->AddChild(new PunchThroughVideoNode(builder));
}
}
void AnimateVideoImage(const ReplacedBox::ReplaceImageCB& replace_image_cb,
ImageNode::Builder* image_node_builder) {
DCHECK(!replace_image_cb.is_null());
DCHECK(image_node_builder);
image_node_builder->source = replace_image_cb.Run();
if (image_node_builder->source) {
image_node_builder->destination_rect =
math::RectF(image_node_builder->source->GetSize());
}
}
// Animates an image, and letterboxes the image as well according to the aspect
// ratio of the resulting animated image versus the aspect ratio of the
// destination box size.
void AnimateVideoWithLetterboxing(
const ReplacedBox::ReplaceImageCB& replace_image_cb,
math::SizeF destination_size,
CompositionNode::Builder* composition_node_builder) {
DCHECK(!replace_image_cb.is_null());
DCHECK(composition_node_builder);
scoped_refptr<render_tree::Image> image = replace_image_cb.Run();
// If the image hasn't changed, then no need to change anything else. The
// image should be the first child (see AddLetterboxedImageToRenderTree()).
if (!composition_node_builder->children().empty()) {
render_tree::ImageNode* existing_image_node =
base::polymorphic_downcast<render_tree::ImageNode*>(
composition_node_builder->GetChild(0)->get());
if (existing_image_node->data().source.get() == image.get()) {
return;
}
}
// Reset the composition node from whatever it was before, we will recreate
// it anew in each animation frame.
*composition_node_builder = CompositionNode::Builder();
// TODO: Detect better when the intrinsic video size is used for the
// node size, and trigger a re-layout from the media element when the size
// changes.
if (image && 0 == destination_size.height()) {
destination_size = image->GetSize();
}
if (image) {
AddLetterboxedImageToRenderTree(
GetLetterboxDimensions(image->GetSize(), destination_size), image,
composition_node_builder);
}
}
void AnimateLottie(
const ReplacedBox::ReplaceImageCB& replace_image_cb,
const render_tree::LottieAnimation::LottieProperties& lottie_properties,
math::RectF destination_rect, LottieNode::Builder* node_builder,
base::TimeDelta time_elapsed) {
scoped_refptr<render_tree::Image> animation = replace_image_cb.Run();
render_tree::LottieAnimation* lottie =
base::polymorphic_downcast<render_tree::LottieAnimation*>(
animation.get());
lottie->BeginRenderFrame(lottie_properties);
node_builder->animation = lottie;
node_builder->destination_rect = destination_rect;
node_builder->animation_time = time_elapsed;
}
} // namespace
void ReplacedBox::RenderAndAnimateContent(
CompositionNode::Builder* border_node_builder,
ContainerBox* stacking_context) const {
if (computed_style()->visibility() != cssom::KeywordValue::GetVisible()) {
return;
}
if (replace_image_cb_.is_null()) {
return;
}
if (replaced_box_mode_ == base::nullopt) {
// If we don't have a data stream associated with this video [yet], then
// we don't yet know if it is punched out or not, and so render black.
border_node_builder->AddChild(new RectNode(
math::RectF(content_box_size()),
std::unique_ptr<render_tree::Brush>(new render_tree::SolidColorBrush(
render_tree::ColorRGBA(0.0f, 0.0f, 0.0f, 1.0f)))));
// Nothing to render.
return;
}
if (*replaced_box_mode_ == ReplacedBox::ReplacedBoxMode::kLottie) {
AnimateNode::Builder animate_node_builder;
scoped_refptr<LottieNode> lottie_node =
new LottieNode(nullptr, math::RectF());
animate_node_builder.Add(
lottie_node,
base::Bind(&AnimateLottie, replace_image_cb_, *lottie_properties_,
math::RectF(content_box_size())));
border_node_builder->AddChild(
new AnimateNode(animate_node_builder, lottie_node));
return;
}
const cssom::MapToMeshFunction* mtm_filter_function =
cssom::MapToMeshFunction::ExtractFromFilterList(
computed_style()->filter());
// Map-to-mesh is only supported with decode-to-texture videos.
const bool supports_mtm =
replaced_box_mode_ &&
*replaced_box_mode_ == ReplacedBox::ReplacedBoxMode::kVideo;
if (supports_mtm && mtm_filter_function &&
mtm_filter_function->mesh_spec().mesh_type() !=
cssom::MapToMeshFunction::kRectangular) {
RenderAndAnimateContentWithMapToMesh(border_node_builder,
mtm_filter_function);
} else {
RenderAndAnimateContentWithLetterboxing(border_node_builder);
}
}
void ReplacedBox::UpdateContentSizeAndMargins(
const LayoutParams& layout_params) {
base::Optional<LayoutUnit> maybe_width = GetUsedWidthIfNotAuto(
computed_style(), layout_params.containing_block_size, NULL);
base::Optional<LayoutUnit> maybe_height = GetUsedHeightIfNotAuto(
computed_style(), layout_params.containing_block_size, NULL);
if (layout_params.freeze_width) {
maybe_width = width();
}
if (layout_params.freeze_height) {
maybe_height = height();
}
base::Optional<LayoutUnit> maybe_left = GetUsedLeftIfNotAuto(
computed_style(), layout_params.containing_block_size);
base::Optional<LayoutUnit> maybe_top = GetUsedTopIfNotAuto(
computed_style(), layout_params.containing_block_size);
if (IsAbsolutelyPositioned()) {
// TODO: Implement CSS section 10.3.8, see
// https://www.w3.org/TR/CSS21/visudet.html#abs-replaced-width.
set_left(maybe_left.value_or(LayoutUnit(GetStaticPositionLeft())));
set_top(maybe_top.value_or(LayoutUnit(GetStaticPositionTop())));
}
// Note that computed height may be "auto", even if it is specified as a
// percentage (depending on conditions of the containing block). See details
// in the spec. https://www.w3.org/TR/CSS22/visudet.html#the-height-property
if (!maybe_height) {
LOG(ERROR) << "ReplacedBox element has computed height \"auto\"!";
}
if (!maybe_width) {
LOG(ERROR) << "ReplacedBox element has computed width \"auto\"!";
}
// In order for Cobalt to handle "auto" dimensions correctly for both punchout
// and decode-to-texture we need to use the content's intrinsic dimensions &
// ratio rather than using the content_box_size directly. Until this
// functionality is found to be useful, we avoid the extra complexity
// introduced by its implementation.
if (!maybe_height || !maybe_width) {
LOG(ERROR)
<< "Cobalt ReplacedBox does not handle \"auto\" dimensions correctly! "
"\"auto\" dimensions are updated using the intrinsic dimensions of "
"the content (e.g. video width/height), which is often not what is "
"intended.";
}
if (!maybe_width) {
if (!maybe_height) {
if (maybe_intrinsic_width_) {
// If "height" and "width" both have computed values of "auto" and
// the element also has an intrinsic width, then that intrinsic width
// is the used value of "width".
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
set_width(*maybe_intrinsic_width_);
} else if (maybe_intrinsic_height_) {
// If "height" and "width" both have computed values of "auto" and
// the element has no intrinsic width, but does have an intrinsic height
// and intrinsic ratio then the used value of "width" is:
// (intrinsic height) * (intrinsic ratio)
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
set_width(*maybe_intrinsic_height_ * intrinsic_ratio_);
} else {
// Otherwise, if "width" has a computed value of "auto", but none of
// the conditions above are met, then the used value of "width" becomes
// 300px.
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
set_width(LayoutUnit(kFallbackWidth));
}
} else {
// If "width" has a computed value of "auto", "height" has some other
// computed value, and the element does have an intrinsic ratio then
// the used value of "width" is:
// (used height) * (intrinsic ratio)
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
set_width(*maybe_height * intrinsic_ratio_);
}
} else {
set_width(*maybe_width);
}
if (!maybe_height) {
if (!maybe_width && maybe_intrinsic_height_) {
// If "height" and "width" both have computed values of "auto" and
// the element also has an intrinsic height, then that intrinsic height
// is the used value of "height".
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
set_height(*maybe_intrinsic_height_);
} else {
// Otherwise, if "height" has a computed value of "auto", and the element
// has an intrinsic ratio then the used value of "height" is:
// (used width) / (intrinsic ratio)
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
set_height(width() / intrinsic_ratio_);
}
} else {
set_height(*maybe_height);
}
if (!maybe_width && !maybe_height) {
// For replaced elements with an intrinsic ratio and both 'width' and
// 'height' specified as 'auto', the algorithm is as described in
// https://www.w3.org/TR/CSS21/visudet.html#min-max-widths.
base::Optional<LayoutUnit> maybe_max_width = GetUsedMaxWidthIfNotNone(
computed_style(), layout_params.containing_block_size, NULL);
LayoutUnit min_width =
GetUsedMinWidthIfNotAuto(computed_style(),
layout_params.containing_block_size, NULL)
.value_or(LayoutUnit());
base::Optional<LayoutUnit> maybe_max_height = GetUsedMaxHeightIfNotNone(
computed_style(), layout_params.containing_block_size);
LayoutUnit min_height =
GetUsedMinHeightIfNotAuto(computed_style(),
layout_params.containing_block_size)
.value_or(LayoutUnit());
// The values w and h stand for the results of the width and height
// computations ignoring the 'min-width', 'min-height', 'max-width' and
// 'max-height' properties. Normally these are the intrinsic width and
// height, but they may not be in the case of replaced elements with
// intrinsic ratios.
// https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
LayoutUnit w = width();
LayoutUnit h = height();
// Take the max-width and max-height as max(min, max) so that min <= max
// holds true.
// https://www.w3.org/TR/CSS21/visudet.html#min-max-widths
base::Optional<LayoutUnit> max_height;
bool h_greater_than_max_height = false;
if (maybe_max_height) {
max_height = std::max(min_height, *maybe_max_height);
h_greater_than_max_height = h > *max_height;
}
base::Optional<LayoutUnit> max_width;
bool w_greater_than_max_width = false;
if (maybe_max_width) {
max_width = std::max(min_width, *maybe_max_width);
w_greater_than_max_width = w > *max_width;
}
// This block sets resolved width and resolved height values according to
// the table listing a number of different constraint violations in
// https://www.w3.org/TR/CSS21/visudet.html#min-max-widths.
if (w_greater_than_max_width) {
if (h_greater_than_max_height) {
LayoutUnit max_width_ratio = *max_width / w.toFloat();
LayoutUnit max_height_ratio = *max_height / h.toFloat();
if (max_width_ratio > max_height_ratio) {
// Constraint: (w > max-width) and (h > max-height), where
// (max-width/w > max-height/h)
set_width(
std::max(min_width, *max_height * (w.toFloat() / h.toFloat())));
set_height(*max_height);
} else {
// Constraint: (w > max-width) and (h > max-height), where
// (max-width/w <= max-height/h)
set_width(*max_width);
set_height(
std::max(min_height, *max_width * (h.toFloat() / w.toFloat())));
}
} else { // not h_greater_than_max_height
if (h < min_height) {
// Constraint: (w > max-width) and (h < min-height)
set_width(*max_width);
set_height(min_height);
} else { // not h < min_height
// Constraint: w > max-width
set_width(*max_width);
set_height(
std::max(*max_width * (h.toFloat() / w.toFloat()), min_height));
}
}
} else { // not w_greater_than_max_width
if (w < min_width) {
if (h_greater_than_max_height) {
// Constraint: (w < min-width) and (h > max-height)
set_width(min_width);
set_height(*max_height);
} else { // not h_greater_than_max_height
if (h < min_height) {
LayoutUnit min_width_ratio = min_width / w.toFloat();
LayoutUnit min_height_ratio = min_height / h.toFloat();
if (min_width_ratio > min_height_ratio) {
// Constraint: (w < min-width) and (h < min-height), where
// (min-width/w > min-height/h)
set_width(min_width);
LayoutUnit height = min_width * (h.toFloat() / w.toFloat());
if (max_height) {
set_height(std::min(*max_height, height));
} else {
set_height(height);
}
} else {
// Constraint: (w < min-width) and (h < min-height), where
// (min-width/w <= min-height/h)
LayoutUnit width = min_height * (w.toFloat() / h.toFloat());
if (max_width) {
set_width(std::min(*max_width, width));
} else {
set_width(width);
}
set_height(min_height);
}
} else { // not h < min-height
// Constraint: w < min-width
set_width(min_width);
LayoutUnit height = min_width * (h.toFloat() / w.toFloat());
if (max_height) {
set_height(std::min(height, *max_height));
} else {
set_height(height);
}
}
}
} else { // not w < min_width
if (h_greater_than_max_height) {
// Constraint: h > max-height
set_width(
std::max(*max_height * (w.toFloat() / h.toFloat()), min_width));
set_height(*max_height);
} else { // not h_greater_than_max_height
if (h < min_height) {
// Constraint: h < min-height
LayoutUnit width = min_height * (w.toFloat() / h.toFloat());
if (max_width) {
set_width(std::min(width, *max_width));
} else {
set_width(width);
}
set_height(min_height);
} else { // not h < min_height
// Constraint: none
// Do nothing (keep w and h).
}
}
}
}
}
// The horizontal margin rules are difference for block level replaced boxes
// versus inline level replaced boxes.
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width
// https://www.w3.org/TR/CSS21/visudet.html#block-replaced-width
base::Optional<LayoutUnit> maybe_margin_left = GetUsedMarginLeftIfNotAuto(
computed_style(), layout_params.containing_block_size);
base::Optional<LayoutUnit> maybe_margin_right = GetUsedMarginRightIfNotAuto(
computed_style(), layout_params.containing_block_size);
LayoutUnit border_box_width = GetBorderBoxWidth();
UpdateHorizontalMargins(layout_params.containing_block_direction,
layout_params.containing_block_size.width(),
border_box_width, maybe_margin_left,
maybe_margin_right);
base::Optional<LayoutUnit> maybe_margin_top = GetUsedMarginTopIfNotAuto(
computed_style(), layout_params.containing_block_size);
base::Optional<LayoutUnit> maybe_margin_bottom = GetUsedMarginBottomIfNotAuto(
computed_style(), layout_params.containing_block_size);
// If "margin-top", or "margin-bottom" are "auto", their used value is 0.
// https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-height
set_margin_top(maybe_margin_top.value_or(LayoutUnit()));
set_margin_bottom(maybe_margin_bottom.value_or(LayoutUnit()));
}
#ifdef COBALT_BOX_DUMP_ENABLED
void ReplacedBox::DumpProperties(std::ostream* stream) const {
Box::DumpProperties(stream);
*stream << "text_position=" << text_position_ << " "
<< "bidi_level=" << paragraph_->GetBidiLevel(text_position_) << " ";
}
#endif // COBALT_BOX_DUMP_ENABLED
void ReplacedBox::RenderAndAnimateContentWithMapToMesh(
CompositionNode::Builder* border_node_builder,
const cssom::MapToMeshFunction* mtm_function) const {
// First setup the animated image node.
AnimateNode::Builder animate_node_builder;
scoped_refptr<ImageNode> image_node = new ImageNode(nullptr);
animate_node_builder.Add(image_node,
base::Bind(&AnimateVideoImage, replace_image_cb_));
scoped_refptr<AnimateNode> animate_node =
new AnimateNode(animate_node_builder, image_node);
// Then wrap the animated image into a MapToMeshFilter render tree node.
const cssom::MapToMeshFunction::MeshSpec& spec = mtm_function->mesh_spec();
const scoped_refptr<cssom::KeywordValue>& stereo_mode_keyword_value =
mtm_function->stereo_mode();
render_tree::StereoMode stereo_mode =
ReadStereoMode(stereo_mode_keyword_value);
scoped_refptr<render_tree::Node> filter_node;
// Fetch either the embedded equirectangular mesh or a custom one depending
// on the spec.
MapToMeshFilter::Builder builder;
if (spec.mesh_type() == cssom::MapToMeshFunction::kUrls) {
// Custom mesh URLs.
// Set a default mesh (in case no resolution-specific mesh matches).
cssom::URLValue* default_url_value =
base::polymorphic_downcast<cssom::URLValue*>(spec.mesh_url().get());
GURL default_url(default_url_value->value());
if (!default_url.is_valid()) {
DLOG(WARNING) << kWarningInvalidMeshUrl;
return;
}
scoped_refptr<loader::mesh::MeshProjection> default_mesh_projection(
used_style_provider()->ResolveURLToMeshProjection(default_url));
if (!default_mesh_projection) {
DLOG(WARNING) << kWarningLoadingMeshFailed;
return;
}
builder.SetDefaultMeshes(
default_mesh_projection->GetMesh(
loader::mesh::MeshProjection::kLeftEyeOrMonoCollection),
default_mesh_projection->GetMesh(
loader::mesh::MeshProjection::kRightEyeCollection));
// Lookup among the list of resolutions for a match and use that mesh URL.
const cssom::MapToMeshFunction::ResolutionMatchedMeshListBuilder& meshes =
spec.resolution_matched_meshes();
for (size_t i = 0; i < meshes.size(); i++) {
cssom::URLValue* url_value = base::polymorphic_downcast<cssom::URLValue*>(
meshes[i]->mesh_url().get());
GURL url(url_value->value());
if (!url.is_valid()) {
DLOG(WARNING) << kWarningInvalidMeshUrl;
return;
}
scoped_refptr<loader::mesh::MeshProjection> mesh_projection(
used_style_provider()->ResolveURLToMeshProjection(url));
if (!mesh_projection) {
DLOG(WARNING) << kWarningLoadingMeshFailed;
}
TRACE_EVENT2("cobalt::layout",
"ReplacedBox::RenderAndAnimateContentWithMapToMesh()",
"height", meshes[i]->height_match(), "crc",
mesh_projection->crc().value_or(-1));
builder.AddResolutionMatchedMeshes(
math::Size(meshes[i]->width_match(), meshes[i]->height_match()),
mesh_projection->GetMesh(
loader::mesh::MeshProjection::kLeftEyeOrMonoCollection),
mesh_projection->GetMesh(
loader::mesh::MeshProjection::kRightEyeCollection));
}
} else if (spec.mesh_type() == cssom::MapToMeshFunction::kEquirectangular) {
GURL url(kEquirectangularMeshURL);
scoped_refptr<loader::mesh::MeshProjection> mesh_projection(
used_style_provider()->ResolveURLToMeshProjection(url));
if (!mesh_projection) {
DLOG(WARNING) << kWarningLoadingMeshFailed;
}
builder.SetDefaultMeshes(
mesh_projection->GetMesh(
loader::mesh::MeshProjection::kLeftEyeOrMonoCollection),
mesh_projection->GetMesh(
loader::mesh::MeshProjection::kRightEyeCollection));
}
filter_node =
new FilterNode(MapToMeshFilter(stereo_mode, builder), animate_node);
#if !SB_HAS(VIRTUAL_REALITY)
// Attach a 3D camera to the map-to-mesh node, so the rendering of its
// content can be transformed.
border_node_builder->AddChild(
used_style_provider()->attach_camera_node_function().Run(
filter_node, mtm_function->horizontal_fov_in_radians(),
mtm_function->vertical_fov_in_radians()));
#else
// Camera node unnecessary in VR, since the 3D scene is completely
// immersive, and the whole render tree is placed within it and subject to
// camera transforms, not just the map-to-mesh node.
// TODO: Reconcile both paths with respect to this if Cobalt adopts a global
// camera or a document-wide notion of 3D space layout.
border_node_builder->AddChild(filter_node);
#endif
}
void ReplacedBox::RenderAndAnimateContentWithLetterboxing(
CompositionNode::Builder* border_node_builder) const {
CompositionNode::Builder composition_node_builder(
math::Vector2dF((border_left_width() + padding_left()).toFloat(),
(border_top_width() + padding_top()).toFloat()));
scoped_refptr<CompositionNode> composition_node =
new CompositionNode(composition_node_builder);
if (*replaced_box_mode_ == ReplacedBox::ReplacedBoxMode::kPunchOutVideo) {
LetterboxDimensions letterbox_dims =
GetLetterboxDimensions(content_size_, content_box_size());
AddLetterboxedPunchThroughVideoNodeToRenderTree(
letterbox_dims, set_bounds_cb_, border_node_builder);
} else {
AnimateNode::Builder animate_node_builder;
animate_node_builder.Add(composition_node,
base::Bind(&AnimateVideoWithLetterboxing,
replace_image_cb_, content_box_size()));
border_node_builder->AddChild(
new AnimateNode(animate_node_builder, composition_node));
}
}
} // namespace layout
} // namespace cobalt