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, PxConstraints, PxConstraints2d, PxDensity};
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 #[inline(always)]
56 pub fn capture_metrics_use<R>(&self, f: impl FnOnce() -> R) -> (LayoutMask, R) {
57 METRICS_USED_CTX.with_context(&mut Some(Arc::new(Atomic::new(LayoutMask::empty()))), || {
58 let r = f();
59 let uses = METRICS_USED_CTX.get().load(Relaxed);
60 (uses, r)
61 })
62 }
63
64 pub fn register_metrics_use(&self, uses: LayoutMask) {
68 let ctx = METRICS_USED_CTX.get();
69 let m = ctx.load(Relaxed);
70 ctx.store(m | uses, Relaxed);
71 }
72
73 pub fn constraints(&self) -> PxConstraints2d {
75 LAYOUT_CTX.get().metrics.constraints()
76 }
77
78 pub fn z_constraints(&self) -> PxConstraints {
80 LAYOUT_CTX.get().metrics.z_constraints()
81 }
82
83 pub fn constraints_for(&self, axis: LayoutAxis) -> PxConstraints {
85 match axis {
86 LayoutAxis::X => self.constraints().x,
87 LayoutAxis::Y => self.constraints().y,
88 LayoutAxis::Z => self.z_constraints(),
89 }
90 }
91
92 pub fn with_constraints<R>(&self, constraints: PxConstraints2d, f: impl FnOnce() -> R) -> R {
94 self.with_context(self.metrics().with_constraints(constraints), f)
95 }
96
97 pub fn with_z_constraints<R>(&self, constraints: PxConstraints, f: impl FnOnce() -> R) -> R {
99 self.with_context(self.metrics().with_z_constraints(constraints), f)
100 }
101
102 pub fn with_constraints_for<R>(&self, axis: LayoutAxis, constraints: PxConstraints, f: impl FnOnce() -> R) -> R {
104 match axis {
105 LayoutAxis::X => {
106 let mut c = self.constraints();
107 c.x = constraints;
108 self.with_constraints(c, f)
109 }
110 LayoutAxis::Y => {
111 let mut c = self.constraints();
112 c.y = constraints;
113 self.with_constraints(c, f)
114 }
115 LayoutAxis::Z => self.with_z_constraints(constraints, f),
116 }
117 }
118
119 pub fn with_sub_size(&self, removed: PxSize, f: impl FnOnce() -> PxSize) -> PxSize {
121 self.with_constraints(self.constraints().with_less_size(removed), f) + removed
122 }
123
124 pub fn with_add_size(&self, added: PxSize, f: impl FnOnce() -> PxSize) -> PxSize {
126 self.with_constraints(self.constraints().with_more_size(added), f) - added
127 }
128
129 pub fn inline_constraints(&self) -> Option<InlineConstraints> {
131 LAYOUT_CTX.get().metrics.inline_constraints()
132 }
133
134 pub fn with_no_inline<R>(&self, f: impl FnOnce() -> R) -> R {
136 let metrics = self.metrics();
137 if metrics.inline_constraints().is_none() {
138 f()
139 } else {
140 self.with_context(metrics.with_inline_constraints(None), f)
141 }
142 }
143
144 pub fn root_font_size(&self) -> Px {
146 LAYOUT_CTX.get().metrics.root_font_size()
147 }
148
149 pub fn font_size(&self) -> Px {
151 LAYOUT_CTX.get().metrics.font_size()
152 }
153
154 pub fn with_font_size<R>(&self, font_size: Px, f: impl FnOnce() -> R) -> R {
156 self.with_context(self.metrics().with_font_size(font_size), f)
157 }
158
159 pub fn viewport(&self) -> PxSize {
161 LAYOUT_CTX.get().metrics.viewport()
162 }
163
164 pub fn viewport_min(&self) -> Px {
166 LAYOUT_CTX.get().metrics.viewport_min()
167 }
168
169 pub fn viewport_max(&self) -> Px {
171 LAYOUT_CTX.get().metrics.viewport_max()
172 }
173
174 pub fn viewport_for(&self, axis: LayoutAxis) -> Px {
176 let vp = self.viewport();
177 match axis {
178 LayoutAxis::X => vp.width,
179 LayoutAxis::Y => vp.height,
180 LayoutAxis::Z => Px::MAX,
181 }
182 }
183
184 pub fn with_viewport<R>(&self, viewport: PxSize, f: impl FnOnce() -> R) -> R {
186 self.with_context(self.metrics().with_viewport(viewport), f)
187 }
188
189 pub fn scale_factor(&self) -> Factor {
191 LAYOUT_CTX.get().metrics.scale_factor()
192 }
193
194 pub fn with_scale_factor<R>(&self, scale_factor: Factor, f: impl FnOnce() -> R) -> R {
196 self.with_context(self.metrics().with_scale_factor(scale_factor), f)
197 }
198
199 pub fn screen_density(&self) -> PxDensity {
201 LAYOUT_CTX.get().metrics.screen_density()
202 }
203
204 pub fn with_screen_density<R>(&self, screen_density: PxDensity, f: impl FnOnce() -> R) -> R {
206 self.with_context(self.metrics().with_screen_density(screen_density), f)
207 }
208
209 pub fn direction(&self) -> LayoutDirection {
211 LAYOUT_CTX.get().metrics.direction()
212 }
213
214 pub fn with_direction<R>(&self, direction: LayoutDirection, f: impl FnOnce() -> R) -> R {
216 self.with_context(self.metrics().with_direction(direction), f)
217 }
218
219 pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
223 LAYOUT_CTX.get().metrics.leftover()
224 }
225
226 pub fn leftover_for(&self, axis: LayoutAxis) -> Option<Px> {
228 let l = self.leftover();
229
230 match axis {
231 LayoutAxis::X => l.width,
232 LayoutAxis::Y => l.height,
233 LayoutAxis::Z => None,
234 }
235 }
236
237 pub fn with_leftover<R>(&self, width: Option<Px>, height: Option<Px>, f: impl FnOnce() -> R) -> R {
241 self.with_context(self.metrics().with_leftover(width, height), f)
242 }
243}
244
245context_local! {
246 static LAYOUT_CTX: LayoutCtx = LayoutCtx::no_context();
247 static LAYOUT_PASS_CTX: LayoutPassId = LayoutPassId::new();
248 static METRICS_USED_CTX: Atomic<LayoutMask> = Atomic::new(LayoutMask::empty());
249}
250
251struct LayoutCtx {
252 metrics: LayoutMetrics,
253}
254impl LayoutCtx {
255 fn no_context() -> Self {
256 panic!("no layout context")
257 }
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
264pub struct LayoutPassId(u32);
265impl LayoutPassId {
266 pub const fn new() -> Self {
268 LayoutPassId(0)
269 }
270
271 pub const fn next(self) -> LayoutPassId {
273 LayoutPassId(self.0.wrapping_add(1))
274 }
275}
276
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
281#[non_exhaustive]
282pub struct InlineConstraintsMeasure {
283 pub first_max: Px,
285 pub mid_clear_min: Px,
290}
291impl InlineConstraintsMeasure {
292 pub fn new(first_max: Px, mid_clear_min: Px) -> Self {
294 Self { first_max, mid_clear_min }
295 }
296}
297
298#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
304#[non_exhaustive]
305pub struct InlineSegmentPos {
306 pub x: f32,
308}
309impl InlineSegmentPos {
310 pub fn new(x: f32) -> Self {
312 Self { x }
313 }
314}
315impl PartialEq for InlineSegmentPos {
316 fn eq(&self, other: &Self) -> bool {
317 about_eq(self.x, other.x, 0.001)
318 }
319}
320impl Eq for InlineSegmentPos {}
321impl std::hash::Hash for InlineSegmentPos {
322 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
323 about_eq_hash(self.x, 0.001, state);
324 }
325}
326impl PartialOrd for InlineSegmentPos {
327 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
328 Some(self.cmp(other))
329 }
330}
331impl Ord for InlineSegmentPos {
332 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
333 about_eq_ord(self.x, other.x, 0.001)
334 }
335}
336
337#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
341#[non_exhaustive]
342pub struct InlineConstraintsLayout {
343 pub first: PxRect,
345 pub mid_clear: Px,
347 pub last: PxRect,
349
350 pub first_segs: Arc<Vec<InlineSegmentPos>>,
352 pub last_segs: Arc<Vec<InlineSegmentPos>>,
354}
355
356impl InlineConstraintsLayout {
357 pub fn new(
359 first: PxRect,
360 mid_clear: Px,
361 last: PxRect,
362 first_segs: Arc<Vec<InlineSegmentPos>>,
363 last_segs: Arc<Vec<InlineSegmentPos>>,
364 ) -> Self {
365 Self {
366 first,
367 mid_clear,
368 last,
369 first_segs,
370 last_segs,
371 }
372 }
373}
374
375#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
377pub enum InlineConstraints {
378 Measure(InlineConstraintsMeasure),
380 Layout(InlineConstraintsLayout),
382}
383impl InlineConstraints {
384 pub fn measure(self) -> InlineConstraintsMeasure {
386 match self {
387 InlineConstraints::Measure(m) => m,
388 InlineConstraints::Layout(l) => InlineConstraintsMeasure {
389 first_max: l.first.width(),
390 mid_clear_min: l.mid_clear,
391 },
392 }
393 }
394
395 pub fn layout(self) -> InlineConstraintsLayout {
397 match self {
398 InlineConstraints::Layout(m) => m,
399 InlineConstraints::Measure(_) => Default::default(),
400 }
401 }
402}
403
404#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
408#[non_exhaustive]
409pub struct LayoutMetricsSnapshot {
410 pub constraints: PxConstraints2d,
414
415 pub inline_constraints: Option<InlineConstraints>,
419
420 pub z_constraints: PxConstraints,
424
425 pub font_size: Px,
429 pub root_font_size: Px,
433 pub scale_factor: Factor,
437 pub viewport: PxSize,
441 pub screen_density: PxDensity,
445
446 pub direction: LayoutDirection,
450
451 pub leftover: euclid::Size2D<Option<Px>, ()>,
455}
456impl LayoutMetricsSnapshot {
457 pub fn masked_eq(&self, other: &Self, mask: LayoutMask) -> bool {
459 (!mask.contains(LayoutMask::CONSTRAINTS)
460 || (self.constraints == other.constraints
461 && self.z_constraints == other.z_constraints
462 && self.inline_constraints == other.inline_constraints))
463 && (!mask.contains(LayoutMask::FONT_SIZE) || self.font_size == other.font_size)
464 && (!mask.contains(LayoutMask::ROOT_FONT_SIZE) || self.root_font_size == other.root_font_size)
465 && (!mask.contains(LayoutMask::SCALE_FACTOR) || self.scale_factor == other.scale_factor)
466 && (!mask.contains(LayoutMask::VIEWPORT) || self.viewport == other.viewport)
467 && (!mask.contains(LayoutMask::SCREEN_DENSITY) || self.screen_density == other.screen_density)
468 && (!mask.contains(LayoutMask::DIRECTION) || self.direction == other.direction)
469 && (!mask.contains(LayoutMask::LEFTOVER) || self.leftover == other.leftover)
470 }
471}
472impl PartialEq for LayoutMetricsSnapshot {
473 fn eq(&self, other: &Self) -> bool {
474 self.constraints == other.constraints
475 && self.z_constraints == other.z_constraints
476 && self.inline_constraints == other.inline_constraints
477 && self.font_size == other.font_size
478 && self.root_font_size == other.root_font_size
479 && self.scale_factor == other.scale_factor
480 && self.viewport == other.viewport
481 && self.screen_density == other.screen_density
482 && self.direction == other.direction
483 && self.leftover == other.leftover
484 }
485}
486impl std::hash::Hash for LayoutMetricsSnapshot {
487 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
488 self.constraints.hash(state);
489 self.inline_constraints.hash(state);
490 self.font_size.hash(state);
491 self.root_font_size.hash(state);
492 self.scale_factor.hash(state);
493 self.viewport.hash(state);
494 self.screen_density.hash(state);
495 self.direction.hash(state);
496 self.leftover.hash(state);
497 }
498}
499
500#[derive(Debug, Clone)]
502pub struct LayoutMetrics {
503 s: LayoutMetricsSnapshot,
504}
505impl LayoutMetrics {
506 pub fn new(scale_factor: Factor, viewport: PxSize, font_size: Px) -> Self {
513 LayoutMetrics {
514 s: LayoutMetricsSnapshot {
515 constraints: PxConstraints2d::new_fill_size(viewport),
516 z_constraints: PxConstraints::new_unbounded().with_min(Px(1)),
517 inline_constraints: None,
518 font_size,
519 root_font_size: font_size,
520 scale_factor,
521 viewport,
522 screen_density: PxDensity::default(),
523 direction: LayoutDirection::default(),
524 leftover: euclid::size2(None, None),
525 },
526 }
527 }
528
529 pub fn constraints(&self) -> PxConstraints2d {
531 LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
532 self.s.constraints
533 }
534
535 pub fn z_constraints(&self) -> PxConstraints {
537 LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
538 self.s.z_constraints
539 }
540
541 pub fn inline_constraints(&self) -> Option<InlineConstraints> {
545 LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
546 self.s.inline_constraints.clone()
547 }
548
549 pub fn direction(&self) -> LayoutDirection {
551 LAYOUT.register_metrics_use(LayoutMask::DIRECTION);
552 self.s.direction
553 }
554
555 pub fn font_size(&self) -> Px {
557 LAYOUT.register_metrics_use(LayoutMask::FONT_SIZE);
558 self.s.font_size
559 }
560
561 pub fn root_font_size(&self) -> Px {
563 LAYOUT.register_metrics_use(LayoutMask::ROOT_FONT_SIZE);
564 self.s.root_font_size
565 }
566
567 pub fn scale_factor(&self) -> Factor {
569 LAYOUT.register_metrics_use(LayoutMask::SCALE_FACTOR);
570 self.s.scale_factor
571 }
572
573 pub fn viewport(&self) -> PxSize {
578 LAYOUT.register_metrics_use(LayoutMask::VIEWPORT);
579 self.s.viewport
580 }
581
582 pub fn viewport_min(&self) -> Px {
586 self.s.viewport.width.min(self.s.viewport.height)
587 }
588
589 pub fn viewport_max(&self) -> Px {
593 self.s.viewport.width.max(self.s.viewport.height)
594 }
595
596 pub fn screen_density(&self) -> PxDensity {
600 LAYOUT.register_metrics_use(LayoutMask::SCREEN_DENSITY);
601 self.s.screen_density
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_density(mut self, screen_density: PxDensity) -> Self {
664 self.s.screen_density = screen_density;
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,
800 BoundaryNeutral,
802
803 Emoji,
805
806 LineBreak,
808 Tab,
810 Space,
812 OtherNeutral,
814 Bracket(char),
818
819 BidiCtrl(char),
834}
835impl TextSegmentKind {
836 pub fn is_word(self) -> bool {
838 use TextSegmentKind::*;
839 matches!(
840 self,
841 LeftToRight
842 | RightToLeft
843 | ArabicLetter
844 | EuropeanNumber
845 | EuropeanSeparator
846 | EuropeanTerminator
847 | ArabicNumber
848 | CommonSeparator
849 | NonSpacingMark
850 | BoundaryNeutral
851 | OtherNeutral
852 | Bracket(_)
853 | Emoji
854 )
855 }
856
857 pub fn is_space(self) -> bool {
859 matches!(self, Self::Space | Self::Tab)
860 }
861
862 pub fn is_line_break(self) -> bool {
866 matches!(self, Self::LineBreak)
867 }
868
869 pub fn can_merge(self) -> bool {
871 use TextSegmentKind::*;
872 !matches!(self, Bracket(_) | BidiCtrl(_))
873 }
874
875 pub fn bracket_info(self) -> Option<unicode_bidi::data_source::BidiMatchedOpeningBracket> {
877 if let TextSegmentKind::Bracket(c) = self {
878 unicode_bidi::HardcodedBidiData.bidi_matched_opening_bracket(c)
879 } else {
880 None
881 }
882 }
883
884 pub fn strong_direction(self) -> Option<LayoutDirection> {
888 use TextSegmentKind::*;
889
890 match self {
891 LeftToRight => Some(LayoutDirection::LTR),
892 RightToLeft | ArabicLetter => Some(LayoutDirection::RTL),
893 BidiCtrl(_) => {
894 use unicode_bidi::BidiClass::*;
895 match unicode_bidi::BidiClass::from(self) {
896 LRE | LRO | LRI => Some(LayoutDirection::LTR),
897 RLE | RLO | RLI => Some(LayoutDirection::RTL),
898 _ => None,
899 }
900 }
901 _ => None,
902 }
903 }
904}
905impl From<char> for TextSegmentKind {
906 fn from(c: char) -> Self {
907 use unicode_bidi::*;
908
909 unicode_bidi::HardcodedBidiData.bidi_class(c).into()
910 }
911}
912
913impl From<unicode_bidi::BidiClass> for TextSegmentKind {
914 fn from(value: unicode_bidi::BidiClass) -> Self {
915 use TextSegmentKind::*;
916 use unicode_bidi::BidiClass::*;
917
918 match value {
919 WS => Space,
920 L => LeftToRight,
921 R => RightToLeft,
922 AL => ArabicLetter,
923 AN => ArabicNumber,
924 CS => CommonSeparator,
925 B => LineBreak,
926 EN => EuropeanNumber,
927 ES => EuropeanSeparator,
928 ET => EuropeanTerminator,
929 S => Tab,
930 ON => OtherNeutral,
931 BN => BoundaryNeutral,
932 NSM => NonSpacingMark,
933 RLE => BidiCtrl('\u{202B}'),
934 LRI => BidiCtrl('\u{2066}'),
935 RLI => BidiCtrl('\u{2067}'),
936 LRO => BidiCtrl('\u{202D}'),
937 FSI => BidiCtrl('\u{2068}'),
938 PDF => BidiCtrl('\u{202C}'),
939 LRE => BidiCtrl('\u{202A}'),
940 PDI => BidiCtrl('\u{2069}'),
941 RLO => BidiCtrl('\u{202E}'),
942 }
943 }
944}
945impl From<TextSegmentKind> for unicode_bidi::BidiClass {
946 fn from(value: TextSegmentKind) -> Self {
947 use TextSegmentKind::*;
948 use unicode_bidi::BidiClass::*;
949
950 match value {
951 Space => WS,
952 LeftToRight => L,
953 RightToLeft => R,
954 ArabicLetter => AL,
955 ArabicNumber => AN,
956 CommonSeparator => CS,
957 LineBreak => B,
958 EuropeanNumber => EN,
959 EuropeanSeparator => ES,
960 EuropeanTerminator => ET,
961 Tab => S,
962 OtherNeutral | Emoji | Bracket(_) => ON,
963 BoundaryNeutral => BN,
964 NonSpacingMark => NSM,
965 BidiCtrl(c) => match c {
966 '\u{202A}' => LRE,
967 '\u{202D}' => LRO,
968 '\u{202B}' => RLE,
969 '\u{202E}' => RLO,
970 '\u{202C}' => PDF,
971 '\u{2066}' => LRI,
972 '\u{2067}' => RLI,
973 '\u{2068}' => FSI,
974 '\u{2069}' => PDI,
975 _c => {
976 #[cfg(debug_assertions)]
977 {
978 tracing::error!("invalid bidi ctrl char '{_c}'");
979 }
980 ON
981 }
982 },
983 }
984 }
985}
986
987bitflags! {
988 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, bytemuck::NoUninit)]
990 #[repr(transparent)]
991 pub struct LayoutMask: u32 {
992 const DEFAULT_VALUE = 1 << 31;
994 const CONSTRAINTS = 1 << 30;
996
997 const FONT_SIZE = 1;
999 const ROOT_FONT_SIZE = 1 << 1;
1001 const SCALE_FACTOR = 1 << 2;
1003 const VIEWPORT = 1 << 3;
1005 const SCREEN_DENSITY = 1 << 4;
1007 const DIRECTION = 1 << 5;
1009 const LEFTOVER = 1 << 6;
1011 }
1012}
1013impl Default for LayoutMask {
1014 fn default() -> Self {
1016 LayoutMask::empty()
1017 }
1018}