Attribute VB_Name = "ex_Color"
'================ Модуль для работы с цветами в разных приложениях =============
' Shared module version: 20210430
' Tested in: TestCommons
' Depends on:
' Required reference:
Option Private Module
Option Explicit

Public Enum ColorType
  CT_RGB = &H0
  CT_AUTO = &HFF
  CT_SYSTEM = &H80
  CT_THEME_LOW = &HD0
  CT_THEME_HIGH = &HDF
End Enum

Public Type ColorDetails
  type_  As Byte
  clrIndex_ As WdThemeColorIndex
  tintShade_  As Double
  rgb_ As Long
End Type

Public Enum HardColor
  HC_INVALID = -1
  HC_WHITE = &HFFFFFF
  HC_BLACK = &H0
  HC_RED = &HFF
  HC_GREEN = &HFF00
  HC_BLUE = &HFF0000
End Enum

Public Declare PtrSafe Function _
  ColorAdjustLuma Lib "shlwapi.dll" ( _
    ByVal nRGB As Long, _
    ByVal nLumaAdjust As Long, _
    ByVal bScale As Long) As Long

Private Declare PtrSafe Function GetSysColor Lib "user32" (ByVal nIndex As Long) As Long

Private Enum AppType
  T_APP_UNDEF = 0
  [_First] = 1
  
  T_APP_WORD = 1
  T_APP_EXCEL = 2
  T_APP_VISIO = 3
  T_APP_POWER_POINT = 4

  [_Last] = 4
End Enum

Public Function ColorGetLuma(nRGB&) As Long
  Dim clrRed%, clrGreen%, clrBlue%
  Call LongColor3Components(nRGB, clrRed, clrGreen, clrBlue)
  ColorGetLuma = Math_Sym(0.2126 * clrRed + 0.7152 * clrGreen + 0.0722 * clrBlue)
End Function

Public Function ColorGetRGB(nColorID&, aDocument As Object) As Long
  ColorGetRGB = QueryColor(nColorID, aDocument).rgb_
End Function

Public Function ConvertRGBtoString(nRGB&) As String
  Dim nRed%, nGreen%, nBlue%
  Call LongColor3Components(nRGB, nRed, nGreen, nBlue)
  ConvertRGBtoString = "RGB(" & nRed & "," & nGreen & "," & nBlue & ")"
End Function

Public Function ConvertStringToRGB(sColor$) As Long
  ConvertStringToRGB = HC_INVALID
  
  Dim nLen&: nLen = VBA.Len(sColor)
  Dim nPos&: nPos = 1
  Dim nRed%: nRed = ExtractFirstInteger(sColor, nPos)
  If nPos > nLen Or nRed = HC_INVALID Or nRed > 255 Then _
    Exit Function
    
  nPos = nPos + 1
  Dim nGreen%: nGreen = ExtractFirstInteger(sColor, nPos)
  If nPos > nLen Or nGreen = HC_INVALID Or nGreen > 255 Then _
    Exit Function
    
  nPos = nPos + 1
  Dim nBlue%: nBlue = ExtractFirstInteger(sColor, nPos)
  If nPos > nLen Or nBlue = HC_INVALID Or nBlue > 255 Then _
    Exit Function
  
  ConvertStringToRGB = RGB(nRed, nGreen, nBlue)
End Function

Public Function QueryColor(nColorID&, aDocument As Object) As ColorDetails
  Dim sHexColor$: sHexColor = VBA.Right$(VBA.String$(7, "0") & VBA.Hex$(nColorID), 8)
  QueryColor.type_ = CByte("&H" & VBA.Left$(sHexColor, 2))
  Select Case QueryColor.type_
    Case CT_RGB: QueryColor.rgb_ = nColorID
    Case CT_SYSTEM: QueryColor.rgb_ = SysColorToRGB(nColorID)
    Case CT_THEME_LOW To CT_THEME_HIGH: QueryColor = QueryThemeColor(QueryColor.type_, sHexColor, aDocument)
  End Select
End Function

' =========
Private Function LongColor3Components(nColor&, ByRef c1%, ByRef c2%, ByRef c3%)
  c1 = Math_Sym(nColor And &HFF&)
  c2 = Math_Sym((nColor And &HFF00&) / &H100&)
  c3 = Math_Sym((nColor And &HFF0000) / &H10000)
End Function

Private Function Math_Sym(ByVal nValue As Double, Optional ByVal dFactor As Double = 1) As Double
   Math_Sym = Fix(nValue * dFactor + 0.5 * Sgn(nValue)) / dFactor
