Commit 94059dd7 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implement outline() and size() interfaces for the text shape

Now the user can see the shape in Krita and move/transform it a bit.

WARNING: setSize() is not implemented for the text! In the future
         it should wrap the text if the shape has an explicit
         permission for it.
parent b1293f77
...@@ -326,6 +326,7 @@ KoShape::~KoShape() ...@@ -326,6 +326,7 @@ KoShape::~KoShape()
{ {
Q_D(KoShape); Q_D(KoShape);
d->shapeChanged(Deleted); d->shapeChanged(Deleted);
d->listeners.clear();
delete d_ptr; delete d_ptr;
} }
......
...@@ -1295,15 +1295,8 @@ KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *merg ...@@ -1295,15 +1295,8 @@ KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *merg
textChunk->loadSvg(e, m_context); textChunk->loadSvg(e, m_context);
KoXmlText onlyTextChild = getTheOnlyTextChild(e); // 1) apply transformation only in case we are not overriding the shape!
if (!onlyTextChild.isNull()) { // 2) the transformation should be applied *before* the shape is added to the group!
textChunk->loadSvgTextNode(onlyTextChild, m_context);
} else {
QList<KoShape*> childShapes = parseContainer(e, true);
addToGroup(childShapes, textChunk);
}
// apply transformation only in case we are not overriding the shape!
if (!mergeIntoShape) { if (!mergeIntoShape) {
// groups should also have their own coordinate system! // groups should also have their own coordinate system!
textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix); textChunk->applyAbsoluteTransformation(m_context.currentGC()->matrix);
...@@ -1316,6 +1309,14 @@ KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *merg ...@@ -1316,6 +1309,14 @@ KoShape *SvgParser::parseTextElement(const KoXmlElement &e, KoSvgTextShape *merg
applyCurrentBasicStyle(textChunk); applyCurrentBasicStyle(textChunk);
} }
KoXmlText onlyTextChild = getTheOnlyTextChild(e);
if (!onlyTextChild.isNull()) {
textChunk->loadSvgTextNode(onlyTextChild, m_context);
} else {
QList<KoShape*> childShapes = parseContainer(e, true);
addToGroup(childShapes, textChunk);
}
m_context.popGraphicsContext(); m_context.popGraphicsContext();
textChunk->normalizeCharTransformations(); textChunk->normalizeCharTransformations();
......
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
#include <KoVectorPatternBackground.h> #include <KoVectorPatternBackground.h>
#include <KoShapeStroke.h> #include <KoShapeStroke.h>
#include <KoSvgTextChunkShape.h>
#include <KoSvgTextChunkShapeLayoutInterface.h>
namespace { namespace {
...@@ -44,6 +47,8 @@ struct TextPropertiesStaticRegistrar { ...@@ -44,6 +47,8 @@ struct TextPropertiesStaticRegistrar {
qRegisterMetaType<KoSvgText::StrokeProperty>("KoSvgText::StrokeProperty"); qRegisterMetaType<KoSvgText::StrokeProperty>("KoSvgText::StrokeProperty");
QMetaType::registerEqualsComparator<KoSvgText::StrokeProperty>(); QMetaType::registerEqualsComparator<KoSvgText::StrokeProperty>();
QMetaType::registerDebugStreamOperator<KoSvgText::StrokeProperty>(); QMetaType::registerDebugStreamOperator<KoSvgText::StrokeProperty>();
qRegisterMetaType<KoSvgText::AssociatedShapeWrapper>("KoSvgText::AssociatedShapeWrapper");
} }
}; };
...@@ -395,6 +400,60 @@ QDebug operator<<(QDebug dbg, const StrokeProperty &prop) ...@@ -395,6 +400,60 @@ QDebug operator<<(QDebug dbg, const StrokeProperty &prop)
return dbg.space(); return dbg.space();
} }
AssociatedShapeWrapper::AssociatedShapeWrapper()
{
}
AssociatedShapeWrapper::AssociatedShapeWrapper(KoSvgTextChunkShape *shape)
: m_shape(shape)
{
if (m_shape) {
m_shape->addShapeChangeListener(this);
}
}
AssociatedShapeWrapper::AssociatedShapeWrapper(const AssociatedShapeWrapper &rhs)
: AssociatedShapeWrapper(rhs.m_shape)
{
}
AssociatedShapeWrapper &AssociatedShapeWrapper::operator=(const AssociatedShapeWrapper &rhs)
{
if (m_shape) {
m_shape->removeShapeChangeListener(this);
m_shape = 0;
}
m_shape = rhs.m_shape;
if (m_shape) {
m_shape->addShapeChangeListener(this);
}
return *this;
}
bool AssociatedShapeWrapper::isValid() const
{
return m_shape;
}
void AssociatedShapeWrapper::notifyShapeChanged(KoShape::ChangeType type, KoShape *shape)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(shape == m_shape);
if (type == KoShape::Deleted) {
m_shape = 0;
}
}
void AssociatedShapeWrapper::addCharacterRect(const QRectF &rect)
{
if (m_shape) {
m_shape->layoutInterface()->addAssociatedOutline(rect);
}
}
} }
...@@ -37,6 +37,9 @@ class KoXmlElement; ...@@ -37,6 +37,9 @@ class KoXmlElement;
class SvgLoadingContext; class SvgLoadingContext;
class QDebug; class QDebug;
#include <KoShape.h>
class KoSvgTextChunkShape;
namespace KoSvgText namespace KoSvgText
{ {
enum WritingMode { enum WritingMode {
...@@ -179,6 +182,43 @@ struct CharTransformation : public boost::equality_comparable<CharTransformation ...@@ -179,6 +182,43 @@ struct CharTransformation : public boost::equality_comparable<CharTransformation
QDebug KRITAFLAKE_EXPORT operator<<(QDebug dbg, const KoSvgText::CharTransformation &t); QDebug KRITAFLAKE_EXPORT operator<<(QDebug dbg, const KoSvgText::CharTransformation &t);
/**
* @brief The AssociatedShapeWrapper struct is a special shared-pointer-like class
* to store a safe reference to the associated shape. It implements the shape listener
* interface and handles 'delete' signal to safely shutdown the link.
*
* It is used in KoSvgCharChunkFormat to store a backward link to a shape containing this
* subchunk of text, so that the layouting engine could notify the shape, where its text
* is located.
*/
struct AssociatedShapeWrapper : public KoShape::ShapeChangeListener
{
AssociatedShapeWrapper();
AssociatedShapeWrapper(KoSvgTextChunkShape *shape);
AssociatedShapeWrapper(const AssociatedShapeWrapper &rhs);
AssociatedShapeWrapper& operator=(const AssociatedShapeWrapper &rhs);
/**
* @brief isValid shows whether the link to the associated shape is still valid
* @return true if the link is valid
*/
bool isValid() const;
/**
* @brief addCharacterRect notifies the associated shape that one of its characters
* occupies the location \p rect. The shape is expected to add
* this rect to its outline.
* @param rect the rectangle associated by the shape
* @see KoSvgTextChunkShapeLayoutInterface::addAssociatedOutline
*/
void addCharacterRect(const QRectF &rect);
void notifyShapeChanged(KoShape::ChangeType type, KoShape *shape) override;
private:
KoSvgTextChunkShape *m_shape = 0;
};
struct KoSvgCharChunkFormat : public QTextCharFormat struct KoSvgCharChunkFormat : public QTextCharFormat
{ {
KoSvgCharChunkFormat() {} KoSvgCharChunkFormat() {}
...@@ -186,7 +226,8 @@ struct KoSvgCharChunkFormat : public QTextCharFormat ...@@ -186,7 +226,8 @@ struct KoSvgCharChunkFormat : public QTextCharFormat
KoSvgCharChunkFormat(const QTextCharFormat &rhs) : QTextCharFormat(rhs) {} KoSvgCharChunkFormat(const QTextCharFormat &rhs) : QTextCharFormat(rhs) {}
enum SvgCharProperty { enum SvgCharProperty {
TextAnchor = UserProperty + 1 TextAnchor = UserProperty + 1,
AssociatedShape
}; };
inline void setTextAnchor(KoSvgText::TextAnchor value) { inline void setTextAnchor(KoSvgText::TextAnchor value) {
...@@ -214,6 +255,13 @@ struct KoSvgCharChunkFormat : public QTextCharFormat ...@@ -214,6 +255,13 @@ struct KoSvgCharChunkFormat : public QTextCharFormat
return result; return result;
} }
inline void setAssociatedShape(KoSvgTextChunkShape *shape) {
setProperty(AssociatedShape, QVariant::fromValue(AssociatedShapeWrapper(shape)));
}
inline AssociatedShapeWrapper associatedShapeWrapper() const {
return property(AssociatedShape).value<AssociatedShapeWrapper>();
}
}; };
struct BackgroundProperty : public boost::equality_comparable<BackgroundProperty> struct BackgroundProperty : public boost::equality_comparable<BackgroundProperty>
...@@ -254,5 +302,6 @@ Q_DECLARE_METATYPE(KoSvgText::AutoValue) ...@@ -254,5 +302,6 @@ Q_DECLARE_METATYPE(KoSvgText::AutoValue)
Q_DECLARE_METATYPE(KoSvgText::TextDecorations) Q_DECLARE_METATYPE(KoSvgText::TextDecorations)
Q_DECLARE_METATYPE(KoSvgText::BackgroundProperty) Q_DECLARE_METATYPE(KoSvgText::BackgroundProperty)
Q_DECLARE_METATYPE(KoSvgText::StrokeProperty) Q_DECLARE_METATYPE(KoSvgText::StrokeProperty)
Q_DECLARE_METATYPE(KoSvgText::AssociatedShapeWrapper)
#endif // KOSVGTEXT_H #endif // KOSVGTEXT_H
...@@ -229,6 +229,29 @@ struct KoSvgTextChunkShapePrivate::LayoutInterface : public KoSvgTextChunkShapeL ...@@ -229,6 +229,29 @@ struct KoSvgTextChunkShapePrivate::LayoutInterface : public KoSvgTextChunkShapeL
return result; return result;
} }
void addAssociatedOutline(const QRectF &rect) override {
KIS_SAFE_ASSERT_RECOVER_RETURN(isTextNode());
QPainterPath path;
path.addRect(rect);
path |= q->d_func()->associatedOutline;
path.setFillRule(Qt::WindingFill);
path = path.simplified();
q->d_func()->associatedOutline = path;
q->d_func()->size = path.boundingRect().size();
q->notifyChanged();
q->d_func()->shapeChanged(KoShape::SizeChanged);
}
void clearAssociatedOutline() override {
q->d_func()->associatedOutline = QPainterPath();
q->d_func()->size = QSizeF();
q->notifyChanged();
q->d_func()->shapeChanged(KoShape::SizeChanged);
}
private: private:
KoSvgTextChunkShape *q; KoSvgTextChunkShape *q;
}; };
...@@ -263,6 +286,42 @@ KoShape *KoSvgTextChunkShape::cloneShape() const ...@@ -263,6 +286,42 @@ KoShape *KoSvgTextChunkShape::cloneShape() const
return new KoSvgTextChunkShape(*this); return new KoSvgTextChunkShape(*this);
} }
QSizeF KoSvgTextChunkShape::size() const
{
return outlineRect().size();
}
void KoSvgTextChunkShape::setSize(const QSizeF &size)
{
Q_UNUSED(size);
// we do not support resizing!
}
QRectF KoSvgTextChunkShape::outlineRect() const
{
return outline().boundingRect();
}
QPainterPath KoSvgTextChunkShape::outline() const
{
Q_D(const KoSvgTextChunkShape);
QPainterPath result;
result.setFillRule(Qt::WindingFill);
if (d->layoutInterface->isTextNode()) {
result = d->associatedOutline;
} else {
Q_FOREACH (KoShape *shape, shapes()) {
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(shape);
KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape);
result |= chunkShape->outline();
}
}
return result.simplified();
}
void KoSvgTextChunkShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) void KoSvgTextChunkShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{ {
...@@ -681,6 +740,9 @@ KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShapePrivate::fetchCharFormat() co ...@@ -681,6 +740,9 @@ KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShapePrivate::fetchCharFormat() co
format.setTextOutline(textPen); format.setTextOutline(textPen);
// TODO: avoid const_cast somehow...
format.setAssociatedShape(const_cast<KoSvgTextChunkShape*>(q));
return format; return format;
} }
......
...@@ -37,6 +37,11 @@ public: ...@@ -37,6 +37,11 @@ public:
KoShape* cloneShape() const override; KoShape* cloneShape() const override;
QSizeF size() const override;
void setSize(const QSizeF &size) override;
QRectF outlineRect() const override;
QPainterPath outline() const override;
void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override; void paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) override;
void saveOdf(KoShapeSavingContext &Context) const override; void saveOdf(KoShapeSavingContext &Context) const override;
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &Context) override; bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &Context) override;
......
...@@ -46,6 +46,9 @@ public: ...@@ -46,6 +46,9 @@ public:
virtual QVector<KoSvgText::CharTransformation> localCharTransformations() const = 0; virtual QVector<KoSvgText::CharTransformation> localCharTransformations() const = 0;
virtual void addAssociatedOutline(const QRectF &rect) = 0;
virtual void clearAssociatedOutline() = 0;
struct SubChunk struct SubChunk
{ {
SubChunk() SubChunk()
......
...@@ -45,6 +45,8 @@ struct KoSvgTextChunkShapePrivate : public KoShapeContainerPrivate ...@@ -45,6 +45,8 @@ struct KoSvgTextChunkShapePrivate : public KoShapeContainerPrivate
struct LayoutInterface; struct LayoutInterface;
QScopedPointer<LayoutInterface> layoutInterface; QScopedPointer<LayoutInterface> layoutInterface;
QPainterPath associatedOutline;
KoSvgText::KoSvgCharChunkFormat fetchCharFormat() const; KoSvgText::KoSvgCharChunkFormat fetchCharFormat() const;
void applyParentCharTransformations(const QVector<KoSvgText::CharTransformation> transformations); void applyParentCharTransformations(const QVector<KoSvgText::CharTransformation> transformations);
......
...@@ -55,6 +55,8 @@ struct KoSvgTextShapePrivate : public KoSvgTextChunkShapePrivate ...@@ -55,6 +55,8 @@ struct KoSvgTextShapePrivate : public KoSvgTextChunkShapePrivate
std::vector<std::unique_ptr<QTextLayout>> layouts; std::vector<std::unique_ptr<QTextLayout>> layouts;
std::vector<QPointF> layoutOffsets; std::vector<QPointF> layoutOffsets;
void clearAssociatedOutlines(KoShape *rootShape);
Q_DECLARE_PUBLIC(KoSvgTextShape) Q_DECLARE_PUBLIC(KoSvgTextShape)
}; };
...@@ -276,37 +278,62 @@ void KoSvgTextShape::relayout() ...@@ -276,37 +278,62 @@ void KoSvgTextShape::relayout()
d->layoutOffsets.push_back(-diff); d->layoutOffsets.push_back(-diff);
} }
}
bool KoSvgTextShape::isRootTextNode() const
{
return true;
}
d->clearAssociatedOutlines(this);
for (int i = 0; i < int(d->layouts.size()); i++) {
const QTextLayout &layout = *d->layouts[i];
const QPointF layoutOffset = d->layoutOffsets[i];
using namespace KoSvgText;
QVector<AssociatedShapeWrapper> shapeMap;
shapeMap.resize(layout.text().size());
Q_FOREACH (const QTextLayout::FormatRange &range, layout.additionalFormats()) {
for (int k = range.start; k < range.start + range.length; k++) {
KIS_SAFE_ASSERT_RECOVER_BREAK(k >= 0 && k < shapeMap.size());
const KoSvgCharChunkFormat &format =
static_cast<const KoSvgCharChunkFormat&>(range.format);
shapeMap[k] = format.associatedShapeWrapper();
}
}
for (int j = 0; j < layout.lineCount(); j++) {
const QTextLine line = layout.lineAt(j);
for (int k = line.textStart(); k < line.textStart() + line.textLength(); k++) {
KIS_SAFE_ASSERT_RECOVER_BREAK(k >= 0 && k < shapeMap.size());
AssociatedShapeWrapper wrapper = shapeMap[k];
if (!wrapper.isValid()) continue;
const qreal x1 = line.cursorToX(k, QTextLine::Leading);
const qreal x2 = line.cursorToX(k, QTextLine::Trailing);
QRectF rect(qMin(x1, x2), line.y(), qAbs(x1 - x2), line.height());
wrapper.addCharacterRect(rect.translated(layoutOffset));
}
}
}
}
void KoSvgTextShapePrivate::clearAssociatedOutlines(KoShape *rootShape)
{
KoSvgTextChunkShape *chunkShape = dynamic_cast<KoSvgTextChunkShape*>(rootShape);
KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape);
chunkShape->layoutInterface()->clearAssociatedOutline();
Q_FOREACH (KoShape *child, chunkShape->shapes()) {
clearAssociatedOutlines(child);
}
}
bool KoSvgTextShape::isRootTextNode() const
{
return true;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment