summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/layout/ng/ng_floats_utils.cc
blob: 0adf9fbfd3bf36c594377293b0a5f1053a58ca35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h"

#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/min_max_size.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"

namespace blink {
namespace {

// Adjusts the provided offset to the top edge alignment rule.
// Top edge alignment rule: the outer top of a floating box may not be higher
// than the outer top of any block or floated box generated by an element
// earlier in the source document.
NGBfcOffset AdjustToTopEdgeAlignmentRule(
    const NGExclusionSpace& exclusion_space,
    const NGBfcOffset& offset) {
  NGBfcOffset adjusted_offset = offset;
  adjusted_offset.block_offset = std::max(
      adjusted_offset.block_offset, exclusion_space.LastFloatBlockStart());

  return adjusted_offset;
}

NGLayoutOpportunity FindLayoutOpportunityForFloat(
    const NGBfcOffset& origin_offset,
    const NGExclusionSpace& exclusion_space,
    const NGUnpositionedFloat& unpositioned_float,
    LayoutUnit inline_size) {
  NGBfcOffset adjusted_origin_point =
      AdjustToTopEdgeAlignmentRule(exclusion_space, origin_offset);
  LayoutUnit clearance_offset =
      exclusion_space.ClearanceOffset(unpositioned_float.ClearType());

  AdjustToClearance(clearance_offset, &adjusted_origin_point);

  NGLogicalSize float_size(inline_size + unpositioned_float.margins.InlineSum(),
                           LayoutUnit());
  // TODO(ikilpatrick): Don't include the block-start margin of a float which
  // has fragmented.
  return exclusion_space.FindLayoutOpportunity(
      adjusted_origin_point, unpositioned_float.available_size.inline_size,
      float_size);
}

scoped_refptr<NGConstraintSpace> CreateConstraintSpaceForFloatFromBuilder(
    const NGUnpositionedFloat& unpositioned_float,
    NGConstraintSpaceBuilder& builder) {
  const ComputedStyle& style = unpositioned_float.node.Style();
  return builder.SetPercentageResolutionSize(unpositioned_float.percentage_size)
      .SetAvailableSize(unpositioned_float.available_size)
      .SetIsNewFormattingContext(true)
      .SetIsShrinkToFit(true)
      .SetTextDirection(style.Direction())
      .ToConstraintSpace(style.GetWritingMode());
}

// Creates a constraint space for an unpositioned float, with the intent to
// position it.
scoped_refptr<NGConstraintSpace> CreateConstraintSpaceForFloat(
    const NGUnpositionedFloat& unpositioned_float,
    const NGConstraintSpace& parent_space,
    LayoutUnit origin_block_offset) {
  NGConstraintSpaceBuilder builder(parent_space);

  DCHECK_EQ(unpositioned_float.node.Style().GetWritingMode(),
            parent_space.GetWritingMode());
  if (parent_space.HasBlockFragmentation()) {
    LayoutUnit fragmentation_offset =
        parent_space.FragmentainerSpaceAtBfcStart() - origin_block_offset;
    builder.SetFragmentainerBlockSize(parent_space.FragmentainerBlockSize());
    builder.SetFragmentainerSpaceAtBfcStart(fragmentation_offset);
    builder.SetFragmentationType(parent_space.BlockFragmentationType());
  } else {
    builder.SetFragmentationType(NGFragmentationType::kFragmentNone);
  }
  return CreateConstraintSpaceForFloatFromBuilder(unpositioned_float, builder);
}

// Creates a constraint space for an unpositioned float, with the intent to
// simply calculate its inline size.
scoped_refptr<NGConstraintSpace>
CreateConstraintSpaceForFloatForInlineSizeCalculation(
    const NGUnpositionedFloat& unpositioned_float,
    const NGConstraintSpace& parent_space) {
  NGConstraintSpaceBuilder builder(parent_space);
  return CreateConstraintSpaceForFloatFromBuilder(unpositioned_float, builder);
}

std::unique_ptr<NGExclusionShapeData> CreateExclusionShapeData(
    const NGBoxStrut& margins,
    const LayoutBox* layout_box,
    const NGUnpositionedFloat& unpositioned_float,
    const NGConstraintSpace& parent_space,
    TextDirection direction) {
  DCHECK(layout_box->GetShapeOutsideInfo());

  // We make the margins on the shape-data relative to line-left/line-right.
  NGBoxStrut new_margins(margins.LineLeft(direction),
                         margins.LineRight(direction), margins.block_start,
                         margins.block_end);
  NGBoxStrut shape_insets;

  const ComputedStyle& style = layout_box->StyleRef();
  switch (style.ShapeOutside()->CssBox()) {
    case CSSBoxType::kMissing:
    case CSSBoxType::kMargin:
      shape_insets -= new_margins;
      break;
    case CSSBoxType::kBorder:
      break;
    case CSSBoxType::kPadding:
      shape_insets =
          ComputeBorders(*CreateConstraintSpaceForFloatForInlineSizeCalculation(
                             unpositioned_float, parent_space),
                         style)
              .ConvertToPhysical(style.GetWritingMode(), style.Direction())
              .ConvertToLogical(parent_space.GetWritingMode(),
                                TextDirection::kLtr);
      break;
    case CSSBoxType::kContent:
      const scoped_refptr<NGConstraintSpace> space =
          CreateConstraintSpaceForFloatForInlineSizeCalculation(
              unpositioned_float, parent_space);
      NGBoxStrut border_padding =
          ComputeBorders(*space, style) + ComputePadding(*space, style);
      shape_insets =
          border_padding
              .ConvertToPhysical(style.GetWritingMode(), style.Direction())
              .ConvertToLogical(parent_space.GetWritingMode(),
                                TextDirection::kLtr);
      break;
  }

  return std::make_unique<NGExclusionShapeData>(layout_box, new_margins,
                                                shape_insets);
}

// Creates an exclusion from the fragment that will be placed in the provided
// layout opportunity.
scoped_refptr<NGExclusion> CreateExclusion(
    const NGFragment& fragment,
    const NGBfcOffset& float_margin_bfc_offset,
    const NGBoxStrut& margins,
    const LayoutBox* layout_box,
    const NGUnpositionedFloat& unpositioned_float,
    const NGConstraintSpace& parent_space,
    TextDirection direction,
    EFloat type) {
  // TODO(ikilpatrick): Don't include the block-start margin of a float which
  // has fragmented.
  NGBfcOffset start_offset = float_margin_bfc_offset;

  NGBfcOffset end_offset(
      /* line_offset */ start_offset.line_offset +
          (fragment.InlineSize() + margins.InlineSum()).ClampNegativeToZero(),
      /* block_offset */ start_offset.block_offset +
          (fragment.BlockSize() + margins.BlockSum()).ClampNegativeToZero());

  std::unique_ptr<NGExclusionShapeData> shape_data =
      layout_box->GetShapeOutsideInfo()
          ? CreateExclusionShapeData(margins, layout_box, unpositioned_float,
                                     parent_space, direction)
          : nullptr;

  return NGExclusion::Create(NGBfcRect(start_offset, end_offset), type,
                             std::move(shape_data));
}

}  // namespace

LayoutUnit ComputeInlineSizeForUnpositionedFloat(
    const NGConstraintSpace& parent_space,
    NGUnpositionedFloat* unpositioned_float) {
  DCHECK(unpositioned_float);

  const ComputedStyle& style = unpositioned_float->node.Style();

  bool is_same_writing_mode =
      style.GetWritingMode() == parent_space.GetWritingMode();

  // If we've already performed layout on the unpositioned float, just return
  // the cached value.
  if (unpositioned_float->layout_result) {
    // TODO(layout-ng): Should this use IsParallelWritingMode()?
    DCHECK(!is_same_writing_mode);
    DCHECK(unpositioned_float->layout_result->PhysicalFragment());
    return NGFragment(parent_space.GetWritingMode(),
                      *unpositioned_float->layout_result->PhysicalFragment())
        .InlineSize();
  }

  const scoped_refptr<NGConstraintSpace> space =
      CreateConstraintSpaceForFloatForInlineSizeCalculation(*unpositioned_float,
                                                            parent_space);

  // If the float has the same writing mode as the block formatting context we
  // shouldn't perform a full layout just yet. Our position may determine where
  // we fragment. If we cannot use NG for layout of the node, though, we do have
  // to lay out (and just read out the inline size and then discard the result),
  // because NG cannot figure out the size of such objects on its own,
  // especially not for tables.
  if (is_same_writing_mode && unpositioned_float->node.CanUseNewLayout()) {
    base::Optional<MinMaxSize> min_max_size;
    if (NeedMinMaxSize(*space.get(), style)) {
      MinMaxSizeInput zero_input;  // Floats do not intrude into floats.
      min_max_size = unpositioned_float->node.ComputeMinMaxSize(
          style.GetWritingMode(), zero_input);
    }
    return ComputeInlineSizeForFragment(*space.get(), style, min_max_size);
  }

  // A float which has a different writing mode can't fragment, and we
  // (probably) need to perform a full layout in order to correctly determine
  // its inline size. We are able to cache this result on the unpositioned_float
  // at this stage. If the writing mode is the same, on the other hand, we
  // cannot keep the result around, since the node may fragment (and we need to
  // find its final block position before we can do so).
  auto result = unpositioned_float->node.Layout(*space);
  if (!is_same_writing_mode) {
    // If we are performing layout on a float to determine its inline size it
    // should never have fragmented.
    DCHECK(!unpositioned_float->token);
    unpositioned_float->layout_result = result;
  }

  DCHECK(result->PhysicalFragment());
  const auto& fragment = *result->PhysicalFragment();

  DCHECK(fragment.BreakToken()->IsFinished());

  return NGFragment(parent_space.GetWritingMode(), fragment).InlineSize();
}

NGPositionedFloat PositionFloat(LayoutUnit origin_block_offset,
                                LayoutUnit parent_bfc_block_offset,
                                NGUnpositionedFloat* unpositioned_float,
                                const NGConstraintSpace& parent_space,
                                NGExclusionSpace* exclusion_space) {
  DCHECK(unpositioned_float);
  LayoutUnit inline_size =
      ComputeInlineSizeForUnpositionedFloat(parent_space, unpositioned_float);

  NGBfcOffset origin_offset = {unpositioned_float->origin_bfc_line_offset,
                               origin_block_offset};

  // Find a layout opportunity that will fit our float.
  NGLayoutOpportunity opportunity = FindLayoutOpportunityForFloat(
      origin_offset, *exclusion_space, *unpositioned_float, inline_size);

#if DCHECK_IS_ON()
  bool is_same_writing_mode =
      unpositioned_float->node.Style().GetWritingMode() ==
      parent_space.GetWritingMode();
#endif

  scoped_refptr<NGLayoutResult> layout_result;
  // We should only have a fragment if its writing mode is different, i.e. it
  // can't fragment.
  if (unpositioned_float->layout_result) {
#if DCHECK_IS_ON()
    DCHECK(!is_same_writing_mode);
#endif
    layout_result = unpositioned_float->layout_result;
  } else {
#if DCHECK_IS_ON()
    DCHECK(is_same_writing_mode);
#endif
    scoped_refptr<NGConstraintSpace> space = CreateConstraintSpaceForFloat(
        *unpositioned_float, parent_space, origin_block_offset);
    layout_result = unpositioned_float->node.Layout(
        *space, unpositioned_float->token.get());
  }

  DCHECK(layout_result->PhysicalFragment());
  NGFragment float_fragment(parent_space.GetWritingMode(),
                            *layout_result->PhysicalFragment());

  LayoutUnit float_margin_box_inline_size =
      float_fragment.InlineSize() + unpositioned_float->margins.InlineSum();

  // Calculate the float's margin box BFC offset.
  NGBfcOffset float_margin_bfc_offset = opportunity.rect.start_offset;
  if (unpositioned_float->IsRight()) {
    float_margin_bfc_offset.line_offset +=
        (opportunity.rect.InlineSize() - float_margin_box_inline_size);
  }

  // Add the float as an exclusion.
  scoped_refptr<NGExclusion> exclusion = CreateExclusion(
      float_fragment, float_margin_bfc_offset, unpositioned_float->margins,
      ToLayoutBox(unpositioned_float->node.GetLayoutObject()),
      *unpositioned_float, parent_space, parent_space.Direction(),
      unpositioned_float->IsRight() ? EFloat::kRight : EFloat::kLeft);
  exclusion_space->Add(std::move(exclusion));

  // Adjust the float's bfc_offset to its border-box (instead of margin-box).
  NGBfcOffset float_bfc_offset(
      float_margin_bfc_offset.line_offset +
          unpositioned_float->margins.LineLeft(parent_space.Direction()),
      float_margin_bfc_offset.block_offset +
          unpositioned_float->margins.block_start);

  return NGPositionedFloat(std::move(layout_result), float_bfc_offset);
}

const Vector<NGPositionedFloat> PositionFloats(
    LayoutUnit origin_block_offset,
    LayoutUnit parent_bfc_block_offset,
    const Vector<scoped_refptr<NGUnpositionedFloat>>& unpositioned_floats,
    const NGConstraintSpace& space,
    NGExclusionSpace* exclusion_space) {
  Vector<NGPositionedFloat> positioned_floats;
  positioned_floats.ReserveCapacity(unpositioned_floats.size());

  for (auto& unpositioned_float : unpositioned_floats) {
    positioned_floats.push_back(
        PositionFloat(origin_block_offset, parent_bfc_block_offset,
                      unpositioned_float.get(), space, exclusion_space));
  }

  return positioned_floats;
}

void AddUnpositionedFloat(
    Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats,
    NGContainerFragmentBuilder* fragment_builder,
    scoped_refptr<NGUnpositionedFloat> unpositioned_float) {
  if (fragment_builder && !fragment_builder->BfcOffset()) {
    fragment_builder->AddAdjoiningFloatTypes(
        unpositioned_float->IsLeft() ? kFloatTypeLeft : kFloatTypeRight);
  }
  unpositioned_floats->push_back(std::move(unpositioned_float));
}

NGFloatTypes ToFloatTypes(EClear clear) {
  switch (clear) {
    default:
      NOTREACHED();
      FALLTHROUGH;
    case EClear::kNone:
      return kFloatTypeNone;
    case EClear::kLeft:
      return kFloatTypeLeft;
    case EClear::kRight:
      return kFloatTypeRight;
    case EClear::kBoth:
      return kFloatTypeBoth;
  };
}

}  // namespace blink