package gliffy2drawio import ( "encoding/base64" "encoding/json" "fmt" "math" "regexp" "sort" "strconv" "strings" ) // Diagram is the root Gliffy document. type Diagram struct { ContentType string `json:"contentType"` Version string `json:"version"` Title string `json:"title"` DefaultPage string `json:"defaultPage"` Stage Stage `json:"stage"` Pages []GliffyPage `json:"pages"` Metadata Metadata `json:"metadata"` EmbeddedResources EmbeddedResources `json:"embeddedResources"` } type Metadata struct { Title string `json:"title"` } type Stage struct { Background string `json:"background"` Width float64 `json:"width"` Height float64 `json:"height"` AutoFit bool `json:"autoFit"` GridOn bool `json:"gridOn"` DrawingGuidesOn bool `json:"drawingGuidesOn"` Objects []*GliffyObject `json:"objects"` Layers []*GliffyLayer `json:"layers"` TextStyles TextStyles `json:"textStyles"` } type GliffyPage struct { ID string `json:"id"` Title string `json:"title"` Index int `json:"index"` Scene Scene `json:"scene"` } type Scene struct { Objects []*GliffyObject `json:"objects"` Layers []*GliffyLayer `json:"layers"` Background string `json:"background"` Width float64 `json:"width"` Height float64 `json:"height"` AutoFit bool `json:"autoFit"` GridOn bool `json:"gridOn"` DrawingGuidesOn bool `json:"drawingGuidesOn"` TextStyles TextStyles `json:"textStyles"` } type GliffyLayer struct { GUID string `json:"guid"` Order int `json:"order"` Name string `json:"name"` Active bool `json:"active"` Locked bool `json:"locked"` Visible bool `json:"visible"` NodeIndex int `json:"nodeIndex"` MxObject *MxCell `json:"-"` } type TextStyles struct { Global GlobalTextStyles `json:"global"` } type GlobalTextStyles struct { Size string `json:"size"` Color string `json:"color"` } func (g GlobalTextStyles) SizeValue() string { if g.Size == "" { return "" } idx := strings.Index(g.Size, "px") if idx == -1 { return g.Size } return g.Size[:idx] } type GraphicType string const ( GraphicTypeSVG GraphicType = "Svg" GraphicTypeLine GraphicType = "Line" GraphicTypeShape GraphicType = "Shape" GraphicTypeText GraphicType = "Text" GraphicTypeImage GraphicType = "Image" GraphicTypeLink GraphicType = "Link" GraphicTypeMindmap GraphicType = "Mindmap" GraphicTypePopup GraphicType = "PopupNote" GraphicTypeUnknown GraphicType = "Unwknown" ) type Graphic struct { Type GraphicType `json:"type"` Text *GliffyText `json:"Text"` Line *GliffyLine `json:"Line"` Link *GliffyLink `json:"Link"` Shape *GliffyShape `json:"Shape"` Image *GliffyImage `json:"Image"` Svg *GliffySvg `json:"Svg"` Mindmap *GliffyMindmap `json:"Mindmap"` PopupNote *GliffyPopupNote `json:"PopupNote"` } func (g *Graphic) GetType() GraphicType { if g == nil { return GraphicTypeUnknown } if g.Type == "" { return GraphicTypeUnknown } return g.Type } type GliffyAbstractShape struct { StrokeWidth float64 `json:"strokeWidth"` StrokeColor string `json:"strokeColor"` FillColor string `json:"fillColor"` DashStyle string `json:"dashStyle"` } func (s GliffyAbstractShape) StrokeWidthValue() int { return int(math.Round(s.StrokeWidth)) } type GliffyLine struct { GliffyAbstractShape StartArrow *int `json:"startArrow"` EndArrow *int `json:"endArrow"` Interpolation string `json:"interpolationType"` CornerRadius *int `json:"cornerRadius"` ControlPath [][]float64 `json:"controlPath"` } type GliffyShape struct { GliffyAbstractShape TID string `json:"tid"` Gradient bool `json:"gradient"` DropShadow bool `json:"dropShadow"` State int `json:"state"` ShadowX float64 `json:"shadowX"` ShadowY float64 `json:"shadowY"` Opacity float64 `json:"opacity"` } func (s GliffyShape) NoFill() bool { return s.TID != "" && strings.Contains(s.TID, "no_fill") } type GliffyImage struct { GliffyShape URL string `json:"url"` } func (g GliffyImage) CleanURL() string { return strings.ReplaceAll(g.URL, ";base64", "") } type GliffySvg struct { GliffyShape EmbeddedResourceID *int `json:"embeddedResourceId"` } type GliffyMindmap struct { GliffyShape } type GliffyPopupNote struct { GliffyShape Text string `json:"text"` } type GliffyLink struct { Href string `json:"href"` RenderIcon bool `json:"renderIcon"` } type EmbeddedResource struct { ID int `json:"id"` MimeType string `json:"mimeType"` Data string `json:"data"` } func (r EmbeddedResource) Base64EncodedData() string { return base64.StdEncoding.EncodeToString([]byte(r.Data)) } type EmbeddedResources struct { Resources []EmbeddedResource `json:"resources"` resourceMap map[int]EmbeddedResource } func (e *EmbeddedResources) Get(id int) (EmbeddedResource, bool) { if e == nil { return EmbeddedResource{}, false } if e.resourceMap == nil { e.resourceMap = make(map[int]EmbeddedResource, len(e.Resources)) for _, r := range e.Resources { e.resourceMap[r.ID] = r } } r, ok := e.resourceMap[id] return r, ok } type Constraints struct { Constraints []*Constraint `json:"constraints"` StartConstraint *Constraint `json:"startConstraint"` EndConstraint *Constraint `json:"endConstraint"` } type ConstraintType string const ( ConstraintTypeStart ConstraintType = "StartPositionConstraint" ConstraintTypeEnd ConstraintType = "EndPositionConstraint" ConstraintTypeHeight ConstraintType = "HeightConstraint" ) type Constraint struct { Type ConstraintType `json:"type"` StartPositionConstraint *ConstraintData `json:"StartPositionConstraint"` EndPositionConstraint *ConstraintData `json:"EndPositionConstraint"` } type ConstraintData struct { NodeID string `json:"nodeId"` PX float64 `json:"px"` PY float64 `json:"py"` } // GliffyText mirrors Gliffy's text object. type GliffyText struct { HTML string `json:"html"` Valign string `json:"valign"` Halign string `json:"-"` VPosition string `json:"vposition"` HPosition string `json:"hposition"` PaddingLeft int `json:"paddingLeft"` PaddingRight int `json:"paddingRight"` PaddingBottom int `json:"paddingBottom"` PaddingTop int `json:"paddingTop"` LineTValue float64 `json:"lineTValue"` LinePerpValue *float64 `json:"linePerpValue"` CardinalityType string `json:"cardinalityType"` Overflow string `json:"overflow"` forceTopPaddingShift bool } func (t *GliffyText) UnmarshalJSON(data []byte) error { type raw GliffyText var r raw if err := json.Unmarshal(data, &r); err != nil { return err } *t = GliffyText(r) if t.LineTValue == 0 { t.LineTValue = 0.5 } t.postDeserialize() return nil } func (t *GliffyText) postDeserialize() { t.Halign = t.extractHorizontalAlignment() t.convertTextAlign() t.replaceParagraphWithDiv() } func (t *GliffyText) GetStyle(x, y float64) string { var sb strings.Builder topPaddingShift := 7 switch t.VPosition { case "above": sb.WriteString("verticalLabelPosition=top;verticalAlign=bottom;") case "below": sb.WriteString("verticalLabelPosition=bottom;verticalAlign=top;") case "none": sb.WriteString("verticalAlign=" + t.Valign + ";") if !t.forceTopPaddingShift && strings.EqualFold(t.Valign, "middle") { topPaddingShift = 0 } } switch t.HPosition { case "left": sb.WriteString("labelPosition=left;align=right;") case "right": sb.WriteString("labelPosition=right;align=left;") case "none": if t.Halign != "" { sb.WriteString("align=" + t.Halign + ";") if strings.EqualFold(t.Halign, "right") { x = 0 } } else { sb.WriteString("align=center;") } } t.PaddingLeft = maxInt(0, t.PaddingLeft-2) t.PaddingRight = maxInt(0, t.PaddingRight-2) sb.WriteString("spacingLeft=" + floatToString(float64(t.PaddingLeft)+x) + ";") sb.WriteString("spacingRight=" + intToString(t.PaddingRight) + ";") if t.forceTopPaddingShift || !strings.EqualFold(t.Valign, "middle") { sb.WriteString("spacingTop=" + floatToString(float64(t.PaddingTop-topPaddingShift)+y) + ";") sb.WriteString("spacingBottom=" + intToString(t.PaddingBottom) + ";") } if t.Overflow == "none" { sb.WriteString("whiteSpace=wrap;") } return sb.String() } func (t *GliffyText) SetHAlign(value string) { t.Halign = value } func (t *GliffyText) SetVAlign(value string) { t.Valign = value } func (t *GliffyText) SetForceTopPaddingShift(v bool) { t.forceTopPaddingShift = v } var ( spanPattern = regexp.MustCompile(``) textAlign = regexp.MustCompile(`.*(text-align: ?(left|center|right);).*`) textAlignToDraw = regexp.MustCompile(`style="text-align:\s?(left|center|right);"`) lineHeight = regexp.MustCompile(`.*(line-height: .*px;).*`) ) func (t *GliffyText) replaceParagraphWithDiv() { m := spanPattern.FindAllStringSubmatchIndex(t.HTML, -1) if len(m) == 0 { t.HTML = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(t.HTML, "

