Luxor.@drawMacro
@draw drawing-instructions [width] [height]

Preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The drawing is stored in memory, not in a file on disk.

Examples

@draw circle(O, 20, :fill)

@draw circle(O, 20, :fill) 400

@draw circle(O, 20, :fill) 400 1200

@draw begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end

@draw begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
source
Luxor.@drawsvgMacro
@drawsvg begin
body
end w h

Create and preview an SVG drawing. Like @draw but using SVG format.

Unlike @draw (PNG), there is no background, by default.

source
Luxor.@epsMacro
@eps drawing-instructions [width] [height] [filename]

Create and preview an EPS drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename if supplied, or luxor-drawing(timestamp).eps.

On some platforms, EPS files are converted automatically to PDF when previewed.

Examples

@eps circle(O, 20, :fill)

@eps circle(O, 20, :fill) 400

@eps circle(O, 20, :fill) 400 1200

@eps circle(O, 20, :fill) 400 1200 "/tmp/A0-version"

@eps circle(O, 20, :fill) 400 1200 "/tmp/A0-version.eps"

@eps begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end

@eps begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
source
Luxor.@imagematrixMacro
@imagematrix drawing-instructions [width=256] [height=256]

Create a drawing and return a matrix of the image.

This macro returns a matrix of pixels that represent the drawing produced by the vector graphics instructions. It uses the image_as_matrix() function.

The default drawing is 256 by 256 points.

You don't need finish() (the macro calls it), and it's not previewed by preview().

m = @imagematrix begin
sethue("red")
box(O, 20, 20, :fill)
end 60 60

julia>  m[1220:1224] |> show
ARGB32[ARGB32(0.0N0f8,0.0N0f8,0.0N0f8,0.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8)]


If, for some strange reason you want to draw the matrix as another Luxor drawing again, use code such as this:

m = @imagematrix begin
sethue("red")
box(O, 20, 20, :fill)
sethue("blue")
box(O, 10, 40, :fill)
end 60 60

function convertmatrixtocolors(m)
return convert.(Colors.RGBA, m)
end

function drawimagematrix(m)
d = Drawing(500, 500, "/tmp/temp.png")
origin()
w, h = size(m)
t = Tiler(500, 500, w, h)
mi = convertmatrixtocolors(m)
@show mi[30, 30]
for (pos, n) in t
c = mi[t.currentrow, t.currentcol]
setcolor(c)
box(pos, t.tilewidth -1, t.tileheight - 1, :fill)
end
finish()
return d
end

drawimagematrix(m)

Transparency

The default value for the cells in an image matrix is transparent black. (Luxor's default color is opaque black.)

julia> @imagematrix begin
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(0.0,0.0,0.0,0.0)  ARGB32(0.0,0.0,0.0,0.0)
ARGB32(0.0,0.0,0.0,0.0)  ARGB32(0.0,0.0,0.0,0.0)

Setting the background to a partially or completely transparent value may give unexpected results:

julia> @imagematrix begin
background(1, 0.5, 0.0, 0.5) # semi-transparent orange
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(0.502,0.251,0.0,0.502)  ARGB32(0.502,0.251,0.0,0.502)
ARGB32(0.502,0.251,0.0,0.502)  ARGB32(0.502,0.251,0.0,0.502)

here the semi-transparent orange color has been partially applied to the transparent background.

julia> @imagematrix begin
sethue(1., 0.5, 0.0)
paint()
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(1.0,0.502,0.0,1.0)  ARGB32(1.0,0.502,0.0,1.0)
ARGB32(1.0,0.502,0.0,1.0)  ARGB32(1.0,0.502,0.0,1.0)

picks up the default alpha of 1.0.

source
Luxor.@imagematrix!Macro
@imagematrix! buffer drawing-instructions [width=256] [height=256]

Like @imagematrix, but use an existing UInt32 buffer.

w = 200
h  = 150
buffer = zeros(UInt32, w, h)
m = @imagematrix! buffer juliacircles(40) 200 150;
Images.RGB.(m)
source
Luxor.@pdfMacro
@pdf drawing-instructions [width] [height] [filename]

Create and preview an PDF drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename if supplied, or luxor-drawing(timestamp).pdf.

Examples

@pdf circle(O, 20, :fill)

@pdf circle(O, 20, :fill) 400

@pdf circle(O, 20, :fill) 400 1200

@pdf circle(O, 20, :fill) 400 1200 "/tmp/A0-version"

@pdf circle(O, 20, :fill) 400 1200 "/tmp/A0-version.pdf"

@pdf begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end

@pdf begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
source
Luxor.@pngMacro
@png drawing-instructions [width] [height] [filename]

Create and preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename, if supplied, or luxor-drawing(timestamp).png.

Examples

@png circle(O, 20, :fill)

@png circle(O, 20, :fill) 400

@png circle(O, 20, :fill) 400 1200

@png circle(O, 20, :fill) 400 1200 "/tmp/round"

@png circle(O, 20, :fill) 400 1200 "/tmp/round.png"

@png begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end

@png begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
source
Luxor.@polarMacro
@polar (p)

Convert a tuple of two numbers to a Point of x, y Cartesian coordinates.

@polar (10, pi/4)
@polar [10, pi/4]
@polar 10, pi/4

produces

Luxor.Point(7.0710678118654755, 7.071067811865475)
source
Luxor.@savesvgMacro
@savesvg begin
body
end w h

Like @drawsvg but returns the raw SVG code of the drawing in a string. Uses svgstring.

Unlike @draw (PNG), there is no background, by default.

THis example scans the generated SVG for color values:

s = @savesvg begin
julialogo()
end

eachmatch(r"rgb.*?;", s) |> collect

5-element Vector{RegexMatch}:
RegexMatch("rgb(0%,0%,0%);")
RegexMatch("rgb(25.1%,38.8%,84.7%);")
RegexMatch("rgb(22%,59.6%,14.9%);")
RegexMatch("rgb(58.4%,34.5%,69.8%);")
RegexMatch("rgb(79.6%,23.5%,20%);")

source
Luxor.@svgMacro
@svg drawing-instructions [width] [height] [filename]

Create and preview an SVG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename if supplied, or luxor-drawing-(timestamp).svg.

Examples

@svg circle(O, 20, :fill)

@svg circle(O, 20, :fill) 400

@svg circle(O, 20, :fill) 400 1200

@svg circle(O, 20, :fill) 400 1200 "/tmp/test"

@svg circle(O, 20, :fill) 400 1200 "/tmp/test.svg"

@svg begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end

@svg begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
source
Base.:*Method

*(m::Matrix, pt::Point)

Transform a point pt by the 3×3 matrix m.

julia> M = [2 0 0; 0 2 0; 0 0 1]
3×3 Matrix{Int64}:
2  0  0
0  2  0
0  0  1

julia> M * Point(20, 20)
Point(40.0, 40.0)

To convert between Cairo matrices (6-element Vector{Float64}) to a 3×3 Matrix, use cairotojuliamatrix() and juliatocairomatrix().

source
Base.convertMethod
convert(Point, bbox::BoundingBox)

Convert a BoundingBox to a four-point clockwise polygon.

convert(Vector{Point}, BoundingBox())
source
Base.hcatMethod
hcat(D::Drawing...; valign=:top, hpad=0, clip=true)

Create a new SVG drawing by horizontal concatenation of SVG drawings. If drawings have different height, the valign option can be used in order to define how to align. The hpad argument can be used to add padding between concatenated images.

The clip argument is a boolean for whether the concatenated images should be clipped before concatenation. Note that drawings sometimes have elements that go beyond it's margins, and they only show when the image is drawn in a larger canvas. The clip argument ensures that these elements are not drawn in the concatenated drawing.

Example:

d1 = Drawing(200, 100, :svg)
origin()
circle(O, 60, :fill)
finish()

d2 = Drawing(200, 200, :svg)
rect(O, 200, 200, :fill)
finish()
hcat(d1, d2; hpad = 10, valign = :top, clip = true)
source
Base.inMethod
in(pt, bbox::BoundingBox)

Test whether pt is inside bbox.

source
Base.isequalMethod

isequal(p1::Point, p2::Point) = isapprox(p1.x, p2.x, atol = 0.00000001) && (isapprox(p1.y, p2.y, atol = 0.00000001))

Compare points.

source
Base.randMethod
rand(bbox::BoundingBox)

Return a random Point that lies inside bbox.

source
Base.vcatMethod
vcat(D::Drawing...; halign=:left, vpad=0, clip=true)

Creates a new SVG drawing by vertical concatenation of SVG drawings. If drawings have different widths, the halign option can be used in order to define how to align. The vpad argument can be used to add padding between concatenated images.

The clip argument is a boolean for whether the concatenated images should be clipped before concatenation. Note that drawings sometimes have elements that go beyond it's margins, and they only show when the image is drawn in a larger canvas. The clip argument ensures that these elements are not drawn in the concatenated drawing.

Example:

d1 = Drawing(200, 100, :svg)
origin()
circle(O, 60, :fill)
finish()

d2 = Drawing(200, 200, :svg)
rect(O, 200, 200, :fill)
finish()
vcat(d1, d2; vpad = 10, halign = :left, clip = true)
source
Luxor.CircleFunction
Circle(t::Turtle, radius=1.0)

Draw a filled circle centered at the current position with the given radius.

source
Luxor.ForwardFunction
Forward(t::Turtle, d=1)

Move the turtle forward by d units. The stored position is updated.

source
Luxor.HueShiftFunction
HueShift(t::Turtle, inc=1.0)

Shift the Hue of the turtle's pen forward by inc. Hue values range between 0 and 360. (Don't start with black, otherwise the saturation and brightness values will be black.)

source
Luxor.OrientationFunction
Orientation(t::Turtle, r=0.0)

Set the turtle's orientation to r degrees. See also Turn.

source
Luxor.RectangleFunction
Rectangle(t::Turtle, width=10.0, height=10.0)

Draw a filled rectangle centered at the current position with the given radius.

source
Luxor.RepositionMethod
Reposition(t::Turtle, pos::Point)
Reposition(t::Turtle, x, y)

Reposition: pick the turtle up and place it at another position.

source
Luxor.TurnFunction
Turn(t::Turtle, r=5.0)

Increase the turtle's rotation by r degrees. See also Orientation.

source
Luxor._adjust_background_rectsMethod
_adjust_background_rects(buffer::UInt8[]; addmarker = true)

See issue https://github.com/JuliaGraphics/Luxor.jl/issues/150 for discussion details.
Setting backgrounds in a recording surface (:rec) and creating a svg from it result in elements as

<rect x="0" y="0" width="16777215" height="16777215" .../>

independent of an existing transformation matrix (e.g. set with origin(...) or snapshot with a crop bounding box).
An existing transformation matrix manifests in the svg file as

<use xlink:href="#surface199" transform="matrix(3 1 -1 3 30 40)"/>

which is applied to every element including the background rects.
This transformation needs to be inversed for the background rects which is added in this function.

If addmarker is not set to false, a class property is set as marker:

<rect class="luxor_adjusted" x="0" y="0" width="16777215" height="16777215" .../>
source
Luxor._betweenpolyMethod
_betweenpoly(loop1, loop2, k;
samples = 100,
easingfunction = easingflat)

Find a simple polygon between the two simple polygons loop1 and loop2 corresponding to k, where 0.0 < k < 1.0.

By default, easingfunction = easingflat, so the intermediate steps are linearly spaced. If you use another easing function, intermediate steps are determined by the value of the easing function at k.

Used by polymorph().

Because polysample() can treat the polygon as open or closed (with different results), you can specify how the sampling is done here, with the closed= keyword:

• closed = true -> polygons are sampled as closed

• closed = false -> polygons are sampled as open

• closed = (true, false) -> first polygon is sampled as closed, second as open

source
Luxor._drawing_indicesMethod
Luxor._drawing_indices()

Get a UnitRange over all available indices of drawings.

With Luxor you can work on multiple drawings simultaneously. Each drawing is stored in an internal array. The first drawing is stored at index 1 when you start a drawing with Drawing(...). To start a second drawing you call Luxor._set_next_drawing_index(), which returns the new index. Calling another Drawing(...) stores the second drawing at this new index. Luxor._set_next_drawing_index() will return and set the next available index which is available for a new drawing. This can be a new index at the end of drawings, or, if you already finished a drawing with finish(), the index of this finished drawing. To specify on which drawing the next graphics command should be applied you call Luxor._set_drawing_index(i). All successive Luxor commands work on this drawing. With Luxor._get_drawing_index() you get the current active drawing index.

Multiple drawings is especially helpful for interactive graphics with live windows like MiniFB.

Example:

using Luxor
Drawing(500, 500, "1.svg")
origin()
setcolor("red")
circle(Point(0, 0), 100, action = :fill)

Luxor._drawing_indices()               # returns 1:1

Luxor._get_next_drawing_index()        # returns 2 but doesn't change current drawing
Luxor._set_next_drawing_index()        # returns 2 and sets current drawing to it
Drawing(500, 500, "2.svg")
origin()
setcolor("green")
circle(Point(0, 0), 100, action = :fill)

Luxor._drawing_indices()               # returns 1:2
Luxor._set_drawing_index(1)            # returns 1

finish()
preview()                             # presents the red circle 1.svg

Luxor._drawing_indices()               # returns 1:2
Luxor._set_next_drawing_index()        # returns 1 because drawing 1 was finished before

Drawing(500, 500, "3.svg")
origin()
setcolor("blue")
circle(Point(0, 0), 100, action = :fill)

finish()
preview()                             # presents the blue circle 3.svg

Luxor._set_drawing_index(2)            # returns 2
finish()
preview()                             # presents the green circle 2.svg

Luxor._drawing_indices()               # returns 1:2, but all are finished
Luxor._set_drawing_index(1)            # returns 1

preview()                             # presents the blue circle 3.svg again

Luxor._set_drawing_index(10)           # returns 1 as 10 does not existing
Luxor._get_drawing_index()             # returns 1
Luxor._get_next_drawing_index()        # returns 1, because 1 was finished
source
Luxor._empty_neighbourhoodMethod

emptyneighbourhood(sample, w, h, cellsize, d, points, grid)

Uses entries in grid to check whether the sample point is more than d units away from any other point in points.

The region we're analyzing lies between the origin and Point(w, h).

source
Luxor._get_next_drawing_indexMethod
Luxor._get_next_drawing_index()

Returns the next available drawing index. This can either be a new index or an existing index where a finished (finish()) drawing was stored before.

source
Luxor._set_drawing_indexMethod
Luxor._set_drawing_index(i::Int)

Set the active drawing for successive graphic commands to index i if exist. if index i doesn't exist, the current drawing is unchanged.

Returns the current drawing index.

Example:

next_index=5
if Luxor._set_drawing_index(next_index) == next_index
# do some additional graphics on the existing drawing
...
else
@warn "Drawing "*string(next_index)*" doesn't exist"
endif
source
Luxor._set_next_drawing_indexMethod
Luxor._set_next_drawing_index()

Set the current drawing to the next available drawing index. This can either be a new index or an existing index where a finished (finish()) drawing was stored before.

Returns the current drawing index.

source
Luxor._split_string_into_head_mid_tailMethod
_split_string_into_head_mid_tail(s::String,id::String)

splits s into head, mid and tail string.
Example:

  s="head...<g id="\$id">...</g>...tail" results in  head="head..." mid="<g id="\$id">...</g>"
tail="...tail"
source
Luxor.add_mesh_patchFunction
add_mesh_patch(pattern::Mesh, bezierpath::BezierPath,
colors=Array{Colors.Colorant, 1})

Add a new patch to the mesh pattern in pattern.

The first three or four elements of the supplied bezierpath define the three or four sides of the mesh shape.

The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.

Use setmesh() to select the mesh, which will be used to fill shapes.

source
Luxor.add_mesh_patchFunction
add_mesh_patch(pattern::Mesh, plist::Array{Point}, colors=Array{Colors.Colorant, 1})

Add a new patch to the mesh pattern in pattern.

The first three or four sides of the supplied points polygon define the three or four sides of the mesh shape.

The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.

source
Luxor.addstopMethod
addstop(b::Blend, offset, col)
addstop(b::Blend, offset, (r, g, b, a))
addstop(b::Blend, offset, string)

Add a color stop to a blend. The offset specifies the location along the blend's 'control vector', which varies between 0 (beginning of the blend) and 1 (end of the blend). For linear blends, the control vector is from the start point to the end point. For radial blends, the control vector is from any point on the start circle, to the corresponding point on the end circle.

Examples:

blendredblue = blend(Point(0, 0), 0, Point(0, 0), 1)
addstop(blendredblue, 0.5, setcolor(randomcolor()...))
source
Luxor.anglethreepointsMethod
anglethreepoints(p1::Point, p2::Point, p3::Point)

Find the angle formed by two lines defined by three points.

If the angle is less than π, the line heads to the left.

source
Luxor.animateMethod
animate(movie::Movie, scene::Scene; creategif=false, framerate=30)

Create the movie defined in movie by rendering the frames define in scene.

source
Luxor.animateMethod
animate(movie::Movie, scenelist::Array{Scene, 1};
creategif=false,
framerate=30,
pathname="",
tempdirectory="",
usenewffmpeg=true)

Create the movie defined in movie by rendering the frames define in the array of scenes in scenelist.

If creategif is true, the function attempts to call the ffmpeg utility on the resulting frames to build a GIF animation. This will be stored in pathname (an existing file will be overwritten; use a ".gif" suffix), or in (movietitle).gif in a temporary directory. ffmpeg should be installed and available, of course, if this is to work.

In suitable environments, the resulting animation is displayed in the Plots window.

Example

