@@ -8,7 +8,6 @@ use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use windows::{
core::*,
- Foundation::Numerics::Matrix3x2,
Win32::{
Foundation::*,
Globalization::GetUserDefaultLocaleName,
@@ -45,6 +44,12 @@ struct DirectWriteComponent {
in_memory_loader: IDWriteInMemoryFontFileLoader,
builder: IDWriteFontSetBuilder1,
text_renderer: Arc<TextRendererWrapper>,
+ render_context: GlyphRenderContext,
+}
+
+struct GlyphRenderContext {
+ params: IDWriteRenderingParams3,
+ dc_target: ID2D1DeviceContext4,
}
// All use of the IUnknown methods should be "thread-safe".
@@ -86,6 +91,7 @@ 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, &d2d1_factory)?;
Ok(DirectWriteComponent {
locale,
@@ -95,11 +101,47 @@ impl DirectWriteComponent {
in_memory_loader,
builder,
text_renderer,
+ render_context,
})
}
}
}
+impl GlyphRenderContext {
+ pub fn new(factory: &IDWriteFactory5, d2d1_factory: &ID2D1Factory) -> Result<Self> {
+ unsafe {
+ let default_params: IDWriteRenderingParams3 =
+ factory.CreateRenderingParams()?.cast()?;
+ let gamma = default_params.GetGamma();
+ let enhanced_contrast = default_params.GetEnhancedContrast();
+ let gray_contrast = default_params.GetGrayscaleEnhancedContrast();
+ let cleartype_level = default_params.GetClearTypeLevel();
+ let grid_fit_mode = default_params.GetGridFitMode();
+
+ let params = factory.CreateCustomRenderingParams(
+ gamma,
+ enhanced_contrast,
+ gray_contrast,
+ cleartype_level,
+ DWRITE_PIXEL_GEOMETRY_RGB,
+ 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, dc_target })
+ }
+ }
+}
+
impl DirectWriteTextSystem {
pub(crate) fn new() -> Result<Self> {
let components = DirectWriteComponent::new()?;
@@ -521,10 +563,12 @@ impl DirectWriteState {
}
}
- unsafe fn get_glyphrun_analysis(
- &self,
- params: &RenderGlyphParams,
- ) -> windows::core::Result<IDWriteGlyphRunAnalysis> {
+ 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];
@@ -539,40 +583,29 @@ impl DirectWriteState {
isSideways: BOOL(0),
bidiLevel: 0,
};
- 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 bounds = unsafe {
+ render_target.GetGlyphRunWorldBounds(
+ D2D_POINT_2F { x: 0.0, y: 0.0 },
+ &glyph_run,
+ DWRITE_MEASURING_MODE_NATURAL,
+ )?
};
- self.components.factory.CreateGlyphRunAnalysis(
- &glyph_run as _,
- Some(&transform as _),
- DWRITE_RENDERING_MODE1_NATURAL,
- DWRITE_MEASURING_MODE_NATURAL,
- DWRITE_GRID_FIT_MODE_DEFAULT,
- DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE,
- 0.0,
- 0.0,
- )
- }
-
- fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
- unsafe {
- let glyph_run_analysis = self.get_glyphrun_analysis(params)?;
- let bounds = glyph_run_analysis.GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1)?;
+ if bounds.right < bounds.left {
Ok(Bounds {
- origin: Point {
- x: DevicePixels(bounds.left),
- y: DevicePixels(bounds.top),
- },
- size: Size {
- width: DevicePixels(bounds.right - bounds.left),
- height: DevicePixels(bounds.bottom - bounds.top),
- },
+ origin: point(0.into(), 0.into()),
+ size: size(0.into(), 0.into()),
+ })
+ } else {
+ Ok(Bounds {
+ origin: point(
+ ((bounds.left * params.scale_factor).ceil() as i32).into(),
+ ((bounds.top * params.scale_factor).ceil() as i32).into(),
+ ),
+ size: size(
+ (((bounds.right - bounds.left) * params.scale_factor).ceil() as i32).into(),
+ (((bounds.bottom - bounds.top) * params.scale_factor).ceil() as i32).into(),
+ ),
})
}
}
@@ -626,66 +659,40 @@ impl DirectWriteState {
bitmap_size.height += DevicePixels(1);
}
let bitmap_size = bitmap_size;
- 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 brush_property = D2D1_BRUSH_PROPERTIES {
- opacity: 1.0,
- transform: Matrix3x2 {
- M11: params.scale_factor,
- M12: 0.0,
- M21: 0.0,
- M22: params.scale_factor,
- M31: 0.0,
- M32: 0.0,
- },
- };
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 {
total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize * 4;
bitmap_format = &GUID_WICPixelFormat32bppPBGRA;
- render_target_property = D2D1_RENDER_TARGET_PROPERTIES {
- r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
- pixelFormat: D2D1_PIXEL_FORMAT {
- format: DXGI_FORMAT_B8G8R8A8_UNORM,
- alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
- },
- dpiX: params.scale_factor * 96.0,
- dpiY: params.scale_factor * 96.0,
- usage: D2D1_RENDER_TARGET_USAGE_NONE,
- minLevel: D2D1_FEATURE_LEVEL_DEFAULT,
- };
+ 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 {
total_bytes = bitmap_size.height.0 as usize * bitmap_size.width.0 as usize;
bitmap_format = &GUID_WICPixelFormat8bppAlpha;
- render_target_property = D2D1_RENDER_TARGET_PROPERTIES {
- r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
- pixelFormat: D2D1_PIXEL_FORMAT {
- format: DXGI_FORMAT_A8_UNORM,
- alphaMode: D2D1_ALPHA_MODE_STRAIGHT,
- },
- dpiX: params.scale_factor * 96.0,
- dpiY: params.scale_factor * 96.0,
- usage: D2D1_RENDER_TARGET_USAGE_NONE,
- minLevel: D2D1_FEATURE_LEVEL_DEFAULT,
- };
+ 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;
}
unsafe {
let bitmap = self.components.bitmap_factory.CreateBitmap(
- bitmap_size.width.0 as u32,
- bitmap_size.height.0 as u32,
+ bitmap_width,
+ bitmap_height,
bitmap_format,
WICBitmapCacheOnLoad,
)?;
@@ -693,7 +700,7 @@ impl DirectWriteState {
.components
.d2d1_factory
.CreateWicBitmapRenderTarget(&bitmap, &render_target_property)?;
- let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, Some(&brush_property))?;
+ let brush = render_target.CreateSolidColorBrush(&BRUSH_COLOR, None)?;
let subpixel_shift = params
.subpixel_variant
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
@@ -705,7 +712,14 @@ impl DirectWriteState {
// 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(
@@ -718,7 +732,7 @@ impl DirectWriteState {
| DWRITE_GLYPH_IMAGE_FORMATS_JPEG
| DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8,
DWRITE_MEASURING_MODE_NATURAL,
- Some(&transform as _),
+ None,
0,
)?;
while enumerator.MoveNext().is_ok() {
@@ -766,9 +780,10 @@ impl DirectWriteState {
);
}
render_target.EndDraw(None, None)?;
+
let mut raw_data = vec![0u8; total_bytes];
- bitmap.CopyPixels(std::ptr::null() as _, bitmap_stride, &mut raw_data)?;
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.;
@@ -776,6 +791,15 @@ impl DirectWriteState {
pixel[1] = (pixel[1] as f32 / a) as u8;
pixel[2] = (pixel[2] as f32 / a) as u8;
}
+ } else {
+ let scaler = self.components.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)?;
}
Ok((bitmap_size, raw_data))
}
@@ -1243,8 +1267,8 @@ fn make_direct_write_feature(feature_name: &str, parameter: u32) -> DWRITE_FONT_
#[inline]
fn make_open_type_tag(tag_name: &str) -> u32 {
- assert_eq!(tag_name.chars().count(), 4);
let bytes = tag_name.bytes().collect_vec();
+ assert_eq!(bytes.len(), 4);
((bytes[3] as u32) << 24)
| ((bytes[2] as u32) << 16)
| ((bytes[1] as u32) << 8)
@@ -1327,6 +1351,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,
+ }
+}
+
const DEFAULT_LOCALE_NAME: PCWSTR = windows::core::w!("en-US");
const BRUSH_COLOR: D2D1_COLOR_F = D2D1_COLOR_F {
r: 1.0,