blob: d7a346f16b264f01e49db47b9ce953adb73f4e2a [file] [log] [blame]
// Copyright 2015 Google Inc. 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/inline_formatting_context.h"
#include <algorithm>
#include "base/logging.h"
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/layout/line_box.h"
#include "cobalt/layout/used_style.h"
namespace cobalt {
namespace layout {
InlineFormattingContext::InlineFormattingContext(
const scoped_refptr<cssom::PropertyValue>& line_height,
const render_tree::FontMetrics& font_metrics,
const LayoutParams& layout_params, BaseDirection base_direction,
const scoped_refptr<cssom::PropertyValue>& text_align,
const scoped_refptr<cssom::PropertyValue>& font_size,
LayoutUnit text_indent_offset, LayoutUnit ellipsis_width)
: line_height_(line_height),
font_metrics_(font_metrics),
layout_params_(layout_params),
base_direction_(base_direction),
text_align_(text_align),
font_size_(font_size),
text_indent_offset_(text_indent_offset),
ellipsis_width_(ellipsis_width),
line_count_(0) {
CreateLineBox();
}
InlineFormattingContext::~InlineFormattingContext() {}
Box* InlineFormattingContext::TryAddChildAndMaybeWrap(Box* child_box) {
// When an inline box exceeds the width of a line box, it is split into
// several boxes and these boxes are distributed across several line boxes.
// https://www.w3.org/TR/CSS21/visuren.html#inline-formatting
//
// We tackle this problem one line at a time.
Box* child_box_before_wrap = line_box_->TryAddChildAndMaybeWrap(child_box);
// If |child_box_before_wrap| is non-NULL, then a line wrap has occurred and a
// new line box must be created.
if (child_box_before_wrap) {
CreateLineBox();
}
return child_box_before_wrap;
}
void InlineFormattingContext::EndUpdates() {
// Treat the end of child boxes almost as an explicit line break,
// but don't create the new line box.
DestroyLineBox();
// The shrink-to-fit width is:
// min(max(preferred minimum width, available width), preferred width).
// https://www.w3.org/TR/CSS21/visudet.html#float-width
//
// Naive solution of the above expression would require two layout passes:
// one to calculate the "preferred minimum width" and another one to
// calculate the "preferred width". It is possible to save one layout pass
// taking into account that:
// - an exact value of "preferred width" does not matter if "available
// width" cannot acommodate it;
// - the inline formatting context has more than one line if and only if
// the "preferred width" is greater than the "available width";
// - "preferred minimum" and "preferred" widths are equal when an inline
// formatting context has only one line.
set_shrink_to_fit_width(std::max(
preferred_min_width_, line_count_ > 1
? layout_params_.containing_block_size.width()
: LayoutUnit()));
}
const std::vector<math::Vector2dF>&
InlineFormattingContext::GetEllipsesCoordinates() const {
return ellipses_coordinates_;
}
void InlineFormattingContext::CreateLineBox() {
if (line_box_) {
DestroyLineBox();
}
// "'Text-indent' only affects a line if it is the first formatted line of an
// element."
// https://www.w3.org/TR/CSS21/text.html#propdef-text-indent
LayoutUnit line_indent_offset =
line_count_ == 0 ? text_indent_offset_ : LayoutUnit();
// Line boxes are stacked with no vertical separation and they never
// overlap.
// https://www.w3.org/TR/CSS21/visuren.html#inline-formatting
line_box_ = make_scoped_ptr(
new LineBox(auto_height(), false, line_height_, font_metrics_, true, true,
layout_params_, base_direction_, text_align_, font_size_,
line_indent_offset, ellipsis_width_));
}
void InlineFormattingContext::DestroyLineBox() {
line_box_->EndUpdates();
// The baseline of an "inline-block" is the baseline of its last line box
// in the normal flow, unless it has no in-flow line boxes.
// https://www.w3.org/TR/CSS21/visudet.html#line-height
if (line_box_->LineExists()) {
++line_count_;
preferred_min_width_ =
std::max(preferred_min_width_, line_box_->shrink_to_fit_width());
// If "height" is "auto", the used value is the distance from box's top
// content edge to the bottom edge of the last line box, if the box
// establishes an inline formatting context with one or more lines.
// https://www.w3.org/TR/CSS21/visudet.html#normal-block
set_auto_height(line_box_->top() + line_box_->height());
set_baseline_offset_from_top_content_edge(
line_box_->top() + line_box_->baseline_offset_from_top());
if (line_box_->IsEllipsisPlaced()) {
ellipses_coordinates_.push_back(line_box_->GetEllipsisCoordinates());
}
}
line_box_.reset();
}
} // namespace layout
} // namespace cobalt