animate(bang, [
Scene(bang, backdrop, 0:200),
Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
creategif=true,
pathname="/tmp/animationtest.gif")

The usenewffmpeg option, true by default, uses single-pass palette generation and more complex filtering provided by recent versions of the ffmpeg utility, mainly to cope with transparent backgrounds. If set to false, the behavior is the same as in previous versions of Luxor.

If you prefer to use the FFMPEG package, use code such as this:

using FFMPEG

...

tempdirectory = "/tmp/temp/"

animate(movie, [
Scene(movie, frame, 1:50)
], creategif=false, tempdirectory=tempdirectory)

FFMPEG.ffmpeg_exe(-r 30 -f image2 -i (tempdirectory)/%10d.png -c:v libx264 -r 30 -pix_fmt yuv420p -y /tmp/animation.mp4)  source Luxor.arcMethod arc(centerpoint::Point, radius, angle1, angle2; action=:none) arc(centerpoint::Point, radius, angle1, angle2, action) Add an arc to the current path from angle1 to angle2 going clockwise, centered at centerpoint. Angles are defined relative to the x-axis, positive clockwise. source Luxor.arc2rMethod  arc2r(c1::Point, p2::Point, p3::Point; action=:none) arc2r(c1::Point, p2::Point, p3::Point, action) Add a circular arc centered at c1 that starts at p2 and ends at p3, going clockwise, to the current path. c1-p2 really determines the radius. If p3 doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position. source Luxor.arc2sagittaMethod arc2sagitta(p1::Point, p2::Point, s; action=:none) Make a clockwise arc starting at p1 and ending at p2 that reaches a height of s, the sagitta, at the middle, and add it to the current path. Return tuple of the center point and the radius of the arc. source Luxor.arrowFunction arrow(start::Point, finish::Point, height::Vector, action=:stroke; keyword arguments...) Draw a Bézier arrow between start and finish, with control points defined to fit in an imaginary box defined by the two supplied height values (see bezierfrompoints()). If the height values are different signs, the arrow will change direction on its way. Keyword arguments are the same as arrow(pt1, pt2, pt3, pt4). Example arrow(pts[1], pts[end], [15, 15], decoration = 0.5, decorate = () -> text(string(pts[1]))) source Luxor.arrowFunction arrow(start::Point, C1::Point, C2::Point, finish::Point, action=:stroke; linewidth = 1.0, arrowheadlength = 10, arrowheadangle = pi/8, startarrow = false, finisharrow = true, decoration = 0.5, decorate = nothing arrowheadfunction = nothing) Draw a Bezier curved arrow, from start to finish, with control points C1 and C2. Arrow heads can be added/hidden by changing startarrow and finisharrow options. The decorate keyword argument accepts a function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft given by scalar or vector decoration, and the x-axis is aligned with the direction of the curve at that point. Example This code draws an arrow head that's filled with orange and outlined in green. function myarrowheadfunction(originalendpoint, newendpoint, shaftangle) @layer begin setline(5) translate(newendpoint) rotate(shaftangle) sethue("orange") ngon(O, 20, 3, 0, :fill) sethue("green") ngon(O, 20, 3, 0, :stroke) end end @drawsvg begin background("white") arrow(O, 220, 0, π, linewidth=10, arrowheadlength=30, arrowheadangle=π/7, clockwise=true, arrowheadfunction = myarrowheadfunction) end source Luxor.arrowMethod arrow(centerpos::Point, radius, startangle, endangle; linewidth = 1.0, arrowheadlength = 10, arrowheadangle = π/8, decoration = 0.5, decorate = nothing, arrowheadfunction = nothing, clockwise = true) Draw a curved arrow, an arc centered at centerpos starting at startangle and ending at endangle with an arrowhead at the end. Angles are measured clockwise from the positive x-axis. Arrows don't use the current linewidth setting (setline()); you can specify the linewidth. The decorate keyword argument accepts a zero-argument function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at points on the shaft between 0 and 1 given by scalar or vector decoration, and the x-axis is aligned with the direction of the curve at that point. A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead. source Luxor.arrowMethod arrow(startpoint::Point, endpoint::Point; linewidth = 1.0, arrowheadlength = 10, arrowheadangle = pi/8, decoration = 0.5 or range(), decorate = nothing, arrowheadfunction = nothing) Draw a line between two points and add an arrowhead at the end. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft. Arrows don't use the current linewidth setting (setline()), and defaults to 1, but you can specify another value. It doesn't need stroking/filling, the shaft is stroked and the head filled with the current color. Decoration The decorate keyword argument accepts a function with zero arguments that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft between 0 and 1 given by scalar or vector decoration, and the x-axis is aligned with the direction of the curve at that point. Arrowheads A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead. Example function redbluearrow(shaftendpoint, endpoint, shaftangle) @layer begin sethue("red") sidept1 = shaftendpoint + polar(10, shaftangle + π/2 ) sidept2 = shaftendpoint - polar(10, shaftangle + π/2) poly([sidept1, endpoint, sidept2], :fill) sethue("blue") poly([sidept1, endpoint, sidept2], :stroke, close=false) end end @drawsvg begin background("white") arrow(O, O + (120, 120), linewidth=4, arrowheadlength=40, arrowheadangle=π/7, arrowheadfunction = redbluearrow) arrow(O, 100, 3π/2, π, linewidth=4, arrowheadlength=20, clockwise=false,arrowheadfunction=redbluearrow) end 800 250 source Luxor.arrowheadFunction arrowhead(target[, action=:fill]; shaftangle=0, headlength=10, headangle=pi/8) Draw an arrow head. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft. This doesn't use the current linewidth setting (setline()), and defaults to 1, but you can specify another value. source Luxor.backgroundMethod background(color) Fill the canvas with a single color. Returns the (red, green, blue, alpha) values. Examples: background("antiquewhite") background(1, 0.0, 1.0) background(1, 0.0, 1.0, .5) background(Luxor.Colors.RGB(0, 1, 0)) If Colors.jl has been imported: background(RGB(0, 1, 0)) background(RGBA(0, 1, 0)) background(RGBA(0, 1, 0, .5)) background(Luv(20, -20, 30)) If you don't specify a background color for a PNG drawing, the background will be transparent. You can set a partly or completely transparent background for PNG files by passing a color with an alpha value, such as this 'transparent black': background(RGBA(0, 0, 0, 0)) or background(0, 0, 0, 0) Returns a tuple (r, g, b, a) of the color that was used to paint the background. source Luxor.barchartMethod barchart(values; boundingbox = BoundingBox(O + (-250, -120), O + (250, 120)), bargap=10, margin = 5, border=false, labels=false, labelfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) -> begin label(string(values[i]), :n, highpos, offset=10) end, barfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) -> begin @layer begin setline(barwidth) line(lowpos, highpos, :stroke) end end) Draw a barchart where each bar is the height of a value in the values array. The bars will be scaled to fit in a bounding box. Text labels are drawn if the keyword labels=true. Extended help The function returns a vector of points; each is the bottom center of a bar. Draw a Fibonacci sequence as a barchart: fib(n) = n > 2 ? fib(n - 1) + fib(n - 2) : 1 fibs = fib.(1:15) @draw begin fontsize(12) barchart(fibs, labels=true) end To control the drawing of the text and bars, define functions that process the end points: mybarfunction(values, i, lowpos, highpos, barwidth, scaledvalue) mylabelfunction(values, i, lowpos, highpos, barwidth, scaledvalue) and pass them like this: barchart(vals, barfunction=mybarfunction) barchart(vals, labelfunction=mylabelfunction) function myprologfunction(values, basepoint, minbarrange, maxbarrange, barchartheight) @layer begin setline(0.2) for i in 0:10:maximum(values) rule(boxbottomcenter(basepoint) + (0, -(rescale(i, minbarrange, maxbarrange) * barchartheight))) end end end source Luxor.betweenFunction between(bb::BoundingBox, x) Find a point between the two corners of a BoundingBox corresponding to x, where x is typically between 0 and 1. source Luxor.betweenMethod between(p1::Point, p2::Point, x) between((p1::Point, p2::Point), x) Find the point between point p1 and point p2 for x, where x is typically between 0 and 1. between(p1, p2, 0.5) is equivalent to midpoint(p1, p2). source Luxor.bezierMethod bezier(t, A::Point, A1::Point, B1::Point, B::Point) Return the result of evaluating the Bézier cubic curve function, t going from 0 to 1, starting at A, finishing at B, control points A1 (controlling A), and B1 (controlling B). source Luxor.beziercurvatureMethod beziercurvature(t, A::Point, A1::Point, B1::Point, B::Point) Return the curvature of the Bézier curve at t ([0-1]), given start and end points A and B, and control points A1 and B1. The value (kappa) will typically be a value between -0.001 and 0.001 for points with coordinates in the 100-500 range. κ(t) is the curvature of the curve at point t, which for a parametric planar curve is: $$$$$\kappa = \frac{\mid \dot{x}\ddot{y}-\dot{y}\ddot{x}\mid} {(\dot{x}^2 + \dot{y}^2)^{\frac{3}{2}}}$$$$$ The radius of curvature, or the radius of an osculating circle at a point, is 1/κ(t). Values of 1/κ will typically be in the range -1000 to 1000 for points with coordinates in the 100-500 range. TODO Fix overshoot... ...The value of kappa can sometimes collapse near 0, returning NaN (and Inf for radius of curvature). source Luxor.bezierfrompointsMethod bezierfrompoints(startpoint::Point, pointonline1::Point, pointonline2::Point, endpoint::Point) Given four points, return the Bézier curve that passes through all four points, starting at startpoint and ending at endpoint. The two middle points of the returned BezierPathSegment are the two control points that make the curve pass through the two middle points supplied. source Luxor.bezierpathtopathMethod bezierpathtopath(bp::BezierPath) Convert a Bezier path to a Path object. @draw drawpath(polytopath(ngon(O, 145, 5, vertices = true)), action = :fill) source Luxor.bezierpathtopolyMethod bezierpathtopoly(bezierpath::BezierPath; steps=10) Convert a Bézier path (an array of BezierPathSegments, where each is a tuple of four points: anchor1, control1, control2, anchor2), to a polygon. To make a Bézier path, use makebezierpath() on a polygon. The steps optional keyword determines how many straight line sections are used for each path. source Luxor.beziersegmentanglesMethod beziersegmentangles(pt1, pt2; out = deg2rad(45), in = deg2rad(135), l = 100) Return a BezierPathSegment joining pt1 and pt2 making the angles out at the start and in at the end. It's similar to the tikZ (a) to [out=135, in=45] (b) drawing instruction (but in radians obviously). out is the angle that a line from pt1 to the outgoing Bézier handle makes with the horizontal. in is the angle that a line joining pt2 from the preceding Bézier handle makes with the horizontal. The function finds the interesction point of two lines with the two angles and constructs a BezierPathSegment that fits. See also the bezierfrompoints() function that makes a BezierPathSegment that passes through four points. Example drawbezierpath(beziersegmentangles(O, O + (100, 0), out = deg2rad(45), in = 2π - deg2rad(45)), :stroke) source Luxor.bezierstrokeFunction bezierstroke(point1, point2, width=0.0) Return a BezierPath, a stroked version of a straight line between two points. It wil have 2 or 6 Bezier path segments that define a brush or pen shape. If width is 0, the brush shape starts and ends at a point. Otherwise the brush shape starts and ends with the thick end. To draw it, use eg drawbezierpath(..., :fill). source Luxor.beziertopolyMethod beziertopoly(bpseg::BezierPathSegment; steps=10) Convert a BezierPathsegment to a polygon (an array of points). source Luxor.bezigonMethod bezigon(pts::Array{Point,1}, sides; close = false, action = :none)) Construct a bezigon, a path made of Bezier curves. corners is an array of points, the corners of the bezigon, eg this triangle: [Point(0, 0), Point(50, 50), Point(100, 0)] sides is an array of arrays of points, where each array contains two control points, eg:  sides = [ [Point(-10, -20), Point(40, -120)], # control points for first side [Point(120, -120), Point(180, -20)], ] The first pair of sides (two points) are control points, which combine with the first two points in corners to define a Bezier curve. And so on for the next pair. Returns a path. source Luxor.blendMethod blend(centerpos1, rad1, centerpos2, rad2, color1, color2) Create a radial blend. Example: redblue = blend( pos, 0, # first circle center and radius pos, tiles.tilewidth/2, # second circle center and radius "red", "blue" ) source Luxor.blendMethod blend(from::Point, startradius, to::Point, endradius) Create an empty radial blend. Radial blends are defined by two circles that define the start and stop locations. The first point is the center of the start circle, the first radius is the radius of the first circle. A new blend is empty. To add colors, use addstop(). source Luxor.blendMethod blend(pt1::Point, pt2::Point, color1, color2) Create a linear blend. Example: redblue = blend(pos, pos, "red", "blue") source Luxor.blendMethod blend(from::Point, to::Point) Create an empty linear blend. A blend is a specification of how one color changes into another. Linear blends are defined by two points: parallel lines through these points define the start and stop locations of the blend. The blend is defined relative to the current axes origin. This means that you should be aware of the current axes when you define blends, and when you use them. To add colors, use addstop(). source Luxor.blendadjustFunction blendadjust(ablend, center::Point, xscale, yscale, rot=0) Modify an existing blend by scaling, translating, and rotating it so that it will fill a shape properly even if the position of the shape is nowhere near the original location of the blend's definition. For example, if your blend definition was this (notice the 1) blendgoldmagenta = blend( Point(0, 0), 0, # first circle center and radius Point(0, 0), 1, # second circle center and radius "gold", "magenta" ) you can use it in a shape that's 100 units across and centered at pos, by calling this: blendadjust(blendgoldmagenta, Point(pos.x, pos.y), 100, 100) then use setblend(): setblend(blendgoldmagenta) source Luxor.blendmatrixMethod blendmatrix(b::Blend, m) Set the matrix of a blend. To apply a sequence of matrix transforms to a blend: A = [1 0 0 1 0 0] Aj = cairotojuliamatrix(A) Sj = scalingmatrix(2, .2) * Aj Tj = translationmatrix(10, 0) * Sj A1 = juliatocairomatrix(Tj) blendmatrix(b, As) source Luxor.boundingboxesintersectMethod boundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox) boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point) Return true if the two bounding boxes intersect. source Luxor.boxMethod box(points::Array; action=:none, reversepath=reversepath, vertices=vertices) box(points::Array; action=:none, reversepath=reversepath, vertices=vertices) Create a box/rectangle using the first two points of an array of Points to defined opposite corners, and add it to the current path. Then apply action. Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right rather than execute action. source Luxor.boxMethod box(bbox::BoundingBox, cornerradii::Array; action = :none) Make a box that is the size of the BoundingBox bbox with curved corners. source Luxor.boxMethod box(bbox::BoundingBox, cornerradius::Real; action = :none) Make a box that is the size of the BoundingBox bbox with curved corners. source Luxor.boxMethod box(bbox::BoundingBox; action = :none, vertices = false) box(bbox::BoundingBox, action::Symbol; vertices=false) Define a box using the bounds in bbox. Use vertices = true to return an array of the four corner points: bottom left, top left, top right, bottom right. source Luxor.boxMethod box(tile::BoxmapTile, action::Symbol=:none; vertices=false) box(tile::BoxmapTile, action=:none, vertices=false) Use a Boxmaptile to make or draw a rectangular box. Use vertices=true to obtain the coordinates. Create boxmaps using boxmap(). source Luxor.boxMethod box(pt, width, height, cornerradii::Array; action=:none) box(pt, width, height, cornerradii::Array, action=:none) Draw a box/rectangle centered at point pt with width and height and round each corner by the corresponding value in the array cornerradii. The constructed path consists of arcs and straight lines. The first corner is the one at the bottom left, the second at the top left, and so on. Example @draw begin box(O, 120, 120, [0, 20, 40, 60], :fill) end source Luxor.boxMethod box(pt, width, height, cornerradius, action=:none) box(pt, width, height, cornerradius; action=:none) Draw a box/rectangle centered at point pt with width and height and round each corner by cornerradius. The constructed path consists of arcs and straight lines. source Luxor.boxMethod box(pt::Point, width, height; action=:none, vertices=false) box(pt::Point, width, height, action=:none; vertices=false) Create a box/rectangle centered at point pt with width and height. Use vertices=true to return an array of the four corner points rather than apply the action. reversepath reverses the direction of the path. source Luxor.boxMethod box(cornerpoint1, cornerpoint2; action=:none, vertices=false, reversepath=false) box(cornerpoint1, cornerpoint2, action; vertices=false, reversepath=false) Create a box (rectangle) between two points and add it to the current path. Then apply action. Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right rather than execute action. reversepath reverses the direction of the path (and returns points in the order: bottom left, bottom right, top right, top left). source Luxor.boxMethod box(t::Table, cellnumber::Int, action::Symbol=:none; vertices=false) box(t::Table, cellnumber::Int; action=:none, vertices=false) Make box around cell cellnumber in table t. source Luxor.boxMethod box(t::Table, r::Integer, c::Integer, action::Symbol) box(t::Table, r::Integer, c::Integer; action=:none) Draw a box in table t at row r and column c. source Luxor.boxMethod box(tiles::Tiler, n::Integer; action=:none, vertices=false, reversepath=false) box(tiles::Tiler, n::Integer, action::Symbol=:none; vertices=false, reversepath=false) Draw a box in tile n of tiles tiles. source Luxor.boxaspectratioFunction boxaspectratio(bb::BoundingBox=BoundingBox()) Return the aspect ratio (the height divided by the width) of bounding box bb. source Luxor.boxbottomcenterFunction boxbottomcenter(bb::BoundingBox=BoundingBox()) Return the point at the bottom center of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ source Luxor.boxbottomleftFunction boxbottomleft(bb::BoundingBox=BoundingBox()) Return the point at the bottom left of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ source Luxor.boxbottomrightFunction boxbottomright(bb::BoundingBox=BoundingBox()) Return the point at the bottom right of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ■ source Luxor.boxdiagonalFunction boxdiagonal(bb::BoundingBox=BoundingBox()) Return the length of the diagonal of bounding box bb. source Luxor.boxmapMethod boxmap(A::Array, pt, w, h) Build a box map of the values in A with one corner at pt and width w and height h. There are length(A) boxes. The areas of the boxes are proportional to the original values, scaled as necessary. The return value is an array of BoxmapTiles. For example: [BoxmapTile(0.0, 0.0, 10.0, 20.0) BoxmapTile(10.0, 0.0, 10.0, 13.3333) BoxmapTile(10.0, 13.3333, 10.0, 6.66667)] with each tile containing (x, y, w, h). box() and BoundingBox() can work with BoxmapTiles as well. Example using Luxor @svg begin fontsize(16) fontface("HelveticaBold") pt = Point(-200, -200) a = rand(10:200, 15) tiles = boxmap(a, Point(-200, -200), 400, 400) for (n, t) in enumerate(tiles) randomhue() bb = BoundingBox(t) box(bb - 2, :stroke) box(bb - 5, :fill) sethue("white") text(string(n), midpoint(bb[1], bb[2]), halign=:center) end end 400 400 "boxmap.svg" source Luxor.boxmiddlecenterFunction boxmiddlecenter(bb::BoundingBox=BoundingBox()) Return the point at the center of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ source Luxor.boxmiddleleftFunction boxmiddleleft(bb::BoundingBox=BoundingBox()) Return the point at the middle left of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ source Luxor.boxmiddlerightFunction boxmiddleright(bb::BoundingBox=BoundingBox()) Return the point at the midde right of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ⋅ ⋅ ⋅ ■ ⋅ ⋅ ⋅ source Luxor.boxtopcenterFunction boxtopcenter(bb::BoundingBox=BoundingBox()) Return the point at the top center of the BoundingBox bb, defaulting to the drawing extent. ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ source Luxor.boxtopleftFunction boxtopleft(bb::BoundingBox=BoundingBox()) Return the point at the top left of the BoundingBox bb, defaulting to the drawing extent. ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ source Luxor.boxtoprightFunction boxtopright(bb::BoundingBox=BoundingBox()) Return the point at the top right of the BoundingBox bb, defaulting to the drawing extent. ⋅ ⋅ ■ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ source Luxor.brushFunction brush(pt1, pt2, width=10; strokes=10, minwidth=0.01, maxwidth=0.03, twist = -1, lowhandle = 0.3, highhandle = 0.7, randomopacity = true, tidystart = false, action = :fill, strokefunction = (nbpb) -> nbpb)) Draw a composite brush stroke made up of some randomized individual filled Bezier paths. strokefunction allows a function to process a BezierPathSegment or do other things before it's drawn. Note There is a lot of randomness in this function. Results are unpredictable. source Luxor.cairotojuliamatrixMethod cairotojuliamatrix(c) Return a 3x3 Julia matrix that's the equivalent of the six-element matrix in c. See getmatrix() for details. source Luxor.carcMethod carc(centerpoint::Point, radius, angle1, angle2; action=:none) carc(centerpoint::Point, radius, angle1, angle2, action) Add an arc centered at centerpoint to the current path from angle1 to angle2, going counterclockwise. Angles are defined relative to the x-axis, positive clockwise. source Luxor.carc2rMethod carc2r(c1::Point, p2::Point, p3::Point; action=:none) Add a circular arc centered at c1 that starts at p2 and ends at p3, going counterclockwise, to the current path. c1-p2 really determines the radius. If p3 doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position. source Luxor.carc2sagittaMethod carc2sagitta(p1::Point, p2::Point, s; action=:none) Make a counterclockwise arc starting at p1 and ending at p2 that reaches a height of s, the sagitta, at the middle, and add it to the current path. Return tuple of center point and radius of arc. source Luxor.center3ptsMethod center3pts(a::Point, b::Point, c::Point) Find the radius and center point for three points lying on a circle. returns (centerpoint, radius) of a circle. If there's no such circle, the function returns (Point(0, 0), 0). If two of the points are the same, use circle(pt1, pt2) instead. source Luxor.circleMethod circle(pt1::Point, pt2::Point, pt3::Point; action=:none) circle(pt1::Point, pt2::Point, pt3::Point, action) Make a circle that passes through three points, and add it to the current path. source Luxor.circleMethod circle(pt1::Point, pt2::Point; action=:none) circle(pt1::Point, pt2::Point, action) Make a circle that passes through two points that define the diameter, and add it to the current path. source Luxor.circleMethod circle(centerpoint::Point, r; action=:none) circle(centerpoint::Point, r, action) Make a circle of radius r centered at 'centerpoint', and add it to the current path. action is one of the actions applied by do_action, defaulting to :none. Returns a tuple of two points, the corners of a bounding box that encloses the circle. You can also use ellipse() to draw circles and place them by their centerpoint. source Luxor.circlecircleinnertangentsMethod circlecircleinnertangents(circle1center::Point, circle1radius, circle2center::Point, circle2radius) Find the inner tangents of two circles. These are tangent lines that cross as they skim past one circle and touch the other. Returns the four points: tangentpoint1 on circle 1, tangentpoint1 on circle2, tangentpoint2 on circle 1, tangentpoint2 on circle2. Returns (O, O, O, O) if inner tangents can't be found (eg when the circles overlap). Use circlecircleoutertangents() to find the outer tangents. source Luxor.circlecircleoutertangentsMethod circlecircleoutertangents(cpt1::Point, r1, cpt2::Point, r2) Return four points, p1, p2,p3,p4, where a line throughp1andp2, and a line throughp3andp4, form the outer tangents to the circles defined bycpt1/r1andcpt2/r2. Returns four identical points (O) if one of the circles lies inside the other. source Luxor.circlepathMethod circlepath(center::Point, radius; action=:none, reversepath=false, kappa = 0.5522847498307936) circlepath(center::Point, radius, action; reversepath=false, kappa = 0.5522847498307936) Make a circle using Bézier curves, and add it to the current path. One benefit of using this rather than circle() is that you can use the reversepath option to draw the circle clockwise rather than circle's counterclockwise. The magic value, kappa, is 4.0 * (sqrt(2.0) - 1.0) / 3.0. Return two points, the corners of a bounding box. source Luxor.circlepointtangentMethod circlepointtangent(through::Point, radius, targetcenter::Point, targetradius) Find the centers of up to two circles of radius radius that pass through point through and are tangential to a circle that has radius targetradius and center targetcenter. This function returns a tuple: • (0, O, O) - no circles exist • (1, pt1, O) - 1 circle exists, centered at pt1 • (2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2 (The O are just dummy points so that three values are always returned.) source Luxor.circletangent2circlesMethod circletangent2circles(radius, circle1center::Point, circle1radius, circle2center::Point, circle2radius) Find the centers of up to two circles of radius radius that are tangent to the two circles defined by circle1... and circle2.... These two circles can overlap, but one can't be inside the other. • (0, O, O) - no such circles exist • (1, pt1, O) - 1 circle exists, centered at pt1 • (2, pt1, pt2) - 2 circles exist, with centers at pt1 and pt2 (The O are just dummy points so that three values are always returned.) source Luxor.clipMethod clip() Establish a new clipping region by intersecting the current clipping region with the current path and then clearing the current path. An existing clipping region is enforced through and after a gsave()-grestore() block, but a clipping region set inside a gsave()-grestore() block is lost after grestore(). [?] source Luxor.clippreserveMethod clippreserve() Establish a new clipping region by intersecting the current clipping region with the current path, but keep the current path. source Luxor.closepathMethod closepath() Draw a line from the current point to the first point of the current subpath. This is Cairo's close_path() function. source Luxor.crescentMethod crescent(cp1, r1, cp2, r2; action=nothing, vertices=false, reversepath=false) Create a crescent-shaped polygon, aligned with the current x-axis, by finding the intersection of two circles, and add it to the current path. The two center positions should be different. See also crescent(point, innerradius, outeradius...). Examples Create a filled crescent shape from two circles. crescent(O, 100, O + (60, 0), 150, :fill) # or crescent(O, 100, O + (60, 0), 150, action=:fill) source Luxor.crescentMethod crescent(pos, innerradius, outeradius; action=nothing, vertices=false, reversepath=false, steps = 30) Create a crescent-shaped polygon, aligned with the current x-axis, and add it to the current path. If the inner radius is 0, you'll get a semicircle. See also crescent(pos1, innerradius, pos2, outeradius...). Examples Create a filled crescent shape with outer radius of 200, inner radius of 130. crescent(O, 130, 200, :fill) # or crescent(O, 130, 200, action=:fill) Create a stroked crescent shape - the inner radius of 0 produces a semicircle - and add it to the current path. crescent(O, 0, 200, :stroke) # or crescent(O, 0, 200, action=:stroke) source Luxor.crossproductMethod crossproduct(p1::Point, p2::Point) This is the perp dot product, really, not the crossproduct proper (which is 3D): source Luxor.currentpointMethod currentpoint() Return the current point. This is the most recent point in the current path, as defined by one of the path building functions such as move(), line(), curve(), arc(), rline(), and rmove(). To see if there is a current point, use hascurrentpoint(). source Luxor.curveMethod curve(x1, y1, x2, y2, x3, y3) curve(p1, p2, p3) Add a Bézier curve to the current path.. The spline starts at the current position, finishing at x3/y3 (p3), following two control points x1/y1 (p1) and x2/y2 (p2). source Luxor.determinant3Method determinant3(p1::Point, p2::Point, p3::Point) Find the determinant of the 3×3 matrix: $$$\begin{bmatrix} p1.x & p1.y & 1 \\ p2.x & p2.y & 1 \\ p3.x & p3.y & 1 \\ \end{bmatrix}$$$ If the value is 0.0, the points are collinear. source Luxor.dimensionMethod dimension(p1::Point, p2::Point; format::Function = (d) -> string(d), # process the measured value into a string offset = 0.0, # left/right, parallel with x axis fromextension = (10.0, 10.0), # length of extensions lines left and right toextension = (10.0, 10.0), # textverticaloffset = 0.0, # range 1.0 (top) to -1.0 (bottom) texthorizontaloffset = 0.0, # range 1.0 (top) to -1.0 (bottom) textgap = 5, # gap between start of each arrow (≈ fontsize?) textrotation = 0.0, arrowlinewidth = 1.0, arrowheadlength = 10, arrowheadangle = π/8) Calculate and draw dimensioning graphics for the distance between p1 and p2. The value can be formatted with function format. p1 is the lower on the page (ie probably the higher y value) point, p2 is the higher on the page (ie probably lower y) point. offset is to the left (-x) when negative. Dimension graphics will be rotated to align with a line between p1 and p2. In textverticaloffset, "vertical" and "horizontal" are best understood by "looking" along the line from the first point to the second. textverticaloffset ranges from -1 to 1, texthorizontaloffset in default units.  toextension [5 , 5] <---> <---> to ----------- + ^ | -50 | v ---------- + from <---> <---> [5 , 5] fromextension <----------------> offset Returns the measured distance and the text. source Luxor.do_actionMethod do_action(action) This is usually called by other graphics functions. Actions for graphics commands include :fill, :stroke, :clip, :fillstroke, :fillpreserve, and :strokepreserve. The :path action adds the graphics to the current path. source Luxor.douglas_peuckerMethod Use a non-recursive Douglas-Peucker algorithm to simplify a polygon. Used by simplify(). douglas_peucker(pointlist::Array, start_index, last_index, epsilon) source Luxor.drawbezierpathMethod drawbezierpath(bezierpath::BezierPath, action=:none; close=true) drawbezierpath(bezierpath::BezierPath; action=:none, close=true) Draw the Bézier path, and apply the action, such as :none, :stroke, :fill, etc. By default the path is closed. TODO Return something more useful than a Boolean. source Luxor.drawbezierpathMethod drawbezierpath(bps::BezierPathSegment; action=:none, close=false) Draw the Bézier path segment, and apply the action, such as :none, :stroke, :fill, etc. By default the path is open. TODO Return something more useful than a Boolean. source Luxor.drawpathMethod drawpath(path::Path, k::Real; steps=10, # used when approximating Bezier curve segments action=:none, startnewpath=true, pathlength = 0.0) Draw the path in path starting at the beginning and stopping at k between 0 and 1. So if k is 0.5, half the path is drawn. Returns the last point processed. The function calculates the length of the entire path before drawing it. If you want to draw a large path more than once, it might be more efficient to calculate the length of the path first, and provide it to the pathlength keyword. The steps parameter is used when approximating the length of any curve (Bezier) sections. source Luxor.drawpathMethod drawpath(cp::Path; action=:none, startnewpath=true) drawpath(cp::Path, action; startnewpath=true) Make the Luxor path stored in cp and apply the action. To make paths, follow some path construction functions such as move(), line(), and curve() with the storepath() function. By default, startnewpath=true, which starts a new path, discarding any existing path contents. source Luxor.easeinoutbezierFunction easeinoutbezier(t, b, c, d, cpt1, cpt2) This easing function takes six arguments, the usual t, b, c, and d, but also two points. These are the normalized control points of a Bezier curve drawn between Point(0, 0) to Point(1.0, 1.0). The y value of the Bezier is the eased value for t. In your frame() generating function, if a Scene specifies the easeinoutbezier easing function, you can use this: ... lineareasing = rescale(framenumber, 1, scene.framerange.stop) beziereasing = scene.easingfunction(lineareasing, 0, 1, 1, Point(0.25, 0.25), Point(0.75, 0.75)) ... These two control points lie on the line between 0/0 and 1/1, so it's equivalent to a linear easing (lineartween() or easingflat). However, in the next example, the two control points define a wave-like curve that changes direction before changing back. When animating with this easing function, an object will 'go retrograde' for a while. lineareasing = rescale(framenumber, 1, scene.framerange.stop) beziereasing = scene.easingfunction(lineareasing, 0, 1, 1, Point(0.01, 1.99), Point(0.99, -1.5)) source Luxor.easingflatMethod easingflat(t, b, c, d) A flat easing function, same as lineartween(). For all easing functions, the four parameters are: • t time, ie the current framenumber • b beginning position or bottom value of the range • c total change in position or top value of the range • d duration, ie a framecount 1. t/d or t/=d normalizes t to between 0 and 1 2. ... * c scales up to the required range value 3. ... + b adds the initial offset source Luxor.ellipseMethod ellipse(focus1::Point, focus2::Point, k; action=:none, stepvalue=pi/100, vertices=false, reversepath=false) Build a polygon approximation to an ellipse, given two points and a distance, k, which is the sum of the distances to the focii of any points on the ellipse (or the shortest length of string required to go from one focus to the perimeter and on to the other focus), and add it to the current path. source Luxor.ellipseMethod ellipse(focus1::Point, focus2::Point, pt::Point; action=:none, stepvalue=pi/100, vertices=false, reversepath=false) Build a polygon approximation to an ellipse, given two points and a point somewhere on the ellipse. source Luxor.ellipseMethod ellipse(centerpoint::Point, w, h; action=:none) ellipse(centerpoint::Point, w, h; action) Make an ellipse, centered at centerpoint, with width w, and height h, and add it to the current path. Returns a tuple of two points, the corners of a bounding box that encloses the ellipse. source Luxor.ellipseinquadMethod ellipseinquad(qgon; action=:none) ellipseinquad(qgon, action) Calculate a Bézier-based ellipse that fits inside the quadrilateral qgon, an array with at least four Points that form a convex polygon, and add it to the current path. It returns ellipsecenter, ellipsesemimajor, ellipsesemiminor, ellipseangle: • ellipsecenter the ellipse center • ellipsesemimajor ellipse semimajor axis • ellipsesemiminor ellipse semiminor axis • ellipseangle ellipse rotation The function returns O, 0, 0, 0 if a suitable ellipse can't be found. (The qgon is probably not a convex polygon.) Examples ellipseinquad(box(O, 130, 130); action=:stroke) ellipseinquad(box(O, 140, 230), :stroke) References http://faculty.mae.carleton.ca/John_Hayes/Papers/InscribingEllipse.pdf source Luxor.epitrochoidMethod epitrochoid(R, r, d; action=:none, stepby=0.01, period=0, vertices=false) epitrochoid(R, r, d, action; stepby=0.01, period=0, vertices=false) Make a epitrochoid with short line segments, and add it to the current path. (Like a Spirograph.) The curve is traced by a point attached to a circle of radius r rolling around the outside of a fixed circle of radius R, where the point is a distance d from the center of the circle. Things get interesting if you supply non-integral values. stepby, the angular step value, controls the amount of detail, ie the smoothness of the polygon. If period is not supplied, or 0, the lowest period is calculated for you. The function can return a polygon (a list of points), or draw the points directly using the supplied action. If the points are drawn, the function returns a tuple showing how many points were drawn and what the period was (as a multiple of pi). source Luxor.fillpathMethod fillpath() Fill the current path according to the current settings. The current path is then emptied. source Luxor.fillstrokeMethod fillstroke() Fill and stroke the current path. After this, the current path is empty, and there is no current point. source Luxor.findbeziercontrolpointsMethod findbeziercontrolpoints( previouspt::Point, pt1::Point, pt2::Point, nextpt::Point; smoothing = 0.5) Find the Bézier control points for the line between pt1 and pt2, where the point before pt1 is previouspt and the next point after pt2 is nextpt. source Luxor.finishMethod finish(;svgpostprocess = false, addmarker = true) Finish the drawing, close any related files. You may be able to view the drawing in another application with preview(). For more information about svgpostprocess and addmarker see help for Luxor._adjust_background_rects source Luxor.get_bezier_lengthMethod get_bezier_length(bps::BezierPathSegment; steps=10) Return the length of a BezierPathSegment, using steps to determine the accuracy, by stepping through the curve and finding all the points, and then measuring between them. This is obviously just an approximation; the maths to do it properly is too difficult for me. :( source Luxor.get_bezier_pointsMethod get_bezier_points(bps::BezierPathSegment; steps=10) The flattening: return a list of all the points on the Bezier curve, including start and end, using steps to determine the accuracy. source Luxor.get_fontsizeMethod get_fontsize() Return the font size set by fontsize or. more precisely. the y-scale of the Cairo font matrix if Cairo.set_font_matrix is used directly. (Toy API) This only works if Cairo is at least at v1.0.5. source Luxor.getmatrixMethod getmatrix() Get the current workspace (position, scale, and orientation) as a 6-element vector: [xx, yx, xy, yy, x0, y0] • xx component of the affine transformation • yx component of the affine transformation • xy component of the affine transformation • yy component of the affine transformation • x0 translation component of the affine transformation • y0 translation component of the affine transformation When a drawing is first created, the 'matrix' looks like this: getmatrix() = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0] When the origin is moved to 400/400, it looks like this: getmatrix() = [1.0, 0.0, 0.0, 1.0, 400.0, 400.0] To reset the 'matrix' to the original: setmatrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]) To modify the current 'matrix' by multiplying it by a 6 element 'matrix' a, see transform(a::Array). To convert between Luxor/Cairo 'matrix' format (6-element Vector{Float64}) and a 3x3 Julia matrix, use cairotojuliamatrix(c) and juliatocairomatrix(c). See also rotationmatrix(a), translationmatrix(), and scalingmatrix(). Extended help Here are some basic matrix transforms: • translate transform([1, 0, 0, 1, dx, dy]) shifts by dx, dy • scale transform([fx 0 0 fy 0 0]) scales by fx and fy • rotate transform([cos(a), -sin(a), sin(a), cos(a), 0, 0]) rotates around to a radians rotate around O: [c -s s c 0 0] • shear transform([1 0 a 1 0 0]) shears in x direction by a shear in y direction by a: [1 a 0 1 0 0] • x-skew transform([1, 0, tan(a), 1, 0, 0]) skews in x by a • y-skew transform([1, tan(a), 0, 1, 0, 0]) skews in y by a • flip transform([fx, 0, 0, fy, centerx * (1 - fx), centery * (fy-1)]) flips with center at centerx/centery • reflect transform([1 0 0 -1 0 0]) reflects in xaxis transform([-1 0 0 1 0 0]) reflects in yaxis source Luxor.getnearestpointonlineMethod getnearestpointonline(pt1::Point, pt2::Point, startpt::Point) Given a line from pt1 to pt2, and startpt is the start of a perpendicular heading to meet the line, at what point does it hit the line? See perpendicular(). source Luxor.getpathMethod getpath() Get the current path and return a CairoPath object, which is an array of element_type and points objects. With the results you can step through and examine each entry like this: o = getpath() x, y = currentpoint() for e in o if e.element_type == Cairo.CAIRO_PATH_MOVE_TO (x, y) = e.points move(x, y) elseif e.element_type == Cairo.CAIRO_PATH_LINE_TO (x, y) = e.points # straight lines line(x, y) strokepath() circle(x, y, 1, :stroke) elseif e.element_type == Cairo.CAIRO_PATH_CURVE_TO (x1, y1, x2, y2, x3, y3) = e.points # Bezier control lines circle(x1, y1, 1, :stroke) circle(x2, y2, 1, :stroke) circle(x3, y3, 1, :stroke) move(x, y) curve(x1, y1, x2, y2, x3, y3) strokepath() (x, y) = (x3, y3) # update current point elseif e.element_type == Cairo.CAIRO_PATH_CLOSE_PATH closepath() else error("unknown CairoPathEntry " * repr(e.element_type)) error("unknown CairoPathEntry " * repr(e.points)) end end source Luxor.getpathflatMethod getpathflat() Get the current path, like getpath() but flattened so that there are no Bézier curves. Returns a CairoPath which is an array of element_type and points objects. source Luxor.getrotationMethod getrotation(R::Matrix) getrotation() Get the rotation of a Julia 3x3 matrix, or the current Luxor rotation. getrotation() 0.0 $$$\begin{bmatrix} a & b & tx \\ c & d & ty \\ 0 & 0 & 1 \\ \end{bmatrix}$$$ The rotation angle is atan(-b, a) or atan(c, d). See getmatrix() for details. source Luxor.getscaleMethod getscale(R::Matrix) getscale() Get the current scale of a 3x3 matrix, or the current Luxor scale. Returns a tuple of x and y values. getscale() (1.0, 1.0) See getmatrix() for details. source Luxor.gettranslationMethod gettranslation(R::Matrix) gettranslation() Get the current translation of a 3x3 matrix R, or get the current Luxor translation. Returns a tuple of x and y values. See getmatrix() for details. source Luxor.getworldpositionFunction getworldposition(pt::Point = O; centered=true) Return the world coordinates of pt. The default coordinate system for Luxor drawings is that the top left corner is 0/0. If you use origin() (or the various @- macro shortcuts), everything moves to the center of the drawing, and this function with the default centered option assumes an origin() function. If you choose centered=false, the returned coordinates will be relative to the top left corner of the drawing. origin() translate(120, 120) @show currentpoint() # => Point(0.0, 0.0) @show getworldposition() # => Point(120.0, 120.0) source Luxor.grestoreMethod grestore() Replace the current graphics state with the one previously saved by the most recent gsave(). source Luxor.hascurrentpointMethod hascurrentpoint() Return true if there is a current point. This is the most recent point in the current path, as defined by one of the path building functions such as move(), line(), curve(), arc(), rline(), and rmove(). To obtain the current point, use currentpoint(). There's no current point after strokepath() and strokepath() calls. source Luxor.hexagons_withinMethod hexagons_within(n::Int, hex::Hexagon) Return all the hexagons within index distance n of hex. If n is 0, only the hex itself is returned. If n is 1, hex and the six hexagons one index away are returned. If n is 2, 19 hexagons surrounding hex are returned. source Luxor.hexcube_linedrawMethod hexcube_linedraw(hexa::Hexagon, hexb::Hexagon) Find and return the hexagons that lie (mostly) on a straight line between hexa and hexb. If you filled/stroked them appropriately, you'd get a jagged line. source Luxor.hexcube_roundFunction hexcube_round(x, y, origin, width = 10.0, height = 10.0) Return the hexagon containing the point x, y, on the hexagonal grid centered at origin, and with tiles of width/height point in Cartesian space can be mapped to the index of the hexagon that contains it. source Luxor.hexnearest_cubicMethod  hexnearest_cubic(x::Real, y::Real, z::Real, origin, width, height) Find the nearest hexagon in cubic coordinates, ie as q, r, s integer indices, given (x, y, z) as Real numbers, with the hexagonal grid centered at origin, and with tiles of width/height. source Luxor.hexneighborsMethod hexneighbors(hex::Hexagon) Return the neighbors of hex. Example julia> h = HexagonOffsetEvenR(0, 0, 70.0) julia> hexneighbors(h) HexagonNeighborIterator(HexagonCubic(0, 0, 0, Point(0.0, 0.0), 70.0, 70.0)) julia> collect(hexneighbors(h)) 6-element Vector{Any}: HexagonCubic(1, -1, 0, Point(0.0, 0.0), 70.0, 70.0) HexagonCubic(1, 0, -1, Point(0.0, 0.0), 70.0, 70.0) HexagonCubic(0, 1, -1, Point(0.0, 0.0), 70.0, 70.0) HexagonCubic(-1, 1, 0, Point(0.0, 0.0), 70.0, 70.0) HexagonCubic(-1, 0, 1, Point(0.0, 0.0), 70.0, 70.0) HexagonCubic(0, -1, 1, Point(0.0, 0.0), 70.0, 70.0) source Luxor.hexringMethod hexring(n::Int, hex::Hexagon) Return the ring of hexagons that surround hex. If n is 1, the hexagons immediately surrounding hex are returned. source Luxor.hexspiralMethod hexspiral(hex, n) Return an array of hexagons to spiral around a central hexagon forming n rings. source Luxor.hextileMethod  hextile(hex::Hexagon) Calculate the six vertices of the hexagon hex and return them in an array of Points. source Luxor.highestaspectratioMethod highestaspectratio() Find the highest aspect ratio of a list of rectangles, given the length of the side along which they are to be laid out. source Luxor.highlightcellsFunction highlightcells(t::Table, cellnumbers, action::Symbol=:stroke; color::Colorant=colorant"red", offset = 0) Highlight (draw or fill) one or more cells of table t. cellnumbers is a range, array, or an array of row/column tuples. highlightcells(t, 1:10, :fill, color=colorant"blue") highlightcells(t, vcat(1:5, 150), :stroke, color=colorant"magenta") highlightcells(t, [(4, 5), (3, 6)]) source Luxor.hypotrochoidMethod hypotrochoid(R, r, d; action=:none, stepby=0.01, period=0.0, vertices=false) hypotrochoid(R, r, d, action; stepby=0.01, period=0.0, vertices=false) Make a hypotrochoid with short line segments, and add it to the current path. (Like a Spirograph.) The curve is traced by a point attached to a circle of radius r rolling around the inside of a fixed circle of radius R, where the point is a distance d from the center of the interior circle. Things get interesting if you supply non-integral values. Special cases include the hypocycloid, if d = r, and an ellipse, if R = 2r. stepby, the angular step value, controls the amount of detail, ie the smoothness of the polygon, If period is not supplied, or 0, the lowest period is calculated for you. The function can return a polygon (a list of points), or draw the points directly using the supplied action. If the points are drawn, the function returns a tuple showing how many points were drawn and what the period was (as a multiple of pi). source Luxor.image_as_matrix!Method image_as_matrix!(buffer) Like image_as_matrix(), but use an existing UInt32 buffer. buffer is a buffer of UInt32. w = 200 h = 150 buffer = zeros(UInt32, w, h) Drawing(w, h, :image) origin() juliacircles(50) m = image_as_matrix!(buffer) finish() # collect(m)) is Array{ARGB32,2} Images.RGB.(m) source Luxor.image_as_matrixMethod image_as_matrix() Return an Array of the current state of the picture as an array of ARGB32. A matrix 50 wide and 30 high => a table 30 rows by 50 cols using Luxor, Images Drawing(50, 50, :png) origin() background(randomhue()...) sethue("white") fontsize(40) fontface("Georgia") text("42", halign=:center, valign=:middle) mat = image_as_matrix() finish() source Luxor.initnoiseMethod initnoise(seed::Int) initnoise() Initialize the noise generation code. julia> initnoise(); noise(1) 0.7453148982810598 julia> initnoise(); noise(1) 0.7027617067916981 If you provide an integer seed, it will be used to seed Random.seed!() when the noise code is initialized: julia> initnoise(41); noise(1) # yesterday 0.7134000046640385 julia> initnoise(41); noise(1) # today 0.7134000046640385 If you need to control which type of random number generator is used, you can provide your own and it will be used instead of the default Julia implementation. julia> rng = MersenneTwister(1234) # any AbstractRNG julia> initnoise(rng) source Luxor.insertvertices!Method insertvertices!(pgon; ratio=0.5) Insert a new vertex into each edge of a polygon pgon. The default ratio of 0.5 divides the original edge of the polygon into half. source Luxor.intersection2circlesMethod intersection2circles(pt1, r1, pt2, r2) Find the area of intersection between two circles, the first centered at pt1 with radius r1, the second centered at pt2 with radius r2. If one circle is entirely within another, that circle's area is returned. source Luxor.intersectioncirclecircleMethod intersectioncirclecircle(cp1, r1, cp2, r2) Find the two points where two circles intersect, if they do. The first circle is centered at cp1 with radius r1, and the second is centered at cp1 with radius r1. Returns (flag, ip1, ip2) where flag is a Boolean true if the circles intersect at the points ip1 and ip2. If the circles don't intersect at all, or one is completely inside the other, flag is false and the points are both Point(0, 0). Use intersection2circles() to find the area of two overlapping circles. In the pure world of maths, it must be possible that two circles 'kissing' only have a single intersection point. At present, this unromantic function reports that two kissing circles have no intersection points. source Luxor.intersectionlinecircleMethod intersectionlinecircle(p1::Point, p2::Point, cpoint::Point, r) Find the intersection points of a line (extended through points p1 and p2) and a circle. Return a tuple of (n, pt1, pt2) where • n is the number of intersections, 0, 1, or 2 • pt1 is first intersection point, or Point(0, 0) if none • pt2 is the second intersection point, or Point(0, 0) if none The calculated intersection points won't necessarily lie on the line segment between p1 and p2. source Luxor.intersectionlinesMethod intersectionlines(p0, p1, p2, p3; crossingonly=false) Find the point where two lines intersect. If crossingonly == true the point of intersection must lie on both lines. If crossingonly == false the point of intersection can be where the lines meet if extended almost to 'infinity'. Accordng to this function, collinear, overlapping, and parallel lines never intersect. Ie, the line segments might be collinear but have no points in common, or the lines segments might be collinear and have many points in common, or the line segments might be collinear and one is entirely contained within the other. If the lines are collinear and share a point in common, that is the intersection point. source Luxor.isarcclockwiseMethod isarcclockwise(c::Point, A::Point, B::Point) Return true if an arc centered at c going from A to B is clockwise. If c, A, and B are collinear, then a hemispherical arc could be either clockwise or not. source Luxor.isinsideMethod isinside(p::Point, bb:BoundingBox) Returns true if pt is inside bounding box bb. source Luxor.isinsideMethod isinside(p, pol; allowonedge=false) Is a point p inside a polygon pol? Returns true if it does, or false. This is an implementation of the Hormann-Agathos (2001) Point in Polygon algorithm. The classification of points lying on the edges of the target polygon, or coincident with its vertices is not clearly defined, due to rounding errors or arithmetical inadequacy. By default these will generate errors, but you can suppress these by setting allowonedge to true. source Luxor.ispointonleftoflineMethod ispointonleftofline(A::Point, B::Point, C::Point) For a line passing through points A and B: • return true if point C is on the left of the line • return false if point C lies on the line • return false if point C is on the right of the line source Luxor.ispointonlineMethod ispointonline(pt::Point, pt1::Point, pt2::Point; extended = false, atol = 10E-5) Return true if the point pt lies on a straight line between pt1 and pt2. If extended is false (the default) the point must lie on the line segment between pt1 and pt2. If extended is true, the point lies on the line if extended in either direction. source Luxor.ispointonpolyMethod ispointonpoly(pt::Point, pgon; atol=10E-5) Return true if pt lies on the polygon pgon. source Luxor.ispolyclockwiseMethod ispolyclockwise(pgon) Returns true if polygon is clockwise. WHEN VIEWED IN A LUXOR DRAWING...? TODO This code is still experimental... source Luxor.ispolyconvexMethod ispolyconvex(pts) Return true if polygon is convex. This tests that every interior angle is less than or equal to 180°. source Luxor.juliacirclesFunction juliacircles(radius=100; outercircleratio=0.75, innercircleratio=0.65, action=:fill) Draw the three Julia circles ("dots") in color centered at the origin. The distance of the centers of each circle from the origin is radius. The optional keyword argument outercircleratio (default 0.75) determines the radius of each circle relative to the main radius. So the default is to draw circles of radius 75 points around a larger circle of radius 100. Return the three centerpoints. The innercircleratio (default 0.65) no longer does anything useful (it used to draw the smaller circles) and will be deprecated. source Luxor.julialogoMethod julialogo(; action=:fill, color=true, bodycolor=colorant"black", centered=false) Draw the Julia logo. The default action is to fill the logo and use the colors: julialogo() If color is false, the bodycolor color is used for the logo. The function uses the current drawing state (position, scale, etc). The centered keyword lets you center the logo at its mathematical center, but the optical center might lie somewhere else - it's difficult to position well due to its asymmetric design. To use the logo as a clipping mask: julialogo(action=:clip) (In this case the color setting is automatically ignored.) To obtain a stroked (outlined) version: julialogo(action=:path) sethue("red") strokepath() TODO Return something more useful than a Boolean. source Luxor.juliatocairomatrixMethod juliatocairomatrix(c) Return a six-element matrix that's the equivalent of the 3x3 Julia matrix in c. See getmatrix() for details. source Luxor.labelFunction label(txt::T where T <: AbstractString, rotation::Float64, pos::Point=O; offset=5, leader=false, leaderoffsets=[0.0, 1.0]) Add a text label at a point, positioned relative to that point, for example, 0.0 is East, pi is West. label("text", pi) # positions text to the left of the origin source Luxor.labelFunction label(txt::T where T <: AbstractString, alignment::Symbol=:N, pos::Point=O; offset=5, leader=false, leaderoffsets=[0.0, 1.0]) Add a text label at a point, positioned relative to that point, for example, :N signifies North and places the text directly above that point. Use one of :N, :S, :E, :W, :NE, :SE, :SW, :NW to position the label relative to that point. label("text") # positions text at North (above), relative to the origin label("text", :S) # positions text at South (below), relative to the origin label("text", :S, pt) # positions text South of pt label("text", :N, pt, offset=20) # positions text North of pt, offset by 20 The default offset is 5 units. If leader is true, draw a line as well. leaderoffsts uses normalized fractions (see between()) to specify the gap between the designated points and the start and end of the lines. TODO: Negative offsets don't give good results. source Luxor.latexboundingboxFunction latexboundingbox(lstr::LaTeXString, font_size=get_fontsize(); halign=:left, valign=:right) Returns the bounding box containing the latex text with (Lower Left Point, Upper Right Point). Use box(latex_bb(testext)...,:stroke) to draw the bounding box. source Luxor.layoutMethod layout(A, x, y, w, h) From A, make a row of tiles (if wider than tall) or a column of tiles (if taller than wide). source Luxor.lineMethod line(pt::Point) Add a straight line to the current path that joins the path's current point to pt. The current point is then updated to pt. Other path-building functions are move(), curve(), arc(), rline(), and rmove(). See also currentpoint() and hascurrentpoint(). source Luxor.lineMethod line(pt1::Point, pt2::Point; action=:path) line(pt1::Point, pt2::Point, action=:path) Add a straight line between two points pt1 and pt2, and do the action. source Luxor.makebezierpathMethod makebezierpath(pgon::Array{Point, 1}; smoothing=1.0) Return a Bézier path (a BezierPath) that represents a polygon (an array of points). The Bézier path is an array of segments (tuples of 4 points); each segment contains the four points that make up a section of the entire Bézier path. smoothing determines how closely the curve follows the polygon. A value of 0 returns a straight-sided path; as values move above 1 the paths deviate further from the original polygon's edges. source Luxor.maskMethod mask(point::Point, focus::Point, width, height) max = 1.0, min = 0.0, easingfunction = easingflat) Calculate a value between 0 and 1 for a point relative to a rectangular area defined by focus, width, and height. The value will approach max (1.0) at the center, and min (0.0) at the edges. source Luxor.maskMethod mask(point::Point, focus::Point, radius) max = 1.0, min = 0.0, easingfunction = easingflat) Calculate a value between 0 and 1 for a point relative to a circular area defined by focus and radius. The value will approach max (1.0) at the center of the circular area, and min (0.0) at the circumference. source Luxor.meshFunction mesh(points::Array{Point}, colors=Array{Colors.Colorant, 1}) Create a mesh. The first three or four sides of the supplied points polygon define the three or four sides of the mesh shape. The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied. Example @svg begin pl = ngon(O, 250, 3, pi/6, vertices=true) mesh1 = mesh(pl, [ "purple", Colors.RGBA(0.0, 1.0, 0.5, 0.5), "yellow" ]) setmesh(mesh1) setline(180) ngon(O, 250, 3, pi/6, :strokepreserve) setline(5) sethue("black") strokepath() end source Luxor.meshFunction mesh(bezierpath::BezierPath, colors=Array{Colors.Colorant, 1}) Create a mesh. The first three or four elements of the supplied bezierpath define the three or four sides of the mesh shape. The colors array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied. Use setmesh() to select the mesh, which will be used to fill shapes. Example @svg begin bp = makebezierpath(ngon(O, 50, 4, 0, vertices=true)) mesh1 = mesh(bp, [ "red", Colors.RGB(0, 1, 0), Colors.RGB(0, 1, 1), Colors.RGB(1, 0, 1) ]) setmesh(mesh1) box(O, 500, 500, :fill) end source Luxor.midpointFunction midpoint(bb::BoundingBox=BoundingBox()) Returns the point midway between the two points of the BoundingBox. This should also be the center, unless I've been very stupid... source Luxor.moveMethod move(pt) Begin a new subpath in the current path, and set the current path's current point to pt, without drawing anything. Other path-building functions are line(), curve(), arc(), rline(), and rmove(). hascurrentpoint() returns true if there is a current point. source Luxor.nearestindexMethod nearestindex(polydistancearray, value) Return a tuple of the index of the largest value in polydistancearray less than value, and the difference value. Array is assumed to be sorted. (Designed for use with polydistances()). source Luxor.newpathMethod newpath() Discard the current path's contents. After this, the current path is empty, and there's no current point. source Luxor.ngonFunction ngon(centerpos, radius, sides=5, orientation=0; action=:none, vertices=false, reversepath=false) Draw a regular polygon centered at point centerpos. Find the vertices of a regular n-sided polygon centered at x, y with circumradius radius. The polygon is constructed counterclockwise, starting with the first vertex drawn below the positive x-axis. If you just want the raw points, use keyword argument vertices=true, which returns the array of points. Compare: ngon(0, 0, 4, 4, 0, vertices=true) # returns the polygon's points: 4-element Array{Luxor.Point, 1}: Luxor.Point(2.4492935982947064e-16, 4.0) Luxor.Point(-4.0, 4.898587196589413e-16) Luxor.Point(-7.347880794884119e-16, -4.0) Luxor.Point(4.0, -9.797174393178826e-16) whereas ngon(0, 0, 4, 4, 0, :close) # draws a polygon source Luxor.ngonFunction ngon(x, y, radius, sides=5, orientation=0; action = :none, vertices = false, reversepath = false) Draw a regular polygon centered at point Point(x,y). source Luxor.ngonsideFunction ngonside(centerpoint::Point, sidelength::Real, sides::Int=5, orientation=0; action=:none, vertices=false, reversepath=false) ngonside(centerpoint::Point, sidelength::Real, sides::Int, orientation, action; vertices=false, reversepath=false) Draw a regular polygon centered at centerpoint with sides sides of length sidelength. source Luxor.noiseMethod noise(x) ; detail = 1, persistence = 1.0) # 1D noise(x, y) ; detail = 1, persistence = 1.0) # 2D noise(x, y, z) ; detail = 1, persistence = 1.0) # 3D noise(x, y, z, w) ; detail = 1, persistence = 1.0) # 4D Generate a noise value between 0.0 and 1.0 corresponding to the x, y, z, and w values. An x value on its own produces 1D noise, x and y make 2D noise, and so on. The detail value is an integer (>= 1) specifying how many octaves of noise you want. The persistence value, typically between 0.0 and 1.0, controls how quickly the amplitude diminishes for each successive octave for values of detail greater than 1. source Luxor.offsetlinesegmentMethod offsetlinesegment(p1, p2, p3, d1, d2) Given three points, find another 3 points that are offset by d1 at the start and d2 at the end. Negative d values put the offset on the left. Used by offsetpoly(). source Luxor.offsetpolyMethod offsetpoly(plist, shape::Function) Return a closed polygon that is offset from and encloses an polyline. The incoming set of points plist is treated as an polyline, and another set of points is created, which form a closed polygon offset from the source poly. There must be at least 4 points in the polyline. This method for offsetpoly() treats the list of points as n vertices connected with n - 1 lines. (The other method offsetpoly(plist, d) treats the list of points as n vertices connected with n lines.) The supplied function determines the width of the line. f(0, θ) gives the width at the start (the slope of the curve at that point is supplied in θ), f(1, θ) provides the width at the end, and f(n, θ) is the width of point n/l. Examples This example draws a tilde, with the ends starting at 20 (10 + 10) units wide, swelling to 50 (10 + 10 + 15 + 15) in the middle, as f(0.5) = 25. f(x, θ) = 10 + 15sin(x * π) sinecurve = [Point(50x, 50sin(x)) for x in -π:π/24:π] pgon = offsetpoly(sinecurve, f) poly(pgon, :fill) This example enhances the vertical part of the curve, and thins the horizontal parts. g(x, θ) = rescale(abs(sin(θ)), 0, 1, 0.1, 30) sinecurve = [Point(50x, 50sin(x)) for x in -π:π/24:π] pgon = offsetpoly(sinecurve, g) poly(pgon, :fill) TODO - rewrite it! source Luxor.offsetpolyMethod offsetpoly(plist; startoffset = 10, endoffset = 10, easingfunction = lineartween) Return a closed polygon that is offset from and encloses an open polygon. The incoming set of points plist is treated as an open polygon, and another set of points is created, which form a polygon lying ...offset units away from the source poly. This method for offsetpoly() treats the list of points as n vertices connected with n - 1 lines. It allows you to vary the offset from the start of the line to the end. The other method offsetpoly(plist, d) treats the list of points as n vertices connected with n lines. Extended help This function accepts a keyword argument that allows you to control the offset using a function, using the easing functionality built in to Luxor. By default the function is lineartween(), so the offset changes linearly between the startoffset and the endoffset. The function: f(a, b, c, d) = 2sin((a * π)) runs from 0 to 2 and back as a runs from 0 to 1. The offsets are scaled by this amount. source Luxor.offsetpolyMethod offsetpoly(plist::Array{Point, 1}, d) where T<:Number Return a polygon that is offset from a polygon by d units. The incoming set of points plist is treated as a polygon, and another set of points is created, which form a polygon lying d units away from the source poly. Polygon offsetting is a topic on which people have written PhD theses and published academic papers, so this short brain-dead routine will give good results for simple polygons up to a point (!). There are a number of issues to be aware of: • very short lines tend to make the algorithm 'flip' and produce larger lines • small polygons that are counterclockwise and larger offsets may make the new polygon appear the wrong side of the original • very sharp vertices will produce even sharper offsets, as the calculated intersection point veers off to infinity • duplicated adjacent points might cause the routine to scratch its head and wonder how to draw a line parallel to them source Luxor.originMethod origin(pt:Point) Reset the current position, scale, and orientation, then move the 0/0 position to pt. source Luxor.originMethod origin() Reset the current position, scale, and orientation, and then set the 0/0 origin to the center of the drawing (otherwise it will stay at the top left corner, the default). You can refer to the 0/0 point as O. (O = Point(0, 0)), source Luxor.pathlengthMethod pathlength(path::Path; steps=10) Return the length of a Path. The steps parameter is used when approximating the length of any curve (Bezier) sections. source Luxor.pathsampleMethod pathsample(path::Path, spacing; steps=10) Return a new Path that resamples the path such that each line and curve of the original path is divided into sections that are approximately spacing units long. The steps parameter is used when approximating the length of any curve (Bezier) sections. For measurement purposes, each Bezier curve is divided in steps straight lines; the error will be smaller for flatter curves and larger for more curvy ones. source Luxor.pathtobezierpathsMethod pathtobezierpaths( ; flat=true) Convert the current Cairo path (which may consist of one or more paths) to an array of Bézier paths, each of which is an array of BezierPathSegments. Each path segment is a tuple of four points. A straight line is converted to a Bézier segment in which the control points are set to be the same as the end points. If flat is true, use getpathflat() rather than getpath(). Example This code draws the BezierPathSegments and shows the control points as "handles", like a vector-editing program might. @svg begin fontface("MyanmarMN-Bold") st = "goo" thefontsize = 100 fontsize(thefontsize) sethue("red") fontsize(thefontsize) textpath(st) nbps = pathtobezierpaths() for nbp in nbps setline(.15) sethue("grey50") drawbezierpath(nbp, :stroke) for p in nbp sethue("red") circle(p[2], 0.16, :fill) circle(p[3], 0.16, :fill) line(p[2], p[1], :stroke) line(p[3], p[4], :stroke) if p[1] != p[4] sethue("black") circle(p[1], 0.26, :fill) circle(p[4], 0.26, :fill) end end end end source Luxor.pathtopolyMethod pathtopoly() Convert the current path to an array of polygons. Returns an array of polygons, corresponding to the paths and subpaths of the original path. source Luxor.perpendicularMethod perpendicular(p1, p2, k) Return a point p3 that is k units away from p1, such that a line p1 p3 is perpendicular to p1 p2. Convention? to the right? source Luxor.perpendicularMethod perpendicular(p1::Point, p2::Point, p3::Point) Return a point on a line passing through p1 and p2 that is perpendicular to p3. source Luxor.perpendicularMethod perpendicular(p1, p2) Return two points p3 and p4 such that a line from p3 to p4 is perpendicular to a line from p1 to p2, the same length, and the lines intersect at their midpoints. source Luxor.pieMethod pie(x, y, radius, startangle, endangle; action=:none) pie(centerpoint, radius, startangle, endangle; action=:none) Make a pie shape centered at x/y. Angles start at the positive x-axis and are measured clockwise, and add it to the current path. TODO - return something more useful than a Boolean source Luxor.pieMethod pie(centerpoint::Point, radius::Real, startangle::Real, endangle::Real, action::Symbol) source Luxor.pieMethod pie(radius, startangle, endangle; action=:none) Make a pie shape centered at the origin, and add it to the current path. source Luxor.placeimageFunction placeimage(svgimg, pos=O; centered=false) Place an SVG image stored in svgimg on the drawing at pos. Use readsvg() to read an SVG image from file, or from SVG code. Use keyword centered=true to place the center of the image at the position. source Luxor.placeimageFunction placeimage(matrix::AbstractMatrix{UInt32}, pos=O; alpha=1, centered=false) Place an image matrix on the drawing at pos with opacity/transparency alpha. Use keyword centered=true to place the center of the image at the position. source Luxor.placeimageMethod placeimage(buffer::AbstractMatrix{ARGB32}, args...; kargs...) Place an array of ARGB32 lements on the drawing at pos with opacity/transparency alpha. Values are "alpha-premultiplied" before being placed. Use keyword centered=true to place the center of the image at the position. source Luxor.placeimageMethod placeimage(img, pt::Point=O, alpha; centered=false) placeimage(pngimg, xpos, ypos, alpha; centered=false) Place a PNG image pngimg on the drawing at pt or Point(xpos, ypos) with opacity/transparency alpha. The image has been previously loaded using readpng(). Use keyword centered=true to place the center of the image at the position. source Luxor.placeimageMethod placeimage(pngimg, pos=O; centered=false) placeimage(pngimg, xpos, ypos; centered=false) Place the PNG image on the drawing at pos, or (xpos/ypos). The image img has been previously read using readpng(). Use keyword centered=true to place the center of the image at the position. source Luxor.pointcircletangentMethod pointcircletangent(point::Point, circlecenter::Point, circleradius) Find the two points on a circle that lie on tangent lines passing through an external point. If both points are O, the external point is inside the circle, and the result is (O, O). source Luxor.pointcrossesboundingboxMethod pointcrossesboundingbox(pt, bbox::BoundingBox) Find and return the point where a line from the center of bounding box bbox to point pt would, if continued, cross the edges of the box. source Luxor.pointinverseMethod pointinverse(A::Point, centerpoint::Point, rad) Find A′, the inverse of a point A with respect to a circle centerpoint/rad, such that: distance(centerpoint, A) * distance(centerpoint, A′) == rad^2 Return (true, A′) or (false, A). source Luxor.pointlinedistanceMethod pointlinedistance(p::Point, a::Point, b::Point) Find the distance between a point p and a line between two points a and b. source Luxor.polarMethod polar(r, theta) Convert a point specified in polar form (radius and angle) to a Point. polar(10, pi/4) produces Luxor.Point(7.071067811865475, 7.0710678118654755) source Luxor.polyFunction poly(bbox::BoundingBox, :action; kwargs...) Make a polygon around the BoundingBox in bbox. source Luxor.polyMethod Draw a polygon. poly(pointlist::Array{Point, 1}, action = :none; close=false, reversepath=false) Create a path with the points in pointlist and apply action. By default poly() doesn't close or fill the polygon. source Luxor.polyareaMethod polyarea(p::Array) Find the area of a simple polygon. It works only for polygons that don't self-intersect. See also polyorientation(). source Luxor.polycentroidMethod Find the centroid of a simple polygon. polycentroid(pointlist) Returns a point. This only works for simple (non-intersecting) polygons. You could test the point using isinside(). source Luxor.polyclipMethod polyclip(s, c) Return a polygon that defines the intersection between an subject polygon and the clip polygon. Return nothing if the function can't find one. S - subject polygon - can be concave or convex. C - clip polygon - must be convex. Uses the Sutherland-Hodgman clipping algorithm. Calls ispointonleftofline(). source Luxor.polycrossFunction polycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0; action = :none, splay = 0.5, vertices = false, reversepath = false) polycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0, action; splay = 0.5, vertices = false, reversepath = false) Make a cross-shaped polygon with npoints arms to fit inside a circle of radius radius centered at pt. ratio specifies the ratio of the two sides of each arm. splay makes the arms ... splayed. Use vertices=true to return the vertices of the shape instead of executing the action. (Adapted from Compose.jl.xgon())) Examples polycross(O, 100, 5, action = :fill, splay = 0.5) polycross(O, 120, 5, 0.5, 0.0, :stroke, splay = 0.5) source Luxor.polyfitFunction polyfit(plist::Array, npoints=30) Build a polygon that constructs a B-spine approximation to it. The resulting list of points makes a smooth path that runs between the first and last points. source Luxor.polyhullMethod polyhull(pts) Find all points in pts that form a convex hull around the points in pts, and return them. This uses the Graham Scan algorithm. TODO : experimental, can be improved. source Luxor.polyintersectMethod polyintersect(p1::AbstractArray{Point, 1}, p2::AbstractArray{Point, 1}; closed=true) TODO: Fix/test/improve this experimental polygon intersection routine. Return the points where polygon p1 and polygon p2 cross. If closed is false, the intersection points must lie on the first n - 1 lines of each polygon. source Luxor.polymorphMethod polymorph(pgon1::Array{Array{Point,1}}, pgon2::Array{Array{Point,1}}, k; samples = 100, easingfunction = easingflat, kludge = true closed = true) "morph" is to gradually change from one thing to another. This function changes one polygon into another. It returns an array of polygons, [p_1, p_2, p_3, ... ], where each polygon p_n is the intermediate shape between the corresponding shape in pgon1[1...n] and pgon2[1...n] at k, where 0.0 < k < 1.0. If k ≈ 0.0, the pgon1[1...n] is returned, and if k ≈ 1.0, pgon2[1...n] is returned. pgon1 and pgon2 can be either simple polygons or arrays of one or more polygonal shapes (eg as created by pathtopoly()). For example, pgon1 might consist of two polygonal shapes, a square and a triangular shaped hole inside; pgon2 might be a triangular shape with a square hole. It makes sense for both arguments to have the same number of polygonal shapes. If one has more than another, some shapes would be lost when it morphs. But the suggestively-named kludge keyword argument, when set to (the default) true, tries to compensate for this. By default, easingfunction = easingflat, so the intermediate steps are linear. If you use another easing function, intermediate steps are determined by the value of the easing function at k. Because polysample() can treat the polygon as open or closed (with different results), you can specify how the sampling is done here, with the closed= keyword: • closed = true -> polygons are sampled as closed • closed = false -> polygons are sampled as open • closed = (true, false) -> first polygon is sampled as closed, second as open This function isn't very efficient, because it copies the polygons and resamples them. TODO : experimental, can surely be improved. Extended help Examples This simple morph between a small square and a larger octagon is controlled by the easing function easeinoutinversequad, which slows down around the middle of the transition. Only the first shape of the returned polygon array is needed. pgon1 = ngon(O, 30, 4, 0, vertices = true) pgon2 = ngon(O, 220, 8, 0, vertices = true) for i in 0:0.1:1.0 poly(first(polymorph(pgon1, pgon2, i, easingfunction = easeinoutinversequad)), action = :stroke, close = true) end This next example morphs between the first shape - a circle with a square hole - and the second shape, a square with a circular hole. ngon(O - (250, 0), 30, 50, 0, :path) newsubpath() ngon(O - (250, 0), 10, 4, 0, reversepath = true, :path) pg1 = pathtopoly() newpath() ngon(O + (250, 0), 30, 4, 0, :path) newsubpath() ngon(O + (250, 0), 10, 50, 0, reversepath = true, :path) pg2 = pathtopoly() for i in reverse(0.0:0.1:1.0) randomhue() newpath() # use :path followed by fillpath() to preserve correct "hole"-iness poly.(polymorph(pg1, pg2, i), :path, close = true) fillpath() end source Luxor.polymove!Method polymove!(pgon, frompoint::Point, topoint::Point) Move (permanently) a polygon from frompoint to topoint. source Luxor.polyorientationMethod polyorientation(pgon) Returns a number which is positive if the polygon is clockwise in Luxor... TODO This code is still experimental... source Luxor.polyportionFunction polyportion(p::Array{Point, 1}, portion=0.5; closed=true, pdist=[]) Return a portion of a polygon, starting at a value between 0.0 (the beginning) and 1.0 (the end). 0.5 returns the first half of the polygon, 0.25 the first quarter, 0.75 the first three quarters, and so on. Use closed=false to exclude the line joining the final point to the first point from the calculations. If you already have a list of the distances between each point in the polygon (the "polydistances"), you can pass them in pdist, otherwise they'll be calculated afresh, using polydistances(p, closed=closed). Use the complementary polyremainder() function to return the other part. source Luxor.polyreflect!Function polyreflect!(pgon, pt1 = O, pt2 = O + (0, 100) Reflect (permanently) a polygon in a line (default to the y-axis) joining two points. source Luxor.polyremainderFunction polyremainder(p::Array{Point, 1}, portion=0.5; closed=true, pdist=[]) Return the rest of a polygon, starting at a value between 0.0 (the beginning) and 1.0 (the end). 0.5 returns the last half of the polygon, 0.25 the last three quarters, 0.75 the last quarter, and so on. Use closed=false to exclude the line joining the final point to the first point from the calculations. If you already have a list of the distances between each point in the polygon (the "polydistances"), you can pass them in pdist, otherwise they'll be calculated afresh, using polydistances(p, closed=closed). Use the complementary polyportion() function to return the other part. source Luxor.polyremovecollinearpointsMethod polyremovecollinearpoints(pgon::Array{Point, 1}) Return copy of polygon with no collinear points. Caution: may return an empty polygon... ! TODO This code is still experimental... source Luxor.polyrotate!Method polyrotate!(pgon, θ; center=O) Rotate (permanently) a polygon around center by θ radians. source Luxor.polysampleMethod polysample(p::Array{Point, 1}, npoints::T where T <: Integer; closed=true) Sample the polygon p, returning a polygon with npoints to represent it. The first sampled point is: 1/npoints * perimeter of p away from the original first point of p. If npoints is the same as length(p) the returned polygon is the same as the original, but the first point finishes up at the end (so new=circshift(old, 1)). If closed is true, the entire polygon (including the edge joining the last point to the first point) is sampled. If include_first is true, the first point of plist is included in the result. If the resulting polygon's first and end points are the same, the end point is discarded. source Luxor.polyscale!Method polyscale!(pgon, sh, sv; center=O) Scale (permanently) a polygon by sh horizontally and sv vertically, relative to center. source Luxor.polyscale!Method polyscale!(pgon, s; center=O) Scale (permanently) a polygon by s, relative to center. source Luxor.polysmoothMethod polysmooth(points, radius, action=:action; debug=false) polysmooth(points, radius; action=:none, debug=false) Make a closed path from the points and round the corners by making them arcs with the given radius. Execute the action when finished. The arcs are sometimes different sizes: if the given radius is bigger than the length of the shortest side, the arc can't be drawn at its full radius and is therefore drawn as large as possible (as large as the shortest side allows). The debug option also draws the construction circles at each corner. TODO Return something more useful than a Boolean. source Luxor.polysortbyangleFunction Sort the points of a polygon into order. Points are sorted according to the angle they make with a specified point. polysortbyangle(pointlist::Array, refpoint=minimum(pointlist)) The refpoint can be chosen, but the default minimum point is usually OK too: polysortbyangle(parray, polycentroid(parray)) source Luxor.polysortbydistanceMethod Sort a polygon by finding the nearest point to the starting point, then the nearest point to that, and so on. polysortbydistance(p, starting::Point) You can end up with convex (self-intersecting) polygons, unfortunately. source Luxor.polysplitMethod polysplit(p, p1, p2) Split a polygon into two where it intersects with a line. It returns two polygons: (poly1, poly2) This doesn't always work, of course. For example, a polygon the shape of the letter "E" might end up being divided into more than two parts. source Luxor.polytopathMethod polytopath(ptlist) Convert a polygon to a Path object. @draw drawpath(polytopath(ngon(O, 145, 5, vertices = true)), action = :fill) source Luxor.polytriangulateMethod polytriangulate(plist::Array{Point,1}; epsilon = -0.01) Triangulate the polygon in plist. This uses the Bowyer–Watson/Delaunay algorithm to make triangles. It returns an array of triangular polygons. TODO: This experimental polygon function is not very efficient, because it first copies the list of points (to avoid modifying the original), and sorts it, before making triangles. source Luxor.prettypolyFunction prettypoly(bbox::BoundingBox, action; kwargs...) Make a decorated polygon around the BoundingBox in bbox. The vertices are in the order: bottom left, top left, top right, and bottom right. source Luxor.prettypolyFunction prettypoly(points::Array{Point, 1}, vertexfunction = () -> circle(O, 2, :stroke); action=:none, close=false, reversepath=false, vertexlabels = (n, l) -> () ) Draw the polygon defined by points, possibly closing and reversing it, using the current parameters, and then evaluate the vertexfunction function at every vertex of the polygon. The default vertexfunction draws a 2 pt radius circle. To mark each vertex of a polygon with a randomly colored filled circle: p = star(O, 70, 7, 0.6, 0, vertices=true) prettypoly(p, action=:fill, () -> begin randomhue() circle(O, 10, :fill) end, close=true) The optional keyword argument vertexlabels lets you supply a function with two arguments that can access the current vertex number and the total number of vertices at each vertex. For example, you can label the vertices of a triangle "1 of 3", "2 of 3", and "3 of 3" using: prettypoly(triangle, action=:stroke, vertexlabels = (n, l) -> (text(string(n, " of ", l)))) source Luxor.previewMethod preview() If you're working in a notebook (eg Jupyter/IJulia), display a PNG or SVG file in the notebook. If you're working in VS-Code, display a PNG or SVG file in the Plots pane. Drawings of type :image should be converted to a matrix with image_as_matrix() before calling finish(). Otherwise: • on macOS, open the file in the default application, which is probably the Preview.app for PNG and PDF, and Safari for SVG • on Unix, open the file with xdg-open • on Windows, refer to COMSPEC. source Luxor.randompointMethod randompoint(lowx, lowy, highx, highy) Return a random point somewhere inside a rectangle defined by the four values. source Luxor.randompointarrayMethod randompointarray(lowx, lowy, highx, highy, n) Return an array of n random points somewhere inside the rectangle defined by the four coordinates. source Luxor.randompointarrayMethod randompointarray(w, h, d; attempts=20) Return an array of randomly positioned points inside the rectangle defined by the current origin (0/0) and the width and height. d determines the minimum distance between each point. Increase attempts if you want the function to try harder to fill empty spaces; decrease it if it's taking too long to look for samples that work. This uses Bridson's Poisson Disk Sampling algorithm: https://www.cs.ubc.ca/~rbridson/docs/bridson-siggraph07-poissondisk.pdf Example for pt in randompointarray(BoundingBox(), 20) randomhue() circle(pt, 10, :fill) end source Luxor.randompointarrayMethod randompointarray(bbox::BoundingBox, d; attempts=20) Return an array of randomly positioned points inside the bounding box d units apart. source Luxor.randompointarrayMethod randompointarray(lowpt, highpt, n) Return an array of n random points somewhere inside the rectangle defined by two points. source Luxor.rawlatexboundingboxMethod rawlatexboundingbox(lstr::LaTeXString, font_size=1) Helper function that returns the coordinate points of the bounding box containing the specific LaTeX text. source Luxor.readpngMethod readpng(pathname) Read a PNG file. This returns a image object suitable for placing on the current drawing with placeimage(). You can access its width and height fields: image = readpng("test-image.png") w = image.width h = image.height source Luxor.readsvgMethod readsvg(str) Read an SVG image. str is either pathname or pure SVG code. This returns an SVG image object suitable for placing on the current drawing with placeimage(). Placing an SVG file: @draw begin mycoollogo = readsvg("mylogo.svg") placeimage(mycoollogo) end Placing SVG code: # from https://github.com/edent/SuperTinyIcons julialogocode = """<svg xmlns="http://www.w3.org/2000/svg" aria-label="Julia" role="img" viewBox="0 0 512 512"> <rect width="512" height="512" rx="15%" fill="#fff"/> <circle fill="#389826" cx="256" cy="137" r="83"/> <circle fill="#cb3c33" cx="145" cy="329" r="83"/> <circle fill="#9558b2" cx="367" cy="329" r="83"/> </svg>""" @draw begin julia_logo = readsvg(julialogocode) placeimage(julia_logo, centered=true) end source Luxor.rectMethod rect(xmin, ymin, w, h; action=:none) rect(xmin, ymin, w, h, action) Create a rectangle with one corner at (xmin/ymin) with width w and height h, and add it to the current path. Then apply action. Returns a tuple of two points, the corners of a bounding box that encloses the rectangle. See box() for more ways to do similar things, such as supplying two opposite corners, placing by centerpoint and dimensions. source Luxor.rectMethod rect(cornerpoint, w, h; action = none, reversepath=false, vertices=false) rect(cornerpoint, w, h, action; reversepath=false, vertices=false) Create a rectangle with one corner at cornerpoint with width w and height h, and add it to the current path. Then apply action. Use vertices=true to return an array of the four corner points: bottom left, top left, top right, bottom right. reversepath reverses the direction of the path (and returns points in the order: bottom left, bottom right, top right, top left). Returns the four corner vertices. source Luxor.rescaleFunction rescale(x, from_min, from_max, to_min=0.0, to_max=1.0) Convert x from one linear scale (from_min to from_max) to another (to_min to to_max). The scales can also be supplied in tuple form: rescale(x, (from_min, from_max), (to_min, to_max)) using Luxor julia> rescale(15, 0, 100, 0, 1) 0.15 julia> rescale(15, (0, 100), (0, 1)) 0.15 julia> rescale(pi/20, 0, 2pi, 0, 1) 0.025 julia> rescale(pi/20, (0, 2pi), (0, 1)) 0.025 julia> rescale(25, 0, 1, 0, 1.609344) 40.2336 julia> rescale(15, (0, 100), (1000, 0)) 850.0  source Luxor.rmoveMethod rmove(pt) Begin a new subpath in the current path, add pt to the current path's current point, then update the current point. Other path-building functions are move(), line(), curve(), arc(), and rline(). There must be a current point before you call this function. See also currentpoint() and hascurrentpoint(). source Luxor.rotateMethod rotate(a::Float64) Rotate the current workspace by a radians clockwise (from positive x-axis to positive y-axis). Values are relative to the current orientation. source Luxor.rotatepointMethod rotatepoint(targetpt::Point, angle) Rotate a target point around the current origin by an angle specified in radians. Returns the new point. source Luxor.rotatepointMethod rotatepoint(targetpt::Point, originpt::Point, angle) Rotate a target point around another point by an angle specified in radians. Returns the new point. source Luxor.rotationmatrixMethod rotationmatrix(a) Return a 3x3 Julia matrix that will apply a rotation through a radians. See getmatrix() for details. source Luxor.ruleFunction rule(pos, theta; boundingbox=BoundingBox(), vertices=false) Draw a straight line through pos at an angle theta from the x axis. By default, the line spans the entire drawing, but you can supply a BoundingBox to change the extent of the line. rule(O) # draws an x axis rule(O, pi/2) # draws a y axis The function: rule(O, pi/2, boundingbox=BoundingBox()/2) draws a line that spans a bounding box half the width and height of the drawing, and returns a Set of end points. If you just want the vertices and don't want to draw anything, use vertices=true. source Luxor.rulersMethod rulers() Draw and label two CAD-style rulers starting at O, the current 0/0, and continuing out along the current positive x and y axes. source Luxor.scaleMethod scale(x, y) Scale the current workspace by different values in x and y. Values are relative to the current scale. Example: scale(0.2, 0.3) source Luxor.scaleMethod scale(f) Scale the current workspace by f in both x and y directions. Values are relative to the current scale. source Luxor.scalingmatrixMethod scalingmatrix(sx, sy) Return a 3x3 Julia matrix that will apply a scaling by sx and sy. See getmatrix() for details. source Luxor.sectorMethod sector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real; action=:none) Make an annular sector centered at the origin, and add it to the current path. source Luxor.sectorMethod sector(centerpoint::Point, innerradius, outerradius, startangle, endangle, cornerradius; action:none) Make an annular sector with rounded corners, basically a bent sausage shape, centered at centerpoint, and add it to the current path. TODO: The results aren't 100% accurate at the moment. There are small discontinuities where the curves join. TODO - return something more useful than a Boolean The cornerradius is reduced from the supplied value if neceesary to prevent overshoots. source Luxor.sectorMethod sector(centerpoint::Point, innerradius, outerradius, startangle, endangle; action=:none) Make an annular sector centered at centerpoint, and add it to the current path. TODO - return something more useful than a Boolean source Luxor.sectorMethod sector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real, cornerradius::Real, action) Make an annular sector with rounded corners, centered at the current origin, and add it to the current path. source Luxor.setantialiasMethod setantialias(n) Set the current antialiasing to a value between 0 and 6: antialias_default = 0, the default antialiasing for the subsystem and target device antialias_none = 1, use a bilevel alpha mask antialias_gray = 2, use single-color antialiasing (using shades of gray for black text on a white background, for example) antialias_subpixel = 3, take advantage of the order of subpixel elements on devices such as LCD panels antialias_fast = 4, perform some antialiasing but prefer speed over quality antialias_good = 5, balance quality against performance antialias_best = 6, render at the highest quality, sacrificing speed if necessary This affects subsequent graphics, but not text, and it doesn't apply to all types of output file. source Luxor.setbezierhandlesMethod setbezierhandles(bps::BezierPathSegment; angles = [0.05, -0.1], handles = [0.3, 0.3]) Return a new BezierPathSegment with new locations for the Bezier control points in the BezierPathSegment bps. angles are the two angles that the "handles" make with the line direciton. handles are the lengths of the "handles". 0.3 is a typical value. source Luxor.setbezierhandlesMethod setbezierhandles(bezpath::BezierPath; angles=[0 .05, -0.1], handles=[0.3, 0.3]) Return a new BezierPath with new locations for the Bézier control points in every Bézier path segment of the BezierPath in bezpath. angles are the two angles that the "handles" make with the line direciton. handles are the lengths of the "handles". 0.3 is a typical value. source Luxor.setblendMethod setblend(blend) Start using the named blend for filling graphics. This aligns the original coordinates of the blend definition with the current axes. source Luxor.setblendextendMethod setblendextend(blend::Blend, mode) Specify how color blend patterns are repeated/extended. Supply the blend and one of the following strings: • "repeat": the pattern is repeated • "reflect": the pattern is reflected (repeated in reverse) • "pad": outside the pattern, use the closest color • "none": outside of the pattern, use transparent pixels source Luxor.setcolorMethod setcolor("gold") setcolor("darkturquoise") Set the current color to a named color. This use the definitions in Colors.jl to convert a string to RGBA eg setcolor("gold") or "green", "darkturquoise", "lavender", etc. The list is at Colors.color_names. Use sethue() for changing colors without changing current opacity level. sethue() and setcolor() return the three or four values that were used: julia> setcolor(sethue("red")..., .8) (1.0, 0.0, 0.0, 0.8) julia> sethue(setcolor("red")[1:3]...) (1.0, 0.0, 0.0) You can also do: using Colors sethue(colorant"red") See also setcolor. source Luxor.setcolorMethod setcolor(r, g, b) setcolor(r, g, b, alpha) setcolor(color) setcolor(col::Colors.Colorant) setcolor(sethue("red")..., .2) Set the current color. Examples: setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1))) setcolor(.2, .3, .4, .5) setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1))) for i in 1:15:360 setcolor(convert(Colors.RGB, Colors.HSV(i, 1, 1))) ... end See also sethue. source Luxor.setdashFunction setdash(dashes::Vector, offset=0.0) Set the dash pattern for lines to the values in dashes. The first number is the length of the inked portion, the second the space, and so on. The offset specifies an offset into the pattern at which the stroke begins. So an offset of 10 means that the stroke starts at dashes[1] + 10 into the pattern. Or use setdash("dot") etc. source Luxor.setdashMethod setdash("dot") Set the dash pattern of lines to one of: "solid", "dotted", "dot", "dotdashed", "longdashed", "shortdashed", "dash", "dashed", "dotdotdashed", "dotdotdotdashed". Use setdash(dashes::Vector) to specify the pattern numerically. source Luxor.setfontMethod setfont(family, fontsize) Select a font and specify the size. Example: setfont("Helvetica", 24) settext("Hello in Helvetica 24 using the Pro API", Point(0, 10)) source Luxor.setgrayMethod setgray(n) setgrey(n) Set the color to a gray level of n, where n is between 0 and 1. source Luxor.sethueMethod sethue("black") sethue(0.3, 0.7, 0.9) setcolor(sethue("red")..., .2) Set the color without changing opacity. sethue() is like setcolor(), but we sometimes want to change the current color without changing alpha/opacity. Using sethue() rather than setcolor() doesn't change the current alpha opacity. See also setcolor. source Luxor.sethueMethod sethue(0.3, 0.7, 0.9) Set the color's r, g, b values. Use setcolor(r, g, b, a) to set transparent colors. source Luxor.setlineMethod setline(n) Set the line width, in points. Use getline() to get the current value. source Luxor.setlinecapFunction setlinecap(s) Set the line ends. s can be "butt" or :butt (the default), "square" or :square, or "round" or :round. source Luxor.setlinejoinFunction setlinejoin("miter") setlinejoin("round") setlinejoin("bevel") Set the way line segments are joined when the path is stroked. The default joining style is "mitered". source Luxor.setmatrixMethod setmatrix(m::Array) Change the current matrix to 6-element matrix m. See getmatrix() for details. source Luxor.setmeshMethod setmesh(mesh::Mesh) Select a mesh, previously created with mesh(), for filling and stroking subsequent graphics. source Luxor.setmodeMethod setmode(mode::AbstractString) Set the compositing/blending mode. mode can be one of: • "clear" Where the second object is drawn, the first is completely removed. • "source" The second object is drawn as if nothing else were below. • "over" The default mode: like two transparent slides overlapping. • "in" The first object is removed completely, the second is only drawn where the first was. • "out" The second object is drawn only where the first one wasn't. • "atop" The first object is mostly intact, but mixes both objects in the overlapping area. The second object is not drawn elsewhere. • "dest" Discard the second object completely. • "dest_over" Like "over" but draw second object below the first • "dest_in" Keep the first object whereever the second one overlaps. • "dest_out" The second object is used to reduce the visibility of the first where they overlap. • "dest_atop" Like "over" but draw second object below the first. • "xor" XOR where the objects overlap • "add" Add the overlapping areas together • "saturate" Increase Saturation where objects overlap • "multiply" Multiply where objects overlap • "screen" Input colors are complemented and multiplied, the product is complemented again. The result is at least as light as the lighter of the input colors. • "overlay" Multiplies or screens colors, depending on the lightness of the destination color. • "darken" Selects the darker of the color values in each component. • "lighten" Selects the lighter of the color values in each component. See the Cairo documentation for details. source Luxor.setopacityMethod setopacity(alpha) Set the current opacity to a value between 0 and 1. This modifies the alpha value of the current color. source Luxor.settextMethod settext(text, pos; halign = "left", valign = "bottom", angle = 0, # degrees! markup = false) settext(text; kwargs) Draw the text at pos (if omitted defaults to 0/0). If no font is specified, on macOS the default font is Times Roman. To align the text, use halign, one of "left", "center", or "right", and valign, one of "top", "center", or "bottom". angle is the rotation - in counterclockwise degrees, rather than Luxor's default clockwise (+x-axis to +y-axis) radians. If markup is true, then the string can contain some HTML-style markup. Supported tags include: <b>, <i>, <s>, <sub>, <sup>, <small>, <big>, <u>, <tt>, and <span> The <span> tag can contains things like this: <span font='26' background='green' foreground='red'>unreadable text</span> source Luxor.shiftbezierhandlesMethod shiftbezierhandles(bps::BezierPathSegment; angles=[0.1, -0.1], handles=[1.1, 1.1]) Return a new BezierPathSegment that modifies the Bézier path in bps by moving the control handles. The values in angles increase the angle of the handles; the values in handles modifies the lengths: 1 preserves the length, 0.5 halves the length of the handles, 2 doubles them. source Luxor.simplifyFunction Simplify a polygon: simplify(pointlist::Array, detail=0.1) detail is the maximum approximation error of simplified polygon. source Luxor.slopeMethod slope(pointA::Point, pointB::Point) Find angle of a line starting at pointA and ending at pointB. Return a value between 0 and 2pi. Value will be relative to the current axes. slope(O, Point(0, 100)) |> rad2deg # y is positive down the page 90.0 slope(Point(0, 100), O) |> rad2deg 270.0 The slope isn't the same as the gradient. A vertical line going up has a slope of 3π/2. source Luxor.snapshotMethod snapshot(; fname = :png, cb = missing, scalefactor = 1.0, addmarker = true) snapshot(fname, cb, scalefactor) -> finished snapshot drawing, for display Take a snapshot and save to 'fname' name and suffix. This requires that the current drawing is a recording surface. You can continue drawing on the same recording surface. Arguments fname the file name or symbol, see Drawing cb crop box::BoundingBox - what's inside is copied to snapshot scalefactor snapshot width/crop box width. Same for height. addmarker for more information about addmarker see help for Luxor._adjust_background_rects ?Luxor._adjust_background_rects Examples snapshot() snapshot(fname = "temp.png") snaphot(fname = :svg) cb = BoundingBox(Point(0, 0), Point(102.4, 96)) snapshot(cb = cb) pngdrawing = snapshot(fname = "temp.png", cb = cb, scalefactor = 10) The last example would return and also write a png drawing with 1024 x 960 pixels to storage. source Luxor.spiralMethod spiral(a, b; action = :none, stepby = 0.01, period = 4pi, vertices = false, log =false) spiral(a, b, action; stepby = 0.01, period = 4pi, vertices = false, log =false) Make a spiral, and add it to the current path. The two primary parameters a and b determine the start radius, and the tightness. For linear spirals (log=false), b values are: lituus: -2 hyperbolic spiral: -1 Archimedes' spiral: 1 Fermat's spiral: 2 For logarithmic spirals (log=true): golden spiral: b = ln(phi)/ (pi/2) (about 0.30) Values of b around 0.1 produce tighter, staircase-like spirals. source Luxor.splitbezierMethod splitbezier(bps::BezierPathSegment, t) Split the Bezier path segment at t, where t is between 0 and 1. Use Paul de Casteljaus' algorithm (the man who really introduced Bezier curves...). Returns a tuple of two BezierPathSegments, the 'lower' one (0 to t) followed by the 'higher' one (t to 1). Example julia> l bps = BezierPathSegment(ngon(O, 200, 4, vertices=true)...) l, h = splitbezier(bps::BezierPathSegment, 0.5) julia> h 4-element BezierPathSegment: Point(1.2246467991473532e-14, 200.0) Point(-100.0, 100.00000000000001) Point(-100.0, 1.4210854715202004e-14) Point(-50.00000000000001, -49.99999999999999) julia> l.p2 == h.p1 true source Luxor.splittextMethod splittext(s) Split the text in string s into an array, but keep all the separators attached to the preceding word. source Luxor.squircleMethod squircle(center::Point, hradius, vradius; action=:none, rt = 0.5, stepby = pi/40, vertices=false) squircle(center::Point, hradius, vradius, action; rt = 0.5, stepby = pi/40, vertices=false) Make a squircle or superellipse (basically a rectangle with rounded corners), and add it to the current path. Specify the center position, horizontal radius (distance from center to a side), and vertical radius (distance from center to top or bottom): The root (rt) option defaults to 0.5, and gives an intermediate shape. Values less than 0.5 make the shape more rectangular. Values above make the shape more round. The horizontal and vertical radii can be different. source Luxor.starFunction star(center, radius, npoints, ratio=0.5, orientation, action=:none; vertices = false, reversepath=false) star(center, radius, npoints, ratio=0.5, orientation=0.0; action=:none, vertices = false, reversepath=false) Make a star centered at center with npoints sections oriented by orientation. ratio specifies the height of the smaller radius of the star relative to the larger. Returns the vertices of the star. Use vertices=true to only return the vertices of a star instead of making it. Examples star(O, 120, 5, 0.5, 0.0, :fill, vertices = false, reversepath=false) star(O, 220, 5, 0.5; action=:stroke, vertices = false, reversepath=false) source Luxor.storepathMethod storepath() Obtain the current Cairo path and make a Luxor Path object, which is an array of PathElements. Returns the Path object. You can draw stored paths using drawpath(). See also getpath(), getpathflat(), and textpath(). source Luxor.strokepathMethod strokepath() Stroke the current path with the current line width, line join, line cap, dash, and stroke scaling settings. The current path is then emptied. source Luxor.strokepreserveMethod strokepreserve() Stroke the current path with current line width, line join, line cap, dash, and stroke scaling settings, but then keep the path current. source Luxor.svgstringMethod svgstring() Return the current and recently completed SVG drawing as a string of SVG commands. Returns "" if there is no SVG information available. To display the SVG string as a graphic, try the HTML() function in Base. ... HTML(svgstring()) In a Pluto notebook, you can also display the SVG using: # using PlutoUI ... PlutoUI.Show(MIME"image/svg+xml"(), svgstring()) (This lets you right-click to save the SVG.) Example This example examines the generated SVG code produced by drawing the Julia logo. Drawing(500, 500, :svg) origin() julialogo() finish() s = svgstring() eachmatch(r"rgb.*?;", s) |> collect 6-element Vector{RegexMatch}: RegexMatch("rgb(100%,100%,100%);") RegexMatch("rgb(0%,0%,0%);") RegexMatch("rgb(79.6%,23.5%,20%);") RegexMatch("rgb(25.1%,38.8%,84.7%);") RegexMatch("rgb(58.4%,34.5%,69.8%);") RegexMatch("rgb(22%,59.6%,14.9%);") Here's another example, post-processing the SVG file with the svgo optimizer. @drawsvg begin background("midnightblue") fontface("JuliaMono-Regular") fontsize(20) sethue("gold") text("JuliaMono: a monospaced font ", halign=:center) text("with reasonable Unicode support", O + (0, 22), halign=:center) end 500 150 write("txt.svg", svgstring()) # minimize SVG run(svgo txt.svg -o txt-min.svg) source Luxor.texalignMethod texalign(halign, valign, bottom_pt, top_pt, font_size) Helper function to align LaTeX text properly. Returns translate_x and translate_y which consists of the amount to be shifted depending on the type of alignment chosen and the bounding box of the text. source Luxor.textMethod text(str) text(str, pos) text(str, pos, angle = pi/2) text(str, x, y) text(str, pos, halign = :left) text(str, valign = :baseline) text(str, valign = :baseline, halign = :left) text(str, pos, valign = :baseline, halign = :left) text(latexstr, pos, valign = :baseline, halign = :left, rotationfixed = false, angle = 0) Draw the text in the string str at x/y or pt, placing the start of the string at the point. If you omit the point, it's placed at the current 0/0. angle specifies the rotation of the text relative to the current x-axis. Horizontal alignment halign can be :left, :center, (also :centre) or :right. Vertical alignment valign can be :baseline, :top, :middle, or :bottom. The default alignment is :left, :baseline. This function uses Cairo's Toy text API. See also textextents(), settext(), label(). source Luxor.textMethod text(lstr::LaTeXString, pt::Point; valign=:baseline, halign=:left, rotationfixed = false, angle=0, paths=false, kwargs...) Draws LaTeX string using MathTexEngine.jl. Hence, uses ModernCMU as font family. When rotationfixed = true, the text will rotate around its own axis, instead of rotating around pt. If paths is true, text paths are added to the current path, rather than drawn. using Luxor using MathTeXEngine using LaTeXStrings @draw begin fontsize(70) text(L"e^{i\pi} + 1 = 0", halign=:center) end source Luxor.textboxFunction textbox(s::T where T <: AbstractString, pos::Point=O; leading = 12, linefunc::Function = (linenumber, linetext, startpos, height) -> (), alignment=:left) source Luxor.textboxFunction textbox(lines::Array, pos::Point=O; leading = 12, linefunc::Function = (linenumber, linetext, startpos, height) -> (), alignment=:left) Draw the strings in the array lines vertically downwards. leading controls the spacing between each line (default 12), and alignment determines the horizontal alignment (default :left). Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, height). Returns the position of what would have been the next line. See also textwrap(), which modifies the text so that the lines fit into a specified width. source Luxor.textcurveFunction textcurve(the_text, start_angle, start_radius, pos; # optional keyword arguments: spiral_ring_step = 0, # step out or in by this amount letter_spacing = 0, # tracking/space between chars, tighter is (-), looser is (+) spiral_in_out_shift = 0, # + values go outwards, - values spiral inwards clockwise = true ) Place a string of text on a curve. It can spiral in or out. start_angle is relative to +ve x-axis, arc/circle is centered on pos with radius start_radius. source Luxor.textcurvecenteredMethod textcurvecentered(the_text, the_angle, the_radius, center::Point; clockwise = true, letter_spacing = 0, baselineshift = 0 This version of the textcurve() function is designed for shorter text strings that need positioning around a circle. (A cheesy effect much beloved of hipster brands and retronauts.) letter_spacing adjusts the tracking/space between chars, tighter is (-), looser is (+)). baselineshift moves the text up or down away from the baseline. textcurvecentred (UK spelling) is a synonym. source Luxor.textextentsMethod textextents(str) Return an array of six Float64s containing the measurements of the string str when set using the current font settings (Toy API): 1 x_bearing 2 y_bearing 3 width 4 height 5 x_advance 6 y_advance The x and y bearings are the displacement from the reference point to the upper-left corner of the bounding box. It is often zero or a small positive value for x displacement, but can be negative x for characters like "j"; it's almost always a negative value for y displacement. The width and height then describe the size of the bounding box. The advance takes you to the suggested reference point for the next letter. Note that bounding boxes for subsequent blocks of text can overlap if the bearing is negative, or the advance is smaller than the width would suggest. Example: textextents("R") returns [1.18652; -9.68335; 8.04199; 9.68335; 9.74927; 0.0] source Luxor.textfitFunction textfit(str, bbox::BoundingBox, maxfontsize = 800; horizontalmargin=12, leading=100) Fit the string str into the bounding box bbox by adjusting the font size and line breaks. Instead of using the current font size, the largest possible value will be calculated. You can specify a size limit in maxfontsize, such that the text will never be larger than this value, although it may have to be smaller. horizontalmargin is applied to each side. Optionally, leading can be supplied, and this will be interpreted as a percentage of the final calculated font size. The default value is 110 (%). The function returns a named tuple with information about the calculated values: (fontsize = 37.6, linecount = 5, finalpos = Point(-117.43, 92.60) Note This function is in need of improvement. It's quite difficult to find out the height of a line of text in a specific font. (Unless we import FreeType.) Suggestions for improvements welcome! source Luxor.textlinesMethod textlines(s::T where T <: AbstractString, width::Real; rightgutter=5) Split the text in s into lines up to width units wide (in the current font). Returns an array of strings. Use textwrap to draw an array of strings. TODO: A rightgutter optional keyword adds some padding to the right hand side of the column. This appears to be needed sometimes -— perhaps the algorithm needs improving to take account of the interaction of textextents and spaces? source Luxor.textonpolyMethod textonpoly(str, pgon; tracking = 0, startoffset = 0.0, baselineshift = 0.0, closed = false) Draw the text in str along the route of the polygon in pgon. The closed option determines whether the final edge of the polygon (joining the last point to the first) is included or not. Eg if you want to draw a string around all three sides of a triangle, you'd use closed=true: textonpoly("mèdeis ageômetrètos eisitô mou tèn stegèn - let no one ignorant of geometry come under my roof ", ngon(O, 100, 3, vertices=true), closed=true) If false, only two sides are considered. Increase tracking from 0 to add space between the glyphs. The startoffset value is a normalized percentage that specifies the start position. So, to start drawing the text halfway along the polygon, specify a start offset value of 0.5. Positive values for baselineshift move the characters upwards from the baseline. Returns a tuple with the number of characters drawn, and the final value of the index, between 0.0 and 1.0. If the returned index value is less than 1, this means that the text supplied ran out before the end of the polygon was reached. source Luxor.textoutlinesMethod textoutlines(s::String, pos::Point=O; action=:none, halign=:left, valign=:baseline, startnewpath=true) Convert text to polygons and apply action. By default this function discards any current path, unless you use startnewpath=false See also textpath(). textpath() retains Bezier curves, whereas textoutlines() returns flattened curves. TODO Return something more useful than a Boolean. source Luxor.textpathMethod textpath(s::String, pos::Point; action=:none, halign=:left, valign=:baseline, startnewpath=true) Convert the text in string s to paths and apply the action. TODO Return something more useful than a Boolean. source Luxor.textpathMethod textpath(t) Convert the text in string t to paths, adding them to the current path, for subsequent filling/stroking etc... You can use pathtopoly() or getpath() or getpathflat() to convert the paths to polygons. See also textoutlines(). textpath() retains Bezier curves, whereas textoutlines() returns flattened curves. source Luxor.textplaceMethod textplace(txt::T where T <: AbstractString, pos::Point, params::Vector; action = :fill, startnewpath = false) A low-level function that places text characters one by one according to the parameters in params. First character uses the first tuple, second character uses the second, and so on. Returns the next text position. A tuple of parameters is: (face = "TimesRoman", size = 12, color=colorant"black", kern = 0, shift = 0, advance = true) where • face is fontface "string" # sticky • size is fontsize # pts # sticky • color is color # sticky • kern amount (pixels) shifted to the right # resets after each char • shift = baseline shifted vertically # resets after each char • advance - whether to advance # resets after each char Some parameters are "sticky": once set, they apply for all subsequent characters until a new value is supplied. Others aren't sticky, and are reset for each character. So font face, size, and color parameters need only be specified once, whereas kern/shift/advance modifiers are reset for each character. Example Draw the Hogwarts Express Platform number 9 and 3/4: txtpos = textplace("93—4!", O - (200, 0), [ # format for 9: (size=120, face="Bodoni-Poster", color=colorant"grey10"), # format for 3: (size=60, kern = 5, shift = 60, advance=false,), # format for -: ( kern = 0, shift = 25, advance=false,), # format for 4: ( kern = 5, shift = -20, advance=true), # format for !: (size=120, kern = 20,), ]) source Luxor.texttrackMethod texttrack(txt, pos, tracking; action=:fill, halign=:left, valign=:baseline, startnewpath=true) texttrack(txt, pos, tracking, fontsize; action=:fill, halign=:left, valign=:baseline, startnewpath=true) Place the text in txt at pos, left-justified, and letter space ('track') the text using the value in tracking. The tracking units depend on the current font size. In a 12‑point font, 1 em equals 12 points. A point is about 0.35mm, 1em is about 4.2mm, and a 1000 units of tracking are about 4.2mm. So a tracking value of 1000 for a 12 point font places about 4mm between each character. A negative value tightens the letter spacing noticeably. The text drawing action applied to each character defaults to textoutlines(... :fill). If startnewpath is true, each character is acted on separately. To clip and track text, specify the clip action and avoid resetting the clipping path for each character.  newpath() texttrack(t, O + (0, 80), 200, action=:clip, startnewpath=false) ... clipreset() TODO Is it possible to fix strings with combining characters such as "̈"? source Luxor.textwrapMethod textwrap(s::T where T<:AbstractString, width::Real, pos::Point; rightgutter=5, leading=0) textwrap(s::T where T<:AbstractString, width::Real, pos::Point, linefunc::Function; rightgutter=5, leading=0) Draw the string in s by splitting it at whitespace characters into lines, so that each line is no longer than width units. The text starts at pos such that the first line of text is drawn entirely below a line drawn horizontally through that position. Each line is aligned on the left side, below pos. See also textbox(). Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, leading). If you don't supply a value for leading, the font's built-in extents are used. Text with no whitespace characters won't wrap. You can write a simple chunking function to split a string or array into chunks: chunk(x, n) = [x[i:min(i+n-1,length(x))] for i in 1:n:length(x)] For example: textwrap(the_text, 300, boxtopleft(BoundingBox()) + 20, (ln, lt, sp, ht) -> begin c = count(t -> occursin(r"[[:punct:]]", t), split(lt, "")) @layer begin fontface("Menlo") sethue("darkred") text(string("[", c, "]"), sp + (310, 0)) end end) puts a count of the number of punctuation characters in each line at the end of the line. Returns the position of what would have been the next line. source Luxor.ticklineMethod tickline(startpos, finishpos; startnumber = 0, finishnumber = 1, major = 1, minor = 0, major_tick_function = nothing, minor_tick_function = nothing, rounding = 2, axis = true, # draw the line? log = false, vertices = false # just return the points ) Draw a line with ticks. major is the number of ticks required between the start and finish point. So 1 divides the line in half. minor is the number of ticks between each major tick. Examples tickline(Point(0, 0), Point(100, 0)) tickline(Point(0, 0), Point(100, 0), major = 4) majorticks, minorticks = tickline(Point(0, 0), Point(100, 0), axis=false) Custom ticks Supply functions to make custom ticks. Custom tick functions should have arguments as follows: function mtick(n, pos; startnumber = 0, finishnumber = 1, nticks = 1) ... and function mntick(n, pos; startnumber = 0, finishnumber = 1, nticks = 1, majorticklocations = []) ... For example: tickline(O - (300, 0), Point(300, 0), startnumber = -10, finishnumber = 10, minor = 0, major = 4, axis = false, major_tick_function = (n, pos; startnumber=30, finishnumber=40, nticks=10) -> begin @layer begin translate(pos) ticklength = get_fontsize() line(O, O + polar(ticklength, 3π/2), :stroke) k = rescale(n, 0, nticks - 1, startnumber, finishnumber) ticklength = get_fontsize() * 1.3 text("(round(k, digits=2))",
O + (0, ticklength),
halign=:center,
valign=:middle,
angle = -getrotation())
end
end)
source
Luxor.tidysvgMethod
tidysvg(fname)

