summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/tex/latex/luatikz/luatikz.lua
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2024-12-09 21:18:16 +0000
committerKarl Berry <karl@freefriends.org>2024-12-09 21:18:16 +0000
commitbd01b8f1508b38fabf50da4ad8fbac6497697ba5 (patch)
tree515068e80d87595ecb606d7dbb3d4003b00407ed /Master/texmf-dist/tex/latex/luatikz/luatikz.lua
parente4c71587aa7865ccd8d4c83a4daf3d1b04c6da6c (diff)
luatikz (9dec24)
git-svn-id: svn://tug.org/texlive/trunk@73087 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/tex/latex/luatikz/luatikz.lua')
-rw-r--r--Master/texmf-dist/tex/latex/luatikz/luatikz.lua1999
1 files changed, 1999 insertions, 0 deletions
diff --git a/Master/texmf-dist/tex/latex/luatikz/luatikz.lua b/Master/texmf-dist/tex/latex/luatikz/luatikz.lua
new file mode 100644
index 00000000000..1c18f2f6278
--- /dev/null
+++ b/Master/texmf-dist/tex/latex/luatikz/luatikz.lua
@@ -0,0 +1,1999 @@
+local _version = '2.12.0'
+
+if tikz
+then
+ if tikz.version ~= _version
+ then
+ error( 'version chaos' )
+ end
+ return
+end
+
+tikz = { version = _version }
+
+--==========================================================================--
+-- DEFINES
+--==========================================================================--
+
+-- directions
+tikz.north = 'north'
+tikz.north_east = 'north east'
+tikz.east = 'east'
+tikz.south_east = 'south east'
+tikz.south = 'south'
+tikz.south_west = 'south west'
+tikz.west = 'west'
+tikz.north_west = 'north west'
+
+-- colors
+tikz.black = 'black'
+tikz.blue = 'blue'
+tikz.brown = 'brown'
+tikz.cyan = 'cyan'
+tikz.darkgray = 'darkgray'
+tikz.gray = 'gray'
+tikz.green = 'green'
+tikz.lightgray = 'lightgray'
+tikz.lime = 'lime'
+tikz.magenta = 'magenta'
+tikz.olive = 'olive'
+tikz.orange = 'orange'
+tikz.pink = 'pink'
+tikz.purple = 'purple'
+tikz.red = 'red'
+tikz.teal = 'teal'
+tikz.violet = 'violet'
+tikz.yellow = 'yellow'
+tikz.white = 'white'
+
+-- align
+tikz.center = 'center'
+tikz.left = 'left'
+tikz.right = 'right'
+
+tikz.none = 'none'
+
+-- line caps
+tikz.round = 'round'
+
+--==========================================================================--
+-- HELPERS --
+--==========================================================================--
+
+-- if defined writes output here
+local outfile
+
+--
+-- Prints arguments to tex and to stdout.
+--
+local function tprint( ... )
+ local args = {...}
+ print( table.concat( args ) )
+
+ if tex
+ then
+ tex.print( table.concat( args ) )
+ end
+
+ if outfile
+ then
+ outfile:write( table.concat( args ) )
+ outfile:write( '\n%%% DO NOT EDIT THIS FILE %%%\n' )
+ end
+end
+tikz.tprint = tprint
+
+--
+-- Flattens an object stack.
+--
+-- The content of all numerated unclassed objects
+-- are put into a copy of obj.
+--
+local function flatten( o )
+ local r = { }
+ for _, v in ipairs( o )
+ do
+ if type( v ) == 'table' and getmetatable( v ) == nil
+ then
+ local fv = flatten( v )
+ for _, fvv in ipairs( fv )
+ do
+ table.insert( r, fvv )
+ end
+ for k, vv in pairs( fv )
+ do
+ if type( k ) ~= 'number'
+ then
+ r[ k ] = vv
+ end
+ end
+ else
+ table.insert( r, v )
+ end
+ end
+ -- copies (and overwrites) all named attributes
+ for k, v in pairs( o )
+ do
+ if type( k ) ~= 'number'
+ then
+ r[ k ] = v
+ end
+ end
+ return r
+end
+
+--
+-- A key marking an object having a zString function.
+--
+local _zString = { }
+
+--
+-- Converts everything to a tikz string.
+--
+local function zString( v )
+ if type( v ) == 'table' and v[ _zString ]
+ then
+ return v[ _zString ]( v )
+ end
+ return tostring( v )
+end
+
+--
+-- Creates an immutable class.
+--
+local function immutable( definer )
+ local def = { }
+ local lazy = { }
+ local proto = { }
+ def.lazy = lazy
+ def.proto = proto
+ definer( def ) -- the callee fills in data
+ local k = { } -- key the hidden table
+
+ local mt = { }
+ mt.__index =
+ function( self, key )
+ if key == 'id' then return def.id end
+
+ -- first check if native value
+ local v = self[ k ][ key ]
+ if v then return v end
+
+ -- then if a lazy function
+ local f = lazy[ key ]
+ if f
+ then
+ local v = f( self )
+ self[ k ][ key ] = v -- cache
+ return v
+ end
+
+ -- then if a proto value
+ v = proto[ key ]
+ if v
+ then
+ if type( v ) == 'function'
+ then
+ return(
+ function(...)
+ return v( self, table.unpack( {...} ) )
+ end
+ )
+ else
+ return v
+ end
+ end
+
+ -- otherwise
+ -- error( 'invalid property' )
+ end
+
+ mt.__newindex =
+ function( )
+ error( 'this is an immutable' )
+ end
+
+ if def.add
+ then
+ mt.__add = def.add
+ end
+
+ if def.concat
+ then
+ mt.__concat = def.concat
+ end
+
+ if def.div
+ then
+ mt.__div = def.div
+ end
+
+ if def.mul
+ then
+ mt.__mul = def.mul
+ end
+
+ if def.sub
+ then
+ mt.__sub = def.sub
+ end
+
+ if def.tostring
+ then
+ mt.__tostring = def.tostring
+ end
+
+ return function(...)
+ local args = {...}
+ local nt = def.constructor( table.unpack( args ) )
+ local o = { [ k ] = nt, [ _zString ] = def.zString }
+ setmetatable( o, mt )
+ return o
+ end
+end
+tikz.immutable = immutable
+
+--
+-- writes out direct options
+--
+local function _opts( s, _opts, haveOpts )
+ if not _opts
+ then
+ return haveOpts
+ end
+
+ for k, v in pairs( _opts )
+ do
+ if not haveOpts
+ then
+ table.insert( s, '[' )
+ else
+ table.insert( s, ',' )
+ end
+ haveOpts = true
+ if type( k ) == 'number'
+ then
+ table.insert( s, v )
+ else
+ table.insert( s, k..'='..v )
+ end
+ end
+ return haveOpts
+end
+
+-------------------------------------------------------------------------------
+-- Point.
+-------------------------------------------------------------------------------
+tikz.p =
+immutable( function( def )
+ def.id = 'point'
+
+ def.add =
+ function( left, right )
+ if right.id == 'line'
+ or right.id == 'bezier3'
+ then
+ return right + left
+ end
+
+ return tikz.p{ left.x + right.x, left.y + right.y }
+ end
+
+ def.concat =
+ function( left, right )
+ return zString( left )..zString( right )
+ end
+
+ -- constructor
+ def.constructor =
+ function( args )
+ return { x = args[ 1 ], y = args[ 2 ] }
+ end
+
+ -- divisor
+ def.div =
+ function( left, right )
+ return tikz.p{ left.x / right, left.y / right }
+ end
+
+ def.mul =
+ function( left, right )
+ if type( right ) == 'number'
+ then
+ return tikz.p{ left.x * right, left.y * right }
+ elseif type( left ) == 'number'
+ then
+ return tikz.p{ left * right.x, left * right.y }
+ elseif type( right ) == 'table'
+ then
+ if right.id == 'point'
+ then
+ return tikz.p{ left.x * right.x, left.y * right.y }
+ end
+ end
+ error( 'invalid operation' )
+ end
+
+ def.sub =
+ function( left, right )
+ return tikz.p{ left.x - right.x, left.y - right.y }
+ end
+
+ def.tostring =
+ function( self )
+ return 'p{ '..self.x..', '..self.y..' }'
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ return '('..self.x..','..self.y..')'
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A full circle.
+-------------------------------------------------------------------------------
+tikz.circle =
+immutable( function( def )
+ def.id = 'circle'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ return {
+ at = args.at,
+ radius = args.radius,
+ }
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ return zString( self.at )..' circle ('.. self.radius .. ')'
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A quadratic bezier curve.
+-------------------------------------------------------------------------------
+tikz.bezier2 =
+immutable( function( def )
+ def.id = 'bezier2'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local p1 = args.p1
+ local pc = args.pc
+ local p2 = args.p2
+
+ return {
+ p1 = p1,
+ pc = pc,
+ p2 = p2,
+ }
+ end
+
+ -- draws helping information
+ def.proto.drawHelpers =
+ function( self, prefix )
+ tikz.draw{ draw = 'red', tikz.line{ self.p1, self.pc } }
+ tikz.draw{ draw = 'blue', tikz.line{ self.pc, self.p2 } }
+ if prefix ~= nil
+ then
+ tikz.put{ tikz.node{
+ at = self.p1,
+ anchor = 'north west',
+ text = prefix .. '1',
+ } }
+ tikz.put{ tikz.node{
+ at = self.pc,
+ anchor = 'north west',
+ text = prefix .. 'c',
+ } }
+ tikz.put{ tikz.node{
+ at = self.p2,
+ anchor = 'north west',
+ text = prefix .. '2',
+ } }
+ end
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { }
+ table.insert( s, zString( self.p1 ) )
+ table.insert( s, '.. controls' )
+ table.insert( s, zString( self.pc ) )
+ table.insert( s, '..' )
+ table.insert( s, zString( self.p2 ) )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A cubic bezier curve.
+-------------------------------------------------------------------------------
+tikz.bezier3 =
+immutable( function( def )
+ def.id = 'bezier3'
+
+ def.add =
+ function( left, right )
+ if right.id == 'point'
+ then
+ return tikz.bezier3{
+ p1 = left.p1 + right,
+ pc1 = left.pc1 + right,
+ pc2 = left.pc2 + right,
+ p2 = left.p2 + right,
+ }
+ end
+ error( 'unknown operation' )
+ end
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local p1 = args.p1
+ local pc1 = args.pc1
+ local pc2 = args.pc2
+ local p2 = args.p2
+
+ return {
+ p1 = p1,
+ pc1 = pc1,
+ pc2 = pc2,
+ p2 = p2,
+ }
+ end
+
+ -- draws helping information
+ def.proto.drawHelpers =
+ function( self, prefix )
+ tikz.draw{ draw = 'red', tikz.line{ self.p1, self.pc1 } }
+ tikz.draw{ draw = 'yellow', tikz.line{ self.pc1, self.pc2 } }
+ tikz.draw{ draw = 'blue', tikz.line{ self.pc2, self.p2 } }
+ if prefix ~= nil
+ then
+ tikz.put{ tikz.node{
+ at = self.p1,
+ anchor = 'north west',
+ text = prefix .. '1',
+ } }
+ tikz.put{ tikz.node{
+ at = self.pc1,
+ anchor = 'north west',
+ text = prefix .. 'c1',
+ } }
+ tikz.put{ tikz.node{
+ at = self.pc2,
+ anchor = 'north west',
+ text = prefix .. 'c2',
+ } }
+ tikz.put{ tikz.node{
+ at = self.p2,
+ anchor = 'north west',
+ text = prefix .. '2',
+ } }
+ end
+ end
+
+ def.mul =
+ function( left, right )
+ if type( right ) == 'number'
+ then
+ return tikz.bezier3 {
+ p1 = left.p1 * right,
+ pc1 = left.pc1 * right,
+ pc2 = left.pc2 * right,
+ p2 = left.p2 * right,
+ }
+ elseif type( left ) == 'number'
+ then
+ return tikz.bezier3 {
+ p1 = right.p1 * left,
+ pc1 = right.pc1 * left,
+ pc2 = right.pc2 * left,
+ p2 = right.p2 * left,
+ }
+ else
+ error( 'invalid operation' )
+ end
+ end
+
+ -- point in center
+ def.lazy.pc =
+ function( self )
+ return self.pt( 0.5 )
+ end
+
+ -- gets angle at t (0-1)
+ def.proto.phit =
+ function( self, t )
+ local p1 = self.p1
+ local pc1 = self.pc1
+ local pc2 = self.pc2
+ local p2 = self.p2
+
+ local u = 1 - t
+ local uu3 = 3 * u * u
+ local ut6 = 6 * u * t
+ local tt3 = 3 * t * t
+
+ return math.atan2(
+ -uu3 * p1.y + uu3 * pc1.y - ut6 * pc1.y + ut6 * pc2.y - tt3 * pc2.y + tt3 * p2.y,
+ -uu3 * p1.x + uu3 * pc1.x - ut6 * pc1.x + ut6 * pc2.x - tt3 * pc2.x + tt3 * p2.x
+ )
+ end
+
+ -- reverts p1 and p2 and pc1 and pc2 respectively
+ def.lazy.revert =
+ function( self )
+ return tikz.bezier3{
+ p1 = self.p2,
+ pc1 = self.pc2,
+ pc2 = self.pc1,
+ p2 = self.p1,
+ }
+ end
+
+ -- gets point at t (0-1)
+ def.proto.pt =
+ function( self, t )
+ if type( t ) ~= 'number' or t < 0 or t > 1
+ then
+ error( 'invalid pt' )
+ end
+
+ local p1 = self.p1
+ local pc1 = self.pc1
+ local pc2 = self.pc2
+ local p2 = self.p2
+ local t1 = 1 - t
+
+ return(
+ tikz.p{
+ t1^3*p1.x + 3*t*t1^2*pc1.x + 3 *t^2*t1*pc2.x + t^3*p2.x,
+ t1^3*p1.y + 3*t*t1^2*pc1.y + 3 *t^2*t1*pc2.y + t^3*p2.y
+ }
+ )
+ end
+
+ def.sub =
+ function( left, right )
+ if right.id == 'point'
+ then
+ return tikz.bezier3{
+ p1 = left.p1 - right,
+ pc1 = left.pc1 - right,
+ pc2 = left.pc2 - right,
+ p2 = left.p2 - right,
+ }
+ end
+ error( 'unknown operation' )
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { }
+ table.insert( s, zString( self.p1 ) )
+ table.insert( s, '.. controls' )
+ table.insert( s, zString( self.pc1 ) )
+ table.insert( s, 'and' )
+ table.insert( s, zString( self.pc2 ) )
+ table.insert( s, '..' )
+ table.insert( s, zString( self.p2 ) )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A bend line
+-------------------------------------------------------------------------------
+tikz.bline =
+immutable( function( def )
+ def.id = 'bline'
+
+ -- moves the line.
+ def.add =
+ function( left, right )
+ if right.id == 'point'
+ then
+ return tikz.line{ p1 = left.p1 + right, p2 = left.p2 + right }
+ end
+ error( 'unknown operation' )
+ end
+
+ -- constructor
+ --
+ -- either
+ -- { p1, p2 }, (a list )
+ -- { p1 = ..., p2 = ... },
+ -- { pc = ..., phi = ..., length = ... },
+ def.constructor =
+ function( args )
+ local p1
+ local p2
+
+ if #args > 1
+ then
+ -- list
+ p1 = args[ 1 ]
+ p2 = args[ 2 ]
+ elseif type( args.p1 ) ~= 'nil' and type( args.p2 ) ~= 'nil'
+ then
+ p1 = args.p1
+ p2 = args.p2
+ elseif args.phi ~= nil
+ then
+ local phi = args.phi
+ if args.pc ~= nil
+ then
+ if args.p1 ~= nil
+ or args.p2 ~= nil
+ then
+ error( 'invalid line options', 2 )
+ end
+ local pc = args.pc
+ local len05 = args.length / 2
+ p1 = pc + tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
+ p2 = pc - tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
+ elseif type( args.p1 ) ~= 'nil'
+ then
+ if args.pc ~= nil
+ or args.p2 ~= nil
+ then
+ error( 'invalid line options', 2 )
+ end
+ p1 = args.p1
+ p2 = p1 + tikz.p{ math.cos( phi ), math.sin( phi ) } * args.length
+ else
+ error( 'invalid line options', 2 )
+ end
+ else
+ error( 'invalid line options', 2 )
+ end
+
+ return {
+ bend_left = args.bend_left,
+ bend_right = args.bend_right,
+ p1 = p1,
+ p2 = p2,
+ }
+ end
+
+ -- converts to a cubic bezier (that should look the same)
+ def.lazy.bezier3 =
+ function( self )
+ local phi
+ if self.bend_right ~= nil then
+ phi = self.bend_right * math.pi / 180
+ else
+ phi = -self.bend_left * math.pi / 180
+ end
+ local line = self.line
+ local phil = line.phi
+ local len = line.length * 0.3915
+ local lt1 =
+ tikz.line{
+ p1 = line.p1,
+ phi = phil-phi,
+ length = len
+ }
+ local lt2 =
+ tikz.line{
+ p1 = line.p2,
+ phi = phil+phi+math.pi,
+ length = len
+ }
+
+ return tikz.bezier3{
+ p1 = line.p1,
+ pc1 = lt1.p2,
+ pc2 = lt2.p2,
+ p2 = line.p2,
+ }
+ end
+
+ -- converts to a straight line.
+ def.lazy.line =
+ function( self )
+ return tikz.line{
+ p1 = self.p1,
+ p2 = self.p2,
+ }
+ end
+
+ -- point in center
+ def.lazy.pc =
+ function( self )
+ return self.bezier3.pc
+ end
+
+ -- gets point at t (0-1).
+ def.proto.pt =
+ function( self, t )
+ return self.bezier3.pt( t )
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { }
+ table.insert( s, zString( self.p1 ) )
+ table.insert( s, 'to' )
+ if self.bend_left ~= nil
+ then
+ table.insert( s, '[bend left='..self.bend_left..']' )
+ end
+ if self.bend_right ~= nil
+ then
+ table.insert( s, '[bend right='..self.bend_right..']' )
+ end
+ table.insert( s, zString( self.p2 ) )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A curve is defined by a list of points.
+-------------------------------------------------------------------------------
+curve =
+immutable( function( def )
+ def.id = 'curve'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ return {
+ points = args.points,
+ tension = args.tension,
+ cycle = args.cycle,
+ }
+ end
+
+ -- draws helping information
+ def.proto.drawHelpers =
+ function( self, prefix )
+ local points = self.points
+ for i, pi in ipairs( points )
+ do
+ tikz.draw{
+ fill = 'black',
+ tikz.circle{
+ at = pi,
+ radius = 0.05,
+ }
+ }
+ if prefix ~= nil
+ then
+ tikz.put{ tikz.node{
+ at = pi,
+ anchor = 'north west',
+ text = prefix .. i,
+ } }
+ end
+ end
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { 'plot' }
+ table.insert( s, '[' )
+ if self.cycle
+ then
+ table.insert( s, 'smooth cycle' )
+ else
+ table.insert( s, 'smooth' )
+ end
+ if self.tension then
+ table.insert( s, ',tension='..self.tension )
+ end
+ table.insert( s, ']' )
+ local points = self.points
+ table.insert( s, 'coordinates' )
+ table.insert( s, '{' )
+ for _, p in ipairs( points )
+ do
+ table.insert( s, zString( p ) )
+ end
+ table.insert( s, '}' )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A full ellipse.
+-------------------------------------------------------------------------------
+tikz.ellipse =
+immutable( function( def )
+ def.id = 'ellipse'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local at = args.at
+ local xradius = args.xradius
+ local yradius = args.yradius
+
+ return {
+ at = at,
+ xradius = xradius,
+ yradius = yradius,
+ }
+ end
+
+ -- intersect with a line.
+ def.proto.intersectLine =
+ function( self, line )
+ local at = self.at
+ local rx = self.xradius
+ local ry = self.yradius
+ local p1 = line.p1
+ local p2 = line.p2
+
+ local x0 = p1.x
+ local y0 = p1.y
+ local x1 = p2.x
+ local y1 = p2.y
+ local cx = at.x
+ local cy = at.y
+
+ x0 = x0 - cx
+ y0 = y0 - cy
+ x1 = x1 - cx
+ y1 = y1 - cy
+
+ local A = ((x1 - x0) * (x1 - x0)) / rx / rx + ((y1 - y0) * (y1 - y0)) / ry / ry
+ local B = (2 * x0 * (x1 - x0)) / rx / rx + (2 * y0 * (y1 - y0)) / ry / ry
+ local C = (x0 * x0) / rx / rx + (y0 * y0) / ry / ry - 1
+ local D = B * B - 4 * A * C
+
+ local tv = { }
+ if D == 0
+ then
+ table.insert( tv, -B / 2 / A )
+ else
+ table.insert( tv, (-B + math.sqrt(D)) / 2 / A )
+ table.insert( tv, (-B - math.sqrt(D)) / 2 / A )
+ end
+
+ local r = { }
+ for _, t in ipairs( tv )
+ do
+ if t >= 0 and t <= 1
+ then
+ table.insert(
+ r,
+ p1 + tikz.p{ t * line.length * math.cos( line.phi ), t * line.length * math.sin( line.phi ) }
+ )
+ end
+ end
+
+ if #r == 0
+ then
+ return
+ elseif #r == 1
+ then
+ return r[ 1 ]
+ else
+ return r
+ end
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ s = { }
+ table.insert( s, zString( self.at ) )
+ table.insert( s, 'ellipse' )
+ table.insert( s, '[' )
+ table.insert( s, 'x radius =' )
+ table.insert( s, zString( self.xradius ) )
+ table.insert( s, ', y radius =' )
+ table.insert( s, zString( self.yradius ) )
+ table.insert( s, ']' )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- An ellipse part.
+-------------------------------------------------------------------------------
+tikz.ellipseArc =
+immutable( function( def )
+ def.id = 'ellipseArc'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ return {
+ at = args.at,
+ from = args.from,
+ to = args.to,
+ xradius = args.xradius,
+ yradius = args.yradius,
+ }
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ s = { }
+ table.insert( s, 'plot' )
+ table.insert( s, '[domain =' )
+ table.insert( s, self.from )
+ table.insert( s, ':' )
+ table.insert( s, self.to )
+ table.insert( s, ',variable = \\phi,smooth]' )
+ table.insert( s,
+ '({ '..zString(self.at.x)..'+'..zString(self.xradius)..'*cos(\\phi)},'
+ ..'{ '..zString(self.at.y)..'+'..zString(self.yradius)..'*sin(\\phi)})'
+ )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- Single line (segment).
+-------------------------------------------------------------------------------
+tikz.line =
+immutable( function( def )
+ def.id = 'line'
+
+ -- moves the line.
+ def.add =
+ function( left, right )
+ if right.id == 'point'
+ then
+ return tikz.line{
+ p1 = left.p1 + right,
+ p2 = left.p2 + right,
+ }
+ end
+ error( 'unknown operation' )
+ end
+
+ -- multiplicator
+ def.mul =
+ function( left, right )
+ if type( left ) == 'number'
+ then
+ return tikz.line{
+ p1 = right.p1 * left,
+ p2 = right.p2 * left,
+ }
+ elseif type( right ) == 'number'
+ then
+ return tikz.line{
+ p1 = left.p1 * right,
+ p2 = left.p2 * right,
+ }
+ end
+ error( 'unknown operation' )
+ end
+
+ -- constructor
+ --
+ -- either
+ -- { p1, p2 }, (a list )
+ -- { p1 = ..., p2 = ... },
+ -- { pc = ..., phi = ..., length = ... },
+ def.constructor =
+ function( args )
+ local p1
+ local p2
+
+ if #args > 1
+ then
+ -- list
+ p1 = args[ 1 ]
+ p2 = args[ 2 ]
+ elseif type( args.p1 ) ~= 'nil' and type( args.p2 ) ~= 'nil'
+ then
+ p1 = args.p1
+ p2 = args.p2
+ elseif args.phi ~= nil
+ then
+ local phi = args.phi
+ if args.pc ~= nil
+ then
+ if args.p1 ~= nil
+ or args.p2 ~= nil
+ then
+ error( 'invalid line options', 2 )
+ end
+ local pc = args.pc
+ local len05 = args.length / 2
+ p1 = pc + tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
+ p2 = pc - tikz.p{ math.cos( phi ), math.sin( phi ) } * len05
+ elseif type( args.p1 ) ~= 'nil'
+ then
+ if args.pc ~= nil
+ or args.p2 ~= nil
+ then
+ error( 'invalid line options', 2 )
+ end
+ p1 = args.p1
+ p2 = p1 + tikz.p{ math.cos( phi ), math.sin( phi ) } * args.length
+ else
+ error( 'invalid line options', 2 )
+ end
+ else
+ error( 'invalid line options', 2 )
+ end
+
+ return {
+ p1 = p1,
+ p2 = p2,
+ }
+ end
+
+ -- divisor
+ def.div =
+ function( left, right )
+ if type( right ) == 'number'
+ then
+ return tikz.line{
+ p1 = left.p1 / right,
+ p2 = left.p2 / right,
+ }
+ end
+ error( 'unknown operation' )
+ end
+
+ -- length of line.
+ def.lazy.length =
+ function( self )
+ local p1 = self.p1
+ local p2 = self.p2
+ local dx = p2.x - p1.x
+ local dy = p2.y - p1.y
+ local result = math.sqrt( dx*dx + dy*dy )
+ return result
+ end
+
+ -- center of line.
+ def.lazy.pc =
+ function( self )
+ local p1 = self.p1
+ local p2 = self.p2
+ local result = tikz.p{ ( p1.x + p2.x ) / 2, ( p1.y + p2.y ) / 2 }
+ return result
+ end
+
+ -- angle of line.
+ def.lazy.phi =
+ function( self )
+ local p1 = self.p1
+ local p2 = self.p2
+ local result = math.atan2( p2.y - p1.y, p2.x - p1.x )
+ return result
+ end
+
+ -- gets point at t (0-1).
+ def.proto.pt =
+ function( self, t )
+ if type( t ) ~= 'number' or t < 0 or t > 1
+ then
+ error( 'invalid pt' )
+ end
+
+ local p1 = self.p1
+ local p2 = self.p2
+ local phi = self.phi
+ local lt = self.length * t
+
+ return(
+ tikz.p{
+ p1.x + lt * math.cos( phi ),
+ p1.y + lt * math.sin( phi ),
+ }
+ )
+ end
+
+ -- intersects the line with another.
+ def.proto.intersectLine =
+ function( self, line )
+ local p1 = self.p1
+ local p2 = self.p2
+ local p3 = line.p1
+ local p4 = line.p2
+
+ if p1.x == p3.x and p1.y == p3.y then return p1 end
+ if p1.x == p4.x and p1.y == p4.y then return p1 end
+ if p2.x == p3.x and p2.y == p3.y then return p2 end
+ if p2.x == p4.x and p2.y == p4.y then return p2 end
+
+ local den = ( p1.x - p2.x )*( p3.y - p4.y ) - ( p1.y - p2.y )*( p3.x - p4.x );
+ if den == 0
+ then
+ -- doesn't intersect
+ return
+ end
+
+ local a = p1.x*p2.y - p1.y*p2.x;
+ local b = p3.x*p4.y - p3.y*p4.x;
+ local x = ( a*( p3.x - p4.x ) - ( p1.x - p2.x )*b ) / den;
+ local y = ( a*( p3.y - p4.y ) - ( p1.y - p2.y )*b ) / den;
+
+ if
+ (
+ ( x >= p1.x and x <= p2.x or x <= p1.x and x >= p2.x )
+ and ( x >= p3.x and x <= p4.x or x <= p3.x and x >= p4.x )
+ )
+ or
+ (
+ ( y >= p1.y and y <= p2.y or y <= p1.y and y >= p2.y )
+ and ( y >= p3.y and y <= p4.y or y <= p3.y and y >= p4.y )
+ )
+ then
+ return tikz.p{ x, y }
+ end
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { }
+ table.insert( s, zString( self.p1 ) )
+ table.insert( s, '--' )
+ table.insert( s, zString( self.p2 ) )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- Function plot.
+-------------------------------------------------------------------------------
+tikz.plot =
+immutable( function( def )
+ def.id = 'plot'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ return {
+ at = args.at,
+ from = args.from,
+ to = args.to,
+ step = args.step,
+ tension = args.tension,
+ func = args.func,
+ }
+ end
+
+ def.lazy.points =
+ function( self )
+ local from = self.from
+ local to = self.to
+ local step = self.step
+ if step == nil
+ then
+ step = ( to - from ) / 100
+ end
+ local points = { }
+ local d
+ local at = self.at or tikz.p0
+ for d = from, to, step
+ do
+ table.insert( points, at + self.func( d ) )
+ end
+ return points
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { 'plot' }
+ table.insert( s, '[' )
+ table.insert( s, 'smooth' )
+ if self.tension
+ then
+ table.insert( s, ',tension='..self.tension )
+ end
+ table.insert( s, ']' )
+
+ local points = self.points
+
+ table.insert( s, 'coordinates' )
+ table.insert( s, '{' )
+ for _, p in ipairs( points )
+ do
+ table.insert( s, zString( p ) )
+ end
+ table.insert( s, '}' )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A superEllipse
+-------------------------------------------------------------------------------
+tikz.superEllipse =
+immutable( function( def )
+ def.id = 'superEllipse'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local at = args.at
+ local n = args.n
+ local xradius = args.xradius
+ local yradius = args.yradius
+
+ return {
+ at = at,
+ n = n,
+ xradius = xradius,
+ yradius = yradius,
+ }
+ end
+
+ -- returns p on degree (0-360)
+ def.proto.pdeg =
+ function( self, t )
+ if type( t ) ~= 'number' or t < 0 or t > 360
+ then
+ error( 'invalid pdeg' )
+ end
+
+ local at = self.at
+ local n = self.n
+ local xo = self.xradius * math.abs( math.cos( t * math.pi / 180 ) )^(2/n)
+ local yo = self.yradius * math.abs( math.sin( t * math.pi / 180 ) )^(2/n)
+
+ if t <= 90
+ then
+ return tikz.p{ at.x + xo, at.y + yo }
+ elseif t <= 180
+ then
+ return tikz.p{ at.x - xo, at.y + yo }
+ elseif t <= 270
+ then
+ return tikz.p{ at.x - xo, at.y - yo }
+ else
+ return tikz.p{ at.x + xo, at.y - yo }
+ end
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { }
+ local at = self.at
+ local xa = at.x
+ local ya = at.y
+ local n = self.n
+ local xr = self.xradius
+ local yr = self.yradius
+ table.insert( s, 'plot[domain=0:90,variable=\\t,smooth]' )
+ table.insert( s, '({'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
+ table.insert( s, '--' )
+ table.insert( s, 'plot[domain=90:0,variable=\\t,smooth]' )
+ table.insert( s, '({-'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
+ table.insert( s, '--' )
+ table.insert( s, 'plot[domain=0:90,variable=\\t,smooth]' )
+ table.insert( s, '({-'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{-'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
+ table.insert( s, '--' )
+ table.insert( s, 'plot[domain=90:0,variable=\\t,smooth]' )
+ table.insert( s, '({'..xr..'*cos(\\t)^(2/'..n..')+'..xa..'},{-'..yr..'*sin(\\t)^(2/'..n..')+'..ya..'})' )
+ table.insert( s, '-- cycle' )
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A reference to a named node.
+-------------------------------------------------------------------------------
+local optionsNamed =
+{
+ ref = false,
+ xshift = 'xshift',
+ yshift = 'yshift',
+}
+
+tikz.named =
+immutable( function( def )
+ def.id = 'named'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local s = { }
+ _doOptions( s, args, optionsNamed, false, 2 )
+ return {
+ opts = table.concat( s, ' ' ),
+ ref = args.ref,
+ }
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { }
+ table.insert( s, '(' )
+ if self.opts
+ then
+ table.insert( s, self.opts )
+ end
+ table.insert( s, self.ref )
+ table.insert( s, ')' )
+ return table.concat( s )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- Multiple connected lines.
+-------------------------------------------------------------------------------
+tikz.polyline =
+immutable( function( def )
+ def.id = 'polyline'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local o = { }
+ for _, v in ipairs( args )
+ do
+ table.insert( o, v )
+ end
+ return o
+ end
+
+ -- converts to a tikz string.
+ def.zString =
+ function( self )
+ local s = { '' }
+ for k, v in ipairs( self )
+ do
+ if k > 1 then table.insert( s, '--' ) end
+ table.insert( s, zString( v ) )
+ end
+ return table.concat( s, ' ' )
+ end
+end )
+
+-------------------------------------------------------------------------------
+-- A rectangle
+-------------------------------------------------------------------------------
+tikz.rect =
+immutable( function( def )
+ def.id = 'rect'
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local psw
+ local pne
+
+ if args.pnw ~= nil
+ then
+ if args.pse ~= nil
+ then
+ psw = tikz.p{ args.pnw.x, args.pse.y }
+ pne = tikz.p{ args.pse.x, args.pnw.y }
+ elseif args.size ~= nil
+ then
+ psw = tikz.p{ args.pnw.x, args.pnw.y - args.size.y }
+ pne = tikz.p{ args.pnw.x + args.size.x, args.pnw.y }
+ else
+ error( 'invalid rect options', 2 )
+ end
+ elseif args.psw ~= nil
+ then
+ psw = args.psw
+ if args.pne ~= nil
+ then
+ pne = args.pne
+ elseif args.size ~= nil
+ then
+ pne = psw + args.size
+ else
+ error( 'invalid rect options', 2 )
+ end
+ elseif args.pse ~= nil
+ then
+ if args.size ~= nil
+ then
+ psw = tikz.p{ args.pse.x - args.size.x, args.pse.y }
+ pne = tikz.p{ args.pse.x, args.pse.y + args.size.y }
+ else
+ error( 'invalid rect options', 2 )
+ end
+ elseif args.pc ~= nil
+ then
+ if args.size ~= nil
+ then
+ psw = tikz.p{ args.pc.x - args.size.x / 2, args.pc.y - args.size.y / 2 }
+ pne = tikz.p{ args.pc.x + args.size.x / 2, args.pc.y + args.size.y / 2 }
+ else
+ error( 'invalid rect options', 2 )
+ end
+ else
+ error( 'invalid rect options', 2 )
+ end
+
+ return {
+ psw = psw,
+ pne = pne,
+ }
+ end
+
+ def.zString =
+ function( self )
+ local s = { }
+ table.insert( s, zString( self.psw ) )
+ table.insert( s, 'rectangle' )
+ table.insert( s, zString( self.pne ) )
+ return table.concat( s, ' ' )
+ end
+
+ def.lazy.e =
+ function( self )
+ return self.pne.x
+ end
+
+ def.lazy.height =
+ function( self )
+ return self.pne.y - self.psw.y
+ end
+
+ def.lazy.n =
+ function( self )
+ return self.pne.y
+ end
+
+ def.lazy.pc =
+ function( self )
+ return tikz.p{ self.psw.x + self.width / 2, self.psw.y + self.height / 2 }
+ end
+
+ def.lazy.pe =
+ function( self )
+ return tikz.p{ self.pne.x, self.psw.y + self.height / 2 }
+ end
+
+ def.lazy.pn =
+ function( self )
+ return tikz.p{ self.psw.x + self.width / 2, self.pne.y }
+ end
+
+ def.lazy.pnw =
+ function( self )
+ return tikz.p{ self.psw.x, self.pne.y }
+ end
+
+ def.lazy.ps =
+ function( self )
+ return tikz.p{ self.psw.x + self.width / 2, self.psw.y }
+ end
+
+ def.lazy.pse =
+ function( self )
+ return tikz.p{ self.pne.x, self.psw.y }
+ end
+
+ def.lazy.pw =
+ function( self )
+ return tikz.p{ self.psw.x, self.psw.y + self.height / 2 }
+ end
+
+ def.lazy.s =
+ function( self )
+ return self.psw.y
+ end
+
+ def.lazy.size =
+ function( self )
+ return tikz.p{ self.width, self.height }
+ end
+
+ def.lazy.w =
+ function( self )
+ return self.psw.x
+ end
+
+ def.lazy.width =
+ function( self )
+ return self.pne.x - self.psw.x
+ end
+end )
+
+
+--==========================================================================--
+-- STYLES --
+--==========================================================================--
+tikz.style =
+immutable( function( def )
+ def.id = 'style'
+
+ -- constructor
+ def.constructor =
+ function( name )
+ return { name=name }
+ end
+end )
+
+tikz.above = tikz.style( 'above' )
+tikz.arrow = tikz.style( '-{Latex}' )
+tikz.below = tikz.style( 'below' )
+tikz.dashed = tikz.style( 'dashed' )
+tikz.dotted = tikz.style( 'dotted' )
+tikz.double_arrow = tikz.style( '{Latex}-{Latex}' )
+tikz.even_odd_rule = tikz.style( 'even odd rule' )
+tikz.midway = tikz.style( 'midway' )
+
+--==========================================================================--
+-- OUTPUT ---
+--==========================================================================--
+
+--
+-- Writes out a named option.
+--
+-- ~s: string to build
+-- ~args: arguments
+-- ~name: name of option
+-- ~haveOpts: true if had options
+-- ~rename: if defined rename options on output
+--
+local function optionNamed( s, args, name, haveOpts, rename )
+
+ local v = args[ name ]
+
+ if not v
+ then
+ return haveOpts
+ end
+
+ if not rename
+ then
+ return haveOpts
+ end
+
+ if not haveOpts
+ then
+ table.insert( s, '[' )
+ else
+ table.insert( s, ',' )
+ end
+
+ if v == true
+ then
+ table.insert( s, rename )
+ else
+ table.insert( s, rename..'='..v )
+ end
+
+ return true
+end
+
+--
+-- Writes out an unnamed option.
+--
+-- ~s: string to build
+-- ~name: name of option
+-- ~haveOpts: true if had options
+--
+local function unnamedOption( s, name, haveOpts )
+ if not haveOpts
+ then
+ table.insert( s, '[' )
+ else
+ table.insert( s, ',' )
+ end
+ table.insert( s, name )
+ return true
+end
+
+--
+-- Custom key compare.
+--
+-- keys to put on top
+local topKeys =
+{
+ node_distance = true,
+}
+
+local function kcompare( ka, kb )
+ local ta = topKeys[ ka ]
+ local tb = topKeys[ kb ]
+ if ta and not tb then return true end
+ if not ta and tb then return false end
+ return ka < kb
+end
+
+--
+-- A helper to parse arguments to pass as drawing options to hand to tikz.
+--
+-- s: string to build
+-- args: the arguments to parse.
+-- opts: named options to pass on
+-- haveOpts: true [ has already been built
+-- level: call level for error throwing
+--
+local function _doOptions( s, args, opts, haveOpts, level )
+ args = flatten( args )
+
+ for _, v in ipairs( args )
+ do
+ if v.id == 'style'
+ then
+ haveOpts = unnamedOption( s, v.name, haveOpts )
+ end
+ end
+
+ local nkeys = { }
+ for key in pairs( args )
+ do
+ if type( key ) ~= 'number'
+ then
+ table.insert( nkeys, key )
+ end
+ end
+ table.sort( nkeys, kcompare )
+
+ for _, k in ipairs( nkeys )
+ do
+ local ok = opts[ k ]
+ if ok
+ then
+ haveOpts = optionNamed( s, args, k, haveOpts, ok )
+ elseif ok == nil
+ then
+ error( 'unknown option '..k, level + 1 )
+ end
+ end
+
+ haveOpts = _opts( s, args._opts, haveOpts )
+
+ if haveOpts
+ then
+ table.insert( s, ']' )
+ end
+end
+
+-------------------------------------------------------------------------------
+-- Drawing shapes.
+-------------------------------------------------------------------------------
+local optionsDraw =
+{
+ decorate = 'decorate',
+ decoration = 'decoration',
+ dash_pattern = 'dash pattern',
+ color = 'color',
+ draw = 'draw',
+ fill = 'fill',
+ line_cap = 'line cap',
+ line_width = 'line width',
+ opacity = 'opacity',
+ pattern = 'pattern',
+ rotate = 'rotate',
+ transform_canvas = 'transform canvas',
+ xshift = 'xshift',
+ yshift = 'yshift',
+ _butt_cap = '-Butt Cap',
+ butt_cap_ = 'Butt Cap-',
+ _round_cap = '-{Round Cap[]}',
+ round_cap_ = '{Round Cap[]}-',
+ _round_cap_ = '{Round Cap[]}-{Round Cap[]}',
+}
+
+--
+-- Puts out a bounding box command
+--
+tikz.boundingbox =
+function( args )
+ tprint( '\\pgfresetboundingbox' )
+ local s = { '\\path [use as bounding box] ' }
+ for _, v in ipairs( args )
+ do
+ if v.id ~= 'style'
+ then
+ table.insert( s, zString( v ) )
+ end
+ end
+ table.insert( s, ';' )
+ tprint( table.unpack( s ) )
+end
+
+--
+-- Puts out a draw command.
+--
+tikz.draw =
+function( args )
+ local s = { '\\draw ' }
+ _doOptions( s, args, optionsDraw, false, 2 )
+
+ for _, v in ipairs( args )
+ do
+ if v.id ~= 'style'
+ then
+ table.insert( s, zString( v ) )
+ end
+ end
+
+ table.insert( s, ';' )
+ tprint( table.unpack( s ) )
+end
+
+--
+-- Puts out a path command.
+--
+tikz.path =
+function( args )
+ local s = { '\\path' }
+ _doOptions( s, args, optionsDraw, false, 2 )
+
+ for _, v in ipairs( args )
+ do
+ if v.id ~= 'style'
+ then
+ table.insert( s, zString( v ) )
+ end
+ end
+ table.insert( s, ';' )
+ tprint( table.unpack( s ) )
+end
+
+--
+-- Puts out a bounding box.
+--
+tikz.clip =
+function( args )
+ local s = { '\\begin{pgfinterruptboundingbox}', '\\clip' }
+ _doOptions( s, args, optionsDraw, false, 2 )
+
+ for _, v in ipairs( args )
+ do
+ if v.id ~= 'style' then table.insert( s, zString( v ) ) end
+ end
+ table.insert( s, ';' )
+ table.insert( s, '\\end{pgfinterruptboundingbox}' )
+ tprint( table.unpack( s ) )
+end
+
+local shadeOptions =
+{
+ ball_color = 'ball color',
+ left_color = 'left color',
+ lower_left_color = 'lower left',
+ lower_right_color = 'lower right',
+ opacity = 'opacity',
+ right_color = 'right color',
+ shading = 'shading',
+ upper_left_color = 'upper left',
+ upper_right_color = 'upper right',
+}
+
+--
+-- Puts out a shade command.
+--
+tikz.shade =
+function( args )
+ local s = { '\\shade' }
+ _doOptions( s, args, shadeOptions, false, 2 )
+
+ for _, v in ipairs( args )
+ do
+ if v.id ~= 'style' then table.insert( s, zString( v ) ) end
+ end
+ table.insert( s, ';' )
+ tprint( table.unpack( s ) )
+end
+
+--
+-- Puts out z stuff.
+--
+tikz.put =
+function( args )
+ for _, v in ipairs( args )
+ do
+ local s = { }
+ if v.id == 'node'
+ then
+ table.insert( s, '\\' )
+ table.insert( s, zString( v ) )
+ table.insert( s, ';' )
+ else
+ error( 'unknown: ' .. v.id, 1 )
+ end
+ tprint( table.unpack( s ) )
+ end
+end
+
+tikz.declarehorizonalshading =
+function( args )
+ local steps = args.steps
+ local name = args.name
+ local s = { '\\pgfdeclarehorizontalshading{' .. name .. '}{100bp}{' }
+
+ local keys = { }
+ for k, _ in pairs( steps )
+ do
+ table.insert( keys, k )
+ end
+
+ table.sort( keys )
+ for _, k in ipairs( keys )
+ do
+ if _ > 1 then table.insert( s, ';' ) end
+ table.insert( s, 'color(' .. k .. 'bp)=(' .. steps[ k ] .. ')' )
+ end
+ table.insert( s, '}' )
+
+ tprint( table.unpack( s ) )
+end
+
+-------------------------------------------------------------------------------
+-- A node to be defined to tikZ
+-------------------------------------------------------------------------------
+local optionsNode =
+{
+ above = 'above',
+ anchor = 'anchor',
+ align = 'align',
+ at = false,
+ below = 'below',
+ color = 'color',
+ draw = 'draw',
+ left = 'left',
+ minimum_height = 'minimum height',
+ node_distance = 'node distance',
+ name = false,
+ right = 'right',
+ rotate = 'rotate',
+ text = false,
+ text_width = 'text width',
+}
+
+tikz.node =
+immutable( function( def )
+ def.id = 'node';
+
+ def.zString =
+ function( self )
+ local s = { 'node' }
+
+ table.insert( s, self.opts )
+ if self.name
+ then
+ table.insert( s, ' (' )
+ table.insert( s, self.name )
+ table.insert( s, ')' )
+ end
+
+ if self.at
+ then
+ table.insert( s, ' at ' )
+ table.insert( s, zString( self.at ) )
+ end
+
+ if self.text
+ then
+ table.insert( s, ' {' )
+ local text = string.gsub( self.text, '\n', '' )
+ table.insert( s, text )
+ table.insert( s, '}' )
+ end
+
+ return table.concat( s )
+ end
+
+ -- constructor
+ def.constructor =
+ function( args )
+ local s = { }
+ _doOptions( s, args, optionsNode, false, 2 )
+ return {
+ at = args.at,
+ name = args.name,
+ opts = table.concat( s, ' ' ),
+ text = args.text
+ }
+ end
+end )
+
+tikz.outfile =
+function( filename )
+ outfile = io.open( filename, 'w' )
+ outfile:write( '%%% DO NOT EDIT THIS FILE %%%\n' )
+end
+
+--
+-- Hacking help.
+--
+function _direct( args )
+ tprint( table.unpack( args ) )
+end
+
+--
+-- Checks version metch.
+--
+tikz.checkVersion =
+function( v )
+ if v ~= tikz.version
+ then
+ error( 'version mismatch', 1 )
+ end
+end
+
+--
+-- Creates a color.
+--
+tikz.colorRGB =
+function( red, green, blue )
+ return '{rgb,255:red,'..red..'; green,'..green..'; blue,'..blue..'}'
+end
+
+--
+-- Makes a tikz scope.
+-- Argument must be a function to call.
+--
+tikz.scope =
+function( args )
+ args = flatten( args )
+ if #args ~= 1 or type( args[ 1 ] ) ~= 'function'
+ then
+ error( 'scope needs a function parameter' )
+ end
+ _direct{ [[\begin{scope}]] }
+ args[ 1 ]( )
+ _direct{ [[\end{scope}]] }
+end
+
+--==========================================================================--
+-- SHORTCUTS
+--==========================================================================--
+tikz.p0 = tikz.p{ 0, 0 }
+
+
+--==========================================================================--
+-- Running code within tikz environment.
+--==========================================================================--
+
+local env_nesting = 0
+local org_env
+tikz.within =
+function( version )
+ if version ~= '*' and version ~= tikz.version
+ then
+ error( 'version mismatch', 1 )
+ end
+
+ env_nesting = env_nesting + 1
+ if env_nesting > 1
+ then
+ return
+ end
+
+ org_env = _ENV
+ local nenv = { }
+ local mt = { }
+ mt.__index =
+ function( _, key )
+ local zk = tikz[ key ]
+ if zk ~= nil
+ then
+ return zk
+ else
+ return org_env[ key ]
+ end
+ end
+ setmetatable( nenv, mt )
+ local func = debug.getinfo( 2 ).func
+ debug.setupvalue( func, 1, nenv )
+end
+
+tikz.without =
+function( )
+ env_nesting = env_nesting - 1
+ if env_nesting > 0 then return end
+ local func = debug.getinfo( 2 ).func
+ debug.setupvalue( func, 1, org_env )
+end