1 -- primitives for saving to file and loading from file
3 function file_exists(filename
)
4 local infile
= App
.open_for_reading(App
.save_dir
..filename
)
6 infile
= App
.open_for_reading(App
.source_dir
..filename
)
16 -- the source editor supports only files in the save dir backed by the source dir
17 function load_from_disk(State
)
18 local infile
= App
.open_for_reading(App
.save_dir
..State
.filename
)
20 infile
= App
.open_for_reading(App
.source_dir
..State
.filename
)
22 State
.lines
= load_from_file(infile
)
23 if infile
then infile
:close() end
26 function load_from_file(infile
)
29 local infile_next_line
= infile
:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)
31 local line
= infile_next_line()
32 if line
== nil then break end
33 if line
== '```lines' then -- inflexible with whitespace since these files are always autogenerated
34 table.insert(result
, load_drawing(infile_next_line
))
36 table.insert(result
, {mode
='text', data
=line
})
41 table.insert(result
, {mode
='text', data
=''})
46 function save_to_disk(State
)
47 local outfile
= App
.open_for_writing(App
.save_dir
..State
.filename
)
49 error('failed to write to "'..State
.filename
..'"')
51 for _
,line
in ipairs(State
.lines
) do
52 if line
.mode
== 'drawing' then
53 store_drawing(outfile
, line
)
55 outfile
:write(line
.data
)
62 function load_drawing(infile_next_line
)
63 local drawing
= {mode
='drawing', h
=256/2, points
={}, shapes
={}, pending
={}}
65 local line
= infile_next_line()
66 assert(line
, 'drawing in file is incomplete')
67 if line
== '```' then break end
68 local shape
= json
.decode(line
)
69 if shape
.mode
== 'freehand' then
71 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
72 local name
= shape
.p1
.name
73 shape
.p1
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p1
.x
, shape
.p1
.y
, --[[large width to minimize overlap]] 1600)
74 drawing
.points
[shape
.p1
].name
= name
76 shape
.p2
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p2
.x
, shape
.p2
.y
, --[[large width to minimize overlap]] 1600)
77 drawing
.points
[shape
.p2
].name
= name
78 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
79 for i
,p
in ipairs(shape
.vertices
) do
81 shape
.vertices
[i
] = Drawing
.find_or_insert_point(drawing
.points
, p
.x
,p
.y
, --[[large width to minimize overlap]] 1600)
82 drawing
.points
[shape
.vertices
[i]]
.name
= name
84 elseif shape
.mode
== 'circle' or shape
.mode
== 'arc' then
85 local name
= shape
.center
.name
86 shape
.center
= Drawing
.find_or_insert_point(drawing
.points
, shape
.center
.x
,shape
.center
.y
, --[[large width to minimize overlap]] 1600)
87 drawing
.points
[shape
.center
].name
= name
88 elseif shape
.mode
== 'deleted' then
91 assert(false, ('unknown drawing mode %s'):format(shape
.mode
))
93 table.insert(drawing
.shapes
, shape
)
98 function store_drawing(outfile
, drawing
)
99 outfile
:write('```lines\n')
100 for _
,shape
in ipairs(drawing
.shapes
) do
101 if shape
.mode
== 'freehand' then
102 outfile
:write(json
.encode(shape
))
104 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
105 local line
= json
.encode({mode
=shape
.mode
, p1
=drawing
.points
[shape
.p1
], p2
=drawing
.points
[shape
.p2
]})
108 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
109 local obj
= {mode
=shape
.mode
, vertices
={}}
110 for _
,p
in ipairs(shape
.vertices
) do
111 table.insert(obj
.vertices
, drawing
.points
[p
])
113 local line
= json
.encode(obj
)
116 elseif shape
.mode
== 'circle' then
117 outfile
:write(json
.encode({mode
=shape
.mode
, center
=drawing
.points
[shape
.center
], radius
=shape
.radius
}))
119 elseif shape
.mode
== 'arc' then
120 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
}))
122 elseif shape
.mode
== 'deleted' then
125 assert(false, ('unknown drawing mode %s'):format(shape
.mode
))
128 outfile
:write('```\n')
132 function load_array(a
)
134 local next_line
= ipairs(a
)
135 local i
,line
,drawing
= 0, ''
137 i
,line
= next_line(a
, i
)
138 if i
== nil then break end
140 if line
== '```lines' then -- inflexible with whitespace since these files are always autogenerated
141 --? print('inserting drawing')
142 i
, drawing
= load_drawing_from_array(next_line
, a
, i
)
143 --? print('i now', i)
144 table.insert(result
, drawing
)
146 --? print('inserting text')
147 local line_info
= {mode
='text'}
148 line_info
.data
= line
149 table.insert(result
, line_info
)
153 table.insert(result
, {mode
='text', data
=''})
158 function load_drawing_from_array(iter
, a
, i
)
159 local drawing
= {mode
='drawing', h
=256/2, points
={}, shapes
={}, pending
={}}
163 assert(i
, 'drawing in array is incomplete')
165 if line
== '```' then break end
166 local shape
= json
.decode(line
)
167 if shape
.mode
== 'freehand' then
169 elseif shape
.mode
== 'line' or shape
.mode
== 'manhattan' then
170 local name
= shape
.p1
.name
171 shape
.p1
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p1
.x
, shape
.p1
.y
, --[[large width to minimize overlap]] 1600)
172 drawing
.points
[shape
.p1
].name
= name
174 shape
.p2
= Drawing
.find_or_insert_point(drawing
.points
, shape
.p2
.x
, shape
.p2
.y
, --[[large width to minimize overlap]] 1600)
175 drawing
.points
[shape
.p2
].name
= name
176 elseif shape
.mode
== 'polygon' or shape
.mode
== 'rectangle' or shape
.mode
== 'square' then
177 for i
,p
in ipairs(shape
.vertices
) do
179 shape
.vertices
[i
] = Drawing
.find_or_insert_point(drawing
.points
, p
.x
,p
.y
, --[[large width to minimize overlap]] 1600)
180 drawing
.points
[shape
.vertices
[i]]
.name
= name
182 elseif shape
.mode
== 'circle' or shape
.mode
== 'arc' then
183 local name
= shape
.center
.name
184 shape
.center
= Drawing
.find_or_insert_point(drawing
.points
, shape
.center
.x
,shape
.center
.y
, --[[large width to minimize overlap]] 1600)
185 drawing
.points
[shape
.center
].name
= name
186 elseif shape
.mode
== 'deleted' then
189 assert(false, ('unknown drawing mode %s'):format(shape
.mode
))
191 table.insert(drawing
.shapes
, shape
)
196 function is_absolute_path(path
)
197 local os_path_separator
= package
.config
:sub(1,1)
198 if os_path_separator
== '/' then
199 -- POSIX systems permit backslashes in filenames
200 return path
:sub(1,1) == '/'
201 elseif os_path_separator
== '\\' then
202 if path
:sub(2,2) == ':' then return true end -- DOS drive letter followed by volume separator
203 local f
= path
:sub(1,1)
204 return f
== '/' or f
== '\\'
206 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator
..'"')
210 function is_relative_path(path
)
211 return not is_absolute_path(path
)
214 function dirname(path
)
215 local os_path_separator
= package
.config
:sub(1,1)
216 if os_path_separator
== '/' then
217 -- POSIX systems permit backslashes in filenames
218 return path
:match('.*/') or './'
219 elseif os_path_separator
== '\\' then
220 return path
:match('.*[/\\]') or './'
222 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator
..'"')
226 function test_dirname()
227 check_eq(dirname('a/b'), 'a/', 'F - test_dirname')
228 check_eq(dirname('x'), './', 'F - test_dirname/current')
231 function basename(path
)
232 local os_path_separator
= package
.config
:sub(1,1)
233 if os_path_separator
== '/' then
234 -- POSIX systems permit backslashes in filenames
235 return string.gsub(path
, ".*/(.*)", "%1")
236 elseif os_path_separator
== '\\' then
237 return string.gsub(path
, ".*[/\\](.*)", "%1")
239 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator
..'"')
244 for _
,_
in pairs(h
) do