Read the SVG image in fname and write it to a file fname-tidy.svg with modified glyph names.

Return the name of the modified file.

SVG images use named defs for text, which cause errors problem when used in a notebook. See for example.

A kludgy workround is to rename the elements...

As of Luxor 3.6 this is done elsewhere.

source
Luxor.transformMethod
transform(a::Array)

Modify the current matrix by multiplying it by matrix a.

For example, to skew the current state by 45 degrees in x and move by 20 in y direction:

transform([1, 0, tand(45), 1, 0, 20])

See getmatrix() for details.

source
Luxor.translateMethod
translate(point)
translate(x::Real, y::Real)

Translate the current workspace to x and y or to pt.

Values are relative to the current location.

source
Luxor.translationmatrixMethod
translationmatrix(x, y)

Return a 3x3 Julia matrix that will apply a translation in x and y.

See getmatrix() for details.

source
Luxor.trianglecenterMethod
trianglecenter(pt1::Point, pt2::Point, pt3::Point)

Return the centroid of the triangle defined by pt1, pt2, and pt3.

source
Luxor.trianglecircumcenterMethod
trianglecircumcenter(pt1::Point, pt2::Point, pt3::Point)

Return the circumcenter of the triangle defined by pt1, pt2, and pt3. The circumcenter is the center of a circle that passes through the vertices of the triangle.

