diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c42b4f..e30fbf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## Turtle 0.1.9: + +* Turtle Text Path Support + * `Turtle.get/set_Text` controls the text (#167) + * `Turtle.get/set_TextAttribute` sets text attributes (#168) + * `Turtle.get/set_TextAnimation` animates text attributes (#171) +* `Get-Turtle` parameter improvements (#169, #170) +* `Get-Turtle` tracks invocation info (#157) + +--- + ## Turtle 0.1.8: * Turtle Performance diff --git a/Commands/Get-Turtle.ps1 b/Commands/Get-Turtle.ps1 index 16e97cd..9cc58ed 100644 --- a/Commands/Get-Turtle.ps1 +++ b/Commands/Get-Turtle.ps1 @@ -30,6 +30,8 @@ function Get-Turtle { Each argument can be the name of a move of the turtle object. After a member name is encountered, subsequent arguments will be passed to the member as parameters. + + Any parameter that begins with whitespace will be split into multiple words. .EXAMPLE # We can write shapes as a series of steps turtle " @@ -370,6 +372,10 @@ function Get-Turtle { $memberNames = $memberNames | Sort-Object @{Expression={ $_.Length };Descending=$true}, name # Create a new turtle object in case we have no turtle input. $currentTurtle = [PSCustomObject]@{PSTypeName='Turtle'} + + $invocationInfo = $MyInvocation + $invocationInfo | + Add-Member ScriptProperty History {Get-History -Id $this.HistoryId} -Force } process { @@ -381,6 +387,12 @@ function Get-Turtle { return $PSBoundParameters.InputObject } + if (-not $currentTurtle.Invocations) { + $currentTurtle | Add-Member NoteProperty Invocations -Force @(,$invocationInfo) + } elseif ($currentTurtle.Invocations -is [object[]]) { + $currentTurtle.Invocations += $invocationInfo + } + # First we want to split each argument into words. # This way, it is roughly the same if you say: @@ -388,14 +400,18 @@ function Get-Turtle { # * `turtle forward 10` # * `turtle 'forward', 10` $wordsAndArguments = @(foreach ($arg in $ArgumentList) { - # If the argument is a string, split it by whitespace. + # If the argument is a string, and it starts with whitespace if ($arg -is [string]) { - $arg -split '\s{1,}' + if ($arg -match '^[\r\n\s]+') { + $arg -split '\s{1,}' + } else { + $arg + } } else { # otherwise, leave the argument alone. $arg } - }) + }) # Now that we have a series of words, we can process them. # We want to keep track of the current member, @@ -406,7 +422,7 @@ function Get-Turtle { # To do this in one pass, we will iterate through the words and arguments. # We use an indexed loop so we can skip past claimed arguments. - for ($argIndex =0; $argIndex -lt $wordsAndArguments.Length; $argIndex++) { + for ($argIndex =0; $argIndex -lt $wordsAndArguments.Length; $argIndex++) { $arg = $wordsAndArguments[$argIndex] # If the argument is not in the member names list, we can complain about it. if ($arg -notin $memberNames) { @@ -415,7 +431,6 @@ function Get-Turtle { } continue } - # If we have a current member, we can invoke it or get it. $currentMember = $arg @@ -478,7 +493,16 @@ function Get-Turtle { # If we have any arguments, if ($argList) { - # lets try to set it. + # Check to see if they are strongly typed + if ($memberInfo -is [Management.Automation.Runspaces.ScriptPropertyData]) { + $desiredType = $memberInfo.SetScriptBlock.Ast.ParamBlock.Parameters.StaticType + if ($desiredType -is [Type] -and + $argList.Length -eq 1 -and + $argList[0] -as $desiredType) { + $argList = $argList[0] -as $desiredType + } + } + # lets try to set it. $currentTurtle.$currentMember = $argList } else { # otherwise, lets get the property @@ -492,9 +516,9 @@ function Get-Turtle { # Properties being returned will largely be strings or numbers, and these will always output directly. if ($null -ne $stepOutput -and -not ($stepOutput.pstypenames -eq 'Turtle')) { # Output the step - $stepOutput + $stepOutput # and set the output turtle to false. - $outputTurtle = $false + $outputTurtle = $false } elseif ($null -ne $stepOutput) { # Set the current turtle to the step output. $currentTurtle = $stepOutput diff --git a/Examples/TurtlesOnATextPath-ATurtleCircle.svg b/Examples/TurtlesOnATextPath-ATurtleCircle.svg new file mode 100644 index 0000000..13991b8 --- /dev/null +++ b/Examples/TurtlesOnATextPath-ATurtleCircle.svg @@ -0,0 +1,10 @@ + + + + + + a turtle circle + + + + \ No newline at end of file diff --git a/Examples/TurtlesOnATextPath-Morph.svg b/Examples/TurtlesOnATextPath-Morph.svg new file mode 100644 index 0000000..b3887d5 --- /dev/null +++ b/Examples/TurtlesOnATextPath-Morph.svg @@ -0,0 +1,15 @@ + + + + + + + turtles on a text path + + + + + + + + \ No newline at end of file diff --git a/Examples/TurtlesOnATextPath.svg b/Examples/TurtlesOnATextPath.svg new file mode 100644 index 0000000..819e67f --- /dev/null +++ b/Examples/TurtlesOnATextPath.svg @@ -0,0 +1,10 @@ + + + + + + turtles on a text path + + + + \ No newline at end of file diff --git a/Examples/TurtlesOnATextPath.turtle.ps1 b/Examples/TurtlesOnATextPath.turtle.ps1 new file mode 100644 index 0000000..49f3008 --- /dev/null +++ b/Examples/TurtlesOnATextPath.turtle.ps1 @@ -0,0 +1,32 @@ +if ($PSScriptRoot) { Push-Location $PSScriptRoot} + +$turtlesOnATextPath = turtle rotate 90 jump 50 rotate -90 ArcRight 50 60 text 'turtles on a text path' textattribute @{'font-size'=36} +$turtlesOnATextPath | Save-Turtle ./TurtlesOnATextPath.svg + + +$textPath2 = turtle rotate 90 jump 50 rotate -90 ArcRight 50 -60 + +$turtlesOnATextPath = + turtle rotate 90 jump 50 rotate -90 rotate -30 forward 200 text 'turtles on a text path' morph @( + turtle rotate 90 jump 50 rotate -90 rotate -10 forward 200 + turtle rotate 90 jump 50 rotate -90 rotate -5 forward 200 + turtle rotate 90 jump 50 rotate -90 rotate -10 forward 200 + ) textAnimation ([Ordered]@{ + attributeName = 'fill' ; values = "#4488ff;#224488;#4488ff" ; repeatCount = 'indefinite'; dur = "4.2s" + },[Ordered]@{ + attributeName = 'font-size' ; values = "1em;1.3em;1em" ; repeatCount = 'indefinite'; dur = "4.2s" + },[Ordered]@{ + attributeName = 'textLength' ; values = "100%;1%;100%" ; repeatCount = 'indefinite'; dur = "4.2s" + },[Ordered]@{ + attributeName = 'x' ; values = "-100%; 100%; -50%" ; repeatCount = 'indefinite'; dur = "4.2s" + }) +$turtlesOnATextPath | Save-Turtle ./TurtlesOnATextPath-Morph.svg + + +turtle rotate -90 circle 42 text "a turtle circle" textattribute ([Ordered]@{ + 'x'='5%' + 'dominant-baseline'='text-before-edge' + 'letter-spacing'='.16em' +}) save ./TurtlesOnATextPath-ATurtleCircle.svg + +if ($PSScriptRoot) { Pop-Location } \ No newline at end of file diff --git a/Turtle.psd1 b/Turtle.psd1 index 21acfbf..aa98f0e 100644 --- a/Turtle.psd1 +++ b/Turtle.psd1 @@ -1,6 +1,6 @@ @{ # Version number of this module. - ModuleVersion = '0.1.8' + ModuleVersion = '0.1.9' # Description of the module Description = "Turtles in a PowerShell" # Script module or binary module file associated with this manifest. @@ -37,15 +37,14 @@ # A URL to the license for this module. LicenseURI = 'https://github.com/PowerShellWeb/Turtle/blob/main/LICENSE' ReleaseNotes = @' -## Turtle 0.1.8: - -* Turtle Performance - * Improving `.Steps` performance (#159) - * Reducing Turtle Verbosity (#160) -* New Moves: - * Step (#161) - * Forward,Teleport, and GoTo now use Step (#161) -* New Reflection Examples (#162) +## Turtle 0.1.9: + +* Turtle Text Path Support + * `Turtle.get/set_Text` controls the text (#167) + * `Turtle.get/set_TextAttribute` sets text attributes (#168) + * `Turtle.get/set_TextAnimation` animates text attributes (#171) +* `Get-Turtle` parameter improvements (#169, #170) +* `Get-Turtle` tracks invocation info (#157) --- diff --git a/Turtle.types.ps1xml b/Turtle.types.ps1xml index 5126e63..d6d1d95 100644 --- a/Turtle.types.ps1xml +++ b/Turtle.types.ps1xml @@ -2859,7 +2859,8 @@ $null, $null, $viewX, $viewY = $viewBox } )>" $(if ($this.PatternAnimation) { $this.PatternAnimation }) - $this.PathElement.OuterXml + $this.PathElement.OuterXml + $this.TextElement.OuterXml "</pattern>" "</defs>" $( @@ -3143,6 +3144,7 @@ $this | Add-Member -MemberType NoteProperty -Force -Name '.StrokeWidth' -Value $ @( "<svg xmlns='http://www.w3.org/2000/svg' viewBox='$($this.ViewBox)' transform-origin='50% 50%' width='100%' height='100%'>" $this.PathElement.OuterXml + $this.TextElement.OuterXml "</svg>" ) -join '' -as [xml] @@ -3169,6 +3171,7 @@ param() "<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%' transform-origin='50% 50%'>" "<symbol id='$($this.ID)-symbol' viewBox='$($this.ViewBox)' transform-origin='50% 50%'>" $this.PathElement.OuterXml + $this.TextElement.OuterXml "</symbol>" $( if ($this.BackgroundColor) { @@ -3180,6 +3183,165 @@ param() ) -join '' -as [xml] + + Text + + <# +.SYNOPSIS + Gets the Turtle text +.DESCRIPTION + Gets the text associated with the Turtle, if any exists. + +#> +return $this.'.Text' + + + <# +.SYNOPSIS + Sets the Turtle text +.DESCRIPTION + Sets the text displayed along the turtle path. + + Once this property is set, a text element will be displayed along with the turtle path. + + To display only text, please also set the path's stroke to `transparent` +#> +param( +[string[]] +$Text +) + +$this | Add-Member NoteProperty '.Text' -Force ($text -join ' ') + + + + + TextAnimation + + if ($this.'.TextAnimation') { + return $this.'.TextAnimation' +} + + + + <# +.SYNOPSIS + Sets the Turtle Text Animation +.DESCRIPTION + Sets an animation for the Turtle path. +.EXAMPLE + turtle rotate 90 jump 50 rotate -90 forward 100 text 'Hello World' textAnimation ([Ordered]@{ + attributeName = 'fill' ; values = "#4488ff;#224488;#4488ff" ; repeatCount = 'indefinite'; dur = "4.2s" + },[Ordered]@{ + attributeName = 'font-size' ; values = "1em;1.3em;1em" ; repeatCount = 'indefinite'; dur = "4.2s" + }) save ./textAnimation.svg +#> +param( +# The text animation object. +# This may be a string containing animation XML, XML, or a dictionary containing animation settings. +[PSObject] +$TextAnimation +) + +$newAnimation = @(foreach ($animation in $TextAnimation) { + if ($animation -is [Collections.IDictionary]) { + $animationCopy = [Ordered]@{} + $animation + if (-not $animationCopy['attributeType']) { + $animationCopy['attributeType'] = 'XML' + } + if (-not $animationCopy['attributeName']) { + $animationCopy['attributeName'] = 'transform' + } + if ($animationCopy.values -is [object[]]) { + $animationCopy['values'] = $animationCopy['values'] -join ';' + } + + $elementName = 'animate' + if ($animationCopy['attributeName'] -eq 'transform') { + $elementName = 'animateTransform' + } + + + if (-not $animationCopy['dur'] -and $this.Duration) { + $animationCopy['dur'] = "$($this.Duration.TotalSeconds)s" + } + + "<$elementName $( + @(foreach ($key in $animationCopy.Keys) { + " $key='$([Web.HttpUtility]::HtmlAttributeEncode($animationCopy[$key]))'" + }) -join '' + )/>" + } + if ($animation -is [string]) { + $animation + } + if ($animation.OuterXml) { + $animation.OuterXml + } +}) + +$this | Add-Member -MemberType NoteProperty -Force -Name '.TextAnimation' -Value $newAnimation + + + + + TextAttribute + + <# +.SYNOPSIS + Gets any Text Attributes +.DESCRIPTION + Gets any attributes associated with the Turtle text + +#> +if (-not $this.'.TextAttribute') { + $this | Add-Member NoteProperty '.TextAttribute' ([Ordered]@{}) -Force +} +return $this.'.TextAttribute' + + + <# +.SYNOPSIS + Sets text attributes +.DESCRIPTION + Sets any attributes associated with the turtle text. + + These will become the attributes on the `<text>` element. +#> +param( +# The text attributes. +[Collections.IDictionary] +$TextAttribute = [Ordered]@{} +) + +if (-not $this.'.TextAttribute') { + $this | Add-Member -MemberType NoteProperty -Name '.TextAttribute' -Value ([Ordered]@{}) -Force +} +foreach ($key in $TextAttribute.Keys) { + $this.'.TextAttribute'[$key] = $TextAttribute[$key] +} + + + + TextElement + + +if ($this.Text) { + return @( + "<text id='$($this.ID)-text' $( + foreach ($TextAttributeName in $this.TextAttribute.Keys) { + " $TextAttributeName='$($this.TextAttribute[$TextAttributeName])'" + } +)>" + "<textPath href='#$($this.id)-path'>$([Security.SecurityElement]::Escape($this.Text))</textPath>" + if ($this.TextAnimation) {$this.TextAnimation} + "</text>" + ) -as [xml] +} + + + + ViewBox diff --git a/Types/Turtle/get_Pattern.ps1 b/Types/Turtle/get_Pattern.ps1 index 74abe88..5a05e6e 100644 --- a/Types/Turtle/get_Pattern.ps1 +++ b/Types/Turtle/get_Pattern.ps1 @@ -14,7 +14,8 @@ $null, $null, $viewX, $viewY = $viewBox } )>" $(if ($this.PatternAnimation) { $this.PatternAnimation }) - $this.PathElement.OuterXml + $this.PathElement.OuterXml + $this.TextElement.OuterXml "" "" $( diff --git a/Types/Turtle/get_SVG.ps1 b/Types/Turtle/get_SVG.ps1 index 4f2c4a0..5dddfa2 100644 --- a/Types/Turtle/get_SVG.ps1 +++ b/Types/Turtle/get_SVG.ps1 @@ -2,5 +2,6 @@ param() @( "" $this.PathElement.OuterXml + $this.TextElement.OuterXml "" ) -join '' -as [xml] \ No newline at end of file diff --git a/Types/Turtle/get_Symbol.ps1 b/Types/Turtle/get_Symbol.ps1 index e697e9a..f29d10f 100644 --- a/Types/Turtle/get_Symbol.ps1 +++ b/Types/Turtle/get_Symbol.ps1 @@ -17,6 +17,7 @@ param() "" "" $this.PathElement.OuterXml + $this.TextElement.OuterXml "" $( if ($this.BackgroundColor) { diff --git a/Types/Turtle/get_Text.ps1 b/Types/Turtle/get_Text.ps1 new file mode 100644 index 0000000..9a19dc5 --- /dev/null +++ b/Types/Turtle/get_Text.ps1 @@ -0,0 +1,8 @@ +<# +.SYNOPSIS + Gets the Turtle text +.DESCRIPTION + Gets the text associated with the Turtle, if any exists. + +#> +return $this.'.Text' \ No newline at end of file diff --git a/Types/Turtle/get_TextAnimation.ps1 b/Types/Turtle/get_TextAnimation.ps1 new file mode 100644 index 0000000..2821d30 --- /dev/null +++ b/Types/Turtle/get_TextAnimation.ps1 @@ -0,0 +1,3 @@ +if ($this.'.TextAnimation') { + return $this.'.TextAnimation' +} diff --git a/Types/Turtle/get_TextAttribute.ps1 b/Types/Turtle/get_TextAttribute.ps1 new file mode 100644 index 0000000..8760d56 --- /dev/null +++ b/Types/Turtle/get_TextAttribute.ps1 @@ -0,0 +1,11 @@ +<# +.SYNOPSIS + Gets any Text Attributes +.DESCRIPTION + Gets any attributes associated with the Turtle text + +#> +if (-not $this.'.TextAttribute') { + $this | Add-Member NoteProperty '.TextAttribute' ([Ordered]@{}) -Force +} +return $this.'.TextAttribute' \ No newline at end of file diff --git a/Types/Turtle/get_TextElement.ps1 b/Types/Turtle/get_TextElement.ps1 new file mode 100644 index 0000000..58d33e2 --- /dev/null +++ b/Types/Turtle/get_TextElement.ps1 @@ -0,0 +1,14 @@ + +if ($this.Text) { + return @( + "" + "$([Security.SecurityElement]::Escape($this.Text))" + if ($this.TextAnimation) {$this.TextAnimation} + "" + ) -as [xml] +} + diff --git a/Types/Turtle/set_Text.ps1 b/Types/Turtle/set_Text.ps1 new file mode 100644 index 0000000..5400905 --- /dev/null +++ b/Types/Turtle/set_Text.ps1 @@ -0,0 +1,16 @@ +<# +.SYNOPSIS + Sets the Turtle text +.DESCRIPTION + Sets the text displayed along the turtle path. + + Once this property is set, a text element will be displayed along with the turtle path. + + To display only text, please also set the path's stroke to `transparent` +#> +param( +[string[]] +$Text +) + +$this | Add-Member NoteProperty '.Text' -Force ($text -join ' ') diff --git a/Types/Turtle/set_TextAnimation.ps1 b/Types/Turtle/set_TextAnimation.ps1 new file mode 100644 index 0000000..11b4bf6 --- /dev/null +++ b/Types/Turtle/set_TextAnimation.ps1 @@ -0,0 +1,57 @@ +<# +.SYNOPSIS + Sets the Turtle Text Animation +.DESCRIPTION + Sets an animation for the Turtle path. +.EXAMPLE + turtle rotate 90 jump 50 rotate -90 forward 100 text 'Hello World' textAnimation ([Ordered]@{ + attributeName = 'fill' ; values = "#4488ff;#224488;#4488ff" ; repeatCount = 'indefinite'; dur = "4.2s" + },[Ordered]@{ + attributeName = 'font-size' ; values = "1em;1.3em;1em" ; repeatCount = 'indefinite'; dur = "4.2s" + }) save ./textAnimation.svg +#> +param( +# The text animation object. +# This may be a string containing animation XML, XML, or a dictionary containing animation settings. +[PSObject] +$TextAnimation +) + +$newAnimation = @(foreach ($animation in $TextAnimation) { + if ($animation -is [Collections.IDictionary]) { + $animationCopy = [Ordered]@{} + $animation + if (-not $animationCopy['attributeType']) { + $animationCopy['attributeType'] = 'XML' + } + if (-not $animationCopy['attributeName']) { + $animationCopy['attributeName'] = 'transform' + } + if ($animationCopy.values -is [object[]]) { + $animationCopy['values'] = $animationCopy['values'] -join ';' + } + + $elementName = 'animate' + if ($animationCopy['attributeName'] -eq 'transform') { + $elementName = 'animateTransform' + } + + + if (-not $animationCopy['dur'] -and $this.Duration) { + $animationCopy['dur'] = "$($this.Duration.TotalSeconds)s" + } + + "<$elementName $( + @(foreach ($key in $animationCopy.Keys) { + " $key='$([Web.HttpUtility]::HtmlAttributeEncode($animationCopy[$key]))'" + }) -join '' + )/>" + } + if ($animation -is [string]) { + $animation + } + if ($animation.OuterXml) { + $animation.OuterXml + } +}) + +$this | Add-Member -MemberType NoteProperty -Force -Name '.TextAnimation' -Value $newAnimation diff --git a/Types/Turtle/set_TextAttribute.ps1 b/Types/Turtle/set_TextAttribute.ps1 new file mode 100644 index 0000000..217de99 --- /dev/null +++ b/Types/Turtle/set_TextAttribute.ps1 @@ -0,0 +1,20 @@ +<# +.SYNOPSIS + Sets text attributes +.DESCRIPTION + Sets any attributes associated with the turtle text. + + These will become the attributes on the `` element. +#> +param( +# The text attributes. +[Collections.IDictionary] +$TextAttribute = [Ordered]@{} +) + +if (-not $this.'.TextAttribute') { + $this | Add-Member -MemberType NoteProperty -Name '.TextAttribute' -Value ([Ordered]@{}) -Force +} +foreach ($key in $TextAttribute.Keys) { + $this.'.TextAttribute'[$key] = $TextAttribute[$key] +} \ No newline at end of file