aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/main/layout/inline.rs
blob: ee718001f433f184f960cf9080f4aa6ef2aa11bf (plain) (blame)
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
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use css::node_style::StyledNode;
use layout::box_::{Box, CannotSplit, GenericBox, IframeBox, ImageBox, ScannedTextBox, SplitDidFit};
use layout::box_::{SplitDidNotFit, UnscannedTextBox, InlineInfo};
use layout::context::LayoutContext;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::flow::{BaseFlow, FlowClass, Flow, InlineFlowClass};
use layout::flow;
use layout::float_context::{FloatContext, FloatLeft, PlacementInfo};
use layout::util::ElementMapping;
use layout::wrapper::ThreadSafeLayoutNode;

use extra::container::Deque;
use extra::ringbuf::RingBuf;
use geom::{Point2D, Rect, Size2D};
use gfx::display_list::DisplayListCollection;
use servo_util::geometry::Au;
use servo_util::range::Range;
use std::cell::RefCell;
use std::u16;
use std::util;
use style::computed_values::{text_align, vertical_align, white_space};

/// Lineboxes are represented as offsets into the child list, rather than
/// as an object that "owns" boxes. Choosing a different set of line
/// breaks requires a new list of offsets, and possibly some splitting and
/// merging of TextBoxes.
///
/// A similar list will keep track of the mapping between CSS boxes and
/// the corresponding boxes in the inline flow.
///
/// After line breaks are determined, render boxes in the inline flow may
/// overlap visually. For example, in the case of nested inline CSS boxes,
/// outer inlines must be at least as large as the inner inlines, for
/// purposes of drawing noninherited things like backgrounds, borders,
/// outlines.
///
/// N.B. roc has an alternative design where the list instead consists of
/// things like "start outer box, text, start inner box, text, end inner
/// box, text, end outer box, text". This seems a little complicated to
/// serve as the starting point, but the current design doesn't make it
/// hard to try out that alternative.
///
/// Line boxes also contain some metadata used during line breaking. The
/// green zone is the area that the line can expand to before it collides
/// with a float or a horizontal wall of the containing block. The top
/// left corner of the green zone is the same as that of the line, but
/// the green zone can be taller and wider than the line itself.
struct LineBox {
    range: Range,
    bounds: Rect<Au>,
    green_zone: Size2D<Au>
}

struct LineboxScanner {
    floats: FloatContext,
    new_boxes: ~[Box],
    work_list: RingBuf<Box>,
    pending_line: LineBox,
    lines: ~[LineBox],
    cur_y: Au,
}

impl LineboxScanner {
    pub fn new(float_ctx: FloatContext) -> LineboxScanner {
        LineboxScanner {
            floats: float_ctx,
            new_boxes: ~[],
            work_list: RingBuf::new(),
            pending_line: LineBox {
                range: Range::empty(),
                bounds: Rect(Point2D(Au::new(0), Au::new(0)), Size2D(Au::new(0), Au::new(0))),
                green_zone: Size2D(Au::new(0), Au::new(0))
            },
            lines: ~[],
            cur_y: Au::new(0)
        }
    }

    pub fn floats_out(&mut self) -> FloatContext {
        self.floats.clone()
    }

    fn reset_scanner(&mut self, flow: &mut InlineFlow) {
        debug!("Resetting line box scanner's state for flow f{:d}.", flow.base.id);
        self.lines = ~[];
        self.new_boxes = ~[];
        self.cur_y = Au::new(0);
        self.reset_linebox();
    }

    fn reset_linebox(&mut self) {
        self.pending_line.range.reset(0,0);
        self.pending_line.bounds = Rect(Point2D(Au::new(0), self.cur_y), Size2D(Au::new(0), Au::new(0)));
        self.pending_line.green_zone = Size2D(Au::new(0), Au::new(0))
    }

