Skip to content

Commit

Permalink
docs for turtle, start work on Latex
Browse files Browse the repository at this point in the history
  • Loading branch information
cormullion committed Aug 3, 2024
1 parent b39d305 commit e0029b8
Show file tree
Hide file tree
Showing 12 changed files with 343 additions and 90 deletions.
6 changes: 4 additions & 2 deletions docs/src/example/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,13 @@ fontsize(35)
end
sethue("grey5")
text(L"f(t) = [4\cos(t) + 2\cos(5t), 4\sin(t) + 2\sin(5t)]", O, halign=:center)
fontsize(5) # hide
text(Libc.strftime(time()), boxtopleft(BoundingBox()) + (0, 12)) # hide
finish()
d
d # hide
```

<!-- ![LaTeX text](../assets/figures/latexequation.svg) -->
![LaTeX text](../assets/figures/latexequation.svg)

See the [Writing LaTeX](@ref) section for more information. You'll have to install the fonts that MathTeXEngine.jl requires.

Expand Down
2 changes: 1 addition & 1 deletion docs/src/howto/tables-grids.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ You often want to position graphics at regularly-spaced locations on the drawing

These are types which act as iterators. Their job is to provide you with centerpoints; you'll probably want to use these in combination with the cell's widths and heights.

There are also functions to make hexagonal grids ([Hexagonal](@ref) grids and [EquilateralTriangleGrid](@ref) grids.
There are also functions to make hexagonal grids ([Hexagonal grids](@ref) grids and [EquilateralTriangleGrid](@ref) grids.

## Tiles and partitions

Expand Down
38 changes: 19 additions & 19 deletions docs/src/howto/turtle.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,25 @@ nothing # hide

![turtles](../assets/figures/turtles.png)

|List of words the turtle knows|Action |
|:--- |:--- |
|[`Forward`](@ref) | More forward by d units |
|[`Turn`](@ref) | Increase the turtle's rotation by n degrees |
|[`Circle`](@ref) | Draw filled circle centered at current pos |
|[`HueShift`](@ref) | Shift the Hue of the turtle's pen color by n |
|[`Message`](@ref) | Output text |
|[`Orientation`](@ref) | Set the turtle's orientation to n degrees |
|[`Pen_opacity_random`](@ref) | Set opacity to random value |
|[`Pencolor`](@ref) | Set the Red, Green, and Blue values |
|[`Pendown`](@ref) | Start drawing |
|[`Penup`](@ref) | Stop drawing |
|[`Penwidth`](@ref) | Set the width of the line to n |
|[`Pop `](@ref) | Move turtle to the value stored on the stack |
|[`Push`](@ref) | Save the turtle's position on the stack |
|[`Randomize_saturation`](@ref)| Randomize the saturation of the current color |
|[`Rectangle`](@ref) | Draw filled rectangle centered at current pos |
|[`Reposition`](@ref) | Place turtle at new position |
|[`Towards`](@ref) | Rotate turtle to face towards a point |
|List of words the turtle knows | Arguments | Action |
|:--- | :--- | :--- |
|[`Forward`](@ref) | n (1) | More forward by d units |
|[`Turn`](@ref) | θ (5°) | Increase the turtle's rotation by n° |
|[`Circle`](@ref) | r (1) | Draw filled circle centered at current pos |
|[`HueShift`](@ref) | n (1) | Shift the Hue of the turtle's pen color by n |
|[`Message`](@ref) | t ("") | Output text `t` |
|[`Orientation`](@ref) | θ (5°) | Set the turtle's orientation to θ degrees |
|[`Pen_opacity_random`](@ref) | | Set opacity to random value |
|[`Pencolor`](@ref) | r g b | Set the Red, Green, and Blue values |
|[`Pendown`](@ref) | | Start drawing |
|[`Penup`](@ref) | | Stop drawing |
|[`Penwidth`](@ref) | w | Set the width of the line to w |
|[`Pop `](@ref) | | Move turtle to the value stored on the stack |
|[`Push`](@ref) | | Save the turtle's position on the stack |
|[`Randomize_saturation`](@ref) | | Randomize the saturation of the current color |
|[`Rectangle`](@ref) | w h | Draw filled rectangle centered at current pos |
|[`Reposition`](@ref) | pt | Place turtle at new position `pt` |
|[`Towards`](@ref) | pt | Rotate turtle to face towards a point |

The turtle commands expect a reference to a turtle as the first argument (it doesn't have to be a turtle emoji!).

Expand Down
13 changes: 8 additions & 5 deletions docs/src/tutorial/pixels.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,34 @@ The array `A` should be a matrix where each element is an *ARGB32& value. ARGB32

You can set and get the values of pixels by treating the drawing's array like a standard Julia array. So we can inspect pixels like this:

```@repl blocks
```julia
using Luxor, Colors # hide
A[10, 200] # row 10, column 200

ARGB32(0.0N0f8,0.0N0f8,0.0N0f8,0.0N0f8)
```

