Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for free drawings (pencil tool) #34

Merged
merged 13 commits into from
Nov 25, 2023
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