/*************************************************************************** * 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 #include #include #include #include #include #include "renderer.h" Renderer::Renderer(): m_pageCount(0), m_linesPerPage(0), m_paraOffset(50), m_fontMetrics(new TQFontMetrics(m_font)), m_curParagraph(0), m_isRendering(false) { connect(&m_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(timeout())); } Renderer::~Renderer() { } /** * If the list is empty do nothing. * \param a_list List of strings to render */ void Renderer::load(const TQStringList & a_list) { if (a_list.isEmpty()) return; m_text.clear(); TQString 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()) { TQChar 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() / (TQFontMetrics(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 TQString a_string(m_text[paraIndex]); if (a_string.isEmpty()) return; TQString 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 TQString & 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 TQString & string, int len) { while (--len >= 0) if (string[len] == ' ') return ++len; return 0; } TQString Renderer::getWord(const TQString & a_string, int a_idx) { int idx = a_idx; while (a_string[++idx] != ' ' && idx < a_string.length()); return TQString(a_string.mid(a_idx, idx - a_idx)); } /** * Draws page number \c pageNumber on \c paint * bounding \c rect rectangle */ void Renderer::drawPage(TQPainter & paint, TQRect 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(TQPainter & paint, int x, int y, const TLines::size_type index) { const TextLine textLine(m_lines[index]); const int length = textLine.size(); const TQString & paragraph = m_text[textLine.paragraphIndex()]; const TQString 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 spaces; spaces.reserve(50); while (((pos = string.find(' ', off)) != -1) && (pos < length)) { spaces.push_back(pos); off = pos + 1; } const std::vector::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; TQString tmp; for (std::vector::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 TQFont & font) { if (font == m_font) return; m_font = font; m_fontMetrics.reset(new TQFontMetrics(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 TQSize & size) { m_pageSize = size; } #include "renderer.moc"