Appendix A — cvd

A.1 Package Dependencies

Load required packages.

\RequirePackage{iftex}
\RequirePackage{xcolor}
\RequirePackage{graphicx}

A.2 Engine Check

Currently only LuaTeX is fully supported.

\sys_if_engine_luatex:F
{
  \msg_error:nn { cvd } { luatex-required }
}
\msg_new:nnn { cvd } { luatex-required }
{
  LuaTeX~required.\\
  This~package~currently~only~works~with~LuaLaTeX.\\
  pdfLaTeX~support~is~under~development.
}

A.3 Color Space Enforcement

Force RGB color model for consistent transformations.

\selectcolormodel{rgb}

A.4 Load Lua Module

Next, load the Lua module that implements the CVD transformations. The 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.

\directlua{lfs = require("lfs"); cvd = require("cvd"); cvd.install_pdf_image_hook()}

A.5 Hook into xcolor

Use xcolor’s hook to transform RGB values before display. This handles text colors, color boxes, and other xcolor-based content.

\cs_set:Npn \XC@bcolor
{
  \directlua
  {
    token.set_macro("current@color",~
    cvd.transform_current_color("\luaescapestring{\current@color}"),~
    "global")
  }
}

A.6 User Commands

A.6.1 \cvdtype

Set the type of color vision deficiency to simulate.

\NewDocumentCommand \cvdtype { m }
{
  \directlua { cvd.set_type("#1") }
}

A.6.2 \cvdseverity

Set the severity of the simulation (0.0 to 1.0).

\NewDocumentCommand \cvdseverity { m }
  {
    \directlua { cvd.set_severity(#1) }
  }

A.6.3 \cvdenable

Enable CVD simulation.

\NewDocumentCommand \cvdenable { }
{
  \directlua { cvd.enable() }
}

A.6.4 \cvddisable

Disable CVD simulation.

\NewDocumentCommand \cvddisable { }
{
  \directlua { cvd.disable() }
}

A.6.5 \cvdincludegraphics

Include a graphics file with CVD transformation applied to raster images.

\tl_new:N \l__cvd_imgpath_tl
\NewDocumentCommand \cvdincludegraphics { O{} m }
{
  \tl_set:Nx \l__cvd_imgpath_tl
  {
    \directlua
    { tex.sprint(cvd.get_image_path("\luaescapestring{#2}")) }
  }
  \includegraphics[#1]{\tl_use:N \l__cvd_imgpath_tl}
}

A.6.6 \cvddefinecolor

Define a new color by applying CVD transformation to an existing color. Usage:

\tl_new:N \l__cvd_model_tl
\tl_new:N \l__cvd_values_tl

\NewDocumentCommand \cvddefinecolor { O{} m m }
{
  % Extract the original color
  \extractcolorspecs{#2}{\l__cvd_model_tl}{\l__cvd_values_tl}
  
  % Apply CVD transformation with specified settings
  \keys_set:nn { cvd } { #1 }
  \cvdenable
  
  % Transform the RGB values directly via Lua
  \directlua{
    local~values~=~"\luaescapestring{\l__cvd_values_tl}"
    local~r,~g,~b~=~values:match("([^,]+),([^,]+),([^,]+)")
    r,~g,~b~=~tonumber(r),~tonumber(g),~tonumber(b)
    r,~g,~b~=~cvd.transform(r,~g,~b)
    token.set_macro("l__cvd_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__cvd_model_tl } { \exp_not:V \l__cvd_values_tl } }
  
  \cvddisable
}

A.7 Package Configuration

Define keys for package configuration using . Keys are available both as package load-time options and via the command.

\bool_new:N \l__cvd_graphics_hook_bool
\bool_new:N \l__cvd_graphics_convert_bool
\cs_new_eq:NN \__cvd_orig_includegraphics \includegraphics

\keys_define:nn { cvd }
  {
    type          .code:n = { \cvdtype{#1} } ,
    severity      .code:n = { \cvdseverity{#1} } ,
    graphics~hook .bool_set:N = \l__cvd_graphics_hook_bool ,
    graphics~hook .initial:n = true ,
    graphics~hook .default:n = true ,
    graphics~hook / true .code:n = { \directlua { cvd.enable_graphics_hook() } } ,
    graphics~hook / false .code:n = { \directlua { cvd.disable_graphics_hook() } } ,
    graphics~convert .bool_set:N = \l__cvd_graphics_convert_bool ,
    graphics~convert .initial:n = false ,
    graphics~convert .default:n = true ,
    graphics~convert / true .code:n = { \__cvd_patch_includegraphics: } ,
    graphics~convert / false .code:n = { \__cvd_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 { cvd } { unknown-option } { \l_keys_key_str } }
  }
\msg_new:nnn { cvd } { unknown-option }
  { Unknown~option~'#1'. }

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

\cs_new:Npn \__cvd_unpatch_includegraphics:
{
  \cs_set_eq:NN \includegraphics \__cvd_orig_includegraphics
  \directlua { cvd.disable_graphics_convert() }
}

\NewDocumentCommand \cvdset { m }
{
  \keys_set:nn { cvd } { #1 }
}
\ProcessKeyOptions [ cvd ]