source
Luxor.triangleincenterMethod
triangleincenter(pt1::Point, pt2::Point, pt3::Point)

Return the incenter of the triangle defined by pt1, pt2, and pt3. The incenter is the center of a circle inscribed inside the triangle.

source
Luxor.triangleorthocenterMethod
triangleorthocenter(pt1::Point, pt2::Point, pt3::Point)

Return the orthocenter of the triangle defined by pt1, pt2, and pt3.

source
Luxor.trimbezierMethod
trimbezier(bps::BezierPathSegment, lowpt, highpt)

Chop the ends of a BezierPathSegment at lowpt and highpt. lowpt and highpt should be between 0 and 1.

Returns a trimmed BezierPathSegment.

source
Luxor.BezierPathSegmentType

BezierPathSegment is an array of four points:

p1 - start point cp1 - control point for start point cp2 - control point for finishpoint p2 - finish point

source
Luxor.BoundingBoxMethod
BoundingBox(str::AbstractString)

Return a BoundingBox that just encloses a text string, given the current font selection. Uses the Toy text API (ie text() and textextents()).

Text is assumed to be placed at the origin (0/0).

source
Luxor.BoundingBoxMethod
BoundingBox(t::table, rownumber, columnnumber)

Return a BoundingBox that encloses cell at row rownumber, column colnumber of table t.