", "

"), "

", "
") return } var mod strings.Builder last := 0 for _, match := range m { _, end := match[0], match[1] styleStart, styleEnd := match[2], match[3] span := t.HTML[last:end] style := t.HTML[styleStart:styleEnd] if style != "" { if !lineHeight.MatchString(style) { if strings.HasPrefix(strings.ToLower(t.HTML[end:]), "", "
"), "

", "
") } func (t *GliffyText) extractHorizontalAlignment() string { m := textAlign.FindStringSubmatch(t.HTML) if len(m) == 0 { return "" } return m[2] } func (t *GliffyText) convertTextAlign() { t.HTML = textAlignToDraw.ReplaceAllString(t.HTML, `align="$1"`) } // GliffyObject represents an element on the canvas. type GliffyObject struct { X float64 `json:"x"` Y float64 `json:"y"` ID string `json:"id"` Width float64 `json:"width"` Height float64 `json:"height"` Rotation float64 `json:"rotation"` UID string `json:"uid"` TID string `json:"tid"` Order OrderValue `json:"order"` LockShape bool `json:"lockshape"` LayerID *string `json:"layerId"` Graphic *Graphic `json:"graphic"` Children []*GliffyObject `json:"children"` Constraints *Constraints `json:"constraints"` MxObject *MxCell `json:"-"` Parent *GliffyObject `json:"-"` } // OrderValue supports both numeric and string orders found in Gliffy files. type OrderValue string func (o *OrderValue) UnmarshalJSON(data []byte) error { if len(data) == 0 { return nil } if data[0] == '"' { var s string if err := json.Unmarshal(data, &s); err != nil { return err } *o = OrderValue(s) return nil } var num json.Number if err := json.Unmarshal(data, &num); err == nil { *o = OrderValue(num.String()) return nil } *o = OrderValue(strings.Trim(string(data), "\"")) return nil } func (o OrderValue) String() string { return string(o) } func (o *GliffyObject) UnmarshalJSON(data []byte) error { type raw GliffyObject var r raw if err := json.Unmarshal(data, &r); err != nil { return err } *o = GliffyObject(r) o.postDeserialize() return nil } func (o *GliffyObject) postDeserialize() { if o.isGroup() && o.hasChildren() { o.normalizeChildrenCoordinates() o.adjustZOrder() } } func (o *GliffyObject) hasChildren() bool { return len(o.Children) > 0 } func (o *GliffyObject) GraphicOrChildGraphic() *Graphic { if o.Graphic != nil { return o.Graphic } if o.isUml() || graphicalessShapes[o.UID] { return o.firstChildGraphic() } return nil } func (o *GliffyObject) firstChildGraphic() *Graphic { if len(o.Children) == 0 { return nil } return o.Children[0].Graphic } func (o *GliffyObject) TextObject() *GliffyObject { return o.textObjectWithOffset(0, 0) } func (o *GliffyObject) textObjectWithOffset(x, y float64) *GliffyObject { if o.isText() { return o } if len(o.Children) == 0 { return nil } for _, child := range o.Children { if g := child.GraphicOrChildGraphic(); g != nil && g.GetType() == GraphicTypeText { child.X += x child.Y += y return child } if txt := child.textObjectWithOffset(child.X, child.Y); txt != nil { return txt } } return nil } func (o *GliffyObject) TextHTML() string { if o.Graphic == nil || o.Graphic.Text == nil { return "" } text := o.Graphic.Text widthDiff := -3 if text.Overflow != "none" { widthDiff = 6 } return "
" + text.HTML + "
" } func (o *GliffyObject) isGroup() bool { if !o.hasChildren() { return false } if o.UID != "" && (groupShapes[o.UID] || strings.HasPrefix(o.UID, "com.gliffy.shape.table")) { return true } if o.UID == "" && len(o.Children) > 0 && !o.Children[0].isText() { return true } return false } func (o *GliffyObject) isSelection() bool { return o.UID != "" && strings.Contains(o.UID, "default.selection") } func (o *GliffyObject) isMindmap() bool { return o.UID != "" && mindmapShapes[o.UID] } func (o *GliffyObject) isLine() bool { return o.Graphic != nil && o.Graphic.GetType() == GraphicTypeLine } func (o *GliffyObject) isLink() bool { return o.Graphic != nil && o.Graphic.GetType() == GraphicTypeLink } func (o *GliffyObject) isUml() bool { return strings.HasPrefix(o.UID, "com.gliffy.shape.uml.uml") } func (o *GliffyObject) isShape() bool { if o.Graphic != nil { t := o.Graphic.GetType() return t == GraphicTypeShape || t == GraphicTypeMindmap } g := o.firstChildGraphic() return g != nil && g.GetType() == GraphicTypeShape } func (o *GliffyObject) isSvg() bool { return o.Graphic != nil && o.Graphic.GetType() == GraphicTypeSVG } func (o *GliffyObject) isSwimlane() bool { return o.UID != "" && strings.Contains(o.UID, "com.gliffy.shape.swimlanes") } func (o *GliffyObject) isText() bool { return o.Graphic != nil && o.Graphic.GetType() == GraphicTypeText } func (o *GliffyObject) isImage() bool { return o.Graphic != nil && o.Graphic.GetType() == GraphicTypeImage } func (o *GliffyObject) isVennCircle() bool { return strings.HasPrefix(o.UID, "com.gliffy.shape.venn") } func (o *GliffyObject) isDeeplyNestedLink() bool { return o.UID != "" && deeplyNestedLinks[o.UID] } func (o *GliffyObject) GradientColor() string { gradientColor := "#FFFFFF" if o.Graphic != nil && o.Graphic.Shape != nil && o.UID != "" && !strings.HasPrefix(o.UID, "com.gliffy.shape.radial") { hex := o.Graphic.Shape.FillColor if len(hex) == 7 && strings.HasPrefix(hex, "#") { clr, err := parseHex(hex[1:]) if err == nil { r := math.Min(0xFF0000, float64(clr&0xFF0000)+0xAA0000) g := math.Min(0x00FF00, float64(clr&0x00FF00)+0x00AA00) b := math.Min(0x0000FF, float64(clr&0x0000FF)+0x0000AA) gradientColor = "#" + strings.ToUpper(hexInt(int(r+g+b))) } } } return gradientColor } func (o *GliffyObject) GradientIgnored() bool { return o.UID != "" && (strings.HasPrefix(o.UID, "com.gliffy.shape.venn.outline") || strings.HasPrefix(o.UID, "com.gliffy.shape.venn.flat")) } func (o *GliffyObject) IsSubRoutine() bool { return o.UID == "com.gliffy.shape.flowchart.flowchart_v1.default.subroutine" } func (o *GliffyObject) IsUnrecognizedGraphicType() bool { return o.Graphic != nil && o.Graphic.Type == "" } func (o *GliffyObject) GetConstraints() *Constraints { return o.Constraints } func (o *GliffyObject) String() string { if o.UID != "" { return o.UID } return o.TID } func (o *GliffyObject) normalizeChildrenCoordinates() { if !o.hasChildren() { return } sort.Slice(o.Children, func(i, j int) bool { return o.Children[i].X < o.Children[j].X }) xMin := o.Children[0].X if xMin < 0 { o.Width += -xMin o.X += xMin for _, c := range o.Children { c.X += -xMin } } sort.Slice(o.Children, func(i, j int) bool { return o.Children[i].Y < o.Children[j].Y }) yMin := o.Children[0].Y if yMin < 0 { o.Height += -yMin o.Y += yMin for _, c := range o.Children { c.Y += -yMin } } } func (o *GliffyObject) adjustZOrder() { var maxOrder *int for _, c := range o.Children { if c.UID == "com.gliffy.shape.basic.basic_v1.default.rectangle" && c.X == 0 && c.Y == 0 && c.Width == o.Width && c.Height == o.Height { if val, err := strconv.Atoi(c.Order.String()); err == nil { if maxOrder == nil || val > *maxOrder { maxOrder = &val } } } } if maxOrder != nil { o.Order = OrderValue(intToString(*maxOrder)) } } func (o *GliffyObject) adjustGeo(geo *MxGeometry) { arr := shapeCoordFix[o.UID] if arr == nil && o.Graphic != nil && o.Graphic.Shape != nil { arr = shapeCoordFix[o.Graphic.Shape.TID] } if arr == nil { return } x, y, w, h := geo.X, geo.Y, geo.Width, geo.Height shifts := o.getAdjustShifts(arr, x, y, w, h) geo.X = x + shifts.X geo.Y = y + shifts.Y geo.Width = w + shifts.Width geo.Height = h + shifts.Height } func (o *GliffyObject) adjustTextPos(textObj *GliffyObject) { arr := shapeCoordFix[o.UID] if arr == nil || len(arr) != 4 { return } shifts := o.getAdjustShifts(arr, o.X, o.Y, o.Width, o.Height) textObj.X -= shifts.X textObj.Y -= shifts.Y } func (o *GliffyObject) getAdjustShifts(arr []float64, x, y, w, h float64) *MxGeometry { xShift := relativeShift(arr[0], w) yShift := relativeShift(arr[1], h) wShift := relativeShift(arr[2], w) hShift := relativeShift(arr[3], h) mod := &MxGeometry{X: x + xShift, Y: y + yShift, Width: w + wShift, Height: h + hShift} if o.Rotation > 0 { orig := &MxGeometry{X: x, Y: y, Width: w, Height: h} rotateGeometry(orig, o.Rotation, 0, 0) rotateGeometry(mod, o.Rotation, 0, 0) xShift += mod.X - orig.X yShift += mod.Y - orig.Y } mod.X = xShift mod.Y = yShift mod.Width = wShift mod.Height = hShift return mod } func (o *GliffyObject) AdjustedLink() string { if len(o.Children) == 0 { return "" } if o.isDeeplyNestedLink() { var sb strings.Builder o.collectDeepLink(&sb) return sb.String() } for _, c := range o.Children { if c.isLink() && c.Graphic != nil && c.Graphic.Link != nil { return c.Graphic.Link.Href } } return "" } func (o *GliffyObject) collectDeepLink(sb *strings.Builder) { for _, c := range o.Children { if c.isLink() && c.Graphic != nil && c.Graphic.Link != nil { sb.WriteString(c.Graphic.Link.Href) return } if len(c.Children) > 0 { c.collectDeepLink(sb) } } } func (o *GliffyObject) IsUseFillColorForStroke() bool { key := o.UID if key == "" && o.Graphic != nil && o.Graphic.Shape != nil { key = o.Graphic.Shape.TID } return fillColorIsStroke[key] } func (o *GliffyObject) ContainsTextBracket() bool { return o.UID != "" && strings.Contains(o.UID, "com.gliffy.shape.uml.uml_v2.activity.frame") } func (o *GliffyObject) ForceConstraints() bool { return forceConstraintsShapes[o.UID] } func (o *GliffyObject) UMLSequenceCombinedFragmentText() string { switch o.UID { case "com.gliffy.shape.uml.uml_v2.sequence.interaction_use": return "ref" case "com.gliffy.shape.uml.uml_v2.sequence.opt_combined_fragment": return "opt" case "com.gliffy.shape.uml.uml_v2.sequence.loop_combined_fragment": return "loop" case "com.gliffy.shape.uml.uml_v2.sequence.alt_combined_fragment": return "alt" default: return "" } } func relativeShift(v, size float64) float64 { if math.Abs(v) < 1 { return size * v } return v } func parseHex(s string) (int64, error) { return strconv.ParseInt(s, 16, 64) } func hexInt(v int) string { return fmt.Sprintf("%06X", v&0xFFFFFF) } // to avoid fmt in hot paths. func floatToString(v float64) string { return strings.TrimRight(strings.TrimRight(strconv.FormatFloat(v, 'f', 4, 64), "0"), ".") } func intToString(v int) string { return strconv.Itoa(v) } func maxInt(a, b int) int { if a > b { return a } return b } // Static shape metadata replicated from Java implementation. var graphicalessShapes = map[string]bool{ "com.gliffy.shape.uml.uml_v1.default.package": true, "com.gliffy.shape.uml.uml_v1.default.class": true, "com.gliffy.shape.uml.uml_v1.default.simple_class": true, "com.gliffy.shape.uml.uml_v1.default.object_timeline": true, "com.gliffy.shape.uml.uml_v1.default.lifeline": true, "com.gliffy.shape.uml.uml_v1.default.use_case": true, "com.gliffy.shape.uml.uml_v1.default.actor": true, "com.gliffy.shape.uml.uml_v1.default.message": true, "com.gliffy.shape.uml.uml_v1.default.activation": true, "com.gliffy.shape.uml.uml_v1.default.dependency": true, "com.gliffy.shape.uml.uml_v1.default.composition": true, "com.gliffy.shape.uml.uml_v1.default.aggregation": true, "com.gliffy.shape.uml.uml_v1.default.association": true, "com.gliffy.shape.uml.uml_v2.class.package": true, "com.gliffy.shape.uml.uml_v2.class.simple_class": true, "com.gliffy.shape.uml.uml_v2.class.class": true, "com.gliffy.shape.uml.uml_v2.class.class2": true, "com.gliffy.shape.uml.uml_v2.class.interface": true, "com.gliffy.shape.uml.uml_v2.class.enumeration": true, "com.gliffy.shape.uml.uml_v2.sequence.lifeline": true, "com.gliffy.shape.uml.uml_v2.sequence.boundary_lifeline": true, "com.gliffy.shape.uml.uml_v2.sequence.control_lifeline": true, "com.gliffy.shape.uml.uml_v2.sequence.entity_lifeline": true, "com.gliffy.shape.uml.uml_v2.deployment.package": true, "com.gliffy.shape.uml.uml_v2.component.package": true, "com.gliffy.shape.uml.uml_v2.use_case.package": true, "com.gliffy.shape.erd.erd_v1.default.entity_with_attributes": true, "com.gliffy.shape.erd.erd_v1.default.entity_with_multiple_attributes": true, "com.gliffy.shape.bpmn.bpmn_v1.data_artifacts.annotation": true, "com.gliffy.shape.ui.ui_v3.navigation.navbar": true, "com.gliffy.shape.ui.ui_v3.forms_controls.combo_box": true, "com.gliffy.shape.ui.ui_v3.containers_content.tooltip_top": true, "com.gliffy.shape.ui.ui_v3.containers_content.tooltip_bottom": true, "com.gliffy.shape.ui.ui_v3.containers_content.tooltip_left": true, "com.gliffy.shape.ui.ui_v3.containers_content.tooltip_right": true, "com.gliffy.shape.ui.ui_v3.containers_content.popover_top": true, "com.gliffy.shape.ui.ui_v3.containers_content.popover_bottom": true, "com.gliffy.shape.ui.ui_v3.forms_controls.selector": true, "com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_left": true, "com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_right": true, "com.gliffy.shape.ui.ui_v3.icon_symbols.annotate_top": true, "com.gliffy.shape.ui.ui_v3.containers_content.speech_bubble_right": true, "com.gliffy.shape.ui.ui_v3.containers_content.speech_bubble_left": true, "com.gliffy.shape.sitemap.sitemap_v2.page": true, "com.gliffy.shape.sitemap.sitemap_v2.home": true, "com.gliffy.shape.sitemap.sitemap_v2.gliffy": true, "com.gliffy.shape.sitemap.sitemap_v2.form": true, "com.gliffy.shape.sitemap.sitemap_v2.shopping_cart": true, "com.gliffy.shape.sitemap.sitemap_v2.text": true, "com.gliffy.shape.sitemap.sitemap_v2.video": true, "com.gliffy.shape.sitemap.sitemap_v2.upload": true, "com.gliffy.shape.sitemap.sitemap_v2.slideshow": true, "com.gliffy.shape.sitemap.sitemap_v2.sitemap": true, "com.gliffy.shape.sitemap.sitemap_v2.settings": true, "com.gliffy.shape.sitemap.sitemap_v2.search": true, "com.gliffy.shape.sitemap.sitemap_v2.script": true, "com.gliffy.shape.sitemap.sitemap_v2.print": true, "com.gliffy.shape.sitemap.sitemap_v2.pricing": true, "com.gliffy.shape.sitemap.sitemap_v2.photo": true, "com.gliffy.shape.sitemap.sitemap_v2.map": true, "com.gliffy.shape.sitemap.sitemap_v2.login": true, "com.gliffy.shape.sitemap.sitemap_v2.game": true, "com.gliffy.shape.sitemap.sitemap_v2.gallery": true, "com.gliffy.shape.sitemap.sitemap_v2.download": true, "com.gliffy.shape.sitemap.sitemap_v2.document": true, "com.gliffy.shape.sitemap.sitemap_v2.chat": true, "com.gliffy.shape.sitemap.sitemap_v2.calendar": true, "com.gliffy.shape.sitemap.sitemap_v2.audio": true, "com.gliffy.shape.sitemap.sitemap_v2.profile": true, "com.gliffy.shape.sitemap.sitemap_v2.error": true, "com.gliffy.shape.sitemap.sitemap_v2.success": true, "com.gliffy.shape.sitemap.sitemap_v2.cloud": true, } var groupShapes = map[string]bool{ "com.gliffy.shape.basic.basic_v1.default.group": true, "com.gliffy.shape.erd.erd_v1.default.entity_with_attributes": true, "com.gliffy.shape.erd.erd_v1.default.entity_with_multiple_attributes": true, "com.gliffy.shape.uml.uml_v2.sequence.interaction_use": true, "com.gliffy.shape.uml.uml_v2.sequence.opt_combined_fragment": true, "com.gliffy.shape.uml.uml_v2.sequence.loop_combined_fragment": true, "com.gliffy.shape.uml.uml_v2.sequence.alt_combined_fragment": true, "com.gliffy.shape.uml.uml_v2.class.object": true, "com.gliffy.shape.uml.uml_v2.class.enumeration": true, "com.gliffy.shape.uml.uml_v2.class.interface": true, "com.gliffy.shape.uml.uml_v2.class.class2": true, "com.gliffy.shape.uml.uml_v2.class.class": true, "com.gliffy.shape.uml.uml_v2.class.data_type": true, "com.gliffy.shape.uml.uml_v2.state_machine.composite_state": true, "com.gliffy.shape.uml.uml_v2.state_machine.orthoganal_state": true, "com.gliffy.shape.uml.uml_v2.class.package": true, "com.gliffy.shape.uml.uml_v2.sequence.boundary_lifeline": true, "com.gliffy.shape.uml.uml_v2.sequence.lifeline": true, "com.gliffy.shape.uml.uml_v2.sequence.entity_lifeline": true, "com.gliffy.shape.uml.uml_v2.sequence.control_lifeline": true, "com.gliffy.shape.uml.uml_v1.default.object_timeline": true, "com.gliffy.shape.uml.uml_v1.default.class": true, "com.gliffy.shape.uml.uml_v1.default.object": true, "com.gliffy.shape.iphone.iphone_ios7.containers_content.table": true, "com.gliffy.shape.iphone.iphone_ios7.forms_controls.button_stack": true, "com.gliffy.shape.iphone.iphone_ios7.forms_controls.alert_2options": true, "com.gliffy.shape.iphone.iphone_ios7.forms_controls.alert": true, "com.gliffy.shape.iphone.iphone_ios7.navigation.contextual_menu": true, "com.gliffy.shape.iphone.iphone_ios7.navigation.nav_3tabs": true, "com.gliffy.shape.iphone.iphone_ios7.containers_content.title_bar": true, "com.gliffy.shape.iphone.iphone_ios7.navigation.tab_bar": true, "com.gliffy.shape.iphone.iphone_ios7.forms_controls.search": true, "com.gliffy.shape.android.android_v1.general.dialog": true, "com.gliffy.shape.android.android_v1.general.list_1line": true, "com.gliffy.shape.android.android_v1.general.list_2lines": true, "com.gliffy.shape.android.android_v1.general.tabs01": true, "com.gliffy.shape.android.android_v1.general.tabs02": true, "com.gliffy.shape.network.network_v3.business.user_group": true, "com.gliffy.shape.ui.ui_v3.navigation.navbar": true, "com.gliffy.shape.ui.ui_v3.navigation.navbar_vertical": true, "com.gliffy.shape.ui.ui_v3.forms_controls.dropdown": true, "com.gliffy.shape.uml.uml_v2.sequence.recursive_message": true, } var mindmapShapes = map[string]bool{ "com.gliffy.shape.mindmap.mindmap_v1.default.main_topic": true, "com.gliffy.shape.mindmap.mindmap_v1.default.subtopic": true, "com.gliffy.shape.mindmap.mindmap_v1.default.child_node": true, } var fillColorIsStroke = map[string]bool{ "com.gliffy.stencil.rectangle.no_fill_line_bottom_2px_off": true, } var deeplyNestedLinks = map[string]bool{ "com.gliffy.shape.basic.basic_v1.default.chevron_box_right": true, "com.gliffy.shape.basic.basic_v1.default.chevron_box_left": true, "com.gliffy.shape.basic.basic_v1.default.chevron_tail_right": true, "com.gliffy.shape.basic.basic_v1.default.chevron_tail_left": true, } var shapeCoordFix = map[string][]float64{ "com.gliffy.shape.flowchart.flowchart_v1.default.paper_tape": {0, -0.1, 0, 0.2}, "com.gliffy.shape.uml.uml_v1.default.node": {0, -10, 10, 10}, "com.gliffy.shape.uml.uml_v2.deployment.node": {0, -10, 10, 10}, "com.gliffy.shape.uml.uml_v2.deployment.device_node": {0, -10, 10, 10}, "com.gliffy.shape.uml.uml_v2.deployment.execution_environment_node": {0, -10, 10, 10}, "com.gliffy.shape.flowchart.flowchart_v1.default.data_storage": {0, 0, 0.115, 0}, "com.gliffy.shape.flowchart.flowchart_v1.default.database": {0, 0, 0, 0.15}, "com.gliffy.stencil.entity_lifeline.uml_v2": {10, 0, -20, 0}, "com.gliffy.stencil.boundary_lifeline.uml_v2": {35, 0, -70, 0}, "com.gliffy.stencil.control_lifeline.uml_v2": {10, 0, -20, 0}, "com.gliffy.shape.ui.ui_v3.containers_content.browser": {0, -40, 0, 40, 1}, } var forceConstraintsShapes = map[string]bool{ "com.gliffy.shape.uml.uml_v2.class.association": true, }