2017-02-22 11:49:55 -06:00
// 12 february 2017
# include "uipriv_windows.hpp"
2018-03-13 17:43:32 -05:00
# include "attrstr.hpp"
2017-02-22 11:49:55 -06:00
2017-05-17 15:15:54 -05:00
// TODO this whole file needs cleanup
2018-03-17 14:29:06 -05:00
// yep, even when it supports C++11, it doesn't support C++11
// we require MSVC 2013; this was added in MSVC 2015 (https://msdn.microsoft.com/en-us/library/wfa0edys.aspx)
# ifdef _MSC_VER
# if _MSC_VER < 1900
# define noexcept
# endif
# endif
2018-03-14 20:08:19 -05:00
// we need to collect all the background parameters and add them all at once
// TODO consider having background parameters in the drawing effects
2017-02-22 11:49:55 -06:00
// TODO contextual alternates override ligatures?
// TODO rename this struct to something that isn't exclusively foreach-ing?
struct foreachParams {
const uint16_t * s ;
2017-05-31 21:24:34 -05:00
size_t len ;
2017-02-22 11:49:55 -06:00
IDWriteTextLayout * layout ;
2018-03-14 20:08:19 -05:00
std : : vector < struct drawTextBackgroundParams * > * backgroundParams ;
2017-02-22 11:49:55 -06:00
} ;
2018-03-13 21:01:15 -05:00
static std : : hash < double > doubleHash ;
2018-03-13 17:43:32 -05:00
// we need to combine color and underline style into one unit for IDWriteLayout::SetDrawingEffect()
// we also want to combine identical effects, which DirectWrite doesn't seem to provide a way to do
// we can at least try to goad it into doing so if we can deduplicate effects once they're all computed
// so what we do is use this class to store in-progress effects, much like uiprivCombinedFontAttr on the OS X code
// we then deduplicate them later while converting them into a form suitable for drawing with; see applyEffectsAttributes() below
class combinedEffectsAttr : public IUnknown {
ULONG refcount ;
uiAttribute * colorAttr ;
uiAttribute * underlineAttr ;
uiAttribute * underlineColorAttr ;
void setAttribute ( uiAttribute * a )
{
if ( a = = NULL )
return ;
switch ( uiAttributeGetType ( a ) ) {
case uiAttributeTypeColor :
if ( this - > colorAttr ! = NULL )
uiprivAttributeRelease ( this - > colorAttr ) ;
this - > colorAttr = uiprivAttributeRetain ( a ) ;
break ;
case uiAttributeTypeUnderline :
if ( this - > underlineAttr ! = NULL )
uiprivAttributeRelease ( this - > underlineAttr ) ;
this - > underlineAttr = uiprivAttributeRetain ( a ) ;
break ;
case uiAttributeTypeUnderlineColor :
if ( this - > underlineAttr ! = NULL )
uiprivAttributeRelease ( this - > underlineAttr ) ;
this - > underlineColorAttr = uiprivAttributeRetain ( a ) ;
break ;
}
}
2018-03-13 21:01:15 -05:00
// this is needed by applyEffectsAttributes() below
// TODO doesn't uiprivAttributeEqual() already do this; if it doesn't, make it so; if (or when) it does, fix all platforms to avoid this extra check
2018-03-17 14:29:06 -05:00
static bool attrEqual ( uiAttribute * a , uiAttribute * b )
2018-03-13 21:01:15 -05:00
{
if ( a = = NULL & & b = = NULL )
return true ;
if ( a = = NULL | | b = = NULL )
return false ;
return uiprivAttributeEqual ( a , b ) ;
}
2018-03-13 17:43:32 -05:00
public :
combinedEffectsAttr ( uiAttribute * a )
{
this - > refcount = 1 ;
this - > colorAttr = NULL ;
this - > underlineAttr = NULL ;
this - > underlineColorAttr = NULL ;
this - > setAttribute ( a ) ;
}
~ combinedEffectsAttr ( )
{
if ( this - > colorAttr ! = NULL )
uiprivAttributeRelease ( this - > colorAttr ) ;
if ( this - > underlineAttr ! = NULL )
uiprivAttributeRelease ( this - > underlineAttr ) ;
if ( this - > underlineColorAttr ! = NULL )
uiprivAttributeRelease ( this - > underlineColorAttr ) ;
}
// IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface ( REFIID riid , void * * ppvObject )
{
if ( ppvObject = = NULL )
return E_POINTER ;
if ( riid = = IID_IUnknown ) {
this - > AddRef ( ) ;
* ppvObject = this ;
return S_OK ;
}
* ppvObject = NULL ;
return E_NOINTERFACE ;
}
virtual ULONG STDMETHODCALLTYPE AddRef ( void )
{
this - > refcount + + ;
return this - > refcount ;
}
virtual ULONG STDMETHODCALLTYPE Release ( void )
{
this - > refcount - - ;
if ( this - > refcount = = 0 ) {
delete this ;
return 0 ;
}
return this - > refcount ;
}
combinedEffectsAttr * cloneWith ( uiAttribute * a )
{
combinedEffectsAttr * b ;
b = new combinedEffectsAttr ( this - > colorAttr ) ;
b - > setAttribute ( this - > underlineAttr ) ;
b - > setAttribute ( this - > underlineColorAttr ) ;
b - > setAttribute ( a ) ;
return b ;
}
2018-03-13 21:01:15 -05:00
// and these are also needed by applyEffectsAttributes() below
size_t hash ( void ) const noexcept
{
size_t ret = 0 ;
double r , g , b , a ;
2018-03-17 14:29:06 -05:00
uiUnderlineColor colorType ;
2018-03-13 21:01:15 -05:00
2018-03-13 21:06:33 -05:00
if ( this - > colorAttr ! = NULL ) {
uiAttributeColor ( this - > colorAttr , & r , & g , & b , & a ) ;
2018-03-13 21:01:15 -05:00
ret ^ = doubleHash ( r ) ;
ret ^ = doubleHash ( g ) ;
ret ^ = doubleHash ( b ) ;
ret ^ = doubleHash ( a ) ;
}
2018-03-13 21:06:33 -05:00
if ( this - > underlineAttr ! = NULL )
ret ^ = ( size_t ) uiAttributeUnderline ( this - > underlineAttr ) ;
if ( this - > underlineColorAttr ! = NULL ) {
uiAttributeUnderlineColor ( this - > underlineColorAttr , & colorType , & r , & g , & b , & a ) ;
2018-03-13 21:01:15 -05:00
ret ^ = ( size_t ) colorType ;
ret ^ = doubleHash ( r ) ;
ret ^ = doubleHash ( g ) ;
ret ^ = doubleHash ( b ) ;
ret ^ = doubleHash ( a ) ;
}
return ret ;
}
bool equals ( const combinedEffectsAttr * b ) const
{
if ( b = = NULL )
return false ;
2018-03-17 14:29:06 -05:00
return combinedEffectsAttr : : attrEqual ( this - > colorAttr , b - > colorAttr ) & &
combinedEffectsAttr : : attrEqual ( this - > underlineAttr , b - > underlineAttr ) & &
combinedEffectsAttr : : attrEqual ( this - > underlineColorAttr , b - > underlineColorAttr ) ;
2018-03-13 21:01:15 -05:00
}
drawingEffectsAttr * toDrawingEffectsAttr ( void )
{
drawingEffectsAttr * dea ;
double r , g , b , a ;
uiUnderlineColor colorType ;
dea = new drawingEffectsAttr ;
2018-03-13 21:06:33 -05:00
if ( this - > colorAttr ! = NULL ) {
uiAttributeColor ( this - > colorAttr , & r , & g , & b , & a ) ;
2018-03-17 14:29:06 -05:00
dea - > setColor ( r , g , b , a ) ;
2018-03-13 21:01:15 -05:00
}
2018-03-13 21:06:33 -05:00
if ( this - > underlineAttr ! = NULL )
2018-03-17 14:29:06 -05:00
dea - > setUnderline ( uiAttributeUnderline ( this - > underlineAttr ) ) ;
2018-03-13 21:06:33 -05:00
if ( this - > underlineColorAttr ! = NULL ) {
2018-03-17 14:29:06 -05:00
uiAttributeUnderlineColor ( this - > underlineColorAttr , & colorType , & r , & g , & b , & a ) ;
2018-03-13 21:01:15 -05:00
// TODO see if Microsoft has any standard colors for these
switch ( colorType ) {
case uiUnderlineColorSpelling :
// TODO consider using the GtkTextView style property error-underline-color here if Microsoft has no preference
r = 1.0 ;
g = 0.0 ;
b = 0.0 ;
a = 1.0 ;
break ;
case uiUnderlineColorGrammar :
r = 0.0 ;
g = 1.0 ;
b = 0.0 ;
a = 1.0 ;
break ;
2018-03-17 14:29:06 -05:00
case uiUnderlineColorAuxiliary :
2018-03-13 21:01:15 -05:00
r = 0.0 ;
g = 0.0 ;
b = 1.0 ;
a = 1.0 ;
break ;
}
2018-03-17 14:29:06 -05:00
dea - > setUnderlineColor ( r , g , b , a ) ;
2018-03-13 21:01:15 -05:00
}
return dea ;
}
} ;
// also needed by applyEffectsAttributes() below
2018-03-17 14:49:00 -05:00
// TODO provide all the fields of std::hash and std::equal_to?
2018-03-13 21:01:15 -05:00
class applyEffectsHash {
public :
typedef combinedEffectsAttr * ceaptr ;
size_t operator ( ) ( applyEffectsHash : : ceaptr const & cea ) const noexcept
{
return cea - > hash ( ) ;
}
} ;
class applyEffectsEqualTo {
public :
typedef combinedEffectsAttr * ceaptr ;
bool operator ( ) ( const applyEffectsEqualTo : : ceaptr & a , const applyEffectsEqualTo : : ceaptr & b ) const
{
return a - > equals ( b ) ;
}
2018-03-13 17:43:32 -05:00
} ;
static HRESULT addEffectAttributeToRange ( struct foreachParams * p , size_t start , size_t end , uiAttribute * attr )
2017-02-22 14:19:11 -06:00
{
2018-03-13 17:43:32 -05:00
IUnknown * u ;
combinedEffectsAttr * cea ;
DWRITE_TEXT_RANGE range ;
size_t diff ;
HRESULT hr ;
while ( start < end ) {
hr = p - > layout - > GetDrawingEffect ( start , & u , & range ) ;
if ( hr ! = S_OK )
return hr ;
2018-03-17 20:26:34 -05:00
cea = ( combinedEffectsAttr * ) u ;
2018-03-13 17:43:32 -05:00
if ( cea = = NULL )
cea = new combinedEffectsAttr ( attr ) ;
else
cea = cea - > cloneWith ( attr ) ;
// clamp range within [start, end)
if ( range . startPosition < start ) {
diff = start - range . startPosition ;
range . startPosition = start ;
range . length - = diff ;
2017-02-22 14:19:11 -06:00
}
2018-03-13 17:43:32 -05:00
if ( ( range . startPosition + range . length ) > end )
range . length = end - range . startPosition ;
hr = p - > layout - > SetDrawingEffect ( cea , range ) ;
2018-03-17 20:26:34 -05:00
// SetDrawingEffect will AddRef(), so Release() our copy
// (and we're abandoning early if that failed, so this will make sure things are cleaned up in that case)
cea - > Release ( ) ;
2018-03-13 17:43:32 -05:00
if ( hr ! = S_OK )
return hr ;
start + = range . length ;
2017-02-22 14:19:11 -06:00
}
2018-03-13 17:43:32 -05:00
return S_OK ;
2017-02-22 14:19:11 -06:00
}
2018-03-14 20:08:19 -05:00
static void addBackgroundParams ( struct foreachParams * p , size_t start , size_t end , const uiAttribute * attr )
2017-02-22 11:49:55 -06:00
{
2018-03-14 20:08:19 -05:00
struct drawTextBackgroundParams * params ;
2017-02-22 11:49:55 -06:00
2018-03-14 20:08:19 -05:00
params = uiprivNew ( struct drawTextBackgroundParams ) ;
params - > start = start ;
params - > end = end ;
uiAttributeColor ( attr , & ( params - > r ) , & ( params - > g ) , & ( params - > b ) , & ( params - > a ) ) ;
p - > backgroundParams - > push_back ( params ) ;
2017-02-22 11:49:55 -06:00
}
2018-03-13 17:43:32 -05:00
static uiForEach processAttribute ( const uiAttributedString * s , const uiAttribute * attr , size_t start , size_t end , void * data )
2017-02-22 11:49:55 -06:00
{
struct foreachParams * p = ( struct foreachParams * ) data ;
DWRITE_TEXT_RANGE range ;
WCHAR * wfamily ;
2017-02-22 18:13:36 -06:00
BOOL hasUnderline ;
2017-05-31 17:45:11 -05:00
IDWriteTypography * dt ;
2017-02-22 11:49:55 -06:00
HRESULT hr ;
2018-03-14 20:08:19 -05:00
start = uiprivAttributedStringUTF8ToUTF16 ( s , start ) ;
end = uiprivAttributedStringUTF8ToUTF16 ( s , end ) ;
2017-02-22 11:49:55 -06:00
range . startPosition = start ;
range . length = end - start ;
2018-03-13 17:43:32 -05:00
switch ( uiAttributeGetType ( attr ) ) {
case uiAttributeTypeFamily :
wfamily = toUTF16 ( uiAttributeFamily ( attr ) ) ;
2017-02-22 11:49:55 -06:00
hr = p - > layout - > SetFontFamilyName ( wfamily , range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying family name attribute " , hr ) ;
2018-04-15 17:12:58 -05:00
uiprivFree ( wfamily ) ;
2017-02-22 11:49:55 -06:00
break ;
2018-03-13 17:43:32 -05:00
case uiAttributeTypeSize :
2017-02-22 11:49:55 -06:00
hr = p - > layout - > SetFontSize (
2018-03-13 17:43:32 -05:00
// TODO unify with fontmatch.cpp and/or attrstr.hpp
2017-02-22 11:49:55 -06:00
# define pointSizeToDWriteSize(size) (size * (96.0 / 72.0))
2018-03-13 17:43:32 -05:00
pointSizeToDWriteSize ( uiAttributeSize ( attr ) ) ,
2017-02-22 11:49:55 -06:00
range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying size attribute " , hr ) ;
break ;
2018-03-13 17:43:32 -05:00
case uiAttributeTypeWeight :
2017-02-22 14:19:11 -06:00
hr = p - > layout - > SetFontWeight (
2018-03-13 17:43:32 -05:00
uiprivWeightToDWriteWeight ( uiAttributeWeight ( attr ) ) ,
2017-02-22 14:19:11 -06:00
range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying weight attribute " , hr ) ;
2017-02-22 11:49:55 -06:00
break ;
2018-03-13 17:43:32 -05:00
case uiAttributeTypeItalic :
2017-02-22 14:19:11 -06:00
hr = p - > layout - > SetFontStyle (
2018-03-13 17:43:32 -05:00
uiprivItalicToDWriteStyle ( uiAttributeItalic ( attr ) ) ,
2017-02-22 14:19:11 -06:00
range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying italic attribute " , hr ) ;
2017-02-22 11:49:55 -06:00
break ;
2018-03-13 17:43:32 -05:00
case uiAttributeTypeStretch :
2017-02-22 14:19:11 -06:00
hr = p - > layout - > SetFontStretch (
2018-03-13 17:43:32 -05:00
uiprivStretchToDWriteStretch ( uiAttributeStretch ( attr ) ) ,
2017-02-22 14:19:11 -06:00
range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying stretch attribute " , hr ) ;
2017-02-22 11:49:55 -06:00
break ;
2018-03-13 17:43:32 -05:00
case uiAttributeTypeUnderline :
2017-02-22 18:13:36 -06:00
// mark that we have an underline; otherwise, DirectWrite will never call our custom renderer's DrawUnderline() method
hasUnderline = FALSE ;
2018-03-13 17:43:32 -05:00
if ( uiAttributeUnderline ( attr ) ! = uiUnderlineNone )
2017-02-22 18:13:36 -06:00
hasUnderline = TRUE ;
hr = p - > layout - > SetUnderline ( hasUnderline , range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying underline attribute " , hr ) ;
2018-03-13 17:43:32 -05:00
// and fall through to set the underline style through the drawing effect
case uiAttributeTypeColor :
case uiAttributeTypeUnderlineColor :
2018-03-17 14:29:06 -05:00
// TODO const-correct this properly
hr = addEffectAttributeToRange ( p , start , end , ( uiAttribute * ) attr ) ;
2018-03-13 17:43:32 -05:00
if ( hr ! = S_OK )
logHRESULT ( L " error applying effect (color, underline, or underline color) attribute " , hr ) ;
break ;
2018-03-17 14:29:06 -05:00
case uiAttributeTypeBackground :
2018-03-14 20:08:19 -05:00
addBackgroundParams ( p , start , end , attr ) ;
2017-02-22 11:49:55 -06:00
break ;
2018-03-13 17:43:32 -05:00
case uiAttributeTypeFeatures :
// only generate an attribute if not NULL
// TODO do we still need to do this or not...
if ( uiAttributeFeatures ( attr ) = = NULL )
2017-06-10 02:37:17 -05:00
break ;
2018-03-13 17:43:32 -05:00
dt = uiprivOpenTypeFeaturesToIDWriteTypography ( uiAttributeFeatures ( attr ) ) ;
2017-05-31 17:45:11 -05:00
hr = p - > layout - > SetTypography ( dt , range ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying features attribute " , hr ) ;
dt - > Release ( ) ;
2017-02-22 11:49:55 -06:00
break ;
}
2017-06-06 11:47:07 -05:00
return uiForEachContinue ;
2017-02-22 11:49:55 -06:00
}
2018-03-13 21:01:15 -05:00
static HRESULT applyEffectsAttributes ( struct foreachParams * p )
2017-02-22 14:19:11 -06:00
{
2018-03-13 21:01:15 -05:00
IUnknown * u ;
combinedEffectsAttr * cea ;
drawingEffectsAttr * dea ;
2017-02-22 14:19:11 -06:00
DWRITE_TEXT_RANGE range ;
2018-03-13 21:01:15 -05:00
// here's the magic: this std::unordered_map will deduplicate all of our combinedEffectsAttrs, mapping all identical ones to a single drawingEffectsAttr
// because drawingEffectsAttr is the *actual* drawing effect we want for rendering, we also replace the combinedEffectsAttrs with them in the IDWriteTextLayout at the same time
// note the use of our custom hash and equal_to implementations
2018-03-17 14:29:06 -05:00
std : : unordered_map < combinedEffectsAttr * , drawingEffectsAttr * ,
2018-03-13 21:01:15 -05:00
applyEffectsHash , applyEffectsEqualTo > effects ;
HRESULT hr ;
2017-05-31 21:24:34 -05:00
2018-03-13 21:01:15 -05:00
// go through, replacing every combinedEffectsAttr with the proper drawingEffectsAttr
2017-05-31 21:24:34 -05:00
range . startPosition = 0 ;
2018-03-17 20:26:34 -05:00
// and in case this while loop never runs, make hr valid to start with
hr = S_OK ;
2018-03-13 21:01:15 -05:00
while ( range . startPosition < p - > len ) {
hr = p - > layout - > GetDrawingEffect ( range . startPosition , & u , & range ) ;
if ( hr ! = S_OK )
2018-03-17 20:26:34 -05:00
// note that we are breaking instead of returning; this allows us to clean up on failure
break ;
2018-03-17 15:10:11 -05:00
cea = ( combinedEffectsAttr * ) u ;
2018-03-13 21:01:15 -05:00
if ( cea ! = NULL ) {
auto diter = effects . find ( cea ) ;
if ( diter ! = effects . end ( ) )
dea = diter - > second ;
else {
dea = cea - > toDrawingEffectsAttr ( ) ;
2018-03-17 14:29:06 -05:00
effects . insert ( { cea , dea } ) ;
2017-05-31 21:24:34 -05:00
}
2018-03-13 21:01:15 -05:00
hr = p - > layout - > SetDrawingEffect ( dea , range ) ;
2018-03-17 20:26:34 -05:00
// don't release dea; we need the reference that's inside the map
// (we don't take extra references on lookup, so this will be fine)
2018-03-13 21:01:15 -05:00
if ( hr ! = S_OK )
2018-03-17 20:26:34 -05:00
break ;
2018-03-13 21:01:15 -05:00
}
range . startPosition + = range . length ;
2017-02-22 14:19:11 -06:00
}
2017-05-31 21:24:34 -05:00
2018-03-13 21:01:15 -05:00
// and clean up, finally destroying the combinedEffectAttrs too
2018-03-17 20:26:34 -05:00
// we do this in the case of failure as well, to make sure everything is properly cleaned up
2018-03-13 21:01:15 -05:00
for ( auto iter = effects . begin ( ) ; iter ! = effects . end ( ) ; iter + + ) {
iter - > first - > Release ( ) ;
iter - > second - > Release ( ) ;
}
2018-03-17 20:26:34 -05:00
return hr ;
2017-02-22 14:19:11 -06:00
}
2018-03-14 20:08:19 -05:00
void uiprivAttributedStringApplyAttributesToDWriteTextLayout ( uiDrawTextLayoutParams * p , IDWriteTextLayout * layout , std : : vector < struct drawTextBackgroundParams * > * * backgroundParams )
2017-02-22 11:49:55 -06:00
{
struct foreachParams fep ;
2018-03-13 21:01:15 -05:00
HRESULT hr ;
2017-02-22 11:49:55 -06:00
2018-03-17 14:29:06 -05:00
fep . s = uiprivAttributedStringUTF16String ( p - > String ) ;
fep . len = uiprivAttributedStringUTF16Len ( p - > String ) ;
2017-02-22 11:49:55 -06:00
fep . layout = layout ;
2018-03-14 20:08:19 -05:00
fep . backgroundParams = new std : : vector < struct drawTextBackgroundParams * > ;
2017-02-22 11:49:55 -06:00
uiAttributedStringForEachAttribute ( p - > String , processAttribute , & fep ) ;
2018-03-13 21:01:15 -05:00
hr = applyEffectsAttributes ( & fep ) ;
if ( hr ! = S_OK )
logHRESULT ( L " error applying effects attributes " , hr ) ;
2018-03-14 20:08:19 -05:00
* backgroundParams = fep . backgroundParams ;
2017-02-22 11:49:55 -06:00
}