diff options
Diffstat (limited to 'src/renderer.cpp')
-rw-r--r-- | src/renderer.cpp | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..a04b0d0 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,398 @@ +/*************************************************************************** +* Copyright (C) 2005 - 2007 by Alexander Nemish * +* atlanter@gmail.com * +* * +* 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 2 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, write to the * +* Free Software Foundation, Inc., * +* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include <kdebug.h> +#include <qfontmetrics.h> +#include <qpainter.h> +#include <vector> +#include <cmath> +#include <iostream> +#include "renderer.h" + +Renderer::Renderer(): + m_pageCount(0), + m_linesPerPage(0), + m_paraOffset(50), + m_fontMetrics(new QFontMetrics(m_font)), + m_curParagraph(0), + m_isRendering(false) +{ + connect(&m_timer, SIGNAL(timeout()), this, SLOT(timeout())); +} + + +Renderer::~Renderer() +{ +} + +/** +* If the list is empty do nothing. +* \param a_list List of strings to render +*/ +void Renderer::load(const QStringList & a_list) +{ + if (a_list.isEmpty()) + return; + m_text.clear(); + + QString string; + TStringIter it(a_list.constBegin()); + + while (it != a_list.constEnd()) + { + //skip spaces; + string = (*it).stripWhiteSpace(); + //process string until paragraph ends + while (++it != a_list.constEnd()) + { + QChar const ch((*it)[0]); + //insert empty lines + if ((*it).isEmpty()) + break; + //check whether paragraph not ends + if (!ch.isSpace() && (ch != '-')) + { + string += " " + *it; + } + else break; + } + // add paragraph + m_text.push_back(string); + //add empty line if we have one + if ((*it).isEmpty()) + m_text.push_back(""); + } + render(); +} + +/** +* \note You need manually update your widget. Like +* \code +* renderer->render(); +* update(); +* \endcode +*/ +void Renderer::render() +{ + if (isEmpty()) + return; + if (busy()) + cancel(); + clear(); + m_isRendering = true; + m_linesPerPage = m_pageSize.height() / (QFontMetrics(font()).height()); + m_timer.start(0, false); +} + +void Renderer::timeout() +{ + if (m_curParagraph < m_text.size()) + { + parseParagraph(m_curParagraph); + m_curParagraph++; + } + else + { + cancel(); + emit renderingFinished(); + } + m_pageCount = m_linesPerPage ? (m_lines.size() / m_linesPerPage) + 1 : 0; +} + +void Renderer::cancel() +{ + m_timer.stop(); + m_curParagraph = 0; + m_isRendering = false; +} + +/** + * Call it for simple view clearing. +* \code +* renderer->clear(); +* update(); +* \endcode +*/ +void Renderer::clear() +{ + m_lines.clear(); + m_pageCount = 0; + m_linesPerPage = 0; +} + +/** +* \param a_string Line to parse +*/ +void Renderer::parseParagraph(TIndex paraIndex) +{ + //Don't parse empty lines + const QString a_string(m_text[paraIndex]); + if (a_string.isEmpty()) + return; + + QString string(a_string); + const int avgCharWidth = m_fontMetrics->width(" "); + //calc approx string width + unsigned int avgLen = m_pageSize.width() / avgCharWidth; + unsigned int len; + int begin = 0; + m_isStartAdded = false; + int curWidth = width(string); + + // whole paragraph in single line + if (curWidth <= m_pageSize.width()) + { + addLine(TextLine(paraIndex, 0, a_string.length(), TextLine::PARA_BOTH)); + return; + } + + for (; curWidth > m_pageSize.width(); curWidth = width(string)) + { + len = avgLen; + //turn left if missed + int w = width(string.left(len)); + for (; (w > m_pageSize.width()) && (len > 0); w = width(string.left(len))) + { + int diff = w - m_pageSize.width(); + int charDiff = diff / 10; + if (charDiff > len) + charDiff = charDiff % len; + charDiff = (charDiff == 0) ? 1 : charDiff; + len -= charDiff; + } + //turn right if missed + while ((width(string.left(len)) <= m_pageSize.width()) + && (len < string.length())) + ++len; + --len; + + // update statistics on average length + avgLen = (unsigned int) (len / (1 + 1. / avgLen)); + + //check whether we in a word + if (!(string[len - 1].isSpace() || string[len].isSpace())) + { + //find last word start index + const int wordBegin = wordAt(string, len); + //check whether its width less than page width + if (width(getWord(string, wordBegin)) <= m_pageSize.width()) + { + //if so, move last word to the next line + // invariant: wordBegin > 0, + // because otherwise the word is greater than page width + + // i points to a space before the word + int i = wordBegin -1; + // skip spaces + while (i && string[i].isSpace()) + --i; + addLine(TextLine(paraIndex, begin, begin + i + 1)); + string = string.right(string.length() - wordBegin); + begin += wordBegin; + } + else + { + //if its width greater than page width - split it + addLine(TextLine(paraIndex, begin, begin + len)); + string = string.right(string.length() - len); + begin += len; + } + } + // line ends with spaces and next line begins with spaces, + // trim them + else if (string[len - 1].isSpace() && string[len].isSpace()) + { + int idx = len - 1; + while (string[--idx] == ' '); + addLine(TextLine(paraIndex, begin, begin + idx + 1)); + idx = len; + while (string[++idx] == ' '); + string = string.right(string.length() - idx); + begin += idx; + } + else if (string[len - 1].isSpace()) + { + int idx = len - 1; + while (string[--idx] == ' '); + addLine(TextLine(paraIndex, begin, begin + idx + 1)); + string = string.right(string.length() - len); + begin += len; + } + else if (string[len].isSpace()) + { + int idx = len; + while (string[++idx] == ' '); + addLine(TextLine(paraIndex, begin, begin + len)); + string = string.right(string.length() - idx); + begin += idx; + } + } + //last line in multiline para. + len = string.length(); + addLine(TextLine(paraIndex, begin, begin + len, TextLine::PARA_END)); +} + +int Renderer::width(const QString & a_string) const +{ + int w = m_fontMetrics->width(a_string); + return m_isStartAdded ? w : w + paraOffset(); +} + +void Renderer::addLine(TextLine line) +{ + if (!m_isStartAdded) + line.setParagraphFlags(line.paragraphFlags() | TextLine::PARA_START); + m_lines.push_back(line); + m_isStartAdded = true; +} + +int Renderer::wordAt(const QString & string, int len) +{ + while (--len >= 0) + if (string[len] == ' ') + return ++len; + return 0; +} + +QString Renderer::getWord(const QString & a_string, int a_idx) +{ + int idx = a_idx; + while (a_string[++idx] != ' ' && idx < a_string.length()); + return QString(a_string.mid(a_idx, idx - a_idx)); +} + +/** +* Draws page number \c pageNumber on \c paint +* bounding \c rect rectangle +*/ +void Renderer::drawPage(QPainter & paint, QRect rect, int pageNumber) +{ + int height = m_fontMetrics->height(); + int line = 1; + TLines::size_type count = m_lines.size(); + + if (count == 0) + return; + + if ((pageNumber * m_linesPerPage) >= count) + return; + + TLines::size_type idx = pageNumber * m_linesPerPage; + for (; (line <= m_linesPerPage) && idx < m_lines.size(); ++idx, ++line) + drawLine(paint, rect.left(), rect.top() + line * height, idx); +} + +void Renderer::drawLine(QPainter & paint, int x, int y, const TLines::size_type index) +{ + const TextLine textLine(m_lines[index]); + const int length = textLine.size(); + const QString & paragraph = m_text[textLine.paragraphIndex()]; + const QString string = paragraph.mid(textLine.begin(), textLine.size()); + + // indent paragraph + if (textLine.isParaStart()) + x += paraOffset(); + + // don't justify on paragraph end + if (textLine.isParaEnd()) + { + paint.drawText(x, y, string); + return; + } + + const int pageWidth = m_pageSize.width(); + const int spaceWidth = m_fontMetrics->width(" "); + int width = m_fontMetrics->width(string); + const int curWidth = textLine.isParaStart() ? width + paraOffset() : width; + + if (curWidth == pageWidth) + { + paint.drawText(x, y, string); + } + + // string width is lower than page width. justify by space width + long pos = 0, off = 0; + + //count spaces + std::vector<int> spaces; + spaces.reserve(50); + while (((pos = string.find(' ', off)) != -1) && (pos < length)) + { + spaces.push_back(pos); + off = pos + 1; + } + const std::vector<int>::size_type spacesCount = spaces.size(); + // no spaces no justifications + if (!spacesCount) + { + paint.drawText(x, y, string); + return; + } + // justify line + double x1 = x; + // calc average space width + const double avgLen = ((double)(pageWidth - curWidth) / spacesCount); + int start = 0; + QString tmp; + for (std::vector<int>::size_type i = 0; i < spacesCount; ++i) + { + pos = spaces[i]; + tmp = string.mid(start, pos - start); + paint.drawText((int)std::ceil(x1), y, tmp); + x1 += m_fontMetrics->width(tmp); + x1 += spaceWidth + avgLen; + start = pos + 1; + } + //last chunk + paint.drawText((int)std::ceil(x1), y, string.right(length - start)); +} + +/** + * Sets current font to \c font and re-renders text. + * You don't need to directly call render(). + */ +void Renderer::setFont(const QFont & font) +{ + if (font == m_font) return; + m_font = font; + m_fontMetrics.reset(new QFontMetrics(m_font)); + render(); +} + +/** +* Sets current paragraph offset to \c offset +* and re-renders text if it's changed. +*/ +void Renderer::setParaOffset(int offset) +{ + if (offset == m_paraOffset) return; + m_paraOffset = offset; + render(); +} + +/** + * Sets current page size to \c size. + * \note It don't call render() after changing. + */ +void Renderer::setPageSize(const QSize & size) +{ + m_pageSize = size; +} + +#include "renderer.moc" |