summaryrefslogtreecommitdiffstats
path: root/src/renderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderer.cpp')
-rw-r--r--src/renderer.cpp398
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"