Текст в QLabel в PyQt5 выходит за границы

Я написал вот такой ридер для fb2-книг, смотрите ниже.

Когда я вставляю текст из списка pages[] в textLabel,
текст "выезжает" за ширину textLabel.

введите сюда описание изображения


setWordWrap помогло, но текст стал нечитабельным.

введите сюда описание изображения

Как это исправить без setWordWrap?


main.py:

import sys
from config import *
from PyQt5.Qt import *
from src.Book import *
from src.styles.WhiteTheme import *


class BookViewer(object):
    def __init__(self, appStyle: style = WhiteTheme, appStyleFromSystem="Windows",
                 app=QApplication(sys.argv)) -> None:

        self.app = app
        self.Book = None

        # creating and configuring BookViewer window
        self.content = QWidget()
        self.content.setFixedSize(500, 500)
        self.content.setWindowIcon(QIcon("./media/karfagen.png"))
        self.content.setWindowTitle(f"Karfagen Book Viewer")
        self.content.setStyleSheet(appStyle.style)
        self.text_font = QFont(FONT_NAME, TEXT_SIZE)
        self.text_height = QFontMetrics(self.text_font)

        self.layout = QVBoxLayout(self.content)

        self.pageNumber = 0

        self.textLabel = QLabel()
        self.textLabel.setFixedWidth(400)
        self.textLabel.setFixedHeight(400)

        self.navigationBox = QGroupBox()
        self.navigationBox.setStyleSheet("""
        QGroupBox {
            border: 0px black solid;
        }
        """)

        #creating navigation panel
        self.navigationTopPanel = QWidget()
        self.navigationTopPanelLayout = QHBoxLayout()
        self.navigationTopPanel.setLayout(self.navigationTopPanelLayout)

        self.openFile = QPushButton(text = "open file")
        self.openFile.clicked.connect(self.openFileFunc)
        self.openFile.setFixedWidth(70)

        self.layout.addWidget(self.navigationTopPanel)

        self.navigationTopPanelLayout.addWidget(self.openFile)
        self.navigationTopPanelLayout.setAlignment(Qt.AlignLeft)

        #creating elements for navigation in Book
        self.navigationBoxLayout = QHBoxLayout()

        self.btn_prev = QPushButton(text="<")
        self.btn_prev.clicked.connect(self.prev_page)

        self.pageNumberLabel = QLabel(text=str(self.pageNumber))

        self.btn_next = QPushButton(text=">")
        self.btn_next.clicked.connect(self.next_page)

        self.navigationBoxLayout.addWidget(self.btn_prev)
        self.navigationBoxLayout.setAlignment(Qt.AlignCenter)
        self.navigationBoxLayout.addWidget(self.pageNumberLabel)
        self.navigationBoxLayout.addWidget(self.btn_next)

        self.navigationBox.setLayout(self.navigationBoxLayout)

        self.layout.addWidget(self.textLabel)
        self.layout.addStretch()
        self.layout.addWidget(self.navigationBox)
        self.content.setLayout(self.layout)

    def start(self):
        self.content.show()
        self.app.exec_()

    def render_page(self, pageNumber):
        try:
            self.pageNumberLabel.setText(str(pageNumber + 1))
            self.textLabel.setText("".join(self.pages[self.pageNumber]))
        except Exception as e:
            self.show_messagebox(str(e))

    def prev_page(self):
        if self.pageNumber > 0 and self.Book:
            self.pageNumber -= 1
            self.render_page(self.pageNumber)

    def next_page(self):
        if self.pageNumber <= len(self.pages) and self.Book:
            self.pageNumber += 1
            print(self.pageNumber)
            self.render_page(self.pageNumber)

    def parseBookData(self):
        """
        Parses raw book data into pages, handling paragraph wrapping and page breaks.

        Returns:
            A list of pages, where each page is a list of strings (paragraphs/lines).
        """

        pages= []
        page = []
        current_text_height = 0
        font_metrics = QFontMetrics(QFont(FONT_NAME, TEXT_SIZE))

        for paragraph in self.Book.text_data:
            # Split paragraph into lines that fit
            lines = self.split_paragraph_into_lines(paragraph, font_metrics, self.textLabel.width())
            for line in lines:
                line_height = font_metrics.height()  # Use actual line height

                if current_text_height + line_height <= self.textLabel.height():
                    page.append(line + "<br>")
                    current_text_height += line_height
                else:
                    pages.append(page)
                    page = [line]
                    current_text_height = line_height  # Reset height to the current line's height

        # Add the last page if it's not empty
        if page:
            pages.append(page)

        return pages

    def split_paragraph_into_lines(self, paragraph: str, font_metrics: QFontMetrics, max_width: int):
        """
        Splits a paragraph into lines that fit within the maximum width, handling word wrapping.

        Args:
            paragraph: The paragraph to split.
            font_metrics: QFontMetrics object.
            max_width: The maximum width for a line.

        Returns:
            A list of strings, where each string is a line.
        """

        if font_metrics.horizontalAdvance(paragraph) >= self.textLabel.width():

            words = paragraph.split()
            lines = []
            current_line = ""

            for word in words:
                test_line = current_line + word + " "  # Add word and a space to test
                if font_metrics.horizontalAdvance(test_line) <= max_width:
                    current_line = test_line
                else:
                    if current_line:  # Add the current line if it's not empty
                        newString = ""
                        for i in range(len(current_line)):
                            newString += current_line[i]
                            if font_metrics.horizontalAdvance(newString) == max_width:
                                break
                    lines.append(newString)
                    current_line = word + " " + current_line[i:len(current_line)]  # Start a new line with the current word

            if current_line:  # Add the last line
                lines.append(current_line.strip())
            return lines

        else:
            return [paragraph]

    def openFileFunc(self):
        options = QFileDialog.Options()

        # Get the file names from the dialog
        files, _ = QFileDialog.getOpenFileNames(self.content,
                                                 "Select Fiction Book Files",
                                                 "",
                                                 "Fiction Book 2 (*.fb2);;All Files (*)",
                                                 options=options)
        if files:
            self.Book = Book(files[0])
            self.Book.parse()

            self.content.setWindowTitle(self.Book.title + " " + self.Book.author + " " + "Karfagen Book Viewer")
            self.pages = self.parseBookData()
            self.render_page(self.pageNumber)

    def show_messagebox(self, text):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Icon.Error)
        msg.setText(text)
        msg.setInformativeText("")
        msg.setWindowTitle("error")
        msg.setDetailedText("")
        msg.exec()

