Source code

Revision control

Other Tools

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 https://mozilla.org/MPL/2.0/. */
4
5
//! Geometry in flow-relative space.
6
7
use crate::properties::style_structs;
8
use euclid::default::{Point2D, Rect, SideOffsets2D, Size2D};
9
use euclid::num::Zero;
10
use std::cmp::{max, min};
11
use std::fmt::{self, Debug, Error, Formatter};
12
use std::ops::{Add, Sub};
13
use unicode_bidi as bidi;
14
15
pub enum BlockFlowDirection {
16
TopToBottom,
17
RightToLeft,
18
LeftToRight,
19
}
20
21
pub enum InlineBaseDirection {
22
LeftToRight,
23
RightToLeft,
24
}
25
26
// TODO: improve the readability of the WritingMode serialization, refer to the Debug:fmt()
27
bitflags!(
28
#[cfg_attr(feature = "servo", derive(MallocSizeOf, Serialize))]
29
#[repr(C)]
30
pub struct WritingMode: u8 {
31
/// A vertical writing mode; writing-mode is vertical-rl,
32
/// vertical-lr, sideways-lr, or sideways-rl.
33
const VERTICAL = 1 << 0;
34
/// The inline flow direction is reversed against the physical
35
/// direction (i.e. right-to-left or bottom-to-top); writing-mode is
36
/// sideways-lr or direction is rtl (but not both).
37
///
38
/// (This bit can be derived from the others, but we store it for
39
/// convenience.)
40
const INLINE_REVERSED = 1 << 1;
41
/// A vertical writing mode whose block progression direction is left-
42
/// to-right; writing-mode is vertical-lr or sideways-lr.
43
///
44
/// Never set without VERTICAL.
45
const VERTICAL_LR = 1 << 2;
46
/// The line-over/line-under sides are inverted with respect to the
47
/// block-start/block-end edge; writing-mode is vertical-lr.
48
///
49
/// Never set without VERTICAL and VERTICAL_LR.
50
const LINE_INVERTED = 1 << 3;
51
/// direction is rtl.
52
const RTL = 1 << 4;
53
/// All text within a vertical writing mode is displayed sideways
54
/// and runs top-to-bottom or bottom-to-top; set in these cases:
55
///
56
/// * writing-mode: sideways-rl;
57
/// * writing-mode: sideways-lr;
58
///
59
/// Never set without VERTICAL.
60
const VERTICAL_SIDEWAYS = 1 << 5;
61
/// Similar to VERTICAL_SIDEWAYS, but is set via text-orientation;
62
/// set in these cases:
63
///
64
/// * writing-mode: vertical-rl; text-orientation: sideways;
65
/// * writing-mode: vertical-lr; text-orientation: sideways;
66
///
67
/// Never set without VERTICAL.
68
const TEXT_SIDEWAYS = 1 << 6;
69
/// Horizontal text within a vertical writing mode is displayed with each
70
/// glyph upright; set in these cases:
71
///
72
/// * writing-mode: vertical-rl; text-orientation: upright;
73
/// * writing-mode: vertical-lr: text-orientation: upright;
74
///
75
/// Never set without VERTICAL.
76
const UPRIGHT = 1 << 7;
77
}
78
);
79
80
impl WritingMode {
81
/// Return a WritingMode bitflags from the relevant CSS properties.
82
pub fn new(inheritedbox_style: &style_structs::InheritedBox) -> Self {
83
use crate::properties::longhands::direction::computed_value::T as Direction;
84
use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
85
86
let mut flags = WritingMode::empty();
87
88
let direction = inheritedbox_style.clone_direction();
89
let writing_mode = inheritedbox_style.clone_writing_mode();
90
91
match direction {
92
Direction::Ltr => {},
93
Direction::Rtl => {
94
flags.insert(WritingMode::RTL);
95
},
96
}
97
98
match writing_mode {
99
SpecifiedWritingMode::HorizontalTb => {
100
if direction == Direction::Rtl {
101
flags.insert(WritingMode::INLINE_REVERSED);
102
}
103
},
104
SpecifiedWritingMode::VerticalRl => {
105
flags.insert(WritingMode::VERTICAL);
106
if direction == Direction::Rtl {
107
flags.insert(WritingMode::INLINE_REVERSED);
108
}
109
},
110
SpecifiedWritingMode::VerticalLr => {
111
flags.insert(WritingMode::VERTICAL);
112
flags.insert(WritingMode::VERTICAL_LR);
113
flags.insert(WritingMode::LINE_INVERTED);
114
if direction == Direction::Rtl {
115
flags.insert(WritingMode::INLINE_REVERSED);
116
}
117
},
118
#[cfg(feature = "gecko")]
119
SpecifiedWritingMode::SidewaysRl => {
120
flags.insert(WritingMode::VERTICAL);
121
flags.insert(WritingMode::VERTICAL_SIDEWAYS);
122
if direction == Direction::Rtl {
123
flags.insert(WritingMode::INLINE_REVERSED);
124
}
125
},
126
#[cfg(feature = "gecko")]
127
SpecifiedWritingMode::SidewaysLr => {
128
flags.insert(WritingMode::VERTICAL);
129
flags.insert(WritingMode::VERTICAL_LR);
130
flags.insert(WritingMode::VERTICAL_SIDEWAYS);
131
if direction == Direction::Ltr {
132
flags.insert(WritingMode::INLINE_REVERSED);
133
}
134
},
135
}
136
137
#[cfg(feature = "gecko")]
138
{
139
use crate::properties::longhands::text_orientation::computed_value::T as TextOrientation;
140
141
// text-orientation only has an effect for vertical-rl and
142
// vertical-lr values of writing-mode.
143
match writing_mode {
144
SpecifiedWritingMode::VerticalRl | SpecifiedWritingMode::VerticalLr => {
145
match inheritedbox_style.clone_text_orientation() {
146
TextOrientation::Mixed => {},
147
TextOrientation::Upright => {
148
flags.insert(WritingMode::UPRIGHT);
149
151
//
152
// > This value causes the used value of direction
153
// > to be ltr, and for the purposes of bidi
154
// > reordering, causes all characters to be treated
155
// > as strong LTR.
156
flags.remove(WritingMode::RTL);
157
flags.remove(WritingMode::INLINE_REVERSED);
158
},
159
TextOrientation::Sideways => {
160
flags.insert(WritingMode::TEXT_SIDEWAYS);
161
},
162
}
163
},
164
_ => {},
165
}
166
}
167
168
flags
169
}
170
171
#[inline]
172
pub fn is_vertical(&self) -> bool {
173
self.intersects(WritingMode::VERTICAL)
174
}
175
176
/// Assuming .is_vertical(), does the block direction go left to right?
177
#[inline]
178
pub fn is_vertical_lr(&self) -> bool {
179
self.intersects(WritingMode::VERTICAL_LR)
180
}
181
182
/// Assuming .is_vertical(), does the inline direction go top to bottom?
183
#[inline]
184
pub fn is_inline_tb(&self) -> bool {
186
!self.intersects(WritingMode::INLINE_REVERSED)
187
}
188
189
#[inline]
190
pub fn is_bidi_ltr(&self) -> bool {
191
!self.intersects(WritingMode::RTL)
192
}
193
194
#[inline]
195
pub fn is_sideways(&self) -> bool {
196
self.intersects(WritingMode::VERTICAL_SIDEWAYS | WritingMode::TEXT_SIDEWAYS)
197
}
198
199
#[inline]
200
pub fn is_upright(&self) -> bool {
201
self.intersects(WritingMode::UPRIGHT)
202
}
203
204
#[inline]
205
pub fn inline_start_physical_side(&self) -> PhysicalSide {
206
match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) {
207
(false, _, true) => PhysicalSide::Left,
208
(false, _, false) => PhysicalSide::Right,
209
(true, true, _) => PhysicalSide::Top,
210
(true, false, _) => PhysicalSide::Bottom,
211
}
212
}
213
214
#[inline]
215
pub fn inline_end_physical_side(&self) -> PhysicalSide {
216
match (self.is_vertical(), self.is_inline_tb(), self.is_bidi_ltr()) {
217
(false, _, true) => PhysicalSide::Right,
218
(false, _, false) => PhysicalSide::Left,
219
(true, true, _) => PhysicalSide::Bottom,
220
(true, false, _) => PhysicalSide::Top,
221
}
222
}
223
224
#[inline]
225
pub fn block_start_physical_side(&self) -> PhysicalSide {
226
match (self.is_vertical(), self.is_vertical_lr()) {
227
(false, _) => PhysicalSide::Top,
228
(true, true) => PhysicalSide::Left,
229
(true, false) => PhysicalSide::Right,
230
}
231
}
232
233
#[inline]
234
pub fn block_end_physical_side(&self) -> PhysicalSide {
235
match (self.is_vertical(), self.is_vertical_lr()) {
236
(false, _) => PhysicalSide::Bottom,
237
(true, true) => PhysicalSide::Right,
238
(true, false) => PhysicalSide::Left,
239
}
240
}
241
242
#[inline]
243
fn physical_sides_to_corner(
244
block_side: PhysicalSide,
245
inline_side: PhysicalSide,
246
) -> PhysicalCorner {
247
match (block_side, inline_side) {
248
(PhysicalSide::Top, PhysicalSide::Left) | (PhysicalSide::Left, PhysicalSide::Top) => {
249
PhysicalCorner::TopLeft
250
},
251
(PhysicalSide::Top, PhysicalSide::Right) | (PhysicalSide::Right, PhysicalSide::Top) => {
252
PhysicalCorner::TopRight
253
},
254
(PhysicalSide::Bottom, PhysicalSide::Right) |
255
(PhysicalSide::Right, PhysicalSide::Bottom) => PhysicalCorner::BottomRight,
256
(PhysicalSide::Bottom, PhysicalSide::Left) |
257
(PhysicalSide::Left, PhysicalSide::Bottom) => PhysicalCorner::BottomLeft,
258
_ => unreachable!("block and inline sides must be orthogonal"),
259
}
260
}
261
262
#[inline]
263
pub fn start_start_physical_corner(&self) -> PhysicalCorner {
264
WritingMode::physical_sides_to_corner(
265
self.block_start_physical_side(),
266
self.inline_start_physical_side(),
267
)
268
}
269
270
#[inline]
271
pub fn start_end_physical_corner(&self) -> PhysicalCorner {
272
WritingMode::physical_sides_to_corner(
273
self.block_start_physical_side(),
274
self.inline_end_physical_side(),
275
)
276
}
277
278
#[inline]
279
pub fn end_start_physical_corner(&self) -> PhysicalCorner {
280
WritingMode::physical_sides_to_corner(
281
self.block_end_physical_side(),
282
self.inline_start_physical_side(),
283
)
284
}
285
286
#[inline]
287
pub fn end_end_physical_corner(&self) -> PhysicalCorner {
288
WritingMode::physical_sides_to_corner(
289
self.block_end_physical_side(),
290
self.inline_end_physical_side(),
291
)
292
}
293
294
#[inline]
295
pub fn block_flow_direction(&self) -> BlockFlowDirection {
296
match (self.is_vertical(), self.is_vertical_lr()) {
297
(false, _) => BlockFlowDirection::TopToBottom,
298
(true, true) => BlockFlowDirection::LeftToRight,
299
(true, false) => BlockFlowDirection::RightToLeft,
300
}
301
}
302
303
#[inline]
304
pub fn inline_base_direction(&self) -> InlineBaseDirection {
305
if self.intersects(WritingMode::RTL) {
306
InlineBaseDirection::RightToLeft
307
} else {
308
InlineBaseDirection::LeftToRight
309
}
310
}
311
312
#[inline]
313
/// The default bidirectional embedding level for this writing mode.
314
///
315
/// Returns bidi level 0 if the mode is LTR, or 1 otherwise.
316
pub fn to_bidi_level(&self) -> bidi::Level {
317
if self.is_bidi_ltr() {
318
bidi::Level::ltr()
319
} else {
320
bidi::Level::rtl()
321
}
322
}
323
}
324
325
impl fmt::Display for WritingMode {
326
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
327
if self.is_vertical() {
328
write!(formatter, "V")?;
329
if self.is_vertical_lr() {
330
write!(formatter, " LR")?;
331
} else {
332
write!(formatter, " RL")?;
333
}
334
if self.is_sideways() {
335
write!(formatter, " Sideways")?;
336
}
337
if self.intersects(WritingMode::LINE_INVERTED) {
338
write!(formatter, " Inverted")?;
339
}
340
} else {
341
write!(formatter, "H")?;
342
}
343
if self.is_bidi_ltr() {
344
write!(formatter, " LTR")
345
} else {
346
write!(formatter, " RTL")
347
}
348
}
349
}
350
351
/// Wherever logical geometry is used, the writing mode is known based on context:
352
/// every method takes a `mode` parameter.
353
/// However, this context is easy to get wrong.
354
/// In debug builds only, logical geometry objects store their writing mode
355
/// (in addition to taking it as a parameter to methods) and check it.
356
/// In non-debug builds, make this storage zero-size and the checks no-ops.
357
#[cfg(not(debug_assertions))]
358
#[derive(Clone, Copy, Eq, PartialEq)]
359
#[cfg_attr(feature = "servo", derive(Serialize))]
360
struct DebugWritingMode;
361
362
#[cfg(debug_assertions)]
363
#[derive(Clone, Copy, Eq, PartialEq)]
364
#[cfg_attr(feature = "servo", derive(Serialize))]
365
struct DebugWritingMode {
366
mode: WritingMode,
367
}
368
369
#[cfg(not(debug_assertions))]
370
impl DebugWritingMode {
371
#[inline]
372
fn check(&self, _other: WritingMode) {}
373
374
#[inline]
375
fn check_debug(&self, _other: DebugWritingMode) {}
376
377
#[inline]
378
fn new(_mode: WritingMode) -> DebugWritingMode {
379
DebugWritingMode
380
}
381
}
382
383
#[cfg(debug_assertions)]
384
impl DebugWritingMode {
385
#[inline]
386
fn check(&self, other: WritingMode) {
387
assert_eq!(self.mode, other)
388
}
389
390
#[inline]
391
fn check_debug(&self, other: DebugWritingMode) {
392
assert_eq!(self.mode, other.mode)
393
}
394
395
#[inline]
396
fn new(mode: WritingMode) -> DebugWritingMode {
397
DebugWritingMode { mode: mode }
398
}
399
}
400
401
impl Debug for DebugWritingMode {
402
#[cfg(not(debug_assertions))]
403
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
404
write!(formatter, "?")
405
}
406
407
#[cfg(debug_assertions)]
408
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
409
write!(formatter, "{}", self.mode)
410
}
411
}
412
413
// Used to specify the logical direction.
414
#[derive(Clone, Copy, Debug, PartialEq)]
415
#[cfg_attr(feature = "servo", derive(Serialize))]
416
pub enum Direction {
417
Inline,
418
Block,
419
}
420
421
/// A 2D size in flow-relative dimensions
422
#[derive(Clone, Copy, Eq, PartialEq)]
423
#[cfg_attr(feature = "servo", derive(Serialize))]
424
pub struct LogicalSize<T> {
425
pub inline: T, // inline-size, a.k.a. logical width, a.k.a. measure
426
pub block: T, // block-size, a.k.a. logical height, a.k.a. extent
427
debug_writing_mode: DebugWritingMode,
428
}
429
430
impl<T: Debug> Debug for LogicalSize<T> {
431
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
432
write!(
433
formatter,
434
"LogicalSize({:?}, i{:?}×b{:?})",
435
self.debug_writing_mode, self.inline, self.block
436
)
437
}
438
}
439
440
// Can not implement the Zero trait: its zero() method does not have the `mode` parameter.
441
impl<T: Zero> LogicalSize<T> {
442
#[inline]
443
pub fn zero(mode: WritingMode) -> LogicalSize<T> {
444
LogicalSize {
445
inline: Zero::zero(),
446
block: Zero::zero(),
447
debug_writing_mode: DebugWritingMode::new(mode),
448
}
449
}
450
}
451
452
impl<T: Copy> LogicalSize<T> {
453
#[inline]
454
pub fn new(mode: WritingMode, inline: T, block: T) -> LogicalSize<T> {
455
LogicalSize {
456
inline: inline,
457
block: block,
458
debug_writing_mode: DebugWritingMode::new(mode),
459
}
460
}
461
462
#[inline]
463
pub fn from_physical(mode: WritingMode, size: Size2D<T>) -> LogicalSize<T> {
464
if mode.is_vertical() {
465
LogicalSize::new(mode, size.height, size.width)
466
} else {
467
LogicalSize::new(mode, size.width, size.height)
468
}
469
}
470
471
#[inline]
472
pub fn width(&self, mode: WritingMode) -> T {
473
self.debug_writing_mode.check(mode);
474
if mode.is_vertical() {
475
self.block
476
} else {
477
self.inline
478
}
479
}
480
481
#[inline]
482
pub fn set_width(&mut self, mode: WritingMode, width: T) {
483
self.debug_writing_mode.check(mode);
484
if mode.is_vertical() {
485
self.block = width
486
} else {
487
self.inline = width
488
}
489
}
490
491
#[inline]
492
pub fn height(&self, mode: WritingMode) -> T {
493
self.debug_writing_mode.check(mode);
494
if mode.is_vertical() {
495
self.inline
496
} else {
497
self.block
498
}
499
}
500
501
#[inline]
502
pub fn set_height(&mut self, mode: WritingMode, height: T) {
503
self.debug_writing_mode.check(mode);
504
if mode.is_vertical() {
505
self.inline = height
506
} else {
507
self.block = height
508
}
509
}
510
511
#[inline]
512
pub fn to_physical(&self, mode: WritingMode) -> Size2D<T> {
513
self.debug_writing_mode.check(mode);
514
if mode.is_vertical() {
515
Size2D::new(self.block, self.inline)
516
} else {
517
Size2D::new(self.inline, self.block)
518
}
519
}
520
521
#[inline]
522
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalSize<T> {
523
if mode_from == mode_to {
524
self.debug_writing_mode.check(mode_from);
525
*self
526
} else {
527
LogicalSize::from_physical(mode_to, self.to_physical(mode_from))
528
}
529
}
530
}
531
532
impl<T: Add<T, Output = T>> Add for LogicalSize<T> {
533
type Output = LogicalSize<T>;
534
535
#[inline]
536
fn add(self, other: LogicalSize<T>) -> LogicalSize<T> {
537
self.debug_writing_mode
538
.check_debug(other.debug_writing_mode);
539
LogicalSize {
540
debug_writing_mode: self.debug_writing_mode,
541
inline: self.inline + other.inline,
542
block: self.block + other.block,
543
}
544
}
545
}
546
547
impl<T: Sub<T, Output = T>> Sub for LogicalSize<T> {
548
type Output = LogicalSize<T>;
549
550
#[inline]
551
fn sub(self, other: LogicalSize<T>) -> LogicalSize<T> {
552
self.debug_writing_mode
553
.check_debug(other.debug_writing_mode);
554
LogicalSize {
555
debug_writing_mode: self.debug_writing_mode,
556
inline: self.inline - other.inline,
557
block: self.block - other.block,
558
}
559
}
560
}
561
562
/// A 2D point in flow-relative dimensions
563
#[derive(Clone, Copy, Eq, PartialEq)]
564
#[cfg_attr(feature = "servo", derive(Serialize))]
565
pub struct LogicalPoint<T> {
566
/// inline-axis coordinate
567
pub i: T,
568
/// block-axis coordinate
569
pub b: T,
570
debug_writing_mode: DebugWritingMode,
571
}
572
573
impl<T: Debug> Debug for LogicalPoint<T> {
574
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
575
write!(
576
formatter,
577
"LogicalPoint({:?} (i{:?}, b{:?}))",
578
self.debug_writing_mode, self.i, self.b
579
)
580
}
581
}
582
583
// Can not implement the Zero trait: its zero() method does not have the `mode` parameter.
584
impl<T: Zero> LogicalPoint<T> {
585
#[inline]
586
pub fn zero(mode: WritingMode) -> LogicalPoint<T> {
587
LogicalPoint {
588
i: Zero::zero(),
589
b: Zero::zero(),
590
debug_writing_mode: DebugWritingMode::new(mode),
591
}
592
}
593
}
594
595
impl<T: Copy> LogicalPoint<T> {
596
#[inline]
597
pub fn new(mode: WritingMode, i: T, b: T) -> LogicalPoint<T> {
598
LogicalPoint {
599
i: i,
600
b: b,
601
debug_writing_mode: DebugWritingMode::new(mode),
602
}
603
}
604
}
605
606
impl<T: Copy + Sub<T, Output = T>> LogicalPoint<T> {
607
#[inline]
608
pub fn from_physical(
609
mode: WritingMode,
610
point: Point2D<T>,
611
container_size: Size2D<T>,
612
) -> LogicalPoint<T> {
613
if mode.is_vertical() {
614
LogicalPoint {
615
i: if mode.is_inline_tb() {
616
point.y
617
} else {
618
container_size.height - point.y
619
},
620
b: if mode.is_vertical_lr() {
621
point.x
622
} else {
623
container_size.width - point.x
624
},
625
debug_writing_mode: DebugWritingMode::new(mode),
626
}
627
} else {
628
LogicalPoint {
629
i: if mode.is_bidi_ltr() {
630
point.x
631
} else {
632
container_size.width - point.x
633
},
634
b: point.y,
635
debug_writing_mode: DebugWritingMode::new(mode),
636
}
637
}
638
}
639
640
#[inline]
641
pub fn x(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
642
self.debug_writing_mode.check(mode);
643
if mode.is_vertical() {
644
if mode.is_vertical_lr() {
645
self.b
646
} else {
647
container_size.width - self.b
648
}
649
} else {
650
if mode.is_bidi_ltr() {
651
self.i
652
} else {
653
container_size.width - self.i
654
}
655
}
656
}
657
658
#[inline]
659
pub fn set_x(&mut self, mode: WritingMode, x: T, container_size: Size2D<T>) {
660
self.debug_writing_mode.check(mode);
661
if mode.is_vertical() {
662
self.b = if mode.is_vertical_lr() {
663
x
664
} else {
665
container_size.width - x
666
}
667
} else {
668
self.i = if mode.is_bidi_ltr() {
669
x
670
} else {
671
container_size.width - x
672
}
673
}
674
}
675
676
#[inline]
677
pub fn y(&self, mode: WritingMode, container_size: Size2D<T>) -> T {
678
self.debug_writing_mode.check(mode);
679
if mode.is_vertical() {
680
if mode.is_inline_tb() {
681
self.i
682
} else {
683
container_size.height - self.i
684
}
685
} else {
686
self.b
687
}
688
}
689
690
#[inline]
691
pub fn set_y(&mut self, mode: WritingMode, y: T, container_size: Size2D<T>) {
692
self.debug_writing_mode.check(mode);
693
if mode.is_vertical() {
694
self.i = if mode.is_inline_tb() {
695
y
696
} else {
697
container_size.height - y
698
}
699
} else {
700
self.b = y
701
}
702
}
703
704
#[inline]
705
pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Point2D<T> {
706
self.debug_writing_mode.check(mode);
707
if mode.is_vertical() {
708
Point2D::new(
709
if mode.is_vertical_lr() {
710
self.b
711
} else {
712
container_size.width - self.b
713
},
714
if mode.is_inline_tb() {
715
self.i
716
} else {
717
container_size.height - self.i
718
},
719
)
720
} else {
721
Point2D::new(
722
if mode.is_bidi_ltr() {
723
self.i
724
} else {
725
container_size.width - self.i
726
},
727
self.b,
728
)
729
}
730
}
731
732
#[inline]
733
pub fn convert(
734
&self,
735
mode_from: WritingMode,
736
mode_to: WritingMode,
737
container_size: Size2D<T>,
738
) -> LogicalPoint<T> {
739
if mode_from == mode_to {
740
self.debug_writing_mode.check(mode_from);
741
*self
742
} else {
743
LogicalPoint::from_physical(
744
mode_to,
745
self.to_physical(mode_from, container_size),
746
container_size,
747
)
748
}
749
}
750
}
751
752
impl<T: Copy + Add<T, Output = T>> LogicalPoint<T> {
753
/// This doesn’t really makes sense,
754
/// but happens when dealing with multiple origins.
755
#[inline]
756
pub fn add_point(&self, other: &LogicalPoint<T>) -> LogicalPoint<T> {
757
self.debug_writing_mode
758
.check_debug(other.debug_writing_mode);
759
LogicalPoint {
760
debug_writing_mode: self.debug_writing_mode,
761
i: self.i + other.i,
762
b: self.b + other.b,
763
}
764
}
765
}
766
767
impl<T: Copy + Add<T, Output = T>> Add<LogicalSize<T>> for LogicalPoint<T> {
768
type Output = LogicalPoint<T>;
769
770
#[inline]
771
fn add(self, other: LogicalSize<T>) -> LogicalPoint<T> {
772
self.debug_writing_mode
773
.check_debug(other.debug_writing_mode);
774
LogicalPoint {
775
debug_writing_mode: self.debug_writing_mode,
776
i: self.i + other.inline,
777
b: self.b + other.block,
778
}
779
}
780
}
781
782
impl<T: Copy + Sub<T, Output = T>> Sub<LogicalSize<T>> for LogicalPoint<T> {
783
type Output = LogicalPoint<T>;
784
785
#[inline]
786
fn sub(self, other: LogicalSize<T>) -> LogicalPoint<T> {
787
self.debug_writing_mode
788
.check_debug(other.debug_writing_mode);
789
LogicalPoint {
790
debug_writing_mode: self.debug_writing_mode,
791
i: self.i - other.inline,
792
b: self.b - other.block,
793
}
794
}
795
}
796
797
/// A "margin" in flow-relative dimensions
798
/// Represents the four sides of the margins, borders, or padding of a CSS box,
799
/// or a combination of those.
800
/// A positive "margin" can be added to a rectangle to obtain a bigger rectangle.
801
#[derive(Clone, Copy, Eq, PartialEq)]
802
#[cfg_attr(feature = "servo", derive(Serialize))]
803
pub struct LogicalMargin<T> {
804
pub block_start: T,
805
pub inline_end: T,
806
pub block_end: T,
807
pub inline_start: T,
808
debug_writing_mode: DebugWritingMode,
809
}
810
811
impl<T: Debug> Debug for LogicalMargin<T> {
812
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
813
let writing_mode_string = if cfg!(debug_assertions) {
814
format!("{:?}, ", self.debug_writing_mode)
815
} else {
816
"".to_owned()
817
};
818
819
write!(
820
formatter,
821
"LogicalMargin({}i:{:?}..{:?} b:{:?}..{:?})",
822
writing_mode_string,
823
self.inline_start,
824
self.inline_end,
825
self.block_start,
826
self.block_end
827
)
828
}
829
}
830
831
impl<T: Zero> LogicalMargin<T> {
832
#[inline]
833
pub fn zero(mode: WritingMode) -> LogicalMargin<T> {
834
LogicalMargin {
835
block_start: Zero::zero(),
836
inline_end: Zero::zero(),
837
block_end: Zero::zero(),
838
inline_start: Zero::zero(),
839
debug_writing_mode: DebugWritingMode::new(mode),
840
}
841
}
842
}
843
844
impl<T: Copy> LogicalMargin<T> {
845
#[inline]
846
pub fn new(
847
mode: WritingMode,
848
block_start: T,
849
inline_end: T,
850
block_end: T,
851
inline_start: T,
852
) -> LogicalMargin<T> {
853
LogicalMargin {
854
block_start: block_start,
855
inline_end: inline_end,
856
block_end: block_end,
857
inline_start: inline_start,
858
debug_writing_mode: DebugWritingMode::new(mode),
859
}
860
}
861
862
#[inline]
863
pub fn new_all_same(mode: WritingMode, value: T) -> LogicalMargin<T> {
864
LogicalMargin::new(mode, value, value, value, value)
865
}
866
867
#[inline]
868
pub fn from_physical(mode: WritingMode, offsets: SideOffsets2D<T>) -> LogicalMargin<T> {
869
let block_start;
870
let inline_end;
871
let block_end;
872
let inline_start;
873
if mode.is_vertical() {
874
if mode.is_vertical_lr() {
875
block_start = offsets.left;
876
block_end = offsets.right;
877
} else {
878
block_start = offsets.right;
879
block_end = offsets.left;
880
}
881
if mode.is_inline_tb() {
882
inline_start = offsets.top;
883
inline_end = offsets.bottom;
884
} else {
885
inline_start = offsets.bottom;
886
inline_end = offsets.top;
887
}
888
} else {
889
block_start = offsets.top;
890
block_end = offsets.bottom;
891
if mode.is_bidi_ltr() {
892
inline_start = offsets.left;
893
inline_end = offsets.right;
894
} else {
895
inline_start = offsets.right;
896
inline_end = offsets.left;
897
}
898
}
899
LogicalMargin::new(mode, block_start, inline_end, block_end, inline_start)
900
}
901
902
#[inline]
903
pub fn top(&self, mode: WritingMode) -> T {
904
self.debug_writing_mode.check(mode);
905
if mode.is_vertical() {
906
if mode.is_inline_tb() {
907
self.inline_start
908
} else {
909
self.inline_end
910
}
911
} else {
912
self.block_start
913
}
914
}
915
916
#[inline]
917
pub fn set_top(&mut self, mode: WritingMode, top: T) {
918
self.debug_writing_mode.check(mode);
919
if mode.is_vertical() {
920
if mode.is_inline_tb() {
921
self.inline_start = top
922
} else {
923
self.inline_end = top
924
}
925
} else {
926
self.block_start = top
927
}
928
}
929
930
#[inline]
931
pub fn right(&self, mode: WritingMode) -> T {
932
self.debug_writing_mode.check(mode);
933
if mode.is_vertical() {
934
if mode.is_vertical_lr() {
935
self.block_end
936
} else {
937
self.block_start
938
}
939
} else {
940
if mode.is_bidi_ltr() {
941
self.inline_end
942
} else {
943
self.inline_start
944
}
945
}
946
}
947
948
#[inline]
949
pub fn set_right(&mut self, mode: WritingMode, right: T) {
950
self.debug_writing_mode.check(mode);
951
if mode.is_vertical() {
952
if mode.is_vertical_lr() {
953
self.block_end = right
954
} else {
955
self.block_start = right
956
}
957
} else {
958
if mode.is_bidi_ltr() {
959
self.inline_end = right
960
} else {
961
self.inline_start = right
962
}
963
}
964
}
965
966
#[inline]
967
pub fn bottom(&self, mode: WritingMode) -> T {
968
self.debug_writing_mode.check(mode);
969
if mode.is_vertical() {
970
if mode.is_inline_tb() {
971
self.inline_end
972
} else {
973
self.inline_start
974
}
975
} else {
976
self.block_end
977
}
978
}
979
980
#[inline]
981
pub fn set_bottom(&mut self, mode: WritingMode, bottom: T) {
982
self.debug_writing_mode.check(mode);
983
if mode.is_vertical() {
984
if mode.is_inline_tb() {
985
self.inline_end = bottom
986
} else {
987
self.inline_start = bottom
988
}
989
} else {
990
self.block_end = bottom
991
}
992
}
993
994
#[inline]
995
pub fn left(&self, mode: WritingMode) -> T {
996
self.debug_writing_mode.check(mode);
997
if mode.is_vertical() {
998
if mode.is_vertical_lr() {
999
self.block_start
1000
} else {
1001
self.block_end
1002
}
1003
} else {
1004
if mode.is_bidi_ltr() {
1005
self.inline_start
1006
} else {
1007
self.inline_end
1008
}
1009
}
1010
}
1011
1012
#[inline]
1013
pub fn set_left(&mut self, mode: WritingMode, left: T) {
1014
self.debug_writing_mode.check(mode);
1015
if mode.is_vertical() {
1016
if mode.is_vertical_lr() {
1017
self.block_start = left
1018
} else {
1019
self.block_end = left
1020
}
1021
} else {
1022
if mode.is_bidi_ltr() {
1023
self.inline_start = left
1024
} else {
1025
self.inline_end = left
1026
}
1027
}
1028
}
1029
1030
#[inline]
1031
pub fn to_physical(&self, mode: WritingMode) -> SideOffsets2D<T> {
1032
self.debug_writing_mode.check(mode);
1033
let top;
1034
let right;
1035
let bottom;
1036
let left;
1037
if mode.is_vertical() {
1038
if mode.is_vertical_lr() {
1039
left = self.block_start;
1040
right = self.block_end;
1041
} else {
1042
right = self.block_start;
1043
left = self.block_end;
1044
}
1045
if mode.is_inline_tb() {
1046
top = self.inline_start;
1047
bottom = self.inline_end;
1048
} else {
1049
bottom = self.inline_start;
1050
top = self.inline_end;
1051
}
1052
} else {
1053
top = self.block_start;
1054
bottom = self.block_end;
1055
if mode.is_bidi_ltr() {
1056
left = self.inline_start;
1057
right = self.inline_end;
1058
} else {
1059
right = self.inline_start;
1060
left = self.inline_end;
1061
}
1062
}
1063
SideOffsets2D::new(top, right, bottom, left)
1064
}
1065
1066
#[inline]
1067
pub fn convert(&self, mode_from: WritingMode, mode_to: WritingMode) -> LogicalMargin<T> {
1068
if mode_from == mode_to {
1069
self.debug_writing_mode.check(mode_from);
1070
*self
1071
} else {
1072
LogicalMargin::from_physical(mode_to, self.to_physical(mode_from))
1073
}
1074
}
1075
}
1076
1077
impl<T: PartialEq + Zero> LogicalMargin<T> {
1078
#[inline]
1079
pub fn is_zero(&self) -> bool {
1080
self.block_start == Zero::zero() &&
1081
self.inline_end == Zero::zero() &&
1082
self.block_end == Zero::zero() &&
1083
self.inline_start == Zero::zero()
1084
}
1085
}
1086
1087
impl<T: Copy + Add<T, Output = T>> LogicalMargin<T> {
1088
#[inline]
1089
pub fn inline_start_end(&self) -> T {
1090
self.inline_start + self.inline_end
1091
}
1092
1093
#[inline]
1094
pub fn block_start_end(&self) -> T {
1095
self.block_start + self.block_end
1096
}
1097
1098
#[inline]
1099
pub fn start_end(&self, direction: Direction) -> T {
1100
match direction {
1101
Direction::Inline => self.inline_start + self.inline_end,
1102
Direction::Block => self.block_start + self.block_end,
1103
}
1104
}
1105
1106
#[inline]
1107
pub fn top_bottom(&self, mode: WritingMode) -> T {
1108
self.debug_writing_mode.check(mode);
1109
if mode.is_vertical() {
1110
self.inline_start_end()
1111
} else {
1112
self.block_start_end()
1113
}
1114
}
1115
1116
#[inline]
1117
pub fn left_right(&self, mode: WritingMode) -> T {
1118
self.debug_writing_mode.check(mode);
1119
if mode.is_vertical() {
1120
self.block_start_end()
1121
} else {
1122
self.inline_start_end()
1123
}
1124
}
1125
}
1126
1127
impl<T: Add<T, Output = T>> Add for LogicalMargin<T> {
1128
type Output = LogicalMargin<T>;
1129
1130
#[inline]
1131
fn add(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1132
self.debug_writing_mode
1133
.check_debug(other.debug_writing_mode);
1134
LogicalMargin {
1135
debug_writing_mode: self.debug_writing_mode,
1136
block_start: self.block_start + other.block_start,
1137
inline_end: self.inline_end + other.inline_end,
1138
block_end: self.block_end + other.block_end,
1139
inline_start: self.inline_start + other.inline_start,
1140
}
1141
}
1142
}
1143
1144
impl<T: Sub<T, Output = T>> Sub for LogicalMargin<T> {
1145
type Output = LogicalMargin<T>;
1146
1147
#[inline]
1148
fn sub(self, other: LogicalMargin<T>) -> LogicalMargin<T> {
1149
self.debug_writing_mode
1150
.check_debug(other.debug_writing_mode);
1151
LogicalMargin {
1152
debug_writing_mode: self.debug_writing_mode,
1153
block_start: self.block_start - other.block_start,
1154
inline_end: self.inline_end - other.inline_end,
1155
block_end: self.block_end - other.block_end,
1156
inline_start: self.inline_start - other.inline_start,
1157
}
1158
}
1159
}
1160
1161
/// A rectangle in flow-relative dimensions
1162
#[derive(Clone, Copy, Eq, PartialEq)]
1163
#[cfg_attr(feature = "servo", derive(Serialize))]
1164
pub struct LogicalRect<T> {
1165
pub start: LogicalPoint<T>,
1166
pub size: LogicalSize<T>,
1167
debug_writing_mode: DebugWritingMode,
1168
}
1169
1170
impl<T: Debug> Debug for LogicalRect<T> {
1171
fn fmt(&self, formatter: &mut Formatter) -> Result<(), Error> {
1172
let writing_mode_string = if cfg!(debug_assertions) {
1173
format!("{:?}, ", self.debug_writing_mode)
1174
} else {
1175
"".to_owned()
1176
};
1177
1178
write!(
1179
formatter,
1180
"LogicalRect({}i{:?}×b{:?}, @ (i{:?},b{:?}))",
1181
writing_mode_string, self.size.inline, self.size.block, self.start.i, self.start.b
1182
)
1183
}
1184
}
1185
1186
impl<T: Zero> LogicalRect<T> {
1187
#[inline]
1188
pub fn zero(mode: WritingMode) -> LogicalRect<T> {
1189
LogicalRect {
1190
start: LogicalPoint::zero(mode),
1191
size: LogicalSize::zero(mode),
1192
debug_writing_mode: DebugWritingMode::new(mode),
1193
}
1194
}
1195
}
1196
1197
impl<T: Copy> LogicalRect<T> {
1198
#[inline]
1199
pub fn new(
1200
mode: WritingMode,
1201
inline_start: T,
1202
block_start: T,
1203
inline: T,
1204
block: T,
1205
) -> LogicalRect<T> {
1206
LogicalRect {
1207
start: LogicalPoint::new(mode, inline_start, block_start),
1208
size: LogicalSize::new(mode, inline, block),
1209
debug_writing_mode: DebugWritingMode::new(mode),
1210
}
1211
}
1212
1213
#[inline]
1214
pub fn from_point_size(
1215
mode: WritingMode,
1216
start: LogicalPoint<T>,
1217
size: LogicalSize<T>,
1218
) -> LogicalRect<T> {
1219
start.debug_writing_mode.check(mode);
1220
size.debug_writing_mode.check(mode);
1221
LogicalRect {
1222
start: start,
1223
size: size,
1224
debug_writing_mode: DebugWritingMode::new(mode),
1225
}
1226
}
1227
}
1228
1229
impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1230
#[inline]
1231
pub fn from_physical(
1232
mode: WritingMode,
1233
rect: Rect<T>,
1234
container_size: Size2D<T>,
1235
) -> LogicalRect<T> {
1236
let inline_start;
1237
let block_start;
1238
let inline;
1239
let block;
1240
if mode.is_vertical() {
1241
inline = rect.size.height;
1242
block = rect.size.width;
1243
if mode.is_vertical_lr() {
1244
block_start = rect.origin.x;
1245
} else {
1246
block_start = container_size.width - (rect.origin.x + rect.size.width);
1247
}
1248
if mode.is_inline_tb() {
1249
inline_start = rect.origin.y;
1250
} else {
1251
inline_start = container_size.height - (rect.origin.y + rect.size.height);
1252
}
1253
} else {
1254
inline = rect.size.width;
1255
block = rect.size.height;
1256
block_start = rect.origin.y;
1257
if mode.is_bidi_ltr() {
1258
inline_start = rect.origin.x;
1259
} else {
1260
inline_start = container_size.width - (rect.origin.x + rect.size.width);
1261
}
1262
}
1263
LogicalRect {
1264
start: LogicalPoint::new(mode, inline_start, block_start),
1265
size: LogicalSize::new(mode, inline, block),
1266
debug_writing_mode: DebugWritingMode::new(mode),
1267
}
1268
}
1269
1270
#[inline]
1271
pub fn inline_end(&self) -> T {
1272
self.start.i + self.size.inline
1273
}
1274
1275
#[inline]
1276
pub fn block_end(&self) -> T {
1277
self.start.b + self.size.block
1278
}
1279
1280
#[inline]
1281
pub fn to_physical(&self, mode: WritingMode, container_size: Size2D<T>) -> Rect<T> {
1282
self.debug_writing_mode.check(mode);
1283
let x;
1284
let y;
1285
let width;
1286
let height;
1287
if mode.is_vertical() {
1288
width = self.size.block;
1289
height = self.size.inline;
1290
if mode.is_vertical_lr() {
1291
x = self.start.b;
1292
} else {
1293
x = container_size.width - self.block_end();
1294
}
1295
if mode.is_inline_tb() {
1296
y = self.start.i;
1297
} else {
1298
y = container_size.height - self.inline_end();
1299
}
1300
} else {
1301
width = self.size.inline;
1302
height = self.size.block;
1303
y = self.start.b;
1304
if mode.is_bidi_ltr() {
1305
x = self.start.i;
1306
} else {
1307
x = container_size.width - self.inline_end();
1308
}
1309
}
1310
Rect {
1311
origin: Point2D::new(x, y),
1312
size: Size2D::new(width, height),
1313
}
1314
}
1315
1316
#[inline]
1317
pub fn convert(
1318
&self,
1319
mode_from: WritingMode,
1320
mode_to: WritingMode,
1321
container_size: Size2D<T>,
1322
) -> LogicalRect<T> {
1323
if mode_from == mode_to {
1324
self.debug_writing_mode.check(mode_from);
1325
*self
1326
} else {
1327
LogicalRect::from_physical(
1328
mode_to,
1329
self.to_physical(mode_from, container_size),
1330
container_size,
1331
)
1332
}
1333
}
1334
1335
pub fn translate_by_size(&self, offset: LogicalSize<T>) -> LogicalRect<T> {
1336
LogicalRect {
1337
start: self.start + offset,
1338
..*self
1339
}
1340
}
1341
1342
pub fn translate(&self, offset: &LogicalPoint<T>) -> LogicalRect<T> {
1343
LogicalRect {
1344
start: self.start +
1345
LogicalSize {
1346
inline: offset.i,
1347
block: offset.b,
1348
debug_writing_mode: offset.debug_writing_mode,
1349
},
1350
size: self.size,
1351
debug_writing_mode: self.debug_writing_mode,
1352
}
1353
}
1354
}
1355
1356
impl<T: Copy + Ord + Add<T, Output = T> + Sub<T, Output = T>> LogicalRect<T> {
1357
#[inline]
1358
pub fn union(&self, other: &LogicalRect<T>) -> LogicalRect<T> {
1359
self.debug_writing_mode
1360
.check_debug(other.debug_writing_mode);
1361
1362
let inline_start = min(self.start.i, other.start.i);
1363
let block_start = min(self.start.b, other.start.b);
1364
LogicalRect {
1365
start: LogicalPoint {
1366
i: inline_start,
1367
b: block_start,
1368
debug_writing_mode: self.debug_writing_mode,
1369
},
1370
size: LogicalSize {
1371
inline: max(self.inline_end(), other.inline_end()) - inline_start,
1372
block: max(self.block_end(), other.block_end()) - block_start,
1373
debug_writing_mode: self.debug_writing_mode,
1374
},
1375
debug_writing_mode: self.debug_writing_mode,
1376
}
1377
}
1378
}
1379
1380
impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Add<LogicalMargin<T>> for LogicalRect<T> {
1381
type Output = LogicalRect<T>;
1382
1383
#[inline]
1384
fn add(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1385
self.debug_writing_mode
1386
.check_debug(other.debug_writing_mode);
1387
LogicalRect {
1388
start: LogicalPoint {
1389
// Growing a rectangle on the start side means pushing its
1390
// start point on the negative direction.
1391
i: self.start.i - other.inline_start,
1392
b: self.start.b - other.block_start,
1393
debug_writing_mode: self.debug_writing_mode,
1394
},
1395
size: LogicalSize {
1396
inline: self.size.inline + other.inline_start_end(),
1397
block: self.size.block + other.block_start_end(),
1398
debug_writing_mode: self.debug_writing_mode,
1399
},
1400
debug_writing_mode: self.debug_writing_mode,
1401
}
1402
}
1403
}
1404
1405
impl<T: Copy + Add<T, Output = T> + Sub<T, Output = T>> Sub<LogicalMargin<T>> for LogicalRect<T> {
1406
type Output = LogicalRect<T>;
1407
1408
#[inline]
1409
fn sub(self, other: LogicalMargin<T>) -> LogicalRect<T> {
1410
self.debug_writing_mode
1411
.check_debug(other.debug_writing_mode);
1412
LogicalRect {
1413
start: LogicalPoint {
1414
// Shrinking a rectangle on the start side means pushing its
1415
// start point on the positive direction.
1416
i: self.start.i + other.inline_start,
1417
b: self.start.b + other.block_start,
1418
debug_writing_mode: self.debug_writing_mode,
1419
},
1420
size: LogicalSize {
1421
inline: self.size.inline - other.inline_start_end(),
1422
block: self.size.block - other.block_start_end(),
1423
debug_writing_mode: self.debug_writing_mode,
1424
},
1425
debug_writing_mode: self.debug_writing_mode,
1426
}
1427
}
1428
}
1429
1430
#[derive(Clone, Copy, Debug, PartialEq)]
1431
pub enum PhysicalSide {
1432
Top,
1433
Right,
1434
Bottom,
1435
Left,
1436
}
1437
1438
#[derive(Clone, Copy, Debug, PartialEq)]
1439
pub enum PhysicalCorner {
1440
TopLeft,
1441
TopRight,
1442
BottomRight,
1443
BottomLeft,
1444
}