Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
//! A custom implementation of the "text analysis source" interface so that
//! we can convey data to the `FontFallback::map_characters` method.
#![allow(non_snake_case)]
use std::borrow::Cow;
use std::ffi::OsStr;
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::ptr::{self, null};
use std::sync::atomic::AtomicUsize;
use winapi::ctypes::wchar_t;
use winapi::shared::basetsd::UINT32;
use winapi::shared::guiddef::REFIID;
use winapi::shared::minwindef::{FALSE, TRUE, ULONG};
use winapi::shared::ntdef::LOCALE_NAME_MAX_LENGTH;
use winapi::shared::winerror::{E_INVALIDARG, S_OK};
use winapi::um::dwrite::IDWriteNumberSubstitution;
use winapi::um::dwrite::IDWriteTextAnalysisSource;
use winapi::um::dwrite::IDWriteTextAnalysisSourceVtbl;
use winapi::um::dwrite::DWRITE_NUMBER_SUBSTITUTION_METHOD;
use winapi::um::dwrite::DWRITE_READING_DIRECTION;
use winapi::um::unknwnbase::{IUnknown, IUnknownVtbl};
use winapi::um::winnt::HRESULT;
use wio::com::ComPtr;
use super::DWriteFactory;
use crate::com_helpers::Com;
use crate::helpers::ToWide;
/// The Rust side of a custom text analysis source implementation.
pub trait TextAnalysisSourceMethods {
/// Determine the locale for a range of text.
///
/// Return locale and length of text (in utf-16 code units) for which the
/// locale is valid.
fn get_locale_name<'a>(&'a self, text_position: u32) -> (Cow<'a, str>, u32);
/// Get the text direction for the paragraph.
fn get_paragraph_reading_direction(&self) -> DWRITE_READING_DIRECTION;
}
#[repr(C)]
pub struct CustomTextAnalysisSourceImpl<'a> {
// NB: This must be the first field.
_refcount: AtomicUsize,
inner: Box<dyn TextAnalysisSourceMethods + 'a>,
text: Cow<'a, [wchar_t]>,
number_subst: Option<NumberSubstitution>,
locale_buf: [wchar_t; LOCALE_NAME_MAX_LENGTH],
}
/// A wrapped version of an `IDWriteNumberSubstitution` object.
pub struct NumberSubstitution {
native: ComPtr<IDWriteNumberSubstitution>,
}
// TODO: implement Clone, for convenience and efficiency?
static TEXT_ANALYSIS_SOURCE_VTBL: IDWriteTextAnalysisSourceVtbl = IDWriteTextAnalysisSourceVtbl {
parent: implement_iunknown!(static IDWriteTextAnalysisSource, CustomTextAnalysisSourceImpl),
GetLocaleName: CustomTextAnalysisSourceImpl_GetLocaleName,
GetNumberSubstitution: CustomTextAnalysisSourceImpl_GetNumberSubstitution,
GetParagraphReadingDirection: CustomTextAnalysisSourceImpl_GetParagraphReadingDirection,
GetTextAtPosition: CustomTextAnalysisSourceImpl_GetTextAtPosition,
GetTextBeforePosition: CustomTextAnalysisSourceImpl_GetTextBeforePosition,
};
impl<'a> CustomTextAnalysisSourceImpl<'a> {
/// Create a new custom TextAnalysisSource for the given text and a trait
/// implementation.
///
/// Note: this method has no NumberSubsitution specified. See
/// `from_text_and_number_subst_native` if you need number substitution.
pub fn from_text_native(
inner: Box<dyn TextAnalysisSourceMethods + 'a>,
text: Cow<'a, [wchar_t]>,
) -> CustomTextAnalysisSourceImpl<'a> {
assert!(text.len() <= (std::u32::MAX as usize));
CustomTextAnalysisSourceImpl {
_refcount: AtomicUsize::new(1),
inner,
text,
number_subst: None,
locale_buf: [0u16; LOCALE_NAME_MAX_LENGTH],
}
}
/// Create a new custom TextAnalysisSource for the given text and a trait
/// implementation.
///
/// Note: this method only supports a single `NumberSubstitution` for the
/// entire string.
pub fn from_text_and_number_subst_native(
inner: Box<dyn TextAnalysisSourceMethods + 'a>,
text: Cow<'a, [wchar_t]>,
number_subst: NumberSubstitution,
) -> CustomTextAnalysisSourceImpl<'a> {
assert!(text.len() <= (std::u32::MAX as usize));
CustomTextAnalysisSourceImpl {
_refcount: AtomicUsize::new(1),
inner,
text,
number_subst: Some(number_subst),
locale_buf: [0u16; LOCALE_NAME_MAX_LENGTH],
}
}
}
impl Com<IDWriteTextAnalysisSource> for CustomTextAnalysisSourceImpl<'_> {
type Vtbl = IDWriteTextAnalysisSourceVtbl;
#[inline]
fn vtbl() -> &'static IDWriteTextAnalysisSourceVtbl {
&TEXT_ANALYSIS_SOURCE_VTBL
}
}
impl Com<IUnknown> for CustomTextAnalysisSourceImpl<'_> {
type Vtbl = IUnknownVtbl;
#[inline]
fn vtbl() -> &'static IUnknownVtbl {
&TEXT_ANALYSIS_SOURCE_VTBL.parent
}
}
unsafe extern "system" fn CustomTextAnalysisSourceImpl_GetLocaleName(
this: *mut IDWriteTextAnalysisSource,
text_position: UINT32,
text_length: *mut UINT32,
locale_name: *mut *const wchar_t,
) -> HRESULT {
let this = CustomTextAnalysisSourceImpl::from_interface(this);
let (locale, text_len) = this.inner.get_locale_name(text_position);
// Copy the locale data into the buffer
for (i, c) in OsStr::new(&*locale).encode_wide().chain(Some(0)).enumerate() {
// -1 here is deliberate: it ensures that we never write to the last character in
// this.locale_buf, so that the buffer is always null-terminated.
if i >= this.locale_buf.len() - 1 {
break
}
*this.locale_buf.get_unchecked_mut(i) = c;
}
*text_length = text_len;
*locale_name = this.locale_buf.as_ptr();
S_OK
}
unsafe extern "system" fn CustomTextAnalysisSourceImpl_GetNumberSubstitution(
this: *mut IDWriteTextAnalysisSource,
text_position: UINT32,
text_length: *mut UINT32,
number_substitution: *mut *mut IDWriteNumberSubstitution,
) -> HRESULT {
let this = CustomTextAnalysisSourceImpl::from_interface(this);
if text_position >= (this.text.len() as u32) {
return E_INVALIDARG;
}
*text_length = (this.text.len() as UINT32) - text_position;
*number_substitution = match &this.number_subst {
Some(number_subst) => {
let com_ptr = &number_subst.native;
com_ptr.AddRef();
com_ptr.as_raw()
},
None => std::ptr::null_mut()
};
S_OK
}
unsafe extern "system" fn CustomTextAnalysisSourceImpl_GetParagraphReadingDirection(
this: *mut IDWriteTextAnalysisSource,
) -> DWRITE_READING_DIRECTION {
let this = CustomTextAnalysisSourceImpl::from_interface(this);
this.inner.get_paragraph_reading_direction()
}
unsafe extern "system" fn CustomTextAnalysisSourceImpl_GetTextAtPosition(
this: *mut IDWriteTextAnalysisSource,
text_position: UINT32,
text_string: *mut *const wchar_t,
text_length: *mut UINT32,
) -> HRESULT {
let this = CustomTextAnalysisSourceImpl::from_interface(this);
if text_position >= (this.text.len() as u32) {
*text_string = null();
*text_length = 0;
return S_OK;
}
*text_string = this.text.as_ptr().add(text_position as usize);
*text_length = (this.text.len() as UINT32) - text_position;
S_OK
}
unsafe extern "system" fn CustomTextAnalysisSourceImpl_GetTextBeforePosition(
this: *mut IDWriteTextAnalysisSource,
text_position: UINT32,
text_string: *mut *const wchar_t,
text_length: *mut UINT32,
) -> HRESULT {
let this = CustomTextAnalysisSourceImpl::from_interface(this);
if text_position == 0 || text_position > (this.text.len() as u32) {
*text_string = null();
*text_length = 0;
return S_OK;
}
*text_string = this.text.as_ptr();
*text_length = text_position;
S_OK
}
impl NumberSubstitution {
pub fn new(
subst_method: DWRITE_NUMBER_SUBSTITUTION_METHOD,
locale: &str,
ignore_user_overrides: bool,
) -> NumberSubstitution {
unsafe {
let mut native: *mut IDWriteNumberSubstitution = ptr::null_mut();
let hr = (*DWriteFactory()).CreateNumberSubstitution(
subst_method,
locale.to_wide_null().as_ptr(),
if ignore_user_overrides { TRUE } else { FALSE },
&mut native,
);
assert_eq!(hr, 0, "error creating number substitution");
NumberSubstitution {
native: ComPtr::from_raw(native),
}
}
}
}