diff --git a/.github/workflows/build_assets.yml b/.github/workflows/build_assets.yml
index 6b252624..26f9eace 100644
--- a/.github/workflows/build_assets.yml
+++ b/.github/workflows/build_assets.yml
@@ -32,7 +32,7 @@ jobs:
with:
name: nw-assets
path: |
- novelwriter/assets/manual.pdf
+ novelwriter/assets/manual*.pdf
novelwriter/assets/sample.zip
novelwriter/assets/i18n/*.qm
if-no-files-found: error
diff --git a/docs/source/tech_source.rst b/docs/source/tech_source.rst
index 93521416..6e0fc62b 100644
--- a/docs/source/tech_source.rst
+++ b/docs/source/tech_source.rst
@@ -153,8 +153,9 @@ You can also build a PDF manual from the documentation using the ``pkgutils.py``
.. code-block:: bash
- python pkgutils.py manual
+ python pkgutils.py docs-pdf en
-This will build the documentation as a PDF using LaTeX. The file will then be copied into the
-assets folder and made available in the **Help** menu in novelWriter. The Sphinx build system has a
-few extra dependencies when building the PDF. Please check the `Sphinx Docs`_ for more details.
+This will build the English documentation as a PDF using LaTeX. The file will then be copied into
+the assets folder and made available in the **Help** menu in novelWriter. Replace ``en`` with
+``all`` to build for all languages. The Sphinx build system has a few extra dependencies when
+building the PDF. Please check the `Sphinx Docs`_ for more details.
diff --git a/novelwriter/config.py b/novelwriter/config.py
index 51d6e164..c6dbb47b 100644
--- a/novelwriter/config.py
+++ b/novelwriter/config.py
@@ -303,7 +303,8 @@ class Config:
@property
def pdfDocs(self) -> Path | None:
- return self._manuals.get(f"manual_{self.locale.name()}", self._manuals.get("manual"))
+ """Return the local manual PDF file, if any exist."""
+ return self._manuals.get(f"manual_{self.locale.bcp47Name()}", self._manuals.get("manual"))
@property
def nwLangPath(self) -> Path:
diff --git a/pkgutils.py b/pkgutils.py
index 8f72d8d1..fc30b985 100755
--- a/pkgutils.py
+++ b/pkgutils.py
@@ -37,6 +37,7 @@ import utils.build_appimage
import utils.build_binary
import utils.build_debian
import utils.build_windows
+import utils.docs
import utils.icon_themes
from utils.common import ROOT_DIR, SETUP_DIR, extractVersion, readFile, stripVersion, writeFile
@@ -88,13 +89,13 @@ def cleanBuildDirs(args: argparse.Namespace) -> None:
print("")
folders = [
- ROOT_DIR / "build",
ROOT_DIR / "build_bin",
- ROOT_DIR / "dist",
+ ROOT_DIR / "build",
+ ROOT_DIR / "dist_appimage",
ROOT_DIR / "dist_bin",
ROOT_DIR / "dist_deb",
- ROOT_DIR / "dist_minimal",
- ROOT_DIR / "dist_appimage",
+ ROOT_DIR / "dist_doc",
+ ROOT_DIR / "dist",
ROOT_DIR / "novelWriter.egg-info",
]
@@ -211,20 +212,21 @@ if __name__ == "__main__":
)
)
cmdUpdateDocsPo.add_argument("lang", nargs="+")
- cmdUpdateDocsPo.set_defaults(func=utils.assets.updateDocsTranslationSources)
+ cmdUpdateDocsPo.set_defaults(func=utils.docs.updateDocsTranslationSources)
- # Build Docs i18n Files
- cmdBuildU18nDocs = parsers.add_parser(
- "docs-lrelease", help="Build the translated PDF manual files."
+ # Build PDF Docs
+ cmdBuildPdfDocs = parsers.add_parser(
+ "docs-pdf", help="Build the PDF manual files."
)
- cmdBuildU18nDocs.add_argument("lang", nargs="+")
- cmdBuildU18nDocs.set_defaults(func=utils.assets.buildDocsTranslationAssets)
+ cmdBuildPdfDocs.add_argument("lang", nargs="+")
+ cmdBuildPdfDocs.set_defaults(func=utils.docs.buildPdfDocAssets)
- # Build Manual
- cmdBuildManual = parsers.add_parser(
- "manual", help="Build the help documentation as a PDF (requires LaTeX)."
+ # Build HTML Docs
+ cmdBuildHtmlDocs = parsers.add_parser(
+ "docs-html", help="Build the HTML docs."
)
- cmdBuildManual.set_defaults(func=utils.assets.buildPdfManual)
+ cmdBuildHtmlDocs.add_argument("lang", nargs="+")
+ cmdBuildHtmlDocs.set_defaults(func=utils.docs.buildHtmlDocs)
# Build Sample
cmdBuildSample = parsers.add_parser(
@@ -234,13 +236,13 @@ if __name__ == "__main__":
# Clean Assets
cmdCleanAssets = parsers.add_parser(
- "clean-assets", help="Delete assets built by manual, sample and qtlrelease."
+ "clean-assets", help="Delete assets built by docs-pdf, sample and qtlrelease."
)
cmdCleanAssets.set_defaults(func=utils.assets.cleanBuiltAssets)
# Build Assets
cmdBuildAssets = parsers.add_parser(
- "build-assets", help="Build all assets. Includes manual, sample and qtlrelease."
+ "build-assets", help="Build all assets. Includes docs-pdf, sample and qtlrelease."
)
cmdBuildAssets.set_defaults(func=utils.assets.buildAllAssets)
diff --git a/utils/assets.py b/utils/assets.py
index c9195b3a..1849c696 100644
--- a/utils/assets.py
+++ b/utils/assets.py
@@ -21,7 +21,6 @@ along with this program. If not, see .
from __future__ import annotations
import argparse
-import os
import subprocess
import sys
import zipfile
@@ -29,51 +28,7 @@ import zipfile
from pathlib import Path
from utils.common import ROOT_DIR, writeFile
-
-
-def buildPdfManual(args: argparse.Namespace | None = None) -> None:
- """This function will build the documentation as manual.pdf."""
- print("")
- print("Building PDF Manual")
- print("===================")
- print("")
-
- buildFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf"
- finalFile = ROOT_DIR / "novelwriter" / "assets" / "manual.pdf"
- finalFile.unlink(missing_ok=True)
-
- try:
- subprocess.call(["make", "clean"], cwd="docs")
- exCode = subprocess.call(["make", "latexpdf"], cwd="docs")
- if exCode == 0:
- print("")
- buildFile.rename(finalFile)
- else:
- raise Exception(f"Build returned error code {exCode}")
-
- print("PDF manual build: OK")
- print("")
-
- except Exception as exc:
- print("PDF manual build: FAILED")
- print("")
- print(str(exc))
- print("")
- print("Dependencies:")
- print(" * pip install sphinx")
- print(" * Package latexmk")
- print(" * LaTeX build system")
- print("")
- print(" On Debian/Ubuntu, install: python3-sphinx latexmk texlive texlive-latex-extra")
- print("")
- sys.exit(1)
-
- if not finalFile.is_file():
- print("No output file was found!")
- print("")
- sys.exit(1)
-
- return
+from utils.docs import buildPdfDocAssets
def buildSampleZip(args: argparse.Namespace | None = None) -> None:
@@ -251,84 +206,6 @@ def buildTranslationAssets(args: argparse.Namespace | None = None) -> None:
return
-def updateDocsTranslationSources(args: argparse.Namespace) -> None:
- """Build the documentation .po files."""
- print("")
- print("Building Docs Translation Files")
- print("===============================")
- print("")
-
- docsDir = ROOT_DIR / "docs"
- locsDir = ROOT_DIR / "docs" / "source" / "locales"
- locsDir.mkdir(exist_ok=True)
-
- print("Generating POT Files")
- subprocess.call(["make", "gettext"], cwd=docsDir)
- print("")
-
- lang = args.lang
- update = []
- if lang == ["all"]:
- update = [i.stem for i in locsDir.iterdir() if i.is_dir()]
- else:
- update = lang
-
- print("Generating PO Files")
- print("Languages: ", update)
- print("")
-
- for code in update:
- subprocess.call(["sphinx-intl", "update", "-p", "build/gettext", "-l", code], cwd=docsDir)
- print("")
-
- print("Done")
- print("")
-
- return
-
-
-def buildDocsTranslationAssets(args: argparse.Namespace | None = None) -> None:
- """Build the documentation i18n PDF files."""
- from PyQt6.QtCore import QLocale
-
- print("")
- print("Building Docs Manuals")
- print("=====================")
- print("")
-
- docsDir = ROOT_DIR / "docs"
- locsDir = ROOT_DIR / "docs" / "source" / "locales"
- pdfFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf"
- locsDir.mkdir(exist_ok=True)
-
- lang = args.lang if args else ["all"]
- build = []
- if lang == ["all"]:
- build = [i.stem for i in locsDir.iterdir() if i.is_dir()]
- else:
- build = lang
-
- for code in build:
- data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8")
- authors = [x for x in data.splitlines() if x and not x.startswith("#")]
- env = os.environ.copy()
- env["SPHINX_I18N_AUTHORS"] = ", ".join(authors)
- exCode = subprocess.call(
- f"make -e SPHINXOPTS=\"-D language='{code}'\" clean latexpdf",
- cwd=docsDir, env=env, shell=True
- )
- if exCode == 0:
- print("")
- name = f"manual_{QLocale(code).name()}.pdf"
- pdfFile.rename(ROOT_DIR / "novelwriter" / "assets" / name)
- else:
- raise Exception(f"Build returned error code {exCode}")
-
- print("")
-
- return
-
-
def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None:
"""Remove assets built by this script."""
print("")
@@ -352,8 +229,7 @@ def cleanBuiltAssets(args: argparse.Namespace | None = None) -> None:
def buildAllAssets(args: argparse.Namespace) -> None:
"""Build all assets."""
cleanBuiltAssets()
- buildPdfManual()
buildSampleZip()
buildTranslationAssets()
- buildDocsTranslationAssets()
+ buildPdfDocAssets()
return
diff --git a/utils/docs.py b/utils/docs.py
new file mode 100644
index 00000000..4d036075
--- /dev/null
+++ b/utils/docs.py
@@ -0,0 +1,151 @@
+"""
+novelWriter – Documentation
+===========================
+
+This file is a part of novelWriter
+Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+"""
+from __future__ import annotations
+
+import argparse
+import os
+import shutil
+import subprocess
+
+from utils.common import ROOT_DIR
+
+
+def updateDocsTranslationSources(args: argparse.Namespace) -> None:
+ """Build the documentation .po files."""
+ print("")
+ print("Building Docs Translation Files")
+ print("===============================")
+ print("")
+
+ docsDir = ROOT_DIR / "docs"
+ locsDir = ROOT_DIR / "docs" / "source" / "locales"
+ locsDir.mkdir(exist_ok=True)
+
+ print("Generating POT Files")
+ subprocess.call(["make", "gettext"], cwd=docsDir)
+ print("")
+
+ lang = args.lang
+ update = []
+ if lang == ["all"]:
+ update = [i.stem for i in locsDir.iterdir() if i.is_dir()]
+ else:
+ update = lang
+
+ print("Generating PO Files")
+ print("Languages: ", update)
+ print("")
+
+ for code in update:
+ subprocess.call(["sphinx-intl", "update", "-p", "build/gettext", "-l", code], cwd=docsDir)
+ print("")
+
+ print("Done")
+ print("")
+
+ return
+
+
+def buildHtmlDocs(args: argparse.Namespace | None = None) -> None:
+ """Build the documentation files."""
+
+ print("")
+ print("Building HTML Docs")
+ print("==================")
+ print("")
+
+ bldRoot = ROOT_DIR / "dist_doc"
+ docsDir = ROOT_DIR / "docs"
+ locsDir = ROOT_DIR / "docs" / "source" / "locales"
+ locsDir.mkdir(exist_ok=True)
+ bldRoot.mkdir(exist_ok=True)
+
+ lang = args.lang if args else ["all"]
+ build = []
+ if lang == ["all"]:
+ build = ["en"] + [i.stem for i in locsDir.iterdir() if i.is_dir()]
+ else:
+ build = lang
+
+ for code in build:
+ outDir = bldRoot / code
+ env = os.environ.copy()
+ cmd = "make clean html"
+ if code != "en":
+ data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8")
+ authors = [x for x in data.splitlines() if x and not x.startswith("#")]
+ env["SPHINX_I18N_AUTHORS"] = ", ".join(authors)
+ cmd += f" -e SPHINXOPTS=\"-D language='{code}'\""
+
+ if (ex := subprocess.call(cmd, cwd=docsDir, env=env, shell=True)) == 0:
+ print("")
+ if outDir.exists():
+ shutil.rmtree(outDir)
+ outDir.mkdir()
+ (docsDir / "build" / "html").rename(outDir / "html")
+ else:
+ raise Exception(f"Build returned error code {ex}")
+
+ print("")
+
+ return
+
+
+def buildPdfDocAssets(args: argparse.Namespace | None = None) -> None:
+ """Build the documentation PDF files."""
+
+ print("")
+ print("Building Docs Manuals")
+ print("=====================")
+ print("")
+
+ docsDir = ROOT_DIR / "docs"
+ locsDir = ROOT_DIR / "docs" / "source" / "locales"
+ pdfFile = ROOT_DIR / "docs" / "build" / "latex" / "manual.pdf"
+ locsDir.mkdir(exist_ok=True)
+
+ lang = args.lang if args else ["all"]
+ build = []
+ if lang == ["all"]:
+ build = ["en"] + [i.stem for i in locsDir.iterdir() if i.is_dir()]
+ else:
+ build = lang
+
+ for code in build:
+ env = os.environ.copy()
+ cmd = "make clean latexpdf"
+ name = "manual.pdf"
+ if code != "en":
+ data = (locsDir / f"authors_{code}.conf").read_text(encoding="utf-8")
+ authors = [x for x in data.splitlines() if x and not x.startswith("#")]
+ env["SPHINX_I18N_AUTHORS"] = ", ".join(authors)
+ cmd += f" -e SPHINXOPTS=\"-D language='{code}'\""
+ name = f"manual_{code}.pdf"
+
+ if (ex := subprocess.call(cmd, cwd=docsDir, env=env, shell=True)) == 0:
+ print("")
+ pdfFile.rename(ROOT_DIR / "novelwriter" / "assets" / name)
+ else:
+ raise Exception(f"Build returned error code {ex}")
+
+ print("")
+
+ return