Rev 21218 |
Rev 21301 |
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.
*/
#include "pageitem.h"
#include "boxes.h"
#include "appmodes.h"
#include "qapplication.h"
#include "pageitem_textframe.h"
#include "scribusdoc.h"
#include "prefsmanager.h"
#include "sccolorengine.h"
#include "colorblind.h"
#include "textlayoutpainter.h"
int GroupBox::pointToPosition(QPointF coord) const
{
QPointF rel = coord - QPointF(m_x, m_y);
foreach (const Box *box, boxes())
{
if (box->containsPoint(rel))
{
int result = box->pointToPosition(rel);
if (result >= 0)
return result;
}
}
return -1;
}
QLineF GroupBox::positionToPoint(int pos) const
{
QLineF result;
foreach (const Box *box, boxes())
{
if (box->containsPos(pos))
{
result = box->positionToPoint(pos);
}
}
if (!result.isNull())
result.translate(x(), y());
return result;
}
void GroupBox::render(TextLayoutPainter *p) const
{
p->save();
p->translate(x(), y());
foreach (const Box *box, boxes())
{
box->render(p);
}
p->restore();
}
void GroupBox::render(TextLayoutPainter *p, PageItem *item) const
{
p->save();
p->translate(x(), y());
foreach (const Box *box, boxes())
{
box->render(p, item);
}
p->restore();
}
void GroupBox::addBox(const Box* box)
{
boxes().append(const_cast<Box*>(box));
QObject::connect(box, SIGNAL(boxChanged()), this, SLOT(childChanged()));
update();
}
void GroupBox::removeBox(int i)
{
if (i < 0 || i > boxes().size())
return;
boxes().removeAt(i);
update();
}
void GroupBox::update()
{
m_naturalHeight = m_naturalWidth = 0;
foreach (Box* box, boxes()) {
m_firstChar = qMin(m_firstChar, box->firstChar());
m_lastChar = qMax(m_lastChar, box->lastChar());
if (m_direction == D_Horizontal)
{
m_naturalWidth += box->width();
m_naturalHeight = qMax(m_naturalHeight, box->naturalHeight());
}
else
{
box->moveTo(box->x(), naturalHeight());
m_naturalWidth = qMax(m_naturalWidth, box->naturalWidth());
m_naturalHeight += box->height();
}
}
emit boxChanged();
}
#if 0
void GroupBox::justify(const ParagraphStyle& style)
{
if ((style.alignment() != ParagraphStyle::Justified) && (style.alignment() != ParagraphStyle::Extended))
return;
for(int i = 0; i < m_boxes.count(); i++)
{
m_boxes[i]->justify(style);
}
}
#endif
int LineBox::pointToPosition(QPointF coord) const
{
int position = -1;
QPointF rel = coord - QPointF(m_x, m_y);
foreach (const Box *box, boxes())
{
int result = box->pointToPosition(rel);
if (result >= 0)
{
position = result;
break;
}
}
if (position < 0)
{
if (containsPoint(coord))
{
if (coord.x() < x())
position = firstChar();
else
position = lastChar();
}
}
return position;
}
QLineF LineBox::positionToPoint(int pos) const
{
QLineF result;
foreach (const Box *box, boxes())
{
if (box->containsPos(pos))
{
double xPos = x() + box->x();
result = QLineF(xPos, y(), xPos, y() + height());
break;
}
}
return result;
}
void LineBox::render(TextLayoutPainter *p) const
{
p->translate(x(), y());
drawBackGround(p);
p->translate(0, ascent());
foreach (const Box *box, boxes())
{
box->render(p);
}
p->translate(-x(), -y() - ascent());
}
void LineBox::render(TextLayoutPainter *p, PageItem *item) const
{
p->translate(x(), y());
drawBackGround(p);
QRectF selection;
foreach (const Box *box, boxes())
{
if (item->itemText.selected(box->firstChar()) || item->itemText.selected(box->lastChar()))
selection |= QRectF(box->x(), 0, box->width(), height());
}
if (!selection.isEmpty())
{
bool s = p->selected();
bool sw = p->strokeWidth();
p->setSelected(true);
p->setStrokeWidth(0);
p->drawRect(selection);
p->setSelected(s);
p->setStrokeWidth(sw);
}
p->translate(0, ascent());
foreach (const Box *box, boxes())
{
box->render(p, item);
}
p->translate(-x(), -y() - ascent());
}
void LineBox::drawBackGround(TextLayoutPainter *p) const
{
QString lastColor;
double lastShade = 100;
QRectF lastRect;
for (int i = 0; i < boxes().count(); i++)
{
const GlyphBox* box = dynamic_cast<const GlyphBox*>(boxes()[i]);
if (!box)
continue;
const CharStyle& style = box->style();
if (style.backColor() != CommonStrings::None)
{
QRectF rect(box->x(), 0, box->width(), height());
if (lastColor.isEmpty() || (lastColor == style.backColor() && lastShade == style.backShade()))
{
lastRect |= rect;
}
else
{
if (!lastRect.isEmpty())
{
TextLayoutColor backColor(lastColor, lastShade);
p->save();
p->setFillColor(backColor);
p->setStrokeColor(backColor);
p->drawRect(lastRect);
p->restore();
}
lastRect = rect;
}
lastColor = style.backColor();
lastShade = style.backShade();
}
else
{
if (!lastRect.isEmpty())
{
TextLayoutColor backColor(lastColor, lastShade);
p->save();
p->setFillColor(backColor);
p->setStrokeColor(backColor);
p->drawRect(lastRect);
p->restore();
}
lastColor.clear();
lastShade = 100;
lastRect = QRectF();
}
}
if (!lastRect.isEmpty())
{
TextLayoutColor backColor(lastColor, lastShade);
p->save();
p->setFillColor(backColor);
p->setStrokeColor(backColor);
p->drawRect(lastRect);
p->restore();
}
}
void LineBox::addBox(const Box* box)
{
m_boxes.append(const_cast<Box*>(box));
update();
}
void LineBox::removeBox(int i)
{
if (i < 0 || i > boxes().size())
return;
boxes().removeAt(i);
update();
}
void LineBox::update()
{
m_naturalWidth = 0;
foreach (Box* box, boxes()) {
m_firstChar = qMin(m_firstChar, box->firstChar());
m_lastChar = qMax(m_lastChar, box->lastChar());
box->moveTo(m_naturalWidth, box->y());
m_naturalWidth += box->width();
}
emit boxChanged();
}
#if 0
void LineBox::justify(const ParagraphStyle& style)
{
double glyphNatural = 0;
double spaceNatural = 0;
double glyphExtension;
double spaceExtension;
int spaceInsertion = 0;
double imSpace = -1;
// measure natural widths for glyphs and spaces
for (int i = 0; i < m_boxes.count(); ++i)
{
GlyphBox *box = dynamic_cast<GlyphBox*>(m_boxes.at(i));
GlyphRun run(box->glyphs);
if (!run.hasFlag(ScLayout_ExpandingSpace))
{
glyphNatural += run.width();
}
else if (!run.hasFlag(ScLayout_SuppressSpace) )
{
spaceNatural += run.width();
if (imSpace < 0.0 || imSpace > run.width())
imSpace = run.width();
}
if (i != 0 && run.hasFlag(ScLayout_ImplicitSpace))
//implicitSpace(itemText.text(sof - 1), ch))
{
spaceInsertion += 1;
}
}
imSpace /= 2;
// decision: prio 1: stretch glyph; prio 2: insert spaces; prio 3: stretch spaces
if (width() < spaceNatural + glyphNatural *
style.minGlyphExtension() && spaceNatural > 0)
{
glyphExtension = style.minGlyphExtension() - 1;
spaceExtension = (width() - glyphNatural * (1+glyphExtension) ) / spaceNatural - 1;
imSpace = 0;
}
else if (width() < spaceNatural + glyphNatural * style.maxGlyphExtension() && glyphNatural > 0)
{
spaceExtension = 0;
glyphExtension = (width() - spaceNatural) / glyphNatural - 1;
imSpace = 0;
}
else
{
glyphExtension = style.maxGlyphExtension() - 1;
if (spaceInsertion) {
double remaining = width() - glyphNatural * (1 + glyphExtension) - spaceNatural;
if (imSpace > 0) {
if (remaining / spaceInsertion < imSpace) {
imSpace = remaining / spaceInsertion;
spaceExtension = 0;
} else {
spaceExtension = (remaining + spaceNatural) / (spaceNatural + spaceInsertion * imSpace) - 1;
imSpace *= spaceExtension + 1;
}
} else {
imSpace = remaining / spaceInsertion;
spaceExtension = 0;
}
} else {
if (spaceNatural > 0)
spaceExtension = (width() - glyphNatural * (1+glyphExtension) ) / spaceNatural - 1;
else
spaceExtension = 0;
}
}
double glyphScale = 1 + glyphExtension;
/*
qDebug() << QString("justify: line = %7 natural = %1 + %2 = %3
(%4); spaces + %5%%; min=%8; glyphs + %6%%; min=%9")
.arg(spaceNatural).arg(glyphNatural).arg(spaceNatural+glyphNatural).arg(line.naturalWidth)
.arg(spaceExtension).arg(glyphExtension).arg(line.width)
.arg(style.minWordTracking()).arg(style.minGlyphExtension());
*/
int startItem = 0;
if (dynamic_cast<GlyphBox*>(m_boxes[startItem])->glyphs.hasFlag(ScLayout_DropCap))
startItem++;
// distribute whitespace on spaces and glyphs
for (int i = startItem; i < m_boxes.count(); ++i)
{
GlyphBox *box = dynamic_cast<GlyphBox*>(m_boxes.at(i));
GlyphRun& run(box->glyphs);
if (i != 0 && run.hasFlag(ScLayout_ImplicitSpace))
{
GlyphBox *lastBox = dynamic_cast<GlyphBox*>(m_boxes.at(i));
GlyphRun& lastRun(lastBox->glyphs);
lastRun.glyphs().last().xadvance += imSpace;
}
double wide = run.width();
if (!run.hasFlag(ScLayout_ExpandingSpace))
{
for (int j = 0; j < run.glyphs().count(); ++j)
{
GlyphLayout& glyph = run.glyphs()[j];
glyph.xadvance += wide * glyphExtension;
glyph.xoffset *= glyphScale;
glyph.scaleH *= glyphScale;
}
}
else if (!run.hasFlag(ScLayout_SuppressSpace))
{
GlyphLayout& glyph = run.glyphs().last();
glyph.xadvance += wide * spaceExtension;
}
}
}
#endif
void PathLineBox::update()
{
foreach (Box* box, boxes()) {
m_firstChar = qMin(m_firstChar, box->firstChar());
m_lastChar = qMax(m_lastChar, box->lastChar());
}
emit boxChanged();
}
void PathLineBox::drawBackGround(TextLayoutPainter *p) const
{
for (int i = 0; i < boxes().count(); i++)
{
const GlyphBox* box = dynamic_cast<const GlyphBox*>(boxes()[i]);
if (!box)
continue;
const CharStyle& style = box->style();
if (style.backColor() != CommonStrings::None)
{
QRectF rect(box->x(), -box->ascent(), box->width(), box->height());
TextLayoutColor backColor(style.backColor(), style.backShade());
p->save();
p->setMatrix(box->matrix());
p->setFillColor(backColor);
p->setStrokeColor(backColor);
p->drawRect(rect);
p->restore();
}
}
}
void GlyphBox::render(TextLayoutPainter *p, PageItem *item) const
{
bool s = p->selected();
bool selected = item->itemText.selected(firstChar()) || item->itemText.selected(lastChar());
if (((selected && item->isSelected()) || ((item->nextInChain() != 0 || item->prevInChain() != 0) && selected)) &&
(item->doc()->appMode == modeEdit || item->doc()->appMode == modeEditTable))
{
p->setSelected(true);
}
render(p);
p->setSelected(s);
}
void GlyphBox::render(TextLayoutPainter *p) const
{
// This is a very hot method and can be easily called tens of thousands of times per second,
// so it is always a good idea to profile changes to this code.
const CharStyle& charStyle = style();
const ScFace& font = charStyle.font();
double fontSize = charStyle.fontSize() / 10.0;
bool hasFillColor = charStyle.fillColor() != CommonStrings::None;
bool hasStrokeColor = charStyle.strokeColor() != CommonStrings::None;
p->save();
p->setFont(font);
p->setFontSize(fontSize);
p->translate(x(), y());
p->setMatrix(m_matrix);
if (hasFillColor)
p->setFillColor(TextLayoutColor(charStyle.fillColor(), charStyle.fillShade()));
if (hasStrokeColor)
p->setStrokeColor(TextLayoutColor(charStyle.strokeColor(), charStyle.strokeShade()));
foreach (const GlyphLayout gl, m_glyphRun.glyphs())
{
p->save();
// Do underlining first so you can get typographically correct
// underlines when drawing a white outline
bool isUnderlined = ((m_effects & ScStyle_Underline) || m_glyphRun.hasFlag(ScLayout_Underlined));
if (isUnderlined && hasStrokeColor)
{
double st, lw;
if ((charStyle.underlineOffset() != -1) || (charStyle.underlineWidth() != -1))
{
if (charStyle.underlineOffset() != -1)
st = (charStyle.underlineOffset() / 1000.0) * font.descent(fontSize);
else
st = font.underlinePos(fontSize);
if (charStyle.underlineWidth() != -1)
lw = (charStyle.underlineWidth() / 1000.0) * fontSize;
else
lw = qMax(font.strokeWidth(fontSize), 1.0);
}
else
{
st = font.underlinePos(fontSize);
lw = qMax(font.strokeWidth(fontSize), 1.0);
}
if (charStyle.baselineOffset() != 0)
st += fontSize * gl.scaleV * (charStyle.baselineOffset() / 1000.0);
double sw = p->strokeWidth();
const TextLayoutColor& sc = p->strokeColor();
p->setStrokeColor(p->fillColor());
p->setStrokeWidth(lw);
if (charStyle.effects() & ScStyle_Subscript)
p->drawLine(QPointF(gl.xoffset, gl.yoffset - st), QPointF(gl.xoffset + gl.xadvance, gl.yoffset - st));
else
p->drawLine(QPointF(gl.xoffset, -st), QPointF(gl.xoffset + gl.xadvance, -st));
p->setStrokeWidth(sw);
p->setStrokeColor(sc);
}
p->translate(gl.xoffset, gl.yoffset);
if (charStyle.baselineOffset() != 0)
p->translate(0, -fontSize * (charStyle.baselineOffset() / 1000.0));
if (gl.glyph == 0)
{
p->setStrokeColor(TextLayoutColor(PrefsManager::instance()->appPrefs.displayPrefs.controlCharColor.name()));
p->setStrokeWidth(charStyle.fontSize() * gl.scaleV * charStyle.outlineWidth() * 2 / 10000.0);
p->drawGlyphOutline(gl, false);
}
else if ((font.isStroked()) && hasStrokeColor && ((charStyle.fontSize() * gl.scaleV * charStyle.outlineWidth() / 10000.0) != 0))
{
p->setStrokeColor(p->fillColor());
p->setStrokeWidth(charStyle.fontSize() * gl.scaleV * charStyle.outlineWidth() / 10000.0);
p->drawGlyphOutline(gl, false);
}
else
{
if ((m_effects & ScStyle_Shadowed) && hasStrokeColor)
{
double xoff = (charStyle.fontSize() * gl.scaleH * charStyle.shadowXOffset() / 10000.0);
double yoff = (charStyle.fontSize() * gl.scaleV * charStyle.shadowYOffset() / 10000.0);
bool s = p->selected();
const TextLayoutColor& fc = p->fillColor();
p->translate(xoff, -yoff);
p->setFillColor(p->strokeColor());
p->setSelected(false);
p->drawGlyph(gl);
p->translate(-xoff, yoff);
p->setSelected(s);
p->setFillColor(fc);
}
if ((charStyle.effects() & ScStyle_Outline) && hasStrokeColor && ((charStyle.fontSize() * gl.scaleV * charStyle.outlineWidth() / 10000.0) != 0))
{
p->setStrokeWidth((charStyle.fontSize() * gl.scaleV * charStyle.outlineWidth() / 10000.0));
p->drawGlyphOutline(gl, hasFillColor);
}
else if (hasFillColor)
p->drawGlyph(gl);
}
if (m_effects & ScStyle_Strikethrough && hasStrokeColor)
{
double st, lw;
if ((charStyle.strikethruOffset() != -1) || (charStyle.strikethruWidth() != -1))
{
if (charStyle.strikethruOffset() != -1)
st = (charStyle.strikethruOffset() / 1000.0) * font.ascent(fontSize);
else
st = font.strikeoutPos(fontSize);
if (charStyle.strikethruWidth() != -1)
lw = (charStyle.strikethruWidth() / 1000.0) * fontSize;
else
lw = qMax(font.strokeWidth(fontSize), 1.0);
}
else
{
st = font.strikeoutPos(fontSize);
lw = qMax(font.strokeWidth(fontSize), 1.0);
}
if (charStyle.baselineOffset() != 0)
st += fontSize * gl.scaleV * (charStyle.baselineOffset() / 1000.0);
double sw = p->strokeWidth();
const TextLayoutColor& sc = p->strokeColor();
p->setStrokeColor(p->fillColor());
p->setStrokeWidth(lw);
p->drawLine(QPointF(gl.xoffset, gl.yoffset - st), QPointF(gl.xoffset + gl.xadvance, gl.yoffset - st));
p->setStrokeWidth(sw);
p->setStrokeColor(sc);
}
p->restore();
p->translate(gl.xadvance, 0);
}
p->restore();
}
int GlyphBox::pointToPosition(QPointF coord) const
{
double relX = coord.x() - m_x;
double xPos = 0.0;
const QList<GlyphLayout>& glyphs = m_glyphRun.glyphs();
for (int i = 0; i < glyphs.length(); ++i)
{
const GlyphLayout& glyph = glyphs.at(i);
double width = glyph.xadvance * glyph.scaleH;
xPos += width;
if (xPos >= relX)
{
int index = (xPos - width / 2.0 > relX) ? i : i + 1; // FIXME: use clusters
return m_firstChar + index;
}
}
return -1;
}
void ObjectBox::render(TextLayoutPainter *p) const
{
p->save();
double oldX = m_item->xPos();
double oldY = m_item->yPos();
const CharStyle& charStyle = style();
p->translate(x(), y() - ascent());
if (m_glyphRun.hasFlag(ScLayout_DropCap))
p->setScale(m_glyphRun.glyphs().first().scaleH, m_glyphRun.glyphs().first().scaleV);
else
p->setScale(charStyle.scaleH() / 1000.0, charStyle.scaleV() / 1000.0);
p->setMatrix(m_matrix);
m_item->setXPos(m_item->gXpos);
m_item->setYPos((m_item->gHeight * (charStyle.scaleV() / 1000.0)) + m_item->gYpos);
if (charStyle.baselineOffset() != 0)
{
p->translate(0, -m_item->gHeight * (charStyle.baselineOffset() / 1000.0));
m_item->setYPos(m_item->yPos() - m_item->gHeight * (charStyle.baselineOffset() / 1000.0));
}
p->drawObject(m_item);
m_item->setXPos(oldX);
m_item->setYPos(oldY);
p->restore();
}
void ObjectBox::render(TextLayoutPainter *p, PageItem *item) const
{
render(p);
}