and set them like this:

```@repl blocks
```julia
using Luxor, Colors # hide
A[10, 200] = colorant"red"

```

or even like this:

```@repl blocks
```julia
using Luxor, Colors # hide
A[:] = colorant"purple"
A[200:250, 100:250] .= colorant"green";
A[300:350, 50:450] .= colorant"blue";
[A[rand(1:(400 * 800))] = RGB(rand(), rand(), rand()) for i in 1:800];
[A[rand(1:(400 * 800))] = RGB(rand(), rand(), rand()) for i in 1:800]
```

Because this is an array rather than a PNG/SVG, we must either use Images.jl to display it in a notebook or code editor such as VS-Code.

```julia blocks
```julia
A
```

Expand Down
76 changes: 60 additions & 16 deletions docs/src/tutorial/turtle.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ end

![turtle splash](../assets/figures/turtle-splash.png)

Luxor includes "turtle graphics". This refers to a way of making drawings by steering a turtle around a drawing surface. The turtle holds (somehow) a pen, and, following some simple instructions, draws lines behind it as it goes.
Luxor includes "turtle graphics". This is a way of making drawings by steering a turtle around a drawing surface. The turtle holds (somehow) a pen, and, following your instructions, draws lines behind it as it goes.

## How to type the turtle emoji

Expand All @@ -19,7 +19,7 @@ using Luxor
🐢 = Turtle() # type the turtle emoji \:turtle: TAB
```

That's "backslash colon turtle colon tab" in VS-Code, for example. (It's Unicode `U+1F422`.)
That's "backslash colon turtle colon tab" in VS-Code, for example. (It's Unicode character `U+1F422`.)

But you can use any simple Julia variable name if you'd prefer:

