Skip to content

Commit 3a6f865

Browse files
committed
paths
1 parent 48fb5b4 commit 3a6f865

File tree

6 files changed

+205
-75
lines changed

6 files changed

+205
-75
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- 3D LaTex text
8+
- draw Luxor paths
89

910
### Changed
1011

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df"
1414

1515
[compat]
1616
julia = "1"
17-
Luxor = "1, 2, 3"
17+
Luxor = "3.1"
1818
StaticArrays = "0.10, 0.11, 0.12, 1.0"
1919
Rotations = "1"
2020
Colors = "0.7, 0.8, 0.9, 0.10, 0.11, 0.12, 0.13, 0.14"

examples/latex3d.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using Thebes
2+
using Luxor
3+
using LaTeXStrings
4+
using MathTeXEngine
5+
using MathTeXEngine
6+
using Rotations
7+
8+
@draw begin
9+
background(0.0, 0.05, 0.1)
10+
helloworld()
11+
perspective(300)
12+
eyepoint(200, 200, 200)
13+
sethue("white")
14+
fontsize(20)
15+
setline(1)
16+
e = L"e^{i\pi} + 1 = 0"
17+
for i in 0:π/10:2π - π/10
18+
text3D(e,
19+
sphericaltocartesian(50, i, π/2),
20+
action=:stroke,
21+
about=sphericaltocartesian(50, i, π/2),
22+
rotation=RotZ(i),
23+
portion=1.0)
24+
end
25+
end

src/Thebes.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ include("utils.jl")
1717
include("Projection.jl")
1818
include("Object.jl")
1919
include("pin.jl")
20+
include("path.jl")
2021
include("text.jl")
2122

2223
export project, Projection, newprojection,

src/latex.jl

Lines changed: 93 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,90 +2,109 @@ using LaTeXStrings
22
import MathTeXEngine:
33
generate_tex_elements, inkwidth, inkheight, bottominkbound, TeXChar, HLine
44

5+
# function text3D(str::LaTeXString, anchor::Point3D;
6+
# halign=:left,
7+
# valign=:baseline,
8+
# about=Point3D(0., 0., 0.),
9+
# rotation::Rotation=RotXYZ(0, 0, 0))
10+
#
11+
# # this is just a hacked version of the Luxor code
12+
#
13+
# # Function from MathTexEngine
14+
# sentence = generate_tex_elements(str)
15+
#
16+
# # Get current font size.
17+
# font_size = get_fontsize()
18+
#
19+
# textw, texth = latextextsize(str)
20+
# bottom_pt, top_pt = rawlatexboundingbox(str)
21+
#
22+
# translate_x, translate_y = Luxor.texalign(halign, valign, bottom_pt, top_pt, font_size)
23+
#
24+
# pt = project(anchor)
25+
#
26+
# rotationfixed=true
27+
# # Writes text using ModernCMU font.
28+
# for text in sentence
29+
# if isnothing(pt)
30+
# continue
31+
# end
32+
# @layer begin
33+
# translate(pt)
34+
# if !rotationfixed
35+
# #rotate(angle) ?
36+
# translate(translate_x, translate_y)
37+
# else
38+
# l_pt, r_pt = Luxor.latexboundingbox(str, halign = halign, valign = valign)
39+
# translate((l_pt + r_pt)/2)
40+
# #rotate(angle) ?
41+
# translate(Point(translate_x, translate_y) - (l_pt + r_pt)/2)
42+
# end
43+
# if text[1] isa TeXChar
44+
# fontface(text[1].font.family_name)
45+
# fontsize(font_size * text[3])
46+
#
47+
# textoutlines(string(text[1].char), Point(text[2]...) * font_size * (1, -1), :path,
48+
# halign=halign,
49+
# valign=valign,
50+
# startnewpath=true)
51+
# o = getpathflat()
52+
# newpath() # otherwise the path would be drawn twice
53+
# for e in o
54+
# if e.element_type == 0
55+
# (x, y) = e.points
56+
# newpt = rotateby(Point3D(anchor.x + x, anchor.y -y, anchor.z), about, rotation)
57+
# pin(newpt, gfunction = (p3, p2) -> move(p2))
58+
# elseif e.element_type == 1
59+
# (x, y) = e.points
60+
# newpt = rotateby(Point3D(anchor.x + x, anchor.y -y, anchor.z), about, rotation)
61+
# pin(newpt, gfunction = (p3, p2) -> line(p2))
62+
# elseif e.element_type == 3
63+
# closepath()
64+
# else
65+
# error("unknown path element " * repr(e.element_type) * repr(e.points))
66+
# end
67+
# end
68+
# fillpath()
69+
#
70+
# elseif text[1] isa HLine
71+
# pointstart = Point(text[2]...) * font_size * (1, -1)
72+
# pointend = pointstart + Point(text[1].width, 0) * font_size
73+
#
74+
# newstartpt = rotateby(Point3D(anchor.x + pointstart.x, anchor.y - pointstart.y, anchor.z), about, rotation)
75+
# newendpt = rotateby(Point3D(anchor.x + pointend.x, anchor.y - pointend.y, anchor.z), about, rotation)
76+
#
77+
# pin(newstartpt, newendpt)
78+
# end
79+
# end
80+
# end
81+
# end
82+
83+
# try this version using Paths
584

685
"""
7-
text3D(str::LaTeXString, anchor::Point3D;
86+
text3D(str::LaTeXString, anchor::Point3D;
887
halign=:left,
988
valign=:baseline,
1089
about=Point3D(0., 0., 0.),
11-
rotation::Rotation=RotXYZ(0, 0, 0))
90+
rotation::Rotation=RotXYZ(0, 0, 0),
91+
portion = 1.0,
92+
steps = 20,
93+
startnewpath=true,
94+
action=:fill)
1295
1396
Like `text3D()` but draws LaTeX strings.
1497
"""
1598
function text3D(str::LaTeXString, anchor::Point3D;
1699
halign=:left,
17100
valign=:baseline,
18101
about=Point3D(0., 0., 0.),
19-
rotation::Rotation=RotXYZ(0, 0, 0))
20-
21-
# this is just a hacked version of the Luxor code
22-
23-
# Function from MathTexEngine
24-
sentence = generate_tex_elements(str)
25-
26-
# Get current font size.
27-
font_size = get_fontsize()
28-
29-
textw, texth = latextextsize(str)
30-
bottom_pt, top_pt = rawlatexboundingbox(str)
31-
32-
translate_x, translate_y = Luxor.texalign(halign, valign, bottom_pt, top_pt, font_size)
33-
34-
pt = project(anchor)
35-
36-
rotationfixed=true
37-
# Writes text using ModernCMU font.
38-
for text in sentence
39-
if isnothing(pt)
40-
continue
41-
end
42-
@layer begin
43-
translate(pt)
44-
if !rotationfixed
45-
#rotate(angle) ?
46-
translate(translate_x, translate_y)
47-
else
48-
l_pt, r_pt = Luxor.latexboundingbox(str, halign = halign, valign = valign)
49-
translate((l_pt + r_pt)/2)
50-
#rotate(angle) ?
51-
translate(Point(translate_x, translate_y) - (l_pt + r_pt)/2)
52-
end
53-
if text[1] isa TeXChar
54-
fontface(text[1].font.family_name)
55-
fontsize(font_size * text[3])
56-
# Luxor.text(string(text[1].char), Point(text[2]...) * font_size * (1, -1))
57-
textoutlines(string(text[1].char), Point(text[2]...) * font_size * (1, -1), :path,
58-
halign=halign,
59-
valign=valign,
60-
startnewpath=true)
61-
o = getpathflat()
62-
newpath() # otherwise the path would be drawn twice
63-
for e in o
64-
if e.element_type == 0
65-
(x, y) = e.points
66-
newpt = rotateby(Point3D(anchor.x + x, anchor.y -y, anchor.z), about, rotation)
67-
pin(newpt, gfunction = (p3, p2) -> move(p2))
68-
elseif e.element_type == 1
69-
(x, y) = e.points
70-
newpt = rotateby(Point3D(anchor.x + x, anchor.y -y, anchor.z), about, rotation)
71-
pin(newpt, gfunction = (p3, p2) -> line(p2))
72-
elseif e.element_type == 3
73-
closepath()
74-
else
75-
error("unknown path element " * repr(e.element_type) * repr(e.points))
76-
end
77-
end
78-
fillpath()
79-
80-
elseif text[1] isa HLine
81-
pointstart = Point(text[2]...) * font_size * (1, -1)
82-
pointend = pointstart + Point(text[1].width, 0) * font_size
83-
84-
newstartpt = rotateby(Point3D(anchor.x + pointstart.x, anchor.y - pointstart.y, anchor.z), about, rotation)
85-
newendpt = rotateby(Point3D(anchor.x + pointend.x, anchor.y - pointend.y, anchor.z), about, rotation)
86-
87-
pin(newstartpt, newendpt)
88-
end
89-
end
90-
end
102+
rotation::Rotation=RotXYZ(0, 0, 0),
103+
portion = 1.0,
104+
steps = 20,
105+
startnewpath=true,
106+
action=:fill)
107+
text(str, paths=true, halign=halign, valign=valign) # angle? rotationfixed?
108+
latexpath = storepath()
109+
drawpath(latexpath, portion, anchor, action=action, startnewpath=startnewpath, about=about, rotation=rotation, steps=steps)
91110
end

