You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
10 KiB
399 lines
10 KiB
/***************************************************************************
|
|
* 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 <tqfontmetrics.h>
|
|
#include <tqpainter.h>
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#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, TQT_SIGNAL(timeout()), this, TQT_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<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;
|
|
TQString 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 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"
|