source
Luxor.BoundingBoxMethod
BoundingBox(t::table, cell)

Return a BoundingBox that encloses cell cell of table t.

source
Luxor.BoundingBoxMethod
BoundingBox(t::Tiler, r, c)

Return the Bounding Box enclosing the tile at row r column c.

source
Luxor.BoundingBoxMethod

BoundingBox() with no arguments returns a BoundingBox that includes the current drawing.

The default BoundingBox(;centered=true) returns a BoundingBox the same size and position as the current drawing, and assumes the origin (0, 0) is at the center.

If the centered option is false, the function assumes that the origin is at the top left of the drawing. So this function doesn't really work if the current matrix has been modified (by translate(), scale(), rotate() etc.)

An instance of the BoundingBox type holds two Points, corner1 and corner2.

BoundingBox(;centered = true) # the bounding box of the Drawing

BoundingBox(s::AbstractString) # the bounding box of a text string at the origin

BoundingBox(pt::Array) # the bounding box of a polygon

BoundingBox(circle(O, 100)) # the bounding box of a path added by circle()

BoundingBox(path::Path) # the bounding box of a Path

You can use BoundingBox() with the functions that add graphic shapes to the current path (eg box(), circle(), star(), ngon()). But note that eg BoundingBox(box(O, 100, 100)) adds a shape to the current path as well as returning a bounding box.

