diff --git a/krita/data/CMakeLists.txt b/krita/data/CMakeLists.txt
index 74adbd2497..ade756f352 100644
--- a/krita/data/CMakeLists.txt
+++ b/krita/data/CMakeLists.txt
@@ -1,22 +1,23 @@
add_subdirectory( actions )
add_subdirectory( brushes )
add_subdirectory( bundles )
add_subdirectory( patterns )
add_subdirectory( gradients )
add_subdirectory( profiles )
add_subdirectory( templates )
add_subdirectory( workspaces )
add_subdirectory( themes )
add_subdirectory( predefined_image_sizes )
add_subdirectory( input )
add_subdirectory( shortcuts )
add_subdirectory( paintoppresets )
add_subdirectory( palettes )
+add_subdirectory( symbols )
########### install files ###############
install( FILES
kritarc
DESTINATION ${CONFIG_INSTALL_DIR}
)
diff --git a/krita/data/symbols/BalloonSymbols.svg b/krita/data/symbols/BalloonSymbols.svg
new file mode 100644
index 0000000000..b2563e47ab
--- /dev/null
+++ b/krita/data/symbols/BalloonSymbols.svg
@@ -0,0 +1,145 @@
+
+
diff --git a/krita/data/symbols/CMakeLists.txt b/krita/data/symbols/CMakeLists.txt
new file mode 100644
index 0000000000..483d7399e5
--- /dev/null
+++ b/krita/data/symbols/CMakeLists.txt
@@ -0,0 +1,6 @@
+install( FILES
+ BalloonSymbols.svg
+ pepper_carrot_speech_bubbles.svg
+ DESTINATION ${DATA_INSTALL_DIR}/krita/symbols)
+
+
diff --git a/krita/data/symbols/pepper_carrot_speech_bubbles.svg b/krita/data/symbols/pepper_carrot_speech_bubbles.svg
new file mode 100644
index 0000000000..a15e6be7b0
--- /dev/null
+++ b/krita/data/symbols/pepper_carrot_speech_bubbles.svg
@@ -0,0 +1,4625 @@
+
+
+
+
diff --git a/krita/krita.action b/krita/krita.action
index f75cd92257..90b5abdf15 100644
--- a/krita/krita.action
+++ b/krita/krita.action
@@ -1,2958 +1,3006 @@
General
Open Resources Folder
Opens a file browser at the location Krita saves resources such as brushes to.
Opens a file browser at the location Krita saves resources such as brushes to.
Open Resources Folder
0
0
false
Cleanup removed files...
Cleanup removed files
Cleanup removed files
0
0
false
C&ascade
Cascade
Cascade
10
0
false
&Tile
Tile
Tile
10
0
false
Create Resource Bundle...
Create Resource Bundle
Create Resource Bundle
0
0
false
Show File Toolbar
Show File Toolbar
Show File Toolbar
false
Show color selector
Show color selector
Show color selector
Shift+I
false
Show MyPaint shade selector
Show MyPaint shade selector
Show MyPaint shade selector
Shift+M
false
Show minimal shade selector
Show minimal shade selector
Show minimal shade selector
Shift+N
false
Show color history
Show color history
Show color history
H
false
Show common colors
Show common colors
Show common colors
U
false
Show Tool Options
Show Tool Options
Show Tool Options
\
false
Show Brush Editor
Show Brush Editor
Show Brush Editor
F5
false
Show Brush Presets
Show Brush Presets
Show Brush Presets
F6
false
Toggle Tablet Debugger
Toggle Tablet Debugger
Toggle Tablet Debugger
0
0
Ctrl+Shift+T
false
Rename Composition...
Rename Composition
Rename Composition
0
0
false
Update Composition
Update Composition
Update Composition
0
0
false
Painting
Make brush color lighter
Make brush color lighter
Make brush color lighter
0
0
L
false
Make brush color darker
Make brush color darker
Make brush color darker
0
0
K
false
Make brush color more saturated
Make brush color more saturated
Make brush color more saturated
false
Make brush color more desaturated
Make brush color more desaturated
Make brush color more desaturated
false
Shift brush color hue clockwise
Shift brush color hue clockwise
Shift brush color hue clockwise
false
Shift brush color hue counter-clockwise
Shift brush color hue counter-clockwise
Shift brush color hue counter-clockwise
false
Make brush color more red
Make brush color more red
Make brush color more red
false
Make brush color more green
Make brush color more green
Make brush color more green
false
Make brush color more blue
Make brush color more blue
Make brush color more blue
false
Make brush color more yellow
Make brush color more yellow
Make brush color more yellow
false
Increase opacity
Increase opacity
Increase opacity
0
0
O
false
Decrease opacity
Decrease opacity
Decrease opacity
0
0
I
false
draw-eraser
Set eraser mode
Set eraser mode
Set eraser mode
10000
0
E
true
view-refresh
Reload Original Preset
Reload Original Preset
Reload Original Preset
10000
false
transparency-unlocked
Preserve Alpha
Preserve Alpha
Preserve Alpha
10000
true
transform_icons_penPressure
Use Pen Pressure
Use Pen Pressure
Use Pen Pressure
10000
true
symmetry-horizontal
Horizontal Mirror Tool
Horizontal Mirror Tool
Horizontal Mirror Tool
10000
true
symmetry-vertical
Vertical Mirror Tool
Vertical Mirror Tool
Vertical Mirror Tool
10000
true
&Invert Selection
Invert current selection
Invert Selection
10000000000
100
Ctrl+Shift+I
false
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
10000
1
Ctrl+Shift+Backspace
false
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
10000
1
Ctrl+Backspace
false
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
10000
1
false
&Toggle Selection Display Mode
Toggle Selection Display Mode
Toggle Selection Display Mode
0
0
false
Next Favourite Preset
Next Favourite Preset
Next Favourite Preset
,
false
Previous Favourite Preset
Previous Favourite Preset
Previous Favourite Preset
.
false
preset-switcher
Switch to Previous Preset
Switch to Previous Preset
Switch to Previous Preset
/
false
Hide Brushes and Stuff Toolbar
Hide Brushes and Stuff Toolbar
Hide Brushes and Stuff Toolbar
true
Reset Foreground and Background Color
Reset Foreground and Background Color
Reset Foreground and Background Color
D
false
Swap Foreground and Background Color
Swap Foreground and Background Color
Swap Foreground and Background Color
X
false
smoothing-weighted
Brush Smoothing: Weighted
Brush Smoothing: Weighted
Brush Smoothing: Weighted
false
smoothing-no
Brush Smoothing: Disabled
Brush Smoothing: Disabled
Brush Smoothing: Disabled
false
smoothing-stabilizer
Brush Smoothing: Stabilizer
Brush Smoothing: Stabilizer
Brush Smoothing: Stabilizer
false
Decrease Brush Size
Decrease Brush Size
Decrease Brush Size
0
0
[
false
smoothing-basic
Brush Smoothing: Basic
Brush Smoothing: Basic
Brush Smoothing: Basic
false
Increase Brush Size
Increase Brush Size
Increase Brush Size
0
0
]
false
Toggle Assistant
Toggle Assistant
ToggleAssistant
Ctrl+Shift+L
true
Undo Polygon Selection Points
Undo Polygon Selection Points
Undo Polygon Selection Points
Shift+Z
false
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
Fill with Foreground Color (Opacity)
10000
1
Ctrl+Shift+Backspace
false
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
Fill with Background Color (Opacity)
10000
1
Ctrl+Backspace
false
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
Fill with Pattern (Opacity)
10000
1
false
Convert &to Shape
Convert to Shape
Convert to Shape
10000000000
0
false
&Select Opaque
Select Opaque
Select Opaque
100000
100
false
&Show Global Selection Mask
Shows global selection as a usual selection mask in <interface>Layers</interface> docker
Show Global Selection Mask
100000
100
true
Filters
color-to-alpha
&Color to Alpha...
Color to Alpha
Color to Alpha
10000
0
false
&Top Edge Detection
Top Edge Detection
Top Edge Detection
10000
0
false
&Index Colors...
Index Colors
Index Colors
10000
0
false
Emboss Horizontal &Only
Emboss Horizontal Only
Emboss Horizontal Only
10000
0
false
D&odge
Dodge
Dodge
10000
0
false
&Sharpen
Sharpen
Sharpen
10000
0
false
B&urn
Burn
Burn
10000
0
false
&Mean Removal
Mean Removal
Mean Removal
10000
0
false
&Gaussian Blur...
Gaussian Blur
Gaussian Blur
10000
0
false
Emboss &in All Directions
Emboss in All Directions
Emboss in All Directions
10000
0
false
&Small Tiles...
Small Tiles
Small Tiles
10000
0
false
&Levels...
Levels
Levels
10000
0
Ctrl+L
false
&Sobel...
Sobel
Sobel
10000
0
false
&Wave...
Wave
Wave
10000
0
false
&Motion Blur...
Motion Blur
Motion Blur
10000
0
false
&Color Adjustment curves...
Color Adjustment curves
Color Adjustment curves
10000
0
Ctrl+M
false
Pi&xelize...
Pixelize
Pixelize
10000
0
false
Emboss (&Laplacian)
Emboss (Laplacian)
Emboss (Laplacian)
10000
0
false
&Left Edge Detection
Left Edge Detection
Left Edge Detection
10000
0
false
&Blur...
Blur
Blur
10000
0
false
&Raindrops...
Raindrops
Raindrops
10000
0
false
&Bottom Edge Detection
Bottom Edge Detection
Bottom Edge Detection
10000
0
false
&Random Noise...
Random Noise
Random Noise
10000
0
false
&Brightness/Contrast curve...
Brightness/Contrast curve
Brightness/Contrast curve
10000
0
false
Colo&r Balance..
Color Balance..
Color Balance..
10000
0
Ctrl+B
false
&Phong Bumpmap...
Phong Bumpmap
Phong Bumpmap
10000
0
false
&Desaturate
Desaturate
Desaturate
10000
0
Ctrl+Shift+U
false
Color &Transfer...
Color Transfer
Color Transfer
10000
0
false
Emboss &Vertical Only
Emboss Vertical Only
Emboss Vertical Only
10000
0
false
&Lens Blur...
Lens Blur
Lens Blur
10000
0
false
M&inimize Channel
Minimize Channel
Minimize Channel
10000
0
false
M&aximize Channel
Maximize Channel
Maximize Channel
10000
0
false
&Oilpaint...
Oilpaint
Oilpaint
10000
0
false
&Right Edge Detection
Right Edge Detection
Right Edge Detection
10000
0
false
&Auto Contrast
Auto Contrast
Auto Contrast
10000
0
false
&Round Corners...
Round Corners
Round Corners
10000
0
false
&Unsharp Mask...
Unsharp Mask
Unsharp Mask
10000
0
false
&Emboss with Variable Depth...
Emboss with Variable Depth
Emboss with Variable Depth
10000
0
false
Emboss &Horizontal && Vertical
Emboss Horizontal & Vertical
Emboss Horizontal & Vertical
10000
0
false
Random &Pick...
Random Pick
Random Pick
10000
0
false
&Gaussian Noise Reduction...
Gaussian Noise Reduction
Gaussian Noise Reduction
10000
0
false
&Posterize...
Posterize
Posterize
10000
0
false
&Wavelet Noise Reducer...
Wavelet Noise Reducer
Wavelet Noise Reducer
10000
0
false
&HSV Adjustment...
HSV Adjustment
HSV Adjustment
10000
0
Ctrl+U
false
Tool Shortcuts
Dynamic Brush Tool
Dynamic Brush Tool
Dynamic Brush Tool
false
Crop Tool
Crop the image to an area
Crop the image to an area
C
false
Polygon Tool
Polygon Tool. Shift-mouseclick ends the polygon.
Polygon Tool. Shift-mouseclick ends the polygon.
false
References
References
References
false
Rectangle Tool
Rectangle Tool
Rectangle Tool
false
Multibrush Tool
Multibrush Tool
Multibrush Tool
Q
false
Shape Manipulation Tool
Shape Manipulation Tool
Shape Manipulation Tool
false
Color Picker
Select a color from the image or current layer
Select a color from the image or current layer
P
false
Text Editing Tool
Text editing
Text editing
false
Outline Selection Tool
Outline Selection Tool
Outline Selection Tool
false
Artistic Text Tool
Artistic text editing
Artistic text editing
false
Bezier Curve Selection Tool
Select a
Bezier Curve Selection Tool
false
Similar Color Selection Tool
Select a
Similar Color Selection Tool
false
Fill Tool
Fill a contiguous area of color with a color, or fill a selection.
Fill a contiguous area of color with a color, or fill a selection.
F
false
Line Tool
Line Tool
Line Tool
false
Freehand Path Tool
Freehand Path Tool
Freehand Path Tool
false
Bezier Curve Tool
Bezier Curve Tool. Shift-mouseclick ends the curve.
Bezier Curve Tool. Shift-mouseclick ends the curve.
false
Ellipse Tool
Ellipse Tool
Ellipse Tool
false
Freehand Brush Tool
Freehand Brush Tool
Freehand Brush Tool
B
false
Create object
Create object
Create object
false
Elliptical Selection Tool
Elliptical Selection Tool
Elliptical Selection Tool
J
false
Contiguous Selection Tool
Contiguous Selection Tool
Contiguous Selection Tool
false
Pattern editing
Pattern editing
Pattern editing
false
Review
Review
Review
false
Draw a gradient.
Draw a gradient.
Draw a gradient.
G
false
Polygonal Selection Tool
Polygonal Selection Tool
Polygonal Selection Tool
false
Measurement Tool
Measure the distance between two points
Measure the distance between two points
false
Rectangular Selection Tool
Rectangular Selection Tool
Rectangular Selection Tool
Ctrl+R
false
Move Tool
Move a layer
Move a layer
T
false
Vector Image Tool
Vector Image (EMF/WMF/SVM/SVG) tool
Vector Image (EMF/WMF/SVM/SVG) tool
false
Calligraphy
Calligraphy
Calligraphy
false
Path editing
Path editing
Path editing
false
Zoom Tool
Zoom Tool
Zoom Tool
false
Polyline Tool
Polyline Tool. Shift-mouseclick ends the polyline.
Polyline Tool. Shift-mouseclick ends the polyline.
false
Transform Tool
Transform a layer or a selection
Transform a layer or a selection
Ctrl+T
false
Ruler assistant editor tool
Ruler assistant editor tool
Ruler assistant editor tool
false
Text tool
Text tool
Text tool
false
Gradient Editing Tool
Gradient editing
Gradient editing
false
Blending Modes
Select Normal Blending Mode
Select Normal Blending Mode
Select Normal Blending Mode
0
0
Alt+Shift+N
false
Select Dissolve Blending Mode
Select Dissolve Blending Mode
Select Dissolve Blending Mode
0
0
Alt+Shift+I
false
Select Behind Blending Mode
Select Behind Blending Mode
Select Behind Blending Mode
0
0
Alt+Shift+Q
false
Select Clear Blending Mode
Select Clear Blending Mode
Select Clear Blending Mode
0
0
Alt+Shift+R
false
Select Darken Blending Mode
Select Darken Blending Mode
Select Darken Blending Mode
0
0
Alt+Shift+K
false
Select Multiply Blending Mode
Select Multiply Blending Mode
Select Multiply Blending Mode
0
0
Alt+Shift+M
false
Select Color Burn Blending Mode
Select Color Burn Blending Mode
Select Color Burn Blending Mode
0
0
Alt+Shift+B
false
Select Linear Burn Blending Mode
Select Linear Burn Blending Mode
Select Linear Burn Blending Mode
0
0
Alt+Shift+A
false
Select Lighten Blending Mode
Select Lighten Blending Mode
Select Lighten Blending Mode
0
0
Alt+Shift+G
false
Select Screen Blending Mode
Select Screen Blending Mode
Select Screen Blending Mode
0
0
Alt+Shift+S
false
Select Color Dodge Blending Mode
Select Color Dodge Blending Mode
Select Color Dodge Blending Mode
0
0
Alt+Shift+D
false
Select Linear Dodge Blending Mode
Select Linear Dodge Blending Mode
Select Linear Dodge Blending Mode
0
0
Alt+Shift+W
false
Select Overlay Blending Mode
Select Overlay Blending Mode
Select Overlay Blending Mode
0
0
Alt+Shift+O
false
Select Hard Overlay Blending Mode
Select Hard Overlay Blending Mode
Select Hard Overlay Blending Mode
0
0
Alt+Shift+P
false
Select Soft Light Blending Mode
Select Soft Light Blending Mode
Select Soft Light Blending Mode
0
0
Alt+Shift+F
false
Select Hard Light Blending Mode
Select Hard Light Blending Mode
Select Hard Light Blending Mode
0
0
Alt+Shift+H
false
Select Vivid Light Blending Mode
Select Vivid Light Blending Mode
Select Vivid Light Blending Mode
0
0
Alt+Shift+V
false
Select Linear Light Blending Mode
Select Linear Light Blending Mode
Select Linear Light Blending Mode
0
0
Alt+Shift+J
false
Select Pin Light Blending Mode
Select Pin Light Blending Mode
Select Pin Light Blending Mode
0
0
Alt+Shift+Z
false
Select Hard Mix Blending Mode
Select Hard Mix Blending Mode
Select Hard Mix Blending Mode
0
0
Alt+Shift+L
false
Select Difference Blending Mode
Select Difference Blending Mode
Select Difference Blending Mode
0
0
Alt+Shift+E
false
Select Exclusion Blending Mode
Select Exclusion Blending Mode
Select Exclusion Blending Mode
0
0
Alt+Shift+X
false
Select Hue Blending Mode
Select Hue Blending Mode
Select Hue Blending Mode
0
0
Alt+Shift+U
false
Select Saturation Blending Mode
Select Saturation Blending Mode
Select Saturation Blending Mode
0
0
Alt+Shift+T
false
Select Color Blending Mode
Select Color Blending Mode
Select Color Blending Mode
0
0
Alt+Shift+C
false
Select Luminosity Blending Mode
Select Luminosity Blending Mode
Select Luminosity Blending Mode
0
0
Alt+Shift+Y
false
Animation
Previous frame
Move to previous frame
Move to previous frame
1
0
false
Next frame
Move to next frame
Move to next frame
1
0
false
Play / pause animation
Play / pause animation
Play / pause animation
1
0
false
Add blank frame
Add blank frame
Add blank frame
100000
0
false
Copy Frame
Add duplicate frame
Add duplicate frame
100000
0
false
Toggle onion skin
Toggle onion skin
Toggle onion skin
100000
0
false
Previous Keyframe
false
Next Keyframe
false
First Frame
false
Last Frame
false
Auto Frame Mode
true
true
Add blank frame
Add blank frame
Add blank frame
100000
0
false
Show in Timeline
true
Layers
Activate next layer
Activate next layer
Activate next layer
1000
0
PgUp
false
Activate previous layer
Activate previous layer
Activate previous layer
1000
0
PgDown
false
Activate previously selected layer
Activate previously selected layer
Activate previously selected layer
1000
0
;
false
groupLayer
&Group Layer
Group Layer
Group Layer
1000
0
false
cloneLayer
&Clone Layer
Clone Layer
Clone Layer
1000
0
false
vectorLayer
&Vector Layer
Vector Layer
Vector Layer
1000
0
false
filterLayer
&Filter Layer...
Filter Layer
Filter Layer
1000
0
false
fillLayer
&Fill Layer...
Fill Layer
Fill Layer
1000
0
false
fileLayer
&File Layer...
File Layer
File Layer
1000
0
false
transparencyMask
&Transparency Mask
Transparency Mask
Transparency Mask
100000
0
false
filterMask
&Filter Mask...
Filter Mask
Filter Mask
100000
0
false
filterMask
&Colorize Mask
Colorize Mask
Colorize Mask
100000
0
false
transformMask
&Transform Mask...
Transform Mask
Transform Mask
100000
0
false
selectionMask
&Local Selection
Local Selection
Local Selection
100000
0
false
view-filter
&Isolate Layer
Isolate Layer
Isolate Layer
1000
0
true
+
+ layer-locked
+ &Lock/unlock layer
+
+ Lock/unlock layer
+ Lock/unlock layer
+ 1000
+ 0
+
+ false
+
+
+
+ visible
+ Toggle layer &visibility
+
+ Toggle layer visibility
+ Toggle layer visibility
+ 1000
+ 0
+
+ false
+
+
+
+ transparency-locked
+ Lock/unlock layer &alpha
+
+ Lock/unlock layer's alpha
+ Lock/unlock layer's alpha
+ 1000
+ 0
+
+ false
+
+
+
+ transparency-enabled
+ Toggle layer alpha &inheritance
+
+ Toggle layer alpha inheritance
+ Toggle layer alpha inheritance
+ 1000
+ 0
+
+ false
+
+
paintLayer
&Paint Layer
Paint Layer
Paint Layer
1000
0
Insert
false
&New Layer From Visible
New layer from visible
New layer from visible
1000
0
false
duplicatelayer
&Duplicate Layer or Mask
Duplicate Layer or Mask
Duplicate Layer or Mask
1000
0
Ctrl+J
false
&Cut Selection to New Layer
Cut Selection to New Layer
Cut Selection to New Layer
100000000
1
Ctrl+Shift+J
false
Copy &Selection to New Layer
Copy Selection to New Layer
Copy Selection to New Layer
100000000
0
Ctrl+Alt+J
false
Copy Layer
Copy layer to clipboard
Copy layer to clipboard
10000
0
false
Cut Layer
Cut layer to clipboard
Cut layer to clipboard
10000
0
false
Paste Layer
Paste layer from clipboard
Paste layer from clipboard
10000
0
false
Quick Group
Create a group layer containing selected layers
Quick Group
100000
0
Ctrl+G
false
Quick Ungroup
Remove grouping of the layers or remove one layer out of the group
Quick Ungroup
100000
0
Ctrl+Alt+G
false
Quick Clipping Group
Group selected layers and add a layer with clipped alpha channel
Quick Clipping Group
100000
0
Ctrl+Shift+G
false
All Layers
Select all layers
Select all layers
10000
0
false
Visible Layers
Select all visible layers
Select all visible layers
10000
0
false
Locked Layers
Select all locked layers
Select all locked layers
10000
0
false
Invisible Layers
Select all invisible layers
Select all invisible layers
10000
0
false
Unlocked Layers
Select all unlocked layers
Select all unlocked layers
10000
0
false
document-save
&Save Layer/Mask...
Save Layer/Mask
Save Layer/Mask
1000
0
false
document-save
Save &Group Layers...
Save Group Layers
Save Group Layers
100000
0
false
Convert group to &animated layer
Convert child layers into animation frames
Convert child layers into animation frames
100000
0
false
I&mport Layer...
Import Layer
Import Layer
100000
0
false
paintLayer
&as Paint Layer...
as Paint Layer
as Paint Layer
1000
0
false
transparencyMask
as &Transparency Mask...
as Transparency Mask
as Transparency Mask
1000
0
false
filterMask
as &Filter Mask...
as Filter Mask
as Filter Mask
1000
0
false
selectionMask
as &Selection Mask...
as Selection Mask
as Selection Mask
1000
0
false
paintLayer
to &Paint Layer
to Paint Layer
to Paint Layer
1000
0
false
transparencyMask
to &Transparency Mask
to Transparency Mask
to Transparency Mask
1000
0
false
filterMask
to &Filter Mask...
to Filter Mask
to Filter Mask
1000
0
false
selectionMask
to &Selection Mask
to Selection Mask
to Selection Mask
1000
0
false
transparencyMask
&Alpha into Mask
Alpha into Mask
Alpha into Mask
100000
10
false
transparency-enabled
&Write as Alpha
Write as Alpha
Write as Alpha
1000000
1
false
document-save
&Save Merged...
Save Merged
Save Merged
1000000
0
false
split-layer
Split Layer...
Split Layer
Split Layer
1000
0
false
Wavelet Decompose ...
Wavelet Decompose
Wavelet Decompose
1000
1
false
symmetry-horizontal
Mirror Layer Hori&zontally
Mirror Layer Horizontally
Mirror Layer Horizontally
1000
1
false
symmetry-vertical
Mirror Layer &Vertically
Mirror Layer Vertically
Mirror Layer Vertically
1000
1
false
&Rotate Layer...
Rotate Layer
Rotate Layer
1000
1
false
object-rotate-right
Rotate &Layer 90° to the Right
Rotate Layer 90° to the Right
Rotate Layer 90° to the Right
1000
1
false
object-rotate-left
Rotate Layer &90° to the Left
Rotate Layer 90° to the Left
Rotate Layer 90° to the Left
1000
1
false
Rotate Layer &180°
Rotate Layer 180°
Rotate Layer 180°
1000
1
false
Scale &Layer to new Size...
Scale Layer to new Size
Scale Layer to new Size
100000
1
false
&Shear Layer...
Shear Layer
Shear Layer
1000
1
false
&Offset Layer...
Offset Layer
Offset Layer
100000
1
false
Clones &Array...
Clones Array
Clones Array
100000
0
false
&Edit metadata...
Edit metadata
Edit metadata
100000
1
false
&Histogram...
Histogram
Histogram
100000
0
false
&Convert Layer Color Space...
Convert Layer Color Space
Convert Layer Color Space
100000
1
false
merge-layer-below
&Merge with Layer Below
Merge with Layer Below
Merge with Layer Below
100000
0
Ctrl+E
false
&Flatten Layer
Flatten Layer
Flatten Layer
100000
0
false
Ras&terize Layer
Rasterize Layer
Rasterize Layer
10000000
1
false
Flatten ima&ge
Flatten image
Flatten image
100000
0
Ctrl+Shift+E
false
La&yer Style...
Layer Style
Layer Style
100000
1
false
Move into previous group
Move into previous group
Move into previous group
0
0
false
Move into next group
Move into next group
Move into next group
0
0
false
Rename current layer
Rename current layer
Rename current layer
100000
0
F2
false
deletelayer
&Remove Layer
Remove Layer
Remove Layer
1000
1
Shift+Delete
false
arrowupblr
Move Layer or Mask Up
Move Layer or Mask Up
Ctrl+PgUp
false
arrowdown
Move Layer or Mask Down
Move Layer or Mask Down
Ctrl+PgDown
false
properties
&Properties...
Properties
Properties
1000
1
F3
false
diff --git a/libs/flake/CMakeLists.txt b/libs/flake/CMakeLists.txt
index d810eae7c0..3042da88c9 100644
--- a/libs/flake/CMakeLists.txt
+++ b/libs/flake/CMakeLists.txt
@@ -1,231 +1,234 @@
project(kritaflake)
include_directories(
${CMAKE_SOURCE_DIR}/libs/flake/commands
${CMAKE_SOURCE_DIR}/libs/flake/tools
${CMAKE_SOURCE_DIR}/libs/flake/svg
${CMAKE_BINARY_DIR}/libs/flake
)
add_subdirectory(styles)
add_subdirectory(tests)
set(kritaflake_SRCS
KoGradientHelper.cpp
KoFlake.cpp
KoCanvasBase.cpp
KoResourceManager_p.cpp
KoDerivedResourceConverter.cpp
KoResourceUpdateMediator.cpp
KoCanvasResourceManager.cpp
KoDocumentResourceManager.cpp
KoCanvasObserverBase.cpp
KoCanvasSupervisor.cpp
KoDockFactoryBase.cpp
KoDockRegistry.cpp
KoDataCenterBase.cpp
KoInsets.cpp
KoPathShape.cpp
KoPathPoint.cpp
KoPathSegment.cpp
KoSelection.cpp
KoSelectedShapesProxy.cpp
KoSelectedShapesProxySimple.cpp
KoShape.cpp
KoShapeAnchor.cpp
KoShapeBasedDocumentBase.cpp
KoShapeApplicationData.cpp
KoShapeContainer.cpp
KoShapeContainerModel.cpp
KoShapeGroup.cpp
KoShapeManager.cpp
KoShapePaintingContext.cpp
KoFrameShape.cpp
KoUnavailShape.cpp
KoMarker.cpp
KoMarkerCollection.cpp
KoToolBase.cpp
KoCanvasController.cpp
KoCanvasControllerWidget.cpp
KoCanvasControllerWidgetViewport_p.cpp
KoShapeRegistry.cpp
KoDeferredShapeFactoryBase.cpp
KoToolFactoryBase.cpp
KoPathShapeFactory.cpp
KoShapeFactoryBase.cpp
KoShapeUserData.cpp
KoParameterShape.cpp
KoPointerEvent.cpp
KoShapeController.cpp
KoToolSelection.cpp
KoShapeLayer.cpp
KoPostscriptPaintDevice.cpp
KoInputDevice.cpp
KoToolManager_p.cpp
KoToolManager.cpp
KoToolRegistry.cpp
KoToolProxy.cpp
KoShapeSavingContext.cpp
KoShapeLoadingContext.cpp
KoLoadingShapeUpdater.cpp
KoPathShapeLoader.cpp
KoShapeStrokeModel.cpp
KoShapeStroke.cpp
KoShapeBackground.cpp
KoColorBackground.cpp
KoGradientBackground.cpp
KoOdfGradientBackground.cpp
KoHatchBackground.cpp
KoPatternBackground.cpp
KoVectorPatternBackground.cpp
KoShapeConfigWidgetBase.cpp
KoDrag.cpp
KoSvgPaste.cpp
KoDragOdfSaveHelper.cpp
KoShapeOdfSaveHelper.cpp
KoConnectionPoint.cpp
KoConnectionShape.cpp
KoConnectionShapeLoadingUpdater.cpp
KoConnectionShapeFactory.cpp
KoConnectionShapeConfigWidget.cpp
KoSnapGuide.cpp
KoSnapProxy.cpp
KoSnapStrategy.cpp
KoSnapData.cpp
KoShapeShadow.cpp
KoSharedLoadingData.cpp
KoSharedSavingData.cpp
KoViewConverter.cpp
KoInputDeviceHandler.cpp
KoInputDeviceHandlerEvent.cpp
KoInputDeviceHandlerRegistry.cpp
KoImageData.cpp
KoImageData_p.cpp
KoImageCollection.cpp
KoOdfWorkaround.cpp
KoFilterEffect.cpp
KoFilterEffectStack.cpp
KoFilterEffectFactoryBase.cpp
KoFilterEffectRegistry.cpp
KoFilterEffectConfigWidgetBase.cpp
KoFilterEffectRenderContext.cpp
KoFilterEffectLoadingContext.cpp
KoTextShapeDataBase.cpp
KoTosContainer.cpp
KoTosContainerModel.cpp
KoClipPath.cpp
KoClipMask.cpp
KoClipMaskPainter.cpp
KoCurveFit.cpp
commands/KoShapeGroupCommand.cpp
commands/KoShapeAlignCommand.cpp
commands/KoShapeBackgroundCommand.cpp
commands/KoShapeCreateCommand.cpp
commands/KoShapeDeleteCommand.cpp
commands/KoShapeDistributeCommand.cpp
commands/KoShapeLockCommand.cpp
commands/KoShapeMoveCommand.cpp
commands/KoShapeResizeCommand.cpp
commands/KoShapeShearCommand.cpp
commands/KoShapeSizeCommand.cpp
commands/KoShapeStrokeCommand.cpp
commands/KoShapeUngroupCommand.cpp
commands/KoShapeReorderCommand.cpp
commands/KoShapeKeepAspectRatioCommand.cpp
commands/KoPathBaseCommand.cpp
commands/KoPathPointMoveCommand.cpp
commands/KoPathControlPointMoveCommand.cpp
commands/KoPathPointTypeCommand.cpp
commands/KoPathPointRemoveCommand.cpp
commands/KoPathPointInsertCommand.cpp
commands/KoPathSegmentBreakCommand.cpp
commands/KoPathBreakAtPointCommand.cpp
commands/KoPathSegmentTypeCommand.cpp
commands/KoPathCombineCommand.cpp
commands/KoSubpathRemoveCommand.cpp
commands/KoSubpathJoinCommand.cpp
commands/KoParameterHandleMoveCommand.cpp
commands/KoParameterToPathCommand.cpp
commands/KoShapeTransformCommand.cpp
commands/KoPathFillRuleCommand.cpp
commands/KoConnectionShapeTypeCommand.cpp
commands/KoShapeShadowCommand.cpp
commands/KoPathReverseCommand.cpp
commands/KoShapeRenameCommand.cpp
commands/KoShapeRunAroundCommand.cpp
commands/KoPathPointMergeCommand.cpp
commands/KoShapeTransparencyCommand.cpp
commands/KoShapeClipCommand.cpp
commands/KoShapeUnclipCommand.cpp
commands/KoPathShapeMarkerCommand.cpp
commands/KoShapeConnectionChangeCommand.cpp
commands/KoMultiPathPointMergeCommand.cpp
commands/KoMultiPathPointJoinCommand.cpp
tools/KoCreateShapeStrategy.cpp
tools/KoPathToolFactory.cpp
tools/KoPathTool.cpp
tools/KoPathToolSelection.cpp
tools/KoPathToolHandle.cpp
tools/PathToolOptionWidget.cpp
tools/KoPathPointRubberSelectStrategy.cpp
tools/KoPathPointMoveStrategy.cpp
tools/KoPathConnectionPointStrategy.cpp
tools/KoPathControlPointMoveStrategy.cpp
tools/KoParameterChangeStrategy.cpp
tools/KoZoomTool.cpp
tools/KoZoomToolFactory.cpp
tools/KoZoomToolWidget.cpp
tools/KoZoomStrategy.cpp
tools/KoPanTool.cpp
tools/KoPanToolFactory.cpp
tools/KoInteractionTool.cpp
tools/KoInteractionStrategy.cpp
tools/KoInteractionStrategyFactory.cpp
tools/KoCreateShapesTool.cpp
tools/KoCreateShapesToolFactory.cpp
tools/KoShapeRubberSelectStrategy.cpp
tools/KoPathSegmentChangeStrategy.cpp
svg/KoShapePainter.cpp
svg/SvgUtil.cpp
svg/SvgGraphicContext.cpp
svg/SvgSavingContext.cpp
svg/SvgWriter.cpp
svg/SvgStyleWriter.cpp
svg/SvgShape.cpp
svg/SvgParser.cpp
svg/SvgStyleParser.cpp
svg/SvgGradientHelper.cpp
svg/SvgFilterHelper.cpp
svg/SvgCssHelper.cpp
svg/SvgClipPathHelper.cpp
svg/SvgLoadingContext.cpp
svg/SvgShapeFactory.cpp
svg/parsers/SvgTransformParser.cpp
+ resources/KoSvgSymbolCollectionResource.cpp
+
FlakeDebug.cpp
tests/MockShapes.cpp
)
ki18n_wrap_ui(kritaflake_SRCS
tools/PathToolOptionWidgetBase.ui
KoConnectionShapeConfigWidget.ui
tools/KoZoomToolWidget.ui
)
add_library(kritaflake SHARED ${kritaflake_SRCS})
generate_export_header(kritaflake BASE_NAME kritaflake)
target_include_directories(kritaflake
PUBLIC
$
$
$
)
target_link_libraries(kritaflake kritapigment kritawidgetutils kritaodf kritacommand KF5::WidgetsAddons Qt5::Svg)
set_target_properties(kritaflake PROPERTIES
VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION}
)
install(TARGETS kritaflake ${INSTALL_TARGETS_DEFAULT_ARGS})
+
diff --git a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
index 7cc545349e..5ab19095b7 100644
--- a/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
+++ b/libs/flake/KoCanvasControllerWidgetViewport_p.cpp
@@ -1,373 +1,408 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2009 Thomas Zander
* Copyright (C) 2006 Thorsten Zachmann
* Copyright (C) 2007-2010 Boudewijn Rempt
*
* 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 "KoCanvasControllerWidgetViewport_p.h"
#include
#include
#include
#include
#include
#include
#include
#include "KoShape.h"
#include "KoShape_p.h"
#include "KoShapeFactoryBase.h" // for the SHAPE mimetypes
#include "KoShapeRegistry.h"
#include "KoShapeController.h"
#include "KoShapeManager.h"
#include "KoSelection.h"
#include "KoCanvasBase.h"
#include "KoShapeLayer.h"
#include "KoShapePaintingContext.h"
#include "KoToolProxy.h"
#include "KoCanvasControllerWidget.h"
#include "KoViewConverter.h"
-
+#include "KoSvgPaste.h"
// ********** Viewport **********
Viewport::Viewport(KoCanvasControllerWidget *parent)
- : QWidget(parent)
- , m_draggedShape(0)
- , m_drawShadow(false)
- , m_canvas(0)
- , m_documentOffset(QPoint(0, 0))
- , m_margin(0)
+ : QWidget(parent)
+ , m_draggedShape(0)
+ , m_drawShadow(false)
+ , m_canvas(0)
+ , m_documentOffset(QPoint(0, 0))
+ , m_margin(0)
{
setAutoFillBackground(true);
setAcceptDrops(true);
setMouseTracking(true);
m_parent = parent;
}
void Viewport::setCanvas(QWidget *canvas)
{
if (m_canvas) {
m_canvas->hide();
delete m_canvas;
}
m_canvas = canvas;
if (!canvas) return;
m_canvas->setParent(this);
m_canvas->show();
if (!m_canvas->minimumSize().isNull()) {
m_documentSize = m_canvas->minimumSize();
}
resetLayout();
}
void Viewport::setDocumentSize(const QSize &size)
{
m_documentSize = size;
resetLayout();
}
void Viewport::documentOffsetMoved(const QPoint &pt)
{
m_documentOffset = pt;
resetLayout();
}
void Viewport::setDrawShadow(bool drawShadow)
{
m_drawShadow = drawShadow;
}
void Viewport::handleDragEnterEvent(QDragEnterEvent *event)
{
// if not a canvas set then ignore this, makes it possible to assume
// we have a canvas in all the support methods.
if (!(m_parent->canvas() && m_parent->canvas()->canvasWidget())) {
event->ignore();
return;
}
+ delete m_draggedShape;
+ m_draggedShape = 0;
+
// only allow dropping when active layer is editable
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
KoShapeLayer *activeLayer = selection->activeLayer();
if (activeLayer && (!activeLayer->isEditable() || activeLayer->isGeometryProtected())) {
event->ignore();
return;
}
const QMimeData *data = event->mimeData();
+
if (data->hasFormat(SHAPETEMPLATE_MIMETYPE) ||
- data->hasFormat(SHAPEID_MIMETYPE)) {
- QByteArray itemData;
- bool isTemplate = true;
- if (data->hasFormat(SHAPETEMPLATE_MIMETYPE))
- itemData = data->data(SHAPETEMPLATE_MIMETYPE);
- else {
- isTemplate = false;
- itemData = data->data(SHAPEID_MIMETYPE);
+ data->hasFormat(SHAPEID_MIMETYPE) ||
+ data->hasFormat("image/svg+xml"))
+ {
+ if (data->hasFormat("image/svg+xml")) {
+ KoCanvasBase *canvas = m_parent->canvas();
+ QSizeF fragmentSize;
+
+ QList shapes = KoSvgPaste::fetchShapesFromData(data->data("image/svg+xml"),
+ canvas->shapeController()->documentRectInPixels(),
+ canvas->shapeController()->pixelsPerInch(),
+ &fragmentSize);
+
+ if (!shapes.isEmpty()) {
+ m_draggedShape = shapes[0];
+ }
}
- QDataStream dataStream(&itemData, QIODevice::ReadOnly);
- QString id;
- dataStream >> id;
- QString properties;
- if (isTemplate)
- dataStream >> properties;
-
- // and finally, there is a point.
- QPointF offset;
- dataStream >> offset;
-
- // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy
- // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave)
- KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
- if (! factory) {
- warnFlake << "Application requested a shape that is not registered '" <<
- id << "', Ignoring";
- event->ignore();
- return;
+ else {
+ QByteArray itemData;
+ bool isTemplate = true;
+
+ if (data->hasFormat(SHAPETEMPLATE_MIMETYPE)) {
+ itemData = data->data(SHAPETEMPLATE_MIMETYPE);
+ }
+ else if (data->hasFormat(SHAPEID_MIMETYPE)) {
+ isTemplate = false;
+ itemData = data->data(SHAPEID_MIMETYPE);
+ }
+
+
+ QDataStream dataStream(&itemData, QIODevice::ReadOnly);
+ QString id;
+ dataStream >> id;
+ QString properties;
+ if (isTemplate)
+ dataStream >> properties;
+
+ // and finally, there is a point.
+ QPointF offset;
+ dataStream >> offset;
+
+ // The rest of this method is mostly a copy paste from the KoCreateShapeStrategy
+ // So, lets remove this again when Zagge adds his new class that does this kind of thing. (KoLoadSave)
+ KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(id);
+ if (! factory) {
+ warnFlake << "Application requested a shape that is not registered '" <<
+ id << "', Ignoring";
+ event->ignore();
+ return;
+ }
+ if (isTemplate) {
+ KoProperties props;
+ props.load(properties);
+ m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager());
+ }
+ else {
+ m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager());
+ }
+
+ if (m_draggedShape->shapeId().isEmpty()) {
+ m_draggedShape->setShapeId(factory->id());
+ }
}
+
event->setDropAction(Qt::CopyAction);
event->accept();
- if (isTemplate) {
- KoProperties props;
- props.load(properties);
- m_draggedShape = factory->createShape(&props, m_parent->canvas()->shapeController()->resourceManager());
- } else
- m_draggedShape = factory->createDefaultShape(m_parent->canvas()->shapeController()->resourceManager());
-
Q_ASSERT(m_draggedShape);
if (!m_draggedShape) return;
- if (m_draggedShape->shapeId().isEmpty())
- m_draggedShape->setShapeId(factory->id());
m_draggedShape->setZIndex(KoShapePrivate::MaxZIndex);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_parent->canvas()->shapeManager()->addShape(m_draggedShape);
} else {
event->ignore();
}
}
void Viewport::handleDropEvent(QDropEvent *event)
{
if (!m_draggedShape) {
m_parent->canvas()->toolProxy()->dropEvent(event, correctPosition(event->pos()));
return;
}
repaint(m_draggedShape);
m_parent->canvas()->shapeManager()->remove(m_draggedShape); // remove it to not interfere with z-index calc.
m_draggedShape->setPosition(QPointF(0, 0)); // always save position.
QPointF newPos = correctPosition(event->pos());
m_parent->canvas()->clipToDocument(m_draggedShape, newPos); // ensure the shape is dropped inside the document.
m_draggedShape->setAbsolutePosition(newPos);
+
+
KUndo2Command * cmd = m_parent->canvas()->shapeController()->addShape(m_draggedShape);
+
if (cmd) {
m_parent->canvas()->addCommand(cmd);
KoSelection *selection = m_parent->canvas()->shapeManager()->selection();
// repaint selection before selecting newly create shape
- Q_FOREACH (KoShape * shape, selection->selectedShapes())
+ Q_FOREACH (KoShape * shape, selection->selectedShapes()) {
shape->update();
+ }
selection->deselectAll();
selection->select(m_draggedShape);
- } else
+ } else {
+
delete m_draggedShape;
+ }
m_draggedShape = 0;
}
QPointF Viewport::correctPosition(const QPoint &point) const
{
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
QPoint correctedPos(point.x() - canvasWidget->x(), point.y() - canvasWidget->y());
correctedPos += m_documentOffset;
return m_parent->canvas()->viewToDocument(correctedPos);
}
void Viewport::handleDragMoveEvent(QDragMoveEvent *event)
{
if (!m_draggedShape) {
m_parent->canvas()->toolProxy()->dragMoveEvent(event, correctPosition(event->pos()));
return;
}
m_draggedShape->update();
repaint(m_draggedShape);
m_draggedShape->setAbsolutePosition(correctPosition(event->pos()));
m_draggedShape->update();
repaint(m_draggedShape);
}
void Viewport::repaint(KoShape *shape)
{
QRect rect = m_parent->canvas()->viewConverter()->documentToView(shape->boundingRect()).toRect();
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
rect.moveLeft(rect.left() + canvasWidget->x() - m_documentOffset.x());
rect.moveTop(rect.top() + canvasWidget->y() - m_documentOffset.y());
rect.adjust(-2, -2, 2, 2); // adjust for antialias
update(rect);
}
void Viewport::handleDragLeaveEvent(QDragLeaveEvent *event)
{
if (m_draggedShape) {
repaint(m_draggedShape);
m_parent->canvas()->shapeManager()->remove(m_draggedShape);
delete m_draggedShape;
m_draggedShape = 0;
} else {
m_parent->canvas()->toolProxy()->dragLeaveEvent(event);
}
}
void Viewport::handlePaintEvent(QPainter &painter, QPaintEvent *event)
{
Q_UNUSED(event);
// Draw the shadow around the canvas.
if (m_parent->canvas() && m_parent->canvas()->canvasWidget() && m_drawShadow) {
QWidget *canvas = m_parent->canvas()->canvasWidget();
painter.setPen(QPen(Qt::black, 0));
QRect rect(canvas->x(), canvas->y(), canvas->width(), canvas->height());
rect.adjust(-1, -1, 0, 0);
painter.drawRect(rect);
painter.drawLine(rect.right() + 2, rect.top() + 2, rect.right() + 2, rect.bottom() + 2);
painter.drawLine(rect.left() + 2, rect.bottom() + 2, rect.right() + 2, rect.bottom() + 2);
}
if (m_draggedShape) {
const KoViewConverter *vc = m_parent->canvas()->viewConverter();
painter.save();
QWidget *canvasWidget = m_parent->canvas()->canvasWidget();
Q_ASSERT(canvasWidget); // since we should not allow drag if there is not.
painter.translate(canvasWidget->x() - m_documentOffset.x(),
- canvasWidget->y() - m_documentOffset.y());
+ canvasWidget->y() - m_documentOffset.y());
QPointF offset = vc->documentToView(m_draggedShape->position());
painter.setOpacity(0.6);
painter.translate(offset.x(), offset.y());
painter.setRenderHint(QPainter::Antialiasing);
KoShapePaintingContext paintContext; //FIXME
m_draggedShape->paint(painter, *vc, paintContext);
painter.restore();
}
}
void Viewport::resetLayout()
{
// Determine the area we have to show
QRect viewRect(m_documentOffset, size());
const int viewH = viewRect.height();
const int viewW = viewRect.width();
const int docH = m_documentSize.height();
const int docW = m_documentSize.width();
int moveX = 0;
int moveY = 0;
int resizeW = viewW;
int resizeH = viewH;
-// debugFlake <<"viewH:" << viewH << endl
-// << "docH: " << docH << endl
-// << "viewW: " << viewW << endl
-// << "docW: " << docW << endl;
+ // debugFlake <<"viewH:" << viewH << endl
+ // << "docH: " << docH << endl
+ // << "viewW: " << viewW << endl
+ // << "docW: " << docW << endl;
if (viewH == docH && viewW == docW) {
// Do nothing
resizeW = docW;
resizeH = docH;
} else if (viewH > docH && viewW > docW) {
// Show entire canvas centered
moveX = (viewW - docW) / 2;
moveY = (viewH - docH) / 2;
resizeW = docW;
resizeH = docH;
} else if (viewW > docW) {
// Center canvas horizontally
moveX = (viewW - docW) / 2;
resizeW = docW;
int marginTop = m_margin - m_documentOffset.y();
int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y());
if (marginTop > 0) moveY = marginTop;
if (marginTop > 0) resizeH = viewH - marginTop;
if (marginBottom > 0) resizeH = viewH - marginBottom;
} else if (viewH > docH) {
// Center canvas vertically
moveY = (viewH - docH) / 2;
resizeH = docH;
int marginLeft = m_margin - m_documentOffset.x();
int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x());
if (marginLeft > 0) moveX = marginLeft;
if (marginLeft > 0) resizeW = viewW - marginLeft;
if (marginRight > 0) resizeW = viewW - marginRight;
} else {
// Take care of the margin around the canvas
int marginTop = m_margin - m_documentOffset.y();
int marginLeft = m_margin - m_documentOffset.x();
int marginRight = viewW - (m_documentSize.width() - m_documentOffset.x());
int marginBottom = viewH - (m_documentSize.height() - m_documentOffset.y());
if (marginTop > 0) moveY = marginTop;
if (marginLeft > 0) moveX = marginLeft;
if (marginTop > 0) resizeH = viewH - marginTop;
if (marginLeft > 0) resizeW = viewW - marginLeft;
if (marginRight > 0) resizeW = viewW - marginRight;
if (marginBottom > 0) resizeH = viewH - marginBottom;
}
if (m_parent->canvasMode() == KoCanvasController::AlignTop) {
// have up to m_margin pixels at top.
moveY = qMin(m_margin, moveY);
}
if (m_canvas) {
QRect geom;
if (m_parent->canvasMode() == KoCanvasController::Infinite)
geom = QRect(0, 0, viewW, viewH);
else
geom = QRect(moveX, moveY, resizeW, resizeH);
if (m_canvas->geometry() != geom) {
m_canvas->setGeometry(geom);
m_canvas->update();
}
}
if (m_drawShadow) {
update();
}
emit sizeChanged();
#if 0
- debugFlake <<"View port geom:" << geometry();
- if (m_canvas)
+ debugFlake <<"View port geom:" << geometry();
+ if (m_canvas)
debugFlake <<"Canvas widget geom:" << m_canvas->geometry();
#endif
}
diff --git a/libs/flake/KoDrag.cpp b/libs/flake/KoDrag.cpp
index 08d5ccd328..c3be65f33f 100644
--- a/libs/flake/KoDrag.cpp
+++ b/libs/flake/KoDrag.cpp
@@ -1,112 +1,113 @@
/* This file is part of the KDE project
* Copyright (C) 2007-2008 Thorsten Zachmann
* Copyright (C) 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 "KoDrag.h"
#include "KoDragOdfSaveHelper.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoShapeSavingContext.h"
#include
#include
#include
class KoDragPrivate {
public:
KoDragPrivate() : mimeData(0) { }
~KoDragPrivate() { delete mimeData; }
QMimeData *mimeData;
};
KoDrag::KoDrag()
: d(new KoDragPrivate())
{
}
KoDrag::~KoDrag()
{
delete d;
}
bool KoDrag::setSvg(const QList originalShapes)
{
QRectF boundingRect;
QList shapes;
Q_FOREACH (KoShape *shape, originalShapes) {
boundingRect |= shape->boundingRect();
shapes.append(shape->cloneShape());
}
qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
QBuffer buffer;
QLatin1String mimeType("image/svg+xml");
buffer.open(QIODevice::WriteOnly);
const QSizeF pageSize(boundingRect.right(), boundingRect.bottom());
SvgWriter writer(shapes, pageSize);
writer.save(buffer);
buffer.close();
+
qDeleteAll(shapes);
setData(mimeType, buffer.data());
return true;
}
void KoDrag::setData(const QString &mimeType, const QByteArray &data)
{
if (d->mimeData == 0) {
d->mimeData = new QMimeData();
}
d->mimeData->setData(mimeType, data);
}
void KoDrag::addToClipboard()
{
if (d->mimeData) {
QApplication::clipboard()->setMimeData(d->mimeData);
d->mimeData = 0;
}
}
QMimeData * KoDrag::mimeData()
{
QMimeData *mimeData = d->mimeData;
d->mimeData = 0;
return mimeData;
}
diff --git a/libs/flake/KoShapeController.cpp b/libs/flake/KoShapeController.cpp
index d277c9366b..0c67c96b15 100644
--- a/libs/flake/KoShapeController.cpp
+++ b/libs/flake/KoShapeController.cpp
@@ -1,212 +1,212 @@
/* This file is part of the KDE project
*
* Copyright (C) 2006-2007, 2010 Thomas Zander
* Copyright (C) 2006-2008 Thorsten Zachmann
* Copyright (C) 2011 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.
*/
#include "KoShapeController.h"
#include "KoShapeBasedDocumentBase.h"
#include "KoShapeRegistry.h"
#include "KoDocumentResourceManager.h"
#include "KoShapeManager.h"
#include "KoShapeLayer.h"
#include "KoSelection.h"
#include "commands/KoShapeCreateCommand.h"
#include "commands/KoShapeDeleteCommand.h"
#include "commands/KoShapeConnectionChangeCommand.h"
#include "KoCanvasBase.h"
#include "KoShapeConfigWidgetBase.h"
#include "KoShapeFactoryBase.h"
#include "KoShape.h"
#include "KoConnectionShape.h"
#include
#include
#include
#include
class KoShapeController::Private
{
public:
Private()
: canvas(0),
shapeBasedDocument(0)
{
}
KoCanvasBase *canvas;
KoShapeBasedDocumentBase *shapeBasedDocument;
KUndo2Command* addShape(KoShape *shape, bool showDialog, KUndo2Command *parent) {
if (canvas) {
- if (showDialog) {
+ if (showDialog && !shape->shapeId().isEmpty()) {
KoShapeFactoryBase *factory = KoShapeRegistry::instance()->value(shape->shapeId());
Q_ASSERT(factory);
int z = 0;
Q_FOREACH (KoShape *sh, canvas->shapeManager()->shapes())
z = qMax(z, sh->zIndex());
shape->setZIndex(z + 1);
// show config dialog.
KPageDialog *dialog = new KPageDialog(canvas->canvasWidget());
dialog->setWindowTitle(i18n("%1 Options", factory->name()));
int pageCount = 0;
QList widgets;
Q_FOREACH (KoShapeConfigWidgetBase* panel, factory->createShapeOptionPanels()) {
if (! panel->showOnShapeCreate())
continue;
panel->open(shape);
panel->connect(panel, SIGNAL(accept()), dialog, SLOT(accept()));
widgets.append(panel);
panel->setResourceManager(canvas->resourceManager());
panel->setUnit(canvas->unit());
QString title = panel->windowTitle().isEmpty() ? panel->objectName() : panel->windowTitle();
dialog->addPage(panel, title);
pageCount ++;
}
if (pageCount > 0) {
if (pageCount > 1)
dialog->setFaceType(KPageDialog::Tabbed);
if (dialog->exec() != KPageDialog::Accepted) {
delete dialog;
return 0;
}
Q_FOREACH (KoShapeConfigWidgetBase *widget, widgets)
widget->save();
}
delete dialog;
}
}
return addShapesDirect({shape}, parent);
}
KUndo2Command* addShapesDirect(const QList shapes, KUndo2Command *parent)
{
return new KoShapeCreateCommand(shapeBasedDocument, shapes, parent);
}
void handleAttachedConnections(KoShape *shape, KUndo2Command *parentCmd) {
foreach (KoShape *dependee, shape->dependees()) {
KoConnectionShape *connection = dynamic_cast(dependee);
if (connection) {
if (shape == connection->firstShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::StartHandle,
shape, connection->firstConnectionId(), 0, -1, parentCmd);
} else if (shape == connection->secondShape()) {
new KoShapeConnectionChangeCommand(connection, KoConnectionShape::EndHandle,
shape, connection->secondConnectionId(), 0, -1, parentCmd);
}
}
}
}
};
KoShapeController::KoShapeController(KoCanvasBase *canvas, KoShapeBasedDocumentBase *shapeBasedDocument)
: d(new Private())
{
d->canvas = canvas;
d->shapeBasedDocument = shapeBasedDocument;
if (shapeBasedDocument) {
shapeBasedDocument->resourceManager()->setShapeController(this);
}
}
KoShapeController::~KoShapeController()
{
delete d;
}
void KoShapeController::reset()
{
d->canvas = 0;
d->shapeBasedDocument = 0;
}
KUndo2Command* KoShapeController::addShape(KoShape *shape, KUndo2Command *parent)
{
return d->addShape(shape, true, parent);
}
KUndo2Command* KoShapeController::addShapeDirect(KoShape *shape, KUndo2Command *parent)
{
return d->addShapesDirect({shape}, parent);
}
KUndo2Command *KoShapeController::addShapesDirect(const QList shapes, KUndo2Command *parent)
{
return d->addShapesDirect(shapes, parent);
}
KUndo2Command* KoShapeController::removeShape(KoShape *shape, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shape, parent);
QList shapes;
shapes.append(shape);
d->shapeBasedDocument->shapesRemoved(shapes, cmd);
// detach shape from any attached connection shapes
d->handleAttachedConnections(shape, cmd);
return cmd;
}
KUndo2Command* KoShapeController::removeShapes(const QList &shapes, KUndo2Command *parent)
{
KUndo2Command *cmd = new KoShapeDeleteCommand(d->shapeBasedDocument, shapes, parent);
d->shapeBasedDocument->shapesRemoved(shapes, cmd);
foreach (KoShape *shape, shapes) {
d->handleAttachedConnections(shape, cmd);
}
return cmd;
}
void KoShapeController::setShapeControllerBase(KoShapeBasedDocumentBase *shapeBasedDocument)
{
d->shapeBasedDocument = shapeBasedDocument;
}
QRectF KoShapeController::documentRectInPixels() const
{
return d->shapeBasedDocument ? d->shapeBasedDocument->documentRectInPixels() : QRectF(0,0,1920,1080);
}
qreal KoShapeController::pixelsPerInch() const
{
return d->shapeBasedDocument ? d->shapeBasedDocument->pixelsPerInch() : 72.0;
}
QRectF KoShapeController::documentRect() const
{
return d->shapeBasedDocument ? d->shapeBasedDocument->documentRect() : documentRectInPixels();
}
KoDocumentResourceManager *KoShapeController::resourceManager() const
{
if (!d->shapeBasedDocument)
return 0;
return d->shapeBasedDocument->resourceManager();
}
KoShapeBasedDocumentBase *KoShapeController::documentBase() const
{
return d->shapeBasedDocument;
}
diff --git a/libs/flake/KoSvgPaste.cpp b/libs/flake/KoSvgPaste.cpp
index 205cca0285..6da3f9abb7 100644
--- a/libs/flake/KoSvgPaste.cpp
+++ b/libs/flake/KoSvgPaste.cpp
@@ -1,72 +1,82 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KoSvgPaste.h"
#include
#include
#include
#include
#include
#include
#include
#include
-KoSvgPaste::KoSvgPaste()
-{
-}
-
-bool KoSvgPaste::hasShapes() const
+bool KoSvgPaste::hasShapes()
{
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
return mimeData && mimeData->hasFormat("image/svg+xml");
}
-QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize) const
+QList KoSvgPaste::fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize)
{
QList shapes;
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
if (!mimeData) return shapes;
QByteArray data = mimeData->data("image/svg+xml");
- if (data.isEmpty()) return shapes;
+ if (data.isEmpty()) {
+ return shapes;
+ }
+
+ return fetchShapesFromData(data, viewportInPx, resolutionPPI, fragmentSize);
+
+}
+
+QList KoSvgPaste::fetchShapesFromData(const QByteArray &data, const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize)
+{
+ QList shapes;
+
+ if (data.isEmpty()) {
+ return shapes;
+ }
KoXmlDocument doc;
QString errorMsg;
int errorLine = 0;
int errorColumn = 0;
const bool documentValid = doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn);
if (!documentValid) {
- errorFlake << "Failed to process an SVG file at"
+ qWarning() << "Failed to process an SVG file at"
<< errorLine << ":" << errorColumn << "->" << errorMsg;
return shapes;
}
KoDocumentResourceManager resourceManager;
SvgParser parser(&resourceManager);
parser.setResolution(viewportInPx, resolutionPPI);
shapes = parser.parseSvg(doc.documentElement(), fragmentSize);
return shapes;
}
diff --git a/libs/flake/KoSvgPaste.h b/libs/flake/KoSvgPaste.h
index 98400cf6e1..6c44b9e3f8 100644
--- a/libs/flake/KoSvgPaste.h
+++ b/libs/flake/KoSvgPaste.h
@@ -1,38 +1,39 @@
/*
* Copyright (c) 2017 Dmitry Kazakov
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KOSVGPASTE_H
#define KOSVGPASTE_H
#include "kritaflake_export.h"
#include
class KoShape;
class QRectF;
class QSizeF;
+class QByteArray;
class KRITAFLAKE_EXPORT KoSvgPaste
{
public:
- KoSvgPaste();
+ static bool hasShapes();
+ static QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0);
+ static QList fetchShapesFromData(const QByteArray &data, const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0);
- bool hasShapes() const;
- QList fetchShapes(const QRectF viewportInPx, qreal resolutionPPI, QSizeF *fragmentSize = 0) const;
};
#endif // KOSVGPASTE_H
diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.cpp b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
new file mode 100644
index 0000000000..9b8e88433a
--- /dev/null
+++ b/libs/flake/resources/KoSvgSymbolCollectionResource.cpp
@@ -0,0 +1,253 @@
+/* This file is part of the KDE project
+
+ Copyright (c) 2017 L. E. Segovia
+
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include "kis_debug.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct KoSvgSymbolCollectionResource::Private {
+ QVector symbols;
+ QString title;
+ QString description;
+};
+
+
+KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const QString& filename)
+ : KoResource(filename)
+ , d(new Private())
+{
+}
+
+KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource()
+ : KoResource(QString())
+ , d(new Private())
+{
+}
+
+KoSvgSymbolCollectionResource::KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs)
+ : QObject(0)
+ , KoResource(QString())
+ , d(new Private())
+{
+ setFilename(rhs.filename());
+ d->symbols = rhs.d->symbols;
+ setValid(true);
+}
+
+KoSvgSymbolCollectionResource::~KoSvgSymbolCollectionResource()
+{
+}
+
+bool KoSvgSymbolCollectionResource::load()
+{
+ QFile file(filename());
+ if (file.size() == 0) return false;
+ if (!file.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+ bool res = loadFromDevice(&file);
+ file.close();
+ return res;
+}
+
+
+void paintGroup(KoShapeGroup *group, QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext)
+{
+ QList shapes = group->shapes();
+ qSort(shapes.begin(), shapes.end(), KoShape::compareShapeZIndex);
+ Q_FOREACH (KoShape *child, shapes) {
+ // we paint recursively here, so we do not have to check recursively for visibility
+ if (!child->isVisible())
+ continue;
+ KoShapeGroup *childGroup = dynamic_cast(child);
+ if (childGroup) {
+ paintGroup(childGroup, painter, converter, paintContext);
+ } else {
+ painter.save();
+ KoShapeManager::renderSingleShape(child, painter, converter, paintContext);
+ painter.restore();
+ }
+ }
+
+}
+
+bool KoSvgSymbolCollectionResource::loadFromDevice(QIODevice *dev)
+{
+ if (!dev->isOpen()) dev->open(QIODevice::ReadOnly);
+
+ KoXmlDocument doc;
+ QString errorMsg;
+ int errorLine = 0;
+ int errorColumn;
+
+ bool ok = doc.setContent(dev->readAll(), false, &errorMsg, &errorLine, &errorColumn);
+ if (!ok) {
+
+ errKrita << "Parsing error in " << filename() << "! 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 false;
+ }
+
+ KoDocumentResourceManager manager;
+ SvgParser parser(&manager);
+ parser.setResolution(QRectF(0,0,100,100), 72); // initialize with default values
+ QSizeF fragmentSize;
+ // We're not interested in the shapes themselves
+ qDeleteAll(parser.parseSvg(doc.documentElement(), &fragmentSize));
+ d->symbols = parser.takeSymbols();
+// qDebug() << "Loaded" << filename() << "\n\t"
+// << "Title" << parser.documentTitle() << "\n\t"
+// << "Description" << parser.documentDescription()
+// << "\n\tgot" << d->symbols.size() << "symbols"
+// << d->symbols[0]->shape->outlineRect()
+// << d->symbols[0]->shape->size();
+
+ d->title = parser.documentTitle();
+ setName(d->title);
+ d->description = parser.documentDescription();
+
+ if (d->symbols.size() < 1) {
+ setValid(false);
+ return false;
+ }
+ setValid(true);;
+
+ for(int i = 0; i < d->symbols.size(); ++i) {
+
+ KoSvgSymbol *symbol = d->symbols[i];
+ KoShapeGroup *group = dynamic_cast(symbol->shape);
+ Q_ASSERT(group);
+
+ QRectF rc = group->boundingRect().normalized();
+
+ QImage image(rc.width(), rc.height(), QImage::Format_ARGB32_Premultiplied);
+ QPainter gc(&image);
+ image.fill(Qt::gray);
+
+ KoViewConverter vc;
+ KoShapePaintingContext ctx;
+
+// qDebug() << "Going to render. Original bounding rect:" << group->boundingRect()
+// << "Normalized: " << rc
+// << "Scale W" << 256 / rc.width() << "Scale H" << 256 / rc.height();
+
+ gc.translate(-rc.x(), -rc.y());
+ paintGroup(group, gc, vc, ctx);
+ gc.end();
+ d->symbols[i]->icon = image.scaled(500, 500, Qt::KeepAspectRatio);
+
+ }
+
+ setImage(d->symbols[0]->icon);
+ return true;
+}
+
+bool KoSvgSymbolCollectionResource::save()
+{
+ QFile file(filename());
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ return false;
+ }
+ saveToDevice(&file);
+ file.close();
+ return true;
+}
+
+bool KoSvgSymbolCollectionResource::saveToDevice(QIODevice *dev) const
+{
+ bool res = false;
+ // XXX
+ if (res) {
+ KoResource::saveToDevice(dev);
+ }
+ return res;
+}
+
+QString KoSvgSymbolCollectionResource::defaultFileExtension() const
+{
+ return QString(".svg");
+}
+
+QString KoSvgSymbolCollectionResource::title() const
+{
+ return d->title;
+}
+
+QString KoSvgSymbolCollectionResource::description() const
+{
+ return d->description;
+}
+
+QString KoSvgSymbolCollectionResource::creator() const
+{
+ return "";
+}
+
+QString KoSvgSymbolCollectionResource::rights() const
+{
+ return "";
+}
+
+QString KoSvgSymbolCollectionResource::language() const
+{
+ return "";
+}
+
+QStringList KoSvgSymbolCollectionResource::subjects() const
+{
+ return QStringList();
+}
+
+QString KoSvgSymbolCollectionResource::license() const
+{
+ return "";
+}
+
+QStringList KoSvgSymbolCollectionResource::permits() const
+{
+ return QStringList();
+}
+
+QVector KoSvgSymbolCollectionResource::symbols() const
+{
+ return d->symbols;
+}
diff --git a/libs/flake/resources/KoSvgSymbolCollectionResource.h b/libs/flake/resources/KoSvgSymbolCollectionResource.h
new file mode 100644
index 0000000000..58fcc3de6e
--- /dev/null
+++ b/libs/flake/resources/KoSvgSymbolCollectionResource.h
@@ -0,0 +1,101 @@
+/* This file is part of the KDE project
+ Copyright (c) 2017 Boudewijn Rempt
+
+ 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ */
+#ifndef KOSVGSYMBOLCOLLECTIONRESOURCE
+#define KOSVGSYMBOLCOLLECTIONRESOURCE
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+
+#include "kritaflake_export.h"
+
+struct KoSvgSymbol {
+ KoSvgSymbol() {}
+ KoSvgSymbol(const QString &_title)
+ : title(_title) {}
+
+ ~KoSvgSymbol()
+ {
+ delete shape;
+ }
+
+ QString id;
+ QString title;
+ KoShape *shape;
+ QImage icon;
+
+ bool operator==(const KoSvgSymbol& rhs) const {
+ return title == rhs.title;
+ }
+};
+
+/**
+ * Loads an svg file that contains "symbol" objects and creates a collection of those objects.
+ */
+class KRITAFLAKE_EXPORT KoSvgSymbolCollectionResource : public QObject, public KoResource
+{
+ Q_OBJECT
+public:
+
+ /**
+ */
+ explicit KoSvgSymbolCollectionResource(const QString &filename);
+
+ /// Create an empty color set
+ KoSvgSymbolCollectionResource();
+
+ /// Explicit copy constructor (KoResource copy constructor is private)
+ KoSvgSymbolCollectionResource(const KoSvgSymbolCollectionResource& rhs);
+
+ ~KoSvgSymbolCollectionResource() override;
+
+ bool load() override;
+ bool loadFromDevice(QIODevice *dev) override;
+ bool save() override;
+ bool saveToDevice(QIODevice* dev) const override;
+
+ QString defaultFileExtension() const override;
+
+ QString title() const;
+ QString description() const;
+ QString creator() const;
+ QString rights() const;
+ QString language() const;
+ QStringList subjects() const;
+ QString license() const;
+ QStringList permits() const;
+
+ QVector symbols() const;
+
+
+private:
+
+ struct Private;
+ const QScopedPointer d;
+
+};
+#endif // KoSvgSymbolCollectionResource
+
diff --git a/libs/flake/svg/SvgParser.cpp b/libs/flake/svg/SvgParser.cpp
index 35de6b9972..4b38d9daa0 100644
--- a/libs/flake/svg/SvgParser.cpp
+++ b/libs/flake/svg/SvgParser.cpp
@@ -1,1545 +1,1622 @@
/* 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()
{
+ qDeleteAll(m_symbols);
}
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;
}
+QVector SvgParser::takeSymbols()
+{
+ QVector symbols = m_symbols;
+ m_symbols.clear();
+ return symbols;
+}
+
// 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 (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(),
+ * TODO: Please also note 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::parseSymbol(const KoXmlElement &e)
+{
+ const QString id = e.attribute("id");
+
+ if (id.isEmpty()) return false;
+
+ KoSvgSymbol *svgSymbol = new KoSvgSymbol();
+
+ // 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(0.0, 0.0, 1.0, 1.0);
+
+ QString title = e.firstChildElement("title").toElement().text();
+
+ KoShape *symbolShape = parseGroup(e);
+
+ m_context.popGraphicsContext();
+
+ if (!symbolShape) return false;
+
+ svgSymbol->shape = symbolShape;
+ svgSymbol->title = title;
+ svgSymbol->id = id;
+ if (title.isEmpty()) svgSymbol->title = id;
+
+ if (svgSymbol->shape->boundingRect() == QRectF(0.0, 0.0, 0.0, 0.0)) {
+ warnFlake << "Symbol" << id << "seems to be empty, discarding";
+ delete svgSymbol;
+ return false;
+ }
+
+ m_symbols << svgSymbol;
+
+ 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()) {
+ // First find the metadata
+ for (KoXmlNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) {
+ KoXmlElement b = n.toElement();
+ if (b.isNull())
+ continue;
+
+ if (b.tagName() == "title") {
+ m_documentTitle = b.text().trimmed();
+ }
+ else if (b.tagName() == "desc") {
+ m_documentDescription = b.text().trimmed();
+ }
+ else if (b.tagName() == "metadata") {
+ // TODO: parse the metadata
+ }
+ }
+
+
+ // SVG 1.1: skip the rendering of the element if it has null viewBox; however an inverted viewbox is just peachy
+ // and as mother makes them -- if mother is inkscape.
+ if (gc->currentBoundingBox.normalized().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();
}
+QString SvgParser::documentTitle() const
+{
+ return m_documentTitle;
+}
+
+QString SvgParser::documentDescription() const
+{
+ return m_documentDescription;
+}
+
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: