diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 0db53ce..d7e7300 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -41,7 +41,7 @@ jobs:
grep -v "pyscard" dev-requirements.txt > docs-requirements.txt
python -m pip install -r docs-requirements.txt
# Explicitly ensure sphinx, theme, and graphviz bindings are installed
- python -m pip install sphinx sphinx-rtd-theme graphviz
+ python -m pip install sphinx sphinx-rtd-theme graphviz cairosvg pillow
echo "Installation check:"
python -m pip list | grep -i sphinx
@@ -61,6 +61,20 @@ jobs:
echo "Generated diagram files:"
ls -la _build/html/_images/*.svg || echo "No SVG files found"
+ # 6b. Install LaTeX and build the PDF (Yubico-style, pdflatex)
+ - name: Install LaTeX for PDF
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y --no-install-recommends \
+ latexmk texlive-latex-recommended texlive-latex-extra \
+ texlive-fonts-recommended tex-gyre
+
+ - name: Build PDF (pdflatex)
+ run: |
+ cd docs
+ sphinx-build -M latexpdf . _build
+ cp _build/latex/cryptnox-sdk-py.pdf _build/html/cryptnox-sdk-py.pdf
+
# 7. Deploy HTML documentation to GitHub Pages
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 4092399..944821d 100644
Binary files a/dev-requirements.txt and b/dev-requirements.txt differ
diff --git a/docs/_static/cryptnox-logo.png b/docs/_static/cryptnox-logo.png
deleted file mode 100644
index b5d3148..0000000
Binary files a/docs/_static/cryptnox-logo.png and /dev/null differ
diff --git a/docs/_static/cryptnox-logo.svg b/docs/_static/cryptnox-logo.svg
new file mode 100644
index 0000000..9fed9ce
--- /dev/null
+++ b/docs/_static/cryptnox-logo.svg
@@ -0,0 +1,15 @@
+
diff --git a/docs/conf.py b/docs/conf.py
index 0cbaf1d..6cb6985 100755
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -10,6 +10,28 @@
import sys
sys.path.insert(0, os.path.abspath(".."))
+# Derive the navy PDF cover logo from the white HTML SVG at build time, so only the
+# SVG is a committed source asset (needs cairosvg + Pillow, both in the docs requirements).
+try:
+ import io as _io
+ import cairosvg
+ from PIL import Image as _Image
+except ImportError as _e:
+ raise RuntimeError(
+ "Docs build requires cairosvg and Pillow to generate the PDF cover logo; "
+ "install them (pip install cairosvg pillow)."
+ ) from _e
+_static = os.path.join(os.path.dirname(__file__), "_static")
+with open(os.path.join(_static, "cryptnox-logo.svg"), encoding="utf-8") as _f:
+ _svg = _f.read()
+_png = cairosvg.svg2png(
+ bytestring=_svg.replace('fill="white"', 'fill="#101f2e"').encode(),
+ output_width=1200, output_height=226,
+)
+_Image.open(_io.BytesIO(_png)).save(
+ os.path.join(_static, "cryptnox-logo-dark.png"), dpi=(400, 400)
+)
+
project = 'cryptnox-sdk-py'
copyright = '2026, Cryptnox SA'
author = 'Cryptnox'
@@ -142,7 +164,7 @@
html_static_path = ['_static']
# Logo configuration
-html_logo = "_static/cryptnox-logo.png"
+html_logo = "_static/cryptnox-logo.svg"
html_favicon = "_static/favicon.png"
# Custom CSS and JS
@@ -174,3 +196,118 @@
html_show_sourcelink = False
html_copy_source = False
html_show_sphinx = False
+
+# -- Options for PDF (LaTeX) output ------------------------------------------
+# Built by CI with pdflatex, same as Yubico's tech manual. Output: cryptnox-sdk-py.pdf
+
+today = 'June 20, 2026' # fixed doc date on the cover
+autodoc_preserve_defaults = True # show source default exprs, not expanded values
+
+latex_engine = 'pdflatex'
+latex_logo = '_static/cryptnox-logo-dark.png' # white logo is invisible on white PDF title page
+latex_domain_indices = False # no Python Module Index in the PDF (kept in HTML)
+latex_documents = [
+ ('index', 'cryptnox-sdk-py.tex', 'Cryptnox SDK for Python Manual', author, 'manual'),
+]
+latex_elements = {
+ 'papersize': 'a4paper',
+ 'pointsize': '11pt',
+ 'figure_align': 'H',
+ 'sphinxsetup': 'pre_border-radius=0pt', # sharp rectangle corners on code-block (command-line) frames
+ 'extraclassoptions': 'oneside,openany', # no blank filler pages (web PDF)
+ 'printindex': '', # drop the general Index from the PDF (kept in HTML)
+ 'fncychap': '', # no fancy chapter rules; titlesec styles chapters instead
+ 'preamble': r'''
+% pdflatex can't render colour emoji; drop the ones used in the docs
+\DeclareUnicodeCharacter{1F4B3}{}% credit card
+\DeclareUnicodeCharacter{1F4CA}{}% bar chart
+\DeclareUnicodeCharacter{1F4DA}{}% books
+\DeclareUnicodeCharacter{1F4C4}{}% page facing up
+% Left-align body text (ragged right instead of justified)
+\usepackage[document]{ragged2e}
+% Drop the "(continues on next page)" / "(continued from previous page)" labels (parens included) on code blocks
+\AtBeginDocument{\renewcommand*\sphinxstylecodecontinued[1]{}\renewcommand*\sphinxstylecodecontinues[1]{}}
+% Whole document in the sans font (TeX Gyre Heros)
+\renewcommand{\familydefault}{\sfdefault}
+% Sans-serif TOC entries
+\AtBeginDocument{\addtocontents{toc}{\protect\sffamily}}
+% Left-align figures/diagrams too (neutralize their built-in \centering)
+\usepackage{etoolbox}
+\AtBeginEnvironment{figure}{\let\centering\raggedright}
+% Cap figure height below the text height so tall diagrams leave room for their
+% caption instead of pushing it onto the page footer
+\makeatletter
+\AtBeginDocument{\spx@image@maxheight=0.85\textheight}
+\makeatother
+% Flatten autodoc indentation: object descriptions + Parameters/Returns lists align left
+\makeatletter
+\renewenvironment{fulllineitems}{%
+ \begin{list}{}{\labelwidth\z@ \leftmargin\z@ \rightmargin\z@
+ \topsep-\parskip \partopsep\parskip \itemsep-\parsep
+ \let\makelabel=\py@itemnewline}%
+}{\end{list}}
+\makeatother
+\setlength{\leftmargini}{1.2em}
+\setlength{\leftmarginii}{1.2em}
+\setlength{\leftmarginiii}{1.2em}
+% Left-aligned chapter headings
+\usepackage{titlesec}
+\titleformat{\chapter}[hang]{\sffamily\bfseries\huge}{\thechapter}{1em}{}
+\titlespacing*{\chapter}{0pt}{0pt}{20pt}
+\usepackage{fancyhdr}
+\def\headruleskip{4pt}\def\footruleskip{4pt}% gap between header/footer text and rule
+\makeatletter
+% Centered page header (doc title) + copyright footer
+\AtBeginDocument{%
+ \fancypagestyle{normal}{%
+ \fancyhf{}%
+ \fancyhead[C]{\sffamily\nouppercase{\@title}}%
+ \fancyfoot[L]{\sffamily\copyright{} 2026 Cryptnox SA}%
+ \fancyfoot[R]{\sffamily\thepage}%
+ \renewcommand{\headrulewidth}{0.4pt}%
+ \renewcommand{\footrulewidth}{0.4pt}%
+ }%
+ \fancypagestyle{plain}{%
+ \fancyhf{}%
+ \fancyfoot[L]{\sffamily\copyright{} 2026 Cryptnox SA}%
+ \fancyfoot[R]{\sffamily\thepage}%
+ \renewcommand{\headrulewidth}{0pt}%
+ \renewcommand{\footrulewidth}{0.4pt}%
+ }%
+ \pagestyle{normal}%
+}
+% Centered title page (default is right-aligned); author line removed (logo brands it)
+\renewcommand{\sphinxmaketitle}{%
+ \let\sphinxrestorepageanchorsetting\relax
+ \ifHy@pageanchor\def\sphinxrestorepageanchorsetting{\Hy@pageanchortrue}\fi
+ \hypersetup{pageanchor=false}%
+ \begin{titlepage}%
+ \let\footnotesize\small \let\footnoterule\relax
+ \begingroup
+ \def\endgraf{ }\def\and{\& }%
+ \pdfstringdefDisableCommands{\def\\{, }}%
+ \hypersetup{pdfauthor={\@author}, pdftitle={\@title}}%
+ \endgroup
+ \noindent\rule{\textwidth}{1pt}\par
+ \begin{flushright}%
+ \vskip 1em%
+ \includegraphics[width=7cm]{cryptnox-logo-dark}\par
+ \vskip 2em%
+ {\LARGE\py@HeaderFamily \@title \par}%
+ \vskip 0.5em%
+ {\large\itshape \py@release\releaseinfo \par}%
+ \vfill
+ {\large \@date \par}%
+ \end{flushright}%
+ \@thanks
+ \end{titlepage}%
+ \setcounter{footnote}{0}%
+ \let\thanks\relax\let\maketitle\relax
+ \clearpage
+ \ifdefined\sphinxbackoftitlepage\sphinxbackoftitlepage\fi
+ \if@openright\cleardoublepage\else\clearpage\fi
+ \sphinxrestorepageanchorsetting
+}
+\makeatother
+''',
+}
diff --git a/docs/index.rst b/docs/index.rst
index 391d18f..eec4cb5 100755
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,7 +1,11 @@
Cryptnox SDK Python Documentation
==================================
-Welcome to the Cryptnox SDK Python technical documentation.
+.. only:: html
+
+ Welcome to the Cryptnox SDK Python technical documentation.
+
+ 📄 `Download this documentation as PDF `__
.. toctree::
:maxdepth: 2