Utilities/Sphinx: Give the Qt Help output a color scheme

The `qthelp` builder renders with Sphinx's `nonav` theme, so the color
scheme that `cmake.css` picks up from the `default` theme in the html
build is never generated for it.  The workaround that inlines the
`@import` in `cmake.css` silently falls back to `basic.css`, which
carries layout rules only, leaving the `.qch` pages with no styling of
their own.

Supply one, generated from a single template and two palettes.  Qt
Creator serves a documentation set's own `offline-dark.css` in place of
its `offline.css` or `offline-simple.css` when the Qt Creator theme is
dark, so link the stylesheet under one of those names and ship a dark
counterpart next to it.  Qt Creator 20 and older substitute only for
`offline-simple.css`, and request `offline.css` instead when the theme
is light, so install the light palette under both names.

The html build is unaffected: the extra stylesheet is linked, and its
static directory is added, only under the `qthelp` tag.
diff --git a/Help/release/dev/qthelp-dark-theme.rst b/Help/release/dev/qthelp-dark-theme.rst
new file mode 100644
index 0000000..692d598
--- /dev/null
+++ b/Help/release/dev/qthelp-dark-theme.rst
@@ -0,0 +1,8 @@
+qthelp-dark-theme
+-----------------
+
+* The Qt Help (``.qch``) rendition of the documentation now carries its own
+  color scheme.  Previously it was rendered with layout rules only, because
+  the ``qthelp`` builder does not generate the color scheme that the HTML
+  documentation uses.  A dark variant is shipped alongside it, which help
+  viewers such as Qt Creator use when their own theme is dark.
diff --git a/Utilities/Sphinx/CMakeLists.txt b/Utilities/Sphinx/CMakeLists.txt
index f80db68..ec3a662 100644
--- a/Utilities/Sphinx/CMakeLists.txt
+++ b/Utilities/Sphinx/CMakeLists.txt
@@ -78,6 +78,7 @@
 
 set(conf_docs "${CMake_SOURCE_DIR}/Help")
 set(conf_path "${CMAKE_CURRENT_SOURCE_DIR}")
+set(conf_qthelp_static "${CMAKE_CURRENT_BINARY_DIR}/static-qthelp")
 set(conf_version "${CMake_VERSION_MAJOR}.${CMake_VERSION_MINOR}.${CMake_VERSION_PATCH}")
 set(conf_release "${CMake_VERSION}")
 configure_file(conf.py.in conf.py @ONLY)
@@ -139,6 +140,11 @@
   endif()
   list(APPEND doc_formats qthelp)
 
+  # Generate the light and dark stylesheets, and tell `conf.py` to link them.
+  set(qthelp_css_outdir "${conf_qthelp_static}")
+  include("${CMAKE_CURRENT_SOURCE_DIR}/qthelp_css.cmake")
+  set(doc_qthelp_opts -t qthelp)
+
   set(qthelp_post_commands
     # Workaround for assistant prior to
     # https://codereview.qt-project.org/#change,82250 in Qt 4.
diff --git a/Utilities/Sphinx/conf.py.in b/Utilities/Sphinx/conf.py.in
index 590ccf4..1c98090 100644
--- a/Utilities/Sphinx/conf.py.in
+++ b/Utilities/Sphinx/conf.py.in
@@ -91,6 +91,14 @@
     'codebgcolor':      '#eeeeee',
     'codetextcolor':    '#333333',
 }
+if tags.has('qthelp'):
+    # The qthelp builder renders with the 'nonav' theme, which contributes no
+    # colors of its own, so link a stylesheet that supplies them.  Name it
+    # 'offline-simple.css' because Qt Creator serves the 'offline-dark.css'
+    # next to it instead when the Qt Creator theme is dark.
+    html_static_path.append('@conf_qthelp_static@')
+    html_css_files = ['offline-simple.css']
+
 html_title = 'CMake %s Documentation' % release
 html_short_title = '%s Documentation' % release
 html_favicon = '@conf_path@/static/cmake-favicon.ico'
