@@ -1,4 +1,4 @@
-use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc};
+use std::{borrow::Cow, sync::Arc};
use ::util::ResultExt;
use anyhow::Result;
@@ -9,7 +9,13 @@ use windows::{
Win32::{
Foundation::*,
Globalization::GetUserDefaultLocaleName,
- Graphics::{DirectWrite::*, Dxgi::Common::*, Gdi::LOGFONTW, Imaging::*},
+ Graphics::{
+ Direct2D::{Common::*, *},
+ DirectWrite::*,
+ Dxgi::Common::*,
+ Gdi::LOGFONTW,
+ Imaging::*,
+ },
System::SystemServices::LOCALE_NAME_MAX_LENGTH,
UI::WindowsAndMessaging::*,
},
@@ -34,6 +40,7 @@ struct DirectWriteComponent {
locale: String,
factory: IDWriteFactory5,
bitmap_factory: AgileReference<IWICImagingFactory>,
+ d2d1_factory: ID2D1Factory,
in_memory_loader: IDWriteInMemoryFontFileLoader,
builder: IDWriteFontSetBuilder1,
text_renderer: Arc<TextRendererWrapper>,
@@ -42,6 +49,7 @@ struct DirectWriteComponent {
struct GlyphRenderContext {
params: IDWriteRenderingParams3,
+ dc_target: ID2D1DeviceContext4,
}
struct DirectWriteState {
@@ -66,6 +74,8 @@ impl DirectWriteComponent {
unsafe {
let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?;
let bitmap_factory = AgileReference::new(bitmap_factory)?;
+ let d2d1_factory: ID2D1Factory =
+ D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?;
// The `IDWriteInMemoryFontFileLoader` here is supported starting from
// Windows 10 Creators Update, which consequently requires the entire
// `DirectWriteTextSystem` to run on `win10 1703`+.
@@ -76,12 +86,13 @@ impl DirectWriteComponent {
GetUserDefaultLocaleName(&mut locale_vec);
let locale = String::from_utf16_lossy(&locale_vec);
let text_renderer = Arc::new(TextRendererWrapper::new(&locale));
- let render_context = GlyphRenderContext::new(&factory)?;
+ let render_context = GlyphRenderContext::new(&factory, &d2d1_factory)?;
Ok(DirectWriteComponent {
locale,
factory,
bitmap_factory,
+ d2d1_factory,
in_memory_loader,
builder,
text_renderer,
@@ -92,7 +103,7 @@ impl DirectWriteComponent {
}
impl GlyphRenderContext {
- pub fn new(factory: &IDWriteFactory5) -> Result<Self> {
+ pub fn new(factory: &IDWriteFactory5, d2d1_factory: &ID2D1Factory) -> Result<Self> {
unsafe {
let default_params: IDWriteRenderingParams3 =
factory.CreateRenderingParams()?.cast()?;
@@ -111,8 +122,17 @@ impl GlyphRenderContext {
DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC,
grid_fit_mode,
)?;
+ let dc_target = {
+ let target = d2d1_factory.CreateDCRenderTarget(&get_render_target_property(
+ DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED,
+ ))?;
+ let target = target.cast::<ID2D1DeviceContext4>()?;
+ target.SetTextRenderingParams(¶ms);
+ target
+ };
- Ok(Self { params })
+ Ok(Self { params, dc_target })
}
}
}
@@ -629,12 +649,17 @@ impl DirectWriteState {
}
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
+ let render_target = &self.components.render_context.dc_target;
+ unsafe {
+ render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
+ render_target.SetDpi(96.0 * params.scale_factor, 96.0 * params.scale_factor);
+ }
let font = &self.fonts[params.font_id.0];
let glyph_id = [params.glyph_id.0 as u16];
let advance = [0.0f32];
let offset = [DWRITE_GLYPH_OFFSET::default()];
let glyph_run = DWRITE_GLYPH_RUN {
- fontFace: ManuallyDrop::new(Some(font.font_face.cast()?)),
+ fontFace: unsafe { std::mem::transmute_copy(&font.font_face) },
fontEmSize: params.font_size.0,
glyphCount: 1,
glyphIndices: glyph_id.as_ptr(),
@@ -643,29 +668,13 @@ impl DirectWriteState {
isSideways: BOOL(0),
bidiLevel: 0,
};
-
- let transform = DWRITE_MATRIX::default();
- let rendering_mode = DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC;
- let measuring_mode = DWRITE_MEASURING_MODE_NATURAL;
- let baseline_origin_x = 0.0;
- let baseline_origin_y = 0.0;
-
- let glyph_analysis = unsafe {
- self.components.factory.CreateGlyphRunAnalysis(
+ let bounds = unsafe {
+ render_target.GetGlyphRunWorldBounds(
+ Vector2 { X: 0.0, Y: 0.0 },
&glyph_run,
- Some(&transform as *const _),
- rendering_mode,
- measuring_mode,
- DWRITE_GRID_FIT_MODE_DEFAULT,
- DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
- baseline_origin_x,
- baseline_origin_y,
+ DWRITE_MEASURING_MODE_NATURAL,
)?
};
-
- let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1;
- let bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? };
-
// todo(windows)
// This is a walkaround, deleted when figured out.
let y_offset;
@@ -687,13 +696,12 @@ impl DirectWriteState {
} else {
Ok(Bounds {
origin: point(
- ((bounds.left as f32 * params.scale_factor).ceil() as i32).into(),
- ((bounds.top as f32 * params.scale_factor).ceil() as i32 + y_offset).into(),
+ ((bounds.left * params.scale_factor).ceil() as i32).into(),
+ ((bounds.top * params.scale_factor).ceil() as i32 + y_offset).into(),
),
size: size(
- (((bounds.right - bounds.left) as f32 * params.scale_factor).ceil() as i32)
- .into(),
- (((bounds.bottom - bounds.top) as f32 * params.scale_factor).ceil() as i32
+ (((bounds.right - bounds.left) * params.scale_factor).ceil() as i32).into(),
+ (((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32
+ extra_height)
.into(),
),
@@ -731,7 +739,7 @@ impl DirectWriteState {
ascenderOffset: glyph_bounds.origin.y.0 as f32 / params.scale_factor,
}];
let glyph_run = DWRITE_GLYPH_RUN {
- fontFace: ManuallyDrop::new(Some(font_info.font_face.cast()?)),
+ fontFace: unsafe { std::mem::transmute_copy(&font_info.font_face) },
fontEmSize: params.font_size.0,
glyphCount: 1,
glyphIndices: glyph_id.as_ptr(),
@@ -751,108 +759,149 @@ impl DirectWriteState {
}
let bitmap_size = bitmap_size;
- let subpixel_shift = params
- .subpixel_variant
- .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
- let baseline_origin_x = subpixel_shift.x / params.scale_factor;
- let baseline_origin_y = subpixel_shift.y / params.scale_factor;
-
- let transform = DWRITE_MATRIX {
- m11: params.scale_factor,
- m12: 0.0,
- m21: 0.0,
- m22: params.scale_factor,
- dx: 0.0,
- dy: 0.0,
- };
-
- let rendering_mode = if params.is_emoji {
- DWRITE_RENDERING_MODE1_NATURAL
- } else {
- DWRITE_RENDERING_MODE1_NATURAL_SYMMETRIC
- };
-
- let measuring_mode = DWRITE_MEASURING_MODE_NATURAL;
-
- let glyph_analysis = unsafe {
- self.components.factory.CreateGlyphRunAnalysis(
- &glyph_run,
- Some(&transform),
- rendering_mode,
- measuring_mode,
- DWRITE_GRID_FIT_MODE_DEFAULT,
- DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
- baseline_origin_x,
- baseline_origin_y,
- )?
- };
-
+ let total_bytes;
+ let bitmap_format;
+ let render_target_property;
+ let bitmap_width;
+ let bitmap_height;
+ let bitmap_stride;
+ let bitmap_dpi;
if params.is_emoji {
- // For emoji, we need to handle color glyphs differently
- // This is a simplified approach - in a full implementation you'd want to
- // properly handle color glyph runs using TranslateColorGlyphRun
- let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1;
- let texture_bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? };
-
- let width = (texture_bounds.right - texture_bounds.left) as u32;
- let height = (texture_bounds.bottom - texture_bounds.top) as u32;
-
- if width == 0 || height == 0 {
- return Ok((
- bitmap_size,
- vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize * 4],
- ));
- }
-
- let mut rgba_data = vec![0u8; (width * height * 4) as usize];
-
- unsafe {
- glyph_analysis.CreateAlphaTexture(texture_type, &texture_bounds, &mut rgba_data)?;
- }
-
- // Resize to match expected bitmap_size if needed
- let expected_size = bitmap_size.width.0 as usize * bitmap_size.height.0 as usize * 4;
- rgba_data.resize(expected_size, 0);
-
- Ok((bitmap_size, rgba_data))
+ total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize * 4;
+ bitmap_format = &GUID_WICPixelFormat32bppPBGRA;
+ render_target_property = get_render_target_property(
+ DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_PREMULTIPLIED,
+ );
+ bitmap_width = bitmap_size.width.0 as u32;
+ bitmap_height = bitmap_size.height.0 as u32;
+ bitmap_stride = bitmap_size.width.0 as u32 * 4;
+ bitmap_dpi = 96.0;
} else {
- // For regular text, use grayscale or cleartype
- let texture_type = DWRITE_TEXTURE_CLEARTYPE_3x1;
- let texture_bounds = unsafe { glyph_analysis.GetAlphaTextureBounds(texture_type)? };
-
- let width = (texture_bounds.right - texture_bounds.left) as u32;
- let height = (texture_bounds.bottom - texture_bounds.top) as u32;
-
- if width == 0 || height == 0 {
- return Ok((
- bitmap_size,
- vec![0u8; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize],
- ));
- }
+ total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize;
+ bitmap_format = &GUID_WICPixelFormat8bppAlpha;
+ render_target_property =
+ get_render_target_property(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT);
+ bitmap_width = bitmap_size.width.0 as u32 * 2;
+ bitmap_height = bitmap_size.height.0 as u32 * 2;
+ bitmap_stride = bitmap_size.width.0 as u32;
+ bitmap_dpi = 192.0;
+ }
- let mut alpha_data = vec![0u8; (width * height) as usize];
+ let bitmap_factory = self.components.bitmap_factory.resolve()?;
+ unsafe {
+ let bitmap = bitmap_factory.CreateBitmap(
+ bitmap_width,
+ bitmap_height,
+ bitmap_format,
+ WICBitmapCacheOnLoad,
+ )?;
+ let render_target = self
+ .components
+ .d2d1_factory
+ .CreateWicBitmapRenderTarget(&bitmap, &render_target_property)?;
+ let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, None)?;
+ let subpixel_shift = params
+ .subpixel_variant
+ .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
+ let baseline_origin = Vector2 {
+ X: subpixel_shift.x / params.scale_factor,
+ Y: subpixel_shift.y / params.scale_factor,
+ };
- unsafe {
- glyph_analysis.CreateAlphaTexture(
- texture_type,
- &texture_bounds,
- &mut alpha_data,
+ // This `cast()` action here should never fail since we are running on Win10+, and
+ // ID2D1DeviceContext4 requires Win8+
+ let render_target = render_target.cast::<ID2D1DeviceContext4>().unwrap();
+ render_target.SetUnitMode(D2D1_UNIT_MODE_DIPS);
+ render_target.SetDpi(
+ bitmap_dpi * params.scale_factor,
+ bitmap_dpi * params.scale_factor,
+ );
+ render_target.SetTextRenderingParams(&self.components.render_context.params);
+ render_target.BeginDraw();
+
+ if params.is_emoji {
+ // WARN: only DWRITE_GLYPH_IMAGE_FORMATS_COLR has been tested
+ let enumerator = self.components.factory.TranslateColorGlyphRun(
+ baseline_origin,
+ &glyph_run as _,
+ None,
+ DWRITE_GLYPH_IMAGE_FORMATS_COLR
+ | DWRITE_GLYPH_IMAGE_FORMATS_SVG
+ | DWRITE_GLYPH_IMAGE_FORMATS_PNG
+ | DWRITE_GLYPH_IMAGE_FORMATS_JPEG
+ | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8,
+ DWRITE_MEASURING_MODE_NATURAL,
+ None,
+ 0,
)?;
+ while enumerator.MoveNext().is_ok() {
+ let Ok(color_glyph) = enumerator.GetCurrentRun() else {
+ break;
+ };
+ let color_glyph = &*color_glyph;
+ let brush_color = translate_color(&color_glyph.Base.runColor);
+ brush.SetColor(&brush_color);
+ match color_glyph.glyphImageFormat {
+ DWRITE_GLYPH_IMAGE_FORMATS_PNG
+ | DWRITE_GLYPH_IMAGE_FORMATS_JPEG
+ | DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8 => render_target
+ .DrawColorBitmapGlyphRun(
+ color_glyph.glyphImageFormat,
+ baseline_origin,
+ &color_glyph.Base.glyphRun,
+ color_glyph.measuringMode,
+ D2D1_COLOR_BITMAP_GLYPH_SNAP_OPTION_DEFAULT,
+ ),
+ DWRITE_GLYPH_IMAGE_FORMATS_SVG => render_target.DrawSvgGlyphRun(
+ baseline_origin,
+ &color_glyph.Base.glyphRun,
+ &brush,
+ None,
+ color_glyph.Base.paletteIndex as u32,
+ color_glyph.measuringMode,
+ ),
+ _ => render_target.DrawGlyphRun(
+ baseline_origin,
+ &color_glyph.Base.glyphRun,
+ Some(color_glyph.Base.glyphRunDescription as *const _),
+ &brush,
+ color_glyph.measuringMode,
+ ),
+ }
+ }
+ } else {
+ render_target.DrawGlyphRun(
+ baseline_origin,
+ &glyph_run,
+ None,
+ &brush,
+ DWRITE_MEASURING_MODE_NATURAL,
+ );
}
-
- // For cleartype, we need to convert the 3x1 subpixel data to grayscale
- // This is a simplified conversion - you might want to do proper subpixel rendering
- let mut grayscale_data = Vec::new();
- for chunk in alpha_data.chunks_exact(3) {
- let avg = (chunk[0] as u32 + chunk[1] as u32 + chunk[2] as u32) / 3;
- grayscale_data.push(avg as u8);
+ render_target.EndDraw(None, None)?;
+
+ let mut raw_data = vec![0u8; total_bytes];
+ if params.is_emoji {
+ bitmap.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?;
+ // Convert from BGRA with premultiplied alpha to BGRA with straight alpha.
+ for pixel in raw_data.chunks_exact_mut(4) {
+ let a = pixel[3] as f32 / 255.;
+ pixel[0] = (pixel[0] as f32 / a) as u8;
+ pixel[1] = (pixel[1] as f32 / a) as u8;
+ pixel[2] = (pixel[2] as f32 / a) as u8;
+ }
+ } else {
+ let scaler = bitmap_factory.CreateBitmapScaler()?;
+ scaler.Initialize(
+ &bitmap,
+ bitmap_size.width.0 as u32,
+ bitmap_size.height.0 as u32,
+ WICBitmapInterpolationModeHighQualityCubic,
+ )?;
+ scaler.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?;
}
-
- // Resize to match expected bitmap_size if needed
- let expected_size = bitmap_size.width.0 as usize * bitmap_size.height.0 as usize;
- grayscale_data.resize(expected_size, 0);
-
- Ok((bitmap_size, grayscale_data))
+ Ok((bitmap_size, raw_data))
}
}
@@ -1422,8 +1471,13 @@ fn get_name(string: IDWriteLocalizedStrings, locale: &str) -> Result<String> {
}
#[inline]
-fn translate_color(color: &DWRITE_COLOR_F) -> [f32; 4] {
- [color.r, color.g, color.b, color.a]
+fn translate_color(color: &DWRITE_COLOR_F) -> D2D1_COLOR_F {
+ D2D1_COLOR_F {
+ r: color.r,
+ g: color.g,
+ b: color.b,
+ a: color.a,
+ }
}
fn get_system_ui_font_name() -> SharedString {
@@ -1450,6 +1504,24 @@ fn get_system_ui_font_name() -> SharedString {
}
}
+#[inline]
+fn get_render_target_property(
+ pixel_format: DXGI_FORMAT,
+ alpha_mode: D2D1_ALPHA_MODE,
+) -> D2D1_RENDER_TARGET_PROPERTIES {
+ D2D1_RENDER_TARGET_PROPERTIES {
+ r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ pixelFormat: D2D1_PIXEL_FORMAT {
+ format: pixel_format,
+ alphaMode: alpha_mode,
+ },
+ dpiX: 96.0,
+ dpiY: 96.0,
+ usage: D2D1_RENDER_TARGET_USAGE_NONE,
+ minLevel: D2D1_FEATURE_LEVEL_DEFAULT,
+ }
+}
+
// One would think that with newer DirectWrite method: IDWriteFontFace4::GetGlyphImageFormats
// but that doesn't seem to work for some glyphs, say ❤
fn is_color_glyph(
@@ -1489,6 +1561,12 @@ fn is_color_glyph(
}
const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
+const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
+ r: 1.0,
+ g: 1.0,
+ b: 1.0,
+ a: 1.0,
+};
#[cfg(test)]
mod tests {