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}