diff --git a/Utilities/Sphinx/qthelp_css.cmake b/Utilities/Sphinx/qthelp_css.cmake
new file mode 100644
index 0000000..bcceb4e
--- /dev/null
+++ b/Utilities/Sphinx/qthelp_css.cmake
@@ -0,0 +1,113 @@
+# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+# file LICENSE.rst or https://cmake.org/licensing for details.
+
+# Generate the stylesheets that the Qt Help (.qch) rendition of the
+# documentation uses, from `static-qthelp/qthelp.css.in` and the two palettes
+# below.  The caller provides `qthelp_css_outdir`.
+#
+# Qt Creator serves a documentation set's own `offline-dark.css` in place of
+# its `offline.css` or `offline-simple.css` when the Qt Creator theme is dark,
+# so the pages link a stylesheet under one of those names and we ship a dark
+# counterpart next to it.  Qt Creator 20 and older only substitute for
+# `offline-simple.css`, and request `offline.css` instead when the theme is
+# light, so the light palette is installed under both names.
+
+# Light palette, matching `html_theme_options` in `conf.py.in` and the
+# Pygments style in `colors.py`.
+set(qthelp_body_bg          "#ffffff")
+set(qthelp_body_fg          "#444444")
+set(qthelp_link             "#2b63a8")
+set(qthelp_link_visited     "#2b63a8")
+set(qthelp_head_bg          "#f2f2f2")
+set(qthelp_head_fg          "#003564")
+set(qthelp_head_border      "#cccccc")
+set(qthelp_headerlink       "#3d8ff2")
+set(qthelp_muted            "#888888")
+set(qthelp_pre_bg           "#eeeeee")
+set(qthelp_pre_fg           "#333333")
+set(qthelp_pre_border       "#aacc99")
+set(qthelp_code_bg          "#ecf0f3")
+set(qthelp_code_fg          "#333333")
+set(qthelp_literal_link_bg  "#dfdfdf")
+set(qthelp_topic_bg         "#eeeeee")
+set(qthelp_note_bg          "#eeeeee")
+set(qthelp_note_border      "#cccccc")
+set(qthelp_seealso_bg       "#ffffcc")
+set(qthelp_seealso_border   "#ffff66")
+set(qthelp_warning_bg       "#ffe4e4")
+set(qthelp_warning_border   "#ff6666")
+set(qthelp_border           "#aaaaaa")
+set(qthelp_hll_bg           "#ffffcc")
+set(qthelp_tok_comment      "#408080")
+set(qthelp_tok_operator     "#555555")
+set(qthelp_tok_string       "#217a21")
+set(qthelp_tok_number       "#105030")
+set(qthelp_tok_builtin      "#333333")
+set(qthelp_tok_function     "#007020")
+set(qthelp_tok_variable     "#1080b0")
+set(qthelp_tok_tag          "#bb60d5")
+set(qthelp_tok_constant     "#4070a0")
+set(qthelp_tok_entity       "#70a020")
+set(qthelp_tok_attribute    "#906060")
+set(qthelp_tok_label        "#a0a000")
+set(qthelp_tok_exception    "#ff0000")
+set(qthelp_tok_whitespace   "#bbbbbb")
+
+configure_file(
+  "${CMAKE_CURRENT_SOURCE_DIR}/static-qthelp/qthelp.css.in"
+  "${qthelp_css_outdir}/offline.css"
+  @ONLY
+  )
+configure_file(
+  "${CMAKE_CURRENT_SOURCE_DIR}/static-qthelp/qthelp.css.in"
+  "${qthelp_css_outdir}/offline-simple.css"
+  @ONLY
+  )
+
+# Dark palette.  The background and foreground match the stylesheet that
+# Qt Creator falls back to for documentation that ships no dark variant, so
+# that the CMake pages sit well next to the Qt pages in the same help pane.
+set(qthelp_body_bg          "#262626")
+set(qthelp_body_fg          "#d0d0d0")
+set(qthelp_link             "#6ab0f3")
+set(qthelp_link_visited     "#6ab0f3")
+set(qthelp_head_bg          "#353637")
+set(qthelp_head_fg          "#a8c8ea")
+set(qthelp_head_border      "#4a4b4c")
+set(qthelp_headerlink       "#6ab0f3")
+set(qthelp_muted            "#9a9a9a")
+set(qthelp_pre_bg           "#2d2e2f")
+set(qthelp_pre_fg           "#d0d0d0")
+set(qthelp_pre_border       "#4e5a46")
+set(qthelp_code_bg          "#353637")
+set(qthelp_code_fg          "#d0d0d0")
+set(qthelp_literal_link_bg  "#3a3b3c")
+set(qthelp_topic_bg         "#2d2e2f")
+set(qthelp_note_bg          "#2d2e2f")
+set(qthelp_note_border      "#4a4b4c")
+set(qthelp_seealso_bg       "#39381f")
+set(qthelp_seealso_border   "#5c5a30")
+set(qthelp_warning_bg       "#3d2727")
+set(qthelp_warning_border   "#7a4040")
+set(qthelp_border           "#4a4b4c")
+set(qthelp_hll_bg           "#4a4a2a")
+set(qthelp_tok_comment      "#7fb7b7")
+set(qthelp_tok_operator     "#b0b0b0")
+set(qthelp_tok_string       "#6fc96f")
+set(qthelp_tok_number       "#5fc090")
+set(qthelp_tok_builtin      "#cccccc")
+set(qthelp_tok_function     "#5fc45f")
+set(qthelp_tok_variable     "#5fb8e8")
+set(qthelp_tok_tag          "#d79ae8")
+set(qthelp_tok_constant     "#85aee0")
+set(qthelp_tok_entity       "#a8d060")
+set(qthelp_tok_attribute    "#c89a9a")
+set(qthelp_tok_label        "#cfcf60")
+set(qthelp_tok_exception    "#ff6b6b")
+set(qthelp_tok_whitespace   "#555555")
+
+configure_file(
+  "${CMAKE_CURRENT_SOURCE_DIR}/static-qthelp/qthelp.css.in"
+  "${qthelp_css_outdir}/offline-dark.css"
+  @ONLY
+  )
diff --git a/Utilities/Sphinx/static-qthelp/qthelp.css.in b/Utilities/Sphinx/static-qthelp/qthelp.css.in
new file mode 100644
index 0000000..2ce0c64
--- /dev/null
+++ b/Utilities/Sphinx/static-qthelp/qthelp.css.in
@@ -0,0 +1,300 @@
+/* Style the Qt Help (.qch) rendition of the documentation.
+ *
+ * The qthelp builder uses Sphinx's "nonav" theme, so the color scheme that
+ * the "default" theme contributes to the html build is not generated here,
+ * leaving help viewers with just the layout rules of "basic.css".  This
+ * template supplies a color scheme on top of them.  It is configured twice:
+ * with a light palette into "offline.css" and "offline-simple.css", and with
+ * a dark palette into "offline-dark.css".  Qt Creator serves the latter in
+ * place of the former when its own user interface theme is dark.
+ *
+ * Qt Creator's default help backend renders with litehtml, which supports
+ * much less CSS than a browser does, so keep the rules here simple.
+ */
+
+/* Undo the full-window layout of "cmake.css", which has no navigation bar or
+   footer to place in the qthelp output, and which litehtml cannot lay out. */
+html,
+body {
+  background-color: @qthelp_body_bg@;
+}
+
+body {
+  display: block;
+  min-height: 0;
+  margin: 0;
+  padding: 0;
+  font-family: sans-serif;
+  color: @qthelp_body_fg@;
+}
+
+div.document,
+div.documentwrapper {
+  background-color: @qthelp_body_bg@;
+}
+
+/* "basic.css" caps the body width for the html build, where a sidebar takes
+   up the rest of the window.  A help viewer has no sidebar, so use the whole
+   pane; it is usually narrow to begin with. */
+div.body {
+  background-color: @qthelp_body_bg@;
+  color: @qthelp_body_fg@;
+  height: auto;
+  max-width: none;
+  padding: 0 20px 30px 20px;
+}
+
+/* -- hyperlinks ------------------------------------------------------------ */
+
+a {
+  color: @qthelp_link@;
+  text-decoration: none;
+}
+
+a:visited {
+  color: @qthelp_link_visited@;
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+/* -- headings -------------------------------------------------------------- */
+
+div.body h1,
+div.body h2,
+div.body h3,
+div.body h4,
+div.body h5,
+div.body h6 {
+  font-family: sans-serif;
+  background-color: @qthelp_head_bg@;
+  font-weight: normal;
+  color: @qthelp_head_fg@;
+  border-bottom: 1px solid @qthelp_head_border@;
+  margin: 20px -20px 10px -20px;
+  padding: 3px 0 3px 10px;
+}
+
+div.body h1 { margin-top: 0; font-size: 200%; }
+div.body h2 { font-size: 160%; }
+div.body h3 { font-size: 140%; }
+div.body h4 { font-size: 120%; }
+div.body h5 { font-size: 110%; }
+div.body h6 { font-size: 100%; }
+
+/* The section title links back to the table of contents.  Keep it looking
+   like a heading rather than like a link. */
+div.body h1 a.toc-backref,
+div.body h2 a.toc-backref,
+div.body h3 a.toc-backref,
+div.body h4 a.toc-backref,
+div.body h5 a.toc-backref,
+div.body h6 a.toc-backref {
+  color: @qthelp_head_fg@;
+  text-decoration: none;
+}
+
+a.headerlink {
+  color: @qthelp_headerlink@;
+  font-size: 0.8em;
+  padding: 0 4px 0 4px;
+  text-decoration: none;
+}
+
+a.headerlink:hover {
+  background-color: @qthelp_headerlink@;
+  color: @qthelp_body_bg@;
+}
+
+/* -- body text ------------------------------------------------------------- */
+
+div.body p,
+div.body dd,
+div.body li,
+div.body blockquote {
+  line-height: 130%;
+}
+
+.versionmodified {
+  color: @qthelp_muted@;
+}
+
+/* -- code ------------------------------------------------------------------ */
+
+pre {
+  padding: 5px;
+  background-color: @qthelp_pre_bg@;
+  color: @qthelp_pre_fg@;
+  line-height: 120%;
+  border: 1px solid @qthelp_pre_border@;
+  border-left: none;
+  border-right: none;
+}
+
+div.highlight {
+  background-color: @qthelp_pre_bg@;
+}
+
+code,
+code.docutils.literal {
+  background-color: @qthelp_code_bg@;
+  color: @qthelp_code_fg@;
+  padding: 0 1px 0 1px;
+  font-size: 0.95em;
+}
+
+/* Cross-reference links are rendered as inline code; let the link color win. */
+a code,
+a code.docutils.literal {
+  color: @qthelp_link@;
+}
+
+th code {
+  background-color: inherit;
+}
+
+.literal-block a.reference.internal {
+  background-color: @qthelp_literal_link_bg@;
+}
+
+/* "signature" directive argument, styled like a <pre> block. */
+.signature .sig {
+  background-color: @qthelp_pre_bg@;
+  color: @qthelp_pre_fg@;
+  border-color: @qthelp_pre_border@;
+}
+
+/* -- admonitions and topics ------------------------------------------------ */
+
+nav.contents,
+aside.topic,
+div.topic {
+  background-color: @qthelp_topic_bg@;
+  border: 1px solid @qthelp_note_border@;
+}
+
+div.admonition,
+div.note {
+  background-color: @qthelp_note_bg@;
+  border: 1px solid @qthelp_note_border@;
+}
+
+div.seealso {
+  background-color: @qthelp_seealso_bg@;
+  border: 1px solid @qthelp_seealso_border@;
+}
+
+div.warning {
+  background-color: @qthelp_warning_bg@;
+  border: 1px solid @qthelp_warning_border@;
+}
+
+p.admonition-title {
+  display: inline;
+}
+
+p.admonition-title:after {
+  content: ":";
+}
+
+div.admonition p.admonition-title + p {
+  display: inline;
+}
+
+div.outdated {
+  background-color: @qthelp_seealso_bg@;
+  color: @qthelp_body_fg@;
+}
+
+/* -- tables ---------------------------------------------------------------- */
+
+table.docutils {
+  border-collapse: collapse;
+}
+
+table.docutils td,
+table.docutils th {
+  border: 1px solid @qthelp_border@;
+}
+
+table.docutils th {
+  background-color: @qthelp_head_bg@;
+  color: @qthelp_head_fg@;
+}
+
+table.indextable tr.cap {
+  background-color: @qthelp_head_bg@;
+}
+
+/* The index and search result tables are layout-only. */
+table.indextable td,
+table.indextable th {
+  border: none;
+}
+
+hr {
+  border: 0;
+  border-top: 1px solid @qthelp_border@;
+}
+
+/* -- search highlighting --------------------------------------------------- */
+
+.highlighted,
+.highlight .hll {
+  background-color: @qthelp_hll_bg@;
+}
+
+/* -- syntax highlighting --------------------------------------------------- */
+
+/* Overrides for "pygments.css", which is generated from `colors.py` with a
+   light background in mind and is linked ahead of this file. */
+
+.highlight { background: @qthelp_pre_bg@; }
+.highlight .c,
+.highlight .ch,
+.highlight .cm,
+.highlight .cp,
+.highlight .cpf,
+.highlight .c1,
+.highlight .cs { color: @qthelp_tok_comment@; }
+.highlight .o,
+.highlight .ow { color: @qthelp_tok_operator@; }
+.highlight .s,
+.highlight .sa,
+.highlight .sb,
+.highlight .sc,
+.highlight .dl,
+.highlight .sd,
+.highlight .s2,
+.highlight .se,
+.highlight .sh,
+.highlight .si,
+.highlight .sx,
+.highlight .sr,
+.highlight .s1,
+.highlight .ss { color: @qthelp_tok_string@; }
+.highlight .m,
+.highlight .mb,
+.highlight .mf,
+.highlight .mh,
+.highlight .mi,
+.highlight .mo,
+.highlight .il { color: @qthelp_tok_number@; }
+.highlight .nb,
+.highlight .bp { color: @qthelp_tok_builtin@; }
+.highlight .nf,
+.highlight .fm { color: @qthelp_tok_function@; }
+.highlight .nv,
+.highlight .vc,
+.highlight .vg,
+.highlight .vi,
+.highlight .vm { color: @qthelp_tok_variable@; }
+.highlight .nt { color: @qthelp_tok_tag@; }
+.highlight .no { color: @qthelp_tok_constant@; }
+.highlight .ni { color: @qthelp_tok_entity@; }
+.highlight .na { color: @qthelp_tok_attribute@; }
+.highlight .nl { color: @qthelp_tok_label@; }
+.highlight .ne { color: @qthelp_tok_exception@; }
+.highlight .w { color: @qthelp_tok_whitespace@; }