bugfix: never use utf8 pos in string.sub
[view.love.git] / file.lua
blob2140bb15fee0f6949af8f22dca9afec52bd9c67c
1 -- primitives for saving to file and loading from file
2 function file_exists(filename)
3 local infile = App.open_for_reading(filename)
4 if infile then
5 infile:close()
6 return true
7 else
8 return false
9 end
10 end
12 function load_from_disk(State)
13 local infile = App.open_for_reading(State.filename)
14 State.lines = load_from_file(infile)
15 if infile then infile:close() end
16 end
18 function load_from_file(infile)
19 local result = {}
20 if infile then
21 local infile_next_line = infile:lines() -- works with both Lua files and LÖVE Files (https://www.love2d.org/wiki/File)
22 while true do
23 local line = infile_next_line()
24 if line == nil then break end
25 if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
26 table.insert(result, load_drawing(infile_next_line))
27 else
28 table.insert(result, {mode='text', data=line})
29 end
30 end
31 end
32 if #result == 0 then
33 table.insert(result, {mode='text', data=''})
34 end
35 return result
36 end
38 function save_to_disk(State)
39 local outfile = App.open_for_writing(State.filename)
40 if outfile == nil then
41 error('failed to write to "'..State.filename..'"')
42 end
43 for _,line in ipairs(State.lines) do
44 if line.mode == 'drawing' then
45 store_drawing(outfile, line)
46 else
47 outfile:write(line.data)
48 outfile:write('\n')
49 end
50 end
51 outfile:close()
52 end
54 function load_drawing(infile_next_line)
55 local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
56 while true do
57 local line = infile_next_line()
58 assert(line)
59 if line == '```' then break end
60 local shape = json.decode(line)
61 if shape.mode == 'freehand' then
62 -- no changes needed
63 elseif shape.mode == 'line' or shape.mode == 'manhattan' then
64 local name = shape.p1.name
65 shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
66 drawing.points[shape.p1].name = name
67 name = shape.p2.name
68 shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
69 drawing.points[shape.p2].name = name
70 elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
71 for i,p in ipairs(shape.vertices) do
72 local name = p.name
73 shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
74 drawing.points[shape.vertices[i]].name = name
75 end
76 elseif shape.mode == 'circle' or shape.mode == 'arc' then
77 local name = shape.center.name
78 shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
79 drawing.points[shape.center].name = name
80 elseif shape.mode == 'deleted' then
81 -- ignore
82 else
83 print(shape.mode)
84 assert(false)
85 end
86 table.insert(drawing.shapes, shape)
87 end
88 return drawing
89 end
91 function store_drawing(outfile, drawing)
92 outfile:write('```lines\n')
93 for _,shape in ipairs(drawing.shapes) do
94 if shape.mode == 'freehand' then
95 outfile:write(json.encode(shape), '\n')
96 elseif shape.mode == 'line' or shape.mode == 'manhattan' then
97 local line = json.encode({mode=shape.mode, p1=drawing.points[shape.p1], p2=drawing.points[shape.p2]})
98 outfile:write(line, '\n')
99 elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
100 local obj = {mode=shape.mode, vertices={}}
101 for _,p in ipairs(shape.vertices) do
102 table.insert(obj.vertices, drawing.points[p])
104 local line = json.encode(obj)
105 outfile:write(line, '\n')
106 elseif shape.mode == 'circle' then
107 outfile:write(json.encode({mode=shape.mode, center=drawing.points[shape.center], radius=shape.radius}), '\n')
108 elseif shape.mode == 'arc' then
109 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')
110 elseif shape.mode == 'deleted' then
111 -- ignore
112 else
113 print(shape.mode)
114 assert(false)
117 outfile:write('```\n')
120 -- for tests
121 function load_array(a)
122 local result = {}
123 local next_line = ipairs(a)
124 local i,line,drawing = 0, ''
125 while true do
126 i,line = next_line(a, i)
127 if i == nil then break end
128 --? print(line)
129 if line == '```lines' then -- inflexible with whitespace since these files are always autogenerated
130 --? print('inserting drawing')
131 i, drawing = load_drawing_from_array(next_line, a, i)
132 --? print('i now', i)
133 table.insert(result, drawing)
134 else
135 --? print('inserting text')
136 table.insert(result, {mode='text', data=line})
139 if #result == 0 then
140 table.insert(result, {mode='text', data=''})
142 return result
145 function load_drawing_from_array(iter, a, i)
146 local drawing = {mode='drawing', h=256/2, points={}, shapes={}, pending={}}
147 local line
148 while true do
149 i, line = iter(a, i)
150 assert(i)
151 --? print(i)
152 if line == '```' then break end
153 local shape = json.decode(line)
154 if shape.mode == 'freehand' then
155 -- no changes needed
156 elseif shape.mode == 'line' or shape.mode == 'manhattan' then
157 local name = shape.p1.name
158 shape.p1 = Drawing.find_or_insert_point(drawing.points, shape.p1.x, shape.p1.y, --[[large width to minimize overlap]] 1600)
159 drawing.points[shape.p1].name = name
160 name = shape.p2.name
161 shape.p2 = Drawing.find_or_insert_point(drawing.points, shape.p2.x, shape.p2.y, --[[large width to minimize overlap]] 1600)
162 drawing.points[shape.p2].name = name
163 elseif shape.mode == 'polygon' or shape.mode == 'rectangle' or shape.mode == 'square' then
164 for i,p in ipairs(shape.vertices) do
165 local name = p.name
166 shape.vertices[i] = Drawing.find_or_insert_point(drawing.points, p.x,p.y, --[[large width to minimize overlap]] 1600)
167 drawing.points[shape.vertices[i]].name = name
169 elseif shape.mode == 'circle' or shape.mode == 'arc' then
170 local name = shape.center.name
171 shape.center = Drawing.find_or_insert_point(drawing.points, shape.center.x,shape.center.y, --[[large width to minimize overlap]] 1600)
172 drawing.points[shape.center].name = name
173 elseif shape.mode == 'deleted' then
174 -- ignore
175 else
176 print(shape.mode)
177 assert(false)
179 table.insert(drawing.shapes, shape)
181 return i, drawing
184 function is_absolute_path(path)
185 local os_path_separator = package.config:sub(1,1)
186 if os_path_separator == '/' then
187 -- POSIX systems permit backslashes in filenames
188 return path:sub(1,1) == '/'
189 elseif os_path_separator == '\\' then
190 if path:sub(2,2) == ':' then return true end -- DOS drive letter followed by volume separator
191 local f = path:sub(1,1)
192 return f == '/' or f == '\\'
193 else
194 error('What OS is this? LÖVE reports that the path separator is "'..os_path_separator..'"')
198 function is_relative_path(path)
199 return not is_absolute_path(path)