1#![allow(unused)]
8
9use std::convert::TryInto;
10use std::ffi::OsString;
11use std::fmt::{Debug, Display, Formatter};
12use std::mem::MaybeUninit;
13use std::ptr::null_mut;
14use std::sync::Arc;
15
16use dwrote::FontCollection as DWFontCollection;
17use winapi::Interface;
18use winapi::shared::minwindef::{FALSE, TRUE};
19use winapi::shared::ntdef::LOCALE_NAME_MAX_LENGTH;
20use winapi::shared::winerror::{HRESULT, S_OK, SUCCEEDED};
21use winapi::um::dwrite::{
22 DWRITE_FACTORY_TYPE_SHARED, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE,
23 DWRITE_FONT_STYLE_ITALIC, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_WEIGHT,
24 DWRITE_FONT_WEIGHT_NORMAL, DWRITE_HIT_TEST_METRICS, DWRITE_LINE_METRICS,
25 DWRITE_OVERHANG_METRICS, DWRITE_READING_DIRECTION_RIGHT_TO_LEFT, DWRITE_TEXT_ALIGNMENT_CENTER,
26 DWRITE_TEXT_ALIGNMENT_JUSTIFIED, DWRITE_TEXT_ALIGNMENT_LEADING, DWRITE_TEXT_ALIGNMENT_TRAILING,
27 DWRITE_TEXT_METRICS, DWRITE_TEXT_RANGE, DWriteCreateFactory, IDWriteFactory,
28 IDWriteFontCollection, IDWriteFontFamily, IDWriteLocalizedStrings, IDWriteTextFormat,
29 IDWriteTextLayout,
30};
31use winapi::um::unknwnbase::IUnknown;
32use winapi::um::winnls::GetUserDefaultLocaleName;
33
34use wio::com::ComPtr;
35use wio::wide::{FromWide, ToWide};
36
37use piet::kurbo::Insets;
38use piet::{FontFamily as PietFontFamily, FontStyle, FontWeight, TextAlignment};
39
40use crate::Brush;
41
42const DEFAULT_LOCALE: &[u16] = &utf16_lit::utf16_null!("en-US");
44
45const MAX_LAYOUT_CONSTRAINT: f32 = 1.6e7;
51
52pub enum Error {
55 WinapiError(HRESULT),
56}
57
58#[derive(Clone)]
61pub struct DwriteFactory(ComPtr<IDWriteFactory>);
62
63unsafe impl Send for DwriteFactory {}
66
67#[derive(Clone)]
68pub struct TextFormat(pub(crate) ComPtr<IDWriteTextFormat>);
69
70#[derive(Clone)]
71struct FontFamily(ComPtr<IDWriteFontFamily>);
72
73pub struct FontCollection(ComPtr<IDWriteFontCollection>);
74
75#[derive(Clone)]
76pub struct TextLayout(ComPtr<IDWriteTextLayout>);
77
78#[derive(Debug, Clone, Copy)]
80pub struct Utf16Range {
81 pub start: usize,
82 pub len: usize,
83}
84
85impl From<HRESULT> for Error {
86 fn from(hr: HRESULT) -> Error {
87 Error::WinapiError(hr)
88 }
89}
90
91impl Debug for Error {
92 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
93 match self {
94 Error::WinapiError(hr) => write!(f, "hresult {hr:x}"),
95 }
96 }
97}
98
99impl Display for Error {
100 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
101 match self {
102 Error::WinapiError(hr) => write!(f, "hresult {hr:x}"),
103 }
104 }
105}
106
107impl std::error::Error for Error {
108 fn description(&self) -> &str {
109 "winapi error"
110 }
111}
112
113impl From<Error> for piet::Error {
114 fn from(e: Error) -> piet::Error {
115 piet::Error::BackendError(Box::new(e))
116 }
117}
118
119unsafe fn wrap<T, U, F>(hr: HRESULT, ptr: *mut T, f: F) -> Result<U, Error>
120where
121 F: Fn(ComPtr<T>) -> U,
122 T: Interface,
123{
124 if SUCCEEDED(hr) {
125 Ok(f(unsafe { ComPtr::from_raw(ptr) }))
126 } else {
127 Err(hr.into())
128 }
129}
130
131impl DwriteFactory {
132 pub fn new() -> Result<DwriteFactory, Error> {
133 unsafe {
134 let mut ptr: *mut IDWriteFactory = null_mut();
135 let hr = DWriteCreateFactory(
136 DWRITE_FACTORY_TYPE_SHARED,
137 &IDWriteFactory::uuidof(),
138 &mut ptr as *mut _ as *mut _,
139 );
140 wrap(hr, ptr, DwriteFactory)
141 }
142 }
143
144 pub fn get_raw(&self) -> *mut IDWriteFactory {
145 self.0.as_raw()
146 }
147
148 pub(crate) fn system_font_collection(&self) -> Result<FontCollection, Error> {
149 unsafe {
150 let mut ptr = null_mut();
151 let hr = self.0.GetSystemFontCollection(&mut ptr, 0);
152 wrap(hr, ptr, FontCollection)
153 }
154 }
155
156 pub unsafe fn from_raw(raw: *mut IDWriteFactory) -> Self {
161 Self(unsafe { ComPtr::from_raw(raw) })
162 }
163}
164
165impl FontCollection {
166 pub(crate) fn font_family(&self, name: &str) -> Option<PietFontFamily> {
167 let wname = name.to_wide_null();
168 let mut idx = u32::MAX;
169 let mut exists = 0_i32;
170
171 let family = unsafe {
172 let hr = self.0.FindFamilyName(wname.as_ptr(), &mut idx, &mut exists);
173 if SUCCEEDED(hr) && exists != 0 {
174 let mut family = null_mut();
175 let hr = self.0.GetFontFamily(idx, &mut family);
176 wrap(hr, family, FontFamily).ok()
177 } else {
178 eprintln!(
179 "failed to find family name {}: err {} not_found: {}",
180 name, hr, !exists
181 );
182 None
183 }
184 }?;
185
186 family.family_name().ok()
187 }
188}
189
190impl FontFamily {
191 fn family_name(&self) -> Result<PietFontFamily, Error> {
194 unsafe {
195 let mut names = null_mut();
196 let hr = self.0.GetFamilyNames(&mut names);
197 if !SUCCEEDED(hr) {
198 return Err(hr.into());
199 }
200
201 let names: ComPtr<IDWriteLocalizedStrings> = ComPtr::from_raw(names);
202
203 let mut index = 0_u32;
204 let mut exists = 0_i32;
205 let mut locale_name = [0_u16; LOCALE_NAME_MAX_LENGTH];
206
207 let success =
208 GetUserDefaultLocaleName(locale_name.as_mut_ptr(), LOCALE_NAME_MAX_LENGTH as i32);
209 let mut hr = if SUCCEEDED(success) {
210 names.FindLocaleName(locale_name.as_ptr(), &mut index, &mut exists)
211 } else {
212 hr
215 };
216 if !SUCCEEDED(hr) || exists == 0 {
217 hr = names.FindLocaleName(DEFAULT_LOCALE.as_ptr(), &mut index, &mut exists);
218 }
219
220 if !SUCCEEDED(hr) {
221 return Err(hr.into());
222 }
223
224 if exists == 0 {
226 index = 0;
227 }
228
229 let mut length = 0_u32;
230 let hr = names.GetStringLength(index, &mut length);
231
232 if !SUCCEEDED(hr) {
233 return Err(hr.into());
234 }
235
236 let mut wide_name: Vec<u16> = Vec::with_capacity(length as usize + 1);
237 let hr = names.GetString(index, wide_name.as_mut_ptr(), length + 1);
238 if SUCCEEDED(hr) {
239 wide_name.set_len(length as usize + 1);
240 let name = OsString::from_wide(&wide_name)
241 .into_string()
242 .unwrap_or_else(|err| err.to_string_lossy().into_owned());
243
244 Ok(PietFontFamily::new_unchecked(name))
245 } else {
246 Err(hr.into())
247 }
248 }
249 }
250}
251
252impl TextFormat {
253 pub(crate) fn new(
254 factory: &DwriteFactory,
255 family: impl AsRef<[u16]>,
256 size: f32,
257 rtl: bool,
258 ) -> Result<TextFormat, Error> {
259 let family = family.as_ref();
260
261 unsafe {
262 let mut ptr = null_mut();
263 let hr = factory.0.CreateTextFormat(
264 family.as_ptr(),
265 null_mut(), DWRITE_FONT_WEIGHT_NORMAL,
267 DWRITE_FONT_STYLE_NORMAL,
268 DWRITE_FONT_STRETCH_NORMAL,
269 size,
270 DEFAULT_LOCALE.as_ptr(),
272 &mut ptr,
273 );
274
275 let r = wrap(hr, ptr, TextFormat)?;
276 if rtl {
277 r.0.SetReadingDirection(DWRITE_READING_DIRECTION_RIGHT_TO_LEFT);
278 }
279 Ok(r)
280 }
281 }
282}
283
284#[allow(overflowing_literals)]
285#[allow(clippy::unreadable_literal)]
286const E_NOT_SUFFICIENT_BUFFER: HRESULT = 0x8007007A;
287
288impl Utf16Range {
289 pub fn new(start: usize, len: usize) -> Self {
290 Utf16Range { start, len }
291 }
292}
293
294impl From<Utf16Range> for DWRITE_TEXT_RANGE {
295 fn from(src: Utf16Range) -> DWRITE_TEXT_RANGE {
296 let Utf16Range { start, len } = src;
297 DWRITE_TEXT_RANGE {
298 startPosition: start.try_into().unwrap(),
299 length: len.try_into().unwrap(),
300 }
301 }
302}
303
304impl TextLayout {
305 pub(crate) fn new(
306 dwrite: &DwriteFactory,
307 format: TextFormat,
308 width: f32,
309 text: &[u16],
310 ) -> Result<Self, Error> {
311 let len: u32 = text.len().try_into().unwrap();
312 let width = if !width.is_finite() {
314 MAX_LAYOUT_CONSTRAINT
315 } else {
316 width
317 };
318
319 unsafe {
320 let mut ptr = null_mut();
321 let hr = dwrite.0.CreateTextLayout(
322 text.as_ptr(),
323 len,
324 format.0.as_raw(),
325 width,
326 MAX_LAYOUT_CONSTRAINT,
327 &mut ptr,
328 );
329 wrap(hr, ptr, TextLayout)
330 }
331 }
332
333 pub(crate) fn set_alignment(&mut self, alignment: TextAlignment) {
335 let alignment = match alignment {
336 TextAlignment::Start => DWRITE_TEXT_ALIGNMENT_LEADING,
337 TextAlignment::End => DWRITE_TEXT_ALIGNMENT_TRAILING,
338 TextAlignment::Center => DWRITE_TEXT_ALIGNMENT_CENTER,
339 TextAlignment::Justified => DWRITE_TEXT_ALIGNMENT_JUSTIFIED,
340 };
341
342 unsafe {
343 self.0.SetTextAlignment(alignment);
344 }
345 }
346
347 pub(crate) fn set_weight(&mut self, range: Utf16Range, weight: FontWeight) {
349 let weight = weight.to_raw() as DWRITE_FONT_WEIGHT;
350 unsafe {
351 self.0.SetFontWeight(weight, range.into());
352 }
353 }
354
355 pub(crate) fn set_font_family(&mut self, range: Utf16Range, family: &str) {
356 let wide_name = family.to_wide_null();
357 unsafe {
358 self.0.SetFontFamilyName(wide_name.as_ptr(), range.into());
359 }
360 }
361
362 pub(crate) fn set_font_collection(&mut self, range: Utf16Range, collection: &DWFontCollection) {
363 unsafe {
364 self.0.SetFontCollection(collection.as_ptr(), range.into());
365 }
366 }
367
368 pub(crate) fn set_style(&mut self, range: Utf16Range, style: FontStyle) {
369 let val = match style {
370 FontStyle::Italic => DWRITE_FONT_STYLE_ITALIC,
371 FontStyle::Regular => DWRITE_FONT_STYLE_NORMAL,
372 };
373 unsafe {
374 self.0.SetFontStyle(val, range.into());
375 }
376 }
377
378 pub(crate) fn set_underline(&mut self, range: Utf16Range, flag: bool) {
379 let flag = if flag { TRUE } else { FALSE };
380 unsafe {
381 self.0.SetUnderline(flag, range.into());
382 }
383 }
384
385 pub(crate) fn set_strikethrough(&mut self, range: Utf16Range, flag: bool) {
386 let flag = if flag { TRUE } else { FALSE };
387 unsafe {
388 self.0.SetStrikethrough(flag, range.into());
389 }
390 }
391
392 pub(crate) fn set_size(&mut self, range: Utf16Range, size: f32) {
393 unsafe {
394 self.0.SetFontSize(size, range.into());
395 }
396 }
397
398 pub(crate) fn set_foreground_brush(&mut self, range: Utf16Range, brush: Brush) {
399 unsafe {
400 self.0
401 .SetDrawingEffect(brush.as_raw() as *mut IUnknown, range.into());
402 }
403 }
404
405 pub fn get_line_metrics(&self, buf: &mut Vec<DWRITE_LINE_METRICS>) {
410 let cap = buf.capacity().min(0xffff_ffff) as u32;
411 unsafe {
412 let mut actual_count = 0;
413 let mut hr = self
414 .0
415 .GetLineMetrics(buf.as_mut_ptr(), cap, &mut actual_count);
416 if hr == E_NOT_SUFFICIENT_BUFFER {
417 buf.reserve(actual_count as usize - buf.len());
418 hr = self
419 .0
420 .GetLineMetrics(buf.as_mut_ptr(), actual_count, &mut actual_count);
421 }
422 if SUCCEEDED(hr) {
423 buf.set_len(actual_count as usize);
424 } else {
425 buf.set_len(0);
426 }
427 }
428 }
429
430 pub fn get_raw(&self) -> *mut IDWriteTextLayout {
431 self.0.as_raw()
432 }
433
434 pub fn get_metrics(&self) -> DWRITE_TEXT_METRICS {
435 unsafe {
436 let mut result = std::mem::zeroed();
437 self.0.GetMetrics(&mut result);
438 result
439 }
440 }
441
442 pub fn get_overhang_metrics(&self) -> Insets {
448 unsafe {
449 let mut result = std::mem::zeroed();
450 let _ = self.0.GetOverhangMetrics(&mut result);
452 let DWRITE_OVERHANG_METRICS {
453 left,
454 top,
455 right,
456 bottom,
457 } = result;
458 Insets::new(left as f64, top as f64, right as f64, bottom as f64)
459 }
460 }
461
462 pub fn set_max_width(&mut self, max_width: f64) -> Result<(), Error> {
463 let max_width = if !max_width.is_finite() {
465 MAX_LAYOUT_CONSTRAINT
466 } else {
467 max_width as f32
468 };
469
470 unsafe {
471 let hr = self.0.SetMaxWidth(max_width);
472
473 if SUCCEEDED(hr) {
474 Ok(())
475 } else {
476 Err(hr.into())
477 }
478 }
479 }
480
481 pub fn hit_test_point(&self, point_x: f32, point_y: f32) -> HitTestPoint {
482 unsafe {
483 let mut trail = 0;
484 let mut inside = 0;
485 let mut metrics = MaybeUninit::uninit();
486 self.0.HitTestPoint(
487 point_x,
488 point_y,
489 &mut trail,
490 &mut inside,
491 metrics.as_mut_ptr(),
492 );
493
494 HitTestPoint {
495 metrics: metrics.assume_init().into(),
496 is_inside: inside != 0,
497 is_trailing_hit: trail != 0,
498 }
499 }
500 }
501
502 pub fn hit_test_text_position(
503 &self,
504 position: u32,
505 trailing: bool,
506 ) -> Option<HitTestTextPosition> {
507 let trailing = trailing as i32;
508 unsafe {
509 let (mut x, mut y) = (0.0, 0.0);
510 let mut metrics = std::mem::zeroed();
511 let res = self
512 .0
513 .HitTestTextPosition(position, trailing, &mut x, &mut y, &mut metrics);
514 if res != S_OK {
515 return None;
516 }
517
518 Some(HitTestTextPosition {
519 metrics: metrics.into(),
520 point_x: x,
521 point_y: y,
522 })
523 }
524 }
525}
526
527#[derive(Copy, Clone)]
528pub struct HitTestPoint {
530 pub metrics: HitTestMetrics,
533 pub is_inside: bool,
536 pub is_trailing_hit: bool,
541}
542
543#[derive(Copy, Clone)]
544pub struct HitTestTextPosition {
546 pub point_x: f32,
548 pub point_y: f32,
550
551 pub metrics: HitTestMetrics,
553}
554
555#[repr(C)]
556#[derive(Copy, Clone, Debug)]
557pub struct HitTestMetrics {
559 pub text_position: u32,
561 pub length: u32,
563 pub left: f32,
565 pub top: f32,
567 pub width: f32,
569 pub height: f32,
571 pub bidi_level: u32,
573 pub is_text: bool,
575 pub is_trimmed: bool,
577}
578
579impl From<DWRITE_HIT_TEST_METRICS> for HitTestMetrics {
580 fn from(metrics: DWRITE_HIT_TEST_METRICS) -> Self {
581 HitTestMetrics {
582 text_position: metrics.textPosition,
583 length: metrics.length,
584 left: metrics.left,
585 top: metrics.top,
586 width: metrics.width,
587 height: metrics.height,
588 bidi_level: metrics.bidiLevel,
589 is_text: metrics.isText != 0,
590 is_trimmed: metrics.isTrimmed != 0,
591 }
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn family_names() {
601 let factory = DwriteFactory::new().unwrap();
602 let fonts = factory.system_font_collection().unwrap();
603 assert!(fonts.font_family("serif").is_none());
604 assert!(fonts.font_family("arial").is_some());
605 assert!(fonts.font_family("Arial").is_some());
606 assert!(fonts.font_family("Times New Roman").is_some());
607 }
608
609 #[test]
610 fn default_locale() {
611 assert_eq!("en-US".to_wide_null().as_slice(), DEFAULT_LOCALE);
612 }
613}