rusttyper/
lib.rs

1#![allow(clippy::nonminimal_bool)]
2
3#[macro_use]
4extern crate glium;
5extern crate unicode_normalization;
6extern crate rusttype;
7
8use std::borrow::Cow;
9use std::ops::Range;
10
11use glium::texture::Texture2d;
12use glium::backend::Facade;
13use glium::backend::glutin::Display;
14
15pub use rusttype::{Scale, point, Point, vector, Vector, Rect, SharedBytes};
16use rusttype::{Font, FontCollection, PositionedGlyph, Glyph, GlyphId};
17use rusttype::gpu_cache::*;
18
19use self::unicode_normalization::UnicodeNormalization;
20
21
22
23pub struct Render<'a> {
24    fonts: Vec<Font<'a>>,
25    pub texture: Texture2d,
26    cache: Cache<'a>,
27    hidpi_factor: f64,
28    tolerance: (f32, f32),
29}
30impl<'a> Render<'a> {
31    /// texture_size: 512, tolerance: (0.1, 0.1)
32    pub fn new(gl: &Display, texture_size: u32, tolerance: (f32, f32)) -> Self {
33        let dpi = gl.gl_window().window().scale_factor();
34        // We can always just resize it if it's too small.
35        let initial_size = (texture_size as f64 * dpi) as u32;
36
37        let mut ret = Render {
38            fonts: Vec::new(),
39            texture: Texture2d::empty_with_format(
40                gl,
41                glium::texture::UncompressedFloatFormat::U8,
42                glium::texture::MipmapsOption::NoMipmap,
43                initial_size,
44                initial_size,
45            ).unwrap(),
46            cache: Cache::builder().build(),
47            hidpi_factor: dpi, // FIXME: ask window + may need updating?
48            tolerance,
49        };
50        ret.set_cache(gl, initial_size);
51        ret
52    }
53
54    pub fn add_fonts<B>(&mut self, bytes: B) -> Result<(), rusttype::Error>
55    where
56        B: Into<SharedBytes<'a>>,
57    {
58        let fonts = FontCollection::from_bytes(bytes)?;
59        for font in fonts.into_fonts() {
60            self.fonts.push(font?);
61        }
62        Ok(())
63    }
64
65    fn set_cache<G: Facade>(&mut self, gl: &G, size: u32) {
66        let dim = (size, size);
67        if self.cache.dimensions() != dim {
68            self.cache = Cache::builder()
69                .dimensions(size, size)
70                .scale_tolerance(self.tolerance.0)
71                .position_tolerance(self.tolerance.1)
72                .build();
73        }
74        if self.texture.dimensions() != dim {
75            self.texture = Texture2d::empty(gl, size, size).unwrap();
76        }
77    }
78}
79
80/// A styled `str`.
81pub struct Text<'a> {
82    pub style: Style,
83    pub text: Cow<'a, str>,
84}
85impl<'a, I> From<(Style, I)> for Text<'a>
86where
87    I: Into<Cow<'a, str>>,
88{
89    fn from((s, t): (Style, I)) -> Self {
90        Text {
91            style: s,
92            text: t.into(),
93        }
94    }
95}
96// We can't just impl for S: Into<Cow> CUZ this conflicts with From<(Style, I)>
97impl<'a> From<String> for Text<'a> {
98    fn from(t: String) -> Self {
99        Text {
100            style: Style::default(),
101            text: t.into(),
102        }
103    }
104}
105impl<'a> From<&'a str> for Text<'a> {
106    fn from(t: &'a str) -> Self {
107        Text {
108            style: Style::default(),
109            text: t.into(),
110        }
111    }
112}
113
114#[derive(Debug, Clone, Copy)]
115pub struct Style {
116    /// The size of the font. The default scale is 24.0.
117    pub scale: f32,
118    /// RGBA color components, from 0 to 255.
119    pub color: [u8; 4],
120    /// NYI
121    pub fontid: usize,
122
123    /// Mark the text for drawing with shadows. This is not implemented here.
124    pub shadow: Option<[u8; 4]>,
125    // FIXME: bitflags
126    /// NYI
127    pub bold: bool,
128    /// NYI
129    pub italic: bool,
130    /// NYI
131    pub underline: bool,
132    /// NYI
133    pub strike: bool,
134}
135impl Default for Style {
136    fn default() -> Self {
137        Style {
138            scale: 24.0,
139            color: [0, 0, 0, 0xFF],
140            fontid: 0,
141
142            shadow: None,
143            bold: false,
144            italic: false,
145            underline: false,
146            strike: false,
147        }
148    }
149}
150impl Style {
151    pub fn new() -> Self {
152        Self::default()
153    }
154
155    fn get_glyph<'context, 'fonts>(
156        &self,
157        c: char,
158        fonts: &'context [Font<'fonts>],
159    ) -> (usize, Glyph<'fonts>)
160    where
161        'fonts: 'context,
162    {
163        // FIXME: Try fallback fonts.
164        // FIXME: How do we figure out who the bold fonts are?
165        let font = &fonts[self.fontid];
166        let g = font.glyph(c);
167        (self.fontid, g)
168    }
169}
170
171pub enum FlowControl {
172    NextGlyph,
173    SkipBlock,
174    StopBuild,
175}
176
177/// The bounds of text.
178#[derive(Debug, Clone)]
179pub struct TextBlock<P> {
180    /// The upper-left corner.
181    pub origin: P,
182    pub width: u32,
183}
184impl<P> TextBlock<P> {
185    fn to_block(&self) -> LayoutBlock {
186        LayoutBlock {
187            width: self.width,
188            caret: point(0.0, 0.0),
189            wrap_unit_start: 0,
190            any_break: false,
191            max_area: point(0.0, 0.0),
192            last_glyph_id: None,
193            query_area: Rect {
194                min: point(0.0, 0.0),
195                max: point(0.0, 0.0),
196            },
197            query_hit: false,
198            just_wrapped: false,
199        }
200    }
201}
202
203#[derive(Debug)]
204struct LayoutBlock {
205    width: u32,
206    caret: Point<f32>,
207    wrap_unit_start: usize,
208    any_break: bool,
209    max_area: Point<f32>,
210    last_glyph_id: Option<GlyphId>,
211    query_area: Rect<f32>,
212    query_hit: bool,
213    just_wrapped: bool,
214}
215impl LayoutBlock {
216    fn bump_line(&mut self, advance_height: f32) {
217        if self.caret.x > self.max_area.x { self.max_area.x = self.caret.x; }
218        self.caret = point(0.0, self.caret.y + advance_height);
219        self.max_area.y = self.caret.y;
220    }
221}
222
223#[derive(Default)]
224pub struct RunBuffer<'text, P> {
225    parts: Vec<Text<'text>>,
226    blocks: Vec<(Range<usize>, TextBlock<P>)>,
227    char_count: usize,
228}
229impl<'text, P> RunBuffer<'text, P>
230where
231    P: 'text,
232{
233    pub fn new() -> Self {
234        RunBuffer {
235            parts: Vec::new(),
236            blocks: Vec::new(),
237            char_count: 0,
238        }
239    }
240
241    pub fn push_blocks<I>(&mut self, layout: TextBlock<P>, it: I)
242    where
243        I: Iterator<Item=Text<'text>>,
244    {
245        let start = self.parts.len();
246        for text in it {
247            self.char_count += text.text.len();
248            self.parts.push(text);
249        }
250        let end = self.parts.len();
251        self.blocks.push((start..end, layout));
252    }
253
254    pub fn push<T>(&mut self, layout: TextBlock<P>, t: T)
255    where
256        T: Into<Text<'text>>,
257    {
258        self.push_blocks(layout, Some(t.into()).into_iter())
259    }
260
261    pub fn write<S>(&mut self, pos: P, width: u32, text: S)
262    where
263        S: Into<Cow<'text, str>>,
264    {
265        self.push(
266            TextBlock {
267                origin: pos,
268                width,
269            },
270            Text {
271                text: text.into(),
272                style: Style::default(),
273            },
274        );
275    }
276
277    pub fn reset(&mut self) {
278        self.parts.clear();
279        self.blocks.clear();
280        self.char_count = 0;
281    }
282
283    /// Returns an estimate of how many glyphs are in the buffer. This may be inaccurate due to eg
284    /// ligatures and normalization.
285    pub fn glyph_estimate(&self) -> usize {
286        self.char_count
287    }
288
289    pub fn parts(&self) -> &[Text<'text>] {
290        &self.parts[..]
291    }
292
293    /// Runs the layout algorithm on the most recently added block, and returns the amount of space
294    /// used, and its `TextBlock` object.
295    pub fn measure_area(
296        &mut self,
297        render: &mut Render,
298        query: Point<f32>,
299    ) -> (
300        Point<f32>,
301        &mut TextBlock<P>,
302        Option<QueryResult>,
303    ) {
304        let &mut(ref range, ref mut layout) = self.blocks.last_mut().expect("measure_area called but there were no blocks");
305        let mut layout_block = layout.to_block();
306        let mut glyphs = Vec::new();
307        let mut max_area = point(0.0, 0.0);
308        let mut qr = None;
309        for (part_index, text) in self.parts[range.clone()].iter().enumerate() {
310            let (nm, q) = layout_block_glyphs(
311                &mut layout_block,
312                &mut glyphs,
313                render.hidpi_factor,
314                &render.fonts,
315                text,
316                query,
317            );
318            if let (None, Some(mut q)) = (&qr, q) {
319                q.part_index = part_index;
320                qr = Some(q);
321            }
322            if nm.x > max_area.x { max_area.x = nm.x; }
323            if nm.y > max_area.y { max_area.y = nm.y; }
324        }
325        layout_block.bump_line(0.0);
326        (max_area, layout, qr)
327    }
328
329    /// Runs the layout algorithm, and uploads glyphs to the cache.
330    ///
331    /// `write_glyph` is called with each positioned glyph. You can use this to build your vertex
332    /// buffer.
333    ///
334    /// The first parameter holds the `Text` and `Style`.
335    /// The second parameter is the `origin`, `&P`.
336    /// The third parameter specifies the rectangle the glyph occupises, relative to the `origin`.
337    /// For example, if P is a point in a 2D UI, then the glyph is simply positioned at `origin + offset`.
338    /// The fourth parameter is UV coordinates.
339    pub fn build<F>(
340        &mut self,
341        render: &mut Render,
342        mut write_glyph: F
343    )
344    where
345        F: for<'x, 'y> FnMut(&'x Text<'y>, &P, Rect<i32>, Rect<f32>) -> FlowControl,
346    {
347        let mut glyphs: Vec<(usize, PositionedGlyph)> = Vec::new();
348        let mut glyph_block: Vec<(&TextBlock<P>, &Text, Range<usize>)> = Vec::new();
349        for (range, layout) in &self.blocks {
350            let mut layout_block = layout.to_block();
351            for text in &self.parts[range.clone()] {
352                let start = glyphs.len();
353                layout_block_glyphs(
354                    &mut layout_block,
355                    &mut glyphs,
356                    render.hidpi_factor,
357                    &render.fonts,
358                    text,
359                    point(-9e9, -9e9),
360                );
361                let end = glyphs.len();
362                glyph_block.push((layout, text, start..end));
363            }
364        }
365        for &(fontid, ref glyph) in &glyphs {
366            render.cache.queue_glyph(fontid, glyph.clone());
367        }
368        let texture = &mut render.texture;
369        // I think this is O(total # of glyphs) rather than O(total # of unique glyphs), but the
370        // two numbers were actually fairly similar. (There are extra variants due to sub-pixel
371        // positioning?)
372        render.cache.cache_queued(|rect, data| {
373            texture.main_level().write(glium::Rect {
374                left: rect.min.x,
375                bottom: rect.min.y,
376                width: rect.width(),
377                height: rect.height(),
378            }, glium::texture::RawImage2d {
379                data: Cow::Borrowed(data),
380                width: rect.width(),
381                height: rect.height(),
382                format: glium::texture::ClientFormat::U8,
383            });
384        }).unwrap();
385        for (layout, text, range) in glyph_block {
386            for &(fontid, ref glyph) in &glyphs[range] {
387                if let Ok(Some((uv, pos))) = render.cache.rect_for(fontid, glyph) {
388                    match write_glyph(text, &layout.origin, pos, uv) {
389                        FlowControl::NextGlyph => (),
390                        FlowControl::SkipBlock => break,
391                        FlowControl::StopBuild => return,
392                    }
393                }
394            }
395        }
396    }
397}
398fn layout_block_glyphs<'layout, 'context, 'fonts, 'result, 'text>(
399    layout: &'layout mut LayoutBlock,
400    result: &'result mut Vec<(usize, PositionedGlyph<'fonts>)>,
401    dpi: f64,
402    fonts: &'context [Font<'fonts>],
403    text: &'text Text<'text>,
404    query: Point<f32>,
405) -> (Point<f32>, Option<QueryResult>)
406where
407    'fonts: 'context,
408{
409    let mut query_result = None;
410    let style = &text.style;
411    let scale = Scale::uniform((text.style.scale as f64 * dpi) as f32);
412    let v_metrics = fonts[0].v_metrics(scale); // FIXME: Various problems.
413    let advance_height: f32 = v_metrics.ascent - v_metrics.descent + v_metrics.line_gap; // FIXME: max(this line)?
414    let is_separator = |c: char| c == ' ';
415    let caret2rect = |caret: Point<f32>| -> Rect<f32> {
416        let min = caret - vector(0.0, v_metrics.ascent);
417        let max = caret; // Tails on j's and g's shouldn't count, I think. - vector(0.0, v_metrics.descent);
418        Rect { min, max }
419    };
420    layout.query_area = caret2rect(layout.caret);
421    for (nfc_char_index, c) in text.text.nfc().enumerate() {
422        if c.is_control() {
423            if c == '\n' {
424                layout.bump_line(advance_height);
425                layout.query_area = caret2rect(layout.caret);
426                layout.wrap_unit_start = result.len();
427                layout.any_break = false;
428                layout.query_hit = false;
429            }
430            continue;
431        }
432        if is_separator(c) {
433            layout.wrap_unit_start = result.len() + 1; // We break at the *next* character
434            layout.any_break = true;
435            layout.query_hit = false;
436            layout.query_area = caret2rect(layout.caret);
437        }
438        let (fontid, base_glyph) = style.get_glyph(c, fonts);
439        let font = &fonts[fontid];
440        if !layout.just_wrapped {
441            if let Some(id) = layout.last_glyph_id.replace(base_glyph.id()) {
442                layout.caret.x += font.pair_kerning(scale, id, base_glyph.id()); // FIXME: Not after a wrap.
443            }
444        }
445        let mut glyph: PositionedGlyph = base_glyph.scaled(scale).positioned(layout.caret);
446        let bb = glyph.pixel_bounding_box();
447        if let Some(bb) = bb {
448            if bb.max.x as i64 > layout.width as i64 {
449                layout.bump_line(advance_height);
450                if result.len() > layout.wrap_unit_start && layout.any_break {
451                    // There's probably some weird degenerate case where this'd panic w/o this
452                    // check.
453                    let delta = layout.caret - result[layout.wrap_unit_start].1.position();
454                    for &mut (_, ref mut g) in &mut result[layout.wrap_unit_start..] {
455                        *g = g.clone().into_unpositioned().positioned(g.position() + delta);
456                    }
457                    let last = &result.last().expect("any glyphs").1;
458                    layout.caret = last.position();
459                    layout.caret.x += last.unpositioned().h_metrics().advance_width;
460                    // Word-wrapping may cause us to either LOSE or GAIN the query, but we only
461                    // need to check LOSE because a GAIN will natively re-check.
462                    layout.query_area.min = layout.query_area.min + delta;
463                    layout.query_area.max = layout.query_area.max + delta;
464                } else {
465                    // If there were glyphs, then everything must get shoved down.
466                    // But if there were no glyphs, we still need to reset the query_area.
467                    layout.query_area = caret2rect(layout.caret);
468                }
469                if layout.query_hit {
470                    layout.query_hit = false;
471                    query_result = None;
472                }
473                layout.any_break = false;
474                glyph = glyph.into_unpositioned().positioned(layout.caret);
475                layout.last_glyph_id = None;
476                layout.just_wrapped = true;
477            }
478        }
479        let metrics = glyph.unpositioned().h_metrics();
480        layout.caret.x += metrics.advance_width;
481        layout.query_area.max.x = layout.caret.x;
482        if true
483            && query_result.is_none()
484            && (layout.query_area.min.x <= query.x && query.x <= layout.query_area.max.x)
485            && (layout.query_area.min.y <= query.y && query.y <= layout.query_area.max.y)
486            && layout.query_area.min.x != layout.query_area.max.x
487        {
488            layout.query_hit = true;
489            query_result = Some(QueryResult {
490                part_index: !0, // NOTE: Fixed later.
491                nfc_char_index,
492                area: layout.query_area,
493            });
494        } else if let (Some(qr), true) = (&mut query_result, layout.query_hit)  {
495            qr.area.max.x = qr.area.max.x.max(layout.caret.x);
496        }
497        result.push((fontid, glyph));
498        // FIXME: You might like to not push spaces, but they're load-bearing.
499        layout.just_wrapped = false;
500    }
501    let mut ret = layout.max_area;
502    ret.x = ret.x.max(layout.caret.x);
503    (ret, query_result)
504}
505
506#[derive(Debug, Clone)]
507pub struct QueryResult {
508    /// The text-part that the query found.
509    pub part_index: usize,
510    /// The character in the block found. **NOTE**: This is possibly not what you'd expect, since it
511    /// is the index in the `str.nfc()` stream. Normalize your strings first.
512    // Sorry, there doesn't seem to be any easy fix for this.
513    pub nfc_char_index: usize,
514    /// The area, on-screen, of the broken word that has been selected.
515    /// Only useful for debugging.
516    pub area: Rect<f32>,
517}
518
519pub mod simple2d {
520    use super::*;
521
522    use glium;
523    use glium::backend::Facade;
524    use glium::{Program, Surface};
525    use glium::program::ProgramChooserCreationError;
526
527    /// Something to get you up & running.
528    pub struct Simple2d {
529        pub program: Program,
530    }
531    impl Simple2d {
532        pub fn create<G: Facade>(gl: &G) -> Result<Simple2d, ProgramChooserCreationError> {
533           let program = program!(
534                gl,
535                // FIXME: The other versions.
536                140 => {
537                    vertex: "
538                        #version 140
539
540                        in vec2 position;
541                        in vec2 tex_coords;
542                        in vec4 color;
543
544                        out vec2 v_tex_coords;
545                        out vec4 v_color;
546
547                        void main() {
548                            gl_Position = vec4(position, 0.0, 1.0);
549                            v_tex_coords = tex_coords;
550                            v_color = color;
551                        }
552                    ",
553
554                    fragment: "
555                        #version 140
556                        uniform sampler2D tex;
557                        in vec2 v_tex_coords;
558                        in vec4 v_color;
559                        out vec4 f_color;
560
561                        void main() {
562                            f_color = v_color * vec4(1.0, 1.0, 1.0, texture(tex, v_tex_coords).r);
563                        }
564                    "
565                }
566           );
567           program.map(|p| Simple2d {
568               program: p,
569           })
570        }
571
572        pub fn draw<G: Facade>(
573            &self,
574            gl: &G,
575            target: &mut glium::Frame,
576            font: &mut Render,
577            buffer: &mut RunBuffer<(i32, i32)>
578        ) -> Result<(), glium::DrawError>
579        {
580            let (screen_width, screen_height) = target.get_dimensions();
581            let (screen_width, screen_height) = (screen_width as f32, screen_height as f32);
582            let mut vertices = Vec::new();
583            buffer.build(font, |text, origin, pos_rect, uv_rect| {
584                let color = {
585                    let c = text.style.color;
586                    let f = |c| c as f32 / 255.0;
587                    [f(c[0]), f(c[1]), f(c[2]), f(c[3])]
588                };
589                let origin = vector(origin.0, origin.1);
590                let (gl_rect_min, gl_rect_max) = (
591                    2.0 * vector(
592                        (pos_rect.min.x + origin.x) as f32 / screen_width - 0.5,
593                        1.0 - (pos_rect.min.y + origin.y) as f32 / screen_height - 0.5,
594                    ),
595                    2.0 * vector(
596                        (pos_rect.max.x + origin.x) as f32 / screen_width - 0.5,
597                        1.0 - (pos_rect.max.y + origin.y) as f32 / screen_height - 0.5,
598                    ),
599                );
600                let verts = [
601                    Vertex {
602                        position: [gl_rect_min.x, gl_rect_max.y],
603                        tex_coords: [uv_rect.min.x, uv_rect.max.y],
604                        color,
605                    },
606                    Vertex {
607                        position: [gl_rect_min.x,  gl_rect_min.y],
608                        tex_coords: [uv_rect.min.x, uv_rect.min.y],
609                        color,
610                    },
611                    Vertex {
612                        position: [gl_rect_max.x,  gl_rect_min.y],
613                        tex_coords: [uv_rect.max.x, uv_rect.min.y],
614                        color,
615                    },
616                    Vertex {
617                        position: [gl_rect_max.x,  gl_rect_min.y],
618                        tex_coords: [uv_rect.max.x, uv_rect.min.y],
619                        color,
620                    },
621                    Vertex {
622                        position: [gl_rect_max.x, gl_rect_max.y],
623                        tex_coords: [uv_rect.max.x, uv_rect.max.y],
624                        color,
625                    },
626                    Vertex {
627                        position: [gl_rect_min.x, gl_rect_max.y],
628                        tex_coords: [uv_rect.min.x, uv_rect.max.y],
629                        color,
630                    },
631                ];
632                vertices.extend_from_slice(&verts[..]);
633                FlowControl::NextGlyph
634            });
635
636            let vbo = glium::VertexBuffer::new(gl, &vertices).unwrap();
637            let uniforms = uniform! {
638                tex: font.texture.sampled().magnify_filter(glium::uniforms::MagnifySamplerFilter::Nearest)
639            };
640            target.draw(
641                &vbo,
642                glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList),
643                &self.program,
644                &uniforms,
645                &glium::DrawParameters {
646                    blend: glium::Blend::alpha_blending(),
647                    .. Default::default()
648                },
649            )
650        }
651    }
652
653
654    #[derive(Copy, Clone)]
655    struct Vertex {
656        position: [f32; 2],
657        tex_coords: [f32; 2],
658        color: [f32; 4]
659    }
660
661    implement_vertex!(Vertex, position, tex_coords, color);
662}