source
Luxor.DrawingType

Create a new drawing, and optionally specify file type (PNG, PDF, SVG, EPS), file-based or in-memory, and dimensions.

Drawing(width=600, height=600, file="luxor-drawing.png")

Extended help

Drawing()

creates a drawing, defaulting to PNG format, default filename "luxor-drawing.png", default size 800 pixels square.

You can specify dimensions, and assume the default output filename:

Drawing(400, 300)

creates a drawing 400 pixels wide by 300 pixels high, defaulting to PNG format, default filename "luxor-drawing.png".

Drawing(400, 300, "my-drawing.pdf")

creates a PDF drawing in the file "my-drawing.pdf", 400 by 300 pixels.

Drawing(1200, 800, "my-drawing.svg")

creates an SVG drawing in the file "my-drawing.svg", 1200 by 800 pixels.

Drawing(width, height, surfacetype | filename)

creates a new drawing of the given surface type (e.g. :svg, :png), storing the picture only in memory if no filename is provided.

Drawing(1200, 1200/Base.Mathconstants.golden, "my-drawing.eps")

creates an EPS drawing in the file "my-drawing.eps", 1200 wide by 741.8 pixels (= 1200 ÷ ϕ) high. Only for PNG files must the dimensions be integers.

Drawing("A4", "my-drawing.pdf")

