diff --git a/nw/assets/fonts/Cantarell/Cantarell-Bold.ttf b/nw/assets/fonts/Cantarell/Cantarell-Bold.ttf new file mode 100644 index 00000000..ca364663 Binary files /dev/null and b/nw/assets/fonts/Cantarell/Cantarell-Bold.ttf differ diff --git a/nw/assets/fonts/Cantarell/Cantarell-BoldItalic.ttf b/nw/assets/fonts/Cantarell/Cantarell-BoldItalic.ttf new file mode 100644 index 00000000..a54eab4c Binary files /dev/null and b/nw/assets/fonts/Cantarell/Cantarell-BoldItalic.ttf differ diff --git a/nw/assets/fonts/Cantarell/Cantarell-Italic.ttf b/nw/assets/fonts/Cantarell/Cantarell-Italic.ttf new file mode 100644 index 00000000..725b1986 Binary files /dev/null and b/nw/assets/fonts/Cantarell/Cantarell-Italic.ttf differ diff --git a/nw/assets/fonts/Cantarell/Cantarell-Regular.ttf b/nw/assets/fonts/Cantarell/Cantarell-Regular.ttf new file mode 100644 index 00000000..ceb92751 Binary files /dev/null and b/nw/assets/fonts/Cantarell/Cantarell-Regular.ttf differ diff --git a/nw/assets/fonts/Cantarell/OFL.txt b/nw/assets/fonts/Cantarell/OFL.txt new file mode 100644 index 00000000..4e495019 --- /dev/null +++ b/nw/assets/fonts/Cantarell/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2009-2010, Understanding Limited (dave@understandinglimited.com) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/nw/config.py b/nw/config.py index 07b58c7e..985eca43 100644 --- a/nw/config.py +++ b/nw/config.py @@ -86,7 +86,8 @@ class Config: self.guiIcons = "typicons_grey_light" self.guiDark = False self.guiLang = "en" # Hardcoded for now - self.guiFontSize = 12 + self.guiFont = "" + self.guiFontSize = 11 ## Sizes self.winGeometry = [1100, 650] @@ -317,6 +318,9 @@ class Config: self.guiDark = self._parseLine( cnfParse, cnfSec, "guidark", self.CNF_BOOL, self.guiDark ) + self.guiFont = self._parseLine( + cnfParse, cnfSec, "guifont", self.CNF_STR, self.guiFont + ) self.guiFontSize = self._parseLine( cnfParse, cnfSec, "guifontsize", self.CNF_INT, self.guiFontSize ) @@ -471,6 +475,7 @@ class Config: cnfParse.set(cnfSec,"syntax", str(self.guiSyntax)) cnfParse.set(cnfSec,"icons", str(self.guiIcons)) cnfParse.set(cnfSec,"guidark", str(self.guiDark)) + cnfParse.set(cnfSec,"guifont", str(self.guiFont)) cnfParse.set(cnfSec,"guifontsize", str(self.guiFontSize)) ## Sizes diff --git a/nw/gui/additions/qconfiglayout.py b/nw/gui/additions/qconfiglayout.py index be7571b5..70b3042d 100644 --- a/nw/gui/additions/qconfiglayout.py +++ b/nw/gui/additions/qconfiglayout.py @@ -107,7 +107,7 @@ class QConfigLayout(QGridLayout): return - def addRow(self, theLabel, theWidget, helpText=None, theUnit=None): + def addRow(self, theLabel, theWidget, helpText=None, theUnit=None, theButton=None): """Add a label and a widget as a new row of the grid. """ thisEntry = { @@ -152,6 +152,12 @@ class QConfigLayout(QGridLayout): controlBox.addWidget(QLabel(theUnit), 0, Qt.AlignVCenter) controlBox.setSpacing(8) self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter) + elif theButton is not None: + controlBox = QHBoxLayout() + controlBox.addWidget(qWidget, 0, Qt.AlignVCenter) + controlBox.addWidget(theButton, 0, Qt.AlignVCenter) + controlBox.setSpacing(8) + self.addLayout(controlBox, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter) else: self.addWidget(qWidget, self._nextRow, 1, 1, 1, Qt.AlignRight | Qt.AlignVCenter) diff --git a/nw/gui/build.py b/nw/gui/build.py index 0c539f35..d30ef7de 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -39,7 +39,7 @@ from PyQt5.QtGui import ( from PyQt5.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel, QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, - QFileDialog, QFontComboBox, QSpinBox + QFileDialog, QFontDialog, QSpinBox ) from nw.gui.additions import QSwitch @@ -170,19 +170,21 @@ class GuiBuildNovel(QDialog): self.textForm = QGridLayout(self) self.textGroup.setLayout(self.textForm) - self.textFont = QFontComboBox() - self.textFont.setFixedWidth(220) - self.textFont.setToolTip( - "The font is used for PDF and printing. Other formats have no font set." - ) - self.textFont.setCurrentFont( - QFont(self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)) + ## Font Family + self.textFont = QLineEdit() + self.textFont.setReadOnly(True) + self.textFont.setFixedWidth(182) + self.textFont.setText( + self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont) ) + self.fontButton = QPushButton("...") + self.fontButton.setMaximumWidth(30) + self.fontButton.clicked.connect(self._selectFont) self.textSize = QSpinBox(self) self.textSize.setFixedWidth(60) - self.textSize.setMinimum(5) - self.textSize.setMaximum(48) + self.textSize.setMinimum(6) + self.textSize.setMaximum(72) self.textSize.setSingleStep(1) self.textSize.setToolTip( "The size is used for PDF and printing. Other formats have no size set." @@ -201,13 +203,15 @@ class GuiBuildNovel(QDialog): self.textForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft) self.textForm.addWidget(self.textFont, 0, 1, 1, 1, Qt.AlignRight) + self.textForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight) self.textForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.textSize, 1, 1, 1, 1, Qt.AlignRight) + self.textForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight) self.textForm.addWidget(QLabel("Justify text"), 2, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.justifyText, 2, 1, 1, 1, Qt.AlignRight) + self.textForm.addWidget(self.justifyText, 2, 1, 1, 2, Qt.AlignRight) self.textForm.setColumnStretch(0, 1) self.textForm.setColumnStretch(1, 0) + self.textForm.setColumnStretch(2, 0) # Include Switches # ================ @@ -385,7 +389,7 @@ class GuiBuildNovel(QDialog): fmtScene = self.fmtScene.text().strip() fmtSection = self.fmtSection.text().strip() justifyText = self.justifyText.isChecked() - textFont = self.textFont.currentFont().family() + textFont = self.textFont.text() textSize = self.textSize.value() incSynopsis = self.includeSynopsis.isChecked() incComments = self.includeComments.isChecked() @@ -667,6 +671,18 @@ class GuiBuildNovel(QDialog): self.docView.qDocument.print(thePrinter) return + def _selectFont(self): + """Open the QFontDialog and set a font for the font style. + """ + currFont = QFont() + currFont.setFamily(self.textFont.text()) + currFont.setPointSize(self.textSize.value()) + theFont, theStatus = QFontDialog.getFont(currFont, self) + if theStatus: + self.textFont.setText(theFont.family()) + self.textSize.setValue(theFont.pointSize()) + return + def _doClose(self): """Close button was clicked. """ @@ -709,7 +725,7 @@ class GuiBuildNovel(QDialog): self.optState.setValue("GuiBuildNovel", "winWidth", self.width()) self.optState.setValue("GuiBuildNovel", "winHeight", self.height()) self.optState.setValue("GuiBuildNovel", "justifyText", self.justifyText.isChecked()) - self.optState.setValue("GuiBuildNovel", "textFont", self.textFont.currentFont().family()) + self.optState.setValue("GuiBuildNovel", "textFont", self.textFont.text()) self.optState.setValue("GuiBuildNovel", "textSize", self.textSize.value()) self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked()) self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked()) diff --git a/nw/gui/dialogs/preferences.py b/nw/gui/dialogs/preferences.py index e6e6ca4f..be45b396 100644 --- a/nw/gui/dialogs/preferences.py +++ b/nw/gui/dialogs/preferences.py @@ -33,9 +33,8 @@ from os import path from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont from PyQt5.QtWidgets import ( - QDialog, QWidget, QHBoxLayout, QVBoxLayout, QTabWidget, QComboBox, QSpinBox, - QPushButton, QFontComboBox, QLineEdit, QDialogButtonBox, QMessageBox, - QFileDialog + QDialog, QWidget, QComboBox, QSpinBox, QPushButton, QLineEdit, QMessageBox, + QDialogButtonBox, QFileDialog, QFontDialog ) from nw.gui.additions import QSwitch, QConfigLayout, PagedDialog @@ -183,6 +182,21 @@ class GuiConfigEditGeneralTab(QWidget): "This may improve the look of icons on dark themes." ) + ## Font Family + self.guiFont = QLineEdit() + self.guiFont.setReadOnly(True) + self.guiFont.setFixedWidth(162) + self.guiFont.setText(self.mainConf.guiFont) + self.fontButton = QPushButton("...") + self.fontButton.setMaximumWidth(30) + self.fontButton.clicked.connect(self._selectFont) + self.mainForm.addRow( + "Font family", + self.guiFont, + "Changing this requires restarting %s." % nw.__package__, + theButton = self.fontButton + ) + ## Font Size self.guiFontSize = QSpinBox(self) self.guiFontSize.setMinimum(8) @@ -193,7 +207,7 @@ class GuiConfigEditGeneralTab(QWidget): "Font size", self.guiFontSize, "Changing this requires restarting %s." % nw.__package__, - theUnit="pt" + theUnit = "pt" ) # GUI Settings @@ -279,6 +293,7 @@ class GuiConfigEditGeneralTab(QWidget): guiTheme = self.selectTheme.currentData() guiIcons = self.selectIcons.currentData() guiDark = self.preferDarkIcons.isChecked() + guiFont = self.guiFont.text() guiFontSize = self.guiFontSize.value() showFullPath = self.showFullPath.isChecked() autoSaveDoc = self.autoSaveDoc.value() @@ -290,11 +305,13 @@ class GuiConfigEditGeneralTab(QWidget): # Check if restart is needed needsRestart |= self.mainConf.guiTheme != guiTheme needsRestart |= self.mainConf.guiIcons != guiIcons + needsRestart |= self.mainConf.guiFont != guiFont needsRestart |= self.mainConf.guiFontSize != guiFontSize self.mainConf.guiTheme = guiTheme self.mainConf.guiIcons = guiIcons self.mainConf.guiDark = guiDark + self.mainConf.guiFont = guiFont self.mainConf.guiFontSize = guiFontSize self.mainConf.showFullPath = showFullPath self.mainConf.autoSaveDoc = autoSaveDoc @@ -337,6 +354,18 @@ class GuiConfigEditGeneralTab(QWidget): self.askBeforeBackup.setEnabled(theState) return + def _selectFont(self): + """Open the QFontDialog and set a font for the font style. + """ + currFont = QFont() + currFont.setFamily(self.mainConf.guiFont) + currFont.setPointSize(self.mainConf.guiFontSize) + theFont, theStatus = QFontDialog.getFont(currFont, self) + if theStatus: + self.guiFont.setText(theFont.family()) + self.guiFontSize.setValue(theFont.pointSize()) + return + # END Class GuiConfigEditGeneralTab class GuiConfigEditLayoutTab(QWidget): @@ -357,14 +386,20 @@ class GuiConfigEditLayoutTab(QWidget): # ========== self.mainForm.addGroupLabel("Text Style") + ## Font Family - self.textStyleFont = QFontComboBox() - self.textStyleFont.setMaximumWidth(200) - self.textStyleFont.setCurrentFont(QFont(self.mainConf.textFont)) + self.textStyleFont = QLineEdit() + self.textStyleFont.setReadOnly(True) + self.textStyleFont.setFixedWidth(162) + self.textStyleFont.setText(self.mainConf.textFont) + self.fontButton = QPushButton("...") + self.fontButton.setMaximumWidth(30) + self.fontButton.clicked.connect(self._selectFont) self.mainForm.addRow( "Font family", self.textStyleFont, - "Font for the document editor and viewer." + "Font for the document editor and viewer.", + theButton = self.fontButton ) ## Font Size @@ -376,7 +411,7 @@ class GuiConfigEditLayoutTab(QWidget): self.mainForm.addRow( "Font size", self.textStyleSize, - theUnit="pt" + theUnit = "pt" ) # Text Flow @@ -477,7 +512,7 @@ class GuiConfigEditLayoutTab(QWidget): validEntries = True needsRestart = False - textFont = self.textStyleFont.currentFont().family() + textFont = self.textStyleFont.text() textSize = self.textStyleSize.value() textWidth = self.textFlowMax.value() zenWidth = self.zenDocWidth.value() @@ -503,6 +538,22 @@ class GuiConfigEditLayoutTab(QWidget): return validEntries, needsRestart + ## + # Slots + ## + + def _selectFont(self): + """Open the QFontDialog and set a font for the font style. + """ + currFont = QFont() + currFont.setFamily(self.mainConf.textFont) + currFont.setPointSize(self.mainConf.textSize) + theFont, theStatus = QFontDialog.getFont(currFont, self) + if theStatus: + self.textStyleFont.setText(theFont.family()) + self.textStyleSize.setValue(theFont.pointSize()) + return + # END Class GuiConfigEditLayoutTab class GuiConfigEditEditingTab(QWidget): diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 5d74036f..2d4bff15 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -32,7 +32,9 @@ import nw from os import path, listdir from PyQt5.QtWidgets import qApp -from PyQt5.QtGui import QPalette, QColor, QIcon, QFontMetrics +from PyQt5.QtGui import ( + QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase +) from nw.constants import nwAlert from nw.gui.icons import GuiIcons @@ -48,7 +50,7 @@ class GuiTheme: self.theIcons = GuiIcons(self.theParent) self.guiPalette = QPalette() self.guiPath = "gui" - self.iconPath = "icons" + self.fontPath = "fonts" self.syntaxPath = "syntax" self.cssName = "style.qss" self.confName = "theme.conf" @@ -110,12 +112,10 @@ class GuiTheme: self.syntaxFile = None self.confFile = None self.cssFile = None + self.guiFontDB = QFontDatabase() - # Set Font Size and Theme - self.guiFont = qApp.font() - self.guiFont.setPointSizeF(self.mainConf.guiFontSize) - qApp.setFont(self.guiFont) - + self.loadFonts() + self.updateFont() self.updateTheme() self.theIcons.updateTheme() @@ -125,6 +125,7 @@ class GuiTheme: # Extract Other Info self.guiDPI = qApp.primaryScreen().physicalDotsPerInch() + self.guiFont = qApp.font() qMetric = QFontMetrics(self.guiFont) self.fontPointSize = self.guiFont.pointSizeF() @@ -144,10 +145,52 @@ class GuiTheme: # Actions ## + def loadFonts(self): + """Add the fonts in the assets fonts folder to the app. + """ + ttfList = [] + fontAssets = path.join(self.mainConf.assetPath, self.fontPath) + for fontFam in listdir(fontAssets): + fontDir = path.join(fontAssets, fontFam) + if path.isdir(fontDir): + if not fontFam in self.guiFontDB.families(): + for fontFile in listdir(fontDir): + ttfFile = path.join(fontDir, fontFile) + if path.isfile(ttfFile) and fontFile.endswith(".ttf"): + ttfList.append(ttfFile) + + for ttfFile in ttfList: + logger.verbose("Font asset: %s" % path.relpath(ttfFile)) + fontID = self.guiFontDB.addApplicationFont(ttfFile) + if fontID < 0: + logger.error("Failed to add font: %s" % path.relpath(ttfFile)) + + return + + def updateFont(self): + """Updated the GUI's font style from settings, + """ + theFont = QFont() + if self.mainConf.guiFont not in self.guiFontDB.families(): + if self.mainConf.osWindows: + # On Windows, default to Cantarell provided by novelWriter + theFont.setFamily("Cantarell") + theFont.setPointSize(11) + else: + theFont = self.guiFontDB.systemFont(QFontDatabase.GeneralFont) + self.mainConf.guiFont = theFont.family() + self.mainConf.guiFontSize = theFont.pointSize() + else: + theFont.setFamily(self.mainConf.guiFont) + theFont.setPointSize(self.mainConf.guiFontSize) + + qApp.setFont(theFont) + + return + def updateTheme(self): """Update the GUI theme from theme files. """ - self.guiTheme = self.mainConf.guiTheme self.guiSyntax = self.mainConf.guiSyntax self.themeRoot = self.mainConf.themeRoot diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index 5f4768e6..f86bddc0 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -4,7 +4,8 @@ theme = default syntax = default_light icons = typicons_grey_light guidark = False -guifontsize = 12 +guifont = +guifontsize = 11 [Sizes] geometry = 1100, 650