% \iffalse meta-comment
% 
% ┌─────────────────────────────────────────────────────────────┐
% │                                                             │
% │                  The “fits-astro” package                   │
% │        Retrieving FITS file header information in LuaTeX    │
% │                                                             │
% │  Copyright (C) 2026 Valentin Dao                            │
% │                                                             │
% │  This file may be distributed and/or modified under the     │
% │  conditions of the LaTeX Project Public License, either     │
% │  version 1.3c of this license or (at your option) any       │
% │  later version.                                             │
% │                                                             │
% │  The latest version of this license is in:                  │
% │                                                             │
% │    > http://www.latex-project.org/lppl.txt                  │
% │                                                             │
% │  This work has the LPPL maintenance status 'maintained'     │
% │  and the current maintainer is Valentin Dao                 │
% │  (vdao.texdev@gmail.com).                                   │
% │                                                             │
% │  This work consists of the files listed in the              │
% │  'MANIFEST.md' file.                                        │
% │                                                             │
% │  The repository of this work can be found here:             │
% │                                                             │
% │    > https://github.com/TeXackers/fits-astro                │
% │                                                             │
% └─────────────────────────────────────────────────────────────┘
%
%<*driver>
% \fi
% \iffalse
\documentclass[11pt, a4paper]{l3doc}

