1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 //! CSS tables.
6 //!
7 //! This follows the "More Precise Definitions of Inline Layout and Table Layout" proposal written
8 //! by L. David Baron (Mozilla) here:
9 //!
10 //! http://dbaron.org/css/intrinsic/
11 //!
12 //! Hereafter this document is referred to as INTRINSIC.
13
14 #![deny(unsafe_code)]
15
16 use app_units::Au;
17 use block::{AbsoluteNonReplaced, BlockFlow, FloatNonReplaced, ISizeAndMarginsComputer, ISizeConstraintInput};
18 use block::{ISizeConstraintSolution, MarginsMayCollapseFlag};
19 use context::LayoutContext;
20 use display_list::{BlockFlowDisplayListBuilding, DisplayListBuildState, StackingContextCollectionFlags};
21 use display_list::StackingContextCollectionState;
22 use euclid::Point2D;
23 use floats::FloatKind;
24 use flow::{Flow, FlowClass, ImmutableFlowUtils, FlowFlags, OpaqueFlow};
25 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
26 use gfx_traits::print_tree::PrintTree;
27 use model::MaybeAuto;
28 use std::cmp::{max, min};
29 use std::fmt;
30 use std::ops::Add;
31 use style::computed_values::{position, table_layout};
32 use style::context::SharedStyleContext;
33 use style::logical_geometry::{LogicalRect, LogicalSize};
34 use style::properties::ComputedValues;
35 use style::values::CSSFloat;
36 use style::values::computed::LengthOrPercentageOrAuto;
37 use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize};
38
39 #[derive(Clone, Copy, Debug, Serialize)]
40 pub enum TableLayout {
41 Fixed,
42 Auto
43 }
44
45 #[allow(unsafe_code)]
46 unsafe impl ::flow::HasBaseFlow for TableWrapperFlow {}
47
48 /// A table wrapper flow based on a block formatting context.
49 #[derive(Serialize)]
50 #[repr(C)]
51 pub struct TableWrapperFlow {
52 pub block_flow: BlockFlow,
53
54 /// Intrinsic column inline sizes according to INTRINSIC § 4.1
55 pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
56
57 /// Table-layout property
58 pub table_layout: TableLayout,
59 }
60
61 impl TableWrapperFlow {
from_fragment(fragment: Fragment) -> TableWrapperFlow62 pub fn from_fragment(fragment: Fragment) -> TableWrapperFlow {
63 TableWrapperFlow::from_fragment_and_float_kind(fragment, None)
64 }
65
from_fragment_and_float_kind(fragment: Fragment, float_kind: Option<FloatKind>) -> TableWrapperFlow66 pub fn from_fragment_and_float_kind(fragment: Fragment, float_kind: Option<FloatKind>)
67 -> TableWrapperFlow {
68 let mut block_flow = BlockFlow::from_fragment_and_float_kind(fragment, float_kind);
69 let table_layout = if block_flow.fragment().style().get_table().table_layout ==
70 table_layout::T::Fixed {
71 TableLayout::Fixed
72 } else {
73 TableLayout::Auto
74 };
75 TableWrapperFlow {
76 block_flow: block_flow,
77 column_intrinsic_inline_sizes: vec!(),
78 table_layout: table_layout
79 }
80 }
81
border_padding_and_spacing(&mut self) -> (Au, Au)82 fn border_padding_and_spacing(&mut self) -> (Au, Au) {
83 let (mut table_border_padding, mut spacing) = (Au(0), Au(0));
84 for kid in self.block_flow.base.child_iter_mut() {
85 if kid.is_table() {
86 let kid_table = kid.as_table();
87 spacing = kid_table.total_horizontal_spacing();
88 table_border_padding =
89 kid_table.block_flow.fragment.border_padding.inline_start_end();
90 break
91 }
92 }
93 (table_border_padding, spacing)
94 }
95
96 // Instructs our first child, which is the table itself, to compute its border and padding.
97 //
98 // This is a little weird because we're computing border/padding/margins for our child,
99 // when normally the child computes it itself. But it has to be this way because the
100 // padding will affect where we place the child. This is an odd artifact of the way that
101 // tables are separated into table flows and table wrapper flows.
compute_border_and_padding_of_table(&mut self)102 fn compute_border_and_padding_of_table(&mut self) {
103 let available_inline_size = self.block_flow.base.block_container_inline_size;
104 for kid in self.block_flow.base.child_iter_mut() {
105 if !kid.is_table() {
106 continue
107 }
108
109 let kid_table = kid.as_mut_table();
110 let kid_block_flow = &mut kid_table.block_flow;
111 kid_block_flow.fragment.compute_border_and_padding(available_inline_size);
112 kid_block_flow.fragment.compute_block_direction_margins(available_inline_size);
113 kid_block_flow.fragment.compute_inline_direction_margins(available_inline_size);
114 return
115 }
116 }
117
118 /// Calculates table column sizes for automatic layout per INTRINSIC § 4.3.
calculate_table_column_sizes_for_automatic_layout( &mut self, intermediate_column_inline_sizes: &mut [IntermediateColumnInlineSize])119 fn calculate_table_column_sizes_for_automatic_layout(
120 &mut self,
121 intermediate_column_inline_sizes: &mut [IntermediateColumnInlineSize]) {
122 let available_inline_size = self.available_inline_size();
123
124 // Compute all the guesses for the column sizes, and sum them.
125 let mut total_guess = AutoLayoutCandidateGuess::new();
126 let guesses: Vec<AutoLayoutCandidateGuess> =
127 self.column_intrinsic_inline_sizes.iter().map(|column_intrinsic_inline_size| {
128 let guess = AutoLayoutCandidateGuess::from_column_intrinsic_inline_size(
129 column_intrinsic_inline_size,
130 available_inline_size);
131 total_guess = &total_guess + &guess;
132 guess
133 }).collect();
134
135 // Assign inline sizes.
136 let selection = SelectedAutoLayoutCandidateGuess::select(&total_guess,
137 available_inline_size);
138 let mut total_used_inline_size = Au(0);
139 for (intermediate_column_inline_size, guess) in
140 intermediate_column_inline_sizes.iter_mut().zip(guesses.iter()) {
141 intermediate_column_inline_size.size = guess.calculate(selection);
142 intermediate_column_inline_size.percentage = 0.0;
143 total_used_inline_size = total_used_inline_size + intermediate_column_inline_size.size
144 }
145
146 // Distribute excess inline-size if necessary per INTRINSIC § 4.4.
147 //
148 // FIXME(pcwalton, spec): How do I deal with fractional excess?
149 let excess_inline_size = available_inline_size - total_used_inline_size;
150 if excess_inline_size > Au(0) && selection ==
151 SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize {
152 let mut info = ExcessInlineSizeDistributionInfo::new();
153 for column_intrinsic_inline_size in &self.column_intrinsic_inline_sizes {
154 info.update(column_intrinsic_inline_size)
155 }
156
157 let mut total_distributed_excess_size = Au(0);
158 for (intermediate_column_inline_size, column_intrinsic_inline_size) in
159 intermediate_column_inline_sizes.iter_mut()
160 .zip(self.column_intrinsic_inline_sizes
161 .iter()) {
162 info.distribute_excess_inline_size_to_column(intermediate_column_inline_size,
163 column_intrinsic_inline_size,
164 excess_inline_size,
165 &mut total_distributed_excess_size)
166 }
167 total_used_inline_size = available_inline_size
168 }
169
170 self.set_inline_size(total_used_inline_size)
171 }
172
available_inline_size(&mut self) -> Au173 fn available_inline_size(&mut self) -> Au {
174 let available_inline_size = self.block_flow.fragment.border_box.size.inline;
175 let (table_border_padding, spacing) = self.border_padding_and_spacing();
176
177 // FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but
178 // says "the basic idea is the same as the shrink-to-fit width that CSS2.1 defines". So we
179 // just use the shrink-to-fit inline size.
180 let available_inline_size = match self.block_flow.fragment.style().content_inline_size() {
181 LengthOrPercentageOrAuto::Auto => {
182 self.block_flow.get_shrink_to_fit_inline_size(available_inline_size) -
183 table_border_padding
184 }
185 // FIXME(mttr): This fixes #4421 without breaking our current reftests, but I'm not
186 // completely sure this is "correct".
187 //
188 // That said, `available_inline_size` is, as far as I can tell, equal to the table's
189 // computed width property (W) and is used from this point forward in a way that seems
190 // to correspond with CSS 2.1 § 17.5.2.2 under "Column and caption widths influence the
191 // final table width as follows: …"
192 _ => available_inline_size,
193 };
194 available_inline_size - spacing
195 }
196
set_inline_size(&mut self, total_used_inline_size: Au)197 fn set_inline_size(&mut self, total_used_inline_size: Au) {
198 let (table_border_padding, spacing) = self.border_padding_and_spacing();
199 self.block_flow.fragment.border_box.size.inline = total_used_inline_size +
200 table_border_padding + spacing;
201 self.block_flow.base.position.size.inline = total_used_inline_size +
202 table_border_padding + spacing + self.block_flow.fragment.margin.inline_start_end();
203
204 let writing_mode = self.block_flow.base.writing_mode;
205 let container_mode = self.block_flow.base.block_container_writing_mode;
206
207 if writing_mode.is_bidi_ltr() != container_mode.is_bidi_ltr() {
208 // If our "start" direction is different from our parent flow, then `border_box.start.i`
209 // depends on `border_box.size.inline`.
210 self.block_flow.fragment.border_box.start.i =
211 self.block_flow.base.block_container_inline_size -
212 self.block_flow.fragment.margin.inline_end -
213 self.block_flow.fragment.border_box.size.inline;
214 }
215 }
216
compute_used_inline_size( &mut self, shared_context: &SharedStyleContext, parent_flow_inline_size: Au, intermediate_column_inline_sizes: &[IntermediateColumnInlineSize])217 fn compute_used_inline_size(
218 &mut self,
219 shared_context: &SharedStyleContext,
220 parent_flow_inline_size: Au,
221 intermediate_column_inline_sizes: &[IntermediateColumnInlineSize]) {
222 let (border_padding, spacing) = self.border_padding_and_spacing();
223 let minimum_width_of_all_columns =
224 intermediate_column_inline_sizes.iter()
225 .fold(border_padding + spacing,
226 |accumulator, intermediate_column_inline_sizes| {
227 accumulator + intermediate_column_inline_sizes.size
228 });
229 let preferred_width_of_all_columns =
230 self.column_intrinsic_inline_sizes.iter()
231 .fold(border_padding + spacing,
232 |accumulator, column_intrinsic_inline_sizes| {
233 accumulator + column_intrinsic_inline_sizes.preferred
234 });
235
236 // Delegate to the appropriate inline size computer to find the constraint inputs and write
237 // the constraint solutions in.
238 if self.block_flow.base.flags.is_float() {
239 let inline_size_computer = FloatedTable {
240 minimum_width_of_all_columns: minimum_width_of_all_columns,
241 preferred_width_of_all_columns: preferred_width_of_all_columns,
242 table_border_padding: border_padding,
243 };
244 let input =
245 inline_size_computer.compute_inline_size_constraint_inputs(&mut self.block_flow,
246 parent_flow_inline_size,
247 shared_context);
248
249 let solution = inline_size_computer.solve_inline_size_constraints(&mut self.block_flow,
250 &input);
251 inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow,
252 solution);
253 inline_size_computer.set_inline_position_of_flow_if_necessary(&mut self.block_flow,
254 solution);
255 return
256 }
257
258 if !self.block_flow.base.flags.contains(FlowFlags::INLINE_POSITION_IS_STATIC) {
259 let inline_size_computer = AbsoluteTable {
260 minimum_width_of_all_columns: minimum_width_of_all_columns,
261 preferred_width_of_all_columns: preferred_width_of_all_columns,
262 table_border_padding: border_padding,
263 };
264 let input =
265 inline_size_computer.compute_inline_size_constraint_inputs(&mut self.block_flow,
266 parent_flow_inline_size,
267 shared_context);
268
269 let solution = inline_size_computer.solve_inline_size_constraints(&mut self.block_flow,
270 &input);
271 inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow,
272 solution);
273 inline_size_computer.set_inline_position_of_flow_if_necessary(&mut self.block_flow,
274 solution);
275 return
276 }
277
278 let inline_size_computer = Table {
279 minimum_width_of_all_columns: minimum_width_of_all_columns,
280 preferred_width_of_all_columns: preferred_width_of_all_columns,
281 table_border_padding: border_padding,
282 };
283 let input =
284 inline_size_computer.compute_inline_size_constraint_inputs(&mut self.block_flow,
285 parent_flow_inline_size,
286 shared_context);
287
288 let solution = inline_size_computer.solve_inline_size_constraints(&mut self.block_flow,
289 &input);
290 inline_size_computer.set_inline_size_constraint_solutions(&mut self.block_flow, solution);
291 inline_size_computer.set_inline_position_of_flow_if_necessary(&mut self.block_flow,
292 solution);
293 }
294 }
295
296 impl Flow for TableWrapperFlow {
class(&self) -> FlowClass297 fn class(&self) -> FlowClass {
298 FlowClass::TableWrapper
299 }
300
as_mut_table_wrapper(&mut self) -> &mut TableWrapperFlow301 fn as_mut_table_wrapper(&mut self) -> &mut TableWrapperFlow {
302 self
303 }
304
as_table_wrapper(&self) -> &TableWrapperFlow305 fn as_table_wrapper(&self) -> &TableWrapperFlow {
306 self
307 }
308
as_mut_block(&mut self) -> &mut BlockFlow309 fn as_mut_block(&mut self) -> &mut BlockFlow {
310 &mut self.block_flow
311 }
312
as_block(&self) -> &BlockFlow313 fn as_block(&self) -> &BlockFlow {
314 &self.block_flow
315 }
316
mark_as_root(&mut self)317 fn mark_as_root(&mut self) {
318 self.block_flow.mark_as_root();
319 }
320
bubble_inline_sizes(&mut self)321 fn bubble_inline_sizes(&mut self) {
322 // Get the intrinsic column inline-sizes info from the table flow.
323 for kid in self.block_flow.base.child_iter_mut() {
324 debug_assert!(kid.is_table_caption() || kid.is_table());
325 if kid.is_table() {
326 let table = kid.as_table();
327 self.column_intrinsic_inline_sizes = table.column_intrinsic_inline_sizes.clone();
328 }
329 }
330
331 self.block_flow.bubble_inline_sizes();
332 }
333
assign_inline_sizes(&mut self, layout_context: &LayoutContext)334 fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
335 debug!("assign_inline_sizes({}): assigning inline_size for flow",
336 if self.block_flow.base.flags.is_float() {
337 "floated table_wrapper"
338 } else {
339 "table_wrapper"
340 });
341
342 let shared_context = layout_context.shared_context();
343 self.block_flow.initialize_container_size_for_root(shared_context);
344
345 let mut intermediate_column_inline_sizes = self.column_intrinsic_inline_sizes
346 .iter()
347 .map(|column_intrinsic_inline_size| {
348 IntermediateColumnInlineSize {
349 size: column_intrinsic_inline_size.minimum_length,
350 percentage: column_intrinsic_inline_size.percentage,
351 }
352 }).collect::<Vec<_>>();
353
354 // Our inline-size was set to the inline-size of the containing block by the flow's parent.
355 // Now compute the real value.
356 let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
357 if self.block_flow.base.flags.is_float() {
358 self.block_flow.float.as_mut().unwrap().containing_inline_size =
359 containing_block_inline_size;
360 }
361
362 // This has to be done before computing our inline size because `compute_used_inline_size`
363 // internally consults the border and padding of the table.
364 self.compute_border_and_padding_of_table();
365
366 self.compute_used_inline_size(shared_context,
367 containing_block_inline_size,
368 &intermediate_column_inline_sizes);
369
370 match self.table_layout {
371 TableLayout::Auto => {
372 self.calculate_table_column_sizes_for_automatic_layout(
373 &mut intermediate_column_inline_sizes)
374 }
375 TableLayout::Fixed => {}
376 }
377
378 let inline_start_content_edge = self.block_flow.fragment.border_box.start.i;
379 let content_inline_size = self.block_flow.fragment.border_box.size.inline;
380 let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end +
381 self.block_flow.fragment.margin.inline_end;
382
383 // In case of fixed layout, column inline-sizes are calculated in table flow.
384 let assigned_column_inline_sizes = match self.table_layout {
385 TableLayout::Fixed => None,
386 TableLayout::Auto => {
387 Some(intermediate_column_inline_sizes.iter().map(|sizes| {
388 ColumnComputedInlineSize {
389 size: sizes.size,
390 }
391 }).collect::<Vec<_>>())
392 }
393 };
394
395 match assigned_column_inline_sizes {
396 None => {
397 self.block_flow
398 .propagate_assigned_inline_size_to_children(shared_context,
399 inline_start_content_edge,
400 inline_end_content_edge,
401 content_inline_size,
402 |_, _, _, _, _, _| {})
403 }
404 Some(ref assigned_column_inline_sizes) => {
405 self.block_flow
406 .propagate_assigned_inline_size_to_children(shared_context,
407 inline_start_content_edge,
408 inline_end_content_edge,
409 content_inline_size,
410 |child_flow, _, _, _, _, _| {
411 if child_flow.class() == FlowClass::Table {
412 child_flow.as_mut_table().column_computed_inline_sizes =
413 assigned_column_inline_sizes.to_vec();
414 }
415 })
416 }
417 }
418
419 }
420
assign_block_size(&mut self, layout_context: &LayoutContext)421 fn assign_block_size(&mut self, layout_context: &LayoutContext) {
422 debug!("assign_block_size: assigning block_size for table_wrapper");
423 let remaining = self.block_flow.assign_block_size_block_base(
424 layout_context,
425 None,
426 MarginsMayCollapseFlag::MarginsMayNotCollapse);
427 debug_assert!(remaining.is_none());
428 }
429
compute_stacking_relative_position(&mut self, layout_context: &LayoutContext)430 fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
431 self.block_flow.compute_stacking_relative_position(layout_context)
432 }
433
place_float_if_applicable<'a>(&mut self)434 fn place_float_if_applicable<'a>(&mut self) {
435 self.block_flow.place_float_if_applicable()
436 }
437
assign_block_size_for_inorder_child_if_necessary(&mut self, layout_context: &LayoutContext, parent_thread_id: u8, content_box: LogicalRect<Au>) -> bool438 fn assign_block_size_for_inorder_child_if_necessary(&mut self,
439 layout_context: &LayoutContext,
440 parent_thread_id: u8,
441 content_box: LogicalRect<Au>)
442 -> bool {
443 self.block_flow.assign_block_size_for_inorder_child_if_necessary(layout_context,
444 parent_thread_id,
445 content_box)
446 }
447
update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au)448 fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
449 self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
450 }
451
update_late_computed_block_position_if_necessary(&mut self, block_position: Au)452 fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
453 self.block_flow.update_late_computed_block_position_if_necessary(block_position)
454 }
455
generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au>456 fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
457 self.block_flow.generated_containing_block_size(flow)
458 }
459
build_display_list(&mut self, state: &mut DisplayListBuildState)460 fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
461 self.block_flow.build_display_list(state);
462 }
463
collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState)464 fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
465 self.block_flow.collect_stacking_contexts_for_block(
466 state,
467 StackingContextCollectionFlags::NEVER_CREATES_CONTAINING_BLOCK |
468 StackingContextCollectionFlags::NEVER_CREATES_CLIP_SCROLL_NODE);
469 }
470
repair_style(&mut self, new_style: &::ServoArc<ComputedValues>)471 fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
472 self.block_flow.repair_style(new_style)
473 }
474
compute_overflow(&self) -> Overflow475 fn compute_overflow(&self) -> Overflow {
476 self.block_flow.compute_overflow()
477 }
478
iterate_through_fragment_border_boxes(&self, iterator: &mut FragmentBorderBoxIterator, level: i32, stacking_context_position: &Point2D<Au>)479 fn iterate_through_fragment_border_boxes(&self,
480 iterator: &mut FragmentBorderBoxIterator,
481 level: i32,
482 stacking_context_position: &Point2D<Au>) {
483 self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
484 }
485
mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment))486 fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
487 self.block_flow.mutate_fragments(mutator)
488 }
489
print_extra_flow_children(&self, print_tree: &mut PrintTree)490 fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
491 self.block_flow.print_extra_flow_children(print_tree);
492 }
493
positioning(&self) -> position::T494 fn positioning(&self) -> position::T {
495 self.block_flow.positioning()
496 }
497 }
498
499 impl fmt::Debug for TableWrapperFlow {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result500 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
501 if self.block_flow.base.flags.is_float() {
502 write!(f, "TableWrapperFlow(Float): {:?}", self.block_flow)
503 } else {
504 write!(f, "TableWrapperFlow: {:?}", self.block_flow)
505 }
506 }
507 }
508
509 /// The layout "guesses" defined in INTRINSIC § 4.3.
510 struct AutoLayoutCandidateGuess {
511 /// The column inline-size assignment where each column is assigned its intrinsic minimum
512 /// inline-size.
513 minimum_guess: Au,
514
515 /// The column inline-size assignment where:
516 /// * A column with an intrinsic percentage inline-size greater than 0% is assigned the
517 /// larger of:
518 /// - Its intrinsic percentage inline-size times the assignable inline-size;
519 /// - Its intrinsic minimum inline-size;
520 /// * Other columns receive their intrinsic minimum inline-size.
521 minimum_percentage_guess: Au,
522
523 /// The column inline-size assignment where:
524 /// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
525 /// larger of:
526 /// - Its intrinsic percentage inline-size times the assignable inline-size;
527 /// - Its intrinsic minimum inline-size;
528 /// * Any other column that is constrained is assigned its intrinsic preferred inline-size;
529 /// * Other columns are assigned their intrinsic minimum inline-size.
530 minimum_specified_guess: Au,
531
532 /// The column inline-size assignment where:
533 /// * Each column with an intrinsic percentage inline-size greater than 0% is assigned the
534 /// larger of:
535 /// - Its intrinsic percentage inline-size times the assignable inline-size;
536 /// - Its intrinsic minimum inline-size;
537 /// * Other columns are assigned their intrinsic preferred inline-size.
538 preferred_guess: Au,
539 }
540
541 impl AutoLayoutCandidateGuess {
542 /// Creates a guess with all elements initialized to zero.
new() -> AutoLayoutCandidateGuess543 fn new() -> AutoLayoutCandidateGuess {
544 AutoLayoutCandidateGuess {
545 minimum_guess: Au(0),
546 minimum_percentage_guess: Au(0),
547 minimum_specified_guess: Au(0),
548 preferred_guess: Au(0),
549 }
550 }
551
552 /// Fills in the inline-size guesses for this column per INTRINSIC § 4.3.
from_column_intrinsic_inline_size(column_intrinsic_inline_size: &ColumnIntrinsicInlineSize, assignable_inline_size: Au) -> AutoLayoutCandidateGuess553 fn from_column_intrinsic_inline_size(column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
554 assignable_inline_size: Au)
555 -> AutoLayoutCandidateGuess {
556 let minimum_percentage_guess =
557 max(assignable_inline_size.scale_by(column_intrinsic_inline_size.percentage),
558 column_intrinsic_inline_size.minimum_length);
559 AutoLayoutCandidateGuess {
560 minimum_guess: column_intrinsic_inline_size.minimum_length,
561 minimum_percentage_guess: minimum_percentage_guess,
562 // FIXME(pcwalton): We need the notion of *constrainedness* per INTRINSIC § 4 to
563 // implement this one correctly.
564 minimum_specified_guess: if column_intrinsic_inline_size.percentage > 0.0 {
565 minimum_percentage_guess
566 } else if column_intrinsic_inline_size.constrained {
567 column_intrinsic_inline_size.preferred
568 } else {
569 column_intrinsic_inline_size.minimum_length
570 },
571 preferred_guess: if column_intrinsic_inline_size.percentage > 0.0 {
572 minimum_percentage_guess
573 } else {
574 column_intrinsic_inline_size.preferred
575 },
576 }
577 }
578
579 /// Calculates the inline-size, interpolating appropriately based on the value of `selection`.
580 ///
581 /// This does *not* distribute excess inline-size. That must be done later if necessary.
calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au582 fn calculate(&self, selection: SelectedAutoLayoutCandidateGuess) -> Au {
583 match selection {
584 SelectedAutoLayoutCandidateGuess::UseMinimumGuess => self.minimum_guess,
585 SelectedAutoLayoutCandidateGuess::
586 InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight) => {
587 interp(self.minimum_guess, self.minimum_percentage_guess, weight)
588 }
589 SelectedAutoLayoutCandidateGuess::
590 InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight) => {
591 interp(self.minimum_percentage_guess, self.minimum_specified_guess, weight)
592 }
593 SelectedAutoLayoutCandidateGuess::
594 InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight) => {
595 interp(self.minimum_specified_guess, self.preferred_guess, weight)
596 }
597 SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize => {
598 self.preferred_guess
599 }
600 }
601 }
602 }
603
604 impl<'a> Add for &'a AutoLayoutCandidateGuess {
605 type Output = AutoLayoutCandidateGuess;
606 #[inline]
add(self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess607 fn add(self, other: &AutoLayoutCandidateGuess) -> AutoLayoutCandidateGuess {
608 AutoLayoutCandidateGuess {
609 minimum_guess: self.minimum_guess + other.minimum_guess,
610 minimum_percentage_guess:
611 self.minimum_percentage_guess + other.minimum_percentage_guess,
612 minimum_specified_guess: self.minimum_specified_guess + other.minimum_specified_guess,
613 preferred_guess: self.preferred_guess + other.preferred_guess,
614 }
615 }
616 }
617
618 /// The `CSSFloat` member specifies the weight of the smaller of the two guesses, on a scale from
619 /// 0.0 to 1.0.
620 #[derive(Clone, Copy, Debug, PartialEq)]
621 enum SelectedAutoLayoutCandidateGuess {
622 UseMinimumGuess,
623 InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(CSSFloat),
624 InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(CSSFloat),
625 InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(CSSFloat),
626 UsePreferredGuessAndDistributeExcessInlineSize,
627 }
628
629 impl SelectedAutoLayoutCandidateGuess {
630 /// See INTRINSIC § 4.3.
631 ///
632 /// FIXME(pcwalton, INTRINSIC spec): INTRINSIC doesn't specify whether these are exclusive or
633 /// inclusive ranges.
select(guess: &AutoLayoutCandidateGuess, assignable_inline_size: Au) -> SelectedAutoLayoutCandidateGuess634 fn select(guess: &AutoLayoutCandidateGuess, assignable_inline_size: Au)
635 -> SelectedAutoLayoutCandidateGuess {
636 if assignable_inline_size < guess.minimum_guess {
637 SelectedAutoLayoutCandidateGuess::UseMinimumGuess
638 } else if assignable_inline_size < guess.minimum_percentage_guess {
639 let weight = weight(guess.minimum_guess,
640 assignable_inline_size,
641 guess.minimum_percentage_guess);
642 SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumGuessAndMinimumPercentageGuess(weight)
643 } else if assignable_inline_size < guess.minimum_specified_guess {
644 let weight = weight(guess.minimum_percentage_guess,
645 assignable_inline_size,
646 guess.minimum_specified_guess);
647 SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumPercentageGuessAndMinimumSpecifiedGuess(weight)
648 } else if assignable_inline_size < guess.preferred_guess {
649 let weight = weight(guess.minimum_specified_guess,
650 assignable_inline_size,
651 guess.preferred_guess);
652 SelectedAutoLayoutCandidateGuess::InterpolateBetweenMinimumSpecifiedGuessAndPreferredGuess(weight)
653 } else {
654 SelectedAutoLayoutCandidateGuess::UsePreferredGuessAndDistributeExcessInlineSize
655 }
656 }
657 }
658
659 /// Computes the weight needed to linearly interpolate `middle` between two guesses `low` and
660 /// `high` as specified by INTRINSIC § 4.3.
weight(low: Au, middle: Au, high: Au) -> CSSFloat661 fn weight(low: Au, middle: Au, high: Au) -> CSSFloat {
662 (middle - low).to_f32_px() / (high - low).to_f32_px()
663 }
664
665 /// Linearly interpolates between two guesses, as specified by INTRINSIC § 4.3.
interp(low: Au, high: Au, weight: CSSFloat) -> Au666 fn interp(low: Au, high: Au, weight: CSSFloat) -> Au {
667 low + (high - low).scale_by(weight)
668 }
669
670 struct ExcessInlineSizeDistributionInfo {
671 preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au,
672 count_of_nonconstrained_columns_with_no_percentage: u32,
673 preferred_inline_size_of_constrained_columns_with_no_percentage: Au,
674 total_percentage: CSSFloat,
675 column_count: u32,
676 }
677
678 impl ExcessInlineSizeDistributionInfo {
new() -> ExcessInlineSizeDistributionInfo679 fn new() -> ExcessInlineSizeDistributionInfo {
680 ExcessInlineSizeDistributionInfo {
681 preferred_inline_size_of_nonconstrained_columns_with_no_percentage: Au(0),
682 count_of_nonconstrained_columns_with_no_percentage: 0,
683 preferred_inline_size_of_constrained_columns_with_no_percentage: Au(0),
684 total_percentage: 0.0,
685 column_count: 0,
686 }
687 }
688
update(&mut self, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize)689 fn update(&mut self, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize) {
690 if !column_intrinsic_inline_size.constrained &&
691 column_intrinsic_inline_size.percentage == 0.0 {
692 self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage =
693 self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage +
694 column_intrinsic_inline_size.preferred;
695 self.count_of_nonconstrained_columns_with_no_percentage += 1
696 }
697 if column_intrinsic_inline_size.constrained &&
698 column_intrinsic_inline_size.percentage == 0.0 {
699 self.preferred_inline_size_of_constrained_columns_with_no_percentage =
700 self.preferred_inline_size_of_constrained_columns_with_no_percentage +
701 column_intrinsic_inline_size.preferred
702 }
703 self.total_percentage += column_intrinsic_inline_size.percentage;
704 self.column_count += 1
705 }
706
707 /// Based on the information here, distributes excess inline-size to the given column per
708 /// INTRINSIC § 4.4.
709 ///
710 /// `#[inline]` so the compiler will hoist out the branch, which is loop-invariant.
711 #[inline]
distribute_excess_inline_size_to_column( &self, intermediate_column_inline_size: &mut IntermediateColumnInlineSize, column_intrinsic_inline_size: &ColumnIntrinsicInlineSize, excess_inline_size: Au, total_distributed_excess_size: &mut Au)712 fn distribute_excess_inline_size_to_column(
713 &self,
714 intermediate_column_inline_size: &mut IntermediateColumnInlineSize,
715 column_intrinsic_inline_size: &ColumnIntrinsicInlineSize,
716 excess_inline_size: Au,
717 total_distributed_excess_size: &mut Au) {
718 let proportion =
719 if self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage > Au(0) {
720 // FIXME(spec, pcwalton): Gecko and WebKit do *something* here when there are
721 // nonconstrained columns with no percentage *and* no preferred width. What do they
722 // do?
723 if !column_intrinsic_inline_size.constrained &&
724 column_intrinsic_inline_size.percentage == 0.0 {
725 column_intrinsic_inline_size.preferred.to_f32_px() /
726 self.preferred_inline_size_of_nonconstrained_columns_with_no_percentage
727 .to_f32_px()
728 } else {
729 0.0
730 }
731 } else if self.count_of_nonconstrained_columns_with_no_percentage > 0 {
732 1.0 / (self.count_of_nonconstrained_columns_with_no_percentage as CSSFloat)
733 } else if self.preferred_inline_size_of_constrained_columns_with_no_percentage >
734 Au(0) {
735 column_intrinsic_inline_size.preferred.to_f32_px() /
736 self.preferred_inline_size_of_constrained_columns_with_no_percentage.to_f32_px()
737 } else if self.total_percentage > 0.0 {
738 column_intrinsic_inline_size.percentage / self.total_percentage
739 } else {
740 1.0 / (self.column_count as CSSFloat)
741 };
742
743 // The `min` here has the effect of throwing away fractional excess at the end of the
744 // table.
745 let amount_to_distribute = min(excess_inline_size.scale_by(proportion),
746 excess_inline_size - *total_distributed_excess_size);
747 *total_distributed_excess_size = *total_distributed_excess_size + amount_to_distribute;
748 intermediate_column_inline_size.size = intermediate_column_inline_size.size +
749 amount_to_distribute
750 }
751 }
752
753 /// An intermediate column size assignment.
754 struct IntermediateColumnInlineSize {
755 size: Au,
756 percentage: f32,
757 }
758
759 /// Returns the computed inline size of the table wrapper represented by `block`.
760 ///
761 /// `table_border_padding` is the sum of the sizes of all border and padding in the inline
762 /// direction of the table contained within this table wrapper.
initial_computed_inline_size(block: &mut BlockFlow, containing_block_inline_size: Au, minimum_width_of_all_columns: Au, preferred_width_of_all_columns: Au, table_border_padding: Au) -> MaybeAuto763 fn initial_computed_inline_size(block: &mut BlockFlow,
764 containing_block_inline_size: Au,
765 minimum_width_of_all_columns: Au,
766 preferred_width_of_all_columns: Au,
767 table_border_padding: Au)
768 -> MaybeAuto {
769 let inline_size_from_style = MaybeAuto::from_style(block.fragment.style.content_inline_size(),
770 containing_block_inline_size);
771 match inline_size_from_style {
772 MaybeAuto::Auto => {
773 if preferred_width_of_all_columns + table_border_padding <= containing_block_inline_size {
774 MaybeAuto::Specified(preferred_width_of_all_columns + table_border_padding)
775 } else if minimum_width_of_all_columns > containing_block_inline_size {
776 MaybeAuto::Specified(minimum_width_of_all_columns)
777 } else {
778 MaybeAuto::Auto
779 }
780 }
781 MaybeAuto::Specified(inline_size_from_style) => {
782 MaybeAuto::Specified(max(inline_size_from_style - table_border_padding,
783 minimum_width_of_all_columns))
784 }
785 }
786 }
787
788 struct Table {
789 minimum_width_of_all_columns: Au,
790 preferred_width_of_all_columns: Au,
791 table_border_padding: Au,
792 }
793
794 impl ISizeAndMarginsComputer for Table {
compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au)795 fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
796 block.fragment.compute_border_and_padding(containing_block_inline_size)
797 }
798
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto799 fn initial_computed_inline_size(&self,
800 block: &mut BlockFlow,
801 parent_flow_inline_size: Au,
802 shared_context: &SharedStyleContext)
803 -> MaybeAuto {
804 let containing_block_inline_size =
805 self.containing_block_inline_size(block, parent_flow_inline_size, shared_context);
806 initial_computed_inline_size(block,
807 containing_block_inline_size,
808 self.minimum_width_of_all_columns,
809 self.preferred_width_of_all_columns,
810 self.table_border_padding)
811 }
812
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution813 fn solve_inline_size_constraints(&self,
814 block: &mut BlockFlow,
815 input: &ISizeConstraintInput)
816 -> ISizeConstraintSolution {
817 self.solve_block_inline_size_constraints(block, input)
818 }
819 }
820
821 struct FloatedTable {
822 minimum_width_of_all_columns: Au,
823 preferred_width_of_all_columns: Au,
824 table_border_padding: Au,
825 }
826
827 impl ISizeAndMarginsComputer for FloatedTable {
compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au)828 fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
829 block.fragment.compute_border_and_padding(containing_block_inline_size)
830 }
831
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto832 fn initial_computed_inline_size(&self,
833 block: &mut BlockFlow,
834 parent_flow_inline_size: Au,
835 shared_context: &SharedStyleContext)
836 -> MaybeAuto {
837 let containing_block_inline_size =
838 self.containing_block_inline_size(block,
839 parent_flow_inline_size,
840 shared_context);
841 initial_computed_inline_size(block,
842 containing_block_inline_size,
843 self.minimum_width_of_all_columns,
844 self.preferred_width_of_all_columns,
845 self.table_border_padding)
846 }
847
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution848 fn solve_inline_size_constraints(&self,
849 block: &mut BlockFlow,
850 input: &ISizeConstraintInput)
851 -> ISizeConstraintSolution {
852 FloatNonReplaced.solve_inline_size_constraints(block, input)
853 }
854 }
855
856 struct AbsoluteTable {
857 minimum_width_of_all_columns: Au,
858 preferred_width_of_all_columns: Au,
859 table_border_padding: Au,
860 }
861
862 impl ISizeAndMarginsComputer for AbsoluteTable {
compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au)863 fn compute_border_and_padding(&self, block: &mut BlockFlow, containing_block_inline_size: Au) {
864 block.fragment.compute_border_and_padding(containing_block_inline_size)
865 }
866
initial_computed_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> MaybeAuto867 fn initial_computed_inline_size(&self,
868 block: &mut BlockFlow,
869 parent_flow_inline_size: Au,
870 shared_context: &SharedStyleContext)
871 -> MaybeAuto {
872 let containing_block_inline_size =
873 self.containing_block_inline_size(block,
874 parent_flow_inline_size,
875 shared_context);
876 initial_computed_inline_size(block,
877 containing_block_inline_size,
878 self.minimum_width_of_all_columns,
879 self.preferred_width_of_all_columns,
880 self.table_border_padding)
881 }
882
containing_block_inline_size(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, shared_context: &SharedStyleContext) -> Au883 fn containing_block_inline_size(&self,
884 block: &mut BlockFlow,
885 parent_flow_inline_size: Au,
886 shared_context: &SharedStyleContext)
887 -> Au {
888 AbsoluteNonReplaced.containing_block_inline_size(block,
889 parent_flow_inline_size,
890 shared_context)
891 }
892
solve_inline_size_constraints(&self, block: &mut BlockFlow, input: &ISizeConstraintInput) -> ISizeConstraintSolution893 fn solve_inline_size_constraints(&self,
894 block: &mut BlockFlow,
895 input: &ISizeConstraintInput)
896 -> ISizeConstraintSolution {
897 AbsoluteNonReplaced.solve_inline_size_constraints(block, input)
898 }
899
set_inline_position_of_flow_if_necessary(&self, block: &mut BlockFlow, solution: ISizeConstraintSolution)900 fn set_inline_position_of_flow_if_necessary(&self,
901 block: &mut BlockFlow,
902 solution: ISizeConstraintSolution) {
903 AbsoluteNonReplaced.set_inline_position_of_flow_if_necessary(block, solution);
904 }
905
906 }
907