    pub fn scan_for_lines(&mut self, flow: &mut InlineFlow) {
        self.reset_scanner(flow);

        loop {
            // acquire the next box to lay out from work list or box list
            let cur_box = if self.work_list.is_empty() {
                if flow.boxes.is_empty() {
                    break;
                }
                let box_ = flow.boxes.remove(0); // FIXME: use a linkedlist
                debug!("LineboxScanner: Working with box from box list: b{}", box_.debug_id());
                box_
            } else {
                let box_ = self.work_list.pop_front().unwrap();
                debug!("LineboxScanner: Working with box from work list: b{}", box_.debug_id());
                box_
            };

            let box_was_appended = match cur_box.white_space() {
                white_space::normal => self.try_append_to_line(cur_box, flow),
                white_space::pre => self.try_append_to_line_by_new_line(cur_box),
            };

            if !box_was_appended {
                debug!("LineboxScanner: Box wasn't appended, because line {:u} was full.",
                        self.lines.len());
                self.flush_current_line();
            } else {
                debug!("LineboxScanner: appended a box to line {:u}", self.lines.len());
            }
        }

        if self.pending_line.range.length() > 0 {
            debug!("LineboxScanner: Partially full linebox {:u} left at end of scanning.",
                    self.lines.len());
            self.flush_current_line();
        }

        flow.elems.repair_for_box_changes(flow.boxes, self.new_boxes);

        self.swap_out_results(flow);
    }

    fn swap_out_results(&mut self, flow: &mut InlineFlow) {
        debug!("LineboxScanner: Propagating scanned lines[n={:u}] to inline flow f{:d}",
               self.lines.len(),
               flow.base.id);

        util::swap(&mut flow.boxes, &mut self.new_boxes);
        util::swap(&mut flow.lines, &mut self.lines);
    }

    fn flush_current_line(&mut self) {
        debug!("LineboxScanner: Flushing line {:u}: {:?}",
               self.lines.len(), self.pending_line);

        // clear line and add line mapping
        debug!("LineboxScanner: Saving information for flushed line {:u}.", self.lines.len());
        self.lines.push(self.pending_line);
        self.cur_y = self.pending_line.bounds.origin.y + self.pending_line.bounds.size.height;
        self.reset_linebox();
    }

    // FIXME(eatkinson): this assumes that the tallest box in the line determines the line height
    // This might not be the case with some weird text fonts.
    fn new_height_for_line(&self, new_box: &Box) -> Au {
        let box_height = new_box.content_height();
        if box_height > self.pending_line.bounds.size.height {
            box_height
        } else {
            self.pending_line.bounds.size.height
        }
    }

    /// Computes the position of a line that has only the provided box. Returns the bounding rect
    /// of the line's green zone (whose origin coincides with the line's origin) and the actual
    /// width of the first box after splitting.
    fn initial_line_placement(&self, first_box: &Box, ceiling: Au, flow: &mut InlineFlow)
                              -> (Rect<Au>, Au) {
        debug!("LineboxScanner: Trying to place first box of line {}", self.lines.len());

        let first_box_size = first_box.position.get().size;
        let splittable = first_box.can_split();
        debug!("LineboxScanner: box size: {}, splittable: {}", first_box_size, splittable);
        let line_is_empty: bool = self.pending_line.range.length() == 0;

        // Initally, pretend a splittable box has 0 width.
        // We will move it later if it has nonzero width
        // and that causes problems.
        let placement_width = if splittable {
            Au::new(0)
        } else {
            first_box_size.width
        };

        let mut info = PlacementInfo {
            width: placement_width,
            height: first_box_size.height,
            ceiling: ceiling,
            max_width: flow.base.position.size.width,
            f_type: FloatLeft
        };

        let line_bounds = self.floats.place_between_floats(&info);

        debug!("LineboxScanner: found position for line: {} using placement_info: {:?}",
               line_bounds,
               info);

        // Simple case: if the box fits, then we can stop here
        if line_bounds.size.width > first_box_size.width {
            debug!("LineboxScanner: case=box fits");
            return (line_bounds, first_box_size.width);
        }

        // If not, but we can't split the box, then we'll place
        // the line here and it will overflow.
        if !splittable {
            debug!("LineboxScanner: case=line doesn't fit, but is unsplittable");
            return (line_bounds, first_box_size.width);
        }

        // Otherwise, try and split the box
        // FIXME(eatkinson): calling split_to_width here seems excessive and expensive.
        // We should find a better abstraction or merge it with the call in
        // try_append_to_line.
        match first_box.split_to_width(line_bounds.size.width, line_is_empty) {
            CannotSplit => {
                error!("LineboxScanner: Tried to split unsplittable render box! {:s}",
                        first_box.debug_str());
                return (line_bounds, first_box_size.width);
            }
            SplitDidFit(left, right) => {

                debug!("LineboxScanner: case=box split and fit");
                let actual_box_width = match (left, right) {
                    (Some(l_box), Some(_))  => l_box.position.get().size.width,
                    (Some(l_box), None)     => l_box.position.get().size.width,
                    (None, Some(r_box))     => r_box.position.get().size.width,
                    (None, None)            => fail!("This case makes no sense.")
                };
                return (line_bounds, actual_box_width);
            }
            SplitDidNotFit(left, right) => {
                // The split didn't fit, but we might be able to
                // push it down past floats.


                debug!("LineboxScanner: case=box split and fit didn't fit; trying to push it down");
                let actual_box_width = match (left, right) {
                    (Some(l_box), Some(_))  => l_box.position.get().size.width,
                    (Some(l_box), None)     => l_box.position.get().size.width,
                    (None, Some(r_box))     => r_box.position.get().size.width,
                    (None, None)            => fail!("This case makes no sense.")
                };

                info.width = actual_box_width;
                let new_bounds = self.floats.place_between_floats(&info);

                debug!("LineboxScanner: case=new line position: {}", new_bounds);
                return (new_bounds, actual_box_width);
            }
        }

    }

