Skip to content

Commit

Permalink
Add text-transform attribute to the style block
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
alexstoick committed Apr 8, 2023
1 parent 9692e47 commit 86a0254
Show file tree
Hide file tree
Showing 17 changed files with 1,336 additions and 1,179 deletions.
1 change: 1 addition & 0 deletions ci/release/changelogs/next.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ⛑️

Expand Down
2 changes: 2 additions & 0 deletions d2compiler/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
}
}

Expand Down
91 changes: 68 additions & 23 deletions d2graph/d2graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"`
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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]
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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": {},
Expand Down
Loading

0 comments on commit 86a0254

Please sign in to comment.