1pub(crate) mod error;
2pub mod gl;
3mod mat4;
4mod position;
5mod url;
6
7pub use ::beamterm_data::{
8 DebugSpacePattern, FontAtlasData, FontAtlasDeserializationError, GlyphEffect,
9};
10pub use beamterm_data::FontStyle;
11pub use error::Error;
12pub use gl::{
13 Atlas, CellData, CellDynamic, CellIterator, CellQuery, Drawable, FontAtlas, GlState, GlyphSlot,
14 GlyphTracker, RenderContext, SelectionMode, SelectionTracker, StaticFontAtlas, TerminalGrid,
15 select,
16};
17pub use position::CursorPosition;
18use unicode_width::UnicodeWidthStr;
19pub use url::{UrlMatch, find_url_at_cursor};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum GlslVersion {
25 Es300,
27 Gl330,
29}
30
31impl GlslVersion {
32 pub fn vertex_preamble(&self) -> &'static str {
33 match self {
34 Self::Es300 => "#version 300 es\nprecision highp float;\n",
35 Self::Gl330 => "#version 330 core\n",
36 }
37 }
38
39 pub fn fragment_preamble(&self) -> &'static str {
40 match self {
41 Self::Es300 => "#version 300 es\nprecision mediump float;\nprecision highp int;\n",
42 Self::Gl330 => "#version 330 core\n",
43 }
44 }
45}
46
47pub fn is_emoji(s: &str) -> bool {
54 match emojis::get(s) {
55 Some(emoji) => {
56 if emoji.as_str().contains('\u{FE0F}') { s.contains('\u{FE0F}') } else { true }
60 },
61 None => false,
62 }
63}
64
65pub fn is_double_width(grapheme: &str) -> bool {
67 grapheme.len() > 1 && (is_emoji(grapheme) || grapheme.width() == 2)
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_is_emoji() {
76 assert!(is_emoji("\u{1F680}"));
78 assert!(is_emoji("\u{1F600}"));
79 assert!(is_emoji("\u{23E9}"));
80 assert!(is_emoji("\u{23EA}"));
81
82 assert!(is_emoji("\u{25B6}\u{FE0F}"));
84
85 assert!(!is_emoji("\u{25B6}"));
87 assert!(!is_emoji("\u{25C0}"));
88 assert!(!is_emoji("\u{23ED}"));
89 assert!(!is_emoji("\u{23F9}"));
90 assert!(!is_emoji("\u{23EE}"));
91 assert!(!is_emoji("\u{25AA}"));
92 assert!(!is_emoji("\u{25AB}"));
93 assert!(!is_emoji("\u{25FC}"));
94
95 assert!(!is_emoji("A"));
97 assert!(!is_emoji("\u{2588}"));
98 }
99
100 #[test]
101 fn test_is_double_width() {
102 assert!(is_double_width("\u{1F600}"));
104 assert!(is_double_width(
105 "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}"
106 )); [
109 "\u{231A}", "\u{231B}", "\u{23E9}", "\u{23F3}", "\u{2614}", "\u{2615}", "\u{2648}",
110 "\u{2653}", "\u{267F}", "\u{2693}", "\u{26A1}", "\u{26AA}", "\u{26AB}", "\u{26BD}",
111 "\u{26BE}", "\u{26C4}", "\u{26C5}", "\u{26CE}", "\u{26D4}", "\u{26EA}", "\u{26F2}",
112 "\u{26F3}", "\u{26F5}", "\u{26FA}", "\u{26FD}", "\u{25FE}", "\u{2B1B}", "\u{2B1C}",
113 "\u{2B50}", "\u{2B55}", "\u{3030}", "\u{303D}", "\u{3297}", "\u{3299}",
114 ]
115 .iter()
116 .for_each(|s| {
117 assert!(is_double_width(s), "Failed for emoji: {s}");
118 });
119
120 assert!(is_double_width("\u{25B6}\u{FE0F}"));
122 assert!(is_double_width("\u{25C0}\u{FE0F}"));
123
124 assert!(!is_double_width("\u{23F8}"));
126 assert!(!is_double_width("\u{23FA}"));
127 assert!(!is_double_width("\u{25AA}"));
128 assert!(!is_double_width("\u{25AB}"));
129 assert!(!is_double_width("\u{25B6}"));
130 assert!(!is_double_width("\u{25C0}"));
131 assert!(!is_double_width("\u{25FB}"));
132 assert!(!is_double_width("\u{2934}"));
133 assert!(!is_double_width("\u{2935}"));
134 assert!(!is_double_width("\u{2B05}"));
135 assert!(!is_double_width("\u{2B07}"));
136 assert!(!is_double_width("\u{26C8}"));
137
138 assert!(is_double_width("\u{4E2D}"));
140 assert!(is_double_width("\u{65E5}"));
141
142 assert!(!is_double_width("A"));
144 assert!(!is_double_width("\u{2192}"));
145 }
146
147 #[test]
148 fn test_font_atlas_config_deserialization() {
149 let _ = FontAtlasData::default();
150 }
151}