% \iffalse meta-comment -------------------------------------------------------
% dichromacy - Color Vision Deficiency simulation for LaTeX
% Copyright 2026 Johan Larsson, Simon Pfahler
%
% This work is licensed under the MIT License
% (https://opensource.org/licenses/MIT). See the LICENSE file for details.
% ------------------------------------------------------------------------- \fi
% \iffalse
%<*package>
\NeedsTeXFormat{LaTeX2e}[2022-06-01]
\ProvidesExplPackage{dichromacy}{2026-08-05}{0.3.0}{Color Vision Deficiency Simulation}
%</package>
% \fi
% \CheckSum{0}
% \StopEventually{}
% \iffalse
%<*package>
% ------------------------------------------------------------------------- \fi
%
% \subsection{Package Dependencies}
%
% Load required packages.
%
%    \begin{macrocode}
\RequirePackage{iftex}
\RequirePackage{xcolor}
\RequirePackage{graphicx}
%    \end{macrocode}
%
% \subsection{Engine Check}
%
% Currently only LuaTeX is fully supported.
%
%    \begin{macrocode}
\sys_if_engine_luatex:F
{
  \msg_error:nn { dichromacy } { luatex-required }
}
\msg_new:nnn { dichromacy } { luatex-required }
{
  LuaTeX~required.\\
  This~package~currently~only~works~with~LuaLaTeX.\\
  pdfLaTeX~support~is~under~development.
}
%    \end{macrocode}
%
% \subsection{Load Lua Module}
%
% Load the Lua module that implements the CVD transformations. The
% \verb|install_pdf_image_hook| function registers a callback that
% transforms colors in embedded PDF pages (vector graphics only). We
% also load the Lua File System module for file timestamp checking.
%
%    \begin{macrocode}
\directlua{lfs = require("lfs"); dichromacy = require("dichromacy"); dichromacy.install_pdf_image_hook()}
%    \end{macrocode}
%
% \subsection{Hook into xcolor}
%
% Use \texttt{xcolor}'s \cs{XC@bcolor} hook to transform RGB values before display.
% This handles text colors, color boxes, and other \texttt{xcolor}-based content.
%
%    \begin{macrocode}
\cs_set:Npn \XC@bcolor
{
  \directlua
  {
    token.set_macro("current@color",~
    dichromacy.transform_current_color("\luaescapestring{\current@color}"),~
    "global")
  }
}
%    \end{macrocode}
%
% \subsection{Hook into pgf Shadings}
%
% TikZ/pgf shadings (linear and radial gradients) store their colors in PDF
% \texttt{Shading} dictionaries whose \texttt{Function} carries \texttt{/C0}
% and \texttt{/C1} color arrays. These objects sit in the page resources rather
% than in any content stream, so none of \pkg{dichromacy}'s other transform paths
% reach them and they need a dedicated hook.
%
% Patch pgf's leaf tuple emitters so the RGB or CMYK tuple is filtered through
% \texttt{dichromacy.transform} first. From one transform each emitter sets both the
% space-separated macro (\cs{pgf@rgb}/\cs{pgf@cmyk}) that ends up in
% \texttt{/C0} and \texttt{/C1} for the pdf/luatex driver, and the
% brace-grouped system-layer record (\cs{pgf@sys@rgb}/\cs{pgf@sys@cmyk}) used
% by the dvisvgm driver, so the two never disagree. Guarded with
% \cs{@ifundefined} so loading \pkg{dichromacy} without \pkg{pgf} or \pkg{tikz} is a
% no-op. \texttt{ShadingType 1} (functional) shadings and \texttt{DeviceGray}
% shadings are intentionally not handled.
%
%    \begin{macrocode}
\def \__dichromacy_patch_pgf_shadings:
  {
    \@ifundefined { pgf@getrgb@@ } { } {
      \def \pgf@getrgb@@ ##1,##2,##3!
        {
          \directlua { dichromacy.set_pgf_rgb("##1",~"##2",~"##3") }
        }
    }
    \@ifundefined { pgf@getcmyk@@ } { } {
      \def \pgf@getcmyk@@ ##1,##2,##3,##4!
        {
          \directlua { dichromacy.set_pgf_cmyk("##1",~"##2",~"##3",~"##4") }
        }
    }
  }
\__dichromacy_patch_pgf_shadings:
\AtBeginDocument { \__dichromacy_patch_pgf_shadings: }
%    \end{macrocode}
%
% \subsection{User Commands}
%
% \begin{macro}{\cvdtype}
%
% Set the type of color vision deficiency to simulate.
%
%    \begin{macrocode}
\NewDocumentCommand \cvdtype { m }
{
  \directlua { dichromacy.set_type("#1") }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\cvdseverity}
%
% Set the severity of the simulation (0.0 to 1.0).
%
%    \begin{macrocode}
\NewDocumentCommand \cvdseverity { m }
  {
    \directlua { dichromacy.set_severity(#1) }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\cvdenable}
%
% Enable CVD simulation.
%
%    \begin{macrocode}
\NewDocumentCommand \cvdenable { }
{
  \directlua { dichromacy.enable() }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\cvddisable}
%
% Disable CVD simulation.
%
%    \begin{macrocode}
\NewDocumentCommand \cvddisable { }
{
  \directlua { dichromacy.disable() }
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\cvdincludegraphics}
% Include a graphics file with CVD transformation applied to raster images.
%
%    \begin{macrocode}
\tl_new:N \l__dichromacy_imgpath_tl
\NewDocumentCommand \cvdincludegraphics { O{} m }
{
  \tl_set:Nx \l__dichromacy_imgpath_tl
  {
    \directlua
    { tex.sprint(dichromacy.get_image_path("\luaescapestring{#2}")) }
  }
  \__dichromacy_orig_includegraphics[#1]{\tl_use:N \l__dichromacy_imgpath_tl}
}
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\cvddefinecolor}
%
% Define a new color by applying CVD transformation to an existing color.
% Usage: \cs{cvddefinecolor}\oarg{options}\marg{source color}\marg{target color}
%
%    \begin{macrocode}
\tl_new:N \l__dichromacy_model_tl
\tl_new:N \l__dichromacy_values_tl

\NewDocumentCommand \cvddefinecolor { O{} m m }
{
  % Extract the original color
  \extractcolorspecs{#2}{\l__dichromacy_model_tl}{\l__dichromacy_values_tl}
  
  % Apply CVD transformation with specified settings
  \keys_set:nn { dichromacy } { #1 }
  \cvdenable
  
  % Transform the RGB values directly via Lua
  \directlua{
    local~values~=~"\luaescapestring{\l__dichromacy_values_tl}"
    local~r,~g,~b~=~values:match("([^,]+),([^,]+),([^,]+)")
    r,~g,~b~=~tonumber(r),~tonumber(g),~tonumber(b)
    r,~g,~b~=~dichromacy.transform("rgb",~r,~g,~b)
    token.set_macro("l__dichromacy_values_tl",~string.format("\csstring\%.6f,\csstring\%.6f,\csstring\%.6f",~r,~g,~b))
  }
  
  % Define the color with transformed values
  \use:x { \definecolor {#3} { \exp_not:V \l__dichromacy_model_tl } { \exp_not:V \l__dichromacy_values_tl } }
  
  \cvddisable
}
%    \end{macrocode}
% \end{macro}
%
% \subsection{Package Configuration}
%
% Define keys for package configuration using \pkg{l3keys}. Keys are available
% both as package load-time options and via the \cs{cvdset} command.
%
%    \begin{macrocode}
\bool_new:N \l__dichromacy_graphics_hook_bool
\bool_new:N \l__dichromacy_graphics_convert_bool
\cs_new_eq:NN \__dichromacy_orig_includegraphics \includegraphics

\keys_define:nn { dichromacy }
  {
    type          .code:n = { \cvdtype{#1} } ,
    severity      .code:n = { \cvdseverity{#1} } ,
    graphics~hook .bool_set:N = \l__dichromacy_graphics_hook_bool ,
    graphics~hook .initial:n = true ,
    graphics~hook .default:n = true ,
    graphics~hook / true .code:n = { \directlua { dichromacy.enable_graphics_hook() } } ,
    graphics~hook / false .code:n = { \directlua { dichromacy.disable_graphics_hook() } } ,
    graphics~convert .bool_set:N = \l__dichromacy_graphics_convert_bool ,
    graphics~convert .initial:n = false ,
    graphics~convert .default:n = true ,
    graphics~convert / true .code:n = { \__dichromacy_patch_includegraphics: } ,
    graphics~convert / false .code:n = { \__dichromacy_unpatch_includegraphics: } ,
    protanopia    .code:n = { \cvdtype{protanopia} \cvdseverity{1.0} } ,
    deuteranopia  .code:n = { \cvdtype{deuteranopia} \cvdseverity{1.0} } ,
    tritanopia    .code:n = { \cvdtype{tritanopia} \cvdseverity{1.0} } ,
    protanomaly   .code:n = { \cvdtype{protanopia} \cvdseverity{0.5} } ,
    deuteranomaly .code:n = { \cvdtype{deuteranopia} \cvdseverity{0.5} } ,
    tritanomaly   .code:n = { \cvdtype{tritanopia} \cvdseverity{0.5} } ,
    unknown       .code:n = 
      { \msg_warning:nnx { dichromacy } { unknown-option } { \l_keys_key_str } }
  }
\msg_new:nnn { dichromacy } { unknown-option }
  { Unknown~option~'#1'. }

\cs_new:Npn \__dichromacy_patch_includegraphics:
{
  \RenewDocumentCommand \includegraphics { O{} m }
  {
    \cvdincludegraphics[##1]{##2}
  }
  \directlua { dichromacy.enable_graphics_convert() }
}

\cs_new:Npn \__dichromacy_unpatch_includegraphics:
{
  \cs_set_eq:NN \includegraphics \__dichromacy_orig_includegraphics
  \directlua { dichromacy.disable_graphics_convert() }
}

\NewDocumentCommand \cvdset { m }
{
  \keys_set:nn { dichromacy } { #1 }
}
\ProcessKeyOptions [ dichromacy ]
%    \end{macrocode}
%
% \iffalse
%</package>
% \fi
% \Finale
\endinput