    /// Performs float collision avoidance. This is called when adding a box is going to increase
    /// the height, and because of that we will collide with some floats.
    ///
    /// We have two options here:
    /// 1) Move the entire line so that it doesn't collide any more.
    /// 2) Break the line and put the new box on the next line.
    ///
    /// The problem with option 1 is that we might move the line and then wind up breaking anyway,
    /// which violates the standard.
    /// But option 2 is going to look weird sometimes.
    ///
    /// So we'll try to move the line whenever we can, but break if we have to.
    ///
    /// Returns false if and only if we should break the line.
    fn avoid_floats(&mut self,
                    in_box: Box,
                    flow: &mut InlineFlow,
                    new_height: Au,
                    line_is_empty: bool)
                    -> bool {
        debug!("LineboxScanner: entering float collision avoider!");

        // First predict where the next line is going to be.
        let this_line_y = self.pending_line.bounds.origin.y;
        let (next_line, first_box_width) = self.initial_line_placement(&in_box, this_line_y, flow);
        let next_green_zone = next_line.size;

        let new_width = self.pending_line.bounds.size.width + first_box_width;

        // Now, see if everything can fit at the new location.
        if next_green_zone.width >= new_width && next_green_zone.height >= new_height {
            debug!("LineboxScanner: case=adding box collides vertically with floats: moving line");

            self.pending_line.bounds.origin = next_line.origin;
            self.pending_line.green_zone = next_green_zone;

            assert!(!line_is_empty, "Non-terminating line breaking");
            self.work_list.push_front(in_box);
            return true
        }

        debug!("LineboxScanner: case=adding box collides vertically with floats: breaking line");
        self.work_list.push_front(in_box);
        false
    }

    fn try_append_to_line_by_new_line(&mut self, in_box: Box) -> bool {
        if in_box.new_line_pos.len() == 0 {
            // In case of box does not include new-line character
            self.push_box_to_line(in_box);
            true
        } else {
            // In case of box includes new-line character
            match in_box.split_by_new_line() {
                SplitDidFit(left, right) => {
                    match (left, right) {
                        (Some(left_box), Some(right_box)) => {
                            self.push_box_to_line(left_box);
                            self.work_list.push_front(right_box);
                        }
                        (Some(left_box), None) => {
                            self.push_box_to_line(left_box);
                        }
                        _ => error!("LineboxScanner: This split case makes no sense!"),
                    }
                }
                _ => {}
            }
            false
        }
    }

