From a260068d2c8165c85507ecb880c9d58eadcf2e22 Mon Sep 17 00:00:00 2001 From: Stoica Alex Date: Fri, 31 Mar 2023 09:58:02 +0100 Subject: [PATCH] Add `text-transform` attribute to the `style` block This provides an option to override the functionality provided by the theme, or apply `text-transform` outside of the theme. The functionality of the `text-transform` is as below: - `text-transform: none` - will disable **any** transformation (like the uppercasing by `terminal` theme) - `text-transform: uppercase` (uppercase not upper as per your message) - will force all characters into uppercase. - `text-transform: lowercase` - will force all characters into lowercase. - `text-transform: capitalize` - will uppercase the first letter of every word In addition, this commit introduces: - helper methods on the `d2graph.Style` struct to determine the type of `text-transform` to be applied. - `ApplyTextTransform` method on the `d2graph.Attributes` which will transform the `Label.Value` to the correct text case. --- ci/release/changelogs/next.md | 1 + d2compiler/compile.go | 2 + d2graph/d2graph.go | 91 ++- .../themes/origami/dagre/board.exp.json | 152 +++-- .../themes/origami/dagre/sketch.exp.svg | 186 +++--- .../themes/origami/elk/board.exp.json | 116 ++-- .../themes/origami/elk/sketch.exp.svg | 188 +++--- .../themes/terminal/dagre/board.exp.json | 106 ++-- .../themes/terminal/dagre/sketch.exp.svg | 568 +++++++++--------- .../themes/terminal/elk/board.exp.json | 18 +- .../themes/terminal/elk/sketch.exp.svg | 564 ++++++++--------- .../terminal_grayscale/dagre/board.exp.json | 92 +-- .../terminal_grayscale/dagre/sketch.exp.svg | 178 +++--- .../terminal_grayscale/elk/board.exp.json | 22 +- .../terminal_grayscale/elk/sketch.exp.svg | 172 +++--- e2etests/themes_test.go | 41 +- go.mod | 2 +- 17 files changed, 1328 insertions(+), 1171 deletions(-) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 7c8dc5311..e4838afec 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -9,6 +9,7 @@ - Labels on parallel `dagre` connections include a gap between them [#1134](https://github.com/terrastruct/d2/pull/1134) - Sequence Diagram `Lifelines` now inherit the Actor `stroke` and `stroke-dash` [#1140](https://github.com/terrastruct/d2/pull/1140) +- Add `text-transform` styling option [#1118](https://github.com/terrastruct/d2/pull/1118) #### Bugfixes ⛑️ diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 99c0de355..817f4169f 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -500,6 +500,8 @@ func compileStyleFieldInit(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Left = &d2graph.Scalar{MapKey: f.LastPrimaryKey()} case "double-border": attrs.Style.DoubleBorder = &d2graph.Scalar{MapKey: f.LastPrimaryKey()} + case "text-transform": + attrs.Style.TextTransform = &d2graph.Scalar{MapKey: f.LastPrimaryKey()} } } diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index e6e9de6b4..6d7e845af 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -10,6 +10,9 @@ import ( "strconv" "strings" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "oss.terrastruct.com/util-go/go2" "oss.terrastruct.com/d2/d2ast" @@ -141,6 +144,25 @@ type Attributes struct { Classes []string `json:"classes,omitempty"` } +// ApplyTextTransform will alter the `Label.Value` of the current object based +// on the specification of the `text-transform` styling option. This function +// has side-effects! +func (a *Attributes) ApplyTextTransform() { + if a.Style.NoneTextTransform() { + return + } + + if a.Style.TextTransform != nil && a.Style.TextTransform.Value == "uppercase" { + a.Label.Value = strings.ToUpper(a.Label.Value) + } + if a.Style.TextTransform != nil && a.Style.TextTransform.Value == "lowercase" { + a.Label.Value = strings.ToLower(a.Label.Value) + } + if a.Style.TextTransform != nil && a.Style.TextTransform.Value == "capitalize" { + a.Label.Value = cases.Title(language.Und).String(a.Label.Value) + } +} + // TODO references at the root scope should have their Scope set to root graph AST type Reference struct { Key *d2ast.KeyPath `json:"key"` @@ -161,25 +183,33 @@ func (r Reference) InEdge() bool { } type Style struct { - Opacity *Scalar `json:"opacity,omitempty"` - Stroke *Scalar `json:"stroke,omitempty"` - Fill *Scalar `json:"fill,omitempty"` - FillPattern *Scalar `json:"fillPattern,omitempty"` - StrokeWidth *Scalar `json:"strokeWidth,omitempty"` - StrokeDash *Scalar `json:"strokeDash,omitempty"` - BorderRadius *Scalar `json:"borderRadius,omitempty"` - Shadow *Scalar `json:"shadow,omitempty"` - ThreeDee *Scalar `json:"3d,omitempty"` - Multiple *Scalar `json:"multiple,omitempty"` - Font *Scalar `json:"font,omitempty"` - FontSize *Scalar `json:"fontSize,omitempty"` - FontColor *Scalar `json:"fontColor,omitempty"` - Animated *Scalar `json:"animated,omitempty"` - Bold *Scalar `json:"bold,omitempty"` - Italic *Scalar `json:"italic,omitempty"` - Underline *Scalar `json:"underline,omitempty"` - Filled *Scalar `json:"filled,omitempty"` - DoubleBorder *Scalar `json:"doubleBorder,omitempty"` + Opacity *Scalar `json:"opacity,omitempty"` + Stroke *Scalar `json:"stroke,omitempty"` + Fill *Scalar `json:"fill,omitempty"` + FillPattern *Scalar `json:"fillPattern,omitempty"` + StrokeWidth *Scalar `json:"strokeWidth,omitempty"` + StrokeDash *Scalar `json:"strokeDash,omitempty"` + BorderRadius *Scalar `json:"borderRadius,omitempty"` + Shadow *Scalar `json:"shadow,omitempty"` + ThreeDee *Scalar `json:"3d,omitempty"` + Multiple *Scalar `json:"multiple,omitempty"` + Font *Scalar `json:"font,omitempty"` + FontSize *Scalar `json:"fontSize,omitempty"` + FontColor *Scalar `json:"fontColor,omitempty"` + Animated *Scalar `json:"animated,omitempty"` + Bold *Scalar `json:"bold,omitempty"` + Italic *Scalar `json:"italic,omitempty"` + Underline *Scalar `json:"underline,omitempty"` + Filled *Scalar `json:"filled,omitempty"` + DoubleBorder *Scalar `json:"doubleBorder,omitempty"` + TextTransform *Scalar `json:"textTransform,omitempty"` +} + +// NoneTextTransform will return a boolean if the text should not have any +// transformation applied. This should overwrite theme specific transformations +// like `CapsLock` from the `terminal` theme. +func (s Style) NoneTextTransform() bool { + return s.TextTransform != nil && s.TextTransform.Value == "none" } func (s *Style) Apply(key, value string) error { @@ -350,6 +380,14 @@ func (s *Style) Apply(key, value string) error { return errors.New(`expected "double-border" to be true or false`) } s.DoubleBorder.Value = value + case "text-transform": + if s.TextTransform == nil { + break + } + if !go2.Contains(textTransforms, strings.ToLower(value)) { + return fmt.Errorf(`expected "text-transform" to be one of (%s)`, strings.Join(textTransforms, ",")) + } + s.TextTransform.Value = value default: return fmt.Errorf("unknown style key: %s", key) } @@ -1329,10 +1367,11 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler } if g.Theme != nil && g.Theme.SpecialRules.CapsLock && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeCode) { - if obj.Attributes.Language != "latex" { + if obj.Attributes.Language != "latex" && !obj.Attributes.Style.NoneTextTransform() { obj.Attributes.Label.Value = strings.ToUpper(obj.Attributes.Label.Value) } } + obj.Attributes.ApplyTextTransform() labelDims, err := obj.GetLabelSize(mtexts, ruler, fontFamily) if err != nil { @@ -1451,9 +1490,11 @@ func (g *Graph) SetDimensions(mtexts []*d2target.MText, ruler *textmeasure.Ruler continue } - if g.Theme != nil && g.Theme.SpecialRules.CapsLock { + if g.Theme != nil && g.Theme.SpecialRules.CapsLock && !edge.Attributes.Style.NoneTextTransform() { edge.Attributes.Label.Value = strings.ToUpper(edge.Attributes.Label.Value) } + edge.Attributes.ApplyTextTransform() + usedFont := fontFamily if edge.Attributes.Style.Font != nil { f := d2fonts.D2_FONT_TO_FAMILY[edge.Attributes.Style.Font.Value] @@ -1479,9 +1520,10 @@ func (g *Graph) Texts() []*d2target.MText { for _, obj := range g.Objects { if obj.Attributes.Label.Value != "" { + obj.Attributes.ApplyTextTransform() text := obj.Text() if capsLock && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeCode) { - if obj.Attributes.Language != "latex" { + if obj.Attributes.Language != "latex" && !obj.Attributes.Style.NoneTextTransform() { text.Text = strings.ToUpper(text.Text) } } @@ -1512,8 +1554,9 @@ func (g *Graph) Texts() []*d2target.MText { } for _, edge := range g.Edges { if edge.Attributes.Label.Value != "" { + edge.Attributes.ApplyTextTransform() text := edge.Text() - if capsLock { + if capsLock && !edge.Attributes.Style.NoneTextTransform() { text.Text = strings.ToUpper(text.Text) } texts = appendTextDedup(texts, text) @@ -1625,6 +1668,8 @@ var FillPatterns = []string{ "paper", } +var textTransforms = []string{"none", "uppercase", "lowercase", "capitalize"} + // BoardKeywords contains the keywords that create new boards. var BoardKeywords = map[string]struct{}{ "layers": {}, diff --git a/e2etests/testdata/themes/origami/dagre/board.exp.json b/e2etests/testdata/themes/origami/dagre/board.exp.json index a0f45ac09..0c5fd8bb2 100644 --- a/e2etests/testdata/themes/origami/dagre/board.exp.json +++ b/e2etests/testdata/themes/origami/dagre/board.exp.json @@ -10,7 +10,7 @@ "x": 0, "y": 275 }, - "width": 399, + "width": 403, "height": 1225, "opacity": 1, "strokeDash": 0, @@ -52,7 +52,7 @@ "x": 95, "y": 340 }, - "width": 284, + "width": 288, "height": 317, "opacity": 1, "strokeDash": 0, @@ -73,7 +73,7 @@ "fields": null, "methods": null, "columns": null, - "label": "cell tower", + "label": "Cell Tower", "fontSize": 24, "fontFamily": "DEFAULT", "language": "", @@ -81,7 +81,7 @@ "italic": false, "bold": false, "underline": false, - "labelWidth": 99, + "labelWidth": 106, "labelHeight": 31, "labelPosition": "OUTSIDE_TOP_CENTER", "zIndex": 0, @@ -91,7 +91,7 @@ "id": "network.cell tower.satellites", "type": "stored_data", "pos": { - "x": 185, + "x": 187, "y": 372 }, "width": 104, @@ -133,7 +133,7 @@ "id": "network.cell tower.transmitter", "type": "rectangle", "pos": { - "x": 185, + "x": 187, "y": 559 }, "width": 104, @@ -259,7 +259,7 @@ "id": "network.data processor", "type": "rectangle", "pos": { - "x": 145, + "x": 147, "y": 814 }, "width": 184, @@ -301,7 +301,7 @@ "id": "network.data processor.storage", "type": "cylinder", "pos": { - "x": 185, + "x": 187, "y": 846 }, "width": 104, @@ -343,7 +343,7 @@ "id": "user", "type": "person", "pos": { - "x": 80, + "x": 81, "y": 0 }, "width": 130, @@ -381,14 +381,56 @@ "zIndex": 0, "level": 1 }, + { + "id": "other-user", + "type": "person", + "pos": { + "x": 271, + "y": 8 + }, + "width": 108, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B3", + "fillPattern": "paper", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "OTHER-USER", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 93, + "labelHeight": 21, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 1 + }, { "id": "api server", "type": "rectangle", "pos": { - "x": 439, + "x": 443, "y": 1076 }, - "width": 153, + "width": 151, "height": 66, "opacity": 1, "strokeDash": 0, @@ -409,7 +451,7 @@ "fields": null, "methods": null, "columns": null, - "label": "API サーバー", + "label": "api サーバー", "fontSize": 16, "fontFamily": "DEFAULT", "language": "", @@ -417,7 +459,7 @@ "italic": false, "bold": true, "underline": false, - "labelWidth": 108, + "labelWidth": 106, "labelHeight": 21, "labelPosition": "INSIDE_MIDDLE_CENTER", "zIndex": 0, @@ -427,7 +469,7 @@ "id": "logs", "type": "page", "pos": { - "x": 474, + "x": 477, "y": 1313 }, "width": 84, @@ -493,19 +535,19 @@ "labelPercentage": 0, "route": [ { - "x": 211, + "x": 212, "y": 439 }, { - "x": 173.4, + "x": 173.6, "y": 487 }, { - "x": 173.4, + "x": 173.8, "y": 511.2 }, { - "x": 211, + "x": 213, "y": 560 } ], @@ -527,7 +569,7 @@ "strokeDash": 0, "strokeWidth": 2, "stroke": "B1", - "label": "send", + "label": "SEND", "fontSize": 16, "fontFamily": "DEFAULT", "language": "", @@ -535,25 +577,25 @@ "italic": true, "bold": false, "underline": false, - "labelWidth": 33, + "labelWidth": 37, "labelHeight": 21, "labelPosition": "INSIDE_MIDDLE_CENTER", "labelPercentage": 0, "route": [ { - "x": 237, + "x": 239, "y": 439 }, { - "x": 237, + "x": 239, "y": 487 }, { - "x": 237, + "x": 239, "y": 511.2 }, { - "x": 237, + "x": 239, "y": 560 } ], @@ -589,19 +631,19 @@ "labelPercentage": 0, "route": [ { - "x": 263, + "x": 266, "y": 439 }, { - "x": 300.6, + "x": 304.4, "y": 487 }, { - "x": 300.6, + "x": 304.2, "y": 511.2 }, { - "x": 263, + "x": 265, "y": 560 } ], @@ -637,31 +679,31 @@ "labelPercentage": 0, "route": [ { - "x": 237, + "x": 239, "y": 625.5 }, { - "x": 237, + "x": 239, "y": 651.1 }, { - "x": 237, + "x": 239, "y": 669.6 }, { - "x": 237, + "x": 239, "y": 687.75 }, { - "x": 237, + "x": 239, "y": 705.9 }, { - "x": 237, + "x": 239, "y": 792.2 }, { - "x": 237, + "x": 239, "y": 847 } ], @@ -697,19 +739,19 @@ "labelPercentage": 0, "route": [ { - "x": 169, + "x": 170, "y": 87 }, { - "x": 223.4, + "x": 225.2, "y": 156.2 }, { - "x": 237, + "x": 239, "y": 248.2 }, { - "x": 237, + "x": 239, "y": 305 } ], @@ -745,11 +787,11 @@ "labelPercentage": 0, "route": [ { - "x": 126, + "x": 127, "y": 87 }, { - "x": 85, + "x": 85.19999999999999, "y": 156.2 }, { @@ -949,12 +991,12 @@ "labelPercentage": 0, "route": [ { - "x": 439.25, - "y": 1127.0397225725094 + "x": 443.25, + "y": 1126.6702127659576 }, { - "x": 183.25, - "y": 1187.4079445145019 + "x": 184.05, + "y": 1187.3340425531915 }, { "x": 116, @@ -997,19 +1039,19 @@ "labelPercentage": 0, "route": [ { - "x": 515.75, + "x": 518.75, "y": 1142 }, { - "x": 515.75, + "x": 518.75, "y": 1190.4 }, { - "x": 515.8, + "x": 518.8, "y": 1273 }, { - "x": 516, + "x": 519, "y": 1313 } ], @@ -1045,20 +1087,20 @@ "labelPercentage": 0, "route": [ { - "x": 237, + "x": 239, "y": 996.5 }, { - "x": 237, + "x": 239, "y": 1020.1 }, { - "x": 277.4, - "y": 1038 + "x": 279.8, + "y": 1038.2 }, { - "x": 439, - "y": 1086 + "x": 443, + "y": 1087 } ], "isCurve": true, diff --git a/e2etests/testdata/themes/origami/dagre/sketch.exp.svg b/e2etests/testdata/themes/origami/dagre/sketch.exp.svg index d1e24160d..e76787aad 100644 --- a/e2etests/testdata/themes/origami/dagre/sketch.exp.svg +++ b/e2etests/testdata/themes/origami/dagre/sketch.exp.svg @@ -1,24 +1,24 @@ -