End Function

Private Function ExtractApplicationType(target As Object) As AppType
  If target Is Nothing Then
    ExtractApplicationType = T_APP_UNDEF
    Exit Function
  End If
  Select Case target.Application.Name
    Case "Microsoft Word":      ExtractApplicationType = T_APP_WORD
    Case "Microsoft Excel":     ExtractApplicationType = T_APP_EXCEL
    Case "Microsoft PowerPoint":  ExtractApplicationType = T_APP_POWER_POINT
    Case "Microsoft Visio": ExtractApplicationType = T_APP_VISIO
    Case Else: ExtractApplicationType = T_APP_UNDEF
  End Select
End Function

Private Function ExtractFirstInteger(sText$, ByRef nPos&) As Integer
  On Error GoTo ERR_OVERFLOW

  Dim bStarted As Boolean: bStarted = False
  Dim nLen&: nLen = VBA.Len(sText)
  Dim sDigit$
  Do
    If nPos > nLen Then _
      Exit Function
    sDigit = VBA.Mid(sText, nPos, 1)
    If VBA.IsNumeric(sDigit) Then
      bStarted = True
      ExtractFirstInteger = ExtractFirstInteger * 10 + VBA.CLng(sDigit)
    ElseIf bStarted Then _
      Exit Function
    End If
    nPos = nPos + 1
  Loop
  
ERR_OVERFLOW:
  On Error GoTo 0
  ExtractFirstInteger = HC_INVALID
End Function

Private Function SysColorToRGB(nColor&) As Long
  SysColorToRGB = GetSysColor(nColor And &HFFFFFF)
End Function

Private Function QueryThemeColor(clrType As Byte, hexColor$, aDocument As Object) As ColorDetails
  QueryThemeColor.type_ = clrType And &HF0
  QueryThemeColor.clrIndex_ = clrType And &HF
  
  Dim bDarkness As Byte: bDarkness = CByte("&H" & Mid$(hexColor, 5, 2))
  If bDarkness <> &HFF Then
    QueryThemeColor.tintShade_ = VBA.Round(-1 + bDarkness / &HFF, 2)
  End If
  
  Dim bLightness As Byte: bLightness = CByte("&H" & Mid$(hexColor, 7, 2))
  If bLightness <> &HFF Then
    QueryThemeColor.tintShade_ = VBA.Round(1 - bLightness / &HFF, 2)
  End If

  QueryThemeColor.rgb_ = GetThemeRGB(QueryThemeColor.clrIndex_, QueryThemeColor.tintShade_, aDocument)
End Function

Private Function GetThemeRGB(clrIndex As WdThemeColorIndex, tintShade As Double, aDocument As Object) As Long
  Dim schemeID  As MsoThemeColorSchemeIndex
  Dim schemeRGB&
  Select Case ExtractApplicationType(aDocument)
    Case T_APP_UNDEF
      schemeID = WordColor(clrIndex)
      schemeRGB = ActiveDocument.DocumentTheme.ThemeColorScheme(schemeID).RGB
    Case T_APP_WORD
      schemeID = WordColor(clrIndex)
      schemeRGB = aDocument.DocumentTheme.ThemeColorScheme(schemeID).RGB
    Case T_APP_VISIO
      schemeRGB = aDocument.DocumentTheme.ThemeColorScheme(clrIndex).RGB
    Case T_APP_EXCEL
      schemeID = ExcelColor(clrIndex)
      schemeRGB = aDocument.DocumentTheme.ThemeColorScheme(schemeID).RGB
    Case T_APP_POWER_POINT
      schemeRGB = aDocument.DocumentTheme.ThemeColorScheme(clrIndex).RGB
  End Select
  GetThemeRGB = ColorAdjustLuma(schemeRGB, Math_Sym(tintShade, 1000) * 1000, True)
End Function
 
Private Function WordColor(clrID As WdThemeColorIndex) As MsoThemeColorSchemeIndex
  Select Case clrID
    Case wdThemeColorMainDark1:         WordColor = msoThemeDark1
    Case wdThemeColorMainLight1:        WordColor = msoThemeLight1
    Case wdThemeColorMainDark2:         WordColor = msoThemeDark2
    Case wdThemeColorMainLight2:        WordColor = msoThemeLight2
    Case wdThemeColorAccent1:           WordColor = msoThemeAccent1
    Case wdThemeColorAccent2:           WordColor = msoThemeAccent2
    Case wdThemeColorAccent3:           WordColor = msoThemeAccent3
    Case wdThemeColorAccent4:           WordColor = msoThemeAccent4
    Case wdThemeColorAccent5:           WordColor = msoThemeAccent5
    Case wdThemeColorAccent6:           WordColor = msoThemeAccent6
    Case wdThemeColorHyperlink:         WordColor = msoThemeHyperlink
    Case wdThemeColorHyperlinkFollowed: WordColor = msoThemeFollowedHyperlink
    Case wdThemeColorBackground1:       WordColor = msoThemeLight1
    Case wdThemeColorText1:             WordColor = msoThemeDark1
    Case wdThemeColorBackground2:       WordColor = msoThemeLight2
    Case wdThemeColorText2:             WordColor = msoThemeDark2
    Case Else:
  End Select
