1pub(crate) mod error;
2pub mod gl;
3mod mat4;
4mod position;
5mod url;
6
7pub use ::beamterm_data::{
8 CellSize, DebugSpacePattern, FontAtlasData, GlyphEffect, SerializationError, TerminalSize,
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};
17#[cfg(feature = "native-dynamic-atlas")]
18pub use gl::{NativeDynamicAtlas, NativeGlyphRasterizer};
19pub use position::CursorPosition;
20use unicode_width::UnicodeWidthStr;
21pub use url::{UrlMatch, find_url_at_cursor};
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[non_exhaustive]
26pub enum GlslVersion {
27 Es300,
29 Gl330,
31}
32
33impl GlslVersion {
34 pub fn vertex_preamble(&self) -> &'static str {
35 match self {
36 Self::Es300 => "#version 300 es\nprecision highp float;\n",
37 Self::Gl330 => "#version 330 core\n",
38 }
39 }
40
41 pub fn fragment_preamble(&self) -> &'static str {
42 match self {
43 Self::Es300 => "#version 300 es\nprecision mediump float;\nprecision highp int;\n",
44 Self::Gl330 => "#version 330 core\n",
45 }
46 }
47}
48
49pub fn is_emoji(s: &str) -> bool {
56 let bytes = s.as_bytes();
57 let first_byte = match bytes.first() {
58 Some(&b) => b,
59 None => return false,
60 };
61
62 if first_byte < 0x80 {
65 return s.len() > 1 && s.width() >= 2;
66 }
67
68 if first_byte < 0xE0 {
70 return s.len() > 2 && s.width() >= 2;
71 }
72
73 let first = unsafe { s.chars().next().unwrap_unchecked() };
76 let first_len = first.len_utf8();
77
78 if s.len() == first_len {
80 return if first_len == 3 {
83 is_emoji_presentation(first)
84 } else {
85 s.width() >= 2 && is_emoji_presentation(first)
86 };
87 }
88
89 s.width() >= 2
91}
92
93pub fn is_double_width(grapheme: &str) -> bool {
95 grapheme.width() >= 2
96}
97
98fn is_emoji_presentation(c: char) -> bool {
106 let cp = c as u32;
107
108 match cp {
109 0x231A..=0x2B55 => matches!(
111 cp,
112 0x231A..=0x231B | 0x23E9..=0x23EC | 0x23F0 | 0x23F3 | 0x25FD..=0x25FE | 0x2614..=0x2615 | 0x2648..=0x2653 | 0x267F | 0x2693 | 0x26A1 | 0x26AA..=0x26AB | 0x26BD..=0x26BE | 0x26C4..=0x26C5 | 0x26CE | 0x26D4 | 0x26EA | 0x26F2..=0x26F3 | 0x26F5 | 0x26FA | 0x26FD | 0x2705 | 0x270A..=0x270B | 0x2728 | 0x274C | 0x274E | 0x2753..=0x2755 | 0x2757 | 0x2795..=0x2797 | 0x27B0 | 0x27BF | 0x2B1B..=0x2B1C | 0x2B50 | 0x2B55 ),
146 0x1F000..=0x1FFFF => !matches!(
149 cp,
150 0x1F200
151 | 0x1F202..=0x1F219
152 | 0x1F21B..=0x1F22E
153 | 0x1F230..=0x1F231
154 | 0x1F237
155 | 0x1F23B..=0x1F24F
156 | 0x1F260..=0x1F265
157 ),
158 _ => false,
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_is_emoji() {
168 assert!(is_emoji("\u{1F680}"));
170 assert!(is_emoji("\u{1F600}"));
171 assert!(is_emoji("\u{23E9}"));
172 assert!(is_emoji("\u{23EA}"));
173
174 assert!(is_emoji("\u{25B6}\u{FE0F}"));
176
177 assert!(!is_emoji("\u{25B6}"));
179 assert!(!is_emoji("\u{25C0}"));
180 assert!(!is_emoji("\u{23ED}"));
181 assert!(!is_emoji("\u{23F9}"));
182 assert!(!is_emoji("\u{23EE}"));
183 assert!(!is_emoji("\u{25AA}"));
184 assert!(!is_emoji("\u{25AB}"));
185 assert!(!is_emoji("\u{25FC}"));
186
187 assert!(!is_emoji("A"));
189 assert!(!is_emoji("\u{2588}"));
190 }
191
192 #[test]
193 fn test_is_double_width() {
194 assert!(is_double_width("\u{1F600}"));
196 assert!(is_double_width(
197 "\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}"
198 )); [
201 "\u{231A}", "\u{231B}", "\u{23E9}", "\u{23F3}", "\u{2614}", "\u{2615}", "\u{2648}",
202 "\u{2653}", "\u{267F}", "\u{2693}", "\u{26A1}", "\u{26AA}", "\u{26AB}", "\u{26BD}",
203 "\u{26BE}", "\u{26C4}", "\u{26C5}", "\u{26CE}", "\u{26D4}", "\u{26EA}", "\u{26F2}",
204 "\u{26F3}", "\u{26F5}", "\u{26FA}", "\u{26FD}", "\u{25FE}", "\u{2B1B}", "\u{2B1C}",
205 "\u{2B50}", "\u{2B55}", "\u{3030}", "\u{303D}", "\u{3297}", "\u{3299}",
206 ]
207 .iter()
208 .for_each(|s| {
209 assert!(is_double_width(s), "Failed for emoji: {s}");
210 });
211
212 assert!(is_double_width("\u{25B6}\u{FE0F}"));
214 assert!(is_double_width("\u{25C0}\u{FE0F}"));
215
216 assert!(!is_double_width("\u{23F8}"));
218 assert!(!is_double_width("\u{23FA}"));
219 assert!(!is_double_width("\u{25AA}"));
220 assert!(!is_double_width("\u{25AB}"));
221 assert!(!is_double_width("\u{25B6}"));
222 assert!(!is_double_width("\u{25C0}"));
223 assert!(!is_double_width("\u{25FB}"));
224 assert!(!is_double_width("\u{2934}"));
225 assert!(!is_double_width("\u{2935}"));
226 assert!(!is_double_width("\u{2B05}"));
227 assert!(!is_double_width("\u{2B07}"));
228 assert!(!is_double_width("\u{26C8}"));
229
230 assert!(is_double_width("\u{4E2D}"));
232 assert!(is_double_width("\u{65E5}"));
233
234 assert!(!is_double_width("A"));
236 assert!(!is_double_width("\u{2192}"));
237 }
238
239 #[test]
240 fn test_font_atlas_config_deserialization() {
241 let _ = FontAtlasData::default();
242 }
243}