diff --git a/novelwriter/constants.py b/novelwriter/constants.py index bf4f58c6..8f97ea2f 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -250,6 +250,7 @@ class nwLabels: nwBuildFmt.NWD: QT_TRANSLATE_NOOP("Constant", "novelWriter Markup (.txt)"), nwBuildFmt.STD_MD: QT_TRANSLATE_NOOP("Constant", "Standard Markdown (.md)"), nwBuildFmt.EXT_MD: QT_TRANSLATE_NOOP("Constant", "Extended Markdown (.md)"), + nwBuildFmt.PDF: QT_TRANSLATE_NOOP("Constant", "Portable Document Format (.pdf)"), nwBuildFmt.J_HTML: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter HTML (.json)"), nwBuildFmt.J_NWD: QT_TRANSLATE_NOOP("Constant", "JSON + novelWriter Markup (.json)"), } @@ -260,6 +261,7 @@ class nwLabels: nwBuildFmt.NWD: ".txt", nwBuildFmt.STD_MD: ".md", nwBuildFmt.EXT_MD: ".md", + nwBuildFmt.PDF: ".pdf", nwBuildFmt.J_HTML: ".json", nwBuildFmt.J_NWD: ".json", } diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index 360ae8ed..664cc1a5 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -41,7 +41,7 @@ from novelwriter.formats.tohtml import ToHtml from novelwriter.formats.tokenizer import Tokenizer from novelwriter.formats.tomarkdown import ToMarkdown from novelwriter.formats.toodt import ToOdt -from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument +from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.formats.toraw import ToRaw logger = logging.getLogger(__name__) @@ -121,24 +121,15 @@ class NWBuildDocument: self._queue.append(item.itemHandle) return - def iterBuildPreview(self, theme: TextDocumentTheme) -> Iterable[tuple[int, bool]]: + def iterBuildPreview(self) -> Iterable[tuple[int, bool]]: """Build a preview QTextDocument.""" makeObj = ToQTextDocument(self._project) filtered = self._setupBuild(makeObj) + makeObj.initDocument() - self._count = True self._outline = True - font = QFont() - font.fromString(self._build.getStr("format.textFont")) - - makeObj.initDocument(font, theme) - for i, tHandle in enumerate(self._queue): - self._error = None - if filtered.get(tHandle, (False, 0))[0]: - yield i, self._doBuild(makeObj, tHandle) - else: - yield i, False + yield from self._iterBuild(makeObj, filtered) makeObj.appendFootnotes() @@ -191,6 +182,19 @@ class NWBuildDocument: if self._build.getBool("format.replaceTabs"): makeObj.replaceTabs(nSpaces=4, spaceChar=" ") + elif bFormat == nwBuildFmt.PDF: + makeObj = ToQTextDocument(self._project) + filtered = self._setupBuild(makeObj) + makeObj.initDocument() + + yield from self._iterBuild(makeObj, filtered) + + makeObj.appendFootnotes() + + else: + logger.error("Unsupported document format") + return + self._error = None self._cache = makeObj @@ -308,6 +312,9 @@ class NWBuildDocument: scale*self._build.getFloat("format.rightMargin"), ) + if isinstance(bldObj, ToQTextDocument): + pass + filtered = self._build.buildItemFilter( self._project, withRoots=self._build.getBool("text.addNoteHeadings") ) diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 649509a1..55bcbfc6 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -184,8 +184,9 @@ class nwBuildFmt(Enum): NWD = 3 STD_MD = 4 EXT_MD = 5 - J_HTML = 6 - J_NWD = 7 + PDF = 6 + J_HTML = 7 + J_NWD = 8 class nwStatusShape(Enum): diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py index 07c54e45..4a46bdd5 100644 --- a/novelwriter/formats/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -31,14 +31,15 @@ from PyQt5.QtGui import ( QColor, QFont, QFontMetricsF, QTextBlockFormat, QTextCharFormat, QTextCursor, QTextDocument ) +from PyQt5.QtPrintSupport import QPrinter from novelwriter.constants import nwHeaders, nwHeadFmt, nwKeyWords, nwLabels, nwUnicode from novelwriter.core.project import NWProject from novelwriter.formats.tokenizer import T_Formats, Tokenizer from novelwriter.types import ( QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignLeft, QtAlignRight, - QtBlack, QtPageBreakAfter, QtPageBreakBefore, QtTransparent, - QtVAlignNormal, QtVAlignSub, QtVAlignSuper + QtPageBreakAfter, QtPageBreakBefore, QtTransparent, QtVAlignNormal, + QtVAlignSub, QtVAlignSuper ) logger = logging.getLogger(__name__) @@ -47,18 +48,18 @@ T_TextStyle = tuple[QTextBlockFormat, QTextCharFormat] class TextDocumentTheme: - text: QColor = QtBlack - highlight: QColor = QtTransparent - head: QColor = QtBlack - comment: QColor = QtBlack - note: QColor = QtBlack - code: QColor = QtBlack - modifier: QColor = QtBlack - keyword: QColor = QtBlack - tag: QColor = QtBlack - optional: QColor = QtBlack - dialog: QColor = QtBlack - altdialog: QColor = QtBlack + text: QColor = QColor(0, 0, 0) + highlight: QColor = QColor(255, 255, 166) + head: QColor = QColor(66, 113, 174) + comment: QColor = QColor(100, 100, 100) + note: QColor = QColor(129, 55, 9) + code: QColor = QColor(66, 113, 174) + modifier: QColor = QColor(129, 55, 9) + keyword: QColor = QColor(245, 135, 31) + tag: QColor = QColor(66, 113, 174) + optional: QColor = QColor(66, 113, 174) + dialog: QColor = QColor(66, 113, 174) + altdialog: QColor = QColor(129, 55, 9) def newBlock(cursor: QTextCursor, bFmt: QTextBlockFormat) -> None: @@ -91,11 +92,21 @@ class ToQTextDocument(Tokenizer): return - def initDocument(self, font: QFont, theme: TextDocumentTheme) -> None: - """Initialise all computed values of the document.""" - self._textFont = font - self._theme = theme + ## + # Setters + ## + def setTheme(self, theme: TextDocumentTheme) -> None: + """Set the document colour theme.""" + self._theme = theme + return + + ## + # Class Methods + ## + + def initDocument(self) -> None: + """Initialise all computed values of the document.""" self._document.setUndoRedoEnabled(False) self._document.blockSignals(True) self._document.clear() @@ -276,7 +287,12 @@ class ToQTextDocument(Tokenizer): return def saveDocument(self, path: Path) -> None: - """Not implemented.""" + """Save the document to a PDF file.""" + printer = QPrinter(QPrinter.PrinterMode.PrinterResolution) + printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) + printer.setPaperSize(QPrinter.PageSize.A4) + printer.setOutputFileName(str(path)) + self._document.print(printer) return def appendFootnotes(self) -> None: diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 665285fa..70acd21a 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -211,7 +211,9 @@ class GuiDocViewer(QTextBrowser): qDoc = ToQTextDocument(SHARED.project) qDoc.setJustify(CONFIG.doJustify) qDoc.setDialogueHighlight(True) - qDoc.initDocument(CONFIG.textFont, self._docTheme) + qDoc.setFont(CONFIG.textFont) + qDoc.setTheme(self._docTheme) + qDoc.initDocument() qDoc.setKeywords(True) qDoc.setComments(CONFIG.viewComments) qDoc.setSynopsis(CONFIG.viewSynopsis) diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 410a33fd..de25da81 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -45,7 +45,7 @@ from novelwriter.core.docbuild import NWBuildDocument from novelwriter.extensions.modified import NIconToggleButton, NIconToolButton, NToolDialog from novelwriter.extensions.progressbars import NProgressCircle from novelwriter.formats.tokenizer import HeadingFormatter -from novelwriter.formats.toqdoc import TextDocumentTheme, ToQTextDocument +from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON from novelwriter.tools.manusbuild import GuiManuscriptBuild from novelwriter.tools.manussettings import GuiBuildSettings @@ -330,22 +330,8 @@ class GuiManuscript(NToolDialog): docBuild = NWBuildDocument(SHARED.project, build) docBuild.queueAll() - theme = TextDocumentTheme() - theme.text = QColor(0, 0, 0) - theme.highlight = QColor(255, 255, 166) - theme.head = QColor(66, 113, 174) - theme.comment = QColor(100, 100, 100) - theme.note = QColor(129, 55, 9) - theme.code = QColor(66, 113, 174) - theme.modifier = QColor(129, 55, 9) - theme.keyword = QColor(245, 135, 31) - theme.tag = QColor(66, 113, 174) - theme.optional = QColor(66, 113, 174) - theme.dialog = QColor(66, 113, 174) - theme.altdialog = QColor(129, 55, 9) - self.docPreview.beginNewBuild(len(docBuild)) - for step, _ in docBuild.iterBuildPreview(theme): + for step, _ in docBuild.iterBuildPreview(): self.docPreview.buildStep(step + 1) QApplication.processEvents() diff --git a/tests/test_formats/test_fmt_toqdoc.py b/tests/test_formats/test_fmt_toqdoc.py index d6a65e7b..fc5170f4 100644 --- a/tests/test_formats/test_fmt_toqdoc.py +++ b/tests/test_formats/test_fmt_toqdoc.py @@ -22,7 +22,7 @@ from __future__ import annotations import pytest -from PyQt5.QtGui import QColor, QTextBlock, QTextCharFormat, QTextCursor +from PyQt5.QtGui import QTextBlock, QTextCharFormat, QTextCursor from novelwriter import CONFIG from novelwriter.constants import nwUnicode @@ -34,18 +34,6 @@ from novelwriter.types import ( ) THEME = TextDocumentTheme() -THEME.text = QColor(0, 0, 0) -THEME.highlight = QColor(255, 255, 166) -THEME.head = QColor(66, 113, 174) -THEME.comment = QColor(100, 100, 100) -THEME.note = QColor(129, 55, 9) -THEME.code = QColor(66, 113, 174) -THEME.modifier = QColor(129, 55, 9) -THEME.keyword = QColor(245, 135, 31) -THEME.tag = QColor(66, 113, 174) -THEME.optional = QColor(66, 113, 174) -THEME.dialog = QColor(113, 140, 0) -THEME.altdialog = QColor(234, 183, 0) def charFmtInBlock(block: QTextBlock, pos: int) -> QTextCharFormat: @@ -60,8 +48,8 @@ def testFmtToQTextDocument_ConvertHeaders(mockGUI): """Test header formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) - qdoc.initDocument(CONFIG.textFont, THEME) - qdoc.saveDocument("") # Doesn't do anything for this format + qdoc.initDocument() + # qdoc.saveDocument("") # Doesn't do anything for this format qdoc._isNovel = True qdoc._isFirst = True @@ -137,7 +125,7 @@ def testFmtToQTextDocument_SeparatorSkip(mockGUI): """Test separator and skip in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) - qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.initDocument() qdoc._isNovel = True qdoc._isFirst = True @@ -203,7 +191,7 @@ def testFmtToQTextDocument_NovelMeta(mockGUI): """Test novel meta formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) - qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.initDocument() qdoc._isNovel = True qdoc._isFirst = True @@ -280,7 +268,7 @@ def testFmtToQTextDocument_NoteMeta(mockGUI): """Test note meta formats in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) - qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.initDocument() qdoc._isNovel = False qdoc._isFirst = True @@ -338,7 +326,7 @@ def testFmtToQTextDocument_TextBlockFormats(mockGUI): project = NWProject() qdoc = ToQTextDocument(project) qdoc.setFirstLineIndent(True, 2.0, False) - qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.initDocument() qdoc._isNovel = True qdoc._isFirst = True @@ -460,7 +448,7 @@ def testFmtToQTextDocument_TextCharFormats(mockGUI): assert qdoc.document.toPlainText() == "" # Init - qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.initDocument() qdoc._isNovel = True qdoc._isFirst = True @@ -581,7 +569,7 @@ def testFmtToQTextDocument_Footnotes(mockGUI): """Test footnotes in the ToQTextDocument class.""" project = NWProject() qdoc = ToQTextDocument(project) - qdoc.initDocument(CONFIG.textFont, THEME) + qdoc.initDocument() qdoc._isNovel = True qdoc._isFirst = True