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.
kbookreader/src/renderer.cpp

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, 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<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"