Book.py:

from xml.dom.minidom import parse

class Book(object):

    def __init__(self, filename, encoding="UTF-8"):
        self.filename = filename
        self.encoding = encoding

        self.text_data = None
        self.document = None

        self.genre = None
        self.author = None
        self.title = None
        self.lang = None

    def parse(self):
        document = parse(self.filename)

        self.document = document
        self.genre = self.loadTagValueFromXML("genre")
        self.lang = self.loadTagValueFromXML("lang")
        self.author = self.loadTagValueFromXML("last-name") + self.loadTagValueFromXML("first-name")
        self.title = self.loadTagValueFromXML("book-title")

        paragraphs = document.getElementsByTagName("section")
        for paragraph in paragraphs:
            text_nodes = [
                node.childNodes[0].nodeValue for node in paragraph.childNodes
                if node.nodeName == 'p' and node.childNodes[0].nodeValue
            ]
        self.text_data = text_nodes
        self.parsedTextData = []

    def loadTagValueFromXML(self, tag_name):
        try:
            tag = self.document.getElementsByTagName(tag_name)[0].childNodes[0].nodeValue
            return tag
        except IndexError:
            return ""

Ответы (1 шт):

Автор решения: S. Nick

Я попробовал сделать некоторые изменения для вас.

Заменил:

    self.textLabel = QLabel()

на:

    self.textLabel = QTextBrowser()

Добавил:

# +++ # +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv           
            paragraph = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + \
                paragraph + "<br>"                              # +++
