1 -- primitives for saving to file and loading from file
3 function file_exists(filename
)
4 local infile
= App
.open_for_reading(filename
)
13 function load_from_disk(State
)
14 local infile
= App
.open_for_reading(State
.filename
)
15 State
.lines
= load_from_file(infile
)
16 if infile
then infile
:close() end
19 function load_from_file(infile
)
22 local infile_next_line
= infile
:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)
24 local line
= infile_next_line()
25 if line
== nil then break end
26 if line
== '```lines' then -- inflexible with whitespace since these files are always autogenerated
27 table.insert(result
, load_drawing(infile_next_line
))
29 table.insert(result
, {mode
='text', data
=line
})
34 table.insert(result
, {mode
='text', data
=''})
39 function save_to_disk(State
)
40 local outfile
= App
.open_for_writing(State
.filename
)
41 if outfile
== nil then
42 error('failed to write to "'..State
.filename
..'"')
44 for _
,line
in ipairs(State
.lines
) do
45 if line
.mode
== 'drawing' then
46 store_drawing(outfile
, line
)
48 outfile
:write(line
.data
)
55 function load_drawing(infile_next_line
)
56 local drawing
= {mode
='drawing', h
=256/2, points
={}, shapes
={}, pending
={}}
58 local line
= infile_next_line()
60 if line
== '```' then break end
61 local shape
= json
.decode(line
)
62 if shape
.mode
== 'freehand' then
64 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
65 local name
= shape
.p1
.name
66 shape
.p1
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p1
.x
, shape
.p1
.y
, --[[large width to minimize overlap]] 1600)
67 drawing
.points
[shape
.p1
].name
= name
69 shape
.p2
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p2
.x
, shape
.p2
.y
, --[[large width to minimize overlap]] 1600)
70 drawing
.points
[shape
.p2
].name
= name
71 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
72 for i
,p
in ipairs(shape
.vertices
) do
74 shape
.vertices
[i
] = Drawing
.find_or_insert_point(drawing
.points
, p
.x
,p
.y
, --[[large width to minimize overlap]] 1600)
75 drawing
.points
[shape
.vertices
[i]]
.name
= name
77 elseif shape
.mode
== 'circle' or shape
.mode
== 'arc' then
78 local name
= shape
.center
.name
79 shape
.center
= Drawing
.find_or_insert_point(drawing
.points
, shape
.center
.x
,shape
.center
.y
, --[[large width to minimize overlap]] 1600)
80 drawing
.points
[shape
.center
].name
= name
81 elseif shape
.mode
== 'deleted' then
87 table.insert(drawing
.shapes
, shape
)
92 function store_drawing(outfile
, drawing
)
93 outfile
:write('```lines\n')
94 for _
,shape
in ipairs(drawing
.shapes
) do
95 if shape
.mode
== 'freehand' then
96 outfile
:write(json
.encode(shape
), '\n')
97 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
98 local line
= json
.encode({mode
=shape
.mode
, p1
=drawing
.points
[shape
.p1
], p2
=drawing
.points
[shape
.p2
]})
99 outfile
:write(line
, '\n')
100 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
101 local obj
= {mode
=shape
.mode
, vertices
={}}
102 for _
,p
in ipairs(shape
.vertices
) do
103 table.insert(obj
.vertices
, drawing
.points
[p
])
105 local line
= json
.encode(obj
)
106 outfile
:write(line
, '\n')
107 elseif shape
.mode
== 'circle' then
108 outfile
:write(json
.encode({mode
=shape
.mode
, center
=drawing
.points
[shape
.center
], radius
=shape
.radius
}), '\n')
109 elseif shape
.mode
== 'arc' then
110 outfile
:write(json
.encode({mode
=shape
.mode
, center
=drawing
.points
[shape
.center
], radius
=shape
.radius
, start_angle
=shape
.start_angle
, end_angle
=shape
.end_angle
}), '\n')
111 elseif shape
.mode
== 'deleted' then
118 outfile
:write('```\n')
122 function load_array(a
)
124 local next_line
= ipairs(a
)
125 local i
,line
,drawing
= 0, ''
127 i
,line
= next_line(a
, i
)
128 if i
== nil then break end
130 if line
== '```lines' then -- inflexible with whitespace since these files are always autogenerated
131 --? print('inserting drawing')
132 i
, drawing
= load_drawing_from_array(next_line
, a
, i
)
133 --? print('i now', i)
134 table.insert(result
, drawing
)
136 --? print('inserting text')
137 local line_info
= {mode
='text'}
138 line_info
.data
= line
139 table.insert(result
, line_info
)
143 table.insert(result
, {mode
='text', data
=''})
148 function load_drawing_from_array(iter
, a
, i
)
149 local drawing
= {mode
='drawing', h
=256/2, points
={}, shapes
={}, pending
={}}
155 if line
== '```' then break end
156 local shape
= json
.decode(line
)
157 if shape
.mode
== 'freehand' then
159 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
160 local name
= shape
.p1
.name
161 shape
.p1
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p1
.x
, shape
.p1
.y
, --[[large width to minimize overlap]] 1600)
162 drawing
.points
[shape
.p1
].name
= name
164 shape
.p2
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p2
.x
, shape
.p2
.y
, --[[large width to minimize overlap]] 1600)
165 drawing
.points
[shape
.p2
].name
= name
166 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
167 for i
,p
in ipairs(shape
.vertices
) do
169 shape
.vertices
[i
] = Drawing
.find_or_insert_point(drawing
.points
, p
.x
,p
.y
, --[[large width to minimize overlap]] 1600)
170 drawing
.points
[shape
.vertices
[i]]
.name
= name
172 elseif shape
.mode
== 'circle' or shape
.mode
== 'arc' then
173 local name
= shape
.center
.name
174 shape
.center
= Drawing
.find_or_insert_point(drawing
.points
, shape
.center
.x
,shape
.center
.y
, --[[large width to minimize overlap]] 1600)
175 drawing
.points
[shape
.center
].name
= name
176 elseif shape
.mode
== 'deleted' then
182 table.insert(drawing
.shapes
, shape
)
187 function is_absolute_path(path
)
188 local os_path_separator
= package
.config
:sub(1,1)
189 if os_path_separator
== '/' then
190 -- POSIX systems permit backslashes in filenames
191 return path
:sub(1,1) == '/'
192 elseif os_path_separator
== '\\' then
193 if path
:sub(2,2) == ':' then return true end -- DOS drive letter followed by volume separator
194 local f
= path
:sub(1,1)
195 return f
== '/' or f
== '\\'
197 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator
..'"')
201 function is_relative_path(path
)
202 return not is_absolute_path(path
)
205 function dirname(path
)
206 local os_path_separator
= package
.config
:sub(1,1)
207 if os_path_separator
== '/' then
208 -- POSIX systems permit backslashes in filenames
209 return path
:match('.*/') or './'
210 elseif os_path_separator
== '\\' then
211 return path
:match('.*[/\\]') or './'
213 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator
..'"')
217 function test_dirname()
218 check_eq(dirname('a/b'), 'a/', 'F - test_dirname')
219 check_eq(dirname('x'), './', 'F - test_dirname/current')
222 function basename(path
)
223 local os_path_separator
= package
.config
:sub(1,1)
224 if os_path_separator
== '/' then
225 -- POSIX systems permit backslashes in filenames
226 return string.gsub(path
, ".*/(.*)", "%1")
227 elseif os_path_separator
== '\\' then
228 return string.gsub(path
, ".*[/\\](.*)", "%1")
230 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator
..'"')
235 for _
,_
in pairs(h
) do