    /// Tries to append the given box to the line, splitting it if necessary. Returns false only if
    /// we should break the line.
    fn try_append_to_line(&mut self, in_box: Box, flow: &mut InlineFlow) -> bool {
        let line_is_empty = self.pending_line.range.length() == 0;
        if line_is_empty {
            let (line_bounds, _) = self.initial_line_placement(&in_box, self.cur_y, flow);
            self.pending_line.bounds.origin = line_bounds.origin;
            self.pending_line.green_zone = line_bounds.size;
        }

        debug!("LineboxScanner: Trying to append box to line {:u} (box size: {}, green zone: \
                {}): {:s}",
               self.lines.len(),
               in_box.position.get().size,
               self.pending_line.green_zone,
               in_box.debug_str());

        let green_zone = self.pending_line.green_zone;

        // NB: At this point, if `green_zone.width < self.pending_line.bounds.size.width` or
        // `green_zone.height < self.pending_line.bounds.size.height`, then we committed a line
        // that overlaps with floats.

        let new_height = self.new_height_for_line(&in_box);
        if new_height > green_zone.height {
            // Uh-oh. Float collision imminent. Enter the float collision avoider
            return self.avoid_floats(in_box, flow, new_height, line_is_empty)
        }

        // If we're not going to overflow the green zone vertically, we might still do so
        // horizontally. We'll try to place the whole box on this line and break somewhere if it
        // doesn't fit.

        let new_width = self.pending_line.bounds.size.width + in_box.position.get().size.width;
        if new_width <= green_zone.width {
            debug!("LineboxScanner: case=box fits without splitting");
            self.push_box_to_line(in_box);
            return true
        }

        if !in_box.can_split() {
            // TODO(eatkinson, issue #224): Signal that horizontal overflow happened?
            if line_is_empty {
                debug!("LineboxScanner: case=box can't split and line {:u} is empty, so \
                        overflowing.",
                        self.lines.len());
                self.push_box_to_line(in_box);
                return true
            }
        }

        let available_width = green_zone.width - self.pending_line.bounds.size.width;
        let split = in_box.split_to_width(available_width, line_is_empty);
        let (left, right) = match (split, line_is_empty) {
            (CannotSplit, _) => {
                debug!("LineboxScanner: Tried to split unsplittable render box! {:s}",
                        in_box.debug_str());
                self.work_list.push_front(in_box);
                return false
            }
            (SplitDidNotFit(_, _), false) => {
                debug!("LineboxScanner: case=split box didn't fit, not appending and deferring \
                        original box.");
                self.work_list.push_front(in_box);
                return false
            }
            (SplitDidFit(left, right), _) => {
                debug!("LineboxScanner: case=split box did fit; deferring remainder box.");
                (left, right)
                // Fall through to push boxes to the line.
            }
            (SplitDidNotFit(left, right), true) => {
                // TODO(eatkinson, issue #224): Signal that horizontal overflow happened?
                debug!("LineboxScanner: case=split box didn't fit and line {:u} is empty, so \
                        overflowing and deferring remainder box.",
                        self.lines.len());
                (left, right)
                // Fall though to push boxes to the line.
            }
        };

        match (left, right) {
            (Some(left_box), Some(right_box)) => {
                self.push_box_to_line(left_box);
                self.work_list.push_front(right_box);
            }
            (Some(left_box), None) => self.push_box_to_line(left_box),
            (None, Some(right_box)) => self.push_box_to_line(right_box),
            (None, None) => error!("LineboxScanner: This split case makes no sense!"),
        }

        true
    }

    // An unconditional push
    fn push_box_to_line(&mut self, box_: Box) {
        debug!("LineboxScanner: Pushing box {} to line {:u}", box_.debug_id(), self.lines.len());

        if self.pending_line.range.length() == 0 {
            assert!(self.new_boxes.len() <= (u16::max_value as uint));
            self.pending_line.range.reset(self.new_boxes.len(), 0);
        }
        self.pending_line.range.extend_by(1);
        self.pending_line.bounds.size.width = self.pending_line.bounds.size.width +
            box_.position.get().size.width;
        self.pending_line.bounds.size.height = Au::max(self.pending_line.bounds.size.height,
                                                       box_.position.get().size.height);
        self.new_boxes.push(box_);
    }
}

pub struct InlineFlow {
    /// Data common to all flows.
    base: BaseFlow,

    /// A vector of all inline render boxes. Several boxes may correspond to one node/element.
    boxes: ~[Box],

    // vec of ranges into boxes that represents line positions.
    // these ranges are disjoint, and are the result of inline layout.
    // also some metadata used for positioning lines
    lines: ~[LineBox],