End Function

Private Function ExcelColor(clrID As XlThemeColor) As MsoThemeColorSchemeIndex
  Select Case clrID
    Case xlThemeColorDark1:               ExcelColor = msoThemeDark1
    Case xlThemeColorLight1:              ExcelColor = msoThemeLight1
    Case xlThemeColorDark2:               ExcelColor = msoThemeDark2
    Case xlThemeColorLight2:              ExcelColor = msoThemeLight2
    Case xlThemeColorAccent1:             ExcelColor = msoThemeAccent1
    Case xlThemeColorAccent2:             ExcelColor = msoThemeAccent2
    Case xlThemeColorAccent3:             ExcelColor = msoThemeAccent3
    Case xlThemeColorAccent4:             ExcelColor = msoThemeAccent4
    Case xlThemeColorAccent5:             ExcelColor = msoThemeAccent5
    Case xlThemeColorAccent6:             ExcelColor = msoThemeAccent6
    Case xlThemeColorHyperlink:           ExcelColor = msoThemeHyperlink
    Case xlThemeColorFollowedHyperlink:   ExcelColor = msoThemeFollowedHyperlink
    Case Else:
  End Select
End Function

'
'
''----------------------Конвертирование ------------------------------------------------
'Function Convert_RGB2CMYK(colr&) As Long
''RGB -> CMYK
'  Dim red As Integer, blue As Integer, green As Integer
'  Dim c As Double, m As Double, y As Double, k As Double, minCMY As Double
'  Dim cyan As Integer, magenta As Integer, yellow As Integer, key As Integer
'
'  Call LongColor3Components(colr, red, green, blue)
'  If red = 0 And green = 0 And blue = 0 Then
'    key = 100
'    GoTo RESULTING
'  End If
'
'  c = 1 - (red / 255)
'  m = 1 - (green / 255)
'  y = 1 - (blue / 255)
'  k = Math_Min(c, Math_Min(m, y))
'
'  cyan = Math_Sym(100 * (c - k) / (1 - k))
'  magenta = Math_Sym(100 * (m - k) / (1 - k))
'  yellow = Math_Sym(100 * (y - k) / (1 - k))
'  key = Math_Sym(100 * k)
'
'RESULTING:
'  Convert_RGB2CMYK = cyan * 101 ^ 3 + 101 ^ 2 * magenta + 101 * yellow + key
'End Function
'
'
''--------------------------Обработка цветовых ссылок ------------------------
'
'Private Function SysColorToRGB(ByVal SysColor&) As Long
''Возвращает RGB системного цвета
'  SysColor = SysColor And &HFFFFFF
'  SysColorToRGB = GetSysColor(SysColor)
'End Function
'
'Private Function QueryThemeColor(ColourTypeByte As Byte, _
'                 ColourToTestHex As String, app As Object) _
'         As ColorDetails
'
'  Const Unchanged    As Byte = &HFF
'
'  Dim LightnessByte  As Byte
'  Dim DarknessByte   As Byte
'
'  LightnessByte = CByte("&H" & Mid$(ColourToTestHex, 7, 2))
'  DarknessByte = CByte("&H" & Mid$(ColourToTestHex, 5, 2))
'
'  QueryThemeColor.ColourType = ColourTypeByte And &HF0
'  QueryThemeColor.ThemeColorIndex = ColourTypeByte And &HF
'
'  If DarknessByte <> Unchanged Then
'    QueryThemeColor.TintAndShade = Round(-1 + DarknessByte / &HFF, 2)
'  End If
'
'  If LightnessByte <> Unchanged Then
'    QueryThemeColor.TintAndShade = Round(1 - LightnessByte / &HFF, 2)
'  End If
'
'  QueryThemeColor.RGB = GetThemeRGB(QueryThemeColor.ThemeColorIndex, _
'                 QueryThemeColor.TintAndShade, app)
'
'End Function