# +++ # +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Убрал + "<br>" из строки

    page.append(line + "<br>")

и еще кое-что по мелочи.

Попробуйте что получилось.
Вам есть еще над чем поработать.


bookviewer.py

import sys
from src.Book import *
from src.configParser import *
from PyQt5.Qt import *


class BookViewer(object):
    def __init__(self, 
        app = QApplication(sys.argv), 
        appConfig = configParser()) -> None:
# +
        app.setFont(QFont("Times", 14, QFont.Bold))             # +
        
        self.app = app
        self.Book = None
        self.pages = []
        self.pageNumber = 0
        self.appConfig = appConfig
        self.appStyle = self.appConfig.APP_THEME

        # creating and configuring BookViewer window
        self.content = QWidget()
#?        self.content.setFixedSize(700, 500)
        self.content.resize(700, 500)                            # +
        self.content.setWindowIcon(QIcon("./media/karfagen.png"))
        self.content.setWindowTitle(f"Karfagen Book Viewer")
        self.content.setStyleSheet(self.appStyle)
        self.text_font = QFont(self.appConfig.FONT_NAME, 
            self.appConfig.TEXT_SIZE)
        self.text_height = QFontMetrics(self.text_font)

#?       self.textLabel = QLabel()
        self.textLabel = QTextBrowser()                         # +++
#        self.textLabel.setWordWrap(True)
#?       self.textLabel.setFixedWidth(400)
#?       self.textLabel.setFixedHeight(400)

        self.navigationBox = QGroupBox()
        self.navigationBox.setStyleSheet("""
        QGroupBox {
            border: 2px black solid;
        }
        """)

        #creating navigation panel
        self.navigationTopPanel = QWidget()

        self.openFile = QPushButton(text = "open file")
        self.openFile.clicked.connect(self.openFileFunc)
#?        self.openFile.setFixedWidth(70)

        self.navigationTopPanelLayout = QHBoxLayout(
            self.navigationTopPanel)
        self.navigationTopPanelLayout.addWidget(self.openFile)
        self.navigationTopPanelLayout.setAlignment(Qt.AlignLeft)

        #creating elements for navigation in Book
        self.btn_prev = QPushButton(text="<")
        self.btn_prev.clicked.connect(self.prev_page)

        self.pageNumberLabel = QLabel(text=str(self.pageNumber))

        self.btn_next = QPushButton(text=">")
        self.btn_next.clicked.connect(self.next_page)

        self.goToPageField = QLineEdit()
        self.goToPageField.setValidator(QIntValidator())
        self.goToPageField.setPlaceholderText(
            "Enter number of page to go")
        self.goToPageField.setStyleSheet("""
            QLineEdit {
                border: 0px black solid;
                background-color: #bbbbbb;
                color: #000000;
            }
        """)
        self.goToPageField.editingFinished.connect(self.pageByNumber)

        self.navigationBoxLayout = QHBoxLayout()
        self.navigationBoxLayout.addWidget(self.btn_prev)
        self.navigationBoxLayout.setAlignment(Qt.AlignCenter)
        self.navigationBoxLayout.addWidget(self.pageNumberLabel)
        self.navigationBoxLayout.addWidget(self.btn_next)
        self.navigationBoxLayout.addWidget(self.goToPageField)
        self.navigationBox.setLayout(self.navigationBoxLayout)

        self.layout = QVBoxLayout(self.content)
        self.layout.addWidget(self.navigationTopPanel)        
        self.layout.addWidget(self.textLabel)
#?        self.layout.addStretch()
        self.layout.addWidget(self.navigationBox)
