diff --git a/README.markdown b/README.markdown index 4abfa91..5f413b9 100644 --- a/README.markdown +++ b/README.markdown @@ -15,6 +15,7 @@ ui.window(im_str!("Hello world")) .size((300.0, 100.0), ImGuiCond::FirstUseEver) .build(|| { ui.text(im_str!("Hello world!")); + ui.text(im_str!("こんにちは世界!")); ui.text(im_str!("This...is...imgui-rs!")); ui.separator(); let mouse_pos = ui.imgui().mouse_pos(); diff --git a/hello_world.png b/hello_world.png index 0561770..886b902 100644 Binary files a/hello_world.png and b/hello_world.png differ diff --git a/imgui-examples/examples/LICENSE.mplus b/imgui-examples/examples/LICENSE.mplus new file mode 100644 index 0000000..e8fa893 --- /dev/null +++ b/imgui-examples/examples/LICENSE.mplus @@ -0,0 +1,16 @@ +M+ FONTS Copyright (C) 2002-2017 M+ FONTS PROJECT + +- + +LICENSE_E + + + + +These fonts are free software. +Unlimited permission is granted to use, copy, and distribute them, with +or without modification, either commercially or noncommercially. +THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY. + + +http://mplus-fonts.osdn.jp diff --git a/imgui-examples/examples/hello_gfx.rs b/imgui-examples/examples/hello_gfx.rs index 5bcdc48..cda16b9 100644 --- a/imgui-examples/examples/hello_gfx.rs +++ b/imgui-examples/examples/hello_gfx.rs @@ -18,6 +18,7 @@ fn hello_world<'a>(ui: &Ui<'a>) -> bool { .size((300.0, 100.0), ImGuiCond::FirstUseEver) .build(|| { ui.text(im_str!("Hello world!")); + ui.text(im_str!("こんにちは世界!")); ui.text(im_str!("This...is...imgui-rs!")); ui.separator(); let mouse_pos = ui.imgui().mouse_pos(); diff --git a/imgui-examples/examples/hello_world.rs b/imgui-examples/examples/hello_world.rs index eec0e78..b5dddef 100644 --- a/imgui-examples/examples/hello_world.rs +++ b/imgui-examples/examples/hello_world.rs @@ -16,6 +16,7 @@ fn hello_world<'a>(ui: &Ui<'a>) -> bool { .size((300.0, 100.0), ImGuiCond::FirstUseEver) .build(|| { ui.text(im_str!("Hello world!")); + ui.text(im_str!("こんにちは世界!")); ui.text(im_str!("This...is...imgui-rs!")); ui.separator(); let mouse_pos = ui.imgui().mouse_pos(); diff --git a/imgui-examples/examples/mplus-1p-regular.ttf b/imgui-examples/examples/mplus-1p-regular.ttf new file mode 100644 index 0000000..4775e3a Binary files /dev/null and b/imgui-examples/examples/mplus-1p-regular.ttf differ diff --git a/imgui-examples/examples/support/mod.rs b/imgui-examples/examples/support/mod.rs index e664c83..7e59b1a 100644 --- a/imgui-examples/examples/support/mod.rs +++ b/imgui-examples/examples/support/mod.rs @@ -1,4 +1,4 @@ -use imgui::{ImGui, ImGuiMouseCursor, Ui}; +use imgui::{FontGlyphRange, ImFontConfig, ImGui, ImGuiMouseCursor, Ui}; use std::time::Instant; #[derive(Copy, Clone, PartialEq, Debug, Default)] @@ -22,6 +22,10 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ let mut imgui = ImGui::init(); imgui.set_ini_filename(None); + let config = ImFontConfig::new().oversample_h(1).pixel_snap_h(true).size_pixels(13.0); + config.rasterizer_multiply(1.75).add_font( + &mut imgui.fonts(), include_bytes!("../mplus-1p-regular.ttf"), &FontGlyphRange::japanese()); + config.merge_mode(true).add_default_font(&mut imgui.fonts()); let mut renderer = Renderer::init(&mut imgui, &display).expect("Failed to initialize renderer"); configure_keys(&mut imgui); diff --git a/imgui-examples/examples/support_gfx/mod.rs b/imgui-examples/examples/support_gfx/mod.rs index b019fca..9915acd 100644 --- a/imgui-examples/examples/support_gfx/mod.rs +++ b/imgui-examples/examples/support_gfx/mod.rs @@ -1,4 +1,4 @@ -use imgui::{ImGui, ImGuiMouseCursor, Ui}; +use imgui::{FontGlyphRange, ImFontConfig, ImGui, ImGuiMouseCursor, Ui}; use imgui_gfx_renderer::{Renderer, Shaders}; use std::time::Instant; @@ -44,6 +44,11 @@ pub fn run bool>(title: String, clear_color: [f32; 4], mut run_ }; let mut imgui = ImGui::init(); + imgui.set_ini_filename(None); + let config = ImFontConfig::new().oversample_h(1).pixel_snap_h(true).size_pixels(13.0); + config.rasterizer_multiply(1.75).add_font( + &mut imgui.fonts(), include_bytes!("../mplus-1p-regular.ttf"), &FontGlyphRange::japanese()); + config.merge_mode(true).add_default_font(&mut imgui.fonts()); let mut renderer = Renderer::init(&mut imgui, &mut factory, shaders, main_color.clone()) .expect("Failed to initialize renderer"); diff --git a/imgui-sys/src/lib.rs b/imgui-sys/src/lib.rs index 7879dc7..5908569 100644 --- a/imgui-sys/src/lib.rs +++ b/imgui-sys/src/lib.rs @@ -13,6 +13,7 @@ extern crate glium; use std::convert::From; use std::mem; use std::os::raw::{c_char, c_float, c_int, c_short, c_uchar, c_uint, c_ushort, c_void}; +use std::ptr; use std::slice; #[cfg(feature = "gfx")] @@ -735,6 +736,31 @@ pub struct ImFontConfig { name: [c_char; 32], dst_font: *mut ImFont, } +impl ImFontConfig { + // This function only exists because cimgui does not provide a wrapper around + // `ImGuiConfig::ImGuiConfig()`. This code is based off that constructor. + pub fn new() -> ImFontConfig { + ImFontConfig { + font_data: ptr::null_mut(), + font_data_size: 0, + font_data_owned_by_atlas: true, + font_no: 0, + size_pixels: 0.0, + oversample_h: 3, + oversample_v: 1, + pixel_snap_h: false, + glyph_extra_spacing: ImVec2::zero(), + glyph_offset: ImVec2::zero(), + glyph_ranges: ptr::null(), + merge_mode: false, + rasterizer_flags: 0, + rasterizer_multiply: 1.0, + + name: [0; 32], + dst_font: ptr::null_mut(), + } + } +} #[repr(C)] #[derive(Copy, Clone, Debug, Default)] diff --git a/src/fonts.rs b/src/fonts.rs new file mode 100644 index 0000000..4602eeb --- /dev/null +++ b/src/fonts.rs @@ -0,0 +1,310 @@ +use std::marker::PhantomData; +use std::mem; +use std::os::raw::{c_float, c_int, c_void}; +use std::ptr; +use sys; + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +enum FontGlyphRangeData { + Chinese, Cyrillic, Default, Japanese, Korean, Thai, Custom(*const sys::ImWchar), +} + +/// A set of 16-bit Unicode codepoints +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct FontGlyphRange(FontGlyphRangeData); +impl FontGlyphRange { + /// The default set of glyph ranges used by imgui. + pub fn default() -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Default) + } + + /// A set of glyph ranges appropriate for use with Chinese text. + pub fn chinese() -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Chinese) + } + /// A set of glyph ranges appropriate for use with Cyrillic text. + pub fn cyrillic() -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Cyrillic) + } + /// A set of glyph ranges appropriate for use with Japanese text. + pub fn japanese() -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Japanese) + } + /// A set of glyph ranges appropriate for use with Korean text. + pub fn korean() -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Korean) + } + /// A set of glyph ranges appropriate for use with Thai text. + pub fn thai() -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Thai) + } + + /// Creates a glyph range from a static slice. The expected format is a series of pairs of + /// non-zero shorts, each representing an inclusive range of codepoints, followed by a single + /// zero terminating the range. The ranges must not overlap. + /// + /// As the slice is expected to last as long as a font is used, and is written into global + /// state, it must be `'static`. + /// + /// Panics + /// ====== + /// + /// This function will panic if the given slice is not a valid font range. + pub fn from_slice(slice: &'static [sys::ImWchar]) -> FontGlyphRange { + assert_eq!(slice.len() % 2, 1, "The length of a glyph range must be odd."); + assert_eq!(slice.last(), Some(&0), "A glyph range must be zero-terminated."); + + for i in 0..slice.len()-1 { + assert_ne!(slice[i], 0, "A glyph in a range cannot be zero. \ + (Glyph is zero at index {})", i) + } + + let mut ranges = Vec::new(); + for i in 0..slice.len()/2 { + let (start, end) = (slice[i * 2], slice[i * 2 + 1]); + assert!(start <= end, "The start of a range cannot be larger than its end. \ + (At index {}, {} > {})", i * 2, start, end); + ranges.push((start, end)); + } + ranges.sort_unstable_by_key(|x| x.0); + for i in 0..ranges.len()-1 { + let (range_a, range_b) = (ranges[i], ranges[i + 1]); + if range_a.1 >= range_b.0 { + panic!("The glyph ranges {:?} and {:?} overlap between {:?}.", + range_a, range_b, (range_a.1, range_b.0)); + } + } + + unsafe { FontGlyphRange::from_slice_unchecked(slice) } + } + + /// Creates a glyph range from a static slice without checking its validity. + /// + /// See [`FontRangeGlyph::from_slice`] for more information. + pub unsafe fn from_slice_unchecked(slice: &'static [sys::ImWchar]) -> FontGlyphRange { + FontGlyphRange::from_ptr(slice.as_ptr()) + } + + /// Creates a glyph range from a pointer, without checking its validity or enforcing its + /// lifetime. The memory the pointer points to must be valid for as long as the font is + /// in use. + pub unsafe fn from_ptr(ptr: *const sys::ImWchar) -> FontGlyphRange { + FontGlyphRange(FontGlyphRangeData::Custom(ptr)) + } + + unsafe fn to_ptr(&self, atlas: *mut sys::ImFontAtlas) -> *const sys::ImWchar { + match &self.0 { + &FontGlyphRangeData::Chinese => sys::ImFontAtlas_GetGlyphRangesChinese(atlas), + &FontGlyphRangeData::Cyrillic => sys::ImFontAtlas_GetGlyphRangesCyrillic(atlas), + &FontGlyphRangeData::Default => sys::ImFontAtlas_GetGlyphRangesDefault(atlas), + &FontGlyphRangeData::Japanese => sys::ImFontAtlas_GetGlyphRangesJapanese(atlas), + &FontGlyphRangeData::Korean => sys::ImFontAtlas_GetGlyphRangesKorean(atlas), + &FontGlyphRangeData::Thai => sys::ImFontAtlas_GetGlyphRangesThai(atlas), + + &FontGlyphRangeData::Custom(ptr) => ptr, + } + } +} + +/// A builder for the configuration for a font. +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct ImFontConfig { + size_pixels: f64, oversample_h: u32, oversample_v: u32, pixel_snap_h: bool, + glyph_extra_spacing: sys::ImVec2, glyph_offset: sys::ImVec2, merge_mode: bool, + rasterizer_multiply: f64, +} +impl ImFontConfig { + pub fn new() -> ImFontConfig { + ImFontConfig { + size_pixels: 0.0, oversample_h: 3, oversample_v: 1, pixel_snap_h: false, + glyph_extra_spacing: sys::ImVec2::zero(), glyph_offset: sys::ImVec2::zero(), + merge_mode: false, rasterizer_multiply: 1.0, + } + } + + pub fn size_pixels(mut self, size_pixels: f64) -> ImFontConfig { + self.size_pixels = size_pixels; + self + } + pub fn oversample_h(mut self, oversample_h: u32) -> ImFontConfig { + self.oversample_h = oversample_h; + self + } + pub fn oversample_v(mut self, oversample_v: u32) -> ImFontConfig { + self.oversample_v = oversample_v; + self + } + pub fn pixel_snap_h(mut self, pixel_snap_h: bool) -> ImFontConfig { + self.pixel_snap_h = pixel_snap_h; + self + } + pub fn glyph_extra_spacing(mut self, glyph_extra_spacing: sys::ImVec2) -> ImFontConfig { + self.glyph_extra_spacing = glyph_extra_spacing; + self + } + pub fn glyph_offset(mut self, glyph_offset: sys::ImVec2) -> ImFontConfig { + self.glyph_offset = glyph_offset; + self + } + pub fn merge_mode(mut self, merge_mode: bool) -> ImFontConfig { + self.merge_mode = merge_mode; + self + } + pub fn rasterizer_multiply(mut self, rasterizer_multiply: f64) -> ImFontConfig { + self.rasterizer_multiply = rasterizer_multiply; + self + } + + fn make_config(self) -> sys::ImFontConfig { + let mut config = sys::ImFontConfig::new(); + config.size_pixels = self.size_pixels as c_float; + config.oversample_h = self.oversample_h as c_int; + config.oversample_v = self.oversample_v as c_int; + config.pixel_snap_h = self.pixel_snap_h; + config.glyph_extra_spacing = self.glyph_extra_spacing; + config.glyph_offset = self.glyph_offset; + config.merge_mode = self.merge_mode; + config.rasterizer_multiply = self.rasterizer_multiply as c_float; + config + } + + /// Adds a custom font to the font set with the given configuration. A font size must be set + /// in the configuration. + /// + /// Panics + /// ====== + /// + /// If no font size is set for the configuration. + pub fn add_font<'a>(self, atlas: &'a mut ImFontAtlas<'a>, data: &[u8], + range: &FontGlyphRange) -> ImFont<'a> { + atlas.add_font_with_config(data, self, range) + } + + /// Adds the default font to a given atlas using this configuration. + pub fn add_default_font<'a>(self, atlas: &'a mut ImFontAtlas<'a>) -> ImFont<'a> { + atlas.add_default_font_with_config(self) + } +} +impl Default for ImFontConfig { + fn default() -> Self { + ImFontConfig::new() + } +} + +/// A handle to an imgui font. +pub struct ImFont<'a> { + font: *mut sys::ImFont, _phantom: PhantomData<&'a mut sys::ImFont>, +} +impl <'a> ImFont<'a> { + unsafe fn from_ptr(font: *mut sys::ImFont) -> ImFont<'a> { + ImFont { font, _phantom: PhantomData } + } + + fn chain(&mut self) -> ImFont { + ImFont { font: self.font, _phantom: PhantomData } + } + + pub fn font_size(&self) -> f64 { + unsafe { sys::ImFont_GetFontSize(self.font) as f64 } + } + pub fn set_font_size(&mut self, size: f64) -> ImFont { + unsafe { sys::ImFont_SetFontSize(self.font, size as c_float) } + self.chain() + } + + pub fn scale(&self) -> f64 { + unsafe { sys::ImFont_GetScale(self.font) as f64 } + } + pub fn set_scale(&mut self, size: f64) -> ImFont { + unsafe { sys::ImFont_SetScale(self.font, size as c_float) } + self.chain() + } + + pub fn display_offset(&self) -> sys::ImVec2 { + let mut display_offset = unsafe { mem::uninitialized() }; + unsafe { sys::ImFont_GetDisplayOffset(self.font, &mut display_offset) } + display_offset + } +} + +/// A handle to imgui's font manager. +#[repr(C)] +pub struct ImFontAtlas<'a> { + atlas: *mut sys::ImFontAtlas, _phantom: PhantomData<&'a mut sys::ImFontAtlas>, +} +impl <'a> ImFontAtlas<'a> { + pub(crate) unsafe fn from_ptr(atlas: *mut sys::ImFontAtlas) -> ImFontAtlas<'a> { + ImFontAtlas { atlas, _phantom: PhantomData } + } + + /// Adds the default font to the font set. + pub fn add_default_font(&mut self) -> ImFont { + unsafe { ImFont::from_ptr(sys::ImFontAtlas_AddFontDefault(self.atlas, ptr::null_mut())) } + } + + /// Adds the default fnt to the font set with the given configuration. + pub fn add_default_font_with_config(&mut self, config: ImFontConfig) -> ImFont { + let config = config.make_config(); + unsafe { ImFont::from_ptr(sys::ImFontAtlas_AddFontDefault(self.atlas, &config)) } + } + + fn raw_add_font(&mut self, data: &[u8], config: ImFontConfig, + range: &FontGlyphRange) -> ImFont { + assert!((data.len() as u64) < (c_int::max_value() as u64), "Font data is too long."); + unsafe { + let mut config = config.make_config(); + assert!(config.size_pixels > 0.0, "Font size cannot be zero."); + config.font_data = data.as_ptr() as *mut c_void; + config.font_data_size = data.len() as c_int; + config.glyph_ranges = range.to_ptr(self.atlas); + config.font_data_owned_by_atlas = false; + + ImFont::from_ptr(sys::ImFontAtlas_AddFont(self.atlas, &config)) + } + } + + /// Adds a custom font to the font set. + pub fn add_font(&mut self, data: &[u8], size: f64, range: &FontGlyphRange) -> ImFont { + self.raw_add_font(data, ImFontConfig::new().size_pixels(size), range) + } + + /// Adds a custom font to the font set with the given configuration. A font size must be set + /// in the configuration. + /// + /// Panics + /// ====== + /// + /// If no font size is set for the configuration. + pub fn add_font_with_config(&mut self, data: &[u8], config: ImFontConfig, + range: &FontGlyphRange) -> ImFont { + self.raw_add_font(data, config, range) + } + + /// The number of fonts currently registered in the atlas. + pub fn font_count(&self) -> usize { + unsafe { sys::ImFontAtlas_Fonts_size(self.atlas) as usize } + } + + /// Gets a font from the atlas. + /// + /// Panics + /// ====== + /// + /// Panics if the index is out of range. + pub fn index_font(&mut self, index: usize) -> ImFont { + assert!(index < self.font_count(), "Font index is out of range."); + unsafe { ImFont::from_ptr(sys::ImFontAtlas_Fonts_index(self.atlas, index as c_int)) } + } + + /// Clears all fonts associated with this texture atlas. + pub fn clear(&mut self) { + unsafe { sys::ImFontAtlas_Clear(self.atlas) } + } + + pub fn texture_id(&self) -> usize { + unsafe { (*self.atlas).tex_id as usize } + } + pub fn set_texture_id(&mut self, value: usize) { + unsafe { (*self.atlas).tex_id = value as *mut c_void; } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c6e3264..cc67855 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub extern crate imgui_sys as sys; use std::ffi::CStr; use std::mem; -use std::os::raw::{c_char, c_float, c_int, c_uchar, c_void}; +use std::os::raw::{c_char, c_float, c_int, c_uchar}; use std::ptr; use std::slice; use std::str; @@ -14,6 +14,7 @@ pub use sys::{ImDrawIdx, ImDrawVert, ImGuiColorEditFlags, ImGuiHoveredFlags, ImG pub use child_frame::ChildFrame; pub use color_editors::{ColorButton, ColorEdit, ColorEditMode, ColorFormat, ColorPicker, ColorPickerMode, ColorPreview, EditableColor}; +pub use fonts::{FontGlyphRange, ImFontAtlas, ImFont, ImFontConfig}; pub use input::{InputFloat, InputFloat2, InputFloat3, InputFloat4, InputInt, InputInt2, InputInt3, InputInt4, InputText}; pub use menus::{Menu, MenuItem}; @@ -29,6 +30,7 @@ pub use window::Window; mod child_frame; mod color_editors; +mod fonts; mod input; mod menus; mod plothistogram; @@ -93,6 +95,7 @@ impl ImGui { fn io_mut(&mut self) -> &mut sys::ImGuiIO { unsafe { &mut *sys::igGetIO() } } pub fn style(&self) -> &ImGuiStyle { unsafe { &*sys::igGetStyle() } } pub fn style_mut(&mut self) -> &mut ImGuiStyle { unsafe { &mut *sys::igGetStyle() } } + pub fn fonts(&mut self) -> ImFontAtlas { unsafe { ImFontAtlas::from_ptr(self.io_mut().fonts) } } pub fn prepare_texture<'a, F, T>(&mut self, f: F) -> T where F: FnOnce(TextureHandle<'a>) -> T, @@ -118,9 +121,7 @@ impl ImGui { } } pub fn set_texture_id(&mut self, value: usize) { - unsafe { - (*self.io_mut().fonts).tex_id = value as *mut c_void; - } + self.fonts().set_texture_id(value); } pub fn set_ini_filename(&mut self, value: Option) { {