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