1use std::sync::RwLock;
10
11use crate::*;
12
13#[derive(Clone, Debug)]
14#[repr(C)]
15pub struct TextIns {
16 pub font_t1: Vec2,
18 pub font_t2: Vec2,
20 pub color: Vec4,
22 pub rect_pos: Vec2,
24 pub rect_size: Vec2,
26 pub char_depth: f32,
28 pub base: Vec2,
30 pub font_size: f32,
32 pub char_offset: f32,
34 pub marker: f32,
36}
37
38#[repr(C)]
39struct TextInsUniforms {
40 brightness: f32,
41 curve: f32,
42}
43
44pub static TEXT_INS_SHADER: Shader = Shader {
45 build_geom: Some(QuadIns::build_geom),
46 code_to_concatenate: &[
47 Cx::STD_SHADER,
48 code_fragment!(
49 r#"
50 uniform brightness: float;
51 uniform curve: float;
52
53 texture texture: texture2D;
54
55 instance font_t1: vec2;
56 instance font_t2: vec2;
57 instance color: vec4;
58 instance rect_pos: vec2;
59 instance rect_size: vec2;
60 instance char_depth: float;
61 instance base: vec2;
62 instance font_size: float;
63 instance char_offset: float;
64 instance marker: float;
65
66 geometry geom: vec2;
67
68 varying tex_coord1: vec2;
69 varying tex_coord2: vec2;
70 varying tex_coord3: vec2;
71 varying clipped: vec2;
72
73 fn get_color() -> vec4 {
74 return color;
75 }
76
77 fn pixel() -> vec4 {
78 let dx = dFdx(vec2(tex_coord1.x * 2048.0, 0.)).x;
79 let dp = 1.0 / 2048.0;
80
81 // basic hardcoded mipmapping so it stops 'swimming' in VR
82 // mipmaps are stored in red/green/blue channel
83 let s = 1.0;
84
85 if dx > 7.0 {
86 s = 0.7;
87 }
88 else if dx > 2.75 {
89 s = (
90 sample2d(texture, tex_coord3.xy + vec2(0., 0.)).z
91 + sample2d(texture, tex_coord3.xy + vec2(dp, 0.)).z
92 + sample2d(texture, tex_coord3.xy + vec2(0., dp)).z
93 + sample2d(texture, tex_coord3.xy + vec2(dp, dp)).z
94 ) * 0.25;
95 }
96 else if dx > 1.75 {
97 s = sample2d(texture, tex_coord3.xy).z;
98 }
99 else if dx > 1.3 {
100 s = sample2d(texture, tex_coord2.xy).y;
101 }
102 else {
103 s = sample2d(texture, tex_coord1.xy).x;
104 }
105
106 s = pow(s, curve);
107 let col = get_color(); //color!(white);//get_color();
108 return vec4(s * col.rgb * brightness * col.a, s * col.a);
109 }
110
111 fn vertex() -> vec4 {
112 let min_pos = vec2(rect_pos.x, rect_pos.y);
113 let max_pos = vec2(rect_pos.x + rect_size.x, rect_pos.y - rect_size.y);
114
115 clipped = clamp(
116 mix(min_pos, max_pos, geom) - draw_scroll,
117 draw_clip.xy,
118 draw_clip.zw
119 );
120
121 let normalized: vec2 = (clipped - min_pos + draw_scroll) / vec2(rect_size.x, -rect_size.y);
122 //rect = vec4(min_pos.x, min_pos.y, max_pos.x, max_pos.y) - draw_scroll.xyxy;
123
124 tex_coord1 = mix(
125 font_t1.xy,
126 font_t2.xy,
127 normalized.xy
128 );
129
130 tex_coord2 = mix(
131 font_t1.xy,
132 font_t1.xy + (font_t2.xy - font_t1.xy) * 0.75,
133 normalized.xy
134 );
135
136 tex_coord3 = mix(
137 font_t1.xy,
138 font_t1.xy + (font_t2.xy - font_t1.xy) * 0.6,
139 normalized.xy
140 );
141
142 return camera_projection * (camera_view * vec4(
143 clipped.x,
144 clipped.y,
145 char_depth + draw_zbias,
146 1.
147 ));
148 }"#
149 ),
150 ],
151 ..Shader::DEFAULT
152};
153
154pub const TEXT_ANCHOR_LEFT: Vec2 = vec2(0., 0.);
159pub const TEXT_ANCHOR_CENTER_H: Vec2 = vec2(0.5, 0.);
160pub const TEXT_ANCHOR_RIGHT: Vec2 = vec2(1., 0.);
161pub const TEXT_ANCHOR_TOP: Vec2 = vec2(0., 0.);
162pub const TEXT_ANCHOR_CENTER_V: Vec2 = vec2(0., 0.5);
163pub const TEXT_ANCHOR_BOTTOM: Vec2 = vec2(0., 1.);
164
165#[derive(Debug)]
167pub struct TextInsProps {
168 pub text_style: TextStyle,
170 pub wrapping: Wrapping,
172 pub font_scale: f32,
174 pub draw_depth: f32,
176 pub color: Vec4,
178 pub position_anchoring: Vec2,
180 pub padding: Padding,
182}
183impl TextInsProps {
184 pub const DEFAULT: TextInsProps = TextInsProps {
187 text_style: TEXT_STYLE_NORMAL,
188 wrapping: Wrapping::DEFAULT,
189 font_scale: 1.0,
190 draw_depth: 0.0,
191 color: COLOR_WHITE,
192 position_anchoring: vec2(0., 0.),
193 padding: Padding::DEFAULT,
194 };
195}
196impl Default for TextInsProps {
197 fn default() -> Self {
198 TextInsProps::DEFAULT
199 }
200}
201
202#[derive(Copy, Clone, Debug)]
205pub enum Wrapping {
206 None,
207 Char,
208 Word,
209 Line,
214 Ellipsis(f32),
215}
216impl Wrapping {
217 pub const DEFAULT: Wrapping = Wrapping::None;
220}
221impl Default for Wrapping {
222 fn default() -> Self {
223 Wrapping::DEFAULT
224 }
225}
226
227#[derive(Default)]
228pub struct DrawGlyphsProps {
229 pub text_style: TextStyle,
230 pub position_anchoring: Vec2,
231}
232
233impl TextIns {
234 pub fn generate_2d_glyphs<F>(
235 text_style: &TextStyle,
236 fonts_data: &RwLock<CxFontsData>,
237 dpi_factor: f32,
238 font_scale: f32,
239 draw_depth: f32,
240 color: Vec4,
241 pos: Vec2,
242 char_offset: usize,
243 chunk: &[char],
244 mut char_callback: F,
245 ) -> Vec<TextIns>
246 where
247 F: FnMut(char, usize, f32, f32) -> f32,
248 {
249 let mut ret = Vec::with_capacity(chunk.len());
250
251 let font_id = text_style.font.font_id;
252
253 let (atlas_page_id, mut read_lock) = get_font_atlas_page_id(fonts_data, font_id, dpi_factor, text_style.font_size);
254
255 let font_size_logical =
256 text_style.font_size * 96.0 / (72.0 * read_lock.fonts[font_id].font_loaded.as_ref().unwrap().units_per_em);
257 let font_size_pixels = font_size_logical * dpi_factor;
258
259 let mut x = pos.x;
260 let mut char_offset = char_offset;
261
262 for wc in chunk {
263 let unicode = *wc as usize;
264
265 let (glyph_id, advance, w, h, min_pos_x, subpixel_x_fract, subpixel_y_fract, scaled_min_pos_x, scaled_min_pos_y) = {
267 let cxfont = read_lock.fonts[font_id].font_loaded.as_ref().unwrap();
268 let glyph_id = cxfont.char_code_to_glyph_index_map[unicode];
269 if glyph_id >= cxfont.glyphs.len() {
270 println!("GLYPHID OUT OF BOUNDS {} {} len is {}", unicode, glyph_id, cxfont.glyphs.len());
271 continue;
272 }
273
274 let glyph = &cxfont.glyphs[glyph_id];
275
276 let advance = glyph.horizontal_metrics.advance_width * font_size_logical * font_scale;
277
278 let w = ((glyph.bounds.p_max.x - glyph.bounds.p_min.x) * font_size_pixels).ceil() + 1.0;
280 let h = ((glyph.bounds.p_max.y - glyph.bounds.p_min.y) * font_size_pixels).ceil() + 1.0;
281
282 let min_pos_x = x + font_size_logical * glyph.bounds.p_min.x;
284 let min_pos_y = pos.y - font_size_logical * glyph.bounds.p_min.y + text_style.font_size * text_style.top_drop;
285
286 let subpixel_x_fract = min_pos_x - (min_pos_x * dpi_factor).floor() / dpi_factor;
288 let subpixel_y_fract = min_pos_y - (min_pos_y * dpi_factor).floor() / dpi_factor;
289
290 let scaled_min_pos_x = x + font_size_logical * font_scale * glyph.bounds.p_min.x - subpixel_x_fract;
292 let scaled_min_pos_y = pos.y - font_size_logical * font_scale * glyph.bounds.p_min.y
293 + text_style.font_size * font_scale * text_style.top_drop
294 - subpixel_y_fract;
295
296 (glyph_id, advance, w, h, min_pos_x, subpixel_x_fract, subpixel_y_fract, scaled_min_pos_x, scaled_min_pos_y)
297 };
298
299 let subpixel_id = if text_style.font_size > 32.0 {
301 0
302 } else {
303 ((subpixel_y_fract * 7.0) as usize) << 3 | (subpixel_x_fract * 7.0) as usize
305 };
306
307 let tc = if let Some(tc) = read_lock.fonts[font_id].atlas_pages[atlas_page_id].atlas_glyphs[glyph_id][subpixel_id] {
308 tc
309 } else {
310 drop(read_lock);
312 {
313 let mut write_fonts_data = fonts_data.write().unwrap();
314
315 write_fonts_data.fonts_atlas.atlas_todo.push(CxFontsAtlasTodo {
316 subpixel_x_fract,
317 subpixel_y_fract,
318 font_id,
319 atlas_page_id,
320 glyph_id,
321 subpixel_id,
322 });
323
324 let new_glyph = write_fonts_data.fonts_atlas.alloc_atlas_glyph(w, h);
325 write_fonts_data.fonts[font_id].atlas_pages[atlas_page_id].atlas_glyphs[glyph_id][subpixel_id] =
326 Some(new_glyph);
327 }
328 read_lock = fonts_data.read().unwrap();
329 read_lock.fonts[font_id].atlas_pages[atlas_page_id].atlas_glyphs[glyph_id][subpixel_id].unwrap()
330 };
331
332 ret.push(TextIns {
333 font_t1: vec2(tc.tx1, tc.ty1),
334 font_t2: vec2(tc.tx2, tc.ty2),
335 color,
336 rect_pos: vec2(scaled_min_pos_x, scaled_min_pos_y),
337 rect_size: vec2(w * font_scale / dpi_factor, h * font_scale / dpi_factor),
338 char_depth: draw_depth + 0.00001 * min_pos_x,
339 base: vec2(x, pos.y),
340 font_size: text_style.font_size,
341 char_offset: char_offset as f32,
342
343 marker: char_callback(*wc, char_offset, x, advance),
345 });
346
347 x += advance;
348 char_offset += 1;
349 }
350
351 ret
352 }
353
354 pub fn set_color(cx: &mut Cx, area: Area, color: Vec4) {
355 let glyphs = area.get_slice_mut::<TextIns>(cx);
356 for glyph in glyphs {
357 glyph.color = color;
358 }
359 }
360
361 fn write_uniforms(cx: &mut Cx, area: &Area, text_style: &TextStyle) {
362 if area.is_first_instance() {
363 let texture_handle = cx.fonts_data.read().unwrap().get_fonts_atlas_texture_handle();
364 area.write_texture_2d(cx, "texture", texture_handle);
365 area.write_user_uniforms(cx, TextInsUniforms { brightness: text_style.brightness, curve: text_style.curve });
366 }
367 }
368
369 pub fn draw_glyphs(cx: &mut Cx, glyphs: &[TextIns], props: &DrawGlyphsProps) -> Area {
370 let area = if props.position_anchoring != vec2(0., 0.) {
371 let horizontal_offset = glyphs.iter().map(|g| g.rect_size.x).sum();
373 let vertical_offset = {
374 let text_style = TextInsProps::DEFAULT.text_style;
376 text_style.font_size * text_style.top_drop
377 };
378 let offset = vec2(horizontal_offset, vertical_offset);
379 let anchor_offset = offset * props.position_anchoring;
380
381 let moved_glyphs: Vec<TextIns> = glyphs
382 .iter()
383 .map(|g| {
384 let mut g = g.clone();
385 g.rect_pos -= anchor_offset; g
387 })
388 .collect();
389 cx.add_instances(&TEXT_INS_SHADER, &moved_glyphs)
390 } else {
391 cx.add_instances(&TEXT_INS_SHADER, glyphs)
392 };
393 Self::write_uniforms(cx, &area, &props.text_style);
394 area
395 }
396
397 pub fn draw_glyphs_with_scroll_sticky(
398 cx: &mut Cx,
399 glyphs: &[TextIns],
400 text_style: &TextStyle,
401 horizontal: bool,
402 vertical: bool,
403 ) -> Area {
404 let area = cx.add_instances_with_scroll_sticky(&TEXT_INS_SHADER, glyphs, horizontal, vertical);
405 Self::write_uniforms(cx, &area, text_style);
406 area
407 }
408
409 pub fn draw_str(cx: &mut Cx, text: &str, pos: Vec2, props: &TextInsProps) -> Area {
410 let glyphs = Self::generate_2d_glyphs(
411 &props.text_style,
412 &cx.fonts_data,
413 cx.current_dpi_factor,
414 props.font_scale,
415 props.draw_depth,
416 props.color,
417 pos,
418 0,
419 &text.chars().collect::<Vec<char>>(),
420 |_, _, _, _| 0.0,
421 );
422
423 Self::draw_glyphs(
424 cx,
425 &glyphs,
426 &DrawGlyphsProps { text_style: props.text_style, position_anchoring: props.position_anchoring },
427 )
428 }
429
430 pub fn draw_walk(cx: &mut Cx, text: &str, props: &TextInsProps) -> Area {
435 let mut width = 0.0;
436 let mut elipct = 0;
437
438 let text_style = &props.text_style;
439 let font_size = text_style.font_size;
440 let line_spacing = text_style.line_spacing;
441 let height_factor = text_style.height_factor;
442 let mut iter = text.chars().peekable();
443
444 let font_id = text_style.font.font_id;
445 let font_size_logical = text_style.font_size * 96.0
446 / (72.0 * cx.fonts_data.read().unwrap().fonts[font_id].font_loaded.as_ref().unwrap().units_per_em);
447
448 let mut buf = Vec::with_capacity(text.len());
449 let mut glyphs: Vec<TextIns> = Vec::with_capacity(text.len());
450
451 cx.begin_row(Width::Compute, Height::Compute);
452 cx.begin_padding_box(props.padding);
453 cx.begin_wrapping_box();
454
455 while let Some(c) = iter.next() {
456 let last = iter.peek().is_none();
457
458 let mut emit = last;
459 let mut newline = false;
460 let slot = if c < '\u{10000}' {
461 cx.fonts_data.read().unwrap().fonts[font_id].font_loaded.as_ref().unwrap().char_code_to_glyph_index_map
462 [c as usize]
463 } else {
464 0
465 };
466 if c == '\n' {
467 emit = true;
468 newline = true;
469 }
470 if slot != 0 {
471 let read_fonts = &cx.fonts_data.read().unwrap().fonts;
472 let glyph = &read_fonts[font_id].font_loaded.as_ref().unwrap().glyphs[slot];
473 width += glyph.horizontal_metrics.advance_width * font_size_logical * props.font_scale;
474 match props.wrapping {
475 Wrapping::Char => {
476 buf.push(c);
477 emit = true
478 }
479 Wrapping::Word => {
480 buf.push(c);
481 if c == ' ' || c == '\t' || c == ',' || c == '\n' {
482 emit = true;
483 }
484 }
485 Wrapping::Line => {
486 buf.push(c);
487 if c == 10 as char || c == 13 as char {
488 emit = true;
489 }
490 newline = true;
491 }
492 Wrapping::None => {
493 buf.push(c);
494 }
495 Wrapping::Ellipsis(ellipsis_width) => {
496 if width > ellipsis_width {
497 if elipct < 3 {
499 buf.push('.');
500 elipct += 1;
501 }
502 } else {
503 buf.push(c)
504 }
505 }
506 }
507 }
508 if emit {
509 let height = font_size * height_factor * props.font_scale;
510 let rect = cx.add_box(LayoutSize { width: Width::Fix(width), height: Height::Fix(height) });
511
512 if !rect.pos.x.is_nan() && !rect.pos.y.is_nan() {
513 glyphs.extend(Self::generate_2d_glyphs(
514 &props.text_style,
515 &cx.fonts_data,
516 cx.current_dpi_factor,
517 props.font_scale,
518 props.draw_depth,
519 props.color,
520 rect.pos,
521 0,
522 &buf,
523 |_, _, _, _| 0.0,
524 ));
525 }
526
527 width = 0.0;
528 buf.truncate(0);
529 if newline {
530 cx.draw_new_line_min_height(font_size * line_spacing * props.font_scale);
531 }
532 }
533 }
534
535 cx.end_wrapping_box();
536 cx.end_padding_box();
537 cx.end_row();
538
539 Self::draw_glyphs(
540 cx,
541 &glyphs,
542 &DrawGlyphsProps {
543 text_style: *text_style,
544 ..DrawGlyphsProps::default()
546 },
547 )
548 }
549
550 pub fn closest_offset(cx: &Cx, area: &Area, pos: Vec2, line_spacing: f32) -> Option<usize> {
552 if let Area::InstanceRange(instance) = area {
553 if instance.instance_count == 0 {
554 return None;
555 }
556 }
557
558 let scroll_pos = area.get_scroll_pos(cx);
559 let spos = Vec2 { x: pos.x + scroll_pos.x, y: pos.y + scroll_pos.y };
560
561 let glyphs = area.get_slice::<TextIns>(cx);
562 let mut i = 0;
563 let len = glyphs.len();
564 while i < len {
565 let glyph = &glyphs[i];
566 if glyph.base.y + glyph.font_size * line_spacing > spos.y {
567 while i < len {
569 let glyph = &glyphs[i];
570 let width = glyph.rect_size.x;
571 if glyph.base.x > spos.x + width * 0.5 || glyph.base.y > spos.y {
572 let prev_glyph = &glyphs[if i == 0 { 0 } else { i - 1 }];
573 let prev_width = prev_glyph.rect_size.x;
574 if i < len - 1 && prev_glyph.base.x > spos.x + prev_width {
575 return Some(glyph.char_offset as usize);
577 }
578 return Some(prev_glyph.char_offset as usize);
579 }
580 i += 1;
581 }
582 }
583 i += 1;
584 }
585 Some(glyphs[len - 1].char_offset as usize)
586 }
587
588 pub fn get_monospace_base(cx: &Cx, text_style: &TextStyle) -> Vec2 {
589 let font_id = text_style.font.font_id;
590 let read_fonts = &cx.fonts_data.read().unwrap().fonts;
591 let font = read_fonts[font_id].font_loaded.as_ref().unwrap();
592 let slot = font.char_code_to_glyph_index_map[33];
593 let glyph = &font.glyphs[slot];
594
595 Vec2 { x: glyph.horizontal_metrics.advance_width * (96.0 / (72.0 * font.units_per_em)), y: text_style.line_spacing }
597 }
598}