Skip to content

Commit

Permalink
feat: Add support for free drawings (pencil tool) (#34)
Browse files Browse the repository at this point in the history
* feat: Add concepts/baseline for freedraw support

* feat: Add handling of embedded resources

* chore: Add freedraw to readme and regression test data

* chore: Update readme
  • Loading branch information
sindrel authored Nov 25, 2023
1 parent 8828a5a commit 8b56eac
Show file tree
Hide file tree
Showing 8 changed files with 3,041 additions and 145 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,14 @@ All fixed shapes and most styling and text options are supported.
* Ellipse
* Arrow
* Line
* Image
* Image :sparkles:
* Free drawing (pencil tool) :sparkles:

### Text
* Font family (Normal and Code)
* Font size
* Font color
* Horizontal and vertical alignment
* Horizontal and vertical alignment :sparkles:
* Text contained in shapes

### Styling
Expand All @@ -140,7 +141,7 @@ All fixed shapes and most styling and text options are supported.
* Stroke width
* Opacity

Free hand drawings are currently not supported. Library graphics are not fully supported (experimental).
Library graphics are not fully supported (experimental).

## Compatibility with draw.io
Converted Gliffy diagrams should also work in [draw.io](https://draw.io).
Expand Down
121 changes: 112 additions & 9 deletions internal/conversion/gliffy.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ func ConvertExcalidrawToGliffy(data string) (string, error) {

var output datastr.GliffyScene
var objects []datastr.GliffyObject

output.EmbeddedResources.Resources = []datastr.GliffyEmbeddedResource{}

objectIDs := map[string]int{}

objects, objectIDs, err = AddElements(false, input, objects, objectIDs)
objects, output, objectIDs, err = AddElements(false, input, output, objects, objectIDs)
if err != nil {
return "", errors.New("Unable to add element(s): " + err.Error())
}

objects, _, err = AddElements(true, input, objects, objectIDs)
objects, output, _, err = AddElements(true, input, output, objects, objectIDs)
if err != nil {
return "", errors.New("Unable to add element(s) with parent(s): " + err.Error())
}
Expand All @@ -84,7 +87,6 @@ func ConvertExcalidrawToGliffy(data string) (string, error) {
layer.Visible = true

output.ContentType = "application/gliffy+json"
output.EmbeddedResources.Resources = []string{}
output.Version = "1.3"
output.Metadata.LastSerialized = timestamp
output.Metadata.Libraries = []string{
Expand Down Expand Up @@ -116,7 +118,7 @@ func ConvertExcalidrawToGliffy(data string) (string, error) {
return string(outputJson), nil
}

func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []datastr.GliffyObject, objectIDs map[string]int) ([]datastr.GliffyObject, map[string]int, error) {
func AddElements(addChildren bool, input datastr.ExcalidrawScene, scene datastr.GliffyScene, objects []datastr.GliffyObject, objectIDs map[string]int) ([]datastr.GliffyObject, datastr.GliffyScene, map[string]int, error) {
graphics := internal.MapGraphics()

for i, element := range input.Elements {
Expand All @@ -132,6 +134,8 @@ func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []data
var text datastr.GliffyText
var line datastr.GliffyLine
var image datastr.GliffyImage
var svg datastr.GliffySvg
var embedded datastr.GliffyEmbeddedResource

object.X = element.X - xOffset
object.Y = element.Y - yOffset
Expand Down Expand Up @@ -185,6 +189,48 @@ func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []data
object.Graphic.Shape = &shape
}

for _, id := range graphics.Freedraw.Excalidraw {
if element.Type == id {
object.UID = graphics.Freedraw.Gliffy[0]
object.Graphic.Type = "Svg"

var embeddedResourceId = len(scene.EmbeddedResources.Resources) + 1

element.Width = element.Width + 8
element.Height = element.Height + 8

svg.EmbeddedResourceID = embeddedResourceId
svg.StrokeColor = element.StrokeColor
svg.StrokeWidth = int64(FreedrawStrokeWidthConvExcGliffy(element.StrokeWidth))
svg.DropShadow = false
svg.ShadowX = 0
svg.ShadowY = 0

var svgFill = "none"
if element.BackgroundColor != "transparent" {
svgFill = element.BackgroundColor
}

xMin, yMin, points := AddPointsOffset(element.Points)
var svgPath = ConvertPointsToSvgPath(points, element.Width, element.Height, svg.StrokeColor, svgFill, svg.StrokeWidth)
svg.Svg = svgPath

object.X = object.X + xMin
object.Y = object.Y + yMin

embedded.ID = embeddedResourceId
embedded.MimeType = "image/svg+xml"
embedded.Data = svgPath
embedded.X = 1
embedded.Y = 1
embedded.Width = element.Width
embedded.Height = element.Height
scene.EmbeddedResources.Resources = append(scene.EmbeddedResources.Resources, embedded)

object.Graphic.Svg = &svg
}
}

for _, id := range graphics.Text.Excalidraw {
if element.Type == id {
object.UID = graphics.Text.Gliffy[0]
Expand Down Expand Up @@ -219,7 +265,7 @@ func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []data
line.DashStyle = StrokeStyleConvExcGliffy(element.StrokeStyle)
line.StrokeColor = element.StrokeColor
line.StrokeWidth = int64(math.Round(element.StrokeWidth))
line.FillColor = "none"
line.FillColor = FillColorConvExcGliffy(element.BackgroundColor)
line.StartArrowRotation = "auto"
line.EndArrowRotation = "auto"
line.InterpolationType = "linear"
Expand All @@ -240,7 +286,7 @@ func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []data

dataUrl, err := EmbeddedImgConvExcGliffy(input, element.FileId)
if err != nil {
return nil, nil, err
return nil, scene, nil, err
}

image.Url = dataUrl
Expand Down Expand Up @@ -269,7 +315,7 @@ func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []data
}

if parent == 999999 {
return nil, nil, errors.New("unable to find object parent")
return nil, scene, nil, errors.New("unable to find object parent")
}

object.X = 2
Expand All @@ -290,7 +336,7 @@ func AddElements(addChildren bool, input datastr.ExcalidrawScene, objects []data
objects = append(objects, object)
}

return objects, objectIDs, nil
return objects, scene, objectIDs, nil
}

func StrokeStyleConvExcGliffy(style string) string {
Expand Down Expand Up @@ -337,6 +383,19 @@ func EmbeddedImgConvExcGliffy(input datastr.ExcalidrawScene, fileId string) (str
return file.DataUrl, nil
}

func FreedrawStrokeWidthConvExcGliffy(strokeWidth float64) float64 {
switch strokeWidth {
case 1:
strokeWidth = 2
case 2:
strokeWidth = 2
case 4:
strokeWidth = 6
}

return strokeWidth
}

func OrderGliffyObjectsByPriority(objects []datastr.GliffyObject, prioritized []string) []datastr.GliffyObject {
prioritySlot := len(objects)

Expand Down Expand Up @@ -377,7 +436,51 @@ func GetXYOffset(input datastr.ExcalidrawScene) (float64, float64) {
}
}

fmt.Printf(" Offset X: %f, Offset Y: %f\n", xMin, yMin)
fmt.Printf(" Canvas Offset X: %f, Offset Y: %f\n", xMin, yMin)

return xMin, yMin
}

func AddPointsOffset(points [][]float64) (float64, float64, [][]float64) {
var xMin float64 = 0
var yMin float64 = 0
var output [][]float64

for _, point := range points {
if point[0] < xMin {
xMin = point[0]
}

if point[1] < yMin {
yMin = point[1]
}
}

for _, point := range points {
x := point[0] + math.Abs(xMin)
y := point[1] + math.Abs(yMin)

output = append(output, []float64{x, y})
}

return xMin, yMin, output
}

func ConvertPointsToSvgPath(points [][]float64, width float64, height float64, stroke string, fill string, strokeWidth int64) string {
var path string

for i, point := range points {
var pointX = point[0] + 3
var pointY = point[1] + 3

if i == 0 {
path = fmt.Sprintf("M%.1f %.1f", pointX, pointY)
} else {
path = fmt.Sprintf("%sL%.1f %.1f", path, pointX, pointY)
}
}

svg := fmt.Sprintf("<svg width=\"%.0f\" height=\"%.0f\" viewBox=\"0 0 %.0f %.0f\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n<path fill=\"%s\" stroke=\"%s\" stroke-width=\"%d\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"%s\"></path>\n</svg>", width, height, width, height, fill, stroke, strokeWidth, path)

return svg
}
38 changes: 38 additions & 0 deletions internal/conversion/gliffy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package conversion

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestAddPointsOffset(t *testing.T) {
points := [][]float64{
{0, 0},
{32, 2},
{34, -4},
{-14, -8},
{-20, -10},
{-35, -13},
{-49, -16},
{-73, -16},
{-109, -12},
{-120, -7},
{-123, -5},
}

expected := [][]float64{
{123, 16}, {155, 18}, {157, 12}, {109, 8}, {103, 6}, {88, 3}, {74, 0}, {50, 0}, {14, 4}, {3, 9}, {0, 11},
}

xMin, yMin, result := AddPointsOffset(points)
fmt.Println(result)

svg := ConvertPointsToSvgPath(result, 297, 415, "none", "none", 2)
fmt.Println(svg)

assert.Equal(t, expected, result)
assert.Equal(t, float64(-123), xMin)
assert.Equal(t, float64(-16), yMin)
}
25 changes: 23 additions & 2 deletions internal/datastructures/gliffy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package internal
type GliffyScene struct {
ContentType string `json:"contentType"`
EmbeddedResources struct {
Index int64 `json:"index"`
Resources []string `json:"resources"`
Index int64 `json:"index"`
Resources []GliffyEmbeddedResource `json:"resources"`
} `json:"embeddedResources"`
Metadata struct {
AutosaveDisabled bool `json:"autosaveDisabled"`
Expand Down Expand Up @@ -51,6 +51,7 @@ type GliffyObject struct {
Text *GliffyText `json:",omitempty"`
Line *GliffyLine `json:",omitempty"`
Image *GliffyImage `json:",omitempty"`
Svg *GliffySvg `json:",omitempty"`
Type string `json:"type"`
} `json:"graphic"`
Height float64 `json:"height"`
Expand Down Expand Up @@ -127,6 +128,16 @@ type GliffyImage struct {
ShadowY int64 `json:"shadowY"`
}

type GliffySvg struct {
EmbeddedResourceID int `json:"embeddedResourceId"`
StrokeWidth int64 `json:"strokeWidth"`
StrokeColor string `json:"strokeColor"`
DropShadow bool `json:"dropShadow"`
Svg string `json:"svg"`
ShadowX int `json:"shadowX"`
ShadowY int `json:"shadowY"`
}

type GliffyLayer struct {
Active bool `json:"active"`
GUID string `json:"guid"`
Expand All @@ -136,3 +147,13 @@ type GliffyLayer struct {
Order int64 `json:"order"`
Visible bool `json:"visible"`
}

type GliffyEmbeddedResource struct {
ID int `json:"id"`
MimeType string `json:"mimeType"`
Data string `json:"data"`
X float64 `json:"x"`
Y float64 `json:"y"`
Width float64 `json:"width"`
Height float64 `json:"height"`
}
4 changes: 4 additions & 0 deletions internal/datastructures/internals.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ type GraphicTypes struct {
Excalidraw []string `json:"excalidraw"`
Gliffy []string `json:"gliffy"`
} `json:"image"`
Freedraw struct {
Excalidraw []string `json:"excalidraw"`
Gliffy []string `json:"gliffy"`
} `json:"freedraw"`
}

type GitHubRelease struct {
Expand Down
7 changes: 7 additions & 0 deletions internal/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,12 @@ func MapGraphics() datastr.GraphicTypes {
"image",
}

graphics.Freedraw.Gliffy = []string{
"com.gliffy.shape.basic.basic_v1.default.svg",
}
graphics.Freedraw.Excalidraw = []string{
"freedraw",
}

return graphics
}
Loading

0 comments on commit 8b56eac

Please sign in to comment.