Contents
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
use std::borrow::Borrow;
use std::i32;
use super::{Drawable, PointCollection};
use crate::style::{FontDesc, FontResult, LayoutBox, TextStyle};
use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind};
/// A single line text element. This can be owned or borrowed string, dependents on
/// `String` or `str` moved into.
pub struct Text<'a, Coord, T: Borrow<str>> {
text: T,
coord: Coord,
style: TextStyle<'a>,
}
impl<'a, Coord, T: Borrow<str>> Text<'a, Coord, T> {
/// Create a new text element
/// - `text`: The text for the element
/// - `points`: The upper left conner for the text element
/// - `style`: The text style
/// - Return the newly created text element
pub fn new<S: Into<TextStyle<'a>>>(text: T, points: Coord, style: S) -> Self {
Self {
text,
coord: points,
style: style.into(),
}
}
}
impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord> for &'a Text<'b, Coord, T> {
type Point = &'a Coord;
type IntoIter = std::iter::Once<&'a Coord>;
fn point_iter(self) -> Self::IntoIter {
std::iter::once(&self.coord)
}
}
impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB> for Text<'a, Coord, T> {
fn draw<I: Iterator<Item = BackendCoord>>(
&self,
mut points: I,
backend: &mut DB,
_: (u32, u32),
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
if let Some(a) = points.next() {
return backend.draw_text(self.text.borrow(), &self.style, a);
}
Ok(())
}
}
/// An multi-line text element. The `Text` element allows only single line text
/// and the `MultiLineText` supports drawing multiple lines
pub struct MultiLineText<'a, Coord, T: Borrow<str>> {
lines: Vec<T>,
coord: Coord,
style: TextStyle<'a>,
line_height: f64,
}
impl<'a, Coord, T: Borrow<str>> MultiLineText<'a, Coord, T> {
/// Create an empty multi-line text element.
/// Lines can be append to the empty multi-line by calling `push_line` method
///
/// `pos`: The upper left corner
/// `style`: The style of the text
pub fn new<S: Into<TextStyle<'a>>>(pos: Coord, style: S) -> Self {
MultiLineText {
lines: vec![],
coord: pos,
style: style.into(),
line_height: 1.25,
}
}
/// Set the line height of the multi-line text element
pub fn set_line_height(&mut self, value: f64) -> &mut Self {
self.line_height = value;
self
}
/// Push a new line into the given multi-line text
/// `line`: The line to be pushed
pub fn push_line<L: Into<T>>(&mut self, line: L) {
self.lines.push(line.into());
}
/// Estimate the multi-line text element's dimension
pub fn estimate_dimension(&self) -> FontResult<(i32, i32)> {
let (mut mx, mut my) = (0, 0);
for ((x, y), t) in self.layout_lines((0, 0)).zip(self.lines.iter()) {
let (dx, dy) = self.style.font.box_size(t.borrow())?;
mx = mx.max(x + dx as i32);
my = my.max(y + dy as i32);
}
Ok((mx, my))
}
/// Move the location to the specified location
pub fn relocate(&mut self, coord: Coord) {
self.coord = coord
}
fn layout_lines(&self, (x0, y0): BackendCoord) -> impl Iterator<Item = BackendCoord> {
let font_height = self.style.font.get_size();
let actual_line_height = font_height * self.line_height;
(0..self.lines.len() as u32).map(move |idx| {
let y = f64::from(y0) + f64::from(idx) * actual_line_height;
// TODO: Support text alignment as well, currently everything is left aligned
let x = f64::from(x0);
(x.round() as i32, y.round() as i32)
})
}
}
fn layout_multiline_text<'a, F: FnMut(&'a str)>(
text: &'a str,
max_width: u32,
font: FontDesc<'a>,
mut func: F,
) {
for line in text.lines() {
if max_width == 0 || line.is_empty() {
func(line);
} else {
let mut remaining = &line[0..];
while !remaining.is_empty() {
let mut left = 0;
while left < remaining.len() {
let width = font.box_size(&remaining[0..=left]).unwrap_or((0, 0)).0 as i32;
if width > max_width as i32 {
break;
}
left += 1;
}
if left == 0 {
left += 1;
}
let cur_line = &remaining[..left];
remaining = &remaining[left..];
func(cur_line);
}
}
}
}
impl<'a, T: Borrow<str>> MultiLineText<'a, BackendCoord, T> {
/// Compute the line layout
pub fn compute_line_layout(&self) -> FontResult<Vec<LayoutBox>> {
let mut ret = vec![];
for ((x, y), t) in self.layout_lines(self.coord).zip(self.lines.iter()) {
let (dx, dy) = self.style.font.box_size(t.borrow())?;
ret.push(((x, y), (x + dx as i32, y + dy as i32)));
}
Ok(ret)
}
}
impl<'a, Coord> MultiLineText<'a, Coord, &'a str> {
/// Parse a multi-line text into an multi-line element.
///
/// `text`: The text that is parsed
/// `pos`: The position of the text
/// `style`: The style for this text
/// `max_width`: The width of the multi-line text element, the line will break
/// into two lines if the line is wider than the max_width. If 0 is given, do not
/// do any line wrapping
pub fn from_str<ST: Into<&'a str>, S: Into<TextStyle<'a>>>(
text: ST,
pos: Coord,
style: S,
max_width: u32,
) -> Self {
let text = text.into();
let mut ret = MultiLineText::new(pos, style);
layout_multiline_text(text, max_width, ret.style.font.clone(), |l| {
ret.push_line(l)
});
ret
}
}
impl<'a, Coord> MultiLineText<'a, Coord, String> {
/// Parse a multi-line text into an multi-line element.
///
/// `text`: The text that is parsed
/// `pos`: The position of the text
/// `style`: The style for this text
/// `max_width`: The width of the multi-line text element, the line will break
/// into two lines if the line is wider than the max_width. If 0 is given, do not
/// do any line wrapping
pub fn from_string<S: Into<TextStyle<'a>>>(
text: String,
pos: Coord,
style: S,
max_width: u32,
) -> Self {
let mut ret = MultiLineText::new(pos, style);
layout_multiline_text(text.as_str(), max_width, ret.style.font.clone(), |l| {
ret.push_line(l.to_string())
});
ret
}
}
impl<'b, 'a, Coord: 'a, T: Borrow<str> + 'a> PointCollection<'a, Coord>
for &'a MultiLineText<'b, Coord, T>
{
type Point = &'a Coord;
type IntoIter = std::iter::Once<&'a Coord>;
fn point_iter(self) -> Self::IntoIter {
std::iter::once(&self.coord)
}
}
impl<'a, Coord: 'a, DB: DrawingBackend, T: Borrow<str>> Drawable<DB>
for MultiLineText<'a, Coord, T>
{
fn draw<I: Iterator<Item = BackendCoord>>(
&self,
mut points: I,
backend: &mut DB,
_: (u32, u32),
) -> Result<(), DrawingErrorKind<DB::ErrorType>> {
if let Some(a) = points.next() {
for (point, text) in self.layout_lines(a).zip(self.lines.iter()) {
backend.draw_text(text.borrow(), &self.style, point)?;
}
}
Ok(())
}
}