    // vec of ranges into boxes that represent elements. These ranges
    // must be well-nested, and are only related to the content of
    // boxes (not lines). Ranges are only kept for non-leaf elements.
    elems: ElementMapping,
}

impl InlineFlow {
    pub fn from_boxes(id: int, node: ThreadSafeLayoutNode, boxes: ~[Box]) -> InlineFlow {
        InlineFlow {
            base: BaseFlow::new(id, node),
            boxes: boxes,
            lines: ~[],
            elems: ElementMapping::new(),
        }
    }

    pub fn teardown(&mut self) {
        for box_ in self.boxes.iter() {
            box_.teardown();
        }
        self.boxes = ~[];
    }

    pub fn build_display_list_inline<E:ExtraDisplayListData>(
                                     &self,
                                     builder: &DisplayListBuilder,
                                     container_block_size: &Size2D<Au>,
                                     dirty: &Rect<Au>,
                                     index: uint,
                                     lists: &RefCell<DisplayListCollection<E>>)
                                     -> uint {
        let abs_rect = Rect(self.base.abs_position, self.base.position.size);
        if !abs_rect.intersects(dirty) {
            return index;
        }

        // TODO(#228): Once we form line boxes and have their cached bounds, we can be smarter and
        // not recurse on a line if nothing in it can intersect the dirty region.
        debug!("Flow[{:d}]: building display list for {:u} inline boxes",
               self.base.id,
               self.boxes.len());

        for box_ in self.boxes.iter() {
            let rel_offset: Point2D<Au> = box_.relative_position(container_block_size);
            box_.build_display_list(builder, dirty, self.base.abs_position + rel_offset, (&*self) as &Flow, index, lists);
        }

        // TODO(#225): Should `inline-block` elements have flows as children of the inline flow or
        // should the flow be nested inside the box somehow?

        // For now, don't traverse the subtree rooted here
        index
    }

    /// Returns the relative offset from the baseline for this box, taking into account the value
    /// of the CSS `vertical-align` property.
    ///
    /// The extra boolean is set if and only if `biggest_top` and/or `biggest_bottom` were updated.
    /// That is, if the box has a `top` or `bottom` value, true is returned.
    fn relative_offset_from_baseline(cur_box: &Box,
                                     ascent: Au,
                                     parent_text_top: Au,
                                     parent_text_bottom: Au,
                                     top_from_base: &mut Au,
                                     bottom_from_base: &mut Au,
                                     biggest_top: &mut Au,
                                     biggest_bottom: &mut Au)
                                     -> (Au, bool) {
        match cur_box.vertical_align() {
            vertical_align::baseline => (-ascent, false),
            vertical_align::middle => {
                // TODO: x-height value should be used from font info.
                let xheight = Au::new(0);
                (-(xheight + cur_box.content_height()).scale_by(0.5), false)
            },
            vertical_align::sub => {
                // TODO: The proper position for subscripts should be used.
                // Lower the baseline to the proper position for subscripts
                let sub_offset = Au::new(0);
                (sub_offset - ascent, false)
            },
            vertical_align::super_ => {
                // TODO: The proper position for superscripts should be used.
                // Raise the baseline to the proper position for superscripts
                let super_offset = Au::new(0);
                (-super_offset - ascent, false)
            },
            vertical_align::text_top => {
                let box_height = *top_from_base + *bottom_from_base;
                let prev_bottom_from_base = *bottom_from_base;
                *top_from_base = parent_text_top;
                *bottom_from_base = box_height - *top_from_base;
                (*bottom_from_base - prev_bottom_from_base - ascent, false)
            },
            vertical_align::text_bottom => {
                let box_height = *top_from_base + *bottom_from_base;
                let prev_bottom_from_base = *bottom_from_base;
                *bottom_from_base = parent_text_bottom;
                *top_from_base = box_height - *bottom_from_base;
                (*bottom_from_base - prev_bottom_from_base - ascent, false)
            },
            vertical_align::top => {
                if *biggest_top < (*top_from_base + *bottom_from_base) {
                    *biggest_top = *top_from_base + *bottom_from_base;
                }
                let offset_top = *top_from_base - ascent;
                (offset_top, true)
            },
            vertical_align::bottom => {
                if *biggest_bottom < (*top_from_base + *bottom_from_base) {
                    *biggest_bottom = *top_from_base + *bottom_from_base;
                }
                let offset_bottom = -(*bottom_from_base + ascent);
                (offset_bottom, true)
            },
            vertical_align::Length(length) => (-(length + ascent), false),
            vertical_align::Percentage(p) => {
                let pt_size = cur_box.font_style().pt_size;
                let line_height = cur_box.calculate_line_height(Au::from_pt(pt_size));
                let percent_offset = line_height.scale_by(p);
                (-(percent_offset + ascent), false)
            }
        }
    }

