1use std::{fmt, sync::Arc};
4
5use bitflags::bitflags;
6use unicode_bidi::BidiDataSource as _;
7use zng_app_context::context_local;
8use zng_unit::{Factor, Px, PxRect, PxSize, about_eq, about_eq_hash, about_eq_ord, euclid};
9use zng_var::context_var;
10
11use atomic::{Atomic, Ordering::Relaxed};
12
13use crate::unit::{LayoutAxis, Ppi, PxConstraints, PxConstraints2d};
14
15pub struct LAYOUT;
19impl LAYOUT {
20    pub fn pass_id(&self) -> LayoutPassId {
24        LAYOUT_PASS_CTX.get_clone()
25    }
26
27    pub fn with_root_context<R>(&self, pass_id: LayoutPassId, metrics: LayoutMetrics, f: impl FnOnce() -> R) -> R {
29        let mut pass = Some(Arc::new(pass_id));
30        LAYOUT_PASS_CTX.with_context(&mut pass, || self.with_context(metrics, f))
31    }
32
33    pub fn with_context<R>(&self, metrics: LayoutMetrics, f: impl FnOnce() -> R) -> R {
35        let mut ctx = Some(Arc::new(LayoutCtx { metrics }));
36        LAYOUT_CTX.with_context(&mut ctx, f)
37    }
38
39    pub fn with_no_context<R>(&self, f: impl FnOnce() -> R) -> R {
41        LAYOUT_CTX.with_default(f)
42    }
43
44    pub fn metrics(&self) -> LayoutMetrics {
46        LAYOUT_CTX.get().metrics.clone()
47    }
48
49    pub fn capture_metrics_use<R>(&self, f: impl FnOnce() -> R) -> (LayoutMask, R) {
56        METRICS_USED_CTX.with_context(&mut Some(Arc::new(Atomic::new(LayoutMask::empty()))), || {
57            let r = f();
58            let uses = METRICS_USED_CTX.get().load(Relaxed);
59            (uses, r)
60        })
61    }
62
63    pub fn register_metrics_use(&self, uses: LayoutMask) {
67        let ctx = METRICS_USED_CTX.get();
68        let m = ctx.load(Relaxed);
69        ctx.store(m | uses, Relaxed);
70    }
71
72    pub fn constraints(&self) -> PxConstraints2d {
74        LAYOUT_CTX.get().metrics.constraints()
75    }
76
77    pub fn z_constraints(&self) -> PxConstraints {
79        LAYOUT_CTX.get().metrics.z_constraints()
80    }
81
82    pub fn constraints_for(&self, axis: LayoutAxis) -> PxConstraints {
84        match axis {
85            LayoutAxis::X => self.constraints().x,
86            LayoutAxis::Y => self.constraints().y,
87            LayoutAxis::Z => self.z_constraints(),
88        }
89    }
90
91    pub fn with_constraints<R>(&self, constraints: PxConstraints2d, f: impl FnOnce() -> R) -> R {
93        self.with_context(self.metrics().with_constraints(constraints), f)
94    }
95
96    pub fn with_z_constraints<R>(&self, constraints: PxConstraints, f: impl FnOnce() -> R) -> R {
98        self.with_context(self.metrics().with_z_constraints(constraints), f)
99    }
100
101    pub fn with_constraints_for<R>(&self, axis: LayoutAxis, constraints: PxConstraints, f: impl FnOnce() -> R) -> R {
103        match axis {
104            LayoutAxis::X => {
105                let mut c = self.constraints();
106                c.x = constraints;
107                self.with_constraints(c, f)
108            }
109            LayoutAxis::Y => {
110                let mut c = self.constraints();
111                c.y = constraints;
112                self.with_constraints(c, f)
113            }
114            LayoutAxis::Z => self.with_z_constraints(constraints, f),
115        }
116    }
117
118    pub fn with_sub_size(&self, removed: PxSize, f: impl FnOnce() -> PxSize) -> PxSize {
120        self.with_constraints(self.constraints().with_less_size(removed), f) + removed
121    }
122
123    pub fn with_add_size(&self, added: PxSize, f: impl FnOnce() -> PxSize) -> PxSize {
125        self.with_constraints(self.constraints().with_more_size(added), f) - added
126    }
127
128    pub fn inline_constraints(&self) -> Option<InlineConstraints> {
130        LAYOUT_CTX.get().metrics.inline_constraints()
131    }
132
133    pub fn with_no_inline<R>(&self, f: impl FnOnce() -> R) -> R {
135        let metrics = self.metrics();
136        if metrics.inline_constraints().is_none() {
137            f()
138        } else {
139            self.with_context(metrics.with_inline_constraints(None), f)
140        }
141    }
142
143    pub fn root_font_size(&self) -> Px {
145        LAYOUT_CTX.get().metrics.root_font_size()
146    }
147
148    pub fn font_size(&self) -> Px {
150        LAYOUT_CTX.get().metrics.font_size()
151    }
152
153    pub fn with_font_size<R>(&self, font_size: Px, f: impl FnOnce() -> R) -> R {
155        self.with_context(self.metrics().with_font_size(font_size), f)
156    }
157
158    pub fn viewport(&self) -> PxSize {
160        LAYOUT_CTX.get().metrics.viewport()
161    }
162
163    pub fn viewport_min(&self) -> Px {
165        LAYOUT_CTX.get().metrics.viewport_min()
166    }
167
168    pub fn viewport_max(&self) -> Px {
170        LAYOUT_CTX.get().metrics.viewport_max()
171    }
172
173    pub fn viewport_for(&self, axis: LayoutAxis) -> Px {
175        let vp = self.viewport();
176        match axis {
177            LayoutAxis::X => vp.width,
178            LayoutAxis::Y => vp.height,
179            LayoutAxis::Z => Px::MAX,
180        }
181    }
182
183    pub fn with_viewport<R>(&self, viewport: PxSize, f: impl FnOnce() -> R) -> R {
185        self.with_context(self.metrics().with_viewport(viewport), f)
186    }
187
188    pub fn scale_factor(&self) -> Factor {
190        LAYOUT_CTX.get().metrics.scale_factor()
191    }
192
193    pub fn with_scale_factor<R>(&self, scale_factor: Factor, f: impl FnOnce() -> R) -> R {
195        self.with_context(self.metrics().with_scale_factor(scale_factor), f)
196    }
197
198    pub fn screen_ppi(&self) -> Ppi {
200        LAYOUT_CTX.get().metrics.screen_ppi()
201    }
202
203    pub fn with_screen_ppi<R>(&self, screen_ppi: Ppi, f: impl FnOnce() -> R) -> R {
205        self.with_context(self.metrics().with_screen_ppi(screen_ppi), f)
206    }
207
208    pub fn direction(&self) -> LayoutDirection {
210        LAYOUT_CTX.get().metrics.direction()
211    }
212
213    pub fn with_direction<R>(&self, direction: LayoutDirection, f: impl FnOnce() -> R) -> R {
215        self.with_context(self.metrics().with_direction(direction), f)
216    }
217
218    pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
222        LAYOUT_CTX.get().metrics.leftover()
223    }
224
225    pub fn leftover_for(&self, axis: LayoutAxis) -> Option<Px> {
227        let l = self.leftover();
228
229        match axis {
230            LayoutAxis::X => l.width,
231            LayoutAxis::Y => l.height,
232            LayoutAxis::Z => None,
233        }
234    }
235
236    pub fn with_leftover<R>(&self, width: Option<Px>, height: Option<Px>, f: impl FnOnce() -> R) -> R {
240        self.with_context(self.metrics().with_leftover(width, height), f)
241    }
242}
243
244context_local! {
245    static LAYOUT_CTX: LayoutCtx = LayoutCtx::no_context();
246    static LAYOUT_PASS_CTX: LayoutPassId = LayoutPassId::new();
247    static METRICS_USED_CTX: Atomic<LayoutMask> = Atomic::new(LayoutMask::empty());
248}
249
250struct LayoutCtx {
251    metrics: LayoutMetrics,
252}
253impl LayoutCtx {
254    fn no_context() -> Self {
255        panic!("no layout context")
256    }
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
263pub struct LayoutPassId(u32);
264impl LayoutPassId {
265    pub const fn new() -> Self {
267        LayoutPassId(0)
268    }
269
270    pub const fn next(self) -> LayoutPassId {
272        LayoutPassId(self.0.wrapping_add(1))
273    }
274}
275
276#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
280#[non_exhaustive]
281pub struct InlineConstraintsMeasure {
282    pub first_max: Px,
284    pub mid_clear_min: Px,
289}
290impl InlineConstraintsMeasure {
291    pub fn new(first_max: Px, mid_clear_min: Px) -> Self {
293        Self { first_max, mid_clear_min }
294    }
295}
296
297#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
303#[non_exhaustive]
304pub struct InlineSegmentPos {
305    pub x: f32,
307}
308impl InlineSegmentPos {
309    pub fn new(x: f32) -> Self {
311        Self { x }
312    }
313}
314impl PartialEq for InlineSegmentPos {
315    fn eq(&self, other: &Self) -> bool {
316        about_eq(self.x, other.x, 0.001)
317    }
318}
319impl Eq for InlineSegmentPos {}
320impl std::hash::Hash for InlineSegmentPos {
321    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
322        about_eq_hash(self.x, 0.001, state);
323    }
324}
325impl PartialOrd for InlineSegmentPos {
326    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
327        Some(self.cmp(other))
328    }
329}
330impl Ord for InlineSegmentPos {
331    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
332        about_eq_ord(self.x, other.x, 0.001)
333    }
334}
335
336#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
340#[non_exhaustive]
341pub struct InlineConstraintsLayout {
342    pub first: PxRect,
344    pub mid_clear: Px,
346    pub last: PxRect,
348
349    pub first_segs: Arc<Vec<InlineSegmentPos>>,
351    pub last_segs: Arc<Vec<InlineSegmentPos>>,
353}
354
355impl InlineConstraintsLayout {
356    pub fn new(
358        first: PxRect,
359        mid_clear: Px,
360        last: PxRect,
361        first_segs: Arc<Vec<InlineSegmentPos>>,
362        last_segs: Arc<Vec<InlineSegmentPos>>,
363    ) -> Self {
364        Self {
365            first,
366            mid_clear,
367            last,
368            first_segs,
369            last_segs,
370        }
371    }
372}
373
374#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
376pub enum InlineConstraints {
377    Measure(InlineConstraintsMeasure),
379    Layout(InlineConstraintsLayout),
381}
382impl InlineConstraints {
383    pub fn measure(self) -> InlineConstraintsMeasure {
385        match self {
386            InlineConstraints::Measure(m) => m,
387            InlineConstraints::Layout(l) => InlineConstraintsMeasure {
388                first_max: l.first.width(),
389                mid_clear_min: l.mid_clear,
390            },
391        }
392    }
393
394    pub fn layout(self) -> InlineConstraintsLayout {
396        match self {
397            InlineConstraints::Layout(m) => m,
398            InlineConstraints::Measure(_) => Default::default(),
399        }
400    }
401}
402
403#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
407#[non_exhaustive]
408pub struct LayoutMetricsSnapshot {
409    pub constraints: PxConstraints2d,
413
414    pub inline_constraints: Option<InlineConstraints>,
418
419    pub z_constraints: PxConstraints,
423
424    pub font_size: Px,
428    pub root_font_size: Px,
432    pub scale_factor: Factor,
436    pub viewport: PxSize,
440    pub screen_ppi: Ppi,
444
445    pub direction: LayoutDirection,
449
450    pub leftover: euclid::Size2D<Option<Px>, ()>,
454}
455impl LayoutMetricsSnapshot {
456    pub fn masked_eq(&self, other: &Self, mask: LayoutMask) -> bool {
458        (!mask.contains(LayoutMask::CONSTRAINTS)
459            || (self.constraints == other.constraints
460                && self.z_constraints == other.z_constraints
461                && self.inline_constraints == other.inline_constraints))
462            && (!mask.contains(LayoutMask::FONT_SIZE) || self.font_size == other.font_size)
463            && (!mask.contains(LayoutMask::ROOT_FONT_SIZE) || self.root_font_size == other.root_font_size)
464            && (!mask.contains(LayoutMask::SCALE_FACTOR) || self.scale_factor == other.scale_factor)
465            && (!mask.contains(LayoutMask::VIEWPORT) || self.viewport == other.viewport)
466            && (!mask.contains(LayoutMask::SCREEN_PPI) || self.screen_ppi == other.screen_ppi)
467            && (!mask.contains(LayoutMask::DIRECTION) || self.direction == other.direction)
468            && (!mask.contains(LayoutMask::LEFTOVER) || self.leftover == other.leftover)
469    }
470}
471impl PartialEq for LayoutMetricsSnapshot {
472    fn eq(&self, other: &Self) -> bool {
473        self.constraints == other.constraints
474            && self.z_constraints == other.z_constraints
475            && self.inline_constraints == other.inline_constraints
476            && self.font_size == other.font_size
477            && self.root_font_size == other.root_font_size
478            && self.scale_factor == other.scale_factor
479            && self.viewport == other.viewport
480            && self.screen_ppi == other.screen_ppi
481            && self.direction == other.direction
482            && self.leftover == other.leftover
483    }
484}
485impl std::hash::Hash for LayoutMetricsSnapshot {
486    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
487        self.constraints.hash(state);
488        self.inline_constraints.hash(state);
489        self.font_size.hash(state);
490        self.root_font_size.hash(state);
491        self.scale_factor.hash(state);
492        self.viewport.hash(state);
493        self.screen_ppi.hash(state);
494        self.direction.hash(state);
495        self.leftover.hash(state);
496    }
497}
498
499#[derive(Debug, Clone)]
501pub struct LayoutMetrics {
502    s: LayoutMetricsSnapshot,
503}
504impl LayoutMetrics {
505    pub fn new(scale_factor: Factor, viewport: PxSize, font_size: Px) -> Self {
512        LayoutMetrics {
513            s: LayoutMetricsSnapshot {
514                constraints: PxConstraints2d::new_fill_size(viewport),
515                z_constraints: PxConstraints::new_unbounded().with_min(Px(1)),
516                inline_constraints: None,
517                font_size,
518                root_font_size: font_size,
519                scale_factor,
520                viewport,
521                screen_ppi: Ppi::default(),
522                direction: LayoutDirection::default(),
523                leftover: euclid::size2(None, None),
524            },
525        }
526    }
527
528    pub fn constraints(&self) -> PxConstraints2d {
530        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
531        self.s.constraints
532    }
533
534    pub fn z_constraints(&self) -> PxConstraints {
536        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
537        self.s.z_constraints
538    }
539
540    pub fn inline_constraints(&self) -> Option<InlineConstraints> {
544        LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
545        self.s.inline_constraints.clone()
546    }
547
548    pub fn direction(&self) -> LayoutDirection {
550        LAYOUT.register_metrics_use(LayoutMask::DIRECTION);
551        self.s.direction
552    }
553
554    pub fn font_size(&self) -> Px {
556        LAYOUT.register_metrics_use(LayoutMask::FONT_SIZE);
557        self.s.font_size
558    }
559
560    pub fn root_font_size(&self) -> Px {
562        LAYOUT.register_metrics_use(LayoutMask::ROOT_FONT_SIZE);
563        self.s.root_font_size
564    }
565
566    pub fn scale_factor(&self) -> Factor {
568        LAYOUT.register_metrics_use(LayoutMask::SCALE_FACTOR);
569        self.s.scale_factor
570    }
571
572    pub fn viewport(&self) -> PxSize {
577        LAYOUT.register_metrics_use(LayoutMask::VIEWPORT);
578        self.s.viewport
579    }
580
581    pub fn viewport_min(&self) -> Px {
585        self.s.viewport.width.min(self.s.viewport.height)
586    }
587
588    pub fn viewport_max(&self) -> Px {
592        self.s.viewport.width.max(self.s.viewport.height)
593    }
594
595    pub fn screen_ppi(&self) -> Ppi {
601        self.s.screen_ppi
602    }
603
604    pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
608        LAYOUT.register_metrics_use(LayoutMask::LEFTOVER);
609        self.s.leftover
610    }
611
612    pub fn with_constraints(mut self, constraints: PxConstraints2d) -> Self {
616        self.s.constraints = constraints;
617        self
618    }
619
620    pub fn with_z_constraints(mut self, constraints: PxConstraints) -> Self {
624        self.s.z_constraints = constraints;
625        self
626    }
627
628    pub fn with_inline_constraints(mut self, inline_constraints: Option<InlineConstraints>) -> Self {
632        self.s.inline_constraints = inline_constraints;
633        self
634    }
635
636    pub fn with_font_size(mut self, font_size: Px) -> Self {
640        self.s.font_size = font_size;
641        self
642    }
643
644    pub fn with_viewport(mut self, viewport: PxSize) -> Self {
648        self.s.viewport = viewport;
649        self
650    }
651
652    pub fn with_scale_factor(mut self, scale_factor: Factor) -> Self {
656        self.s.scale_factor = scale_factor;
657        self
658    }
659
660    pub fn with_screen_ppi(mut self, screen_ppi: Ppi) -> Self {
664        self.s.screen_ppi = screen_ppi;
665        self
666    }
667
668    pub fn with_direction(mut self, direction: LayoutDirection) -> Self {
672        self.s.direction = direction;
673        self
674    }
675
676    pub fn with_leftover(mut self, width: Option<Px>, height: Option<Px>) -> Self {
680        self.s.leftover = euclid::size2(width, height);
681        self
682    }
683
684    pub fn snapshot(&self) -> LayoutMetricsSnapshot {
688        self.s.clone()
689    }
690}
691
692context_var! {
693    pub static DIRECTION_VAR: LayoutDirection = LayoutDirection::LTR;
695}
696
697#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
709pub enum LayoutDirection {
710    LTR,
712    RTL,
714}
715impl LayoutDirection {
716    pub fn is_ltr(self) -> bool {
718        matches!(self, Self::LTR)
719    }
720
721    pub fn is_rtl(self) -> bool {
723        matches!(self, Self::RTL)
724    }
725}
726impl Default for LayoutDirection {
727    fn default() -> Self {
729        Self::LTR
730    }
731}
732impl fmt::Debug for LayoutDirection {
733    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734        if f.alternate() {
735            write!(f, "LayoutDirection::")?;
736        }
737        match self {
738            Self::LTR => write!(f, "LTR"),
739            Self::RTL => write!(f, "RTL"),
740        }
741    }
742}
743
744#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
748#[non_exhaustive]
749pub struct InlineSegment {
750    pub width: f32,
752    pub kind: TextSegmentKind,
754}
755
756impl InlineSegment {
757    pub fn new(width: f32, kind: TextSegmentKind) -> Self {
759        Self { width, kind }
760    }
761}
762impl PartialEq for InlineSegment {
763    fn eq(&self, other: &Self) -> bool {
764        about_eq(self.width, other.width, 0.001) && self.kind == other.kind
765    }
766}
767impl Eq for InlineSegment {}
768impl std::hash::Hash for InlineSegment {
769    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
770        about_eq_hash(self.width, 0.001, state);
771        self.kind.hash(state);
772    }
773}
774
775#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
777pub enum TextSegmentKind {
778    LeftToRight,
780    RightToLeft,
782    ArabicLetter,
784
785    EuropeanNumber,
787    EuropeanSeparator,
789    EuropeanTerminator,
791    ArabicNumber,
793    CommonSeparator,
795    NonSpacingMark,
797    BoundaryNeutral,
799
800    Emoji,
802
803    LineBreak,
805    Tab,
807    Space,
809    OtherNeutral,
811    Bracket(char),
815
816    BidiCtrl(char),
831}
832impl TextSegmentKind {
833    pub fn is_word(self) -> bool {
835        use TextSegmentKind::*;
836        matches!(
837            self,
838            LeftToRight
839                | RightToLeft
840                | ArabicLetter
841                | EuropeanNumber
842                | EuropeanSeparator
843                | EuropeanTerminator
844                | ArabicNumber
845                | CommonSeparator
846                | NonSpacingMark
847                | BoundaryNeutral
848                | OtherNeutral
849                | Bracket(_)
850                | Emoji
851        )
852    }
853
854    pub fn is_space(self) -> bool {
856        matches!(self, Self::Space | Self::Tab)
857    }
858
859    pub fn is_line_break(self) -> bool {
863        matches!(self, Self::LineBreak)
864    }
865
866    pub fn can_merge(self) -> bool {
868        use TextSegmentKind::*;
869        !matches!(self, Bracket(_) | BidiCtrl(_))
870    }
871
872    pub fn bracket_info(self) -> Option<unicode_bidi::data_source::BidiMatchedOpeningBracket> {
874        if let TextSegmentKind::Bracket(c) = self {
875            unicode_bidi::HardcodedBidiData.bidi_matched_opening_bracket(c)
876        } else {
877            None
878        }
879    }
880
881    pub fn strong_direction(self) -> Option<LayoutDirection> {
885        use TextSegmentKind::*;
886
887        match self {
888            LeftToRight => Some(LayoutDirection::LTR),
889            RightToLeft | ArabicLetter => Some(LayoutDirection::RTL),
890            BidiCtrl(_) => {
891                use unicode_bidi::BidiClass::*;
892                match unicode_bidi::BidiClass::from(self) {
893                    LRE | LRO | LRI => Some(LayoutDirection::LTR),
894                    RLE | RLO | RLI => Some(LayoutDirection::RTL),
895                    _ => None,
896                }
897            }
898            _ => None,
899        }
900    }
901}
902impl From<char> for TextSegmentKind {
903    fn from(c: char) -> Self {
904        use unicode_bidi::*;
905
906        unicode_bidi::HardcodedBidiData.bidi_class(c).into()
907    }
908}
909
910impl From<unicode_bidi::BidiClass> for TextSegmentKind {
911    fn from(value: unicode_bidi::BidiClass) -> Self {
912        use TextSegmentKind::*;
913        use unicode_bidi::BidiClass::*;
914
915        match value {
916            WS => Space,
917            L => LeftToRight,
918            R => RightToLeft,
919            AL => ArabicLetter,
920            AN => ArabicNumber,
921            CS => CommonSeparator,
922            B => LineBreak,
923            EN => EuropeanNumber,
924            ES => EuropeanSeparator,
925            ET => EuropeanTerminator,
926            S => Tab,
927            ON => OtherNeutral,
928            BN => BoundaryNeutral,
929            NSM => NonSpacingMark,
930            RLE => BidiCtrl('\u{202B}'),
931            LRI => BidiCtrl('\u{2066}'),
932            RLI => BidiCtrl('\u{2067}'),
933            LRO => BidiCtrl('\u{202D}'),
934            FSI => BidiCtrl('\u{2068}'),
935            PDF => BidiCtrl('\u{202C}'),
936            LRE => BidiCtrl('\u{202A}'),
937            PDI => BidiCtrl('\u{2069}'),
938            RLO => BidiCtrl('\u{202E}'),
939        }
940    }
941}
942impl From<TextSegmentKind> for unicode_bidi::BidiClass {
943    fn from(value: TextSegmentKind) -> Self {
944        use TextSegmentKind::*;
945        use unicode_bidi::BidiClass::*;
946
947        match value {
948            Space => WS,
949            LeftToRight => L,
950            RightToLeft => R,
951            ArabicLetter => AL,
952            ArabicNumber => AN,
953            CommonSeparator => CS,
954            LineBreak => B,
955            EuropeanNumber => EN,
956            EuropeanSeparator => ES,
957            EuropeanTerminator => ET,
958            Tab => S,
959            OtherNeutral | Emoji | Bracket(_) => ON,
960            BoundaryNeutral => BN,
961            NonSpacingMark => NSM,
962            BidiCtrl(c) => match c {
963                '\u{202A}' => LRE,
964                '\u{202D}' => LRO,
965                '\u{202B}' => RLE,
966                '\u{202E}' => RLO,
967                '\u{202C}' => PDF,
968                '\u{2066}' => LRI,
969                '\u{2067}' => RLI,
970                '\u{2068}' => FSI,
971                '\u{2069}' => PDI,
972                _c => {
973                    #[cfg(debug_assertions)]
974                    {
975                        tracing::error!("invalid bidi ctrl char '{_c}'");
976                    }
977                    ON
978                }
979            },
980        }
981    }
982}
983
984bitflags! {
985    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, bytemuck::NoUninit)]
987    #[repr(transparent)]
988    pub struct LayoutMask: u32 {
989        const DEFAULT_VALUE = 1 << 31;
991        const CONSTRAINTS = 1 << 30;
993
994        const FONT_SIZE = 1;
996        const ROOT_FONT_SIZE = 1 << 1;
998        const SCALE_FACTOR = 1 << 2;
1000        const VIEWPORT = 1 << 3;
1002        const SCREEN_PPI = 1 << 4;
1004        const DIRECTION = 1 << 5;
1006        const LEFTOVER = 1 << 6;
1008    }
1009}
1010impl Default for LayoutMask {
1011    fn default() -> Self {
1013        LayoutMask::empty()
1014    }
1015}