#?        self.content.setLayout(self.layout)

    def start(self):
        self.content.show()
        self.app.exec_()

    def render_page(self, pageNumber):
        try:
            self.pageNumberLabel.setText(
                f"{str(pageNumber + 1)}/{len(self.pages)}")
            self.textLabel.setText("".join(self.pages[pageNumber]))
        except Exception as e:
            self.show_messagebox(str(e))

    def pageByNumber(self):
        pageNumber = int(self.goToPageField.text())
        if pageNumber > 0 and pageNumber <= len(self.pages):
            self.pageNumber = pageNumber
            self.render_page(pageNumber - 1)
 
    def prev_page(self):
        if self.pageNumber > 0 and self.Book:
            self.pageNumber -= 1
            self.render_page(self.pageNumber)

    def next_page(self):
        if self.pageNumber <= len(self.pages) and self.Book:
            self.pageNumber += 1
            self.render_page(self.pageNumber)

    def parseBookData(self):
        """
        Parses raw book data into pages, handling paragraph 
        wrapping and page breaks.

        Returns:
            A list of pages, where each page is a list of 
            strings (paragraphs/lines).
        """

        pages= []
        page = []
        current_text_height = 0
#        font_metrics = QFontMetrics(
#            QFont(self.appConfig.FONT_NAME, self.appConfig.TEXT_SIZE))

        fm = QFontMetrics(self.textLabel.font())                # +         

        for paragraph in self.Book.text_data:
        
# +++ # +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv           
            paragraph = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + \
                paragraph + "<br>"                              # +++
# +++ # +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            # Разделите абзац на строки, которые подходят
            lines = self.split_paragraph_into_lines(
                paragraph, fm, self.textLabel.width())
    
            line_height = fm.height()                           # +    
            for line in lines:
                if current_text_height + line_height <= \
                    self.textLabel.height() - 30:
#                                           ^^^^  чтобы убрать прокрутку

#?                    page.append(line + "<br>") # заменил, смотри выше
                    page.append(line)
                    current_text_height += line_height
                else:
                    pages.append(page)
                    page = [line]
                    current_text_height = line_height  # Reset height to the current line's height
# +
            QApplication.processEvents()                        # +
            
        # Добавить последнюю страницу, если она не пустая
        if page:
            pages.append(page)

        return pages

    def split_paragraph_into_lines(self, 
        paragraph: str, 
        font_metrics: QFontMetrics, 
        max_width: int):
        """
        Splits a paragraph into lines that fit within the maximum width, handling word wrapping.

        Args:
            paragraph: The paragraph to split.
            font_metrics: QFontMetrics object.
            max_width: The maximum width for a line.

        Returns:
            A list of strings, where each string is a line.
        """

        if font_metrics.horizontalAdvance(paragraph) >= self.textLabel.width():
            words = paragraph.split()
            lines = []
            current_line = ""
            for word in words:
                test_line = current_line + word + " "  # Add word and a space to test
                if font_metrics.horizontalAdvance(test_line) <= max_width:
                    current_line = test_line
                else:
                    if current_line:  # Add the current line if it's not empty
                        newString = ""
                        for i in range(len(current_line)):
                            newString += current_line[i]
                            if font_metrics.horizontalAdvance(newString) == max_width:
                                break
                  
                    lines.append(newString)

                    current_line = word + " " + \
                        current_line[i:len(current_line)] # Start a new line with the current word
            if current_line:  # Add the last line
                lines.append(current_line.strip())
            return lines
        else:
            return [paragraph]

    def openFileFunc(self):
        options = QFileDialog.Options()

        # Get the file names from the dialog
        files, _ = QFileDialog.getOpenFileNames(
            self.content,
            "Select Fiction Book Files",
            "",
            "Fiction Book 2 (*.fb2);;All Files (*)",
            options=options)
        if files:
            self.Book = Book(files[0])
            self.Book.parse()

            self.content.setWindowTitle(
                self.Book.title + " " + self.Book.author + " " + "Karfagen Book Viewer")

            self.pages = self.parseBookData()
            self.render_page(self.pageNumber)

    def show_messagebox(self, text):
        msg = QMessageBox()
#           msg.setIcon(QMessageBox.Icon.Error)
        msg.setText(text)
        msg.setInformativeText("")
        msg.setWindowTitle("error")
        msg.setDetailedText("")
        msg.exec()

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

введите сюда описание изображения

→ Ссылка