-- to do: clean up dtx file of remnants of classical sseq code -- to do: documentation function sseq_init() -- sseqobject is a 2D-array containing arrays of all the nodes that are dropped at (x,y) -- Thus the first object dropped at (2,3) is sseqobject[2][3][1]. -- The object itself is a dictionary, containing: -- name a given name -- code the TeX code (in math mode) to typeset -- extends if it's an extension, then what it extends -- color the color -- nodetype the pgf node type (circle etc) -- cmd pgf code for drawing it -- with one %s for the color -- posx,posy the absolute position on the picture (in sp) -- determined at the end sseqobject = {} -- sseqname is a dictionary containing all the given names of dropped objects -- Its value is a dictionary, containing -- x, y, n the triple index in the sseqobject array sseqname = {} -- sseqlabel is an array of labels of sseqobjects -- Its values are dictionaries, containing -- x,y,n the triple the label belongs to -- pos one of L,LU,U,RU,R,RD,D,LD -- code the TeX code (in math mode) to typeset -- color the color sseqlabel = {} -- sseqconnection is an array containing all connection (lines) -- the connection itself is a dictionary, containing: -- from an array (x,y,n) if n=0 then source void -- to an array (x,y,n) if n=0 then target void -- color the color -- curving a number denoting the curving factor -- dashing the pgf dashing code -- arrowfrom the arrowstyle at from (or nil) -- arrowto the arrowstyle at to (or nil) sseqconnection = {} -- -- Initialize some global variables -- sseqcurrentindex = nil sseqpreviousindex = nil sseqopenconnection = nil sseqxstart = 0 -- minimal x sseqystart = 0 -- minimal y sseqgriddrawer = sseq_grid_crossword sseqgridstrokethickness = 6554 -- 0.1pt in sp sseqxlabels = sseq_parse_label_list("&n") sseqylabels = sseq_parse_label_list("&n") sseqprefix = {} sseqposx, sseqposy = 0,0 sseqcurrabsx, sseqcurrabsy = 0,0 sseq_set_defaults() end function sseq_set_defaults() sseqentrysize = 745860 -- 0.4 cm in scaled points sseqxgap = 559409 -- 0.3 cm in scaled points sseqygap = 559409 sseqxstep = 2 -- every other label is drawn sseqystep = 2 sseqdefaultarrowstyle = "to" ssequsescolor = true sseqpacking = sseq_pack_auto end -- parselabelrange: input = a string of the form a...b,c...d, etc. -- defstart: the default start index -- return = an array of dictionaries -- min a number -- max a number function parselabelrange(s,defstart) local res = {} local mini,maxi,found for rng in string.gmatch(s.."," , "([^,]+)") do found,_,mini,maxi = string.find(rng,"([+-]?[0-9]+)%.%.%.([+-]?[0-9]+)") if not found then mini = defstart maxi = tonumber(rng) if not maxi then error("invalid range : "..rng) end maxi = maxi+defstart-1 else mini = tonumber(mini) maxi = tonumber(maxi) end table.insert(res,{min = mini, max = maxi}) end return res end function sseq_setup_ranges(xr,yr,defxstart,defystart) sseqxrange = parselabelrange(xr,defxstart) sseqyrange = parselabelrange(yr,defystart) end function sseq_get_rangepart(range,x,gap) local pos = 0 for j,rng in ipairs(range) do if x >= rng.min and x <= rng.max then return pos,pos+(rng.max+1-rng.min)*sseqentrysize else pos = pos+(rng.max+1-rng.min)*sseqentrysize+gap end end end function sseq_getabsoluteposition(range,x,gap) local pos = 0 for j,rng in ipairs(range) do if x >= rng.min and x <= rng.max then return pos+sseqentrysize*(x-rng.min),false end pos = pos+(rng.max+1-rng.min)*sseqentrysize+gap end end function sseq_getcoords(x,y) local xpos,xout,ypos,yout xpos = sseq_getabsoluteposition(sseqxrange,x,sseqxgap) ypos = sseq_getabsoluteposition(sseqyrange,y,sseqygap) return xpos, ypos end function sseq_grid_none() end function sseq_grid_crossword(x,y,width,height) tex.print("\\pgfsetlinewidth{"..sseqgridstrokethickness.."sp}") tex.print("\\pgfpathgrid[stepx="..sseqentrysize.."sp,stepy="..sseqentrysize.."sp]{\\pgfpointorigin}{\\pgfpoint{"..width*sseqentrysize.."sp}{"..height*sseqentrysize.."sp}}") tex.print("\\pgfusepath{stroke}") end function sseq_grid_go(x,y,width,heigh) tex.print("\\pgfsetlinewidth{"..sseqgridstrokethickness.."sp}") tex.print("\\pgftransformxshift{"..(sseqentrysize/2).."sp}") tex.print("\\pgftransformyshift{"..(sseqentrysize/2).."sp}") tex.print("\\pgfpathgrid[stepx="..sseqentrysize.."sp,stepy="..sseqentrysize.."sp]{\\pgfpoint{"..(-sseqentrysize/2).."sp}{"..(-sseqentrysize/2).."sp}}{\\pgfpoint{"..(width*sseqentrysize-sseqentrysize/2).."sp}{"..(height*sseqentrysize-sseqentrysize/2).."sp}}") tex.print("\\pgfusepath{stroke}") end function sseq_grid_dots(x,y,width,height) tex.print("\\pgfsetlinewidth{1pt}") tex.print("\\pgfsetdash{{1pt}{"..(sseqentrysize-65536).."sp}}{"..(sseqentrysize/2+32768).."sp}") tex.print("\\pgftransformxshift{"..(sseqentrysize/2).."sp}") tex.print("\\pgftransformyshift{"..(sseqentrysize/2).."sp}") tex.print("\\pgfpathgrid[stepx="..sseqentrysize.."sp,stepy="..sseqentrysize.."sp]{\\pgfpoint{"..(-sseqentrysize/2).."sp}{"..(-sseqentrysize/2).."sp}}{\\pgfpoint{"..(width*sseqentrysize-sseqentrysize/2).."sp}{"..(height*sseqentrysize-sseqentrysize/2).."sp}}") tex.print("\\pgfusepath{stroke}") end function sseq_grid_chess(x,y,width,height) tex.print("\\pgfsetcolor{sslightgr}") if ((x+y) % 2) == 1 then -- invert everything by first drawing a solid gray rectangle -- and then draw the grid in white. This way, even bidegree -- is always white. tex.print("\\pgfpathrectangle{\\pgfpoint{0sp}{0sp}}{\\pgfpoint{" ..(width*sseqentrysize).."sp}{"..(height*sseqentrysize).."sp}}") tex.print("\\pgfusepath{fill}") tex.print("\\pgfsetcolor{white}") end tex.print("\\pgfsetlinewidth{"..sseqentrysize.."sp}") tex.print("\\pgfsetdash{{"..sseqentrysize.."sp}{"..sseqentrysize.."sp}}{"..sseqentrysize.."sp}") tex.print("\\pgftransformxshift{"..(sseqentrysize/2).."sp}") tex.print("\\pgftransformyshift{"..(sseqentrysize/2).."sp}") tex.print("\\pgfpathgrid[stepx="..(sseqentrysize*2).."sp,stepy="..(sseqentrysize*2).."sp]{\\pgfpoint{"..(-sseqentrysize/2).."sp}{"..(-sseqentrysize/2).."sp}}{\\pgfpoint{"..(width*sseqentrysize-sseqentrysize/2).."sp}{"..(height*sseqentrysize-sseqentrysize/2).."sp}}") tex.print("\\pgfusepath{stroke}") end function sseq_drawgrid() -- draws the background grid. This seems like pgf patterns are made -- for this purpose, but you can't specify a phase for patterns, -- so they are useless except for ornamental purposes. local xmin,ymin,width,heigt for x,xrng in ipairs(sseqxrange) do for y,yrng in ipairs(sseqyrange) do xmin, ymin = sseq_getcoords(xrng.min,yrng.min) tex.print("\\begin{pgfscope}") tex.print("\\pgftransformshift{\\pgfpoint{"..xmin.."sp}{"..ymin.."sp}}") width = (xrng.max+1)-xrng.min height = (yrng.max+1)-yrng.min sseqgriddrawer(xrng.min,yrng.min,width,height) tex.print("\\end{pgfscope}") end end end -- A label list has the form x1;x2;...;xn,y1;y2;...;yn,... where x1 etc are labels -- A label may contain the placeholders: -- &n = actual coordinate -- &c = number of chunk (begin with 0) -- &i = index within the chunk (begin with 0) function sseq_parse_label_list(s) local res = {} local chunk = 0 local index for rng in string.gmatch(s.."," , "([^,]*),") do res[chunk] = {} index = 0 for label in string.gmatch(rng..";", "([^;]*);") do res[chunk][index] = label index = index+1 end chunk = chunk+1 end return res end function sseq_format_label(label, n,c,i) local res res = string.gsub(label,"&n",n) res = string.gsub(res,"&c",c) res = string.gsub(res,"&i",i) res = string.gsub(res,"&&","&") return res end function sseq_label_fromlist(n,c,i,list) -- will return list[c][i] unless this is out of range, then take the last one given local chunks = #list local chunklen local chunk if list[0] then chunks = chunks+1 else return "" end chunk = list[math.min(chunks-1,c)] chunklen = #chunk if chunk[0] then chunklen = chunklen+1 end return sseq_format_label(chunk[math.min(chunklen-1,i)],n,c,i) end function sseq_draw_horizontal_labels(range) local k if sseqxstep == 0 then return end -- old-fashioned way of disabling labels for c,rng in ipairs(range) do k=0 for i=rng.min,rng.max,sseqxstep do x = sseq_getcoords(i,0) label = sseq_label_fromlist(i,c-1,k,sseqxlabels) -- bug fix with bounding box sizes in pgf -- tex.print("\\sbox\\sseq@labelbox{\\strut\\ensuremath{"..label.."}}") -- tex.print("\\dimen0=\\ht\\sseq@labelbox") -- tex.print("\\advance\\dimen0 by \\dp\\sseq@labelbox") -- tex.print("\\pgf@protocolsizes{0pt}{-\\dimen0}") tex.print("\\pgftext[top,at=\\pgfpoint{"..(x+sseqentrysize/2).."sp}{0sp}]{\\ensuremath{\\strut "..label.."}}") k=k+sseqxstep end end end function sseq_draw_vertical_labels(range) local k if sseqystep == 0 then return end -- old-fashioned way of disabling labels for c,rng in ipairs(range) do k=0 for i=rng.min,rng.max,sseqystep do _,y = sseq_getcoords(0,i) label = sseq_label_fromlist(i,c,k,sseqylabels) tex.print("\\pgftext[right,at=\\pgfpoint{-2pt}{"..(y+sseqentrysize/2).."sp}]{\\ensuremath{"..label.."}}") k=k+sseqystep end end end function sseq_drawlabels() sseq_draw_horizontal_labels(sseqxrange) sseq_draw_vertical_labels(sseqyrange) end function sseq_getdroplist(x,y) return sseqobject[x] and sseqobject[x][y] end function sseq_openposition() local l = sseq_getdroplist(sseqposx,sseqposy) if not l then error(string.format("sseq: cannot open position (%d,%d): nothing dropped yet",sseqposx,sseqposy)) elseif #l ~= 1 then error(string.format("sseq: cannot open position (%d,%d): multiple drops",sseqposx,sseqposy)) else sseqcurrentindex = {sseqposx,sseqposy,1} end end function sseq_assert_source() sseq_flush_connection() if not sseqcurrentindex then sseq_openposition() end end function sseq_finish_pos() sseq_flush_connection() if sseqcurrentindex then sseq_conclude_connection() sseqpreviousindex, sseqcurrentindex = sseqcurrentindex,nil end end function sseq_drop_and_open(x,y) if not sseqobject[x] then sseqobject[x] = {} end if not sseqobject[x][y] then sseqobject[x][y] = {} end table.insert(sseqobject[x][y],{}) sseqcurrentindex = {x,y,#sseqobject[x][y]} end function sseq_drop_object(x,y,shape,pathusage,content,col) local obj sseq_finish_pos() sseq_drop_and_open(x,y) obj = sseqobject[x][y][sseqcurrentindex[3]] obj.code = content obj.color = col obj.nodetype = shape sseq_conclude_connection() -- now do the optimizations such as \bullet -> drawn black circle etc if (pathusage == "fill") or (obj.code == "\\bullet ") then if (obj.nodetype == "circle") or (obj.code == "\\bullet ") then obj.nodetype = "circle" obj.cmd = "\\pgfsetfillcolor{%s}\\pgfpathqcircle{2pt}\\pgfusepathqfill" obj.radius = 131072 -- 2pt in sp obj.wd,obj.ht = 262144,262144 -- 4pt in sp else obj.cmd = "\\pgfsetfillcolor{%s}\\pgfsys@rect{-2pt}{-2pt}{4pt}{4pt}\\pgfsys@fill" obj.wd,obj.ht = 262144,262144 obj.radius = 370727 end elseif (obj.code == "\\circ ") then obj.nodetype = "circle" obj.cmd = "\\pgfsetstrokecolor{%s}\\pgfsetfillcolor{white}\\pgfsetdash{}{0pt}\\pgfpathqcircle{2pt}\\pgfusepathqfillstroke" obj.radius = 131072 -- 2pt in sp obj.wd,obj.ht = 262144,262144 -- 4pt in sp elseif(obj.code == "\\square ") then obj.nodetype="rectangle" obj.cmd = "\\pgfsetstrokecolor{%s}\\pgfsetdash{}{0pt}\\pgfsys@rect{-2pt}{-2pt}{4pt}{4pt}\\pgfsys@stroke" obj.wd,obj.ht = 262144,262144 obj.radius = 370727 else obj.cmd = "\\pgfsetstrokecolor{%s}\\pgfsetfillcolor{white}\\pgfsetshapeinnerxsep{1pt}\\pgfsetshapeinnerysep{1pt}"..string.format("\\pgfnode{%s}{center}{\\color{%%s}\\ensuremath{%s}}{}{\\pgfusepath{fill,%s}}",obj.nodetype,obj.code,pathusage) tex.print (string.format("\\setbox%d=\\hbox{\\pgfinterruptpicture\\ensuremath{%s}\\endpgfinterruptpicture}",sseqboxno,obj.code)) tex.print("\\directlua0{sseq_register_size()}") end end function sseq_drop_extension(shape,pathusage,col) local obj,refobj sseq_assert_source() table.insert(sseqobject[sseqposx][sseqposy],{}) obj = sseqobject[sseqposx][sseqposy][#sseqobject[sseqposx][sseqposy]] obj.code = "" -- no TeX code to typeset obj.extends = sseqcurrentindex[3] obj.color = col obj.nodetype = shape refobj = sseqobject[sseqcurrentindex[1]][sseqcurrentindex[2]][sseqcurrentindex[3]] sseqcurrentindex[3] = #sseqobject[sseqposx][sseqposy] sseq_conclude_connection() if (shape=="circle") then obj.radius = refobj.radius+65536 -- add 1pt in sp obj.wd,obj.ht = 2*obj.radius,2*obj.radius obj.cmd = "\\pgfsetstrokecolor{%s}\\pgfpathqcircle{"..obj.radius.."sp}\\pgfusepath{stroke,"..pathusage.."}" else obj.wd,obj.ht = refobj.wd+131072,refobj.ht +131072 -- add 1 pt at each side obj.radius = 0.5*math.sqrt(obj.wd*obj.wd+obj.ht*obj.ht) obj.cmd = "\\pgfsetstrokecolor{%s}\\pgfsetshapeinnerxsep{"..(.5*obj.wd).."sp}\\pgfsetshapeinnerysep{"..(.5*obj.ht).."sp}\\pgfnode{rectangle}{center}{}{}{\\pgfusepath{stroke,"..pathusage.."}}" end end function sseq_register_size() local currobj = sseqobject[sseqcurrentindex[1]][sseqcurrentindex[2]][sseqcurrentindex[3]] currobj.wd = tex.box[sseqboxno].width currobj.ht = (tex.box[sseqboxno].height+tex.box[sseqboxno].depth) currobj.dp = tex.box[sseqboxno].depth currobj.radius = 0.5*math.sqrt(currobj.wd*currobj.wd+currobj.ht*currobj.ht) end function sseq_drop_object_here(shape,pathusage,content,col) sseq_drop_object(sseqposx,sseqposy,shape,pathusage,content,col) end function sseq_bullstring(x,y,n,col) if n==0 then return end sseq_drop_object_here("circle","fill","\\bullet",col) for i=2,n do sseq_move(x,y) sseq_drop_object_here("circle","fill","\\bullet",col) sseq_late_connection("","",col,false,false) end end function sseq_moveto(x,y) sseq_conclude_connection() sseq_finish_pos() sseqposx = x sseqposy = y end function sseq_grayout(col) sseq_assert_source() sseqobject[sseqcurrentindex[1]][sseqcurrentindex[2]][sseqcurrentindex[3]].color = col for _,conn in pairs(sseqconnection) do if (conn.from[1] == sseqcurrentindex[1] and conn.from[2] == sseqcurrentindex[2] and conn.from[3] == sseqcurrentindex[3]) or (conn.to[1] == sseqcurrentindex[1] and conn.to[2] == sseqcurrentindex[2] and conn.to[3] == sseqcurrentindex[3]) then conn.color = col end end end function sseq_move(x,y) sseq_moveto(sseqposx+x,sseqposy+y) end -- New prefix model: sseqprefix is a stack of lists -- sseq_begin_prefix_context pushes an empty prefix onto that stack -- sseq_end_prefix_context pops the last prefix off the stack -- A prefix is an (unordered) list of names function uses_old_prefix_model(pref) if #pref == 0 then return true end if type(pref[1]) == "table" then return false end return true end function stringtolist(pref,name) local res = {} -- check if we're using the old or the new model if uses_old_prefix_model(pref) then for i,p in ipairs(pref) do table.insert(res,p) end else -- new prefix model for i,l in ipairs(pref) do for j,p in ipairs(l) do table.insert(res,p) end end end for w in string.gmatch(name,"%w+") do table.insert(res,w) end table.sort(res) return res end function sseq_set_global_name(name) local namestring = table.concat(name) sseq_assert_source() if sseqname[namestring] then error("sseq: duplicate name "..namestring) else sseqname[namestring] = {sseqcurrentindex[1],sseqcurrentindex[2],sseqcurrentindex[3]} end end function sseq_global_name(name) return stringtolist(sseqprefix,name) end function sseq_name(name) sseq_set_global_name(sseq_global_name(name)) end function sseq_global_goto(name) local namestring = table.concat(name) sseq_conclude_connection() sseq_finish_pos() if not sseqname[namestring] then error("sseq: goto name does not exist: '"..namestring.."'") else sseqcurrentindex = { sseqname[namestring][1],sseqname[namestring][2],sseqname[namestring][3] } sseqposx,sseqposy = sseqname[namestring][1], sseqname[namestring][2] end end function sseq_abs_goto(name) sseq_global_goto(stringtolist({},name)) end function sseq_prefix(pref) if uses_old_prefix_model(sseqprefix) then sseqprefix = stringtolist(sseqprefix,pref) else sseqprefix[#sseqprefix] = stringtolist(sseqprefix[#sseqprefix],pref) end end function sseq_begin_prefix_context() if (#sseqprefix > 0) and uses_old_prefix_model(sseqprefix) then error("sseq: you cannot use stacked prefixes when there's a non-stacked active prefix") else table.insert(sseqprefix,{}) end end function sseq_end_prefix_context() if uses_old_prefix_model(sseqprefix) then error("sseq: no prefix context to end") else table.remove(sseqprefix,#sseqprefix) end end -- sseq_reset_prefix is deprecated. It'll throw an error if we ever used -- sseq_begin_prefix_context (because that would be incompatible) function sseq_reset_prefix() if uses_old_prefix_model(sseqprefix) then sseqprefix = {} else error("sseq: you cannot use \\ssresetprefix if you use stacked prefixes.") end end function sseq_goto(name) sseq_global_goto(sseq_global_name(name)) end function sseq_flush_connection() end function sseq_conclude_connection() if not sseqopenconnection then return end if not sseqcurrentindex then sseq_openposition() end sseqopenconnection.to = { sseqcurrentindex[1],sseqcurrentindex[2],sseqcurrentindex[3] } table.insert(sseqconnection,sseqopenconnection) sseqopenconnection = nil end -- immediately register a connection between sseqpreviousindex and sseqcurrentindex function sseq_late_connection(dash,bending,col,sourcevoid,targetvoid) local newconn sseq_flush_connection() -- finish previous connection if not targetvoid then sseq_assert_source() end if (not sseqpreviousindex) or (not targetvoid and (sseqpreviousindex[3] == 0)) then error("sseq: connection without well-defined source") end newconn = { color = col, dashing = dash } if(bending ~= "") then newconn.curving = bending end if sourcevoid then newconn.from = {sseqpreviousindex[1],sseqpreviousindex[2],0} else newconn.from = {sseqpreviousindex[1],sseqpreviousindex[2],sseqpreviousindex[3]} end if targetvoid then newconn.to = {sseqposx,sseqposy,0} else newconn.to = {sseqcurrentindex[1],sseqcurrentindex[2],sseqcurrentindex[3]} end table.insert(sseqconnection,newconn) end function sseq_void_line(dash,bending,col,x,y) local newconn = { color = col, dashing = dash } if(bending ~= "") then newconn.curving = bending end sseq_assert_source(); newconn.from = {sseqcurrentindex[1],sseqcurrentindex[2],sseqcurrentindex[3]} newconn.to = {sseqcurrentindex[1]+x,sseqcurrentindex[2]+y,0} table.insert(sseqconnection,newconn) end function sseq_open_connection(dash,bending,col,x,y) local newconn = { color = col, dashing = dash } if(bending ~= "") then newconn.curving = bending end sseq_assert_source(); sseq_finish_pos() newconn.from = {sseqpreviousindex[1],sseqpreviousindex[2],sseqpreviousindex[3]} sseqopenconnection = newconn end function sseq_add_arrow(fromto,type) if sseqopenconnection then sseqopenconnection[fromto] = type else sseqconnection[#sseqconnection][fromto] = type end end function sseq_drop_label(p,col,label) sseq_assert_source() table.insert(sseqlabel,{x = sseqcurrentindex[1], y = sseqcurrentindex[2], n = sseqcurrentindex[3], color = col, code = label, pos = p}) end function sseq_pack_diagonal(i,n) return sseqentrysize/2+sseqentrysize*(n-i)/4, sseqentrysize/2-sseqentrysize*(n-i)/4 end function sseq_pack_horizontal(i,n) return sseqentrysize/2+sseqentrysize*(i-1)/4, sseqentrysize/2 end function sseq_pack_vertical(i,n) return sseqentrysize/2, sseqentrysize/2-sseqentrysize*(n-i)/4 end sseqautopackdata = { {{.5,.5}}, -- one {{.25,.75},{.75,.25}}, -- two {{.167,.833},{.5,.5},{.833,.167}}, -- three {{.167,.75},{.389,.25},{.611,.75},{.833,.25}} -- four } function sseq_pack_auto(i,n) -- return offset of the ith square out of n. if n > 4 then return sseq_pack_diagonal(i,n) end dat = sseqautopackdata[n][i] return dat[1]*sseqentrysize,dat[2]*sseqentrysize end function sseq_position_object_list(x,y,list) local absx,absy = sseq_getcoords(x,y) -- the lower left corner of the square local numobj = #list local j = 1 if not absx or not absy then return end -- outside clipping area -- don't draw. for i,obj in ipairs(list) do if (obj.extends) then numobj = numobj-1 end end for i,obj in ipairs(list) do if (obj.extends) then obj.posx,obj.posy = list[obj.extends].posx,list[obj.extends].posy else obj.posx, obj.posy = sseqpacking(j,numobj) obj.posx = obj.posx + absx obj.posy = obj.posy + absy j = j+1 end if not obj.color then obj.color = "black" end end end function sseq_position_objects() for x,ylist in pairs(sseqobject) do for y,olist in pairs(ylist) do sseq_position_object_list(x,y,olist) end end end function sseq_dump_translation(x,y) tex.print("\\pgfsys@transformshift{"..(x-sseqcurrabsx).."sp}{"..(y-sseqcurrabsy).."sp}") sseqcurrabsx,sseqcurrabsy = x,y end function sseq_dump_object(x,y,obj) if not obj.posx or not obj.posy then return end sseq_dump_translation(obj.posx,obj.posy) tex.print(string.format(obj.cmd,obj.color,obj.color)) end -- -- return the intersection of the vector to (fromx, fromy) with the boundary of obj -- function sseq_correct_line_end(obj,fromx,fromy,curving) local distsq,posx,posy,deltax,deltay,dirx,diry posx,posy = obj.posx,obj.posy dirx = posx-fromx diry = posy-fromy if(curving) then dirx,diry = dirx/2-curving*diry,diry/2+curving*dirx end if obj.nodetype == "circle" then dist = math.sqrt(dirx*dirx + diry*diry) return math.floor(posx - obj.radius*(dirx)/dist), math.floor(posy - obj.radius*(diry)/dist) else deltax = dirx/diry*obj.ht/2 -- no problem with infinity in LUA deltay = diry/dirx*obj.wd/2 if(math.abs(deltax) <= obj.wd/2) then -- boundary point on one of the horizontal lines if fromy > posy then return math.floor(posx + deltax), math.floor(posy + obj.ht/2) else return math.floor(posx - deltax), math.floor(posy - obj.ht/2) end else if fromx > posx then return math.floor(posx + obj.wd/2), math.floor(posy + deltay) else return math.floor(posx - obj.wd/2), math.floor(posy - deltay) end end end end -- -- return the intersection of the vector from (x,y) to the clipped object at -- coordinate (clipi,clipj) with the boundary of the grid segment containing (i,j) -- function sseq_fix_clipped_connection(clipi,clipj,i,j,x,y) local minx,maxx,miny,maxy,deltax,deltay minx,maxx = sseq_get_rangepart(sseqxrange,i,sseqxgap) miny,maxy = sseq_get_rangepart(sseqyrange,j,sseqygap) minx = minx - sseqxleak maxx = maxx + sseqxleak miny = miny - sseqyleak maxy = maxy + sseqyleak deltax = 1e10*(clipi-i) deltay = 1e10*(clipj-j) -- compute intersection of the vector (deltax, deltay) based at (x,y) -- with the rectangle minx,miny,maxx,maxy if(x+deltax > maxx) then deltax,deltay = maxx-x, deltay * (maxx-x)/deltax end if(x+deltax < minx) then deltax,deltay = minx-x, deltay*(minx-x)/deltax end if(y+deltay > maxy) then deltax,deltay = deltax * (maxy-y)/deltay, maxy-y end if(y+deltay < miny) then deltax,deltay = deltax * (miny-y)/deltay, miny-y end return x+deltax,y+deltay end function sseq_dump_connection(conn) local fromx, fromy, tox, toy, ctrlx,ctrly, helpx, helpy local fromobj,toobj -- possibilities: * from, to not in the displayed range -- * from[3] = 0 or to[3] = 0 (void line) -- or any combination. -- if source or target is clipped, will make it a void line -- -- TODO: locations outside displayed range are incorrect fromx,fromy = sseq_getcoords(conn.from[1],conn.from[2]) tox,toy = sseq_getcoords(conn.to[1],conn.to[2]) if ((not fromx) or (not fromy) or (conn.from[3] == 0)) and ((not tox) or (not toy) or (conn.to[3] == 0)) then -- forget it -- source and target are clipped and/or void return end if ((not fromx) or (not fromy)) then -- source clipped: we can be sure the target is regular toobj = sseqobject[conn.to[1]][conn.to[2]][conn.to[3]] tox,toy = toobj.posx, toobj.posy fromx,fromy = sseq_fix_clipped_connection(conn.from[1],conn.from[2],conn.to[1],conn.to[2],tox,toy) elseif ((not tox) or (not toy)) then -- target clipped: we can be sure the source is regular fromobj = sseqobject[conn.from[1]][conn.from[2]][conn.from[3]] fromx,fromy = fromobj.posx, fromobj.posy tox,toy = sseq_fix_clipped_connection(conn.to[1],conn.to[2],conn.from[1],conn.from[2],fromx,fromy) elseif conn.from[3] == 0 then -- source not clipped but void toobj = sseqobject[conn.to[1]][conn.to[2]][conn.to[3]] fromx = fromx+(toobj.posx-tox) fromy = fromy+(toobj.posy-toy) tox,toy = toobj.posx, toobj.posy elseif conn.to[3] == 0 then -- target not clipped but void fromobj = sseqobject[conn.from[1]][conn.from[2]][conn.from[3]] tox = tox+(fromobj.posx-fromx) toy = toy+(fromobj.posy-fromy) fromx,fromy = fromobj.posx, fromobj.posy else -- both source and target regular fromobj = sseqobject[conn.from[1]][conn.from[2]][conn.from[3]] fromx,fromy = fromobj.posx, fromobj.posy toobj = sseqobject[conn.to[1]][conn.to[2]][conn.to[3]] tox,toy = toobj.posx, toobj.posy end if fromobj then -- we have to be more careful where it ends fromx,fromy = sseq_correct_line_end(fromobj,tox,toy,conn.curving) else fromx,fromy = math.floor(fromx),math.floor(fromy) end if toobj then tox,toy = sseq_correct_line_end(toobj,fromx,fromy,conn.curving and -conn.curving) else tox,toy = math.floor(tox),math.floor(toy) end if conn.curving then ctrlx = math.floor(tox/2+fromx/2-conn.curving*(toy-fromy)) ctrly = math.floor(toy/2+fromy/2+conn.curving*(tox-fromx)) end if conn.arrowfrom or conn.arrowto or conn.curving then -- got to use slow code tex.print("\\pgfsetdash{"..conn.dashing.."}{0pt}") tex.print("\\pgfsetstrokecolor{"..conn.color.."}") if (fromx and fromy) then if (tox and toy) then tex.print(string.format("\\pgfpathmoveto{\\pgfpoint{%dsp}{%dsp}}",fromx,fromy)) if conn.curving then tex.print(string.format("\\pgfpathquadraticcurveto{\\pgfpoint{%dsp}{%dsp}}{\\pgfpoint{%dsp}{%dsp}}",ctrlx,ctrly,tox,toy)) else tex.print(string.format("\\pgfpathlineto{\\pgfpoint{%dsp}{%dsp}}",tox,toy)) end if conn.arrowfrom then tex.print("\\pgfsetarrowsstart{"..conn.arrowfrom.."}") else tex.print("\\pgfsetarrowsstart{}") end if conn.arrowto then tex.print("\\pgfsetarrowsend{"..conn.arrowto.."}") else tex.print("\\pgfsetarrowsend{}") end tex.print("\\pgfusepath{stroke}") end end else tex.print("\\pgfsetdash{"..conn.dashing.."}{0pt}") tex.print("\\pgfsetstrokecolor{"..conn.color.."}") if (fromx and fromy) then if (tox and toy) then tex.print(string.format("\\pgfsys@moveto{%dsp}{%dsp}",fromx,fromy)) tex.print(string.format("\\pgfsys@lineto{%dsp}{%dsp}",tox,toy)) tex.print("\\pgfsys@stroke") end end end end local labelpositioninrect = { U = {0,.5,"bottom"}, UL = {-.5,.5,"bottom,right"}, LU = {-.5,.5,"bottom,right"}, L = {-.5,0,"right"}, DL = {-.5,-.5,"top,right"}, LD = {-.5,-.5,"top,right"}, D = {0,-.5,"top"}, DR = {.5,-.5,"top,left"}, RD = {.5,-.5,"top,left"}, R = {.5,0,"left"}, UR = {.5,.5,"bottom,left"}, RU = {.5,.5,"bottom,left"} } local labelpositionincirc = { U = {0,1,"bottom"}, UL = {-.71,.71,"bottom,right"}, LU = {-.71,.71,"bottom,right"}, L = {-1,0,"right"}, DL = {-.71,-.71,"top,right"}, LD = {-.71,-.71,"top,right"}, D = {0,-1,"top"}, DR = {.71,-.71,"top,left"}, RD = {.71,-.71,"top,left"}, R = {1,0,"left"}, UR = {.71,.71,"bottom,left"}, RU = {.71,.71,"bottom,left"} } function sseq_dump_label(label) local labelledobj = sseqobject[label.x][label.y][label.n] local posx,posy if (not labelledobj.posx) or (not labelledobj.posy) then return end tex.print("\\color{"..label.color.."}") if labelledobj.nodetype == "circle" then posx = labelledobj.posx+labelpositionincirc[label.pos][1]*labelledobj.radius posy = labelledobj.posy+labelpositionincirc[label.pos][2]*labelledobj.radius sseq_dump_translation(posx,posy) tex.print(string.format("\\pgftext[%s]{\\ensuremath{%s}}",labelpositioninrect[label.pos][3],label.code)) else posx = labelledobj.posx+labelpositioninrect[label.pos][1]*labelledobj.wd posy = labelledobj.posy+labelpositioninrect[label.pos][2]*labelledobj.ht sseq_dump_translation(posx,posy) tex.print(string.format("\\pgftext[%s]{\\ensuremath{%s}}",labelpositioninrect[label.pos][3],label.code)) end end -- Write out all the pgf code to produce the chart function sseq_dump_code() if (not sseqxleak) then sseqxleak = 0.3*sseqxgap end if (not sseqyleak) then sseqyleak = 0.3*sseqygap end tex.print("\\makeatletter") tex.print("\\pgfset{inner sep=0pt}") -- connections for _,conn in pairs(sseqconnection) do sseq_dump_connection(conn) end -- objects tex.print("\\color{white}") -- background fill color tex.print("\\pgfsetdash{}{0pt}") -- reset dashing for x,ylist in pairs(sseqobject) do for y,olist in pairs(ylist) do for z,obj in ipairs(olist) do sseq_dump_object(x,y,obj) end end end -- labels for _,label in pairs(sseqlabel) do sseq_dump_label(label) end tex.print("\\makeatother") end