/* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! 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, text: Cow<'a, [wchar_t]>, number_subst: Option, locale_buf: [wchar_t; LOCALE_NAME_MAX_LENGTH], } /// A wrapped version of an `IDWriteNumberSubstitution` object. pub struct NumberSubstitution { native: ComPtr, } // 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, 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, 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 for CustomTextAnalysisSourceImpl<'_> { type Vtbl = IDWriteTextAnalysisSourceVtbl; #[inline] fn vtbl() -> &'static IDWriteTextAnalysisSourceVtbl { &TEXT_ANALYSIS_SOURCE_VTBL } } impl Com 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), } } } }