Rev 18988 |
Rev 21107 |
Go to most recent revision |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
For general Scribus (>=1.3.2) copyright and licensing information please refer
to the COPYING file provided with the program. Following this notice may exist
a copyright and/or license notice that predates the release of Scribus 1.3.2
for which a new license (GPL+exception) is in place.
*/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#undef NDEBUG
#include <cassert>
#include "../styles/charstyle.h"
#include "pageitem.h"
#include "prefsstructs.h"
#include "../styles/paragraphstyle.h"
#include "specialchars.h"
#include "storytext.h"
#include "textlayout.h"
TextLayout::TextLayout(StoryText* text, PageItem* frame)
{
m_story = text;
m_frame = frame;
m_firstInFrame = 0;
m_lastInFrame = -1;
m_validLayout = false;
m_magicX = 0.0;
m_lastMagicPos = -1;
}
uint TextLayout::lines() const
{
return m_lines.count();
}
const LineSpec& TextLayout::line(uint i) const
{
return m_lines[i];
}
const PathData& TextLayout::point(int pos) const
{
return m_path[pos];
}
PathData& TextLayout::point(int pos)
{
if (pos >= story()->length())
m_path.resize(story()->length());
return m_path[pos];
}
void TextLayout::appendLine(const LineSpec& ls)
{
assert( ls.firstItem >= 0 );
assert( ls.firstItem < story()->length() );
assert( ls.lastItem >= 0 && ls.firstItem - ls.lastItem < 1 );
assert( ls.lastItem < story()->length() );
m_lines.append(ls);
if (m_lastInFrame < m_firstInFrame) {
m_firstInFrame = ls.firstItem;
m_lastInFrame = ls.lastItem;
}
else {
m_firstInFrame = qMin(m_firstInFrame, ls.firstItem);
m_lastInFrame = qMax(m_lastInFrame, ls.lastItem);
}
}
// Remove the last line from the list. Used when we need to backtrack on the layouting.
void TextLayout::removeLastLine ()
{
if (m_lines.isEmpty()) return;
LineSpec last = m_lines.takeLast ();
if (m_lines.isEmpty()) {
clear();
return;
}
// fix lastFrameItem
if (m_lastInFrame != last.lastItem) return;
m_lastInFrame = m_lines.last().lastItem;
}
void TextLayout::clear()
{
m_lines.clear();
m_path.clear();
m_firstInFrame = 0;
m_lastInFrame = -1;
if (m_frame->asPathText() != NULL)
m_path.resize(story()->length());
}
void TextLayout::setStory(StoryText *story)
{
m_story = story;
clear();
}
int TextLayout::startOfLine(int pos) const
{
for (int i=0; i < m_lines.count(); ++i) {
const LineSpec & ls(m_lines.at(i));
if (ls.firstItem <= pos && pos <= ls.lastItem)
return ls.firstItem;
}
return 0;
}
int TextLayout::endOfLine(int pos) const
{
for (int i=0; i < m_lines.count(); ++i) {
const LineSpec & ls(m_lines.at(i));
if (ls.firstItem <= pos && pos <= ls.lastItem)
return story()->text(ls.lastItem) == SpecialChars::PARSEP ? ls.lastItem :
story()->text(ls.lastItem) == ' ' ? ls.lastItem : ls.lastItem + 1;
}
return story()->length();
}
int TextLayout::prevLine(int pos) const
{
for (int i=0; i < m_lines.count(); ++i)
{
// find line for pos
const LineSpec & ls(m_lines.at(i));
if (ls.firstItem <= pos && pos <= ls.lastItem)
{
if (i == 0)
return startOfLine(pos);
// find current xpos
qreal xpos = 0.0;
for (int j = ls.firstItem; j < pos; ++j)
xpos += story()->getGlyphs(j)->wide();
if (pos != m_lastMagicPos || xpos > m_magicX)
m_magicX = xpos;
const LineSpec & ls2(m_lines.at(i-1));
// find new cpos
xpos = 0.0;
for (int j = ls2.firstItem; j <= ls2.lastItem; ++j)
{
xpos += story()->getGlyphs(j)->wide();
if (xpos > m_magicX) {
m_lastMagicPos = j;
return j;
}
}
m_lastMagicPos = ls2.lastItem;
return ls2.lastItem;
}
}
return m_firstInFrame;
}
int TextLayout::nextLine(int pos) const
{
for (int i=0; i < m_lines.count(); ++i)
{
// find line for pos
const LineSpec & ls(m_lines.at(i));
if (ls.firstItem <= pos && pos <= ls.lastItem)
{
if (i+1 == m_lines.count())
return endOfLine(pos);
// find current xpos
qreal xpos = 0.0;
for (int j = ls.firstItem; j < pos; ++j)
xpos += story()->getGlyphs(j)->wide();
if (pos != m_lastMagicPos || xpos > m_magicX)
m_magicX = xpos;
const LineSpec & ls2(m_lines.at(i+1));
// find new cpos
xpos = 0.0;
for (int j = ls2.firstItem; j <= ls2.lastItem; ++j)
{
xpos += story()->getGlyphs(j)->wide();
if (xpos > m_magicX) {
m_lastMagicPos = j;
return j;
}
}
m_lastMagicPos = ls2.lastItem + 1;
return ls2.lastItem + 1;
}
}
return m_lastInFrame;
}
int TextLayout::startOfFrame() const
{
return m_firstInFrame;
}
int TextLayout::endOfFrame() const
{
return m_lastInFrame + 1;
}
int TextLayout::screenToPosition(FPoint coord) const
{
qreal maxx = coord.x() - 1.0;
for (unsigned int i=0; i < lines(); ++i)
{
LineSpec ls = line(i);
// qDebug() << QString("screenToPosition: (%1,%2) -> y %3 - %4 + %5").arg(coord.x()).arg(coord.y()).arg(ls.y).arg(ls.ascent).arg(ls.descent);
if (ls.y + ls.descent < coord.y())
continue;
qreal xpos = ls.x;
for (int j = ls.firstItem; j <= ls.lastItem; ++j)
{
// qDebug() << QString("screenToPosition: (%1,%2) -> x %3 + %4").arg(coord.x()).arg(coord.y()).arg(xpos).arg(item(j)->glyph.wide());
qreal width = story()->getGlyphs(j)->wide();
xpos += width;
if (xpos >= coord.x())
{
if (story()->hasObject(j))
return j;
else
return xpos - width/2 > coord.x() ? j : j+1;
}
}
if (xpos > maxx)
maxx = xpos;
if (xpos + 1.0 > coord.x()) // allow 1pt after end of line
return ls.lastItem + 1;
else if (coord.x() <= ls.x + ls.width) // last line of paragraph?
return ((ls.lastItem == m_lastInFrame) ? (ls.lastItem + 1) : ls.lastItem);
else if (xpos < ls.x + 0.01 && maxx >= coord.x()) // check for empty line
return ls.firstItem;
}
return qMax(m_lastInFrame+1, m_firstInFrame);
}
FRect TextLayout::boundingBox(int pos, uint len) const
{
FRect result;
LineSpec ls;
for (uint i=0; i < lines(); ++i)
{
ls = line(i);
if (ls.lastItem < pos)
continue;
if (ls.firstItem <= pos) {
/*
//if (ls.lastItem == pos && (item(pos)->effects() & ScLayout_SuppressSpace) )
{
if (i+1 < lines())
{
ls = line(i+1);
result.setRect(ls.x, ls.y - ls.ascent, 1, ls.ascent + ls.descent);
}
else
{
ls = line(lines()-1);
const ParagraphStyle& pstyle(paragraphStyle(pos));
result.setRect(ls.x, ls.y + pstyle.lineSpacing() - ls.ascent, 1, ls.ascent + ls.descent);
}
}
else */
{
qreal xpos = ls.x;
for (int j = ls.firstItem; j < pos; ++j)
{
if (story()->hasObject(j))
xpos += (story()->object(j)->width() + story()->object(j)->lineWidth()) * story()->getGlyphs(j)->scaleH;
else
xpos += story()->getGlyphs(j)->wide();
}
qreal finalw = 1;
if (story()->hasObject(pos))
finalw = (story()->object(pos)->width() + story()->object(pos)->lineWidth()) * story()->getGlyphs(pos)->scaleH;
else
finalw = story()->getGlyphs(pos)->wide();
const CharStyle& cs(story()->charStyle(pos));
qreal desc = -cs.font().descent(cs.fontSize() / 10.0);
qreal asce = cs.font().ascent(cs.fontSize() / 10.0);
result.setRect(xpos, ls.y - asce, pos < story()->length()? finalw : 1, desc+asce);
}
return result;
}
}
const ParagraphStyle& pstyle(story()->paragraphStyle(qMin(pos, story()->length()))); // rather the trailing style than a segfault.
if (lines() > 0)
{
ls = line(lines()-1);
result.setRect(ls.x, ls.y + pstyle.lineSpacing() - ls.ascent, 1, ls.ascent + ls.descent);
}
else
{
result.setRect(1, 1, 1, pstyle.lineSpacing());
}
return result;
}