align.rs (21879B)
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 //! Values for CSS Box Alignment properties 6 //! 7 //! https://drafts.csswg.org/css-align/ 8 9 use crate::derives::*; 10 use crate::parser::{Parse, ParserContext}; 11 use cssparser::Parser; 12 use std::fmt::{self, Write}; 13 use style_traits::{CssWriter, KeywordsCollectFn, ParseError, SpecifiedValueInfo, ToCss}; 14 15 /// Constants shared by multiple CSS Box Alignment properties 16 #[derive( 17 Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, 18 )] 19 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 20 #[repr(C)] 21 pub struct AlignFlags(u8); 22 bitflags! { 23 impl AlignFlags: u8 { 24 // Enumeration stored in the lower 5 bits: 25 /// {align,justify}-{content,items,self}: 'auto' 26 const AUTO = 0; 27 /// 'normal' 28 const NORMAL = 1; 29 /// 'start' 30 const START = 2; 31 /// 'end' 32 const END = 3; 33 /// 'flex-start' 34 const FLEX_START = 4; 35 /// 'flex-end' 36 const FLEX_END = 5; 37 /// 'center' 38 const CENTER = 6; 39 /// 'left' 40 const LEFT = 7; 41 /// 'right' 42 const RIGHT = 8; 43 /// 'baseline' 44 const BASELINE = 9; 45 /// 'last-baseline' 46 const LAST_BASELINE = 10; 47 /// 'stretch' 48 const STRETCH = 11; 49 /// 'self-start' 50 const SELF_START = 12; 51 /// 'self-end' 52 const SELF_END = 13; 53 /// 'space-between' 54 const SPACE_BETWEEN = 14; 55 /// 'space-around' 56 const SPACE_AROUND = 15; 57 /// 'space-evenly' 58 const SPACE_EVENLY = 16; 59 /// `anchor-center` 60 const ANCHOR_CENTER = 17; 61 62 // Additional flags stored in the upper bits: 63 /// 'legacy' (mutually exclusive w. SAFE & UNSAFE) 64 const LEGACY = 1 << 5; 65 /// 'safe' 66 const SAFE = 1 << 6; 67 /// 'unsafe' (mutually exclusive w. SAFE) 68 const UNSAFE = 1 << 7; 69 70 /// Mask for the additional flags above. 71 const FLAG_BITS = 0b11100000; 72 } 73 } 74 75 impl AlignFlags { 76 /// Returns the enumeration value stored in the lower 5 bits. 77 #[inline] 78 pub fn value(&self) -> Self { 79 *self & !AlignFlags::FLAG_BITS 80 } 81 82 /// Returns an updated value with the same flags. 83 #[inline] 84 pub fn with_value(&self, value: AlignFlags) -> Self { 85 debug_assert!(!value.intersects(Self::FLAG_BITS)); 86 value | self.flags() 87 } 88 89 /// Returns the flags stored in the upper 3 bits. 90 #[inline] 91 pub fn flags(&self) -> Self { 92 *self & AlignFlags::FLAG_BITS 93 } 94 } 95 96 impl ToCss for AlignFlags { 97 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 98 where 99 W: Write, 100 { 101 let flags = self.flags(); 102 let value = self.value(); 103 match flags { 104 AlignFlags::LEGACY => { 105 dest.write_str("legacy")?; 106 if value.is_empty() { 107 return Ok(()); 108 } 109 dest.write_char(' ')?; 110 }, 111 AlignFlags::SAFE => dest.write_str("safe ")?, 112 AlignFlags::UNSAFE => dest.write_str("unsafe ")?, 113 _ => { 114 debug_assert_eq!(flags, AlignFlags::empty()); 115 }, 116 } 117 118 dest.write_str(match value { 119 AlignFlags::AUTO => "auto", 120 AlignFlags::NORMAL => "normal", 121 AlignFlags::START => "start", 122 AlignFlags::END => "end", 123 AlignFlags::FLEX_START => "flex-start", 124 AlignFlags::FLEX_END => "flex-end", 125 AlignFlags::CENTER => "center", 126 AlignFlags::LEFT => "left", 127 AlignFlags::RIGHT => "right", 128 AlignFlags::BASELINE => "baseline", 129 AlignFlags::LAST_BASELINE => "last baseline", 130 AlignFlags::STRETCH => "stretch", 131 AlignFlags::SELF_START => "self-start", 132 AlignFlags::SELF_END => "self-end", 133 AlignFlags::SPACE_BETWEEN => "space-between", 134 AlignFlags::SPACE_AROUND => "space-around", 135 AlignFlags::SPACE_EVENLY => "space-evenly", 136 AlignFlags::ANCHOR_CENTER => "anchor-center", 137 _ => unreachable!(), 138 }) 139 } 140 } 141 142 /// An axis direction, either inline (for the `justify` properties) or block, 143 /// (for the `align` properties). 144 #[derive(Clone, Copy, PartialEq)] 145 pub enum AxisDirection { 146 /// Block direction. 147 Block, 148 /// Inline direction. 149 Inline, 150 } 151 152 /// Shared value for the `align-content` and `justify-content` properties. 153 /// 154 /// <https://drafts.csswg.org/css-align/#content-distribution> 155 /// <https://drafts.csswg.org/css-align/#propdef-align-content> 156 #[derive( 157 Clone, 158 Copy, 159 Debug, 160 Eq, 161 MallocSizeOf, 162 PartialEq, 163 ToComputedValue, 164 ToCss, 165 ToResolvedValue, 166 ToShmem, 167 ToTyped, 168 )] 169 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 170 #[repr(C)] 171 pub struct ContentDistribution { 172 primary: AlignFlags, 173 // FIXME(https://github.com/w3c/csswg-drafts/issues/1002): This will need to 174 // accept fallback alignment, eventually. 175 } 176 177 impl ContentDistribution { 178 /// The initial value 'normal' 179 #[inline] 180 pub fn normal() -> Self { 181 Self::new(AlignFlags::NORMAL) 182 } 183 184 /// `start` 185 #[inline] 186 pub fn start() -> Self { 187 Self::new(AlignFlags::START) 188 } 189 190 /// The initial value 'normal' 191 #[inline] 192 pub fn new(primary: AlignFlags) -> Self { 193 Self { primary } 194 } 195 196 /// Returns whether this value is a <baseline-position>. 197 pub fn is_baseline_position(&self) -> bool { 198 matches!( 199 self.primary.value(), 200 AlignFlags::BASELINE | AlignFlags::LAST_BASELINE 201 ) 202 } 203 204 /// The primary alignment 205 #[inline] 206 pub fn primary(self) -> AlignFlags { 207 self.primary 208 } 209 210 /// Parse a value for align-content 211 pub fn parse_block<'i>( 212 _: &ParserContext, 213 input: &mut Parser<'i, '_>, 214 ) -> Result<Self, ParseError<'i>> { 215 Self::parse(input, AxisDirection::Block) 216 } 217 218 /// Parse a value for justify-content 219 pub fn parse_inline<'i>( 220 _: &ParserContext, 221 input: &mut Parser<'i, '_>, 222 ) -> Result<Self, ParseError<'i>> { 223 Self::parse(input, AxisDirection::Inline) 224 } 225 226 fn parse<'i, 't>( 227 input: &mut Parser<'i, 't>, 228 axis: AxisDirection, 229 ) -> Result<Self, ParseError<'i>> { 230 // NOTE Please also update the `list_keywords` function below 231 // when this function is updated. 232 233 // Try to parse normal first 234 if input 235 .try_parse(|i| i.expect_ident_matching("normal")) 236 .is_ok() 237 { 238 return Ok(ContentDistribution::normal()); 239 } 240 241 // Parse <baseline-position>, but only on the block axis. 242 if axis == AxisDirection::Block { 243 if let Ok(value) = input.try_parse(parse_baseline) { 244 return Ok(ContentDistribution::new(value)); 245 } 246 } 247 248 // <content-distribution> 249 if let Ok(value) = input.try_parse(parse_content_distribution) { 250 return Ok(ContentDistribution::new(value)); 251 } 252 253 // <overflow-position>? <content-position> 254 let overflow_position = input 255 .try_parse(parse_overflow_position) 256 .unwrap_or(AlignFlags::empty()); 257 258 let content_position = try_match_ident_ignore_ascii_case! { input, 259 "start" => AlignFlags::START, 260 "end" => AlignFlags::END, 261 "flex-start" => AlignFlags::FLEX_START, 262 "flex-end" => AlignFlags::FLEX_END, 263 "center" => AlignFlags::CENTER, 264 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT, 265 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT, 266 }; 267 268 Ok(ContentDistribution::new( 269 content_position | overflow_position, 270 )) 271 } 272 } 273 274 impl SpecifiedValueInfo for ContentDistribution { 275 fn collect_completion_keywords(f: KeywordsCollectFn) { 276 f(&["normal"]); 277 list_baseline_keywords(f); // block-axis only 278 list_content_distribution_keywords(f); 279 list_overflow_position_keywords(f); 280 f(&["start", "end", "flex-start", "flex-end", "center"]); 281 f(&["left", "right"]); // inline-axis only 282 } 283 } 284 285 /// The specified value of the {align,justify}-self properties. 286 /// 287 /// <https://drafts.csswg.org/css-align/#self-alignment> 288 /// <https://drafts.csswg.org/css-align/#propdef-align-self> 289 #[derive( 290 Clone, 291 Copy, 292 Debug, 293 Deref, 294 Eq, 295 MallocSizeOf, 296 PartialEq, 297 ToComputedValue, 298 ToCss, 299 ToResolvedValue, 300 ToShmem, 301 ToTyped, 302 )] 303 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 304 #[repr(C)] 305 pub struct SelfAlignment(pub AlignFlags); 306 307 impl SelfAlignment { 308 /// The initial value 'auto' 309 #[inline] 310 pub fn auto() -> Self { 311 SelfAlignment(AlignFlags::AUTO) 312 } 313 314 /// Returns whether this value is valid for both axis directions. 315 pub fn is_valid_on_both_axes(&self) -> bool { 316 match self.0.value() { 317 // left | right are only allowed on the inline axis. 318 AlignFlags::LEFT | AlignFlags::RIGHT => false, 319 320 _ => true, 321 } 322 } 323 324 /// Parse self-alignment on the block axis (for align-self) 325 pub fn parse_block<'i, 't>( 326 _: &ParserContext, 327 input: &mut Parser<'i, 't>, 328 ) -> Result<Self, ParseError<'i>> { 329 Self::parse(input, AxisDirection::Block) 330 } 331 332 /// Parse self-alignment on the block axis (for align-self) 333 pub fn parse_inline<'i, 't>( 334 _: &ParserContext, 335 input: &mut Parser<'i, 't>, 336 ) -> Result<Self, ParseError<'i>> { 337 Self::parse(input, AxisDirection::Inline) 338 } 339 340 /// Parse a self-alignment value on one of the axes. 341 fn parse<'i, 't>( 342 input: &mut Parser<'i, 't>, 343 axis: AxisDirection, 344 ) -> Result<Self, ParseError<'i>> { 345 // NOTE Please also update the `list_keywords` function below 346 // when this function is updated. 347 348 // <baseline-position> 349 // 350 // It's weird that this accepts <baseline-position>, but not 351 // justify-content... 352 if let Ok(value) = input.try_parse(parse_baseline) { 353 return Ok(SelfAlignment(value)); 354 } 355 356 // auto | normal | stretch 357 if let Ok(value) = input.try_parse(parse_auto_normal_stretch) { 358 return Ok(SelfAlignment(value)); 359 } 360 361 // <overflow-position>? <self-position> 362 let overflow_position = input 363 .try_parse(parse_overflow_position) 364 .unwrap_or(AlignFlags::empty()); 365 let self_position = parse_self_position(input, axis)?; 366 Ok(SelfAlignment(overflow_position | self_position)) 367 } 368 369 fn list_keywords(f: KeywordsCollectFn, axis: AxisDirection) { 370 list_baseline_keywords(f); 371 list_auto_normal_stretch(f); 372 list_overflow_position_keywords(f); 373 list_self_position_keywords(f, axis); 374 } 375 376 /// Performs a flip of the position, that is, for self-start we return self-end, for left 377 /// we return right, etc. 378 pub fn flip_position(self) -> Self { 379 let flipped_value = match self.0.value() { 380 AlignFlags::START => AlignFlags::END, 381 AlignFlags::END => AlignFlags::START, 382 AlignFlags::FLEX_START => AlignFlags::FLEX_END, 383 AlignFlags::FLEX_END => AlignFlags::FLEX_START, 384 AlignFlags::LEFT => AlignFlags::RIGHT, 385 AlignFlags::RIGHT => AlignFlags::LEFT, 386 AlignFlags::SELF_START => AlignFlags::SELF_END, 387 AlignFlags::SELF_END => AlignFlags::SELF_START, 388 389 AlignFlags::AUTO 390 | AlignFlags::NORMAL 391 | AlignFlags::BASELINE 392 | AlignFlags::LAST_BASELINE 393 | AlignFlags::STRETCH 394 | AlignFlags::CENTER 395 | AlignFlags::SPACE_BETWEEN 396 | AlignFlags::SPACE_AROUND 397 | AlignFlags::SPACE_EVENLY 398 | AlignFlags::ANCHOR_CENTER => return self, 399 _ => { 400 debug_assert!(false, "Unexpected alignment enumeration value"); 401 return self; 402 }, 403 }; 404 self.with_value(flipped_value) 405 } 406 407 /// Returns a fixed-up alignment value. 408 #[inline] 409 pub fn with_value(self, value: AlignFlags) -> Self { 410 Self(self.0.with_value(value)) 411 } 412 } 413 414 impl SpecifiedValueInfo for SelfAlignment { 415 fn collect_completion_keywords(f: KeywordsCollectFn) { 416 // TODO: This technically lists left/right for align-self. Not amazing but also not sure 417 // worth fixing here, could be special-cased on the caller. 418 Self::list_keywords(f, AxisDirection::Block); 419 } 420 } 421 422 /// Value of the `align-items` and `justify-items` properties 423 /// 424 /// <https://drafts.csswg.org/css-align/#propdef-align-items> 425 /// <https://drafts.csswg.org/css-align/#propdef-justify-items> 426 #[derive( 427 Clone, 428 Copy, 429 Debug, 430 Deref, 431 Eq, 432 MallocSizeOf, 433 PartialEq, 434 ToComputedValue, 435 ToCss, 436 ToResolvedValue, 437 ToShmem, 438 ToTyped, 439 )] 440 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 441 #[repr(C)] 442 pub struct ItemPlacement(pub AlignFlags); 443 444 impl ItemPlacement { 445 /// The value 'normal' 446 #[inline] 447 pub fn normal() -> Self { 448 Self(AlignFlags::NORMAL) 449 } 450 } 451 452 impl ItemPlacement { 453 /// Parse a value for align-items 454 pub fn parse_block<'i>( 455 _: &ParserContext, 456 input: &mut Parser<'i, '_>, 457 ) -> Result<Self, ParseError<'i>> { 458 Self::parse(input, AxisDirection::Block) 459 } 460 461 /// Parse a value for justify-items 462 pub fn parse_inline<'i>( 463 _: &ParserContext, 464 input: &mut Parser<'i, '_>, 465 ) -> Result<Self, ParseError<'i>> { 466 Self::parse(input, AxisDirection::Inline) 467 } 468 469 fn parse<'i, 't>( 470 input: &mut Parser<'i, 't>, 471 axis: AxisDirection, 472 ) -> Result<Self, ParseError<'i>> { 473 // NOTE Please also update `impl SpecifiedValueInfo` below when 474 // this function is updated. 475 476 // <baseline-position> 477 if let Ok(baseline) = input.try_parse(parse_baseline) { 478 return Ok(Self(baseline)); 479 } 480 481 // normal | stretch 482 if let Ok(value) = input.try_parse(parse_normal_stretch) { 483 return Ok(Self(value)); 484 } 485 486 if axis == AxisDirection::Inline { 487 // legacy | [ legacy && [ left | right | center ] ] 488 if let Ok(value) = input.try_parse(parse_legacy) { 489 return Ok(Self(value)); 490 } 491 } 492 493 // <overflow-position>? <self-position> 494 let overflow = input 495 .try_parse(parse_overflow_position) 496 .unwrap_or(AlignFlags::empty()); 497 let self_position = parse_self_position(input, axis)?; 498 Ok(ItemPlacement(self_position | overflow)) 499 } 500 } 501 502 impl SpecifiedValueInfo for ItemPlacement { 503 fn collect_completion_keywords(f: KeywordsCollectFn) { 504 list_baseline_keywords(f); 505 list_normal_stretch(f); 506 list_overflow_position_keywords(f); 507 list_self_position_keywords(f, AxisDirection::Block); 508 } 509 } 510 511 /// Value of the `justify-items` property 512 /// 513 /// <https://drafts.csswg.org/css-align/#justify-items-property> 514 #[derive( 515 Clone, Copy, Debug, Deref, Eq, MallocSizeOf, PartialEq, ToCss, ToResolvedValue, ToShmem, ToTyped, 516 )] 517 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 518 #[repr(C)] 519 pub struct JustifyItems(pub ItemPlacement); 520 521 impl JustifyItems { 522 /// The initial value 'legacy' 523 #[inline] 524 pub fn legacy() -> Self { 525 Self(ItemPlacement(AlignFlags::LEGACY)) 526 } 527 528 /// The value 'normal' 529 #[inline] 530 pub fn normal() -> Self { 531 Self(ItemPlacement::normal()) 532 } 533 } 534 535 impl Parse for JustifyItems { 536 fn parse<'i, 't>( 537 context: &ParserContext, 538 input: &mut Parser<'i, 't>, 539 ) -> Result<Self, ParseError<'i>> { 540 ItemPlacement::parse_inline(context, input).map(Self) 541 } 542 } 543 544 impl SpecifiedValueInfo for JustifyItems { 545 fn collect_completion_keywords(f: KeywordsCollectFn) { 546 ItemPlacement::collect_completion_keywords(f); 547 list_legacy_keywords(f); // Inline axis only 548 } 549 } 550 551 // auto | normal | stretch 552 fn parse_auto_normal_stretch<'i, 't>( 553 input: &mut Parser<'i, 't>, 554 ) -> Result<AlignFlags, ParseError<'i>> { 555 // NOTE Please also update the `list_auto_normal_stretch` function 556 // below when this function is updated. 557 try_match_ident_ignore_ascii_case! { input, 558 "auto" => Ok(AlignFlags::AUTO), 559 "normal" => Ok(AlignFlags::NORMAL), 560 "stretch" => Ok(AlignFlags::STRETCH), 561 } 562 } 563 564 fn list_auto_normal_stretch(f: KeywordsCollectFn) { 565 f(&["auto", "normal", "stretch"]); 566 } 567 568 // normal | stretch 569 fn parse_normal_stretch<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> { 570 // NOTE Please also update the `list_normal_stretch` function below 571 // when this function is updated. 572 try_match_ident_ignore_ascii_case! { input, 573 "normal" => Ok(AlignFlags::NORMAL), 574 "stretch" => Ok(AlignFlags::STRETCH), 575 } 576 } 577 578 fn list_normal_stretch(f: KeywordsCollectFn) { 579 f(&["normal", "stretch"]); 580 } 581 582 // <baseline-position> 583 fn parse_baseline<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> { 584 // NOTE Please also update the `list_baseline_keywords` function 585 // below when this function is updated. 586 try_match_ident_ignore_ascii_case! { input, 587 "baseline" => Ok(AlignFlags::BASELINE), 588 "first" => { 589 input.expect_ident_matching("baseline")?; 590 Ok(AlignFlags::BASELINE) 591 }, 592 "last" => { 593 input.expect_ident_matching("baseline")?; 594 Ok(AlignFlags::LAST_BASELINE) 595 }, 596 } 597 } 598 599 fn list_baseline_keywords(f: KeywordsCollectFn) { 600 f(&["baseline", "first baseline", "last baseline"]); 601 } 602 603 // <content-distribution> 604 fn parse_content_distribution<'i, 't>( 605 input: &mut Parser<'i, 't>, 606 ) -> Result<AlignFlags, ParseError<'i>> { 607 // NOTE Please also update the `list_content_distribution_keywords` 608 // function below when this function is updated. 609 try_match_ident_ignore_ascii_case! { input, 610 "stretch" => Ok(AlignFlags::STRETCH), 611 "space-between" => Ok(AlignFlags::SPACE_BETWEEN), 612 "space-around" => Ok(AlignFlags::SPACE_AROUND), 613 "space-evenly" => Ok(AlignFlags::SPACE_EVENLY), 614 } 615 } 616 617 fn list_content_distribution_keywords(f: KeywordsCollectFn) { 618 f(&["stretch", "space-between", "space-around", "space-evenly"]); 619 } 620 621 // <overflow-position> 622 fn parse_overflow_position<'i, 't>( 623 input: &mut Parser<'i, 't>, 624 ) -> Result<AlignFlags, ParseError<'i>> { 625 // NOTE Please also update the `list_overflow_position_keywords` 626 // function below when this function is updated. 627 try_match_ident_ignore_ascii_case! { input, 628 "safe" => Ok(AlignFlags::SAFE), 629 "unsafe" => Ok(AlignFlags::UNSAFE), 630 } 631 } 632 633 fn list_overflow_position_keywords(f: KeywordsCollectFn) { 634 f(&["safe", "unsafe"]); 635 } 636 637 // <self-position> | left | right in the inline axis. 638 fn parse_self_position<'i, 't>( 639 input: &mut Parser<'i, 't>, 640 axis: AxisDirection, 641 ) -> Result<AlignFlags, ParseError<'i>> { 642 // NOTE Please also update the `list_self_position_keywords` 643 // function below when this function is updated. 644 Ok(try_match_ident_ignore_ascii_case! { input, 645 "start" => AlignFlags::START, 646 "end" => AlignFlags::END, 647 "flex-start" => AlignFlags::FLEX_START, 648 "flex-end" => AlignFlags::FLEX_END, 649 "center" => AlignFlags::CENTER, 650 "self-start" => AlignFlags::SELF_START, 651 "self-end" => AlignFlags::SELF_END, 652 "left" if axis == AxisDirection::Inline => AlignFlags::LEFT, 653 "right" if axis == AxisDirection::Inline => AlignFlags::RIGHT, 654 "anchor-center" if static_prefs::pref!("layout.css.anchor-positioning.enabled") => AlignFlags::ANCHOR_CENTER, 655 }) 656 } 657 658 fn list_self_position_keywords(f: KeywordsCollectFn, axis: AxisDirection) { 659 f(&[ 660 "start", 661 "end", 662 "flex-start", 663 "flex-end", 664 "center", 665 "self-start", 666 "self-end", 667 ]); 668 669 if static_prefs::pref!("layout.css.anchor-positioning.enabled") { 670 f(&["anchor-center"]); 671 } 672 673 if axis == AxisDirection::Inline { 674 f(&["left", "right"]); 675 } 676 } 677 678 fn parse_left_right_center<'i, 't>( 679 input: &mut Parser<'i, 't>, 680 ) -> Result<AlignFlags, ParseError<'i>> { 681 // NOTE Please also update the `list_legacy_keywords` function below 682 // when this function is updated. 683 Ok(try_match_ident_ignore_ascii_case! { input, 684 "left" => AlignFlags::LEFT, 685 "right" => AlignFlags::RIGHT, 686 "center" => AlignFlags::CENTER, 687 }) 688 } 689 690 // legacy | [ legacy && [ left | right | center ] ] 691 fn parse_legacy<'i, 't>(input: &mut Parser<'i, 't>) -> Result<AlignFlags, ParseError<'i>> { 692 // NOTE Please also update the `list_legacy_keywords` function below 693 // when this function is updated. 694 let flags = try_match_ident_ignore_ascii_case! { input, 695 "legacy" => { 696 let flags = input.try_parse(parse_left_right_center) 697 .unwrap_or(AlignFlags::empty()); 698 699 return Ok(AlignFlags::LEGACY | flags) 700 }, 701 "left" => AlignFlags::LEFT, 702 "right" => AlignFlags::RIGHT, 703 "center" => AlignFlags::CENTER, 704 }; 705 706 input.expect_ident_matching("legacy")?; 707 Ok(AlignFlags::LEGACY | flags) 708 } 709 710 fn list_legacy_keywords(f: KeywordsCollectFn) { 711 f(&["legacy", "left", "right", "center"]); 712 }