creates a drawing in ISO A4 size (595 wide by 842 high) in the file "my-drawing.pdf". Other sizes available are: "A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "A", "B", "C", "D", "E". Append "landscape" to get the landscape version.

Drawing("A4landscape")

creates the drawing A4 landscape size.

PDF files default to a white background, but PNG defaults to transparent, unless you specify one using background().

Drawing(width, height, :image)

creates the drawing in an image buffer in memory. You can obtain the data as a matrix with image_as_matrix().

Drawing(width, height, :rec)

creates the drawing in a recording surface in memory. snapshot(fname, ...) to any file format and bounding box, or render as pixels with image_as_matrix().

Drawing(width, height, strokescale=true)

creates the drawing and enables stroke scaling (strokes will be scaled according to the current transformation). (Stroke scaling is disabled by default.)

Drawing(img, strokescale=true)

creates the drawing from an existing image buffer of type Matrix{Union{RGB24,ARGB32}}, e.g.:

using Luxor, Colors
buffer=zeros(ARGB32, 100, 100)
d=Drawing(buffer)
source
Luxor.GridHexType
GridHex(startpoint, radius, width=1200.0, height=1200.0)

Define a hexagonal grid, to start at startpoint and proceed along the x-axis and then along the y-axis, radius is the radius of a circle that encloses each hexagon. The distance in x between the centers of successive hexagons is:

$\frac{\sqrt{(3)} radius}{2}$

To get the next point from the grid, use nextgridpoint(g::Grid).

When you run out of grid points, you'll wrap round and start again.

source
Luxor.GridRectType
GridRect(startpoint, xspacing, yspacing, width, height)

Define a rectangular grid, to start at startpoint and proceed along the x-axis in steps of xspacing, then along the y-axis in steps of yspacing.

GridRect(startpoint, xspacing=100.0, yspacing=100.0, width=1200.0, height=1200.0)

For a column, set the xspacing to 0:

grid = GridRect(O, 0, 40)

To get points from the grid, use nextgridpoint(g::Grid).

julia> grid = GridRect(O, 0, 40);
julia> nextgridpoint(grid)
Luxor.Point(0.0, 0.0)

julia> nextgridpoint(grid)
Luxor.Point(0.0, 40.0)

When you run out of grid points, you'll wrap round and start again.

source
Luxor.MovieType

The Movie and Scene types and the animate() function are designed to help you create the frames that can be used to make an animated GIF or movie.

1 Provide width, height, title, and optionally a frame range to the Movie constructor:

demo = Movie(400, 400, "test", 1:500)

2 Define one or more scenes and scene-drawing functions.

3 Run the animate() function, calling those scenes.

Example

bang = Movie(400, 100, "bang")

background("white")
sethue("black")
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
circle(O, 40 * eased_n, :fill)
end

animate(bang, [
Scene(bang, backdrop, 0:200),
Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
creategif=true,
pathname="/tmp/animationtest.gif")

source
Luxor.MovieMethod
Movie(width, height, movietitle)

Define a movie, specifying the width, height, and a title. The title will be used to make the output file name. The range defaults to 1:250.

source
Luxor.PartitionType
p = Partition(areawidth, areaheight, tilewidth, tileheight)

A Partition is an iterator that, for each iteration, returns a tuple of:

• the x/y point of the center of each tile in a set of tiles that divide up a rectangular space such as a page into rows and columns (relative to current 0/0)

• the number of the tile

areawidth and areaheight are the dimensions of the area to be tiled, tilewidth/tileheight are the dimensions of the tiles.

Tiler and Partition are similar:

• Partition lets you specify the width and height of a cell

• Tiler lets you specify how many rows and columns of cells you want, and a margin

tiles = Partition(1200, 1200, 30, 30) for (pos, n) in tiles

# the point pos is the center of the tile

end

You can access the calculated tile width and height like this:

tiles = Partition(1200, 1200, 30, 30)
for (pos, n) in tiles
ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end

It's sometimes useful to know which row and column you're currently on:

tiles.currentrow
tiles.currentcol

should have that information for you.

Unless the tilewidth and tileheight are exact multiples of the area width and height, you'll see a border at the right and bottom where the tiles won't fit.

source
Luxor.PathType

A Path object contains, in the .path field, a vector of PathElements (PathCurve, PathMove, PathLine, PathClose) that describe a Cairo path. Use drawpath() to draw it.

Path([PathMove(Point(2.0, 90.5625)),
PathCurve(Point(4.08203, 68.16015), Point(11.28, 45.28), Point(24.8828, 26.40234)),
PathLine(Point(2.0, 90.5625)),
PathClose()
])
source
Luxor.PathMethod
Path(ptlist::Vector{Point}; close=false))

Create a Path from the points in ptlist.

source
Luxor.SceneType

The Scene type defines a function to be used to render a range of frames in a movie.

• the movie created by Movie()
• the framefunction is a function taking two arguments: the scene and the framenumber.
• the framerange determines which frames are processed by the function. Defaults to the entire movie.
• the optional easingfunction can be accessed by the framefunction to vary the transition speed
• the optional opts which is a single argument of an abstract type which can be accessed within the framefunction
source
Luxor.SceneMethod
Scene(movie, function, range;
optarg=nothing)

Use the Scene() constructor function to create a scene. Supply a movie, a function to generate the scene, and a range of frames. Optionally you can supply an easing function, and other information, in optarg, which can be accessed as scene.opts.

Example

function initial(scene, framenumber)
balls = scene.opts
...
end

animate(poolmovie, [
Scene(poolmovie, initial, optarg=balls,   1:20),
...
])

To use an easing function inside the frame-generating function, you can create a normalized value with, for example:

eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)

Or, if the scene doesn't start at frame 1, calculate normalized easing function like this:

eased_n = scene.easingfunction(framenumber - scene.framerange.start,
0, 1, scene.framerange.stop - scene.framerange.start)
source
Luxor.TableType
t = Table(nrows, ncols)
t = Table(nrows, ncols, colwidth, rowheight)
t = Table(rowheights, columnwidths)

Tables are centered at O, but you can supply a point after the specifications.

t = Table(nrows, ncols, centerpoint)
t = Table(nrows, ncols, colwidth, rowheight, centerpoint)
t = Table(rowheights, columnwidths, centerpoint)

Examples

Simple tables

t = Table(4, 3) # 4 rows and 3 cols, default is 100w, 50 h
t = Table(4, 3, 80, 30)   # 4 rows of 30pts high, 3 cols of 80pts wide
t = Table(4, 3, (80, 30)) # same
t = Table((4, 3), (80, 30)) # same

Specify row heights and column widths instead of quantities:

t = Table([60, 40, 100], 50) # 3 different height rows, 1 column 50 wide
t = Table([60, 40, 100], [100, 60, 40]) # 3 rows, 3 columns
t = Table(fill(30, (10)), [50, 50, 50]) # 10 rows 30 high, 3 columns 10 wide
t = Table(50, [60, 60, 60]) # just 1 row (50 high), 3 columns 60 wide
t = Table([50], [50]) # just 1 row, 1 column, both 50 units wide
t = Table(50, 50, 10, 5) # 50 rows, 50 columns, 10 units wide, 5 units high
t = Table([6, 11, 16, 21, 26, 31, 36, 41, 46], [6, 11, 16, 21, 26, 31, 36, 41, 46])
t = Table(15:5:55, vcat(5:2:15, 15:-2:5))
#  table has 108 cells, with:
#  row heights: 15 20 25 30 35 40 45 50 55
#  col widths:  5 7 9 11 13 15 15 13 11 9 7 5
t = Table(vcat(5:10:60, 60:-10:5), vcat(5:10:60, 60:-10:5))
t = Table(vcat(5:10:60, 60:-10:5), 50) # 1 column 50 units wide
t = Table(vcat(5:10:60, 60:-10:5), 1:5:50)

A Table is an iterator that, for each iteration, returns a tuple of:

• the x/y point of the center of cells arranged in rows and columns (relative to current 0/0)

• the number of the cell (left to right, then top to bottom)

nrows/ncols are the number of rows and columns required.

It's sometimes useful to know which row and column you're currently on while iterating:

t.currentrow
t.currentcol

and row heights and column widths are available in:

t.rowheights
t.colwidths

box(t::Table, r, c) can be used to fill table cells:

@svg begin
for (pt, n) in (t = Table(8, 3, 30, 15))
randomhue()
box(t, t.currentrow, t.currentcol, :fill)
sethue("white")
text(string(n), pt)
end
end

or without iteration, using cellnumber:

@svg begin
t = Table(8, 3, 30, 15)
for n in eachindex(t)
randomhue()
box(t, n, :fill)
sethue("white")
text(string(n), t[n])
end
end

To use a Table to make grid points:

julia> first.(collect(Table(10, 6)))
60-element Array{Luxor.Point,1}:
Luxor.Point(-10.0, -18.0)
Luxor.Point(-6.0, -18.0)
Luxor.Point(-2.0, -18.0)
⋮
Luxor.Point(2.0, 18.0)
Luxor.Point(6.0, 18.0)
Luxor.Point(10.0, 18.0)

which returns an array of points that are the center points of the cells in the table.

source
Luxor.TilerType
tiles = Tiler(areawidth, areaheight, nrows, ncols, margin=20)

A Tiler is an iterator that, for each iteration, returns a tuple of:

• the x/y point of the center of each tile in a set of tiles that divide up a rectangular space such as a page into rows and columns (relative to current 0/0)

• the number of the tile

areawidth and areaheight are the dimensions of the area to be tiled, nrows/ncols are the number of rows and columns required, and margin is applied to all four edges of the area before the function calculates the tile sizes required.

Tiler and Partition are similar:

• Partition lets you specify the width and height of a cell

• Tiler lets you specify how many rows and columns of cells you want, and a margin:

tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
# the point pos is the center of the tile
end

You can access the calculated tile width and height like this:

tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end

It's sometimes useful to know which row and column you're currently on. tiles.currentrow and tiles.currentcol should have that information for you.

To use a Tiler to make grid points:

first.(collect(Tiler(800, 800, 4, 4)))

which returns an array of points that are the center points of the grid.

source
Luxor.TurtleType
Turtle()
Turtle(O)
Turtle(0, 0)
Turtle(O, pendown=true, orientation=0, pencolor=(1.0, 0.25, 0.25))

Create a Turtle. You can command a turtle to move and draw "turtle graphics".

The commands (unusually for Julia) start with a capital letter, and angles are specified in degrees.

Basic commands are Forward(), Turn(), Pendown(), Penup(), Pencolor(), Penwidth(), Circle(), Orientation(), Rectangle(), and Reposition().

Others include Push(), Pop(), Message(), HueShift(), Randomize_saturation(), Reposition(), and Pen_opacity_random().

source
Luxor.paper_sizesConstant
paper_sizes

The paper_sizes Dictionary holds a few paper sizes, width is first, so default is Portrait:

"A0"      => (2384, 3370),
"A1"      => (1684, 2384),
"A2"      => (1191, 1684),
"A3"      => (842, 1191),
"A4"      => (595, 842),
"A5"      => (420, 595),
"A6"      => (298, 420),
"A"       => (612, 792),
"Letter"  => (612, 792),
"Legal"   => (612, 1008),
"Ledger"  => (792, 1224),
"B"       => (612, 1008),
"C"       => (1584, 1224),
"D"       => (2448, 1584),
"E"       => (3168, 2448))
source