Expand Down Expand Up @@ -102,33 +102,41 @@ using Luxor, Colors # hide
function a_pentagon(t::Turtle, s)
for i in 1:5
Forward(t, s)
Turn(t, 72)
Turn(t, 72.0)
end
Turn(t, 72) # it's less interesting without this
end
@drawsvg begin
background("honeydew")
🐢 = Turtle()
for i in 1:100
a_pentagon(🐢, 20 + i)
Penwidth(🐢, 10)
Pencolor(🐢, "red")
for i in 1:5
a_pentagon(🐢, 100.0)
HueShift(🐢, 30)
Turn(🐢, 72)
end
end
```

After the first pentagon is drawn, the turtle rotates 72° - so the next pentagon doesn't overlap the previous one.

## More instructions

Turtles understand more instructions than just `Forward` and `Turn`:

- `Pendown()` (the default)
- `Penup()` lift the pen from the drawing
- `Pencolor()` change the color (default is "black")
- `Penwidth()` change the width
- `Circle()` draw a filled circle
- `Rectangle()` draw a rectangle
- `Reposition()` move the turtle to a new location
- `Message()` draw some text
- `HueShift()` change the hue of the pen's color by a small amount
- `Pendown(🐢)` (the default)
- `Penup(🐢)` lift the pen from the drawing
- `Pencolor(🐢, r, g, b)` change the color (default is (0, 0, 0) or "black")
- `Penwidth(🐢, w)` change the pen width to `w`
- `Circle(🐢, r)` draw a filled circle with radius `r`
- `Orientation(🐢, θ)` face θ°
- `Rectangle(🐢, w, h)` draw a rectangle with width `w` and height `h`
- `Reposition(🐢, pt)` move the turtle to a new location point
- `Message(🐢, t)` draw the text in `t`
- `HueShift(🐢, h)` change the hue of the pen's color by a `h`, or a small amount

plus a few more.

All these require a turtle name as the first argument. The full list is here: [Turtle graphics](@ref).

Expand Down Expand Up @@ -160,6 +168,42 @@ end

You don't have to restrict yourself to drawing lines. With `Circle()` and `Rectangle()` you can create all kinds of images.

```@example
using Luxor, Colors # hide
@drawsvg begin
colors = [Luxor.julia_purple, Luxor.julia_red, Luxor.julia_green]
🐢 = Turtle()
S = 50
Penup(🐢)
Turn(🐢, 30) ; Forward(🐢, S) ; Pencolor(🐢, colors[1]) ; Pendown(🐢) ; Circle(🐢, 40) ; Penup(🐢)
Turn(🐢, 150) ; Forward(🐢, 2S); Pencolor(🐢, colors[2]) ; Pendown(🐢) ; Circle(🐢, 40) ; Penup(🐢)
Turn(🐢, 120) ; Forward(🐢, 2S); Pencolor(🐢, colors[3]) ; Pendown(🐢) ; Circle(🐢, 40) ; Penup(🐢)
end
```

This is not a very elegant approach to drawing the Julia logo. Here's a better way:

```@example
using Luxor, Colors # hide
@drawsvg begin
🐢 = Turtle()
colors = [Luxor.julia_purple, Luxor.julia_red, Luxor.julia_green]
for i in 1:3
Push(🐢)
Orientation(🐢, [30, 150, 270][i])
Penup(🐢)
Forward(🐢, 120)
Pencolor(🐢, colors[i])
Pendown(🐢)
Circle(🐢, 80)
Pop(🐢)
end
end
```

The `Push()` instruction tells the turtle to remember the current position and rotation on a stack. `Pop()` gets that information from the stack and then teleports the turtle back to that position - forgetting where it was and where it was heading. This way, the same task is easily repeated.

## Adding new commands

There's isn't a `Back()` instruction as you might expect - because it could behave in various different ways (is there another 180° turn afterwards?). Here's how to add your own `Back()` command:
Expand Down Expand Up @@ -216,7 +260,7 @@ for i in 1:1500
pt = Point(t.xpos, t.ypos)
if !isinside(pt, box(BoundingBox()))
Reposition(t, pointcrossesboundingbox(pt, BoundingBox()))
Turn(t, 90)
Towards(t, O)
end
end
finish()
Expand Down
85 changes: 51 additions & 34 deletions ext/latex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ function text(lstr::LaTeXString, pt::Point;

translate_x, translate_y = texalign(halign, valign, bottom_pt, top_pt, font_size)

# Writes text using ModernCMU font.
# Write each character
for text in sentence
@layer begin
translate(pt)
Expand All @@ -154,8 +154,9 @@ function text(lstr::LaTeXString, pt::Point;
end

if text[1] isa TeXChar

if paths == true
if paths == false
writelatexchar(text, font_size)
else
fontface(text[1].font.family_name)
fontsize(font_size * text[3])
newsubpath()
Expand All @@ -164,57 +165,73 @@ function text(lstr::LaTeXString, pt::Point;
Point(text[2]...) * font_size * (1, -1),
action=:path,
startnewpath=false)
else
writelatexchar(text, font_size)
end
elseif text[1] isa HLine
# text is eg (HLine{Float64}(0.7105, 0.009), [0.0, 0.2106], 1.0))
# width thick x y scale
if paths == true
if paths == false
pointstart = Point(text[2]...) * font_size * (1, -1)
pointend = pointstart + Point(text[1].width, 0) * font_size
poly([pointstart, pointend], :path)
closepath()
newsubpath()
setline(0.5)
line(pointstart, pointend, :stroke)
else
pointstart = Point(text[2]...) * font_size * (1, -1)
pointend = pointstart + Point(text[1].width, 0) * font_size
setline(0.5)
line(pointstart, pointend, :stroke)
poly([pointstart, pointend], :path)
closepath()
newsubpath()
end
end
end
end
end

# dictionary to convert MathTeXEngine font requirement to toy font names
fontdict = Dict{String, String}(
"NewComputerModern|10 Regular" => "NewCM10-Regular",
"NewComputerModern|10 Italic" => "NewCM10-Italic",
"NewComputerModern|12 Regular" => "NewCM12-Regular",
"NewComputerModern|12 Italic" => "NewCM12-Italic",
"NewComputerModern Math|Regular" => "NewCMMath-Regular",
)

"""
writelatexchar(t::AbstractString)
Helper function to handle extra chars that are not supported
in MathTeXEngine.
"""
function writelatexchar(text, font_size)
# Extra chars not supported by MathTeXEngine
extrachars = ["⨟","{","}","𝔸","𝔹","ℂ","𝔻","𝔽", "𝔾", "ℍ", "𝕀", "𝕁", "𝕂", "𝕃", "𝕄", "ℕ", "𝕆", "ℙ", "ℚ", "ℝ", "𝕊", "𝕋", "𝕌", "𝕎", "𝕍", "𝕏", "ℤ", "𝔄", "𝔅", "ℭ", "𝔇", "𝔈", "𝔉", "𝔊", "ℌ", "ℑ", "𝔍", "𝔎", "𝔏", "𝔐", "𝔑", "𝔒", "𝔓", "𝔔", "ℜ", "𝔖", "𝔗", "𝔘", "𝔙", "𝔚", "𝔛", "𝔜", "ℨ", "𝕬", "𝕭", "𝕮", "𝕯", "𝕰", "𝕱", "𝕲", "𝕳", "𝕴", "𝕵", "𝕶", "𝕷", "𝕸", "𝕹", "𝕺", "𝕻", "𝕼", "𝕽", "𝕾", "𝕿", "𝖀", "𝖁", "𝖂", "𝖃", "𝖄", "𝖅", "𝒜", "ℬ", "𝒞", "𝒟", "ℰ", "ℱ", "𝒢", "ℋ", "ℐ", "𝒥", "𝒦", "ℒ", "ℳ", "𝒩", "𝒪", "𝒫", "𝒬", "ℛ", "𝒮", "𝒯", "𝒰", "𝒱", "𝒲", "𝒳", "𝒴", "𝒵","𝕒","𝕓" ,"𝕔" ,"𝕕" ,"𝕖" ,"𝕗","𝕘" ,"𝕙" ,"𝕚" ,"𝕛" ,"𝕜" ,"𝕝" ,"𝕞" ,"𝕟" ,"𝕠" ,"𝕡" ,"𝕢" ,"𝕣" ,"𝕤" ,"𝕥" ,"𝕦" ,"𝕧" ,"𝕩" ,"𝕨" ,"𝕪" ,"𝕫" ,"𝐚" ,"𝐛" ,"𝐜" ,"𝐝" ,"𝐞" ,"𝐟" ,"𝐠" ,"𝐡" ,"𝐢" ,"𝐣" ,"𝐤" ,"𝐥" ,"𝐦" ,"𝐧" ,"𝐨" ,"𝐩" ,"𝐪" ,"𝐫" ,"𝐬" ,"𝐭" ,"𝐮" ,"𝐯" ,"𝐱" ,"𝐰" ,"𝐲" ,"𝐳" ,"𝐀" ,"𝐁" ,"𝐂" ,"𝐃" ,"𝐄" ,"𝐅" ,"𝐆" ,"𝐇" ,"𝐈" ,"𝐉" ,"𝐊" ,"𝐋" ,"𝐌" ,"𝐍" ,"𝐎" ,"𝐏" ,"𝐐" ,"𝐑" ,"𝐒" ,"𝐓" ,"𝐔" ,"𝐕" ,"𝐗" ,"𝐖" ,"𝐘" ,"𝐙" ,"𝓐" ,"𝓑" ,"𝓒" ,"𝓓" ,"𝓔" ,"𝓕" ,"𝓖" ,"𝓗" ,"𝓘" ,"𝓙" ,"𝓚" ,"𝓛" ,"𝓜" ,"𝓝" ,"𝓞" ,"𝓟" ,"𝓠" ,"𝓡" ,"𝓢" ,"𝓣" ,"𝓤" ,"𝓥" ,"𝓧" ,"𝓦" ,"𝓨" ,"𝓩"]

fontface(text[1].font.family_name)
fontsize(font_size * text[3])

if string(text[1].represented_char) == ""
setfont(text[1].font.family_name, font_size * text[3])
Luxor.settext(string(text[1].represented_char), Point(text[2]...) * font_size * (1, -1)+Point(0.25,0.3)*font_size)

elseif text[1].represented_char == '{' || text[1].represented_char == '}'
Luxor.text(string(text[1].represented_char), Point(text[2]...) * font_size * (1, -1)+Point(0,-0.8)*font_size)

elseif string(text[1].represented_char) in extrachars
setfont(text[1].font.family_name, 1.3font_size * text[3])
Luxor.settext(string(text[1].represented_char), Point(text[2]...) * font_size * (1, -1)+Point(0,0.3)*font_size)

else
Luxor.text(string(text[1].represented_char), Point(text[2]...) * font_size * (1, -1))
function writelatexchar(texchar, font_size)
#extrachars = ["⨟","{","}","𝔸","𝔹","ℂ","𝔻","𝔽", "𝔾", "ℍ", "𝕀", "𝕁", "𝕂", "𝕃", "𝕄", "ℕ", "𝕆", "ℙ", "ℚ", "ℝ", "𝕊", "𝕋", "𝕌", "𝕎", "𝕍", "𝕏", "ℤ", "𝔄", "𝔅", "ℭ", "𝔇", "𝔈", "𝔉", "𝔊", "ℌ", "ℑ", "𝔍", "𝔎", "𝔏", "𝔐", "𝔑", "𝔒", "𝔓", "𝔔", "ℜ", "𝔖", "𝔗", "𝔘", "𝔙", "𝔚", "𝔛", "𝔜", "ℨ", "𝕬", "𝕭", "𝕮", "𝕯", "𝕰", "𝕱", "𝕲", "𝕳", "𝕴", "𝕵", "𝕶", "𝕷", "𝕸", "𝕹", "𝕺", "𝕻", "𝕼", "𝕽", "𝕾", "𝕿", "𝖀", "𝖁", "𝖂", "𝖃", "𝖄", "𝖅", "𝒜", "ℬ", "𝒞", "𝒟", "ℰ", "ℱ", "𝒢", "ℋ", "ℐ", "𝒥", "𝒦", "ℒ", "ℳ", "𝒩", "𝒪", "𝒫", "𝒬", "ℛ", "𝒮", "𝒯", "𝒰", "𝒱", "𝒲", "𝒳", "𝒴", "𝒵","𝕒","𝕓" ,"𝕔" ,"𝕕" ,"𝕖" ,"𝕗","𝕘" ,"𝕙" ,"𝕚" ,"𝕛" ,"𝕜" ,"𝕝" ,"𝕞" ,"𝕟" ,"𝕠" ,"𝕡" ,"𝕢" ,"𝕣" ,"𝕤" ,"𝕥" ,"𝕦" ,"𝕧" ,"𝕩" ,"𝕨" ,"𝕪" ,"𝕫" ,"𝐚" ,"𝐛" ,"𝐜" ,"𝐝" ,"𝐞" ,"𝐟" ,"𝐠" ,"𝐡" ,"𝐢" ,"𝐣" ,"𝐤" ,"𝐥" ,"𝐦" ,"𝐧" ,"𝐨" ,"𝐩" ,"𝐪" ,"𝐫" ,"𝐬" ,"𝐭" ,"𝐮" ,"𝐯" ,"𝐱" ,"𝐰" ,"𝐲" ,"𝐳" ,"𝐀" ,"𝐁" ,"𝐂" ,"𝐃" ,"𝐄" ,"𝐅" ,"𝐆" ,"𝐇" ,"𝐈" ,"𝐉" ,"𝐊" ,"𝐋" ,"𝐌" ,"𝐍" ,"𝐎" ,"𝐏" ,"𝐐" ,"𝐑" ,"𝐒" ,"𝐓" ,"𝐔" ,"𝐕" ,"𝐗" ,"𝐖" ,"𝐘" ,"𝐙" ,"𝓐" ,"𝓑" ,"𝓒" ,"𝓓" ,"𝓔" ,"𝓕" ,"𝓖" ,"𝓗" ,"𝓘" ,"𝓙" ,"𝓚" ,"𝓛" ,"𝓜" ,"𝓝" ,"𝓞" ,"𝓟" ,"𝓠" ,"𝓡" ,"𝓢" ,"𝓣" ,"𝓤" ,"𝓥" ,"𝓧" ,"𝓦" ,"𝓨" ,"𝓩"]

# first(texchar)[1] has fields .font .font_family .glyph_id .represented_char .slanted,
finfo = first(texchar)

represented_char = finfo.represented_char
required_font_family_name = finfo.font.family_name
required_font_style_name = finfo.font.style_name
chosen_font = get(fontdict, string(required_font_family_name, "|", required_font_style_name), "TimesNewRoman")

@debug begin
print(represented_char)
print(": ", required_font_family_name)
print(": ", required_font_style_name)
println(": ", chosen_font)
end

# if string(represented_char) == "⨟"
# setfont(chosen_font, font_size * texchar[3])
# Luxor.settext(string(represented_char), Point(texchar[2]...) * font_size * (1, -1) + Point(0.25,0.3) * font_size)

# elseif represented_char == '{' || represented_char == '}'
# setfont(chosen_font, font_size * texchar[3])
# Luxor.text(string(represented_char), Point(texchar[2]...) * font_size * (1, -1) + Point(0, -0.8) * font_size)

# elseif string(represented_char) in extrachars
# setfont(chosen_font, 1.3font_size * texchar[3])
# Luxor.settext(string(represented_char), Point(texchar[2]...) * font_size * (1, -1) + Point(0, 0.3) * font_size)
# else
fontface(chosen_font)
fontsize(font_size * texchar[3])
Luxor.text(string(represented_char), Point(texchar[2]...) * font_size * (1, -1))
#end
end

# versions of _textformat that accept LaTeXString
Expand Down
Loading

0 comments on commit e0029b8

Please sign in to comment.