diff --git a/krita/pics/svg/dark_brush_ratio.svg b/krita/pics/svg/dark_brush_ratio.svg
new file mode 100644
index 0000000000..c82751844f
--- /dev/null
+++ b/krita/pics/svg/dark_brush_ratio.svg
@@ -0,0 +1,82 @@
+
+
+
+
diff --git a/krita/pics/svg/dark_brush_rotation.svg b/krita/pics/svg/dark_brush_rotation.svg
new file mode 100644
index 0000000000..592eebba9a
--- /dev/null
+++ b/krita/pics/svg/dark_brush_rotation.svg
@@ -0,0 +1,86 @@
+
+
+
+
diff --git a/krita/pics/svg/dark_brush_size.svg b/krita/pics/svg/dark_brush_size.svg
new file mode 100644
index 0000000000..dc2c89c629
--- /dev/null
+++ b/krita/pics/svg/dark_brush_size.svg
@@ -0,0 +1,81 @@
+
+
+
+
diff --git a/krita/pics/svg/light_brush_ratio.svg b/krita/pics/svg/light_brush_ratio.svg
new file mode 100644
index 0000000000..0f368422d0
--- /dev/null
+++ b/krita/pics/svg/light_brush_ratio.svg
@@ -0,0 +1,82 @@
+
+
+
+
diff --git a/krita/pics/svg/light_brush_rotation.svg b/krita/pics/svg/light_brush_rotation.svg
new file mode 100644
index 0000000000..e82f1a7c49
--- /dev/null
+++ b/krita/pics/svg/light_brush_rotation.svg
@@ -0,0 +1,86 @@
+
+
+
+
diff --git a/krita/pics/svg/light_brush_size.svg b/krita/pics/svg/light_brush_size.svg
new file mode 100644
index 0000000000..e4baa5e27e
--- /dev/null
+++ b/krita/pics/svg/light_brush_size.svg
@@ -0,0 +1,81 @@
+
+
+
+
diff --git a/krita/pics/svg/svg-icons.qrc b/krita/pics/svg/svg-icons.qrc
index 312f78b568..a163ad11ba 100644
--- a/krita/pics/svg/svg-icons.qrc
+++ b/krita/pics/svg/svg-icons.qrc
@@ -1,150 +1,156 @@
broken-preset.svgzdark_addblankframe.svgdark_addcolor.svgdark_addduplicateframe.svg
+ dark_brush_size.svg
+ dark_brush_rotation.svg
+ dark_brush_ratio.svgdark_deletekeyframe.svgdark_docker_lock_a.svgdark_docker_lock_b.svgdark_layer-locked.svgdark_layer-unlocked.svgdark_nextframe.svgdark_nextkeyframe.svgdark_lastframe.svgdark_prevkeyframe.svgdark_firstframe.svgdark_pallete_librarysvg.svgdark_passthrough-disabled.svgdark_passthrough-enabled.svgdark_prevframe.svgdark_selection-mode_ants.svgdark_selection-mode_invisible.svgdark_selection-mode_mask.svgdark_transparency-disabled.svgdark_transparency-enabled.svgdark_trim-to-image.svgdelete.svgzlayer-style-disabled.svglayer-style-enabled.svglight_addblankframe.svglight_addcolor.svglight_addduplicateframe.svg
+ light_brush_size.svg
+ light_brush_rotation.svg
+ light_brush_ratio.svglight_deletekeyframe.svglight_docker_lock_a.svglight_docker_lock_b.svglight_layer-locked.svglight_layer-unlocked.svglight_nextframe.svglight_pallete_library.svglight_passthrough-disabled.svgzlight_passthrough-enabled.svgzlight_prevframe.svglight_nextkeyframe.svglight_lastframe.svglight_prevkeyframe.svglight_firstframe.svglight_selection-mode_ants.svglight_selection-mode_invisible.svglight_selection-mode_mask.svglight_timeline_keyframe.svglight_transparency-disabled.svglight_transparency-enabled.svglight_trim-to-image.svgpaintop_presets_disabled.svgzpaintop_settings_01.svgzselection-info.svgselection-mode_invisible.svgsvg-icons.qrctransparency-locked.svgtransparency-unlocked.svgworkspace-chooser.svglight_lazyframeOn.svglight_lazyframeOff.svgdark_lazyframeOn.svgdark_lazyframeOff.svgdark_mirror-view.svglight_mirror-view.svgdark_rotation-reset.svglight_rotation-reset.svglight_smoothing-basic.svglight_smoothing-no.svglight_smoothing-stabilizer.svglight_smoothing-weighted.svgdark_smoothing-basic.svgdark_smoothing-no.svgdark_smoothing-stabilizer.svgdark_smoothing-weighted.svglight_merge-layer-below.svgdark_merge-layer-below.svglight_rotate-canvas-left.svglight_rotate-canvas-right.svgdark_rotate-canvas-left.svgdark_rotate-canvas-right.svglight_gmic.svgdark_gmic.svglight_split-layer.svgdark_split-layer.svglight_color-to-alpha.svgdark_color-to-alpha.svglight_preset-switcher.svgdark_preset-switcher.svgdark_animation_play.svgdark_animation_stop.svgdark_dropframe.svgdark_droppedframes.svglight_animation_play.svglight_animation_stop.svglight_dropframe.svglight_droppedframes.svgdark_landscape.svgdark_portrait.svglight_landscape.svglight_portrait.svgdark_interpolation_constant.svgdark_interpolation_linear.svgdark_interpolation_bezier.svgdark_interpolation_sharp.svgdark_interpolation_smooth.svglight_interpolation_bezier.svglight_interpolation_constant.svglight_interpolation_linear.svglight_interpolation_sharp.svglight_interpolation_smooth.svgdark_audio-none.svgdark_audio-volume-high.svgdark_audio-volume-mute.svgdark_keyframe-add.svgdark_keyframe-remove.svgdark_zoom-fit.svgdark_zoom-horizontal.svgdark_zoom-vertical.svglight_audio-none.svglight_audio-volume-high.svglight_audio-volume-mute.svglight_keyframe-add.svglight_keyframe-remove.svglight_zoom-fit.svglight_zoom-horizontal.svglight_zoom-vertical.svgdark_showColoring.svgdark_showMarks.svgdark_showColoringOff.svgdark_showMarksOff.svgdark_updateColorize.svglight_showColoring.svglight_showMarks.svglight_showColoringOff.svglight_showMarksOff.svglight_updateColorize.svg
diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt
index a92a04f737..48573ebd8b 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -1,19 +1,22 @@
+include_directories(libpaintop)
+
+add_subdirectory( libpaintop )
add_subdirectory( version )
add_subdirectory( global )
add_subdirectory( koplugin )
add_subdirectory( widgetutils )
add_subdirectory( widgets )
add_subdirectory( store )
add_subdirectory( odf )
add_subdirectory( flake )
add_subdirectory( basicflakes )
add_subdirectory( pigment )
add_subdirectory( command )
add_subdirectory( brush )
add_subdirectory( psd )
add_subdirectory( color )
add_subdirectory( image )
add_subdirectory( ui )
add_subdirectory( vectorimage )
add_subdirectory( impex )
add_subdirectory( libkis )
diff --git a/libs/flake/KoFilterEffect.h b/libs/flake/KoFilterEffect.h
index f0173f79c5..2817f2861c 100644
--- a/libs/flake/KoFilterEffect.h
+++ b/libs/flake/KoFilterEffect.h
@@ -1,170 +1,170 @@
/* This file is part of the KDE project
* Copyright (c) 2009 Cyrille Berger
* Copyright (c) 2009 Jan Hambrecht
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _KO_FILTER_EFFECT_H_
#define _KO_FILTER_EFFECT_H_
class QImage;
class QString;
class QRectF;
class KoXmlWriter;
class KoFilterEffectRenderContext;
class KoFilterEffectLoadingContext;
-class KoXmlElement;
+#include
#include "kritaflake_export.h"
#include
/**
* This is the base for filter effect (blur, invert...) that can be applied on a shape.
* All sizes and coordinates of the filter effect are stored in object bounding box
* coordinates, where (0,0) refers to the top-left corner of a shapes bounding rect
* and (1,1) refers to the bottom-right corner.
* When loading, a transformation matrix is given to convert from user space coordinates.
* Another transformation matrix is given via the render context to convert back to
* user space coordinates when applying the effect.
* Using object bounding box coordinates internally makes it easy to share effects
* between shapes or even between users via the filter effect resources.
*/
class KRITAFLAKE_EXPORT KoFilterEffect
{
public:
KoFilterEffect(const QString &id, const QString &name);
virtual ~KoFilterEffect();
/// Returns the user visible name of the filter
QString name() const;
/// Returns the unique id of the filter
QString id() const;
/// Sets the region the filter is applied to in bounding box units
void setFilterRect(const QRectF &filterRect);
/// Returns the region this filter is applied to in bounding box units
QRectF filterRect() const;
/// Returns the region this filter is applied to for the given bounding rect
QRectF filterRectForBoundingRect(const QRectF &boundingRect) const;
/**
* Sets the name of the output image
*
* The name is used so that other effects can reference
* the output of this effect as one of their input images.
*
* @param output the output image name
*/
void setOutput(const QString &output);
/// Returns the name of the output image
QString output() const;
/**
* Returns list of named input images of this filter effect.
*
* These names identify the input images for this filter effect.
* These can be one of the keywords SourceGraphic, SourceAlpha,
* BackgroundImage, BackgroundAlpha, FillPaint or StrokePaint,
* as well as a named output of another filter effect in the stack.
* An empty input list of the first effect in the stack default to
* SourceGraphic, whereas on subsequent effects it defaults to the
* result of the previous filter effect.
*/
QList inputs() const;
/// Adds a new input at the end of the input list
void addInput(const QString &input);
/// Inserts an input at the giben position in the input list
void insertInput(int index, const QString &input);
/// Sets an existing input to a new value
void setInput(int index, const QString &input);
/// Removes an input from the given position in the input list
void removeInput(int index);
/**
* Return the required number of input images.
* The default required number of input images is 1.
* Derived classes should call setRequiredInputCount to set
* a different number.
*/
int requiredInputCount() const;
/**
* Returns the maximal number of input images.
* The default maximal number of input images is 1.
* Derived classes should call setMaximalInputCount to set
* a different number.
*/
int maximalInputCount() const;
/**
* Apply the effect on an image.
* @param image the image the filter should be applied to
* @param context the render context providing additional data
*/
virtual QImage processImage(const QImage &image, const KoFilterEffectRenderContext &context) const = 0;
/**
* Apply the effect on a list of images.
* @param images the images the filter should be applied to
* @param context the render context providing additional data
*/
virtual QImage processImages(const QList &images, const KoFilterEffectRenderContext &context) const;
/**
* Loads data from given xml element.
* @param element the xml element to load data from
* @param context the loading context providing additional data
* @return true if loading was successful, else false
*/
virtual bool load(const KoXmlElement &element, const KoFilterEffectLoadingContext &context) = 0;
/**
* Writes custom data to given xml element.
* @param writer the xml writer to write data to
*/
virtual void save(KoXmlWriter &writer) = 0;
protected:
/// Sets the required number of input images
void setRequiredInputCount(int count);
/// Sets the maximal number of input images
void setMaximalInputCount(int count);
/**
* Saves common filter attributes
*
* Saves result, subregion and input attributes. The input attrinbute
* is only saved if required, maximal and actual input count equals 1.
* All other filters have to write inputs on their own.
*/
void saveCommonAttributes(KoXmlWriter &writer);
private:
class Private;
Private* const d;
};
#endif // _KO_FILTER_EFFECT_H_
diff --git a/libs/flake/KoFilterEffectRegistry.h b/libs/flake/KoFilterEffectRegistry.h
index c79213c745..1176385d2f 100644
--- a/libs/flake/KoFilterEffectRegistry.h
+++ b/libs/flake/KoFilterEffectRegistry.h
@@ -1,61 +1,61 @@
/* This file is part of the KDE project
* Copyright (c) 2009 Jan Hambrecht
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOFILTEREFFECTREGISTRY_H
#define KOFILTEREFFECTREGISTRY_H
#include
#include
#include "kritaflake_export.h"
-class KoXmlElement;
+#include
class KoFilterEffectLoadingContext;
class KoFilterEffect;
class KRITAFLAKE_EXPORT KoFilterEffectRegistry : public KoGenericRegistry
{
public:
KoFilterEffectRegistry();
virtual ~KoFilterEffectRegistry();
/**
* Return the only instance of KoFilterEffectRegistry.
* Creates an instance on the first call.
*/
static KoFilterEffectRegistry *instance();
/**
* Creates filter effect from given xml element.
* @param element the xml element to load form
* @return the created filter effect if successful, otherwise returns 0
*/
KoFilterEffect *createFilterEffectFromXml(const KoXmlElement &element, const KoFilterEffectLoadingContext &context);
private:
KoFilterEffectRegistry(const KoFilterEffectRegistry&);
KoFilterEffectRegistry operator=(const KoFilterEffectRegistry&);
void init();
class Private;
Private * const d;
};
#endif // KOFILTEREFFECTREGISTRY_H
diff --git a/libs/flake/KoFrameShape.h b/libs/flake/KoFrameShape.h
index 062bfd2ffd..466a85e1e4 100644
--- a/libs/flake/KoFrameShape.h
+++ b/libs/flake/KoFrameShape.h
@@ -1,94 +1,94 @@
/* This file is part of the KDE project
Copyright (C) 2008 Thorsten Zachmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOFRAMESHAPE_H
#define KOFRAMESHAPE_H
#include "kritaflake_export.h"
class KoShapeLoadingContext;
-class KoXmlElement;
+#include
class QString;
/**
* @brief Base class for shapes that are saved as a part of a draw:frame.
*
* Shapes like the TextShape or the PictureShape are implementing this
* class to deal with frames and their attributes.
*
* What follows is a sample taken out of an ODT-file that shows how this works
* together;
* @code
*
*
*
* @endcode
*
* The loading code of the shape gets passed the draw:frame element. Out of this element the
* odf attributes can be loaded. Then it calls loadOdfFrame which loads the correct frame element
* the object supports. The loading of the frame element is done in the loadOdfFrameElement.
*
* @code
* bool PictureShape::loadOdf( const KoXmlElement & element, KoShapeLoadingContext &context )
* {
* loadOdfAttributes( element, context, OdfAllAttributes );
* return loadOdfFrame( element, context );
* }
* @endcode
*/
class KRITAFLAKE_EXPORT KoFrameShape
{
public:
/**
* Constructor.
*
* \param ns The namespace. E.g. KoXmlNS::draw
* \param element The tag-name. E.g. "image"
*/
KoFrameShape(const QString &ns, const QString &tag);
/**
* Destructor.
*/
virtual ~KoFrameShape();
/**
* Loads the content of the draw:frame element and it's children. This
* method calls the abstract loadOdfFrameElement() method.
*
* @return false if loading failed
*/
virtual bool loadOdfFrame(const KoXmlElement &element, KoShapeLoadingContext &context);
protected:
/**
* Abstract method to handle loading of the defined inner element like
* e.g. the draw:image element.
* @return false if loading failed
*/
virtual bool loadOdfFrameElement(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
private:
class Private;
Private * const d;
};
#endif /* KOFRAMESHAPE_H */
diff --git a/libs/flake/KoMarker.h b/libs/flake/KoMarker.h
index a50bc27b4f..2413ced08b 100644
--- a/libs/flake/KoMarker.h
+++ b/libs/flake/KoMarker.h
@@ -1,122 +1,123 @@
/* This file is part of the KDE project
Copyright (C) 2011 Thorsten Zachmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOMARKER_H
#define KOMARKER_H
#include
#include
#include "kritaflake_export.h"
#include
-class KoXmlElement;
+#include
+
class KoShapeLoadingContext;
class KoShapeSavingContext;
class QString;
class QPainterPath;
class KoShape;
class QPainter;
class KoShapeStroke;
class KRITAFLAKE_EXPORT KoMarker : public QSharedData
{
public:
KoMarker();
~KoMarker();
/**
* Display name of the marker
*
* @return Display name of the marker
*/
QString name() const;
KoMarker(const KoMarker &rhs);
bool operator==(const KoMarker &other) const;
enum MarkerCoordinateSystem {
StrokeWidth,
UserSpaceOnUse
};
void setCoordinateSystem(MarkerCoordinateSystem value);
MarkerCoordinateSystem coordinateSystem() const;
static MarkerCoordinateSystem coordinateSystemFromString(const QString &value);
static QString coordinateSystemToString(MarkerCoordinateSystem value);
void setReferencePoint(const QPointF &value);
QPointF referencePoint() const;
void setReferenceSize(const QSizeF &size);
QSizeF referenceSize() const;
bool hasAutoOtientation() const;
void setAutoOrientation(bool value);
// measured in radians!
qreal explicitOrientation() const;
// measured in radians!
void setExplicitOrientation(qreal value);
void setShapes(const QList &shapes);
QList shapes() const;
/**
* @brief paintAtOrigin paints the marker at the position \p pos.
* Scales and rotates the masrker if needed.
*/
void paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
/**
* Return maximum distance that the marker can take outside the shape itself
*/
qreal maxInset(qreal strokeWidth) const;
/**
* Bounding rect of the marker in local coordinates. It is assumed that the marker
* is painted with the reference point placed at position (0,0)
*/
QRectF boundingRect(qreal strokeWidth, qreal nodeAngle) const;
/**
* Outline of the marker in local coordinates. It is assumed that the marker
* is painted with the reference point placed at position (0,0)
*/
QPainterPath outline(qreal strokeWidth, qreal nodeAngle) const;
/**
* Draws a preview of the marker in \p previewRect of \p painter
*/
void drawPreview(QPainter *painter, const QRectF &previewRect,
const QPen &pen, KoFlake::MarkerPosition position);
void applyShapeStroke(KoShape *shape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle);
private:
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoMarker*)
#endif /* KOMARKER_H */
diff --git a/libs/flake/KoMarkerCollection.cpp b/libs/flake/KoMarkerCollection.cpp
index 22c53dffca..f723f34dee 100644
--- a/libs/flake/KoMarkerCollection.cpp
+++ b/libs/flake/KoMarkerCollection.cpp
@@ -1,142 +1,139 @@
/* This file is part of the KDE project
Copyright (C) 2011 Thorsten Zachmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoMarkerCollection.h"
#include
#include
#include "KoMarker.h"
#include
#include
#include
#include
#include
#include
#include
#include "kis_debug.h"
// WARNING: there is a bug in GCC! It doesn't warn that we are
// deleting an uninitialized type here!
#include
class Q_DECL_HIDDEN KoMarkerCollection::Private
{
public:
~Private()
{
}
QList > markers;
};
KoMarkerCollection::KoMarkerCollection(QObject *parent)
: QObject(parent)
, d(new Private)
{
// Add no marker so the user can remove a marker from the line.
d->markers.append(QExplicitlySharedDataPointer(0));
// Add default markers
loadDefaultMarkers();
}
KoMarkerCollection::~KoMarkerCollection()
{
delete d;
}
void KoMarkerCollection::loadMarkersFromFile(const QString &svgFile)
{
QFile file(svgFile);
if (!file.exists()) return;
if (!file.open(QIODevice::ReadOnly)) return;
- QXmlStreamReader reader(&file);
- reader.setNamespaceProcessing(false);
-
QString errorMsg;
int errorLine = 0;
int errorColumn;
KoXmlDocument doc;
- bool ok = doc.setContent(&reader, &errorMsg, &errorLine, &errorColumn);
+ bool ok = doc.setContent(&file, false, &errorMsg, &errorLine, &errorColumn);
if (!ok) {
errKrita << "Parsing error in " << svgFile << "! Aborting!" << endl
<< " In line: " << errorLine << ", column: " << errorColumn << endl
<< " Error message: " << errorMsg << endl;
errKrita << i18n("Parsing error in the main document at line %1, column %2\nError message: %3"
, errorLine , errorColumn , errorMsg);
return;
}
KoDocumentResourceManager manager;
SvgParser parser(&manager);
parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
parser.setXmlBaseDir(QFileInfo(svgFile).absolutePath());
parser.setFileFetcher(
[](const QString &fileName) {
QFile file(fileName);
if (!file.exists()) return QByteArray();
file.open(QIODevice::ReadOnly);
return file.readAll();
});
QSizeF fragmentSize;
QList shapes = parser.parseSvg(doc.documentElement(), &fragmentSize);
qDeleteAll(shapes);
Q_FOREACH (const QExplicitlySharedDataPointer &marker, parser.knownMarkers()) {
addMarker(marker.data());
}
}
void KoMarkerCollection::loadDefaultMarkers()
{
QString filePath = KoResourcePaths::findResource("data", "styles/markers.svg");
loadMarkersFromFile(filePath);
}
QList KoMarkerCollection::markers() const
{
QList markerList;
foreach (const QExplicitlySharedDataPointer& m, d->markers){
markerList.append(m.data());
}
return markerList;
}
KoMarker * KoMarkerCollection::addMarker(KoMarker *marker)
{
foreach (const QExplicitlySharedDataPointer& m, d->markers) {
if (marker == m.data()) {
return marker;
}
if (m && *marker == *m) {
debugFlake << "marker is the same as other";
return m.data();
}
}
d->markers.append(QExplicitlySharedDataPointer(marker));
return marker;
}
diff --git a/libs/flake/KoMarkerCollection.h b/libs/flake/KoMarkerCollection.h
index 56a280da9f..044b79d963 100644
--- a/libs/flake/KoMarkerCollection.h
+++ b/libs/flake/KoMarkerCollection.h
@@ -1,68 +1,68 @@
/* This file is part of the KDE project
Copyright (C) 2011 Thorsten Zachmann
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOMARKERCOLLECTION_H
#define KOMARKERCOLLECTION_H
#include "kritaflake_export.h"
#include
#include
#include
#include
class KoMarker;
-class KoXmlElement;
+#include
class KoShapeLoadingContext;
class KRITAFLAKE_EXPORT KoMarkerCollection : public QObject
{
Q_OBJECT
public:
explicit KoMarkerCollection(QObject *parent = 0);
virtual ~KoMarkerCollection();
QList markers() const;
/**
* Add marker to collection
*
* The collection checks if a marker with the same content exists and if so deletes the
* passed marker and returns a pointer to an existing marker. If no such marker exists it
* adds the marker and return the same pointer as passed.
* Calling that function passes ownership of the marker to this class.
*
* @param marker Marker to add
* @return pointer to marker that should be used. This might be different to the marker passed
*/
KoMarker * addMarker(KoMarker *marker);
void loadMarkersFromFile(const QString &svgFile);
private:
/// load the markers that are available per default.
void loadDefaultMarkers();
class Private;
Private * const d;
};
Q_DECLARE_METATYPE(KoMarkerCollection *)
#endif /* KOMARKERCOLLECTION_H */
diff --git a/libs/flake/KoOdfGradientBackground.h b/libs/flake/KoOdfGradientBackground.h
index 2170ef2aac..bd5210e363 100644
--- a/libs/flake/KoOdfGradientBackground.h
+++ b/libs/flake/KoOdfGradientBackground.h
@@ -1,66 +1,66 @@
/* This file is part of the KDE project
*
* Copyright (C) 2011 Lukáš Tvrdý
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOODFGRADIENTBACKGROUND_H
#define KOODFGRADIENTBACKGROUND_H
#include "KoShapeBackground.h"
#include "kritaflake_export.h"
class QImage;
class KoOdfGradientBackgroundPrivate;
-class KoXmlElement;
+#include
class KoGenStyles;
class KoGenStyle;
/// Gradients from odf that are not native to Qt
class KoOdfGradientBackground : public KoShapeBackground {
public:
// constructor
KoOdfGradientBackground();
// destructor
virtual ~KoOdfGradientBackground();
bool compareTo(const KoShapeBackground *other) const override;
/// reimplemented from KoShapeBackground
virtual void fillStyle(KoGenStyle& style, KoShapeSavingContext& context);
/// reimplemented from KoShapeBackground
virtual bool loadStyle(KoOdfLoadingContext& context, const QSizeF& shapeSize);
/// reimplemented from KoShapeBackground
virtual void paint(QPainter& painter, const KoViewConverter &converter, KoShapePaintingContext &context, const QPainterPath& fillPath) const;
private:
bool loadOdf(const KoXmlElement &element);
void saveOdf(KoGenStyle& styleFill, KoGenStyles& mainStyles) const;
void renderSquareGradient(QImage &buffer) const;
void renderRectangleGradient(QImage &buffer) const;
private:
void debug() const;
private:
Q_DECLARE_PRIVATE(KoOdfGradientBackground)
Q_DISABLE_COPY(KoOdfGradientBackground)
};
#endif
diff --git a/libs/flake/KoOdfWorkaround.h b/libs/flake/KoOdfWorkaround.h
index 684828c37d..038f4cf8c9 100644
--- a/libs/flake/KoOdfWorkaround.h
+++ b/libs/flake/KoOdfWorkaround.h
@@ -1,162 +1,162 @@
/* This file is part of the KDE project
Copyright (C) 2009 Thorsten Zachmann
Copyright (C) 2011 Jan Hambrecht
Copyright (C) 2011 Lukáš Tvrdý
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOODFWORKAROUND_H
#define KOODFWORKAROUND_H
#include "kritaflake_export.h"
#include "KoTextShapeDataBase.h"
#include
#include
-class KoXmlElement;
+#include
class KoShape;
class KoShapeLoadingContext;
class QPen;
class QColor;
class QString;
class KoColorBackground;
/**
* This class should contain all workarounds to correct problems with different ODF
* implementations. If you need to access application specific things please create a
* new namespace in the application you need it in
* All calls to methods of this class should be wrapped into ifndefs like e.g.
*
* @code
* #ifndef NWORKAROUND_ODF_BUGS
* KoOdfWorkaround::fixPenWidth(pen, context);
* #endif
* @endcode
*/
namespace KoOdfWorkaround
{
/**
* OpenOffice handles a line with the width of 0 as a cosmetic line but in svg it makes the line invisible.
* To show it in calligra use a very small line width. However this is not a cosmetic line.
*/
KRITAFLAKE_EXPORT void fixPenWidth(QPen &pen, KoShapeLoadingContext &context);
/**
* OpenOffice < 3.0 does not store the draw:enhanced-path for draw:type="ellipse"
* Add the path needed for the ellipse
*/
KRITAFLAKE_EXPORT void fixEnhancedPath(QString &path, const KoXmlElement &element, KoShapeLoadingContext &context);
/**
* OpenOffice interchanges the position coordinates for polar handles.
* According to the specification the first coordinate is the angle, the
* second coordinates is the radius. OpenOffice does it the other way around.
*/
KRITAFLAKE_EXPORT void fixEnhancedPathPolarHandlePosition(QString &position, const KoXmlElement &element, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT bool fixMissingStroke(QPen &pen, const KoXmlElement &element, KoShapeLoadingContext &context, const KoShape *shape = 0);
KRITAFLAKE_EXPORT QColor fixMissingFillColor(const KoXmlElement &element, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT bool fixMissingStyle_DisplayLabel(const KoXmlElement &element, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT QSharedPointer fixBackgroundColor(const KoShape *shape, KoShapeLoadingContext &context);
/**
* Old versions of ooimpress does not set the placeholder for shapes that should have it set
* See open office issue http://www.openoffice.org/issues/show_bug.cgi?id=96406
* And kde bug https://bugs.kde.org/show_bug.cgi?id=185354
*/
KRITAFLAKE_EXPORT void setFixPresentationPlaceholder(bool fix, KoShapeLoadingContext &context);
KRITAFLAKE_EXPORT bool fixPresentationPlaceholder();
KRITAFLAKE_EXPORT void fixPresentationPlaceholder(KoShape *shape);
/**
* OpenOffice and LibreOffice save gluepoint positions wrong when no align is specified.
* According to the specification for the above situation, the position should be saved
* as percent values relative to the shapes center point. OpenOffice seems to write
* these percent values converted to length units, where the millimeter value corresponds
* to the correct percent value (i.e. -5cm = -50mm = -50%).
*/
KRITAFLAKE_EXPORT void fixGluePointPosition(QString &positionString, KoShapeLoadingContext &context);
/**
* OpenOffice and LibreOffice does not conform to the specification about default value
* of the svg:fill-rule. If this attribute is missing, according the spec, the initial
* value is nonzero, but OOo uses evenodd. Because we are conform to the spec, we need
* to set what OOo display.
* See http://www.w3.org/TR/SVG/painting.html#FillRuleProperty
*/
KRITAFLAKE_EXPORT void fixMissingFillRule(Qt::FillRule &fillRule, KoShapeLoadingContext &context);
/**
- * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to
+ * OpenOffice resizes text shapes with autogrow in both directions. If the text box is saved to
* small the text will not fit and it needs to be adjusted during the first layout.
* This methods returns true if we need to adjust the layout. The adjusting is handled at a different place.
*/
KRITAFLAKE_EXPORT bool fixAutoGrow(KoTextShapeDataBase::ResizeMethod method, KoShapeLoadingContext &context);
/**
* OpenOffice and LibreOffice do not set the svg:width, svg:height, svg:x and svg:y correctly when saving
* parts of draw:ellipses or draw:circle
* This method returns true when the width, height, x and y is given for the full circle
*/
KRITAFLAKE_EXPORT bool fixEllipse(const QString &kind, KoShapeLoadingContext &context);
/**
* Calligra did use the bad strings "Formula.hidden" and "protected Formula.hidden" as values
* for style:cell-protect, instead of "formula-hidden" and "protected formula-hidden".
* This method fixes the bad strings to the correct ones.
*/
KRITAFLAKE_EXPORT void fixBadFormulaHiddenForStyleCellProtect(QString &value);
/**
* Calligra used to store text:time-value with a "0-00-00T" prefix
* This method removes that prefix.
*/
KRITAFLAKE_EXPORT void fixBadDateForTextTime(QString &value);
/**
* OpenOffice.org used to write the "rect(...)" value for fo:clip without
* separating the 4 offset values by commas.
* This method changes the string with the offset values to have commas as separators.
*/
KRITAFLAKE_EXPORT void fixClipRectOffsetValuesString(QString &offsetValuesString);
/**
* LibreOffice used to write text:style-name attribute for table:table-template element,
* which is not a valid attribute for the element.
*/
KRITAFLAKE_EXPORT QString fixTableTemplateName(const KoXmlElement &e);
/**
* LibreOffice used to write text:style-name attribute for
* table:first-row, table:last-row, table:first-column,
* table:last-column, table:odd-rows, table:odd-columns,
* table:body elements, which is not a valid attribute for the element.
*/
KRITAFLAKE_EXPORT QString fixTableTemplateCellStyleName(const KoXmlElement &e);
/**
* LibreOffice used to have a bug with handling of z command in svg path.
* This resulted in broken marker path used (and copied also to Calligra).
* This methods substitutes known old marker paths with the latest (fixed)
* path variant.
*/
KRITAFLAKE_EXPORT void fixMarkerPath(QString &path);
}
#endif /* KOODFWORKAROUND_H */
diff --git a/libs/flake/KoParameterShape.cpp b/libs/flake/KoParameterShape.cpp
index 684e5a09ee..2c41e93d60 100644
--- a/libs/flake/KoParameterShape.cpp
+++ b/libs/flake/KoParameterShape.cpp
@@ -1,165 +1,164 @@
/* This file is part of the KDE project
Copyright (C) 2006 Thorsten Zachmann
Copyright (C) 2007, 2009 Thomas Zander
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KoParameterShape.h"
#include "KoParameterShape_p.h"
#include
#include
#include
KoParameterShapePrivate::KoParameterShapePrivate(KoParameterShape *shape)
: KoPathShapePrivate(shape),
parametric(true)
{
}
KoParameterShapePrivate::KoParameterShapePrivate(const KoParameterShapePrivate &rhs, KoParameterShape *q)
: KoPathShapePrivate(rhs, q),
handles(rhs.handles)
{
}
KoParameterShape::KoParameterShape()
: KoPathShape(new KoParameterShapePrivate(this))
{
}
KoParameterShape::KoParameterShape(KoParameterShapePrivate *dd)
: KoPathShape(dd)
{
}
KoParameterShape::~KoParameterShape()
{
}
void KoParameterShape::moveHandle(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers)
{
Q_D(KoParameterShape);
if (handleId >= d->handles.size()) {
warnFlake << "handleId out of bounds";
return;
}
update();
// function to do special stuff
moveHandleAction(handleId, documentToShape(point), modifiers);
-
updatePath(size());
update();
}
int KoParameterShape::handleIdAt(const QRectF & rect) const
{
Q_D(const KoParameterShape);
int handle = -1;
for (int i = 0; i < d->handles.size(); ++i) {
if (rect.contains(d->handles.at(i))) {
handle = i;
break;
}
}
return handle;
}
QPointF KoParameterShape::handlePosition(int handleId) const
{
Q_D(const KoParameterShape);
return d->handles.value(handleId);
}
void KoParameterShape::paintHandles(KisHandlePainterHelper &handlesHelper)
{
Q_D(KoParameterShape);
QList::const_iterator it(d->handles.constBegin());
for (; it != d->handles.constEnd(); ++it) {
handlesHelper.drawGradientHandle(*it);
}
}
void KoParameterShape::paintHandle(KisHandlePainterHelper &handlesHelper, int handleId)
{
Q_D(KoParameterShape);
handlesHelper.drawGradientHandle(d->handles[handleId]);
}
void KoParameterShape::setSize(const QSizeF &newSize)
{
Q_D(KoParameterShape);
QTransform matrix(resizeMatrix(newSize));
for (int i = 0; i < d->handles.size(); ++i) {
d->handles[i] = matrix.map(d->handles[i]);
}
KoPathShape::setSize(newSize);
}
QPointF KoParameterShape::normalize()
{
Q_D(KoParameterShape);
QPointF offset(KoPathShape::normalize());
QTransform matrix;
matrix.translate(-offset.x(), -offset.y());
for (int i = 0; i < d->handles.size(); ++i) {
d->handles[i] = matrix.map(d->handles[i]);
}
return offset;
}
bool KoParameterShape::isParametricShape() const
{
Q_D(const KoParameterShape);
return d->parametric;
}
void KoParameterShape::setParametricShape(bool parametric)
{
Q_D(KoParameterShape);
d->parametric = parametric;
update();
}
QList KoParameterShape::handles() const
{
Q_D(const KoParameterShape);
return d->handles;
}
void KoParameterShape::setHandles(const QList &handles)
{
Q_D(KoParameterShape);
d->handles = handles;
d->shapeChanged(ParameterChanged);
}
int KoParameterShape::handleCount() const
{
Q_D(const KoParameterShape);
return d->handles.count();
}
diff --git a/libs/flake/KoShapeAnchor.h b/libs/flake/KoShapeAnchor.h
index 8a3e700416..06b3465e7e 100644
--- a/libs/flake/KoShapeAnchor.h
+++ b/libs/flake/KoShapeAnchor.h
@@ -1,262 +1,262 @@
/* This file is part of the KDE project
* Copyright (C) 2007, 2009 Thomas Zander
* Copyright (C) 2011 Matus Hanzes
* Copyright (C) 2013 C. Boemann
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOSHAPEANCHOR_H
#define KOSHAPEANCHOR_H
#include "kritaflake_export.h"
class KoShape;
-class KoXmlElement;
+#include
class KoShapeLoadingContext;
class KoShapeSavingContext;
class KoShapeAnchorPrivate;
class QTextDocument;
class QPointF;
class QString;
/**
* This class is the object that explains how a shape is anchored to something.
*
* The anchored shape will be positioned (in supporting applications) based on the properties
* defined in this class.
*
* This class can be used in three different ways:
* -page anchor
* -as-char
* -char, paragraph anchor
*
* If it's a page anchor it just provide the info about how the shape relates to a page with a specific
* page number.
*
* For the other types of anchoring it has to have a TextLocation in a QTextDocument. This TextLocation
* can either be an inline character (type as-char) or a position (type char or paragraph) The
* KoShapeAnchor and TextLocation connects the anchored-shape to the text flow so the anchored shape
* can be repositioned on the canvas if new text is inserted or removed before the anchor character.
*
* For as-char, char and paragraph use cases:
* @see KoAnchorInlineObject
* @see KoAnchorTextRange
* which are both implemented as subclasses of TextLocation
*
* The position of the shape relative to the anchor is called the offset. It's loaded by loadOdf().
* @see PlacementStrategy for more information about the layout of anchors/shapes.
*/
class KRITAFLAKE_EXPORT KoShapeAnchor
{
public:
/**
* This class is an interface that positions the shape linked to text anchor
*/
class PlacementStrategy {
public:
PlacementStrategy(){};
virtual ~PlacementStrategy(){};
/**
* Reparent the anchored shape to not have a parent shape container (and model)
*
*/
virtual void detachFromModel() = 0;
/**
* Reparent the anchored shape under an appropriate shape container (and model)
*
* If needed, it changes the parent KoShapeContainerModel and KoShapeContainer of the anchored
* shape.
*/
virtual void updateContainerModel() = 0;
};
class TextLocation {
public:
TextLocation(){};
virtual ~TextLocation(){};
virtual const QTextDocument *document() const = 0;
virtual int position() const = 0;
};
enum HorizontalPos {
HCenter,
HFromInside,
HFromLeft,
HInside,
HLeft,
HOutside,
HRight
};
enum HorizontalRel { //NOTE: update KWAnchoringProperties if you change this
HChar,
HPage,
HPageContent,
HPageStartMargin,
HPageEndMargin,
HFrame,
HFrameContent,
HFrameEndMargin,
HFrameStartMargin,
HParagraph,
HParagraphContent,
HParagraphEndMargin,
HParagraphStartMargin
};
enum VerticalPos {
VBelow,
VBottom,
VFromTop,
VMiddle,
VTop
};
enum VerticalRel { //NOTE: update KWAnchoringProperties if you change this
VBaseline,
VChar,
VFrame,
VFrameContent,
VLine,
VPage,
VPageContent,
VParagraph,
VParagraphContent,
VText
};
enum AnchorType {
AnchorAsCharacter,
AnchorToCharacter,
AnchorParagraph,
AnchorPage
};
/**
* Constructor for an in-place anchor.
* @param shape the anchored shape that this anchor links to.
*/
explicit KoShapeAnchor(KoShape *shape);
virtual ~KoShapeAnchor();
/**
* Return the shape that is linked to from the text anchor.
*/
KoShape *shape() const;
/**
* Returns the type of the anchor.
*
* The text:anchor-type attribute specifies how a frame is bound to a
* text document. The anchor position is the point at which a frame is
* bound to a text document. The defined values for the text:anchor-type
* attribute are;
*
* - as-char
* There is no anchor position. The drawing shape behaves like a
* character.
* - char
* The character after the drawing shape element.
* - frame
* The parent text box that the current drawing shape element is
* contained in.
* FIXME we don't support type frame
* - page
* The page that has the same physical page number as the value of the
* text:anchor-page-number attribute that is attached to the drawing
* shape element.
* - paragraph
* The paragraph that the current drawing shape element is contained in.
*/
AnchorType anchorType() const;
/**
* Set how the anchor behaves
*/
void setAnchorType(AnchorType type);
/// set the current vertical-pos
void setHorizontalPos(HorizontalPos);
/// return the current vertical-pos
HorizontalPos horizontalPos() const;
/// set the current vertical-rel
void setHorizontalRel(HorizontalRel);
/// return the current vertical-rel
HorizontalRel horizontalRel() const;
/// set the current horizontal-pos
void setVerticalPos(VerticalPos);
/// return the current horizontal-pos
VerticalPos verticalPos() const;
/// set the current horizontal-rel
void setVerticalRel(VerticalRel);
/// return the current horizontal-rel
VerticalRel verticalRel() const;
/// return the wrap influence on position
QString wrapInfluenceOnPosition() const;
/// return if flow-with-text (odf attribute)
bool flowWithText() const;
/// return the page number of the shape (valid with page anchoring, -1 indicates auto).
int pageNumber() const;
/// return the offset of the shape from the anchor.
const QPointF &offset() const;
/// set the new offset of the shape. Causes a new layout soon.
void setOffset(const QPointF &offset);
/// Load the additional attributes.
/// This will also make the shape invisible so it doesn't mess up any layout
/// before it's ready to be placed where it belongs
/// The textlayout should make it visible again
bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context);
/// Save the additional attributes.
void saveOdf(KoShapeSavingContext &context) const;
/// Get extra data structure that is what is actually inside a text document
TextLocation *textLocation() const;
/// Set extra data structure that is what is actually inside a text document
/// We do NOT take ownership (may change in the future)
void setTextLocation(TextLocation *textLocation);
/// Get placement strategy which is used to position shape linked to text anchor
PlacementStrategy *placementStrategy() const;
/// Set placement strategy which is used to position shape linked to text anchor
/// We take owner ship and will make sure the strategy is deleted
void setPlacementStrategy(PlacementStrategy *placementStrategy);
private:
class Private;
Private * const d;
};
#endif
diff --git a/libs/flake/KoTextShapeDataBase.h b/libs/flake/KoTextShapeDataBase.h
index e217ff7875..36f4aab9a8 100644
--- a/libs/flake/KoTextShapeDataBase.h
+++ b/libs/flake/KoTextShapeDataBase.h
@@ -1,145 +1,145 @@
/* This file is part of the KDE project
* Copyright (C) 2006-2010 Thomas Zander
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KOTEXTSHAPEDATABASE_H
#define KOTEXTSHAPEDATABASE_H
#include "kritaflake_export.h"
#include "KoShapeUserData.h"
class KoTextShapeDataBasePrivate;
-class KoXmlElement;
+#include
class KoShapeLoadingContext;
class KoShapeSavingContext;
class KoGenStyle;
struct KoInsets;
class QTextDocument;
/**
* \internal
*/
class KRITAFLAKE_EXPORT KoTextShapeDataBase : public KoShapeUserData
{
Q_OBJECT
public:
/// constructor
KoTextShapeDataBase();
virtual ~KoTextShapeDataBase();
/// return the document
QTextDocument *document() const;
/**
* Set the margins that will make the shapes text area smaller.
* The shape that owns this textShapeData object will layout text in an area
* confined by the shape size made smaller by the margins set here.
* @param margins the margins that shrink the text area.
*/
void setShapeMargins(const KoInsets &margins);
/**
* returns the currently set margins for the shape.
*/
KoInsets shapeMargins() const;
/**
* Load the text from ODF.
*/
virtual bool loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
/**
* Save the text to ODF.
*/
virtual void saveOdf(KoShapeSavingContext &context, int from = 0, int to = -1) const = 0;
/**
* Load the style of the element
*
* This method is used to load the style in case the TextShape is used as TOS. In this case
* the paragraph style of the shape e.g. a custom-shape needs to be applied before we load the
* text so all looks as it should look.
*/
virtual void loadStyle(const KoXmlElement &element, KoShapeLoadingContext &context) = 0;
/**
* Save the style of the element
*
* This method save the style in case the TextShape is used as TOS. In this case the paragraph
* style of the shape e.g. a custom-shape needs to be saved with the style of the shape.
*/
virtual void saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const = 0;
/** Sets the vertical alignment of all the text inside the shape. */
void setVerticalAlignment(Qt::Alignment alignment);
/** Returns the vertical alignment of all the text in the shape */
Qt::Alignment verticalAlignment() const;
/**
* Enum to describe the text document's automatic resizing behaviour.
*/
enum ResizeMethod {
/// Resize the shape to fit the content. This makes sure that the text shape takes op
/// only as much space as absolutely necessary to fit the entire text into its boundaries.
AutoResize,
/// Specifies whether or not to automatically increase the width of the drawing object
/// if text is added to fit the entire width of the text into its boundaries.
/// Compared to AutoResize above this only applied to the width whereas the height is
/// not resized. Also this only grows but does not shrink again if text is removed again.
AutoGrowWidth,
/// Specifies whether or not to automatically increase the height of the drawing object
/// if text is added to fit the entire height of the text into its boundaries.
AutoGrowHeight,
/// This combines the AutoGrowWidth and AutoGrowHeight and automatically increase width
/// and height to fit the entire text into its boundaries.
AutoGrowWidthAndHeight,
/// Shrink the content displayed within the shape to match into the shape's boundaries. This
/// will scale the content down as needed to display the whole document.
ShrinkToFitResize,
/// Deactivates auto-resizing. This is the default resizing method.
NoResize
};
/**
* Specifies how the document should be resized upon a change in the document.
*
* If auto-resizing is turned on, text will not be wrapped unless enforced by e.g. a newline.
*
* By default, NoResize is set.
*/
void setResizeMethod(ResizeMethod method);
/**
* Returns the auto-resizing mode. By default, this is NoResize.
*
* @see setResizeMethod
*/
ResizeMethod resizeMethod() const;
protected:
/// constructor
KoTextShapeDataBase(KoTextShapeDataBasePrivate *);
KoTextShapeDataBasePrivate *d_ptr;
private:
Q_DECLARE_PRIVATE(KoTextShapeDataBase)
};
#endif
diff --git a/libs/flake/KoUnavailShape.cpp b/libs/flake/KoUnavailShape.cpp
index 240a9d2ba1..3e407cb220 100644
--- a/libs/flake/KoUnavailShape.cpp
+++ b/libs/flake/KoUnavailShape.cpp
@@ -1,656 +1,657 @@
/* This file is part of the KDE project
*
* Copyright (C) 2010-2011 Inge Wallin
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
// Own
#include "KoUnavailShape.h"
// Qt
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Calligra
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoShapeLoadingContext.h"
#include "KoShapeSavingContext.h"
#include "SimpleShapeContainerModel.h"
#include "KoShapeBackground.h"
#include
// The XML of a frame looks something like this:
-//
+//
// 1.
// 2.
// 3.
// 4.
//
// or
-//
+//
// 1.
-// 2. ...inline xml here...
+// 2. ...inline xml here...
// 3.
// 4.
//
// We define each Xml statement on lines 2 and 3 above as an "object".
// (Strictly only the first child element is an object in the ODF sense,
// but we have to have some terminology here.)
-//
+//
// In an ODF frame, only the first line, i.e. the first object
// contains the real contents. All the rest of the objects are used /
// shown if we cannot handle the first one. The most common cases are
// that there is only one object inside the frame OR that there are 2
// and the 2nd is a picture.
//
// Sometimes, e.g. in the case of an embedded document, the reference
// points not to a file but to a directory structure inside the ODF
-// store.
+// store.
//
// When we load and save in the UnavailShape, we have to be general
// enough to cover all possible cases of references and inline XML,
// embedded files and embedded directory structures.
//
// We also have to be careful because we cannot reuse the object names
// that are in the original files when saving. Instead we need to
// create new object names because the ones that were used in the
// original file may already be used by other embedded files/objects
// that are saved by other shapes.
//
// FIXME: There should only be ONE place where new object / file names
// are generated, not 2(?) like there are now:
// KoEmbeddedDocumentSaver and the KoImageCollection.
//
// An ObjectEntry is used to store information about objects in the
// frame, as defined above.
struct ObjectEntry {
QByteArray objectXmlContents; // the XML tree in the object
QString objectName; // object name in the frame without "./"
// This is extracted from objectXmlContents.
bool isDir;
KoOdfManifestEntry *manifestEntry; // manifest entry for the above.
};
// A FileEntry is used to store information about embedded files
// inside (i.e. referred to by) an object.
struct FileEntry {
QString path; // Normalized filename, i.e. without "./".
QString mimeType;
bool isDir;
QByteArray contents;
};
class KoUnavailShape::Private
{
public:
Private(KoUnavailShape* qq);
~Private();
void draw(QPainter &painter) const;
void drawNull(QPainter &painter) const;
void storeObjects(const KoXmlElement &element);
void storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer,
ObjectEntry *object, QHash &unknownNamespaces);
void storeFile(const QString &filename, KoShapeLoadingContext &context);
QByteArray loadFile(const QString &filename, KoShapeLoadingContext &context);
// Objects inside the frame. For each file, we store:
// - The XML code for the object
// - Any embedded files (names, contents) that are referenced by xlink:href
// - Whether they are directories, i.e. if they contain a file tree and not just one file.
// - The manifest entries
QList objectEntries;
// Embedded files
QList embeddedFiles; // List of embedded files.
// Some cached values.
QPixmap questionMark;
QPixmap pixmapPreview;
QSvgRenderer *scalablePreview;
KoUnavailShape* q;
};
KoUnavailShape::Private::Private(KoUnavailShape* qq)
: scalablePreview(new QSvgRenderer())
, q(qq)
{
// Get the question mark "icon".
questionMark.load(":/questionmark.png");
}
KoUnavailShape::Private::~Private()
{
qDeleteAll(objectEntries);
qDeleteAll(embeddedFiles);
// It's a QObject, but we haven't parented it.
delete(scalablePreview);
}
// ----------------------------------------------------------------
// The main class
KoUnavailShape::KoUnavailShape()
: KoFrameShape( "", "" )
, KoShapeContainer(new SimpleShapeContainerModel())
, d(new Private(this))
{
setShapeId(KoUnavailShape_SHAPEID);
// Default size of the shape.
KoShape::setSize( QSizeF( CM_TO_POINT( 5 ), CM_TO_POINT( 3 ) ) );
}
KoUnavailShape::~KoUnavailShape()
{
delete d;
}
void KoUnavailShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
{
applyConversion(painter, converter);
// If the frame is empty, just draw a background.
debugFlake << "Number of objects:" << d->objectEntries.size();
if (d->objectEntries.isEmpty()) {
// But... only try to draw the background if there's one such
if (background()) {
QPainterPath p;
p.addRect(QRectF(QPointF(), size()));
background()->paint(painter, converter, paintContext, p);
}
} else {
if(shapes().isEmpty()) {
d->draw(painter);
}
}
}
void KoUnavailShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
Q_UNUSED(painter);
Q_UNUSED(converter);
}
void KoUnavailShape::Private::draw(QPainter &painter) const
{
painter.save();
painter.setRenderHint(QPainter::Antialiasing);
// Run through the previews in order of preference. Draw a placeholder
// questionmark if there is no preview available for rendering.
if (scalablePreview->isValid()) {
QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height());
scalablePreview->render(&painter, bounds);
}
else if (!pixmapPreview.isNull()) {
QRect bounds(0, 0, q->boundingRect().width(), q->boundingRect().height());
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.drawPixmap(bounds, pixmapPreview);
}
else if (q->shapes().isEmpty()) {
// Draw a nice question mark with a frame around it if there
// is no other preview image. If there is a contained image
// shape, we don't need to draw anything.
// Get the question mark "icon".
// FIXME: We should be able to use d->questionMark here.
QPixmap questionMark;
questionMark.load(":/questionmark.png");
// The size of the image is:
// - the size of the shape if shapesize < 2cm
// - 2 cm if 2cm <= shapesize <= 8cm
// - shapesize / 4 if shapesize > 8cm
qreal width = q->size().width();
qreal height = q->size().height();
qreal picSize = CM_TO_POINT(2); // Default size is 2 cm.
if (width < CM_TO_POINT(2) || height < CM_TO_POINT(2))
picSize = qMin(width, height);
else if (width > CM_TO_POINT(8) && height > CM_TO_POINT(8))
picSize = qMin(width, height) / qreal(4.0);
painter.drawPixmap((width - picSize) / qreal(2.0), (height - picSize) / qreal(2.0),
picSize, picSize, questionMark);
// Draw a gray rectangle around the shape.
painter.setPen(QPen(QColor(172, 196, 206), 0));
painter.drawRect(QRectF(QPointF(0,0), q->size()));
}
painter.restore();
}
void KoUnavailShape::Private::drawNull(QPainter &painter) const
{
QRectF rect(QPointF(0,0), q->size());
painter.save();
// Draw a simple cross in a rectangle just to indicate that there is something here.
painter.drawLine(rect.topLeft(), rect.bottomRight());
painter.drawLine(rect.bottomLeft(), rect.topRight());
painter.restore();
}
// ----------------------------------------------------------------
// Loading and Saving
void KoUnavailShape::saveOdf(KoShapeSavingContext & context) const
{
debugFlake << "START SAVING ##################################################";
KoEmbeddedDocumentSaver &fileSaver = context.embeddedSaver();
KoXmlWriter &writer = context.xmlWriter();
writer.startElement("draw:frame");
// See also loadOdf() in loadOdfAttributes.
saveOdfAttributes( context, OdfAllAttributes );
// Write the stored XML to the file, but don't reuse object names.
int lap = 0;
QString newName;
foreach (const ObjectEntry *object, d->objectEntries) {
QByteArray xmlArray(object->objectXmlContents);
QString objectName(object->objectName); // Possibly empty.
KoOdfManifestEntry *manifestEntry(object->manifestEntry);
// Create a name for this object. If this is not the first
// object, i.e. a replacement object (most likely a picture),
// then reuse the name but put it in ReplacementObjects.
if (++lap == 1) {
// The first lap in the loop is the actual object. All
// other laps are replacement objects.
newName = fileSaver.getFilename("Object ");
}
else if (lap == 2) {
newName = "ObjectReplacements/" + newName;
}
else
// FIXME: what should replacement 2 and onwards be called?
newName = newName + "_";
// If there was a previous object name, replace it with the new one.
if (!objectName.isEmpty() && manifestEntry) {
// FIXME: We must make a copy of the byte array here because
// otherwise we won't be able to save > 1 time.
xmlArray.replace(objectName.toLatin1(), newName.toLatin1());
}
writer.addCompleteElement(xmlArray.data());
// If the objectName is empty, this may be inline XML.
// If so, we are done now.
if (objectName.isEmpty() || !manifestEntry) {
continue;
}
// Save embedded files for this object.
foreach (FileEntry *entry, d->embeddedFiles) {
QString fileName(entry->path);
// If we found a file for this object, we need to write it
// but with the new object name instead of the old one.
if (!fileName.startsWith(objectName))
continue;
debugFlake << "Object name: " << objectName << "newName: " << newName
<< "filename: " << fileName << "isDir: " << entry->isDir;
fileName.replace(objectName, newName);
fileName.prepend("./");
debugFlake << "New filename: " << fileName;
// FIXME: Check if we need special treatment of directories.
fileSaver.saveFile(fileName, entry->mimeType.toLatin1(), entry->contents);
}
// Write the manifest entry for the object itself. If it's a
// file, the manifest is already written by saveFile, so skip
// it here.
if (object->isDir) {
fileSaver.saveManifestEntry(newName + '/', manifestEntry->mediaType(),
manifestEntry->version());
}
}
writer.endElement(); // draw:frame
}
bool KoUnavailShape::loadOdf(const KoXmlElement &frameElement, KoShapeLoadingContext &context)
{
debugFlake << "START LOADING ##################################################";
//debugFlake << "Loading ODF frame in the KoUnavailShape. Element = "
// << frameElement.tagName();
loadOdfAttributes(frameElement, context, OdfAllAttributes);
// NOTE: We cannot use loadOdfFrame() because we want to save all
// the things inside the frame, not just one of them, like
// loadOdfFrame() provides.
// Get the manifest.
QList manifest = context.odfLoadingContext().manifestEntries();
#if 0 // Enable to show all manifest entries.
debugFlake << "MANIFEST: ";
foreach (KoOdfManifestEntry *entry, manifest) {
debugFlake << entry->mediaType() << entry->fullPath() << entry->version();
}
#endif
// 1. Get the XML contents of the objects from the draw:frame. As
// a side effect, this extracts the object names from all
// xlink:href and stores them into d->objectNames. The saved
// xml contents itself is saved into d->objectXmlContents
// (QByteArray) so we can save it back from saveOdf().
d->storeObjects(frameElement);
#if 1
// Debug only
debugFlake << "----------------------------------------------------------------";
debugFlake << "After storeObjects():";
foreach (ObjectEntry *object, d->objectEntries) {
debugFlake << "objectXmlContents: " << object->objectXmlContents
<< "objectName: " << object->objectName;
// Note: at this point, isDir and manifestEntry are not set.
#endif
}
// 2. Loop through the objects that were found in the frame and
// save all the files associated with them. Some of the
// objects are files, and some are directories. The
// directories are searched and the files within are saved as
// well.
//
// In this loop, isDir and manifestEntry of each ObjectEntry are set.
bool foundPreview = false;
foreach (ObjectEntry *object, d->objectEntries) {
QString objectName = object->objectName;
if (objectName.isEmpty())
continue;
debugFlake << "Storing files for object named:" << objectName;
// Try to find out if the entry is a directory.
// If the object is a directory, then save all the files
// inside it, otherwise save the file as it is.
QString dirName = objectName + '/';
bool isDir = !context.odfLoadingContext().mimeTypeForPath(dirName).isEmpty();
if (isDir) {
// A directory: the files can be found in the manifest.
foreach (KoOdfManifestEntry *entry, manifest) {
if (entry->fullPath() == dirName)
continue;
if (entry->fullPath().startsWith(dirName)) {
d->storeFile(entry->fullPath(), context);
}
}
}
else {
// A file: save it.
d->storeFile(objectName, context);
}
// Get the manifest entry for this object.
KoOdfManifestEntry *entry = 0;
QString entryName = isDir ? dirName : objectName;
for (int j = 0; j < manifest.size(); ++j) {
KoOdfManifestEntry *temp = manifest.value(j);
if (temp->fullPath() == entryName) {
entry = new KoOdfManifestEntry(*temp);
break;
}
}
object->isDir = isDir;
object->manifestEntry = entry;
// If we have not already found a preview in previous times
// through the loop, then see if this one may be a preview.
if (!foundPreview) {
debugFlake << "Attempting to load preview from " << objectName;
QByteArray previewData = d->loadFile(objectName, context);
// Check to see if we know the mimetype for this entry. Specifically:
// 1. Check to see if the item is a loadable SVG file
// FIXME: Perhaps check in the manifest first? But this
// seems to work well.
d->scalablePreview->load(previewData);
if (d->scalablePreview->isValid()) {
debugFlake << "Found scalable preview image!";
d->scalablePreview->setViewBox(d->scalablePreview->boundsOnElement("svg"));
foundPreview = true;
continue;
}
// 2. Otherwise check to see if it's a loadable pixmap file
d->pixmapPreview.loadFromData(previewData);
if (!d->pixmapPreview.isNull()) {
debugFlake << "Found pixel based preview image!";
foundPreview = true;
}
}
}
#if 0 // Enable to get more detailed debug messages
debugFlake << "Object manifest entries:";
for (int i = 0; i < d->manifestEntries.size(); ++i) {
KoOdfManifestEntry *entry = d->manifestEntries.value(i);
debugFlake << i << ":" << entry;
if (entry)
debugFlake << entry->fullPath() << entry->mediaType() << entry->version();
else
debugFlake << "--";
}
debugFlake << "END LOADING ####################################################";
#endif
return true;
}
// Load the actual contents inside the frame.
bool KoUnavailShape::loadOdfFrameElement(const KoXmlElement & /*element*/,
KoShapeLoadingContext &/*context*/)
{
return true;
}
// ----------------------------------------------------------------
// Private functions
void KoUnavailShape::Private::storeObjects(const KoXmlElement &element)
{
// Loop through all the child elements of the draw:frame and save them.
KoXmlNode n = element.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
debugFlake << "In draw:frame, node =" << n.nodeName();
// This disregards #text, but that's not in the spec anyway so
// it doesn't need to be saved.
if (!n.isElement())
continue;
KoXmlElement el = n.toElement();
ObjectEntry *object = new ObjectEntry;
QByteArray contentsTmp;
QBuffer buffer(&contentsTmp); // the member
KoXmlWriter writer(&buffer);
// 1. Find out the objectName
// Save the normalized filename, i.e. without a starting "./".
// An empty string is saved if no name is found.
QString name = el.attributeNS(KoXmlNS::xlink, "href", QString());
if (name.startsWith(QLatin1String("./")))
name.remove(0, 2);
object->objectName = name;
// 2. Copy the XML code.
QHash unknownNamespaces;
storeXmlRecursive(el, writer, object, unknownNamespaces);
object->objectXmlContents = contentsTmp;
// 3, 4: the isDir and manifestEntry members are not set here,
// but initialize them anyway. .
object->isDir = false; // Has to be initialized to something.
object->manifestEntry = 0;
objectEntries.append(object);
}
}
void KoUnavailShape::Private::storeXmlRecursive(const KoXmlElement &el, KoXmlWriter &writer,
ObjectEntry *object, QHash &unknownNamespaces)
{
// Start the element;
// keep the name in a QByteArray so that it stays valid until end element is called.
const QByteArray name(el.nodeName().toLatin1());
writer.startElement(name.constData());
+#ifndef KOXML_USE_QDOM
// Copy all the attributes, including namespaces.
QList< QPair > attributeNames = el.attributeFullNames();
for (int i = 0; i < attributeNames.size(); ++i) {
QPair attrPair(attributeNames.value(i));
if (attrPair.first.isEmpty()) {
writer.addAttribute(attrPair.second.toLatin1(), el.attribute(attrPair.second));
}
else {
// This somewhat convoluted code is because we need the
// namespace, not the namespace URI.
QString nsShort = KoXmlNS::nsURI2NS(attrPair.first.toLatin1());
// in case we don't find the namespace in our list create a own one and use that
// so the document created on saving is valid.
if (nsShort.isEmpty()) {
nsShort = unknownNamespaces.value(attrPair.first);
if (nsShort.isEmpty()) {
nsShort = QString("ns%1").arg(unknownNamespaces.size() + 1);
unknownNamespaces.insert(attrPair.first, nsShort);
}
QString name = QString("xmlns:") + nsShort;
writer.addAttribute(name.toLatin1(), attrPair.first.toLatin1());
}
QString attr(nsShort + ':' + attrPair.second);
writer.addAttribute(attr.toLatin1(), el.attributeNS(attrPair.first,
attrPair.second));
}
}
-
+#endif
// Child elements
// Loop through all the child elements of the draw:frame.
KoXmlNode n = el.firstChild();
for (; !n.isNull(); n = n.nextSibling()) {
if (n.isElement()) {
storeXmlRecursive(n.toElement(), writer, object, unknownNamespaces);
}
else if (n.isText()) {
writer.addTextNode(n.toText().data()/*.toUtf8()*/);
}
}
// End the element
writer.endElement();
}
/**
* This function stores the embedded file in an internal store - it does not save files to disk,
* and thus it is named in this manner, to avoid the function being confused with functions which
* save files to disk.
*/
void KoUnavailShape::Private::storeFile(const QString &fileName, KoShapeLoadingContext &context)
{
debugFlake << "Saving file: " << fileName;
// Directories need to be saved too, but they don't have any file contents.
if (fileName.endsWith('/')) {
FileEntry *entry = new FileEntry;
entry->path = fileName;
entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path);
entry->isDir = true;
embeddedFiles.append(entry);
}
QByteArray fileContent = loadFile(fileName, context);
if (fileContent.isNull())
return;
// Actually store the file in the list.
FileEntry *entry = new FileEntry;
entry->path = fileName;
if (entry->path.startsWith(QLatin1String("./")))
entry->path.remove(0, 2);
entry->mimeType = context.odfLoadingContext().mimeTypeForPath(entry->path);
entry->isDir = false;
entry->contents = fileContent;
embeddedFiles.append(entry);
debugFlake << "File length: " << fileContent.size();
}
QByteArray KoUnavailShape::Private::loadFile(const QString &fileName, KoShapeLoadingContext &context)
{
// Can't load a file which is a directory, return an invalid QByteArray
if (fileName.endsWith('/'))
return QByteArray();
KoStore *store = context.odfLoadingContext().store();
QByteArray fileContent;
if (!store->open(fileName)) {
store->close();
return QByteArray();
}
int fileSize = store->size();
fileContent = store->read(fileSize);
store->close();
//debugFlake << "File content: " << fileContent;
return fileContent;
}
diff --git a/libs/flake/svg/SvgCssHelper.h b/libs/flake/svg/SvgCssHelper.h
index a3f26dfc32..5ddd5b3141 100644
--- a/libs/flake/svg/SvgCssHelper.h
+++ b/libs/flake/svg/SvgCssHelper.h
@@ -1,48 +1,48 @@
/* This file is part of the KDE project
* Copyright (C) 2009 Jan Hambrecht
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef SVGCSSHELPER_H
#define SVGCSSHELPER_H
#include
-class KoXmlElement;
+#include
class SvgCssHelper
{
public:
SvgCssHelper();
~SvgCssHelper();
/// Parses css style sheet in given xml element
void parseStylesheet(const KoXmlElement &);
/**
* Matches css styles to given xml element and returns them
* @param element the element to match styles for
* @return list of matching css styles sorted by priority
*/
QStringList matchStyles(const KoXmlElement &element) const;
private:
class Private;
Private * const d;
};
#endif // SVGCSSHELPER_H
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index 970730f12f..35de6b9972 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1,1544 +1,1545 @@
/* This file is part of the KDE project
* Copyright (C) 2002-2005,2007 Rob Buis
* Copyright (C) 2002-2004 Nicolas Goutte
* Copyright (C) 2005-2006 Tim Beaulen
* Copyright (C) 2005-2009 Jan Hambrecht
* Copyright (C) 2005,2007 Thomas Zander
* Copyright (C) 2006-2007 Inge Wallin
* Copyright (C) 2007-2008,2010 Thorsten Zachmann
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "SvgParser.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
#include "KoFilterEffectStack.h"
#include "KoFilterEffectLoadingContext.h"
#include
#include
#include
#include "SvgUtil.h"
#include "SvgShape.h"
#include "SvgGraphicContext.h"
#include "SvgFilterHelper.h"
#include "SvgGradientHelper.h"
#include "SvgClipPathHelper.h"
#include "parsers/SvgTransformParser.h"
#include "kis_pointer_utils.h"
#include
#include
#include "kis_debug.h"
#include "kis_global.h"
SvgParser::SvgParser(KoDocumentResourceManager *documentResourceManager)
: m_context(documentResourceManager)
, m_documentResourceManager(documentResourceManager)
{
}
SvgParser::~SvgParser()
{
}
void SvgParser::setXmlBaseDir(const QString &baseDir)
{
m_context.setInitialXmlBaseDir(baseDir);
setFileFetcher(
[this](const QString &name) {
const QString fileName = m_context.xmlBaseDir() + QDir::separator() + name;
QFile file(fileName);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(file.exists(), QByteArray());
file.open(QIODevice::ReadOnly);
return file.readAll();
});
}
void SvgParser::setResolution(const QRectF boundsInPixels, qreal pixelsPerInch)
{
KIS_ASSERT(!m_context.currentGC());
m_context.pushGraphicsContext();
m_context.currentGC()->pixelsPerInch = pixelsPerInch;
const qreal scale = 72.0 / pixelsPerInch;
const QTransform t = QTransform::fromScale(scale, scale);
m_context.currentGC()->currentBoundingBox = boundsInPixels;
m_context.currentGC()->matrix = t;
}
QList SvgParser::shapes() const
{
return m_shapes;
}
// Helper functions
// ---------------------------------------------------------------------------------------
SvgGradientHelper* SvgParser::findGradient(const QString &id)
{
SvgGradientHelper *result = 0;
// check if gradient was already parsed, and return it
if (m_gradients.contains(id)) {
result = &m_gradients[ id ];
}
// check if gradient was stored for later parsing
if (!result && m_context.hasDefinition(id)) {
const KoXmlElement &e = m_context.definition(id);
if (e.tagName().contains("Gradient")) {
result = parseGradient(m_context.definition(id));
}
}
return result;
}
QSharedPointer SvgParser::findPattern(const QString &id, const KoShape *shape)
{
QSharedPointer result;
// check if gradient was stored for later parsing
if (m_context.hasDefinition(id)) {
const KoXmlElement &e = m_context.definition(id);
if (e.tagName() == "pattern") {
result = parsePattern(m_context.definition(id), shape);
}
}
return result;
}
SvgFilterHelper* SvgParser::findFilter(const QString &id, const QString &href)
{
// check if filter was already parsed, and return it
if (m_filters.contains(id))
return &m_filters[ id ];
// check if filter was stored for later parsing
if (!m_context.hasDefinition(id))
return 0;
const KoXmlElement &e = m_context.definition(id);
- if (e.childNodesCount() == 0) {
+ if (KoXml::childNodesCount(e) == 0) {
QString mhref = e.attribute("xlink:href").mid(1);
if (m_context.hasDefinition(mhref))
return findFilter(mhref, id);
else
return 0;
} else {
// ok parse filter now
if (! parseFilter(m_context.definition(id), m_context.definition(href)))
return 0;
}
// return successfully parsed filter or 0
QString n;
if (href.isEmpty())
n = id;
else
n = href;
if (m_filters.contains(n))
return &m_filters[ n ];
else
return 0;
}
SvgClipPathHelper* SvgParser::findClipPath(const QString &id)
{
return m_clipPaths.contains(id) ? &m_clipPaths[id] : 0;
}
// Parsing functions
// ---------------------------------------------------------------------------------------
qreal SvgParser::parseUnit(const QString &unit, bool horiz, bool vert, const QRectF &bbox)
{
return SvgUtil::parseUnit(m_context.currentGC(), unit, horiz, vert, bbox);
}
qreal SvgParser::parseUnitX(const QString &unit)
{
return SvgUtil::parseUnitX(m_context.currentGC(), unit);
}
qreal SvgParser::parseUnitY(const QString &unit)
{
return SvgUtil::parseUnitY(m_context.currentGC(), unit);
}
qreal SvgParser::parseUnitXY(const QString &unit)
{
return SvgUtil::parseUnitXY(m_context.currentGC(), unit);
}
qreal SvgParser::parseAngular(const QString &unit)
{
return SvgUtil::parseUnitAngular(m_context.currentGC(), unit);
}
SvgGradientHelper* SvgParser::parseGradient(const KoXmlElement &e)
{
// IMPROVEMENTS:
// - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again.
// - A gradient inherits attributes it does not have from the referencing gradient.
// - Gradients with no color stops have no fill or stroke.
// - Gradients with one color stop have a solid color.
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc) return 0;
SvgGradientHelper gradHelper;
QString gradientId = e.attribute("id");
if (gradientId.isEmpty()) return 0;
// check if we have this gradient already parsed
// copy existing gradient if it exists
if (m_gradients.contains(gradientId)) {
return &m_gradients[gradientId];
}
if (e.hasAttribute("xlink:href")) {
// strip the '#' symbol
QString href = e.attribute("xlink:href").mid(1);
if (!href.isEmpty()) {
// copy the referenced gradient if found
SvgGradientHelper *pGrad = findGradient(href);
if (pGrad) {
gradHelper = *pGrad;
}
}
}
const QGradientStops defaultStops = gradHelper.gradient()->stops();
if (e.attribute("gradientUnits") == "userSpaceOnUse") {
gradHelper.setGradientUnits(KoFlake::UserSpaceOnUse);
}
m_context.pushGraphicsContext(e);
uploadStyleToContext(e);
if (e.tagName() == "linearGradient") {
QLinearGradient *g = new QLinearGradient();
if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setStart(QPointF(SvgUtil::fromPercentage(e.attribute("x1", "0%")),
SvgUtil::fromPercentage(e.attribute("y1", "0%"))));
g->setFinalStop(QPointF(SvgUtil::fromPercentage(e.attribute("x2", "100%")),
SvgUtil::fromPercentage(e.attribute("y2", "0%"))));
} else {
g->setStart(QPointF(parseUnitX(e.attribute("x1")),
parseUnitY(e.attribute("y1"))));
g->setFinalStop(QPointF(parseUnitX(e.attribute("x2")),
parseUnitY(e.attribute("y2"))));
}
gradHelper.setGradient(g);
} else if (e.tagName() == "radialGradient") {
QRadialGradient *g = new QRadialGradient();
if (gradHelper.gradientUnits() == KoFlake::ObjectBoundingBox) {
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setCenter(QPointF(SvgUtil::fromPercentage(e.attribute("cx", "50%")),
SvgUtil::fromPercentage(e.attribute("cy", "50%"))));
g->setRadius(SvgUtil::fromPercentage(e.attribute("r", "50%")));
g->setFocalPoint(QPointF(SvgUtil::fromPercentage(e.attribute("fx", "50%")),
SvgUtil::fromPercentage(e.attribute("fy", "50%"))));
} else {
g->setCenter(QPointF(parseUnitX(e.attribute("cx")),
parseUnitY(e.attribute("cy"))));
g->setFocalPoint(QPointF(parseUnitX(e.attribute("fx")),
parseUnitY(e.attribute("fy"))));
g->setRadius(parseUnitXY(e.attribute("r")));
}
gradHelper.setGradient(g);
} else {
qDebug() << "WARNING: Failed to parse gradient with tag" << e.tagName();
}
// handle spread method
QGradient::Spread spreadMethod = QGradient::PadSpread;
QString spreadMethodStr = e.attribute("spreadMethod");
if (!spreadMethodStr.isEmpty()) {
if (spreadMethodStr == "reflect") {
spreadMethod = QGradient::ReflectSpread;
} else if (spreadMethodStr == "repeat") {
spreadMethod = QGradient::RepeatSpread;
}
}
gradHelper.setSpreadMode(spreadMethod);
// Parse the color stops.
m_context.styleParser().parseColorStops(gradHelper.gradient(), e, gc, defaultStops);
if (e.hasAttribute("gradientTransform")) {
SvgTransformParser p(e.attribute("gradientTransform"));
if (p.isValid()) {
gradHelper.setTransform(p.transform());
}
}
m_context.popGraphicsContext();
m_gradients.insert(gradientId, gradHelper);
return &m_gradients[gradientId];
}
inline QPointF bakeShapeOffset(const QTransform &patternTransform, const QPointF &shapeOffset)
{
QTransform result =
patternTransform *
QTransform::fromTranslate(-shapeOffset.x(), -shapeOffset.y()) *
patternTransform.inverted();
KIS_ASSERT_RECOVER_NOOP(result.type() <= QTransform::TxTranslate);
return QPointF(result.dx(), result.dy());
}
QSharedPointer SvgParser::parsePattern(const KoXmlElement &e, const KoShape *shape)
{
/**
* Unlike the gradient parsing function, this method is called every time we
* *reference* the pattern, not when we define it. Therefore we can already
* use the coordinate system of the destination.
*/
QSharedPointer pattHelper;
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc) return pattHelper;
const QString patternId = e.attribute("id");
if (patternId.isEmpty()) return pattHelper;
pattHelper = toQShared(new KoVectorPatternBackground);
if (e.hasAttribute("xlink:href")) {
// strip the '#' symbol
QString href = e.attribute("xlink:href").mid(1);
if (!href.isEmpty() &&href != patternId) {
// copy the referenced pattern if found
QSharedPointer pPatt = findPattern(href, shape);
if (pPatt) {
pattHelper = pPatt;
}
}
}
pattHelper->setReferenceCoordinates(
KoFlake::coordinatesFromString(e.attribute("patternUnits"),
pattHelper->referenceCoordinates()));
pattHelper->setContentCoordinates(
KoFlake::coordinatesFromString(e.attribute("patternContentUnits"),
pattHelper->contentCoordinates()));
if (e.hasAttribute("patternTransform")) {
SvgTransformParser p(e.attribute("patternTransform"));
if (p.isValid()) {
pattHelper->setPatternTransform(p.transform());
}
}
if (pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox) {
QRectF referenceRect(
SvgUtil::fromPercentage(e.attribute("x", "0%")),
SvgUtil::fromPercentage(e.attribute("y", "0%")),
SvgUtil::fromPercentage(e.attribute("width", "0%")), // 0% is according to SVG 1.1, don't ask me why!
SvgUtil::fromPercentage(e.attribute("height", "0%"))); // 0% is according to SVG 1.1, don't ask me why!
pattHelper->setReferenceRect(referenceRect);
} else {
QRectF referenceRect(
parseUnitX(e.attribute("x", "0")),
parseUnitY(e.attribute("y", "0")),
parseUnitX(e.attribute("width", "0")), // 0 is according to SVG 1.1, don't ask me why!
parseUnitY(e.attribute("height", "0"))); // 0 is according to SVG 1.1, don't ask me why!
pattHelper->setReferenceRect(referenceRect);
}
/**
* In Krita shapes X,Y coordinates are baked into the the shape global transform, but
* the pattern should be painted in "user" coordinates. Therefore, we should handle
* this offfset separately.
*
* TODO: Please also not that this offset is different from extraShapeOffset(),
* because A.inverted() * B != A * B.inverted(). I'm not sure which variant is
* correct (DK)
*/
const QTransform dstShapeTransform = shape->absoluteTransformation(0);
const QTransform shapeOffsetTransform = dstShapeTransform * gc->matrix.inverted();
KIS_SAFE_ASSERT_RECOVER_NOOP(shapeOffsetTransform.type() <= QTransform::TxTranslate);
const QPointF extraShapeOffset(shapeOffsetTransform.dx(), shapeOffsetTransform.dy());
m_context.pushGraphicsContext(e);
gc = m_context.currentGC();
gc->workaroundClearInheritedFillProperties(); // HACK!
// start building shape tree from scratch
gc->matrix = QTransform();
const QRectF boundingRect = shape->outline().boundingRect()/*.translated(extraShapeOffset)*/;
const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
// WARNING1: OBB and ViewBox transformations are *baked* into the pattern shapes!
// although we expect the pattern be reusable, but it is not so!
// WARNING2: the pattern shapes are stored in *User* coordinate system, although
// the "official" content system might be either OBB or User. It means that
// this baked transform should be stripped before writing the shapes back
// into SVG
if (e.hasAttribute("viewBox")) {
gc->currentBoundingBox =
pattHelper->referenceCoordinates() == KoFlake::ObjectBoundingBox ?
relativeToShape.mapRect(pattHelper->referenceRect()) :
pattHelper->referenceRect();
applyViewBoxTransform(e);
pattHelper->setContentCoordinates(pattHelper->referenceCoordinates());
} else if (pattHelper->contentCoordinates() == KoFlake::ObjectBoundingBox) {
gc->matrix = relativeToShape * gc->matrix;
}
// We do *not* apply patternTransform here! Here we only bake the untransformed
// version of the shape. The transformed one will be done in the very end while rendering.
QList patternShapes = parseContainer(e);
if (pattHelper->contentCoordinates() == KoFlake::UserSpaceOnUse) {
// In Krita we normalize the shapes, bake this transform into the pattern shapes
const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
Q_FOREACH (KoShape *shape, patternShapes) {
shape->applyAbsoluteTransformation(QTransform::fromTranslate(offset.x(), offset.y()));
}
}
if (pattHelper->referenceCoordinates() == KoFlake::UserSpaceOnUse) {
// In Krita we normalize the shapes, bake this transform into reference rect
// NOTE: this is possible *only* when pattern transform is not perspective
// (which is always true for SVG)
const QPointF offset = bakeShapeOffset(pattHelper->patternTransform(), extraShapeOffset);
QRectF ref = pattHelper->referenceRect();
ref.translate(offset);
pattHelper->setReferenceRect(ref);
}
m_context.popGraphicsContext();
gc = m_context.currentGC();
if (!patternShapes.isEmpty()) {
pattHelper->setShapes(patternShapes);
}
return pattHelper;
}
bool SvgParser::parseFilter(const KoXmlElement &e, const KoXmlElement &referencedBy)
{
SvgFilterHelper filter;
// Use the filter that is referencing, or if there isn't one, the original filter
KoXmlElement b;
if (!referencedBy.isNull())
b = referencedBy;
else
b = e;
// check if we are referencing another filter
if (e.hasAttribute("xlink:href")) {
QString href = e.attribute("xlink:href").mid(1);
if (! href.isEmpty()) {
// copy the referenced filter if found
SvgFilterHelper *refFilter = findFilter(href);
if (refFilter)
filter = *refFilter;
}
} else {
filter.setContent(b);
}
if (b.attribute("filterUnits") == "userSpaceOnUse")
filter.setFilterUnits(KoFlake::UserSpaceOnUse);
if (b.attribute("primitiveUnits") == "objectBoundingBox")
filter.setPrimitiveUnits(KoFlake::ObjectBoundingBox);
// parse filter region rectangle
if (filter.filterUnits() == KoFlake::UserSpaceOnUse) {
filter.setPosition(QPointF(parseUnitX(b.attribute("x")),
parseUnitY(b.attribute("y"))));
filter.setSize(QSizeF(parseUnitX(b.attribute("width")),
parseUnitY(b.attribute("height"))));
} else {
// x, y, width, height are in percentages of the object referencing the filter
// so we just parse the percentages
filter.setPosition(QPointF(SvgUtil::fromPercentage(b.attribute("x", "-0.1")),
SvgUtil::fromPercentage(b.attribute("y", "-0.1"))));
filter.setSize(QSizeF(SvgUtil::fromPercentage(b.attribute("width", "1.2")),
SvgUtil::fromPercentage(b.attribute("height", "1.2"))));
}
m_filters.insert(b.attribute("id"), filter);
return true;
}
bool SvgParser::parseMarker(const KoXmlElement &e)
{
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
QScopedPointer marker(new KoMarker());
marker->setCoordinateSystem(
KoMarker::coordinateSystemFromString(e.attribute("markerUnits", "strokeWidth")));
marker->setReferencePoint(QPointF(parseUnitX(e.attribute("refX")),
parseUnitY(e.attribute("refY"))));
marker->setReferenceSize(QSizeF(parseUnitX(e.attribute("markerWidth", "3")),
parseUnitY(e.attribute("markerHeight", "3"))));
const QString orientation = e.attribute("orient", "0");
if (orientation == "auto") {
marker->setAutoOrientation(true);
} else {
marker->setExplicitOrientation(parseAngular(orientation));
}
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e, false);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->currentBoundingBox = QRectF(QPointF(0, 0), marker->referenceSize());
KoShape *markerShape = parseGroup(e);
m_context.popGraphicsContext();
if (!markerShape) return false;
marker->setShapes({markerShape});
m_markers.insert(id, QExplicitlySharedDataPointer(marker.take()));
return true;
}
bool SvgParser::parseClipPath(const KoXmlElement &e)
{
SvgClipPathHelper clipPath;
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipPath.setClipPathUnits(
KoFlake::coordinatesFromString(e.attribute("clipPathUnits"), KoFlake::UserSpaceOnUse));
// ensure that the clip path is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK!
KoShape *clipShape = parseGroup(e);
m_context.popGraphicsContext();
if (!clipShape) return false;
clipPath.setShapes({clipShape});
m_clipPaths.insert(id, clipPath);
return true;
}
bool SvgParser::parseClipMask(const KoXmlElement &e)
{
QSharedPointer clipMask(new KoClipMask);
const QString id = e.attribute("id");
if (id.isEmpty()) return false;
clipMask->setCoordinates(KoFlake::coordinatesFromString(e.attribute("maskUnits"), KoFlake::ObjectBoundingBox));
clipMask->setContentCoordinates(KoFlake::coordinatesFromString(e.attribute("maskContentUnits"), KoFlake::UserSpaceOnUse));
QRectF maskRect;
if (clipMask->coordinates() == KoFlake::ObjectBoundingBox) {
maskRect.setRect(
SvgUtil::fromPercentage(e.attribute("x", "-10%")),
SvgUtil::fromPercentage(e.attribute("y", "-10%")),
SvgUtil::fromPercentage(e.attribute("width", "120%")),
SvgUtil::fromPercentage(e.attribute("height", "120%")));
} else {
maskRect.setRect(
parseUnitX(e.attribute("x", "-10%")), // yes, percents are insane in this case,
parseUnitY(e.attribute("y", "-10%")), // but this is what SVG 1.1 tells us...
parseUnitX(e.attribute("width", "120%")),
parseUnitY(e.attribute("height", "120%")));
}
clipMask->setMaskRect(maskRect);
// ensure that the clip mask is loaded in local coordinates system
m_context.pushGraphicsContext(e);
m_context.currentGC()->matrix = QTransform();
m_context.currentGC()->workaroundClearInheritedFillProperties(); // HACK!
KoShape *clipShape = parseGroup(e);
m_context.popGraphicsContext();
if (!clipShape) return false;
clipMask->setShapes({clipShape});
m_clipMasks.insert(id, clipMask);
return true;
}
void SvgParser::uploadStyleToContext(const KoXmlElement &e)
{
SvgStyles styles = m_context.styleParser().collectStyles(e);
m_context.styleParser().parseFont(styles);
m_context.styleParser().parseStyle(styles);
}
void SvgParser::applyCurrentStyle(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
if (!shape) return;
SvgGraphicsContext *gc = m_context.currentGC();
KIS_ASSERT(gc);
if (!dynamic_cast(shape)) {
applyFillStyle(shape);
applyStrokeStyle(shape);
}
if (KoPathShape *pathShape = dynamic_cast(shape)) {
applyMarkers(pathShape);
}
applyFilter(shape);
applyClipping(shape, shapeToOriginalUserCoordinates);
applyMaskClipping(shape, shapeToOriginalUserCoordinates);
if (!gc->display || !gc->visible) {
/**
* WARNING: here is a small inconsistency with the standard:
* in the standard, 'display' is not inherited, but in
* flake it is!
*
* NOTE: though the standard says: "A value of 'display:none' indicates
* that the given element and ***its children*** shall not be
* rendered directly". Therefore, using setVisible(false) is fully
* legitimate here (DK 29.11.16).
*/
shape->setVisible(false);
}
shape->setTransparency(1.0 - gc->opacity);
}
void SvgParser::applyStyle(KoShape *obj, const KoXmlElement &e, const QPointF &shapeToOriginalUserCoordinates)
{
applyStyle(obj, m_context.styleParser().collectStyles(e), shapeToOriginalUserCoordinates);
}
void SvgParser::applyStyle(KoShape *obj, const SvgStyles &styles, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
m_context.styleParser().parseStyle(styles);
if (!obj)
return;
if (!dynamic_cast(obj)) {
applyFillStyle(obj);
applyStrokeStyle(obj);
}
if (KoPathShape *pathShape = dynamic_cast(obj)) {
applyMarkers(pathShape);
}
applyFilter(obj);
applyClipping(obj, shapeToOriginalUserCoordinates);
applyMaskClipping(obj, shapeToOriginalUserCoordinates);
if (!gc->display || !gc->visible) {
obj->setVisible(false);
}
obj->setTransparency(1.0 - gc->opacity);
}
QGradient* prepareGradientForShape(const SvgGradientHelper *gradient,
const KoShape *shape,
const SvgGraphicsContext *gc,
QTransform *transform)
{
QGradient *resultGradient = 0;
KIS_ASSERT(transform);
if (gradient->gradientUnits() == KoFlake::ObjectBoundingBox) {
resultGradient = KoFlake::cloneGradient(gradient->gradient());
*transform = gradient->transform();
} else {
if (gradient->gradient()->type() == QGradient::LinearGradient) {
/**
* Create a converted gradient that looks the same, but linked to the
* bounding rect of the shape, so it would be transformed with the shape
*/
const QRectF boundingRect = shape->outline().boundingRect();
const QTransform relativeToShape(boundingRect.width(), 0, 0, boundingRect.height(),
boundingRect.x(), boundingRect.y());
const QTransform relativeToUser =
relativeToShape * shape->transformation() * gc->matrix.inverted();
const QTransform userToRelative = relativeToUser.inverted();
const QLinearGradient *o = static_cast(gradient->gradient());
QLinearGradient *g = new QLinearGradient();
g->setStart(userToRelative.map(o->start()));
g->setFinalStop(userToRelative.map(o->finalStop()));
g->setCoordinateMode(QGradient::ObjectBoundingMode);
g->setStops(o->stops());
g->setSpread(o->spread());
resultGradient = g;
*transform = relativeToUser * gradient->transform() * userToRelative;
} else if (gradient->gradient()->type() == QGradient::RadialGradient) {
// For radial and conical gradients such conversion is not possible
resultGradient = KoFlake::cloneGradient(gradient->gradient());
*transform = gradient->transform() * gc->matrix * shape->transformation().inverted();
}
}
return resultGradient;
}
void SvgParser::applyFillStyle(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->fillType == SvgGraphicsContext::None) {
shape->setBackground(QSharedPointer(0));
} else if (gc->fillType == SvgGraphicsContext::Solid) {
shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor)));
} else if (gc->fillType == SvgGraphicsContext::Complex) {
// try to find referenced gradient
SvgGradientHelper *gradient = findGradient(gc->fillId);
if (gradient) {
QTransform transform;
QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
if (result) {
QSharedPointer bg;
bg = toQShared(new KoGradientBackground(result));
bg->setTransform(transform);
shape->setBackground(bg);
}
} else {
QSharedPointer pattern =
findPattern(gc->fillId, shape);
if (pattern) {
shape->setBackground(pattern);
} else {
// no referenced fill found, use fallback color
shape->setBackground(QSharedPointer(new KoColorBackground(gc->fillColor)));
}
}
}
KoPathShape *path = dynamic_cast(shape);
if (path)
path->setFillRule(gc->fillRule);
}
void applyDashes(const KoShapeStrokeSP srcStroke, KoShapeStrokeSP dstStroke)
{
const double lineWidth = srcStroke->lineWidth();
QVector dashes = srcStroke->lineDashes();
// apply line width to dashes and dash offset
if (dashes.count() && lineWidth > 0.0) {
const double dashOffset = srcStroke->dashOffset();
QVector dashes = srcStroke->lineDashes();
for (int i = 0; i < dashes.count(); ++i) {
dashes[i] /= lineWidth;
}
dstStroke->setLineStyle(Qt::CustomDashLine, dashes);
dstStroke->setDashOffset(dashOffset / lineWidth);
} else {
dstStroke->setLineStyle(Qt::SolidLine, QVector());
}
}
void SvgParser::applyStrokeStyle(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->strokeType == SvgGraphicsContext::None) {
shape->setStroke(KoShapeStrokeModelSP());
} else if (gc->strokeType == SvgGraphicsContext::Solid) {
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
} else if (gc->strokeType == SvgGraphicsContext::Complex) {
// try to find referenced gradient
SvgGradientHelper *gradient = findGradient(gc->strokeId);
if (gradient) {
QTransform transform;
QGradient *result = prepareGradientForShape(gradient, shape, gc, &transform);
if (result) {
QBrush brush = *result;
delete result;
brush.setTransform(transform);
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
stroke->setLineBrush(brush);
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
}
} else {
// no referenced stroke found, use fallback color
KoShapeStrokeSP stroke(new KoShapeStroke(*gc->stroke));
applyDashes(gc->stroke, stroke);
shape->setStroke(stroke);
}
}
}
void SvgParser::applyFilter(KoShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->filterId.isEmpty())
return;
SvgFilterHelper *filter = findFilter(gc->filterId);
if (! filter)
return;
KoXmlElement content = filter->content();
// parse filter region
QRectF bound(shape->position(), shape->size());
// work on bounding box without viewbox tranformation applied
// so user space coordinates of bounding box and filter region match up
bound = gc->viewboxTransform.inverted().mapRect(bound);
QRectF filterRegion(filter->position(bound), filter->size(bound));
// convert filter region to boundingbox units
QRectF objectFilterRegion;
objectFilterRegion.setTopLeft(SvgUtil::userSpaceToObject(filterRegion.topLeft(), bound));
objectFilterRegion.setSize(SvgUtil::userSpaceToObject(filterRegion.size(), bound));
KoFilterEffectLoadingContext context(m_context.xmlBaseDir());
context.setShapeBoundingBox(bound);
// enable units conversion
context.enableFilterUnitsConversion(filter->filterUnits() == KoFlake::UserSpaceOnUse);
context.enableFilterPrimitiveUnitsConversion(filter->primitiveUnits() == KoFlake::UserSpaceOnUse);
KoFilterEffectRegistry *registry = KoFilterEffectRegistry::instance();
KoFilterEffectStack *filterStack = 0;
QSet stdInputs;
stdInputs << "SourceGraphic" << "SourceAlpha";
stdInputs << "BackgroundImage" << "BackgroundAlpha";
stdInputs << "FillPaint" << "StrokePaint";
QMap inputs;
// create the filter effects and add them to the shape
for (KoXmlNode n = content.firstChild(); !n.isNull(); n = n.nextSibling()) {
KoXmlElement primitive = n.toElement();
KoFilterEffect *filterEffect = registry->createFilterEffectFromXml(primitive, context);
if (!filterEffect) {
debugFlake << "filter effect" << primitive.tagName() << "is not implemented yet";
continue;
}
const QString input = primitive.attribute("in");
if (!input.isEmpty()) {
filterEffect->setInput(0, input);
}
const QString output = primitive.attribute("result");
if (!output.isEmpty()) {
filterEffect->setOutput(output);
}
QRectF subRegion;
// parse subregion
if (filter->primitiveUnits() == KoFlake::UserSpaceOnUse) {
const QString xa = primitive.attribute("x");
const QString ya = primitive.attribute("y");
const QString wa = primitive.attribute("width");
const QString ha = primitive.attribute("height");
if (xa.isEmpty() || ya.isEmpty() || wa.isEmpty() || ha.isEmpty()) {
bool hasStdInput = false;
bool isFirstEffect = filterStack == 0;
// check if one of the inputs is a standard input
Q_FOREACH (const QString &input, filterEffect->inputs()) {
if ((isFirstEffect && input.isEmpty()) || stdInputs.contains(input)) {
hasStdInput = true;
break;
}
}
if (hasStdInput || primitive.tagName() == "feImage") {
// default to 0%, 0%, 100%, 100%
subRegion.setTopLeft(QPointF(0, 0));
subRegion.setSize(QSizeF(1, 1));
} else {
// defaults to bounding rect of all referenced nodes
Q_FOREACH (const QString &input, filterEffect->inputs()) {
if (!inputs.contains(input))
continue;
KoFilterEffect *inputFilter = inputs[input];
if (inputFilter)
subRegion |= inputFilter->filterRect();
}
}
} else {
const qreal x = parseUnitX(xa);
const qreal y = parseUnitY(ya);
const qreal w = parseUnitX(wa);
const qreal h = parseUnitY(ha);
subRegion.setTopLeft(SvgUtil::userSpaceToObject(QPointF(x, y), bound));
subRegion.setSize(SvgUtil::userSpaceToObject(QSizeF(w, h), bound));
}
} else {
// x, y, width, height are in percentages of the object referencing the filter
// so we just parse the percentages
const qreal x = SvgUtil::fromPercentage(primitive.attribute("x", "0"));
const qreal y = SvgUtil::fromPercentage(primitive.attribute("y", "0"));
const qreal w = SvgUtil::fromPercentage(primitive.attribute("width", "1"));
const qreal h = SvgUtil::fromPercentage(primitive.attribute("height", "1"));
subRegion = QRectF(QPointF(x, y), QSizeF(w, h));
}
filterEffect->setFilterRect(subRegion);
if (!filterStack)
filterStack = new KoFilterEffectStack();
filterStack->appendFilterEffect(filterEffect);
inputs[filterEffect->output()] = filterEffect;
}
if (filterStack) {
filterStack->setClipRect(objectFilterRegion);
shape->setFilterEffectStack(filterStack);
}
}
void SvgParser::applyMarkers(KoPathShape *shape)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (!gc->markerStartId.isEmpty() && m_markers.contains(gc->markerStartId)) {
shape->setMarker(m_markers[gc->markerStartId].data(), KoFlake::StartMarker);
}
if (!gc->markerMidId.isEmpty() && m_markers.contains(gc->markerMidId)) {
shape->setMarker(m_markers[gc->markerMidId].data(), KoFlake::MidMarker);
}
if (!gc->markerEndId.isEmpty() && m_markers.contains(gc->markerEndId)) {
shape->setMarker(m_markers[gc->markerEndId].data(), KoFlake::EndMarker);
}
shape->setAutoFillMarkers(gc->autoFillMarkers);
}
void SvgParser::applyClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (! gc)
return;
if (gc->clipPathId.isEmpty())
return;
SvgClipPathHelper *clipPath = findClipPath(gc->clipPathId);
if (!clipPath || clipPath->isEmpty())
return;
QList shapes;
Q_FOREACH (KoShape *item, clipPath->shapes()) {
KoShape *clonedShape = item->cloneShape();
KIS_ASSERT_RECOVER(clonedShape) { continue; }
shapes.append(clonedShape);
}
if (!shapeToOriginalUserCoordinates.isNull()) {
const QTransform t =
QTransform::fromTranslate(shapeToOriginalUserCoordinates.x(),
shapeToOriginalUserCoordinates.y());
Q_FOREACH(KoShape *s, shapes) {
s->applyAbsoluteTransformation(t);
}
}
KoClipPath *clipPathObject = new KoClipPath(shapes,
clipPath->clipPathUnits() == KoFlake::ObjectBoundingBox ?
KoFlake::ObjectBoundingBox : KoFlake::UserSpaceOnUse);
shape->setClipPath(clipPathObject);
}
void SvgParser::applyMaskClipping(KoShape *shape, const QPointF &shapeToOriginalUserCoordinates)
{
SvgGraphicsContext *gc = m_context.currentGC();
if (!gc)
return;
if (gc->clipMaskId.isEmpty())
return;
QSharedPointer originalClipMask = m_clipMasks.value(gc->clipMaskId);
if (!originalClipMask || originalClipMask->isEmpty()) return;
KoClipMask *clipMask = originalClipMask->clone();
clipMask->setExtraShapeOffset(shapeToOriginalUserCoordinates);
shape->setClipMask(clipMask);
}
KoShape* SvgParser::parseUse(const KoXmlElement &e)
{
KoShape *result = 0;
QString id = e.attribute("xlink:href");
if (!id.isEmpty()) {
QString key = id.mid(1);
if (m_context.hasDefinition(key)) {
SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
// TODO: parse 'width' and 'hight' as well
gc->matrix.translate(parseUnitX(e.attribute("x", "0")), parseUnitY(e.attribute("y", "0")));
const KoXmlElement &referencedElement = m_context.definition(key);
result = parseGroup(e, referencedElement);
m_context.popGraphicsContext();
}
}
return result;
}
void SvgParser::addToGroup(QList shapes, KoShapeGroup *group)
{
m_shapes += shapes;
if (!group || shapes.isEmpty())
return;
// not clipped, but inherit transform
KoShapeGroupCommand cmd(group, shapes, false, true, false);
cmd.redo();
}
QList SvgParser::parseSvg(const KoXmlElement &e, QSizeF *fragmentSize)
{
// check if we are the root svg element
const bool isRootSvg = m_context.isRootContext();
// parse 'transform' field if preset
SvgGraphicsContext *gc = m_context.pushGraphicsContext(e);
applyStyle(0, e, QPointF());
const QString w = e.attribute("width");
const QString h = e.attribute("height");
const qreal width = w.isEmpty() ? 666.0 : parseUnitX(w);
const qreal height = h.isEmpty() ? 555.0 : parseUnitY(h);
QSizeF svgFragmentSize(QSizeF(width, height));
if (fragmentSize) {
*fragmentSize = svgFragmentSize;
}
gc->currentBoundingBox = QRectF(QPointF(0, 0), svgFragmentSize);
if (!isRootSvg) {
// x and y attribute has no meaning for outermost svg elements
const qreal x = parseUnit(e.attribute("x", "0"));
const qreal y = parseUnit(e.attribute("y", "0"));
QTransform move = QTransform::fromTranslate(x, y);
gc->matrix = move * gc->matrix;
}
applyViewBoxTransform(e);
QList shapes;
// SVG 1.1: skip the rendering of the element if it has null viewBox
if (gc->currentBoundingBox.isValid()) {
shapes = parseContainer(e);
}
m_context.popGraphicsContext();
return shapes;
}
void SvgParser::applyViewBoxTransform(const KoXmlElement &element)
{
SvgGraphicsContext *gc = m_context.currentGC();
QRectF viewRect = gc->currentBoundingBox;
QTransform viewTransform;
if (SvgUtil::parseViewBox(gc, element, gc->currentBoundingBox,
&viewRect, &viewTransform)) {
gc->matrix = viewTransform * gc->matrix;
gc->currentBoundingBox = viewRect;
}
}
QList > SvgParser::knownMarkers() const
{
return m_markers.values();
}
void SvgParser::setFileFetcher(SvgParser::FileFetcherFunc func)
{
m_context.setFileFetcher(func);
}
inline QPointF extraShapeOffset(const KoShape *shape, const QTransform coordinateSystemOnLoading)
{
const QTransform shapeToOriginalUserCoordinates =
shape->absoluteTransformation(0).inverted() *
coordinateSystemOnLoading;
KIS_SAFE_ASSERT_RECOVER_NOOP(shapeToOriginalUserCoordinates.type() <= QTransform::TxTranslate);
return QPointF(shapeToOriginalUserCoordinates.dx(), shapeToOriginalUserCoordinates.dy());
}
KoShape* SvgParser::parseGroup(const KoXmlElement &b, const KoXmlElement &overrideChildrenFrom)
{
m_context.pushGraphicsContext(b);
KoShapeGroup *group = new KoShapeGroup();
group->setZIndex(m_context.nextZIndex());
// groups should also have their own coordinate system!
group->applyAbsoluteTransformation(m_context.currentGC()->matrix);
const QPointF extraOffset = extraShapeOffset(group, m_context.currentGC()->matrix);
uploadStyleToContext(b);
QList childShapes;
if (!overrideChildrenFrom.isNull()) {
// we upload styles from both: