1 use std::ffi::{CString, CStr};
2 use std::os::raw::{c_uint, c_int, c_long};
3 use std::path::Path;
4 use std::error;
5 use std::error::Error;
6 use std::ffi::NulError;
7 use std::fmt;
8 use std::marker::PhantomData;
9 use ::surface::Surface;
10 use sys;
11 use sys::SDL_Surface;
12 use ::get_error;
13 use ::pixels::Color;
14 use ::rwops::RWops;
15 
16 // Absolute paths are a workaround for https://github.com/rust-lang-nursery/bitflags/issues/39 .
17 bitflags! {
18     /// The styling of a font.
19     pub struct FontStyle: i32 {
20         const NORMAL        = sys::ttf::TTF_STYLE_NORMAL as i32;
21         const BOLD          = sys::ttf::TTF_STYLE_BOLD as i32;
22         const ITALIC        = sys::ttf::TTF_STYLE_ITALIC as i32;
23         const UNDERLINE     = sys::ttf::TTF_STYLE_UNDERLINE as i32;
24         const STRIKETHROUGH = sys::ttf::TTF_STYLE_STRIKETHROUGH as i32;
25     }
26 }
27 
28 /// Information about the hinting of a font.
29 /// See [wikipedia](https://en.wikipedia.org/wiki/Font_hinting)
30 #[repr(i32)]
31 #[derive(Debug, PartialEq, Clone)]
32 pub enum Hinting {
33     Normal = sys::ttf::TTF_HINTING_NORMAL as i32,
34     Light  = sys::ttf::TTF_HINTING_LIGHT  as i32,
35     Mono   = sys::ttf::TTF_HINTING_MONO   as i32,
36     None   = sys::ttf::TTF_HINTING_NONE   as i32,
37 }
38 
39 /// Information about a specific glyph (character) in a font face.
40 #[derive(Debug, PartialEq, Clone)]
41 pub struct GlyphMetrics {
42     pub minx: i32,
43     pub maxx: i32,
44     pub miny: i32,
45     pub maxy: i32,
46     pub advance: i32
47 }
48 
49 /// The result of an `SDL2_TTF` font operation.
50 pub type FontResult<T> = Result<T, FontError>;
51 
52 /// A font-related error.
53 #[derive(Debug)]
54 pub enum FontError {
55     /// A Latin-1 encoded byte string is invalid.
56     InvalidLatin1Text(NulError),
57     /// A SDL2-related error occured.
58     SdlError(String),
59 }
60 
61 impl error::Error for FontError {
description(&self) -> &str62     fn description(&self) -> &str {
63         match *self {
64             FontError::InvalidLatin1Text(ref error) => {
65                 error.description()
66             },
67             FontError::SdlError(ref message) => {
68                 message
69             },
70         }
71     }
72 
cause(&self) -> Option<&error::Error>73     fn cause(&self) -> Option<&error::Error> {
74         match *self {
75             FontError::InvalidLatin1Text(ref error) => {
76                 Some(error)
77             },
78             FontError::SdlError(_) => {
79                 None
80             },
81         }
82     }
83 }
84 
85 impl fmt::Display for FontError {
fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error>86     fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
87         match *self {
88             FontError::InvalidLatin1Text(ref err) => {
89                 write!(f, "Invalid Latin-1 bytes: {}", err.description())
90             },
91             FontError::SdlError(ref msg) => {
92                 write!(f, "SDL2 error: {}", msg)
93             },
94         }
95 
96     }
97 }
98 
99 /// A renderable piece of text in the UTF8 or Latin-1 format.
100 enum RenderableText<'a> {
101     Utf8(&'a str),
102     Latin1(&'a [u8]),
103     Char(String),
104 }
105 impl<'a> RenderableText<'a> {
106     /// Converts the given text to a c-style string if possible.
convert(&self) -> FontResult<CString>107     fn convert(&self) -> FontResult<CString> {
108         match *self {
109             RenderableText::Utf8(text) => {
110                 Ok(CString::new(text).unwrap())
111             },
112             RenderableText::Latin1(bytes) => {
113                 match CString::new(bytes) {
114                     Err(err) => {
115                         Err(FontError::InvalidLatin1Text(err))
116                     },
117                     Ok(cstring) => {
118                         Ok(cstring)
119                     }
120                 }
121             },
122             RenderableText::Char(ref string) => {
123                 Ok(CString::new(string.as_bytes()).unwrap())
124             }
125         }
126     }
127 }
128 
129 /// A builder for a font rendering.
130 #[must_use]
131 pub struct PartialRendering<'f, 'text> {
132     text: RenderableText<'text>,
133     font: &'f Font<'f, 'f>,
134 }
135 
136 /// Converts the given raw pointer to a surface.
convert_to_surface<'a>(raw: *mut SDL_Surface) -> FontResult<Surface<'a>>137 fn convert_to_surface<'a>(raw: *mut SDL_Surface) -> FontResult<Surface<'a>> {
138     if (raw as *mut ()).is_null() {
139         Err(FontError::SdlError(get_error()))
140     } else {
141         Ok(unsafe {
142             Surface::from_ll(raw)
143         })
144     }
145 }
146 
147 impl<'f,'text> PartialRendering<'f,'text> {
148     /// Renders the text in *solid* mode.
149     /// See [the SDL2_TTF docs](https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html#SEC42)
150     /// for an explanation.
solid<'b, T>(self, color: T ) -> FontResult<Surface<'b>> where T: Into<Color>151     pub fn solid<'b, T>(self, color: T )
152             -> FontResult<Surface<'b>> where T: Into<Color> {
153         let source = try!(self.text.convert());
154         let color = color.into().into();
155         let raw = unsafe {
156             match self.text {
157                 RenderableText::Utf8(_) | RenderableText::Char(_) => {
158                     sys::ttf::TTF_RenderUTF8_Solid(self.font.raw(),
159                         source.as_ptr(), color)
160                 },
161                 RenderableText::Latin1(_) => {
162                     sys::ttf::TTF_RenderText_Solid(self.font.raw(),
163                         source.as_ptr(), color)
164                 },
165             }
166         };
167         convert_to_surface(raw)
168     }
169 
170     /// Renders the text in *shaded* mode.
171     /// See [the SDL2_TTF docs](https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html#SEC42)
172     /// for an explanation.
shaded<'b, T>(self, color: T, background: T) -> FontResult<Surface<'b>> where T: Into<Color>173     pub fn shaded<'b, T>(self, color: T, background: T)
174             -> FontResult<Surface<'b>> where T: Into<Color> {
175         let source = try!(self.text.convert());
176         let foreground = color.into().into();
177         let background = background.into().into();
178         let raw = unsafe {
179             match self.text {
180                 RenderableText::Utf8(_) | RenderableText::Char(_) => {
181                     sys::ttf::TTF_RenderUTF8_Shaded(self.font.raw(),
182                         source.as_ptr(), foreground, background)
183                 },
184                 RenderableText::Latin1(_) => {
185                     sys::ttf::TTF_RenderText_Shaded(self.font.raw(),
186                         source.as_ptr(), foreground, background)
187                 },
188             }
189         };
190         convert_to_surface(raw)
191     }
192 
193     /// Renders the text in *blended* mode.
194     /// See [the SDL2_TTF docs](https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html#SEC42)
195     /// for an explanation.
blended<'b, T>(self, color: T) -> FontResult<Surface<'b>> where T: Into<Color>196     pub fn blended<'b, T>(self, color: T)
197             -> FontResult<Surface<'b>> where T: Into<Color> {
198         let source = try!(self.text.convert());
199         let color = color.into().into();
200         let raw = unsafe {
201             match self.text {
202                 RenderableText::Utf8(_) | RenderableText::Char(_) => {
203                     sys::ttf::TTF_RenderUTF8_Blended(self.font.raw(),
204                         source.as_ptr(), color)
205                 },
206                 RenderableText::Latin1(_) => {
207                     sys::ttf::TTF_RenderText_Blended(self.font.raw(),
208                         source.as_ptr(), color)
209                 },
210             }
211         };
212         convert_to_surface(raw)
213     }
214 
215     /// Renders the text in *blended* mode but wrapping the words if the width
216     /// exceeds the given maximum width.
217     /// See [the SDL2_TTF docs](https://www.libsdl.org/projects/SDL_ttf/docs/SDL_ttf.html#SEC42)
218     /// for an explanation of the mode.
blended_wrapped<'b, T>(self, color: T, wrap_max_width: u32) -> FontResult<Surface<'b>> where T: Into<Color>219     pub fn blended_wrapped<'b, T>(self, color: T, wrap_max_width: u32)
220             -> FontResult<Surface<'b>> where T: Into<Color> {
221         let source = try!(self.text.convert());
222         let color = color.into().into();
223         let raw = unsafe {
224             match self.text {
225                 RenderableText::Utf8(_) | RenderableText::Char(_) => {
226                     sys::ttf::TTF_RenderUTF8_Blended_Wrapped(self.font.raw(),
227                         source.as_ptr(), color, wrap_max_width)
228                 },
229                 RenderableText::Latin1(_) => {
230                     sys::ttf::TTF_RenderText_Blended_Wrapped(self.font.raw(),
231                         source.as_ptr(), color, wrap_max_width)
232                 },
233             }
234         };
235         convert_to_surface(raw)
236     }
237 }
238 
239 /// A loaded TTF font.
240 pub struct Font<'ttf_module,'rwops> {
241     raw: *mut sys::ttf::TTF_Font,
242     // RWops is only stored here because it must not outlive
243     // the Font struct, and this RWops should not be used by
244     // anything else
245     // None means that the RWops is handled by SDL itself,
246     // and Some(rwops) means that the RWops is handled by the Rust
247     // side
248     #[allow(dead_code)]
249     rwops: Option<RWops<'rwops>>,
250     #[allow(dead_code)]
251     _marker: PhantomData<&'ttf_module ()>,
252 }
253 
254 
255 
256 impl<'ttf,'r> Drop for Font<'ttf,'r> {
drop(&mut self)257     fn drop(&mut self) {
258         unsafe {
259             // avoid close font after quit()
260             if sys::ttf::TTF_WasInit() == 1 {
261                 sys::ttf::TTF_CloseFont(self.raw);
262             }
263         }
264     }
265 }
266 
267 /// Internally used to load a font (for internal visibility).
internal_load_font<'ttf,P:AsRef<Path>>(path: P, ptsize: u16) -> Result<Font<'ttf,'static>, String>268 pub fn internal_load_font<'ttf,P:AsRef<Path>>(path: P, ptsize: u16) -> Result<Font<'ttf,'static>, String> {
269     unsafe {
270         let cstring = CString::new(path.as_ref().to_str().unwrap()).unwrap();
271         let raw = sys::ttf::TTF_OpenFont(cstring.as_ptr(), ptsize as c_int);
272         if raw.is_null() {
273             Err(get_error())
274         } else {
275             Ok(Font { raw: raw, rwops: None, _marker: PhantomData })
276         }
277     }
278 }
279 
280 /// Internally used to load a font (for internal visibility).
internal_load_font_from_ll<'ttf,'r, R>(raw: *mut sys::ttf::TTF_Font, rwops: R) -> Font<'ttf,'r> where R: Into<Option<RWops<'r>>>281 pub fn internal_load_font_from_ll<'ttf,'r, R>(raw: *mut sys::ttf::TTF_Font, rwops: R)
282         -> Font<'ttf,'r>
283 where R: Into<Option<RWops<'r>>> {
284     Font { raw: raw, rwops: rwops.into(), _marker: PhantomData }
285 }
286 
287 /// Internally used to load a font (for internal visibility).
internal_load_font_at_index<'ttf,P: AsRef<Path>>(path: P, index: u32, ptsize: u16) -> Result<Font<'ttf, 'static>, String>288 pub fn internal_load_font_at_index<'ttf,P: AsRef<Path>>(path: P, index: u32, ptsize: u16)
289         -> Result<Font<'ttf, 'static>, String> {
290     unsafe {
291         let cstring = CString::new(path.as_ref().to_str().unwrap().as_bytes())
292             .unwrap();
293         let raw = sys::ttf::TTF_OpenFontIndex(cstring.as_ptr(),
294             ptsize as c_int, index as c_long);
295         if raw.is_null() {
296             Err(get_error())
297         } else {
298             Ok(Font { raw: raw, rwops: None, _marker: PhantomData})
299         }
300     }
301 }
302 
303 impl<'ttf,'r> Font<'ttf,'r> {
304     /// Returns the underlying C font object.
raw(&self) -> *mut sys::ttf::TTF_Font305     unsafe fn raw(&self) -> *mut sys::ttf::TTF_Font {
306         self.raw
307     }
308 
309     /// Starts specifying a rendering of the given UTF-8-encoded text.
render<'a, 'b>(&'a self, text: &'b str) -> PartialRendering<'a, 'b>310     pub fn render<'a, 'b>(&'a self, text: &'b str) -> PartialRendering<'a, 'b> {
311         PartialRendering {
312             text: RenderableText::Utf8(text),
313             font: self,
314         }
315     }
316 
317     /// Starts specifying a rendering of the given Latin-1-encoded text.
render_latin1<'a, 'b>(&'a self, text: &'b [u8]) -> PartialRendering<'a, 'b>318     pub fn render_latin1<'a, 'b>(&'a self, text: &'b [u8]) -> PartialRendering<'a, 'b> {
319         PartialRendering {
320             text: RenderableText::Latin1(text),
321             font: self,
322         }
323     }
324 
325     /// Starts specifying a rendering of the given UTF-8-encoded character.
render_char<'a>(&'a self, ch: char) -> PartialRendering<'a, 'static>326     pub fn render_char<'a>(&'a self, ch: char) -> PartialRendering<'a, 'static> {
327         let mut s = String::new();
328         s.push(ch);
329         PartialRendering {
330             text: RenderableText::Char(s),
331             font: self,
332         }
333     }
334 
335     /// Returns the width and height of the given text when rendered using this
336     /// font.
size_of(&self, text: &str) -> FontResult<(u32, u32)>337     pub fn size_of(&self, text: &str) -> FontResult<(u32, u32)> {
338         let c_string = try!(RenderableText::Utf8(text).convert());
339         let (res, size) = unsafe {
340             let mut w = 0; // mutated by C code
341             let mut h = 0; // mutated by C code
342             let ret = sys::ttf::TTF_SizeUTF8(self.raw, c_string.as_ptr(), &mut w, &mut h);
343             (ret, (w as u32, h as u32))
344         };
345         if res == 0 {
346             Ok(size)
347         } else {
348             Err(FontError::SdlError(get_error()))
349         }
350     }
351 
352     /// Returns the width and height of the given text when rendered using this
353     /// font.
354     #[allow(unused_mut)]
size_of_latin1(&self, text: &[u8]) -> FontResult<(u32, u32)>355     pub fn size_of_latin1(&self, text: &[u8])
356         -> FontResult<(u32, u32)> {
357         let c_string = try!(RenderableText::Latin1(text).convert());
358         let (res, size) = unsafe {
359             let mut w : i32 = 0; // mutated by C code
360             let mut h : i32 = 0; // mutated by C code
361             let ret = sys::ttf::TTF_SizeText(self.raw, c_string.as_ptr(), &w as *const _ as *mut i32, &h as *const _ as *mut i32);
362             (ret, (w as u32, h as u32))
363         };
364         if res == 0 {
365             Ok(size)
366         } else {
367             Err(FontError::SdlError(get_error()))
368         }
369     }
370 
371     /// Returns the width and height of the given text when rendered using this
372     /// font.
size_of_char(&self, ch: char) -> FontResult<(u32, u32)>373     pub fn size_of_char(&self, ch: char) -> FontResult<(u32, u32)> {
374         let mut s = String::new();
375         s.push(ch);
376         self.size_of(&s)
377     }
378 
379     /// Returns the font's style flags.
get_style(&self) -> FontStyle380     pub fn get_style(&self) -> FontStyle {
381         unsafe {
382             let raw = sys::ttf::TTF_GetFontStyle(self.raw);
383             FontStyle::from_bits_truncate(raw as i32)
384         }
385     }
386 
387     /// Sets the font's style flags.
set_style(&mut self, styles: FontStyle)388     pub fn set_style(&mut self, styles: FontStyle) {
389         unsafe {
390             sys::ttf::TTF_SetFontStyle(self.raw, styles.bits() as c_int)
391         }
392     }
393 
394     /// Returns the width of the font's outline.
get_outline_width(&self) -> u16395     pub fn get_outline_width(&self) -> u16 {
396         unsafe {
397             sys::ttf::TTF_GetFontOutline(self.raw) as u16
398         }
399     }
400 
401     /// Sets the width of the font's outline.
set_outline_width(&mut self, width: u16)402     pub fn set_outline_width(&mut self, width: u16) {
403         unsafe {
404             sys::ttf::TTF_SetFontOutline(self.raw, width as c_int)
405         }
406     }
407 
408     /// Returns the font's freetype hints.
get_hinting(&self) -> Hinting409     pub fn get_hinting(&self) -> Hinting {
410         unsafe {
411             match sys::ttf::TTF_GetFontHinting(self.raw) as c_uint {
412                 sys::ttf::TTF_HINTING_NORMAL   => Hinting::Normal,
413                 sys::ttf::TTF_HINTING_LIGHT    => Hinting::Light,
414                 sys::ttf::TTF_HINTING_MONO     => Hinting::Mono,
415                 sys::ttf::TTF_HINTING_NONE | _ => Hinting::None
416             }
417         }
418     }
419 
420     /// Sets the font's freetype hints.
set_hinting(&mut self, hinting: Hinting)421     pub fn set_hinting(&mut self, hinting: Hinting) {
422         unsafe {
423             sys::ttf::TTF_SetFontHinting(self.raw, hinting as c_int)
424         }
425     }
426 
427     /// Returns whether the font is kerning.
get_kerning(&self) -> bool428     pub fn get_kerning(&self) -> bool {
429         unsafe {
430             sys::ttf::TTF_GetFontKerning(self.raw) != 0
431         }
432     }
433 
434     /// Sets whether the font should use kerning.
set_kerning(&mut self, kerning: bool)435     pub fn set_kerning(&mut self, kerning: bool) {
436         unsafe {
437             sys::ttf::TTF_SetFontKerning(self.raw, kerning as c_int)
438         }
439     }
440 
height(&self) -> i32441     pub fn height(&self) -> i32 {
442         //! Get font maximum total height.
443         unsafe {
444             sys::ttf::TTF_FontHeight(self.raw) as i32
445         }
446     }
447 
448     /// Returns the font's highest ascent (height above base).
ascent(&self) -> i32449     pub fn ascent(&self) -> i32 {
450         unsafe {
451             sys::ttf::TTF_FontAscent(self.raw) as i32
452         }
453     }
454 
455     /// Returns the font's lowest descent (height below base).
456     /// This is a negative number.
descent(&self) -> i32457     pub fn descent(&self) -> i32 {
458         unsafe {
459             sys::ttf::TTF_FontDescent(self.raw) as i32
460         }
461     }
462 
463     /// Returns the recommended line spacing for text rendered with this font.
recommended_line_spacing(&self) -> i32464     pub fn recommended_line_spacing(&self) -> i32 {
465         unsafe {
466             sys::ttf::TTF_FontLineSkip(self.raw) as i32
467         }
468     }
469 
470     /// Returns the number of faces in this font.
face_count(&self) -> u16471     pub fn face_count(&self) -> u16 {
472         unsafe {
473             sys::ttf::TTF_FontFaces(self.raw) as u16
474         }
475     }
476 
477     /// Returns whether the font is monospaced.
face_is_fixed_width(&self) -> bool478     pub fn face_is_fixed_width(&self) -> bool {
479         unsafe {
480             sys::ttf::TTF_FontFaceIsFixedWidth(self.raw) != 0
481         }
482     }
483 
484     /// Returns the family name of the current font face.
face_family_name(&self) -> Option<String>485     pub fn face_family_name(&self) -> Option<String> {
486         unsafe {
487             // not owns buffer
488             let cname = sys::ttf::TTF_FontFaceFamilyName(self.raw);
489             if cname.is_null() {
490                 None
491             } else {
492                 Some(String::from_utf8_lossy(CStr::from_ptr(cname).to_bytes()).to_string())
493             }
494         }
495     }
496 
497     /// Returns the name of the current font face.
face_style_name(&self) -> Option<String>498     pub fn face_style_name(&self) -> Option<String> {
499         unsafe {
500             let cname = sys::ttf::TTF_FontFaceStyleName(self.raw);
501             if cname.is_null() {
502                 None
503             } else {
504                 Some(String::from_utf8_lossy(CStr::from_ptr(cname).to_bytes()).to_string())
505             }
506         }
507     }
508 
509     /// Returns the index of the given character in this font face.
find_glyph(&self, ch: char) -> Option<u16>510     pub fn find_glyph(&self, ch: char) -> Option<u16> {
511         unsafe {
512             let ret = sys::ttf::TTF_GlyphIsProvided(self.raw, ch as u16);
513             if ret == 0 {
514                 None
515             } else {
516                 Some(ret as u16)
517             }
518         }
519     }
520 
521     /// Returns the glyph metrics of the given character in this font face.
find_glyph_metrics(&self, ch: char) -> Option<GlyphMetrics>522     pub fn find_glyph_metrics(&self, ch: char) -> Option<GlyphMetrics> {
523         let minx = 0;
524         let maxx = 0;
525         let miny = 0;
526         let maxy = 0;
527         let advance = 0;
528         let ret = unsafe {
529             sys::ttf::TTF_GlyphMetrics(
530                 self.raw,
531                 ch as u16,
532                 &minx as *const _ as *mut _,
533                 &maxx as *const _ as *mut _,
534                 &miny as *const _ as *mut _,
535                 &maxy as *const _ as *mut _,
536                 &advance as *const _ as *mut _
537             )
538         };
539         if ret == 0 {
540             Some(GlyphMetrics {
541                 minx: minx as i32, maxx: maxx as i32, miny: miny as i32,
542                 maxy: maxy as i32, advance: advance as i32
543             } )
544         } else {
545             None
546         }
547     }
548 }
549