src/path.jl

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import Luxor: drawpath
2+
3+
"""
4+
drawpath(path::Path, k::Real, anchor::Point3D;
5+
about=Point3D(0., 0., 0.),
6+
rotation::Rotation=RotXYZ(0, 0, 0),
7+
steps=20, # used when approximating Bezier curve segments
8+
action=:none,
9+
startnewpath=true,
10+
pathlength = 0.0)
11+
12+
Draw a Luxor Path object on a 2D plane, starting at `anchor`
13+
with `rotation` about `about`.
14+
"""
15+
function drawpath(path::Path, k::Real, anchor::Point3D;
16+
about=Point3D(0., 0., 0.),
17+
rotation::Rotation=RotXYZ(0, 0, 0),
18+
steps=20, # used when approximating Bezier curve segments
19+
action=:none,
20+
startnewpath=true,
21+
pathlength = 0.0)
22+
23+
if iszero(pathlength)
24+
pathlength = Luxor.pathlength(path)
25+
end
26+
requiredlength = k * pathlength # we'll stop when we get past this
27+
currentlength = 0
28+
startnewpath && newpath()
29+
# firstpoint₂ is the point we will return to for a Close
30+
# currentpoint₂ is Cairo's current point
31+
# mostrecentpoint₂ is the point we last visited
32+
currentpoint₂ = mostrecentpoint₂ = firstpoint₂ = O
33+
for pathelement in path
34+
# move
35+
if pathelement isa PathMove
36+
currentpoint₂ = firstpoint₂ = pathelement.pt1
37+
newpt = rotateby(Point3D(anchor.x + currentpoint₂.x, anchor.y - currentpoint₂.y, anchor.z), about, rotation)
38+
newpt₂ = project(newpt)
39+
if !isnothing(newpt₂)
40+
move(newpt₂)
41+
end
42+
# curve
43+
elseif pathelement isa PathCurve
44+
plength = Luxor.get_bezier_length(BezierPathSegment(currentpoint₂, pathelement.pt1, pathelement.pt2, pathelement.pt3), steps=steps)
45+
controlpoint1₃ = rotateby(Point3D(anchor.x + pathelement.pt1.x, anchor.y - pathelement.pt1.y, anchor.z), about, rotation)
46+
controlpoint2₃ = rotateby(Point3D(anchor.x + pathelement.pt2.x, anchor.y - pathelement.pt2.y, anchor.z), about, rotation)
47+
endpoint₃ = rotateby(Point3D(anchor.x + pathelement.pt3.x, anchor.y - pathelement.pt3.y, anchor.z), about, rotation)
48+
pts₂ = map(p -> project(Point3D(p)), (controlpoint1₃, controlpoint2₃, endpoint₃))
49+
if all(!isnothing, pts₂) # draw if all points are valid
50+
curve(pts₂...)
51+
end
52+
mostrecentpoint₂ = currentpoint₂ # remember how far we got
53+
currentpoint₂ = pathelement.pt3 # update currentpoint₂
54+
currentlength += plength
55+
# line
56+
elseif pathelement isa PathLine # pt1
57+
plength = distance(currentpoint₂, pathelement.pt1)
58+
newpt₃ = rotateby(Point3D(anchor.x + pathelement.pt1.x, anchor.y - pathelement.pt1.y, anchor.z), about, rotation)
59+
endpoint₂ = project(newpt₃)
60+
if !isnothing(endpoint₂)
61+
line(endpoint₂)
62+
end
63+
mostrecentpoint₂ = currentpoint₂
64+
currentpoint₂ = pathelement.pt1
65+
currentlength += plength
66+
# close
67+
elseif pathelement isa PathClose
68+
plength = distance(currentpoint₂, firstpoint₂)
69+
# I think Close is just drawing to the point established by previous Move...
70+
endpoint₃ = rotateby(Point3D(anchor.x + firstpoint₂.x, anchor.y - firstpoint₂.y, anchor.z), about, rotation)
71+
endpoint₂ = project(endpoint₃)
72+
if !isnothing(endpoint₂)
73+
line(endpoint₂)
74+
end
75+
mostrecentpoint₂ = currentpoint₂
76+
currentpoint₂ = firstpoint₂
77+
currentlength += plength
78+
end
79+
if currentlength > requiredlength || abs(currentlength - requiredlength) < 1.0
80+
break
81+
end
82+
end
83+
do_action(action)
84+
end

0 commit comments

Comments
 (0)