    /// Sets box X positions based on alignment for one line.
    fn set_horizontal_box_positions(boxes: &[Box], line: &LineBox, linebox_align: text_align::T) {
        // Figure out how much width we have.
        let slack_width = Au::max(Au(0), line.green_zone.width - line.bounds.size.width);

        // Set the box x positions based on that alignment.
        let mut offset_x = line.bounds.origin.x;
        offset_x = offset_x + match linebox_align {
            // So sorry, but justified text is more complicated than shuffling linebox
            // coordinates.
            //
            // TODO(burg, issue #213): Implement `text-align: justify`.
            text_align::left | text_align::justify => Au(0),
            text_align::center => slack_width.scale_by(0.5),
            text_align::right => slack_width,
        };

        for i in line.range.eachi() {
            let box_ = &boxes[i];
            let size = box_.position.get().size;
            box_.position.set(Rect(Point2D(offset_x, box_.position.get().origin.y), size));
            offset_x = offset_x + size.width;
        }
    }
}

impl Flow for InlineFlow {
    fn class(&self) -> FlowClass {
        InlineFlowClass
    }

    fn as_immutable_inline<'a>(&'a self) -> &'a InlineFlow {
        self
    }

    fn as_inline<'a>(&'a mut self) -> &'a mut InlineFlow {
        self
    }

    fn bubble_widths(&mut self, _: &mut LayoutContext) {
        let mut num_floats = 0;

        for kid in self.base.child_iter() {
            let child_base = flow::mut_base(kid);
            num_floats += child_base.num_floats;
            child_base.floats_in = FloatContext::new(child_base.num_floats);
        }

        let mut min_width = Au::new(0);
        let mut pref_width = Au::new(0);

        for box_ in self.boxes.iter() {
            debug!("Flow[{:d}]: measuring {:s}", self.base.id, box_.debug_str());
            box_.compute_borders(box_.style());
            let (this_minimum_width, this_preferred_width) =
                box_.minimum_and_preferred_widths();
            min_width = Au::max(min_width, this_minimum_width);
            pref_width = Au::max(pref_width, this_preferred_width);
        }

        self.base.min_width = min_width;
        self.base.pref_width = pref_width;
        self.base.num_floats = num_floats;
    }

    /// Recursively (top-down) determines the actual width of child contexts and boxes. When called
    /// on this context, the context has had its width set by the parent context.
    fn assign_widths(&mut self, _: &mut LayoutContext) {
        // Initialize content box widths if they haven't been initialized already.
        //
        // TODO: Combine this with `LineboxScanner`'s walk in the box list, or put this into `Box`.

        debug!("InlineFlow::assign_widths: floats_in: {:?}", self.base.floats_in);

        {
            let this = &mut *self;
            for box_ in this.boxes.iter() {
                box_.assign_width(self.base.position.size.width);
            }
        }

        // FIXME(ksh8281) avoid copy
        let flags_info = self.base.flags_info.clone();
        for kid in self.base.child_iter() {
            let child_base = flow::mut_base(kid);
            child_base.position.size.width = self.base.position.size.width;
            child_base.flags_info.flags.set_inorder(self.base.flags_info.flags.inorder());
            child_base.flags_info.propagate_text_alignment_from_parent(&flags_info)
        }
        // There are no child contexts, so stop here.

        // TODO(Issue #225): once there are 'inline-block' elements, this won't be
        // true.  In that case, set the InlineBlockBox's width to the
        // shrink-to-fit width, perform inline flow, and set the block
        // flow context's width as the assigned width of the
        // 'inline-block' box that created this flow before recursing.
    }

    fn assign_height_inorder(&mut self, ctx: &mut LayoutContext) {
        for kid in self.base.child_iter() {
            kid.assign_height_inorder(ctx);
        }
        self.assign_height(ctx);
    }

    fn assign_height(&mut self, _: &mut LayoutContext) {
        debug!("assign_height_inline: assigning height for flow {}", self.base.id);

        // Divide the boxes into lines.
        //
        // TODO(#226): Get the CSS `line-height` property from the containing block's style to
        // determine minimum linebox height.
        //
        // TODO(#226): Get the CSS `line-height` property from each non-replaced inline element to
        // determine its height for computing linebox height.
        //
        // TODO(pcwalton): Cache the linebox scanner?
        debug!("assign_height_inline: floats_in: {:?}", self.base.floats_in);
        // assign height for inline boxes
        for box_ in self.boxes.iter() {
            box_.assign_height();
        }
        let scanner_floats = self.base.floats_in.clone();
        let mut scanner = LineboxScanner::new(scanner_floats);

        // Access the linebox scanner.
        scanner.scan_for_lines(self);
        let mut line_height_offset = Au::new(0);

        // All lines use text alignment of the flow.
        let text_align = self.base.flags_info.flags.text_align();

        // Now, go through each line and lay out the boxes inside.
        for line in self.lines.mut_iter() {
            // Lay out boxes horizontally.
            InlineFlow::set_horizontal_box_positions(self.boxes, line, text_align);

            // Set the top y position of the current linebox.
            // `line_height_offset` is updated at the end of the previous loop.
            line.bounds.origin.y = line.bounds.origin.y + line_height_offset;

            // Calculate the distance from baseline to the top and bottom of the linebox.
            let (mut topmost, mut bottommost) = (Au(0), Au(0));
            // Calculate the biggest height among boxes with 'top' and 'bottom' values
            // respectively.
            let (mut biggest_top, mut biggest_bottom) = (Au(0), Au(0));

            for box_i in line.range.eachi() {
                let cur_box = &self.boxes[box_i];
                let top = cur_box.noncontent_top();

                // FIXME(pcwalton): Move into `box.rs` like the rest of box-specific layout code?
                let (top_from_base, bottom_from_base, ascent) = match cur_box.specific {
                    ImageBox(_) => {
                        let mut height = cur_box.content_height();

                        // TODO: margin, border, padding's top and bottom should be calculated in
                        // advance, since baseline of image is bottom margin edge.
                        let bottom = cur_box.noncontent_bottom();
                        let noncontent_height = top + bottom;
                        height = height + noncontent_height;

                        let ascent = height + bottom;
                        (height, Au::new(0), ascent)
                    },
                    ScannedTextBox(ref text_box) => {
                        let range = &text_box.range;
                        let run = &text_box.run;

                        // Compute the height based on the line-height and font size
                        let text_bounds = run.get().metrics_for_range(range).bounding_box;
                        let em_size = text_bounds.size.height;
                        let line_height = cur_box.calculate_line_height(em_size);

                        // Find the top and bottom of the content area.
                        // Those are used in text-top and text-bottom value of 'vertical-align'
                        let text_ascent = text_box.run.get().font_metrics.ascent;

                        // Offset from the top of the box is 1/2 of the leading + ascent
                        let text_offset = text_ascent + (line_height - em_size).scale_by(0.5);
                        text_bounds.translate(&Point2D(cur_box.position.get().origin.x, Au(0)));

                        (text_offset, line_height - text_offset, text_ascent)
                    },
                    GenericBox | IframeBox(_) => {
                        let height = cur_box.position.get().size.height;
                        (height, Au::new(0), height)
                    },
                    UnscannedTextBox(_) => {
                        fail!("Unscanned text boxes should have been scanned by now.")
                    }
                };

                let mut top_from_base = top_from_base;
                let mut bottom_from_base = bottom_from_base;

                // To calculate text-top and text-bottom value of 'vertical-align',
                //  we should find the top and bottom of the content area of parent box.
                // The content area is defined in:
                //      http://www.w3.org/TR/CSS2/visudet.html#inline-non-replaced
                //
                // TODO: We should extract em-box info from the font size of the parent and
                // calculate the distances from the baseline to the top and the bottom of the
                // parent's content area.

                // We should calculate the distance from baseline to the top of parent's content
                // area. But for now we assume it's the font size.
                //
                // The spec does not state which font to use. Previous versions of the code used
                // the parent's font; this code uses the current font.
                let parent_text_top = cur_box.style().Font.get().font_size;

                // We should calculate the distance from baseline to the bottom of the parent's
                // content area. But for now we assume it's zero.
                let parent_text_bottom = Au::new(0);

                // Calculate a relative offset from the baseline.
                //
                // The no-update flag decides whether `biggest_top` and `biggest_bottom` are
                // updated or not. That is, if the box has a `top` or `bottom` value,
                // `no_update_flag` becomes true.
                let (offset, no_update_flag) =
                    InlineFlow::relative_offset_from_baseline(cur_box,
                                                              ascent,
                                                              parent_text_top,
                                                              parent_text_bottom,
                                                              &mut top_from_base,
                                                              &mut bottom_from_base,
                                                              &mut biggest_top,
                                                              &mut biggest_bottom);

                // If the current box has 'top' or 'bottom' value, no_update_flag is true.
                // Otherwise, topmost and bottomost are updated.
                if !no_update_flag && top_from_base > topmost {
                    topmost = top_from_base;
                }
                if !no_update_flag && bottom_from_base > bottommost {
                    bottommost = bottom_from_base;
                }

                cur_box.position.borrow_mut().get().origin.y = line.bounds.origin.y + offset + top;
            }

            // Calculate the distance from baseline to the top of the biggest box with 'bottom'
            // value. Then, if necessary, update the topmost.
            let topmost_of_bottom = biggest_bottom - bottommost;
            if topmost_of_bottom > topmost {
                topmost = topmost_of_bottom;
            }

            // Calculate the distance from baseline to the bottom of the biggest box with 'top'
            // value. Then, if necessary, update the bottommost.
            let bottommost_of_top = biggest_top - topmost;
            if bottommost_of_top > bottommost {
                bottommost = bottommost_of_top;
            }

            // Now, the baseline offset from the top of linebox is set as topmost.
            let baseline_offset = topmost;

            // All boxes' y position is updated following the new baseline offset.
            for box_i in line.range.eachi() {
                let cur_box = &self.boxes[box_i];
                let adjust_offset = match cur_box.vertical_align() {
                    vertical_align::top => Au::new(0),
                    vertical_align::bottom => baseline_offset + bottommost,
                    _ => baseline_offset,
                };

                cur_box.position.borrow_mut().get().origin.y = cur_box.position.get().origin.y +
                    adjust_offset;

                if cur_box.inline_info.with(|info| info.is_none()) {
                    cur_box.inline_info.set(Some(InlineInfo::new()));
                }
                cur_box.inline_info.with_mut( |info| {
                    match info {
                        &Some(ref mut info) => {
                            // TODO (ksh8281) compute vertical-align, line-height
                            info.baseline = line.bounds.origin.y + baseline_offset;
                        },
                        &None => {}
                    }
                });
            }

            // This is used to set the top y position of the next linebox in the next loop.
            line_height_offset = line_height_offset + topmost + bottommost -
                line.bounds.size.height;
            line.bounds.size.height = topmost + bottommost;
        } // End of `lines.each` loop.

        self.base.position.size.height =
            if self.lines.len() > 0 {
                self.lines.last().bounds.origin.y + self.lines.last().bounds.size.height
            } else {
                Au::new(0)
            };

        self.base.floats_out = scanner.floats_out()
                                      .translate(Point2D(Au::new(0),
                                                         -self.base.position.size.height));
    }

    fn collapse_margins(&mut self,
                        _: bool,
                        _: &mut bool,
                        _: &mut Au,
                        _: &mut Au,
                        collapsing: &mut Au,
                        collapsible: &mut Au) {
        *collapsing = Au::new(0);
        // Non-empty inline flows prevent collapsing between the previous margion and the next.
        if self.base.position.size.height > Au::new(0) {
            *collapsible = Au::new(0);
        }
    }

    fn debug_str(&self) -> ~str {
        ~"InlineFlow: " + self.boxes.map(|s| s.debug_str()).connect(", ")
    }
}