limn_text_layout/
lib.rs

1//! Text layout logic.
2
3// ---- START CLIPPY CONFIG
4
5#![cfg_attr(all(not(test), feature="clippy"), warn(result_unwrap_used))]
6#![cfg_attr(feature="clippy", warn(unseparated_literal_suffix))]
7#![cfg_attr(feature="clippy", warn(wrong_pub_self_convention))]
8
9// Enable clippy if our Cargo.toml file asked us to do so.
10#![cfg_attr(feature="clippy", feature(plugin))]
11#![cfg_attr(feature="clippy", plugin(clippy))]
12
13#![warn(missing_copy_implementations,
14        trivial_numeric_casts,
15        trivial_casts,
16        unused_extern_crates,
17        unused_import_braces,
18        unused_qualifications)]
19#![cfg_attr(feature="clippy", warn(cast_possible_truncation))]
20#![cfg_attr(feature="clippy", warn(cast_possible_wrap))]
21#![cfg_attr(feature="clippy", warn(cast_precision_loss))]
22#![cfg_attr(feature="clippy", warn(cast_sign_loss))]
23#![cfg_attr(feature="clippy", warn(missing_docs_in_private_items))]
24#![cfg_attr(feature="clippy", warn(mut_mut))]
25
26// Disallow `println!`. Use `debug!` for debug output
27// (which is provided by the `log` crate).
28#![cfg_attr(feature="clippy", warn(print_stdout))]
29
30// This allows us to use `unwrap` on `Option` values (because doing makes
31// working with Regex matches much nicer) and when compiling in test mode
32// (because using it in tests is idiomatic).
33#![cfg_attr(all(not(test), feature="clippy"), warn(result_unwrap_used))]
34#![cfg_attr(feature="clippy", warn(unseparated_literal_suffix))]
35#![cfg_attr(feature="clippy", warn(wrong_pub_self_convention))]
36
37// ---- START CLIPPY CONFIG
38
39extern crate rusttype;
40extern crate euclid;
41
42pub mod types;
43pub mod cursor;
44pub mod glyph;
45pub mod line;
46
47use std::f32;
48use rusttype::Scale;
49use self::line::{LineRects, LineInfo, LineInfos};
50use self::types::*;
51
52
53
54/// The RustType `PositionedGlyph` type.
55pub type PositionedGlyph = rusttype::PositionedGlyph<'static>;
56
57pub type Font = rusttype::Font<'static>;
58
59pub use types::Align;
60
61/// The way in which text should wrap around the width.
62#[derive(Copy, Clone, Debug, PartialEq)]
63pub enum Wrap {
64    NoWrap,
65    /// Wrap at the first character that exceeds the width.
66    Character,
67    /// Wrap at the first word that exceeds the width.
68    Whitespace,
69}
70
71pub fn get_text_size(text: &str,
72                     font: &Font,
73                     font_size: f32,
74                     line_height: f32,
75                     wrap: Wrap) -> Size {
76
77    let line_infos = LineInfos::new(text, font, font_size, wrap, f32::MAX);
78    let max_width = line_infos.fold(0.0, |max, line_info| f32::max(max, line_info.width));
79    Size::new(max_width, line_infos.count() as f32 * line_height)
80}
81
82pub fn get_text_height(text: &str,
83                        font: &Font,
84                        font_size: f32,
85                        line_height: f32,
86                        wrap: Wrap,
87                        width: f32)
88                        -> f32 {
89    let line_infos = LineInfos::new(text, font, font_size, wrap, width);
90    line_infos.count() as f32 * line_height
91}
92
93pub fn get_line_rects(text: &str,
94                      rect: Rect,
95                      font: &Font,
96                      font_size: f32,
97                      line_height: f32,
98                      line_wrap: Wrap,
99                      align: Align)
100                      -> Vec<Rect> {
101
102    let line_infos: Vec<LineInfo> = LineInfos::new(text, font, font_size, line_wrap, rect.width())
103        .collect();
104    let line_infos = line_infos.iter().cloned();
105    let line_rects = LineRects::new(line_infos, font_size, rect, align, line_height);
106    line_rects.collect()
107}
108
109pub fn get_positioned_glyphs(text: &str,
110                             rect: Rect,
111                             font: &Font,
112                             font_size: f32,
113                             line_height: f32,
114                             line_wrap: Wrap,
115                             align: Align)
116                             -> Vec<PositionedGlyph>
117{
118    let line_infos: Vec<LineInfo> = LineInfos::new(text, font, font_size, line_wrap, rect.width())
119        .collect();
120    let line_infos = line_infos.iter().cloned();
121    let line_texts = line_infos.clone().map(|info| &text[info.byte_range()]);
122    let line_rects = LineRects::new(line_infos, font_size, rect, align, line_height);
123    let scale = Scale::uniform(font_size);
124
125    let mut positioned_glyphs = Vec::new();
126    for (line_text, line_rect) in line_texts.zip(line_rects) {
127        // point specifies bottom left corner of text line
128        let point = rusttype::Point {
129            x: line_rect.left(),
130            y: line_rect.top() + font_size,
131        };
132
133        positioned_glyphs.extend(font.glyphs_for(line_text.chars())
134            .scan((None, 0.0), |state, g| {
135                let &mut (last, x) = state;
136                let g = g.scaled(scale);
137
138                let kern = last.map(|last| font.pair_kerning(scale, last, g.id())).unwrap_or(0.0);
139                let width = g.h_metrics().advance_width;
140
141                let next = g.positioned(point + rusttype::vector(x, 0.0));
142                *state = (Some(next.id()), x + width + kern);
143                Some(next.standalone())
144            }));
145    }
146    positioned_glyphs
147}
148
149/// An iterator yielding each line within the given `text` as a new `&str`, where the start and end
150/// indices into each line are provided by the given iterator.
151#[derive(Clone)]
152pub struct Lines<'a, I>
153    where I: Iterator<Item = std::ops::Range<usize>>
154{
155    text: &'a str,
156    ranges: I,
157}
158
159/// Produce an iterator yielding each line within the given `text` as a new `&str`, where the
160/// start and end indices into each line are provided by the given iterator.
161pub fn lines<I>(text: &str, ranges: I) -> Lines<I>
162    where I: Iterator<Item = std::ops::Range<usize>>
163{
164    Lines {
165        text: text,
166        ranges: ranges,
167    }
168}
169
170impl<'a, I> Iterator for Lines<'a, I>
171    where I: Iterator<Item = std::ops::Range<usize>>
172{
173    type Item = &'a str;
174    fn next(&mut self) -> Option<Self::Item> {
175        let Lines { text, ref mut ranges } = *self;
176        ranges.next().map(|range| &text[range])
177    }
178}
179
180/// Converts the given font size in "points" to its font size in pixels.
181/// assumes 96 dpi display. 1 pt = 1/72"
182pub fn pt_to_px(font_size_in_points: f32) -> f32 {
183    (font_size_in_points * 4.0) / 3.0
184}
185
186pub fn px_to_pt(font_size_in_px: f32) -> f32 {
187    (font_size_in_px * 3.0) / 4.0
188}
189
190/// Converts the given font size in "points" to a uniform `rusttype::Scale`.
191pub fn pt_to_scale(font_size_in_points: f32) -> Scale {
192    Scale::uniform(font_size_in_points)
193}