\catcode`\@=11

\usepackage{fontspec}

\IfFontExistsTF{ChronicleTextG3}
	{
		\setmainfont{ChronicleTextG3}[
			Extension             = .otf,
			UprightFont           = *-Roman-Pro,
			UprightFeatures       = { Kerning = On },
			ItalicFont            = *-Italic-Pro,
			BoldFont              = *-Bold-Pro,
			BoldItalicFont        = *-BoldIta-Pro,
		]
	}{}

\IfFontExistsTF{Whitney}
	{
		\setsansfont{Whitney}[
			Extension        = .otf,
			UprightFont      = *-Medium,
			ItalicFont       = *-MediumItalic,
			BoldFont         = *-Bold,
			BoldItalicFont   = *-BoldItalic,
			UprightFeatures  = 
				{
					StylisticSet = 1,
					StylisticSet = 10,
					StylisticSet = 11,
				},
			BoldFeatures     =
				{
					StylisticSet = 1,
					StylisticSet = 10,
					StylisticSet = 11,
				},
			ItalicFeatures   =
				{
					StylisticSet = 1,
					StylisticSet = 10,
					StylisticSet = 11,
					StylisticSet = 8,
					StylisticSet = 9
				}
		]
	}{}

\IfFontExistsTF{MonospaceNeon}
	{
		\setmonofont{MonaspaceNeon}[
			Extension              = .otf,
			Scale                  = 0.8,
			UprightFont            = *-Regular,
			UprightFeatures        = { Kerning = On },
			ItalicFont             = *-Italic,
			BoldFont               = *-Bold,
			BoldItalicFont         = *-BoldItalic,
		]
	}{}

\usepackage{microtype}

\ExplSyntaxOn
\debug_on:n { all }
\usepackage{fits-astro}
\debug_off:n { all }
\ExplSyntaxOff

\usepackage[
	a4paper,
	marginparwidth  = 32mm,
	top             = 3cm,
	bottom          = 2cm,
	left            = 44mm,
	right           = 30mm,
	marginparsep    = 2mm,
	]{geometry}

\usepackage{fancyhdr}
\fancyhf{}
\fancyfoot[C]{\small\sffamily\itshape---\space\thepage\space\kern0.4mm\/---}
\renewcommand{\headrulewidth}{0pt}
\pagestyle{fancy}

\usepackage[silence]{codedescribe}
\usepackage{hologo}

\usepackage[svgnames]{xcolor}

\definecolor{RoyalBlue}{RGB}{0, 35, 102}
\definecolor{RoyalRed}{RGB}{157, 16, 45}
\definecolor{RoyalGreen}{HTML}{48845F}
\definecolor{urlcolour}{HTML}{B42F5E}

\definecolor{packagecolour}{HTML}{B86B00}
\definecolor{keycolour}{HTML}{004766}
\definecolor{macrocolour}{HTML}{047658}
\definecolor{macro3colour}{HTML}{27262E}
\definecolor{optioncolour}{HTML}{630476}

\defobjectfmt{macro}{code}{color=macrocolour}
\defobjectfmt{macro3}{code}{color=macro3colour}
\defobjectfmt{variable3}{code}{color=SlateGrey}
\defobjectfmt{pkg}{pkg}{font=\ttfamily}
\defobjectfmt{option}{option}{color=optioncolour, shape preadj=thin, shape posadj=thin, noshape}
\defgroupfmt{keys}{color=keycolour, noshape}

\newcodekey{fits}{
	ruleht        = 0,
	texcsstyle    = \bfseries\color{RoyalBlue},
	texcs         =
		[2]{
			loadheader,
			showheader,
			headerkey,
			setheaderkey,
			gsetheaderkey,
			HST,
			ra,
			dec,
			exptime
		},
	texcsstyle   = [2]{\color{macrocolour}},
	codeprefix    = {},
	resultprefix  = {}
}

\setcodelabels{
	new       = \textsf{new},
	update    = \textsf{update},
	note      = \textsf{N.B},
	remark    = \textsf{remark},
	and       = \textsf{and},
	or        = \textsf{or},
}

\lstset{
	gobble=2,
}

\usepackage{titlesec}
\titleformat*{\section}{\sffamily\LARGE\bfseries}
\titleformat*{\subsection}{\sffamily\Large\bfseries}

\usepackage{changelog}
\usepackage{enumitem}
\renewenvironment{changelogitemize}
  {\begin{itemize}[label=\raisebox{0.5ex}{$\scriptscriptstyle\blacktriangleright$}]}
  {\end{itemize}}

\setlength{\parindent}{0pt}

\usepackage{polyglossia}
\setmainlanguage{english}

\usepackage[perpage]{footmisc}

\usepackage{hyperref}
\hypersetup{
  pdfauthor   = {Valentin Dao},
  pdftitle    = {The fits-astro package},
  pdfcreator  = {LuaLaTeX with hyperref package},
  colorlinks  = true,
  linkcolor   = RoyalBlue,
  urlcolor    = urlcolour,
}

\title{The \href{https://ctan.org/pkg/fits-astro}{\textsf{\fitsastro@module}} package\medbreak{\Large\fitsastro@version}}
\author{Valentin Dao\footnote{E-mail: \href{mailto:vdao.texdev@gmail.com}{\ttfamily vdao.texdev@gmail.com}}}
\date{\today}

\listfiles

\EnableCrossrefs
\EnableDocumentation
\EnableImplementation

\begin{document}
	\DocInput{fits-astro-en.dtx}
\end{document}
%</driver>
% \fi
% 
% \begin{documentation}
% 
% \maketitle
% The \tsobj[pkg]{\fitsastro@module} package provides a simple interface to read \href{https://www.stsci.edu/hst/wfpc2/Wfpc2_dhb/intro_ch23.html}{Flexible Image Transport System} (FITS) files and access their header to retrieve information. It relies on a custom Lua script and therefore requires the Lua\hologo{LaTeXTeX} engine. Unlike more complex libraries, such as \texttt{astropy.io.fits} in Python, this package is designed solely for directly recovering key-value pairs from headers. It is \emph{not} intended for modifying them or fixing standardisation issues, let alone for reading the data part. Also note that as of now, the package \emph{does not} support files with multiple Header Data Units (HDUs) and will only read the primary header; i.e, all extensions will be ignored.
% 
% \section{Loading the package}
% 
% The package may be loaded in either of the following ways:
% \begin{itemize}
%		\item \tsobj[code]{\usepackage{fits-astro}} if compiling with Lua\LaTeX.
%		\item \tsobj[code]{\input{fits-astro.tex}} if compiling with Lua\TeX.
% \end{itemize}
% 
% \section{Package macros}
% 
% \begin{codedescribe}[macro]{\loadheader}
% \begin{codesyntax}
%		\tsobj[code]{\loadheader}\tsargs[marg]{FITS identification}\tsargs[marg]{FITS file path}
% \end{codesyntax}
%		This macro reads the \texttt{FITS} file and allocates the header information to the specified FITS identification, which can either be a string or a control sequence. In the latter case, \tsobj[macro]{\csstring} is used to strip off the leading escape character for internal storage. Therefore, if a control sequence is specified, it does not actually contain the header in any way. As a result, the two inputs are interchangeable. The macro furthermore uses the \texttt{Kpathsea} library, so the files shouldn't necessarily be in the current directory. This documentation will use a \href{https://fits.gsfc.nasa.gov/fits_samples.html}{public sample image} to demonstrate the package's macros (the image used is the first one in the table).
% \end{codedescribe}
% 
% \begin{codestore}[loadfits]
%		\loadheader\HST{WFPC2u5780205r_c0fx.fits}
%		\loadheader{HST}{WFPC2u5780205r_c0fx.fits} % Alternative way to load the file
% \end{codestore}
% 
% \tscode*[fits]{loadfits}
% \loadheader\HST{../../WFPC2u5780205r_c0fx.fits}
% 
% \begin{codedescribe}[macro]{\showheader}
% \begin{codesyntax}
%		\tsobj[code]{\showheader}\tsargs[marg]{FITS identification}
% \end{codesyntax}
%		This macro displays the header information for the specified FITS identification in the log file.
% \end{codedescribe}
% 
% \begin{codestore}[showfits]
%		\showheader\HST
%		\showheader{HST} % Would produce the same result
% \end{codestore}
% 
% \tscode*[fits]{showfits}
% 
% \begin{codedescribe}[macro]{\headerkey}
% \begin{codesyntax}
%		\tsobj[code]{\headerkey}\tsargs[marg]{FITS identification}\tsargs[marg]{key}
% \end{codesyntax}
%		This macro retrieves the value associated with the specified key from the header of the given FITS identification.
% \end{codedescribe}
% 
% \begin{codestore}[keyfits]
%		\headerkey\HST{FITSDATE}\par
%		\headerkey\HST{TELESCOP}
% \end{codestore}
% 
% \tsdemo[fits]{keyfits}
% 
% \begin{codedescribe}[macro]{\setheaderkey, \gsetheaderkey}
% \begin{codesyntax}
%		\tsobj[code]{\setheaderkey}\tsargs[marg]{FITS identification}\tsargs[marg]{key}\tsargs[marg]{control sequence}
% \end{codesyntax}
%		This macro retrieves the value associated with the specified key from the header of the given FITS identification and assigns it to the specified control sequence. The assignment is either local to the current group or global.
% \end{codedescribe}
% 
% \begin{codestore}[setfitskey]
%		\ttfamily
%		\begingroup
%		\setheaderkey\HST{RA_TARG}\ra
%		\setheaderkey\HST{DEC_TARG}\dec
%		\gsetheaderkey\HST{EXPTIME}\exptime
%		\meaning\ra\par
%		\meaning\dec\par
%		\meaning\exptime\par
%		\endgroup
%		\meaning\exptime\par
%		\meaning\ra\par
%		\meaning\dec
% \end{codestore}
% 
% \tsdemo[fits]{setfitskey}
% 
% \begin{changelog}[sectioncmd=\section*]
% \begin{version}[v=1.0.1, date=09-07-2026]
% \added
% \item French documentation.
% \fixed
% \item Improved Lua script and documentations.
% \end{version}
% \shortversion{v=1.0.0, date=06-04-2026, changes=Initial version.}
% \end{changelog}
% 
% \end{documentation}
% 
% \begin{implementation}
% 
% \newpage
% 
% \section{Implementation}
% 
% \subsection{\TeX\ package}
% 
%    \begin{macrocode}
%<*package-tex>
\ifdefined\loadheader\expandafter\endinput\fi

\def\FITSerr#1{\errmessage{Package fits Error: #1}}
\def\FITSwar#1{\message{Package fits Warning: #1}}

\unless\ifdefined\directlua\FITSerr{LuaTeX required}\fi

\directlua{dofile('fits-astro.lua')}

\def\loadheader#1#2{
  \directlua{
    local file = '\csstring#1'
    if FITS_data[file] then
      tex.print('\luaescapestring{\FITSerr{FITS file '#2' already loaded}}')
    else
      FITS_data[file] = load_FITS_header('#2')
    end
  }
}

\def\showheader#1{
  \directlua{
    show_fits(FITS_data['\csstring#1'])
  }
}

\def\headerkey#1#2{
  \directlua{
    if FITS_data['\csstring#1']['#2'] then
      get_header_key(FITS_data['\csstring#1'], '#2')
    else
      tex.print('\luaescapestring{\FITSwar{Header key '#2' not found}}')
    end
  }
}

\def\setheaderkey#1#2#3{
  \directlua{
    if FITS_data['\csstring#1']['#2'] then
      token.set_macro('\csstring#3', FITS_data['\csstring#1']['#2'])
    end
  }
}

\def\gsetheaderkey#1#2#3{
  \directlua{
    if FITS_data['\csstring#1']['#2'] then
      token.set_macro('\csstring#3', FITS_data['\csstring#1']['#2'], 'global')
    end
  }
}

%</package-tex>
%    \end{macrocode}
% \subsection{\LaTeX\ package}
% \c@CodelineNo=\z@
%    \begin{macrocode}
%<*package-latex>
\NeedsTeXFormat{LaTeX2e}

\def\fitsastro@module{fits-astro}
\def\fitsastro@version{v1.0.1}
\def\fitsastro@date{2026-07-09}
\def\fitsastro@description{Retrieving FITS file header information in LuaTeX}

\ProvidesPackage{fits-astro}[\fitsastro@date\space\fitsastro@version\space\fitsastro@description]

\input{fits-astro.tex}
%</package-latex>
%    \end{macrocode}
% \subsection{Lua script}
% \c@CodelineNo=\z@
%    \begin{macrocode}
%<*package-lua>
tex = tex or {}
texio = texio or {}
FITS_data = {}

function load_FITS_header(file)
  local file_path = kpse.find_file(file)
  if not file_path then
    tex.error("FITS file not found: " .. file)
  else
    local FITS_file = io.open(file_path, "rb")
    local header = {}
    repeat
      local header_line = FITS_file:read(80)
      if not header_line or #header_line < 80 then break end

      -- string
      local key, value = header_line:match("([%u%-%d_]+)%s*=%s*'([^']*)'")
      if not key then
        -- number or boolean
        key, value = header_line:match("([%u%-%d_]+)%s*=%s*([^/]+)")
      end

      if key and value then
        value = sanitize_FITS_key(value)
        if value:match("^%s*$") then
          header[key] = nil
        else
          header[key] = parse_FITS_key(key, value)
        end
      end
  until header_line:sub(1, 3) == "END"
  FITS_file:close()
  return header
  end
end

function parse_FITS_key(key, value)
  -- converts FITS standard status to a boolean
  if key == "SIMPLE" then
    if value == "T" then
      value = true
    else
      value = false
    end
    return value
  end
  -- converts numbers into proper integers
  local num = tonumber(value)
  if num then value = num end
  return value
end

function sanitize_FITS_key(value)
  value = value:match("^%s*(.-)%s*$") or value
  return value
end

function get_header_key(header, key)
  local value = header[key]
  if value then
    tex.print(value)
  end
end

function show_fits(header)
  texio.write_nl("FITS header information:")
  for key, value in pairs(header) do
    texio.write_nl(string.format("%s = %s", key, tostring(value)))
  end
end
%</package-lua>
%    \end{macrocode}
% \end{implementation}
% 
% \newpage
% 
% \PrintIndex
