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, 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}
325
326#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
330#[non_exhaustive]
331pub struct InlineConstraintsLayout {
332 pub first: PxRect,
334 pub mid_clear: Px,
336 pub last: PxRect,
338
339 pub first_segs: Arc<Vec<InlineSegmentPos>>,
341 pub last_segs: Arc<Vec<InlineSegmentPos>>,
343}
344
345impl InlineConstraintsLayout {
346 pub fn new(
348 first: PxRect,
349 mid_clear: Px,
350 last: PxRect,
351 first_segs: Arc<Vec<InlineSegmentPos>>,
352 last_segs: Arc<Vec<InlineSegmentPos>>,
353 ) -> Self {
354 Self {
355 first,
356 mid_clear,
357 last,
358 first_segs,
359 last_segs,
360 }
361 }
362}
363
364#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
366pub enum InlineConstraints {
367 Measure(InlineConstraintsMeasure),
369 Layout(InlineConstraintsLayout),
371}
372impl InlineConstraints {
373 pub fn measure(self) -> InlineConstraintsMeasure {
375 match self {
376 InlineConstraints::Measure(m) => m,
377 InlineConstraints::Layout(l) => InlineConstraintsMeasure {
378 first_max: l.first.width(),
379 mid_clear_min: l.mid_clear,
380 },
381 }
382 }
383
384 pub fn layout(self) -> InlineConstraintsLayout {
386 match self {
387 InlineConstraints::Layout(m) => m,
388 InlineConstraints::Measure(_) => Default::default(),
389 }
390 }
391}
392
393#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
397#[non_exhaustive]
398pub struct LayoutMetricsSnapshot {
399 pub constraints: PxConstraints2d,
403
404 pub inline_constraints: Option<InlineConstraints>,
408
409 pub z_constraints: PxConstraints,
413
414 pub font_size: Px,
418 pub root_font_size: Px,
422 pub scale_factor: Factor,
426 pub viewport: PxSize,
430 pub screen_ppi: Ppi,
434
435 pub direction: LayoutDirection,
439
440 pub leftover: euclid::Size2D<Option<Px>, ()>,
444}
445impl LayoutMetricsSnapshot {
446 pub fn masked_eq(&self, other: &Self, mask: LayoutMask) -> bool {
448 (!mask.contains(LayoutMask::CONSTRAINTS)
449 || (self.constraints == other.constraints
450 && self.z_constraints == other.z_constraints
451 && self.inline_constraints == other.inline_constraints))
452 && (!mask.contains(LayoutMask::FONT_SIZE) || self.font_size == other.font_size)
453 && (!mask.contains(LayoutMask::ROOT_FONT_SIZE) || self.root_font_size == other.root_font_size)
454 && (!mask.contains(LayoutMask::SCALE_FACTOR) || self.scale_factor == other.scale_factor)
455 && (!mask.contains(LayoutMask::VIEWPORT) || self.viewport == other.viewport)
456 && (!mask.contains(LayoutMask::SCREEN_PPI) || self.screen_ppi == other.screen_ppi)
457 && (!mask.contains(LayoutMask::DIRECTION) || self.direction == other.direction)
458 && (!mask.contains(LayoutMask::LEFTOVER) || self.leftover == other.leftover)
459 }
460}
461impl PartialEq for LayoutMetricsSnapshot {
462 fn eq(&self, other: &Self) -> bool {
463 self.constraints == other.constraints
464 && self.z_constraints == other.z_constraints
465 && self.inline_constraints == other.inline_constraints
466 && self.font_size == other.font_size
467 && self.root_font_size == other.root_font_size
468 && self.scale_factor == other.scale_factor
469 && self.viewport == other.viewport
470 && self.screen_ppi == other.screen_ppi
471 }
472}
473impl std::hash::Hash for LayoutMetricsSnapshot {
474 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
475 self.constraints.hash(state);
476 self.inline_constraints.hash(state);
477 self.font_size.hash(state);
478 self.root_font_size.hash(state);
479 self.scale_factor.hash(state);
480 self.viewport.hash(state);
481 self.screen_ppi.hash(state);
482 }
483}
484
485#[derive(Debug, Clone)]
487pub struct LayoutMetrics {
488 s: LayoutMetricsSnapshot,
489}
490impl LayoutMetrics {
491 pub fn new(scale_factor: Factor, viewport: PxSize, font_size: Px) -> Self {
498 LayoutMetrics {
499 s: LayoutMetricsSnapshot {
500 constraints: PxConstraints2d::new_fill_size(viewport),
501 z_constraints: PxConstraints::new_unbounded().with_min(Px(1)),
502 inline_constraints: None,
503 font_size,
504 root_font_size: font_size,
505 scale_factor,
506 viewport,
507 screen_ppi: Ppi::default(),
508 direction: LayoutDirection::default(),
509 leftover: euclid::size2(None, None),
510 },
511 }
512 }
513
514 pub fn constraints(&self) -> PxConstraints2d {
516 LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
517 self.s.constraints
518 }
519
520 pub fn z_constraints(&self) -> PxConstraints {
522 LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
523 self.s.z_constraints
524 }
525
526 pub fn inline_constraints(&self) -> Option<InlineConstraints> {
530 LAYOUT.register_metrics_use(LayoutMask::CONSTRAINTS);
531 self.s.inline_constraints.clone()
532 }
533
534 pub fn direction(&self) -> LayoutDirection {
536 LAYOUT.register_metrics_use(LayoutMask::DIRECTION);
537 self.s.direction
538 }
539
540 pub fn font_size(&self) -> Px {
542 LAYOUT.register_metrics_use(LayoutMask::FONT_SIZE);
543 self.s.font_size
544 }
545
546 pub fn root_font_size(&self) -> Px {
548 LAYOUT.register_metrics_use(LayoutMask::ROOT_FONT_SIZE);
549 self.s.root_font_size
550 }
551
552 pub fn scale_factor(&self) -> Factor {
554 LAYOUT.register_metrics_use(LayoutMask::SCALE_FACTOR);
555 self.s.scale_factor
556 }
557
558 pub fn viewport(&self) -> PxSize {
563 LAYOUT.register_metrics_use(LayoutMask::VIEWPORT);
564 self.s.viewport
565 }
566
567 pub fn viewport_min(&self) -> Px {
571 self.s.viewport.width.min(self.s.viewport.height)
572 }
573
574 pub fn viewport_max(&self) -> Px {
578 self.s.viewport.width.max(self.s.viewport.height)
579 }
580
581 pub fn screen_ppi(&self) -> Ppi {
587 self.s.screen_ppi
588 }
589
590 pub fn leftover(&self) -> euclid::Size2D<Option<Px>, ()> {
594 LAYOUT.register_metrics_use(LayoutMask::LEFTOVER);
595 self.s.leftover
596 }
597
598 pub fn with_constraints(mut self, constraints: PxConstraints2d) -> Self {
602 self.s.constraints = constraints;
603 self
604 }
605
606 pub fn with_z_constraints(mut self, constraints: PxConstraints) -> Self {
610 self.s.z_constraints = constraints;
611 self
612 }
613
614 pub fn with_inline_constraints(mut self, inline_constraints: Option<InlineConstraints>) -> Self {
618 self.s.inline_constraints = inline_constraints;
619 self
620 }
621
622 pub fn with_font_size(mut self, font_size: Px) -> Self {
626 self.s.font_size = font_size;
627 self
628 }
629
630 pub fn with_viewport(mut self, viewport: PxSize) -> Self {
634 self.s.viewport = viewport;
635 self
636 }
637
638 pub fn with_scale_factor(mut self, scale_factor: Factor) -> Self {
642 self.s.scale_factor = scale_factor;
643 self
644 }
645
646 pub fn with_screen_ppi(mut self, screen_ppi: Ppi) -> Self {
650 self.s.screen_ppi = screen_ppi;
651 self
652 }
653
654 pub fn with_direction(mut self, direction: LayoutDirection) -> Self {
658 self.s.direction = direction;
659 self
660 }
661
662 pub fn with_leftover(mut self, width: Option<Px>, height: Option<Px>) -> Self {
666 self.s.leftover = euclid::size2(width, height);
667 self
668 }
669
670 pub fn snapshot(&self) -> LayoutMetricsSnapshot {
674 self.s.clone()
675 }
676}
677
678context_var! {
679 pub static DIRECTION_VAR: LayoutDirection = LayoutDirection::LTR;
681}
682
683#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
695pub enum LayoutDirection {
696 LTR,
698 RTL,
700}
701impl LayoutDirection {
702 pub fn is_ltr(self) -> bool {
704 matches!(self, Self::LTR)
705 }
706
707 pub fn is_rtl(self) -> bool {
709 matches!(self, Self::RTL)
710 }
711}
712impl Default for LayoutDirection {
713 fn default() -> Self {
715 Self::LTR
716 }
717}
718impl fmt::Debug for LayoutDirection {
719 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720 if f.alternate() {
721 write!(f, "LayoutDirection::")?;
722 }
723 match self {
724 Self::LTR => write!(f, "LTR"),
725 Self::RTL => write!(f, "RTL"),
726 }
727 }
728}
729
730#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
734#[non_exhaustive]
735pub struct InlineSegment {
736 pub width: f32,
738 pub kind: TextSegmentKind,
740}
741
742impl InlineSegment {
743 pub fn new(width: f32, kind: TextSegmentKind) -> Self {
745 Self { width, kind }
746 }
747}
748impl PartialEq for InlineSegment {
749 fn eq(&self, other: &Self) -> bool {
750 about_eq(self.width, other.width, 0.001) && self.kind == other.kind
751 }
752}
753impl Eq for InlineSegment {}
754impl std::hash::Hash for InlineSegment {
755 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
756 about_eq_hash(self.width, 0.001, state);
757 self.kind.hash(state);
758 }
759}
760
761#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
763pub enum TextSegmentKind {
764 LeftToRight,
766 RightToLeft,
768 ArabicLetter,
770
771 EuropeanNumber,
773 EuropeanSeparator,
775 EuropeanTerminator,
777 ArabicNumber,
779 CommonSeparator,
781 NonSpacingMark,
783 BoundaryNeutral,
785
786 Emoji,
788
789 LineBreak,
791 Tab,
793 Space,
795 OtherNeutral,
797 Bracket(char),
801
802 BidiCtrl(char),
817}
818impl TextSegmentKind {
819 pub fn is_word(self) -> bool {
821 use TextSegmentKind::*;
822 matches!(
823 self,
824 LeftToRight
825 | RightToLeft
826 | ArabicLetter
827 | EuropeanNumber
828 | EuropeanSeparator
829 | EuropeanTerminator
830 | ArabicNumber
831 | CommonSeparator
832 | NonSpacingMark
833 | BoundaryNeutral
834 | OtherNeutral
835 | Bracket(_)
836 | Emoji
837 )
838 }
839
840 pub fn is_space(self) -> bool {
842 matches!(self, Self::Space | Self::Tab)
843 }
844
845 pub fn is_line_break(self) -> bool {
849 matches!(self, Self::LineBreak)
850 }
851
852 pub fn can_merge(self) -> bool {
854 use TextSegmentKind::*;
855 !matches!(self, Bracket(_) | BidiCtrl(_))
856 }
857
858 pub fn bracket_info(self) -> Option<unicode_bidi::data_source::BidiMatchedOpeningBracket> {
860 if let TextSegmentKind::Bracket(c) = self {
861 unicode_bidi::HardcodedBidiData.bidi_matched_opening_bracket(c)
862 } else {
863 None
864 }
865 }
866
867 pub fn strong_direction(self) -> Option<LayoutDirection> {
871 use TextSegmentKind::*;
872
873 match self {
874 LeftToRight => Some(LayoutDirection::LTR),
875 RightToLeft | ArabicLetter => Some(LayoutDirection::RTL),
876 BidiCtrl(_) => {
877 use unicode_bidi::BidiClass::*;
878 match unicode_bidi::BidiClass::from(self) {
879 LRE | LRO | LRI => Some(LayoutDirection::LTR),
880 RLE | RLO | RLI => Some(LayoutDirection::RTL),
881 _ => None,
882 }
883 }
884 _ => None,
885 }
886 }
887}
888impl From<char> for TextSegmentKind {
889 fn from(c: char) -> Self {
890 use unicode_bidi::*;
891
892 unicode_bidi::HardcodedBidiData.bidi_class(c).into()
893 }
894}
895
896impl From<unicode_bidi::BidiClass> for TextSegmentKind {
897 fn from(value: unicode_bidi::BidiClass) -> Self {
898 use TextSegmentKind::*;
899 use unicode_bidi::BidiClass::*;
900
901 match value {
902 WS => Space,
903 L => LeftToRight,
904 R => RightToLeft,
905 AL => ArabicLetter,
906 AN => ArabicNumber,
907 CS => CommonSeparator,
908 B => LineBreak,
909 EN => EuropeanNumber,
910 ES => EuropeanSeparator,
911 ET => EuropeanTerminator,
912 S => Tab,
913 ON => OtherNeutral,
914 BN => BoundaryNeutral,
915 NSM => NonSpacingMark,
916 RLE => BidiCtrl('\u{202B}'),
917 LRI => BidiCtrl('\u{2066}'),
918 RLI => BidiCtrl('\u{2067}'),
919 LRO => BidiCtrl('\u{202D}'),
920 FSI => BidiCtrl('\u{2068}'),
921 PDF => BidiCtrl('\u{202C}'),
922 LRE => BidiCtrl('\u{202A}'),
923 PDI => BidiCtrl('\u{2069}'),
924 RLO => BidiCtrl('\u{202E}'),
925 }
926 }
927}
928impl From<TextSegmentKind> for unicode_bidi::BidiClass {
929 fn from(value: TextSegmentKind) -> Self {
930 use TextSegmentKind::*;
931 use unicode_bidi::BidiClass::*;
932
933 match value {
934 Space => WS,
935 LeftToRight => L,
936 RightToLeft => R,
937 ArabicLetter => AL,
938 ArabicNumber => AN,
939 CommonSeparator => CS,
940 LineBreak => B,
941 EuropeanNumber => EN,
942 EuropeanSeparator => ES,
943 EuropeanTerminator => ET,
944 Tab => S,
945 OtherNeutral | Emoji | Bracket(_) => ON,
946 BoundaryNeutral => BN,
947 NonSpacingMark => NSM,
948 BidiCtrl(c) => match c {
949 '\u{202A}' => LRE,
950 '\u{202D}' => LRO,
951 '\u{202B}' => RLE,
952 '\u{202E}' => RLO,
953 '\u{202C}' => PDF,
954 '\u{2066}' => LRI,
955 '\u{2067}' => RLI,
956 '\u{2068}' => FSI,
957 '\u{2069}' => PDI,
958 _c => {
959 #[cfg(debug_assertions)]
960 {
961 tracing::error!("invalid bidi ctrl char '{_c}'");
962 }
963 ON
964 }
965 },
966 }
967 }
968}
969
970bitflags! {
971 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, bytemuck::NoUninit)]
973 #[repr(transparent)]
974 pub struct LayoutMask: u32 {
975 const DEFAULT_VALUE = 1 << 31;
977 const CONSTRAINTS = 1 << 30;
979
980 const FONT_SIZE = 1;
982 const ROOT_FONT_SIZE = 1 << 1;
984 const SCALE_FACTOR = 1 << 2;
986 const VIEWPORT = 1 << 3;
988 const SCREEN_PPI = 1 << 4;
990 const DIRECTION = 1 << 5;
992 const LEFTOVER = 1 << 6;
994 }
995}
996impl Default for LayoutMask {
997 fn default() -> Self {
999 LayoutMask::empty()
1000 }
1001}