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 //! CSS handling for the specified value of
6 //! [`position`][position]s
7 //!
8 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
9
10 use crate::parser::{Parse, ParserContext};
11 use crate::selector_map::PrecomputedHashMap;
12 use crate::str::HTML_SPACE_CHARACTERS;
13 use crate::values::computed::LengthPercentage as ComputedLengthPercentage;
14 use crate::values::computed::{Context, Percentage, ToComputedValue};
15 use crate::values::generics::position::AspectRatio as GenericAspectRatio;
16 use crate::values::generics::position::Position as GenericPosition;
17 use crate::values::generics::position::PositionComponent as GenericPositionComponent;
18 use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto;
19 use crate::values::generics::position::ZIndex as GenericZIndex;
20 use crate::values::specified::{AllowQuirks, Integer, LengthPercentage, NonNegativeNumber};
21 use crate::{Atom, Zero};
22 use cssparser::Parser;
23 use selectors::parser::SelectorParseErrorKind;
24 use servo_arc::Arc;
25 use std::fmt::{self, Write};
26 use style_traits::values::specified::AllowedNumericType;
27 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
28
29 /// The specified value of a CSS `<position>`
30 pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
31
32 /// The specified value of an `auto | <position>`.
33 pub type PositionOrAuto = GenericPositionOrAuto<Position>;
34
35 /// The specified value of a horizontal position.
36 pub type HorizontalPosition = PositionComponent<HorizontalPositionKeyword>;
37
38 /// The specified value of a vertical position.
39 pub type VerticalPosition = PositionComponent<VerticalPositionKeyword>;
40
41 /// The specified value of a component of a CSS `<position>`.
42 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
43 pub enum PositionComponent<S> {
44 /// `center`
45 Center,
46 /// `<length-percentage>`
47 Length(LengthPercentage),
48 /// `<side> <length-percentage>?`
49 Side(S, Option<LengthPercentage>),
50 }
51
52 /// A keyword for the X direction.
53 #[derive(
54 Clone,
55 Copy,
56 Debug,
57 Eq,
58 Hash,
59 MallocSizeOf,
60 Parse,
61 PartialEq,
62 SpecifiedValueInfo,
63 ToComputedValue,
64 ToCss,
65 ToResolvedValue,
66 ToShmem,
67 )]
68 #[allow(missing_docs)]
69 #[repr(u8)]
70 pub enum HorizontalPositionKeyword {
71 Left,
72 Right,
73 }
74
75 /// A keyword for the Y direction.
76 #[derive(
77 Clone,
78 Copy,
79 Debug,
80 Eq,
81 Hash,
82 MallocSizeOf,
83 Parse,
84 PartialEq,
85 SpecifiedValueInfo,
86 ToComputedValue,
87 ToCss,
88 ToResolvedValue,
89 ToShmem,
90 )]
91 #[allow(missing_docs)]
92 #[repr(u8)]
93 pub enum VerticalPositionKeyword {
94 Top,
95 Bottom,
96 }
97
98 impl Parse for Position {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>99 fn parse<'i, 't>(
100 context: &ParserContext,
101 input: &mut Parser<'i, 't>,
102 ) -> Result<Self, ParseError<'i>> {
103 let position = Self::parse_three_value_quirky(context, input, AllowQuirks::No)?;
104 if position.is_three_value_syntax() {
105 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
106 }
107 Ok(position)
108 }
109 }
110
111 impl Position {
112 /// Parses a `<bg-position>`, with quirks.
parse_three_value_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>113 pub fn parse_three_value_quirky<'i, 't>(
114 context: &ParserContext,
115 input: &mut Parser<'i, 't>,
116 allow_quirks: AllowQuirks,
117 ) -> Result<Self, ParseError<'i>> {
118 match input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks)) {
119 Ok(x_pos @ PositionComponent::Center) => {
120 if let Ok(y_pos) =
121 input.try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
122 {
123 return Ok(Self::new(x_pos, y_pos));
124 }
125 let x_pos = input
126 .try_parse(|i| PositionComponent::parse_quirky(context, i, allow_quirks))
127 .unwrap_or(x_pos);
128 let y_pos = PositionComponent::Center;
129 return Ok(Self::new(x_pos, y_pos));
130 },
131 Ok(PositionComponent::Side(x_keyword, lp)) => {
132 if input
133 .try_parse(|i| i.expect_ident_matching("center"))
134 .is_ok()
135 {
136 let x_pos = PositionComponent::Side(x_keyword, lp);
137 let y_pos = PositionComponent::Center;
138 return Ok(Self::new(x_pos, y_pos));
139 }
140 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
141 let y_lp = input
142 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
143 .ok();
144 let x_pos = PositionComponent::Side(x_keyword, lp);
145 let y_pos = PositionComponent::Side(y_keyword, y_lp);
146 return Ok(Self::new(x_pos, y_pos));
147 }
148 let x_pos = PositionComponent::Side(x_keyword, None);
149 let y_pos = lp.map_or(PositionComponent::Center, PositionComponent::Length);
150 return Ok(Self::new(x_pos, y_pos));
151 },
152 Ok(x_pos @ PositionComponent::Length(_)) => {
153 if let Ok(y_keyword) = input.try_parse(VerticalPositionKeyword::parse) {
154 let y_pos = PositionComponent::Side(y_keyword, None);
155 return Ok(Self::new(x_pos, y_pos));
156 }
157 if let Ok(y_lp) =
158 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
159 {
160 let y_pos = PositionComponent::Length(y_lp);
161 return Ok(Self::new(x_pos, y_pos));
162 }
163 let y_pos = PositionComponent::Center;
164 let _ = input.try_parse(|i| i.expect_ident_matching("center"));
165 return Ok(Self::new(x_pos, y_pos));
166 },
167 Err(_) => {},
168 }
169 let y_keyword = VerticalPositionKeyword::parse(input)?;
170 let lp_and_x_pos: Result<_, ParseError> = input.try_parse(|i| {
171 let y_lp = i
172 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
173 .ok();
174 if let Ok(x_keyword) = i.try_parse(HorizontalPositionKeyword::parse) {
175 let x_lp = i
176 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
177 .ok();
178 let x_pos = PositionComponent::Side(x_keyword, x_lp);
179 return Ok((y_lp, x_pos));
180 };
181 i.expect_ident_matching("center")?;
182 let x_pos = PositionComponent::Center;
183 Ok((y_lp, x_pos))
184 });
185 if let Ok((y_lp, x_pos)) = lp_and_x_pos {
186 let y_pos = PositionComponent::Side(y_keyword, y_lp);
187 return Ok(Self::new(x_pos, y_pos));
188 }
189 let x_pos = PositionComponent::Center;
190 let y_pos = PositionComponent::Side(y_keyword, None);
191 Ok(Self::new(x_pos, y_pos))
192 }
193
194 /// `center center`
195 #[inline]
center() -> Self196 pub fn center() -> Self {
197 Self::new(PositionComponent::Center, PositionComponent::Center)
198 }
199
200 /// Returns true if this uses a 3 value syntax.
201 #[inline]
is_three_value_syntax(&self) -> bool202 fn is_three_value_syntax(&self) -> bool {
203 self.horizontal.component_count() != self.vertical.component_count()
204 }
205 }
206
207 impl ToCss for Position {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,208 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
209 where
210 W: Write,
211 {
212 match (&self.horizontal, &self.vertical) {
213 (
214 x_pos @ &PositionComponent::Side(_, Some(_)),
215 &PositionComponent::Length(ref y_lp),
216 ) => {
217 x_pos.to_css(dest)?;
218 dest.write_str(" top ")?;
219 y_lp.to_css(dest)
220 },
221 (
222 &PositionComponent::Length(ref x_lp),
223 y_pos @ &PositionComponent::Side(_, Some(_)),
224 ) => {
225 dest.write_str("left ")?;
226 x_lp.to_css(dest)?;
227 dest.write_str(" ")?;
228 y_pos.to_css(dest)
229 },
230 (x_pos, y_pos) => {
231 x_pos.to_css(dest)?;
232 dest.write_str(" ")?;
233 y_pos.to_css(dest)
234 },
235 }
236 }
237 }
238
239 impl<S: Parse> Parse for PositionComponent<S> {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>240 fn parse<'i, 't>(
241 context: &ParserContext,
242 input: &mut Parser<'i, 't>,
243 ) -> Result<Self, ParseError<'i>> {
244 Self::parse_quirky(context, input, AllowQuirks::No)
245 }
246 }
247
248 impl<S: Parse> PositionComponent<S> {
249 /// Parses a component of a CSS position, with quirks.
parse_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>250 pub fn parse_quirky<'i, 't>(
251 context: &ParserContext,
252 input: &mut Parser<'i, 't>,
253 allow_quirks: AllowQuirks,
254 ) -> Result<Self, ParseError<'i>> {
255 if input
256 .try_parse(|i| i.expect_ident_matching("center"))
257 .is_ok()
258 {
259 return Ok(PositionComponent::Center);
260 }
261 if let Ok(lp) =
262 input.try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
263 {
264 return Ok(PositionComponent::Length(lp));
265 }
266 let keyword = S::parse(context, input)?;
267 let lp = input
268 .try_parse(|i| LengthPercentage::parse_quirky(context, i, allow_quirks))
269 .ok();
270 Ok(PositionComponent::Side(keyword, lp))
271 }
272 }
273
274 impl<S> GenericPositionComponent for PositionComponent<S> {
is_center(&self) -> bool275 fn is_center(&self) -> bool {
276 match *self {
277 PositionComponent::Center => true,
278 PositionComponent::Length(LengthPercentage::Percentage(ref per)) => per.0 == 0.5,
279 // 50% from any side is still the center.
280 PositionComponent::Side(_, Some(LengthPercentage::Percentage(ref per))) => per.0 == 0.5,
281 _ => false,
282 }
283 }
284 }
285
286 impl<S> PositionComponent<S> {
287 /// `0%`
zero() -> Self288 pub fn zero() -> Self {
289 PositionComponent::Length(LengthPercentage::Percentage(Percentage::zero()))
290 }
291
292 /// Returns the count of this component.
component_count(&self) -> usize293 fn component_count(&self) -> usize {
294 match *self {
295 PositionComponent::Length(..) | PositionComponent::Center => 1,
296 PositionComponent::Side(_, ref lp) => {
297 if lp.is_some() {
298 2
299 } else {
300 1
301 }
302 },
303 }
304 }
305 }
306
307 impl<S: Side> ToComputedValue for PositionComponent<S> {
308 type ComputedValue = ComputedLengthPercentage;
309
to_computed_value(&self, context: &Context) -> Self::ComputedValue310 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
311 match *self {
312 PositionComponent::Center => ComputedLengthPercentage::new_percent(Percentage(0.5)),
313 PositionComponent::Side(ref keyword, None) => {
314 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
315 ComputedLengthPercentage::new_percent(p)
316 },
317 PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
318 let length = length.to_computed_value(context);
319 // We represent `<end-side> <length>` as `calc(100% - <length>)`.
320 ComputedLengthPercentage::hundred_percent_minus(length, AllowedNumericType::All)
321 },
322 PositionComponent::Side(_, Some(ref length)) |
323 PositionComponent::Length(ref length) => length.to_computed_value(context),
324 }
325 }
326
from_computed_value(computed: &Self::ComputedValue) -> Self327 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
328 PositionComponent::Length(ToComputedValue::from_computed_value(computed))
329 }
330 }
331
332 impl<S: Side> PositionComponent<S> {
333 /// The initial specified value of a position component, i.e. the start side.
initial_specified_value() -> Self334 pub fn initial_specified_value() -> Self {
335 PositionComponent::Side(S::start(), None)
336 }
337 }
338
339 /// Represents a side, either horizontal or vertical, of a CSS position.
340 pub trait Side {
341 /// Returns the start side.
start() -> Self342 fn start() -> Self;
343
344 /// Returns whether this side is the start side.
is_start(&self) -> bool345 fn is_start(&self) -> bool;
346 }
347
348 impl Side for HorizontalPositionKeyword {
349 #[inline]
start() -> Self350 fn start() -> Self {
351 HorizontalPositionKeyword::Left
352 }
353
354 #[inline]
is_start(&self) -> bool355 fn is_start(&self) -> bool {
356 *self == Self::start()
357 }
358 }
359
360 impl Side for VerticalPositionKeyword {
361 #[inline]
start() -> Self362 fn start() -> Self {
363 VerticalPositionKeyword::Top
364 }
365
366 #[inline]
is_start(&self) -> bool367 fn is_start(&self) -> bool {
368 *self == Self::start()
369 }
370 }
371
372 bitflags! {
373 /// Controls how the auto-placement algorithm works
374 /// specifying exactly how auto-placed items get flowed into the grid
375 #[derive(
376 MallocSizeOf,
377 SpecifiedValueInfo,
378 ToComputedValue,
379 ToResolvedValue,
380 ToShmem
381 )]
382 #[value_info(other_values = "row,column,dense")]
383 #[repr(C)]
384 pub struct GridAutoFlow: u8 {
385 /// 'row' - mutually exclusive with 'column'
386 const ROW = 1 << 0;
387 /// 'column' - mutually exclusive with 'row'
388 const COLUMN = 1 << 1;
389 /// 'dense'
390 const DENSE = 1 << 2;
391 }
392 }
393
394 #[derive(
395 Clone,
396 Copy,
397 Debug,
398 Eq,
399 MallocSizeOf,
400 PartialEq,
401 SpecifiedValueInfo,
402 ToComputedValue,
403 ToCss,
404 ToResolvedValue,
405 ToShmem,
406 )]
407 /// Masonry auto-placement algorithm packing.
408 pub enum MasonryPlacement {
409 /// Place the item in the track(s) with the smallest extent so far.
410 Pack,
411 /// Place the item after the last item, from start to end.
412 Next,
413 }
414
415 #[derive(
416 Clone,
417 Copy,
418 Debug,
419 Eq,
420 MallocSizeOf,
421 PartialEq,
422 SpecifiedValueInfo,
423 ToComputedValue,
424 ToCss,
425 ToResolvedValue,
426 ToShmem,
427 )]
428 /// Masonry auto-placement algorithm item sorting option.
429 pub enum MasonryItemOrder {
430 /// Place all items with a definite placement before auto-placed items.
431 DefiniteFirst,
432 /// Place items in `order-modified document order`.
433 Ordered,
434 }
435
436 #[derive(
437 Clone,
438 Copy,
439 Debug,
440 Eq,
441 MallocSizeOf,
442 PartialEq,
443 SpecifiedValueInfo,
444 ToComputedValue,
445 ToCss,
446 ToResolvedValue,
447 ToShmem,
448 )]
449 /// Controls how the Masonry layout algorithm works
450 /// specifying exactly how auto-placed items get flowed in the masonry axis.
451 pub struct MasonryAutoFlow {
452 /// Specify how to pick a auto-placement track.
453 #[css(contextual_skip_if = "is_pack_with_non_default_order")]
454 pub placement: MasonryPlacement,
455 /// Specify how to pick an item to place.
456 #[css(skip_if = "is_item_order_definite_first")]
457 pub order: MasonryItemOrder,
458 }
459
460 #[inline]
is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool461 fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool {
462 *placement == MasonryPlacement::Pack && *order != MasonryItemOrder::DefiniteFirst
463 }
464
465 #[inline]
is_item_order_definite_first(order: &MasonryItemOrder) -> bool466 fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool {
467 *order == MasonryItemOrder::DefiniteFirst
468 }
469
470 impl MasonryAutoFlow {
471 #[inline]
472 /// Get initial `masonry-auto-flow` value.
initial() -> MasonryAutoFlow473 pub fn initial() -> MasonryAutoFlow {
474 MasonryAutoFlow {
475 placement: MasonryPlacement::Pack,
476 order: MasonryItemOrder::DefiniteFirst,
477 }
478 }
479 }
480
481 impl Parse for MasonryAutoFlow {
482 /// [ definite-first | ordered ] || [ pack | next ]
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<MasonryAutoFlow, ParseError<'i>>483 fn parse<'i, 't>(
484 _context: &ParserContext,
485 input: &mut Parser<'i, 't>,
486 ) -> Result<MasonryAutoFlow, ParseError<'i>> {
487 let mut value = MasonryAutoFlow::initial();
488 let mut got_placement = false;
489 let mut got_order = false;
490 while !input.is_exhausted() {
491 let location = input.current_source_location();
492 let ident = input.expect_ident()?;
493 let success = match_ignore_ascii_case! { &ident,
494 "pack" if !got_placement => {
495 got_placement = true;
496 true
497 },
498 "next" if !got_placement => {
499 value.placement = MasonryPlacement::Next;
500 got_placement = true;
501 true
502 },
503 "definite-first" if !got_order => {
504 got_order = true;
505 true
506 },
507 "ordered" if !got_order => {
508 value.order = MasonryItemOrder::Ordered;
509 got_order = true;
510 true
511 },
512 _ => false
513 };
514 if !success {
515 return Err(location
516 .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
517 }
518 }
519
520 if got_placement || got_order {
521 Ok(value)
522 } else {
523 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
524 }
525 }
526 }
527
528 #[cfg(feature = "gecko")]
529 impl From<u8> for MasonryAutoFlow {
from(bits: u8) -> MasonryAutoFlow530 fn from(bits: u8) -> MasonryAutoFlow {
531 use crate::gecko_bindings::structs;
532 let mut value = MasonryAutoFlow::initial();
533 if bits & structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8 == 0 {
534 value.placement = MasonryPlacement::Next;
535 }
536 if bits & structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8 == 0 {
537 value.order = MasonryItemOrder::Ordered;
538 }
539 value
540 }
541 }
542
543 #[cfg(feature = "gecko")]
544 impl From<MasonryAutoFlow> for u8 {
from(v: MasonryAutoFlow) -> u8545 fn from(v: MasonryAutoFlow) -> u8 {
546 use crate::gecko_bindings::structs;
547
548 let mut result: u8 = 0;
549 if v.placement == MasonryPlacement::Pack {
550 result |= structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8;
551 }
552 if v.order == MasonryItemOrder::DefiniteFirst {
553 result |= structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8;
554 }
555 result
556 }
557 }
558
559 impl Parse for GridAutoFlow {
560 /// [ row | column ] || dense
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<GridAutoFlow, ParseError<'i>>561 fn parse<'i, 't>(
562 _context: &ParserContext,
563 input: &mut Parser<'i, 't>,
564 ) -> Result<GridAutoFlow, ParseError<'i>> {
565 let mut track = None;
566 let mut dense = GridAutoFlow::empty();
567
568 while !input.is_exhausted() {
569 let location = input.current_source_location();
570 let ident = input.expect_ident()?;
571 let success = match_ignore_ascii_case! { &ident,
572 "row" if track.is_none() => {
573 track = Some(GridAutoFlow::ROW);
574 true
575 },
576 "column" if track.is_none() => {
577 track = Some(GridAutoFlow::COLUMN);
578 true
579 },
580 "dense" if dense.is_empty() => {
581 dense = GridAutoFlow::DENSE;
582 true
583 },
584 _ => false,
585 };
586 if !success {
587 return Err(location
588 .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone())));
589 }
590 }
591
592 if track.is_some() || !dense.is_empty() {
593 Ok(track.unwrap_or(GridAutoFlow::ROW) | dense)
594 } else {
595 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
596 }
597 }
598 }
599
600 impl ToCss for GridAutoFlow {
to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,601 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
602 where
603 W: Write,
604 {
605 if *self == GridAutoFlow::ROW {
606 return dest.write_str("row");
607 }
608
609 if *self == GridAutoFlow::COLUMN {
610 return dest.write_str("column");
611 }
612
613 if *self == GridAutoFlow::ROW | GridAutoFlow::DENSE {
614 return dest.write_str("dense");
615 }
616
617 if *self == GridAutoFlow::COLUMN | GridAutoFlow::DENSE {
618 return dest.write_str("column dense");
619 }
620
621 debug_assert!(false, "Unknown or invalid grid-autoflow value");
622 Ok(())
623 }
624 }
625
626 #[derive(
627 Clone,
628 Debug,
629 MallocSizeOf,
630 PartialEq,
631 SpecifiedValueInfo,
632 ToComputedValue,
633 ToCss,
634 ToResolvedValue,
635 ToShmem,
636 )]
637 #[repr(C)]
638 /// https://drafts.csswg.org/css-grid/#named-grid-area
639 pub struct TemplateAreas {
640 /// `named area` containing for each template area
641 #[css(skip)]
642 pub areas: crate::OwnedSlice<NamedArea>,
643 /// The original CSS string value of each template area
644 #[css(iterable)]
645 pub strings: crate::OwnedSlice<crate::OwnedStr>,
646 /// The number of columns of the grid.
647 #[css(skip)]
648 pub width: u32,
649 }
650
651 impl TemplateAreas {
652 /// Transform `vector` of str into `template area`
from_vec(strings: Vec<crate::OwnedStr>) -> Result<Self, ()>653 pub fn from_vec(strings: Vec<crate::OwnedStr>) -> Result<Self, ()> {
654 if strings.is_empty() {
655 return Err(());
656 }
657 let mut areas: Vec<NamedArea> = vec![];
658 let mut width = 0;
659 {
660 let mut row = 0u32;
661 let mut area_indices = PrecomputedHashMap::<Atom, usize>::default();
662 for string in &strings {
663 let mut current_area_index: Option<usize> = None;
664 row += 1;
665 let mut column = 0u32;
666 for token in TemplateAreasTokenizer(string) {
667 column += 1;
668 let name = if let Some(token) = token? {
669 Atom::from(token)
670 } else {
671 if let Some(index) = current_area_index.take() {
672 if areas[index].columns.end != column {
673 return Err(());
674 }
675 }
676 continue;
677 };
678 if let Some(index) = current_area_index {
679 if areas[index].name == name {
680 if areas[index].rows.start == row {
681 areas[index].columns.end += 1;
682 }
683 continue;
684 }
685 if areas[index].columns.end != column {
686 return Err(());
687 }
688 }
689 if let Some(index) = area_indices.get(&name).cloned() {
690 if areas[index].columns.start != column || areas[index].rows.end != row {
691 return Err(());
692 }
693 areas[index].rows.end += 1;
694 current_area_index = Some(index);
695 continue;
696 }
697 let index = areas.len();
698 assert!(area_indices.insert(name.clone(), index).is_none());
699 areas.push(NamedArea {
700 name,
701 columns: UnsignedRange {
702 start: column,
703 end: column + 1,
704 },
705 rows: UnsignedRange {
706 start: row,
707 end: row + 1,
708 },
709 });
710 current_area_index = Some(index);
711 }
712 if column == 0 {
713 // Each string must produce a valid token.
714 // https://github.com/w3c/csswg-drafts/issues/5110
715 return Err(());
716 }
717 if let Some(index) = current_area_index {
718 if areas[index].columns.end != column + 1 {
719 assert_ne!(areas[index].rows.start, row);
720 return Err(());
721 }
722 }
723 if row == 1 {
724 width = column;
725 } else if width != column {
726 return Err(());
727 }
728 }
729 }
730 Ok(TemplateAreas {
731 areas: areas.into(),
732 strings: strings.into(),
733 width,
734 })
735 }
736 }
737
738 impl Parse for TemplateAreas {
parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>739 fn parse<'i, 't>(
740 _context: &ParserContext,
741 input: &mut Parser<'i, 't>,
742 ) -> Result<Self, ParseError<'i>> {
743 let mut strings = vec![];
744 while let Ok(string) =
745 input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned().into()))
746 {
747 strings.push(string);
748 }
749
750 TemplateAreas::from_vec(strings)
751 .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
752 }
753 }
754
755 /// Arc type for `Arc<TemplateAreas>`
756 #[derive(
757 Clone,
758 Debug,
759 MallocSizeOf,
760 PartialEq,
761 SpecifiedValueInfo,
762 ToComputedValue,
763 ToCss,
764 ToResolvedValue,
765 ToShmem,
766 )]
767 #[repr(transparent)]
768 pub struct TemplateAreasArc(#[ignore_malloc_size_of = "Arc"] pub Arc<TemplateAreas>);
769
770 impl Parse for TemplateAreasArc {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>771 fn parse<'i, 't>(
772 context: &ParserContext,
773 input: &mut Parser<'i, 't>,
774 ) -> Result<Self, ParseError<'i>> {
775 let parsed = TemplateAreas::parse(context, input)?;
776 Ok(TemplateAreasArc(Arc::new(parsed)))
777 }
778 }
779
780 /// A range of rows or columns. Using this instead of std::ops::Range for FFI
781 /// purposes.
782 #[repr(C)]
783 #[derive(
784 Clone,
785 Debug,
786 MallocSizeOf,
787 PartialEq,
788 SpecifiedValueInfo,
789 ToComputedValue,
790 ToResolvedValue,
791 ToShmem,
792 )]
793 pub struct UnsignedRange {
794 /// The start of the range.
795 pub start: u32,
796 /// The end of the range.
797 pub end: u32,
798 }
799
800 #[derive(
801 Clone,
802 Debug,
803 MallocSizeOf,
804 PartialEq,
805 SpecifiedValueInfo,
806 ToComputedValue,
807 ToResolvedValue,
808 ToShmem,
809 )]
810 #[repr(C)]
811 /// Not associated with any particular grid item, but can be referenced from the
812 /// grid-placement properties.
813 pub struct NamedArea {
814 /// Name of the `named area`
815 pub name: Atom,
816 /// Rows of the `named area`
817 pub rows: UnsignedRange,
818 /// Columns of the `named area`
819 pub columns: UnsignedRange,
820 }
821
822 /// Tokenize the string into a list of the tokens,
823 /// using longest-match semantics
824 struct TemplateAreasTokenizer<'a>(&'a str);
825
826 impl<'a> Iterator for TemplateAreasTokenizer<'a> {
827 type Item = Result<Option<&'a str>, ()>;
828
next(&mut self) -> Option<Self::Item>829 fn next(&mut self) -> Option<Self::Item> {
830 let rest = self.0.trim_start_matches(HTML_SPACE_CHARACTERS);
831 if rest.is_empty() {
832 return None;
833 }
834 if rest.starts_with('.') {
835 self.0 = &rest[rest.find(|c| c != '.').unwrap_or(rest.len())..];
836 return Some(Ok(None));
837 }
838 if !rest.starts_with(is_name_code_point) {
839 return Some(Err(()));
840 }
841 let token_len = rest.find(|c| !is_name_code_point(c)).unwrap_or(rest.len());
842 let token = &rest[..token_len];
843 self.0 = &rest[token_len..];
844 Some(Ok(Some(token)))
845 }
846 }
847
is_name_code_point(c: char) -> bool848 fn is_name_code_point(c: char) -> bool {
849 c >= 'A' && c <= 'Z' ||
850 c >= 'a' && c <= 'z' ||
851 c >= '\u{80}' ||
852 c == '_' ||
853 c >= '0' && c <= '9' ||
854 c == '-'
855 }
856
857 /// This property specifies named grid areas.
858 ///
859 /// The syntax of this property also provides a visualization of the structure
860 /// of the grid, making the overall layout of the grid container easier to
861 /// understand.
862 #[repr(C, u8)]
863 #[derive(
864 Clone,
865 Debug,
866 MallocSizeOf,
867 Parse,
868 PartialEq,
869 SpecifiedValueInfo,
870 ToComputedValue,
871 ToCss,
872 ToResolvedValue,
873 ToShmem,
874 )]
875 pub enum GridTemplateAreas {
876 /// The `none` value.
877 None,
878 /// The actual value.
879 Areas(TemplateAreasArc),
880 }
881
882 impl GridTemplateAreas {
883 #[inline]
884 /// Get default value as `none`
none() -> GridTemplateAreas885 pub fn none() -> GridTemplateAreas {
886 GridTemplateAreas::None
887 }
888 }
889
890 /// A specified value for the `z-index` property.
891 pub type ZIndex = GenericZIndex<Integer>;
892
893 /// A specified value for the `aspect-ratio` property.
894 pub type AspectRatio = GenericAspectRatio<NonNegativeNumber>;
895
896 impl Parse for AspectRatio {
parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>897 fn parse<'i, 't>(
898 context: &ParserContext,
899 input: &mut Parser<'i, 't>,
900 ) -> Result<Self, ParseError<'i>> {
901 use crate::values::generics::position::PreferredRatio;
902 use crate::values::specified::Ratio;
903
904 let location = input.current_source_location();
905 let mut auto = input.try_parse(|i| i.expect_ident_matching("auto"));
906 let ratio = input.try_parse(|i| Ratio::parse(context, i));
907 if auto.is_err() {
908 auto = input.try_parse(|i| i.expect_ident_matching("auto"));
909 }
910
911 if auto.is_err() && ratio.is_err() {
912 return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
913 }
914
915 Ok(AspectRatio {
916 auto: auto.is_ok(),
917 ratio: match ratio {
918 Ok(ratio) => PreferredRatio::Ratio(ratio),
919 Err(..) => PreferredRatio::None,
920 },
921 })
922 }
923 }
924
925 impl AspectRatio {
926 /// Returns Self by a valid ratio.
from_mapped_ratio(w: f32, h: f32) -> Self927 pub fn from_mapped_ratio(w: f32, h: f32) -> Self {
928 use crate::values::generics::position::PreferredRatio;
929 use crate::values::generics::ratio::Ratio;
930 AspectRatio {
931 auto: true,
932 ratio: PreferredRatio::Ratio(Ratio(
933 NonNegativeNumber::new(w),
934 NonNegativeNumber::new(h),
935 )),
936 }
937 }
938 }
939