use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
use dwrote::{
FontCollection, FontFace, FontFallback, FontStretch, FontStyle, FontWeight, GlyphOffset,
GlyphRunAnalysis, TextAnalysisSource, TextAnalysisSourceMethods, DWRITE_GLYPH_RUN,
};
use winapi::shared::ntdef::{HRESULT, LOCALE_NAME_MAX_LENGTH};
use winapi::um::dwrite;
use winapi::um::winnls::GetUserDefaultLocaleName;
use super::{
BitmapBuffer, Error, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style,
Weight,
};
const MISSING_GLYPH_INDEX: u16 = 0;
struct Font {
face: FontFace,
family_name: String,
weight: FontWeight,
style: FontStyle,
stretch: FontStretch,
}
pub struct DirectWriteRasterizer {
fonts: HashMap<FontKey, Font>,
keys: HashMap<FontDesc, FontKey>,
device_pixel_ratio: f32,
available_fonts: FontCollection,
fallback_sequence: Option<FontFallback>,
}
impl DirectWriteRasterizer {
fn rasterize_glyph(
&self,
face: &FontFace,
size: Size,
character: char,
glyph_index: u16,
) -> Result<RasterizedGlyph, Error> {
let em_size = em_size(size);
let glyph_run = DWRITE_GLYPH_RUN {
fontFace: unsafe { face.as_ptr() },
fontEmSize: em_size,
glyphCount: 1,
glyphIndices: &glyph_index,
glyphAdvances: &0.0,
glyphOffsets: &GlyphOffset::default(),
isSideways: 0,
bidiLevel: 0,
};
let rendering_mode = face.get_recommended_rendering_mode_default_params(
em_size,
self.device_pixel_ratio,
dwrote::DWRITE_MEASURING_MODE_NATURAL,
);
let glyph_analysis = GlyphRunAnalysis::create(
&glyph_run,
self.device_pixel_ratio,
None,
rendering_mode,
dwrote::DWRITE_MEASURING_MODE_NATURAL,
0.0,
0.0,
)?;
let bounds =
glyph_analysis.get_alpha_texture_bounds(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1)?;
let buffer = BitmapBuffer::Rgb(
glyph_analysis.create_alpha_texture(dwrote::DWRITE_TEXTURE_CLEARTYPE_3x1, bounds)?,
);
Ok(RasterizedGlyph {
character,
width: (bounds.right - bounds.left) as i32,
height: (bounds.bottom - bounds.top) as i32,
top: -bounds.top,
left: bounds.left,
advance: (0, 0),
buffer,
})
}
fn get_loaded_font(&self, font_key: FontKey) -> Result<&Font, Error> {
self.fonts.get(&font_key).ok_or(Error::UnknownFontKey)
}
fn get_glyph_index(&self, face: &FontFace, character: char) -> u16 {
face.get_glyph_indices(&[character as u32]).first().copied().unwrap_or(MISSING_GLYPH_INDEX)
}
fn get_fallback_font(&self, loaded_font: &Font, character: char) -> Option<dwrote::Font> {
let fallback = self.fallback_sequence.as_ref()?;
let mut buffer = [0u16; 2];
character.encode_utf16(&mut buffer);
let length = character.len_utf16() as u32;
let utf16_codepoints = &buffer[..length as usize];
let locale = get_current_locale();
let text_analysis_source_data = TextAnalysisSourceData { locale: &locale, length };
let text_analysis_source = TextAnalysisSource::from_text(
Box::new(text_analysis_source_data),
Cow::Borrowed(utf16_codepoints),
);
let fallback_result = fallback.map_characters(
&text_analysis_source,
0,
length,
&self.available_fonts,
Some(&loaded_font.family_name),
loaded_font.weight,
loaded_font.style,
loaded_font.stretch,
);
fallback_result.mapped_font
}
}
impl crate::Rasterize for DirectWriteRasterizer {
fn new(device_pixel_ratio: f32, _: bool) -> Result<DirectWriteRasterizer, Error> {
Ok(DirectWriteRasterizer {
fonts: HashMap::new(),
keys: HashMap::new(),
device_pixel_ratio,
available_fonts: FontCollection::system(),
fallback_sequence: FontFallback::get_system_fallback(),
})
}
fn metrics(&self, key: FontKey, size: Size) -> Result<Metrics, Error> {
let face = &self.get_loaded_font(key)?.face;
let vmetrics = face.metrics().metrics0();
let scale = em_size(size) * self.device_pixel_ratio / f32::from(vmetrics.designUnitsPerEm);
let underline_position = f32::from(vmetrics.underlinePosition) * scale;
let underline_thickness = f32::from(vmetrics.underlineThickness) * scale;
let strikeout_position = f32::from(vmetrics.strikethroughPosition) * scale;
let strikeout_thickness = f32::from(vmetrics.strikethroughThickness) * scale;
let ascent = f32::from(vmetrics.ascent) * scale;
let descent = -f32::from(vmetrics.descent) * scale;
let line_gap = f32::from(vmetrics.lineGap) * scale;
let line_height = f64::from(ascent - descent + line_gap);
let character = '!';
let glyph_index = self.get_glyph_index(face, character);
let glyph_metrics = face.get_design_glyph_metrics(&[glyph_index], false);
let hmetrics = glyph_metrics.first().ok_or(Error::MetricsNotFound)?;
let average_advance = f64::from(hmetrics.advanceWidth) * f64::from(scale);
Ok(Metrics {
descent,
average_advance,
line_height,
underline_position,
underline_thickness,
strikeout_position,
strikeout_thickness,
})
}
fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> {
if let Some(key) = self.keys.get(desc) {
return Ok(*key);
}
let family = self
.available_fonts
.get_font_family_by_name(&desc.name)
.ok_or_else(|| Error::FontNotFound(desc.clone()))?;
let font = match desc.style {
Style::Description { weight, slant } => {
Ok(family.get_first_matching_font(weight.into(), FontStretch::Normal, slant.into()))
},
Style::Specific(ref style) => {
let mut idx = 0;
let count = family.get_font_count();
loop {
if idx == count {
break Err(Error::FontNotFound(desc.clone()));
}
let font = family.get_font(idx);
if font.face_name() == *style {
break Ok(font);
}
idx += 1;
}
},
}?;
let key = FontKey::next();
self.keys.insert(desc.clone(), key);
self.fonts.insert(key, font.into());
Ok(key)
}
fn get_glyph(&mut self, glyph: GlyphKey) -> Result<RasterizedGlyph, Error> {
let loaded_font = self.get_loaded_font(glyph.font_key)?;
let loaded_fallback_font;
let mut font = loaded_font;
let mut glyph_index = self.get_glyph_index(&loaded_font.face, glyph.character);
if glyph_index == MISSING_GLYPH_INDEX {
if let Some(fallback_font) = self.get_fallback_font(loaded_font, glyph.character) {
loaded_fallback_font = Font::from(fallback_font);
glyph_index = self.get_glyph_index(&loaded_fallback_font.face, glyph.character);
font = &loaded_fallback_font;
}
}
let rasterized_glyph =
self.rasterize_glyph(&font.face, glyph.size, glyph.character, glyph_index)?;
if glyph_index == MISSING_GLYPH_INDEX {
Err(Error::MissingGlyph(rasterized_glyph))
} else {
Ok(rasterized_glyph)
}
}
fn kerning(&mut self, left: GlyphKey, right: GlyphKey) -> (f32, f32) {
(0., 0.)
}
fn update_dpr(&mut self, device_pixel_ratio: f32) {
self.device_pixel_ratio = device_pixel_ratio;
}
}
fn em_size(size: Size) -> f32 {
size.as_f32_pts() * (96.0 / 72.0)
}
impl From<dwrote::Font> for Font {
fn from(font: dwrote::Font) -> Font {
Font {
face: font.create_font_face(),
family_name: font.family_name(),
weight: font.weight(),
style: font.style(),
stretch: font.stretch(),
}
}
}
impl From<Weight> for FontWeight {
fn from(weight: Weight) -> FontWeight {
match weight {
Weight::Bold => FontWeight::Bold,
Weight::Normal => FontWeight::Regular,
}
}
}
impl From<Slant> for FontStyle {
fn from(slant: Slant) -> FontStyle {
match slant {
Slant::Oblique => FontStyle::Oblique,
Slant::Italic => FontStyle::Italic,
Slant::Normal => FontStyle::Normal,
}
}
}
fn get_current_locale() -> String {
let mut buffer = vec![0u16; LOCALE_NAME_MAX_LENGTH];
let len =
unsafe { GetUserDefaultLocaleName(buffer.as_mut_ptr(), buffer.len() as i32) as usize };
OsString::from_wide(&buffer[..len - 1]).into_string().expect("Locale not valid unicode")
}
struct TextAnalysisSourceData<'a> {
locale: &'a str,
length: u32,
}
impl TextAnalysisSourceMethods for TextAnalysisSourceData<'_> {
fn get_locale_name(&self, _text_position: u32) -> (Cow<str>, u32) {
(Cow::Borrowed(self.locale), self.length)
}
fn get_paragraph_reading_direction(&self) -> dwrite::DWRITE_READING_DIRECTION {
dwrite::DWRITE_READING_DIRECTION_LEFT_TO_RIGHT
}
}
impl From<HRESULT> for Error {
fn from(hresult: HRESULT) -> Self {
let message = format!("a DirectWrite rendering error occurred: {:X}", hresult);
Error::PlatformError(message)
}
}