1
2use crate::cx::*;
3
4#[derive(Clone)]
5pub enum Wrapping {
6 Char,
7 Word,
8 Line,
9 None,
10 Ellipsis(f32)
11}
12
13#[derive(Clone, Copy)]
14pub struct TextStyle {
15 pub font: Font,
16 pub font_size: f32,
17 pub brightness: f32,
18 pub curve: f32,
19 pub line_spacing: f32,
20 pub top_drop: f32,
21 pub height_factor: f32,
22}
23
24impl Default for TextStyle {
25 fn default() -> Self {
26 TextStyle {
27 font: Font::default(),
28 font_size: 8.0,
29 brightness: 1.0,
30 curve: 0.6,
31 line_spacing: 1.4,
32 top_drop: 1.1,
33 height_factor: 1.3,
34 }
35 }
36}
37
38#[derive(Clone)]
39pub struct Text {
40 pub text_style: TextStyle,
41 pub shader: Shader,
42 pub color: Color,
43 pub z: f32,
44 pub wrapping: Wrapping,
45 pub font_scale: f32,
46}
47
48
49impl Text {
50 pub fn new(cx: &mut Cx) -> Self {
51 Self {
52 text_style: TextStyle::default(),
53 shader: cx.add_shader(Self::def_text_shader(), "TextAtlas"),
54 z: 0.0,
55 wrapping: Wrapping::Word,
56 color: color("white"),
57 font_scale: 1.0,
58 }
59 }
60
61 pub fn instance_font_tc() -> InstanceVec4 {uid!()}
62 pub fn instance_color() -> InstanceColor {uid!()}
63 pub fn instance_x() -> InstanceFloat {uid!()}
64 pub fn instance_y() -> InstanceFloat {uid!()}
65 pub fn instance_w() -> InstanceFloat {uid!()}
66 pub fn instance_h() -> InstanceFloat {uid!()}
67 pub fn instance_z() -> InstanceFloat {uid!()}
68 pub fn instance_base_x() -> InstanceFloat {uid!()}
69 pub fn instance_base_y() -> InstanceFloat {uid!()}
70 pub fn instance_font_size() -> InstanceFloat {uid!()}
71 pub fn instance_marker() -> InstanceFloat {uid!()}
72 pub fn instance_char_offset() -> InstanceFloat {uid!()}
73
74 pub fn uniform_brightness() -> UniformFloat {uid!()}
75 pub fn uniform_curve() -> UniformFloat {uid!()}
76
77 pub fn def_text_shader() -> ShaderGen {
78 let mut sg = ShaderGen::new();
80 sg.geometry_vertices = vec![0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0];
81 sg.geometry_indices = vec![0, 1, 2, 0, 3, 2];
82 sg.compose(shader_ast!({
83 let geom: vec2<Geometry>;
84 let texturez: texture2d<Texture>;
85
86 let font_tc: Self::instance_font_tc();
87 let color: Self::instance_color();
88 let x: Self::instance_x();
89 let y: Self::instance_y();
90 let w: Self::instance_w();
91 let h: Self::instance_h();
92 let z: Self::instance_z();
93 let base_x: Self::instance_base_x();
94 let base_y: Self::instance_base_y();
95 let font_size: Self::instance_font_size();
96 let char_offset: Self::instance_char_offset();
97 let marker: Self::instance_marker();
98
99 let tex_coord1: vec2<Varying>;
100 let tex_coord2: vec2<Varying>;
101 let tex_coord3: vec2<Varying>;
102 let clipped: vec2<Varying>;
103 let brightness: Self::uniform_brightness();
106 let curve: Self::uniform_curve();
107
108 fn get_color()->vec4{
109 return color
110 }
111
112 fn pixel() -> vec4 {
113 let dx = dfdx(vec2(tex_coord1.x * 2048.0, 0.)).x;
114 let dp = 1.0 / 2048.0;
115
116 let s = 1.0;
119 if dx > 5.0 {
120 s = 0.7;
121 }
122 else if dx > 2.75 {
123 s = (
124 sample2d(texturez, tex_coord3.xy + vec2(0., 0.)).z
125 + sample2d(texturez, tex_coord3.xy + vec2(dp, 0.)).z
126 + sample2d(texturez, tex_coord3.xy + vec2(0., dp)).z
127 + sample2d(texturez, tex_coord3.xy + vec2(dp, dp)).z
128 ) * 0.25;
129 }
130 else if dx > 1.75 {
131 s = sample2d(texturez, tex_coord3.xy).z;
132 }
133 else if dx > 1.3 {
134 s = sample2d(texturez, tex_coord2.xy).y;
135 }
136 else {
137 s = sample2d(texturez, tex_coord1.xy).x;
138 }
139
140 s = pow(s, curve);
141 let col = get_color();
142 return vec4(s * col.rgb * brightness * col.a, s * col.a); }
144
145 fn vertex() -> vec4 {
146 let min_pos = vec2(x, y);
147 let max_pos = vec2(x + w, y - h);
148
149 clipped = clamp(
150 mix(min_pos, max_pos, geom) - draw_scroll.xy,
151 draw_clip.xy,
152 draw_clip.zw
153 );
154
155 let normalized: vec2 = (clipped - min_pos + draw_scroll.xy) / vec2(w,-h);
156 tex_coord1 = mix(
159 font_tc.xy,
160 font_tc.zw,
161 normalized.xy
162 );
163
164 tex_coord2 = mix(
165 font_tc.xy,
166 font_tc.xy + (font_tc.zw - font_tc.xy) * 0.75,
167 normalized.xy
168 );
169
170 tex_coord3 = mix(
171 font_tc.xy,
172 font_tc.xy + (font_tc.zw - font_tc.xy) * 0.6,
173 normalized.xy
174 );
175
176 return camera_projection * (camera_view * (view_transform * vec4(clipped.x, clipped.y, z + draw_zbias, 1.)));
177 }
178 }))
179 }
180
181 pub fn begin_text(&mut self, cx: &mut Cx) -> AlignedInstance {
182
183 let inst = cx.new_instance(&self.shader, 0);
185 let aligned = cx.align_instance(inst);
186 let text_style = &self.text_style;
187 let brightness = text_style.brightness;
188 let curve = text_style.curve;
189 if aligned.inst.need_uniforms_now(cx) {
190 aligned.inst.push_uniform_texture_2d_id(cx, cx.fonts_atlas.texture_id);
191 aligned.inst.push_uniform_float(cx, brightness);
192 aligned.inst.push_uniform_float(cx, curve);
193 }
194 return aligned
195 }
196
197 pub fn add_text<F>(&mut self, cx: &mut Cx, geom_x: f32, geom_y: f32, char_offset: usize, aligned: &mut AlignedInstance, chunk: &[char], mut char_callback: F)
198 where F: FnMut(char, usize, f32, f32) -> f32
199 {
200 let text_style = &self.text_style;
201 let mut geom_x = geom_x;
202 let mut char_offset = char_offset;
203 let font_id = text_style.font.font_id.unwrap();
204
205 let cxfont = &mut cx.fonts[font_id];
206
207 let dpi_factor = cx.current_dpi_factor;
208
209 let atlas_page_id = cxfont.get_atlas_page_id(dpi_factor, text_style.font_size);
211
212 let font = &mut cxfont.font_loaded.as_ref().unwrap();
213
214 let font_size_logical = text_style.font_size * 96.0 / (72.0 * font.units_per_em);
215 let font_size_pixels = font_size_logical * dpi_factor;
216
217 let atlas_page = &mut cxfont.atlas_pages[atlas_page_id];
218
219 let instance = {
220 let cxview = &mut cx.views[aligned.inst.view_id];
221 let draw_call = &mut cxview.draw_calls[aligned.inst.draw_call_id];
222 &mut draw_call.instance
223 };
224
225 for wc in chunk {
226 let unicode = *wc as usize;
227 let glyph_id = font.char_code_to_glyph_index_map[unicode];
228 if glyph_id >= font.glyphs.len() {
229 println!("GLYPHID OUT OF BOUNDS {} {} len is {}", unicode, glyph_id, font.glyphs.len());
230 continue;
231 }
232 let glyph = &font.glyphs[glyph_id];
233
234 let advance = glyph.horizontal_metrics.advance_width * font_size_logical * self.font_scale;
235
236 let w = ((glyph.bounds.p_max.x - glyph.bounds.p_min.x) * font_size_pixels).ceil() + 1.0;
238 let h = ((glyph.bounds.p_max.y - glyph.bounds.p_min.y) * font_size_pixels).ceil() + 1.0;
239
240 let min_pos_x = geom_x + font_size_logical * glyph.bounds.p_min.x;
242 let min_pos_y = geom_y - font_size_logical * glyph.bounds.p_min.y + text_style.font_size * text_style.top_drop;
243
244 let subpixel_x_fract = min_pos_x - (min_pos_x * dpi_factor).floor() / dpi_factor;
246 let subpixel_y_fract = min_pos_y - (min_pos_y * dpi_factor).floor() / dpi_factor;
247
248 let scaled_min_pos_x = geom_x + font_size_logical * self.font_scale * glyph.bounds.p_min.x - subpixel_x_fract;
250 let scaled_min_pos_y = geom_y - font_size_logical * self.font_scale * glyph.bounds.p_min.y + text_style.font_size * self.font_scale * text_style.top_drop - subpixel_y_fract;
251
252 let subpixel_id = if text_style.font_size>32.0 {
254 0
255 }
256 else { ((subpixel_y_fract * 7.0) as usize) << 3 |
258 (subpixel_x_fract * 7.0) as usize
259 };
260
261 let tc = if let Some(tc) = &atlas_page.atlas_glyphs[glyph_id][subpixel_id] {
262 tc
264 }
265 else {
266 cx.fonts_atlas.atlas_todo.push(CxFontsAtlasTodo {
269 subpixel_x_fract,
270 subpixel_y_fract,
271 font_id,
272 atlas_page_id,
273 glyph_id,
274 subpixel_id
275 });
276
277 atlas_page.atlas_glyphs[glyph_id][subpixel_id] = Some(
278 cx.fonts_atlas.alloc_atlas_glyph(&cxfont.path, w, h)
279 );
280
281 atlas_page.atlas_glyphs[glyph_id][subpixel_id].as_ref().unwrap()
282 };
283
284 let marker = char_callback(*wc, char_offset, geom_x, advance);
286
287 let data = [
288 tc.tx1,
289 tc.ty1,
290 tc.tx2,
291 tc.ty2,
292 self.color.r, self.color.g,
294 self.color.b,
295 self.color.a,
296 scaled_min_pos_x,
297 scaled_min_pos_y,
298 w * self.font_scale / dpi_factor,
299 h * self.font_scale / dpi_factor,
300 self.z + 0.00001 * min_pos_x, geom_x,
302 geom_y,
303 text_style.font_size,
304 char_offset as f32, marker, ];
307 instance.extend_from_slice(&data);
308 geom_x += advance;
311 char_offset += 1;
312 aligned.inst.instance_count += 1;
313 }
314 }
315
316 pub fn end_text(&mut self, cx: &mut Cx, aligned: &AlignedInstance) -> Area {
317 cx.update_aligned_instance_count(aligned);
318 aligned.inst.into()
319 }
320
321 pub fn draw_text(&mut self, cx: &mut Cx, text: &str) -> Area {
322 let mut aligned = self.begin_text(cx);
323
324 let mut chunk = Vec::new();
325 let mut width = 0.0;
326 let mut elipct = 0;
327 let text_style = &self.text_style;
328 let font_size = text_style.font_size;
329 let line_spacing = text_style.line_spacing;
330 let height_factor = text_style.height_factor;
331 let mut iter = text.chars().peekable();
332
333 let font_id = text_style.font.font_id.unwrap();
334 let font_size_logical = text_style.font_size * 96.0 / (72.0 * cx.fonts[font_id].font_loaded.as_ref().unwrap().units_per_em);
335
336 while let Some(c) = iter.next() {
337 let last = iter.peek().is_none();
338
339 let mut emit = last;
340 let mut newline = false;
341 let slot = if c < '\u{10000}' {
342 cx.fonts[font_id].font_loaded.as_ref().unwrap().char_code_to_glyph_index_map[c as usize]
343 } else {
344 0
345 };
346 if c == '\n' {
347 emit = true;
348 newline = true;
349 }
350 if slot != 0 {
351 let glyph = &cx.fonts[font_id].font_loaded.as_ref().unwrap().glyphs[slot];
352 width += glyph.horizontal_metrics.advance_width * font_size_logical * self.font_scale;
353 match self.wrapping {
354 Wrapping::Char => {
355 chunk.push(c);
356 emit = true
357 },
358 Wrapping::Word => {
359 chunk.push(c);
360 if c == ' ' || c == '\t' || c == ',' || c == '\n'{
361 emit = true;
362 }
363 },
364 Wrapping::Line => {
365 chunk.push(c);
366 if c == 10 as char || c == 13 as char {
367 emit = true;
368 }
369 newline = true;
370 },
371 Wrapping::None => {
372 chunk.push(c);
373 },
374 Wrapping::Ellipsis(ellipsis_width) => {
375 if width>ellipsis_width { if elipct < 3 {
377 chunk.push('.');
378 elipct += 1;
379 }
380 }
381 else {
382 chunk.push(c)
383 }
384 }
385 }
386 }
387 if emit {
388 let height = font_size * height_factor * self.font_scale;
389 let geom = cx.walk_turtle(Walk {
390 width: Width::Fix(width),
391 height: Height::Fix(height),
392 margin: Margin::zero()
393 });
394
395 self.add_text(cx, geom.x, geom.y, 0, &mut aligned, &chunk, | _, _, _, _ | {0.0});
396 width = 0.0;
397 chunk.truncate(0);
398 if newline {
399 cx.turtle_new_line_min_height(font_size * line_spacing * self.font_scale);
400 }
401 }
402 }
403 self.end_text(cx, &aligned)
404 }
405
406 pub fn find_closest_offset(&self, cx: &Cx, area: &Area, pos: Vec2) -> usize {
408 let scroll_pos = area.get_scroll_pos(cx);
409 let spos = Vec2 {x: pos.x + scroll_pos.x, y: pos.y + scroll_pos.y};
410 let x_o = area.get_instance_offset(cx, Self::instance_base_x().instance_type()).unwrap();
411 let y_o = area.get_instance_offset(cx, Self::instance_base_y().instance_type()).unwrap();
412 let w_o = area.get_instance_offset(cx, Self::instance_w().instance_type()).unwrap();
413 let font_size_o = area.get_instance_offset(cx, Self::instance_font_size().instance_type()).unwrap();
414 let char_offset_o = area.get_instance_offset(cx, Self::instance_char_offset().instance_type()).unwrap();
415 let read = area.get_read_ref(cx);
416 let text_style = &self.text_style;
417 let line_spacing = text_style.line_spacing;
418 let mut index = 0;
419 if let Some(read) = read {
420 while index < read.count {
421 let y = read.buffer[read.offset + y_o + index * read.slots];
422 let font_size = read.buffer[read.offset + font_size_o + index * read.slots];
423 if y + font_size * line_spacing > spos.y { while index < read.count {
425 let x = read.buffer[read.offset + x_o + index * read.slots];
426 let y = read.buffer[read.offset + y_o + index * read.slots];
427 let w = read.buffer[read.offset + w_o + index * read.slots];
429 if x > spos.x + w * 0.5 || y > spos.y {
430 let prev_index = if index == 0 {0}else {index - 1};
431 let prev_x = read.buffer[read.offset + x_o + prev_index * read.slots];
432 let prev_w = read.buffer[read.offset + w_o + prev_index * read.slots];
433 if index < read.count - 1 && prev_x > spos.x + prev_w { return read.buffer[read.offset + char_offset_o + index * read.slots] as usize;
435 }
436 return read.buffer[read.offset + char_offset_o + prev_index * read.slots] as usize;
437 }
438 index += 1;
439 }
440 }
441 index += 1;
442 }
443 if read.count == 0 {
444 return 0
445 }
446 return read.buffer[read.offset + char_offset_o + (read.count - 1) * read.slots] as usize;
447 }
448 return 0
449 }
450
451 pub fn get_monospace_base(&self, cx: &Cx) -> Vec2 {
452 let font_id = self.text_style.font.font_id.unwrap();
453 let font = cx.fonts[font_id].font_loaded.as_ref().unwrap();
454 let slot = font.char_code_to_glyph_index_map[33];
455 let glyph = &font.glyphs[slot];
456
457 Vec2 {
459 x: glyph.horizontal_metrics.advance_width * (96.0 / (72.0 * font.units_per_em)),
460 y: self.text_style.line_spacing
461 }
462 }
463}