1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
|
-- class gaCanvas
-- Copyright (C) 2020 Roberto Giacomelli
-- ga -- basic function
local gaCanvas = {
_VERSION = "gacanvas v0.0.4",
_NAME = "gaCanvas",
_DESCRIPTION = "A library for dealing with ga stream",
}
gaCanvas.__index = gaCanvas
-- ga specification: see the file ga-grammar.pdf in the doc directory
-- gaCanvas constructor
function gaCanvas:new() --> object
local o = {
_data = {},
_v = 100, -- version of the ga format
}
setmetatable(o, self)
return o
end
-- Ipothetical another constructor
-- function gaCanvas:from_tcp_server() --> err
-- end
-- line width: opcode <1> <w: DIM>
function gaCanvas:encode_linethick(w) --> err
if type(w) ~= "number" then return "[ArgErr] 'w' number expected" end
if w < 0 then return "[ArgErr] negative value for 'w'" end
local data = self._data
data[#data + 1] = 1 -- opcode for line thickness
data[#data + 1] = w
end
-- line cap style: opcode <2> <cap: u8>
-- 0 Butt cap
-- 1 Round cap
-- 2 Projecting square cap
function gaCanvas:encode_linecap(cap) --> err
if type(cap) ~= "number" then return "[ArgErr] 'cap' arg number expected" end
if cap == 0 or cap == 1 or cap == 2 then
local data = self._data
data[#data + 1] = 2 -- opcode for line cap style
data[#data + 1] = cap
else
return "[ArgErr] invalid value for 'cap'"
end
end
-- line cap style: opcode <3> <join: u8>
-- 0 Miter join
-- 1 Round join
-- 2 Bevel join
function gaCanvas:encode_linejoin(join) --> err
if type(join) ~= "number" then return "[ArgErr] 'join' arg number expected" end
if join == 0 or join == 1 or join == 2 then
local data = self._data
data[#data + 1] = 3 -- opcode for line join style
data[#data + 1] = join
else
return "[ArgErr] invalid value for 'join'"
end
end
-- Stop checking the bounding box
-- opcode: <30>
function gaCanvas:start_bbox_group() --> err
local data = self._data
data[#data + 1] = 30
end
-- restart checking the bounding box
-- and insert the specified bb for the entire object group
-- code: <31> x1 y1 x2 y2
function gaCanvas:stop_bbox_group(x1, y1, x2, y2) --> err
if type(x1) ~= "number" then return "[ArgErr] 'x1' number expected" end
if type(y1) ~= "number" then return "[ArgErr] 'y1' number expected" end
if type(x2) ~= "number" then return "[ArgErr] 'x2' number expected" end
if type(y2) ~= "number" then return "[ArgErr] 'y2' number expected" end
if x1 > x2 then x1, x2 = x2, x1 end -- reorder coordinates
if y1 > y2 then y1, y2 = y2, y1 end
local data = self._data
data[#data + 1] = 31 -- bounding box of the object group
data[#data + 1] = x1
data[#data + 1] = y1
data[#data + 1] = x2
data[#data + 1] = y2
end
-- insert a line from point (x1, y1) to the point (x2, y2)
-- <32> x1 y1 x2 y2
function gaCanvas:encode_line(x1, y1, x2, y2) --> err
if type(x1) ~= "number" then return "[ArgErr] 'x1' number expected" end
if type(y1) ~= "number" then return "[ArgErr] 'y1' number expected" end
if type(x2) ~= "number" then return "[ArgErr] 'x2' number expected" end
if type(y2) ~= "number" then return "[ArgErr] 'y2' number expected" end
local data = self._data
data[#data + 1] = 32 -- append line data
data[#data + 1] = x1
data[#data + 1] = y1
data[#data + 1] = x2
data[#data + 1] = y2
end
-- insert an horizontal line from point (x1, y) to point (x2, y)
-- <33> x1 x2 y
function gaCanvas:encode_hline(x1, x2, y) --> err
if type(x1) ~= "number" then return "[ArgErr] 'x1' number expected" end
if type(x2) ~= "number" then return "[ArgErr] 'x2' number expected" end
if type(y) ~= "number" then return "[ArgErr] 'y2' number expected" end
local data = self._data
data[#data + 1] = 33 -- append hline data
data[#data + 1] = x1
data[#data + 1] = x2
data[#data + 1] = y
end
-- insert a rectangle from point (x1, x2) to (x2, y2)
-- <48> <x1: DIM> <y1: DIM> <x2: DIM> <y2: DIM>
function gaCanvas:encode_rectangle(x1, y1, x2, y2) --> err
if type(x1) ~= "number" then return "[ArgErr] 'x1' number expected" end
if type(y1) ~= "number" then return "[ArgErr] 'y1' number expected" end
if type(x2) ~= "number" then return "[ArgErr] 'x2' number expected" end
if type(y2) ~= "number" then return "[ArgErr] 'y2' number expected" end
local d = self._data
d[#d + 1] = 48 -- append rectangle data
d[#d + 1] = x1
d[#d + 1] = y1
d[#d + 1] = x2
d[#d + 1] = y2
end
-- Vbar object: opcode <36>
-- x0, y1, y2 ordinates
function gaCanvas:encode_Vbar(vbar, x0, y1, y2) --> err
if type(vbar) ~= "table" then
return "[ArgErr] table expected for 'vbar'"
end
if x0 == nil then
x0 = 0
elseif type(x0) ~= "number" then
return "[ArgErr] 'x0' number expected"
end
if type(y1) ~= "number" then return "[ArgErr] 'y1' number expected" end
if type(y2) ~= "number" then return "[ArgErr] 'y2' number expected" end
-- ordinates
if y1 == y2 then return "[ArgErr] 'y1' 'y2' are the same value" end
if y1 > y2 then y1, y2 = y2, y1 end
local bars = assert(vbar._yline, "[InternalErr] no '_yline' field")
local bdim = #bars
if bdim == 0 then return "[InternalErr] number of bars is zero" end
if bdim % 2 ~= 0 then
return "[InternalErr] '_yline' does not have an even number of elements"
end
local data = self._data
data[#data + 1] = 36 -- vbar sequence start
data[#data + 1] = y1
data[#data + 1] = y2
data[#data + 1] = bdim / 2 -- the number of bars <x_i t_i>
for i = 1, bdim, 2 do
local coord = bars[i]
local width = bars[i + 1]
if type(coord) ~= "number" then
return "[Err] a coordinates is not a number"
end
if type(width) ~= "number" then
return "[Err] a width is not a number"
end
data[#data + 1] = coord + x0
data[#data + 1] = width
end
end
-- [text] <130> ax ay x y chars
function gaCanvas:encode_Text(txt, xpos, ypos, ax, ay) --> err
if type(txt) ~= "table" then
return "[ArgErr] 'txt' object table expected"
end
if type(xpos) ~= "number" then
return "[ArgErr] 'xpos' number expected"
end
if type(ypos) ~= "number" then
return "[ArgErr] 'ypos' number expected"
end
if ax == nil then
ax = 0
elseif type(ax) ~= "number" then
return "[ArgErr] 'ax' number expected"
end
if ay == nil then
ay = 0
elseif type(ay) ~= "number" then
return "[ArgErr] 'ay' number expected"
end
local data = self._data
data[#data + 1] = 130
data[#data + 1] = ax -- relative anchor x-coordinate
data[#data + 1] = ay -- relative anchor y-coordinate
data[#data + 1] = xpos -- text x-coordinate
data[#data + 1] = ypos -- text y-coordinate
local chars = assert(txt.codepoint, "[InternalErr] no 'codepoint' field in txt")
if #chars == 0 then return "[InternalErr] 'txt' has no chars" end
for _, c in ipairs(chars) do
data[#data + 1] = c
end
data[#data + 1] = 0 -- end string signal
end
-- glyphs equally spaced along the baseline
-- [text_xspaced] <131> x1 xgap ay ypos chars
function gaCanvas:encode_Text_xspaced(txt, x1, xgap, ypos, ay) --> err
if type(txt)~= "table" then return "[ArgErr] 'txt' object table expected" end
local chars = assert(txt.codepoint, "[InternalErr] no 'codepoint' field in txt")
if #chars == 0 then return "[InternalErr] 'txt' has no chars" end
if type(x1) ~= "number" then return "[ArgErr] 'x1' number expected" end
if type(xgap) ~= "number" then return "[ArgErr] 'xgap' number expected" end
if xgap < 0 then
local n = #chars
x1 = x1 + (n - 1) * xgap
xgap = -xgap
end
if type(ypos) ~= "number" then return "[ArgErr] 'ypos' number expected" end
if ay == nil then
ay = 0
elseif type(ay) ~= "number" then
return "[ArgErr] 'ay' number expected"
end
local data = self._data
data[#data + 1] = 131
data[#data + 1] = x1 -- x-coordinate of the first axis from left to right
data[#data + 1] = xgap -- axial distance among gliphs
data[#data + 1] = ay -- anchor relative y-coordinate
data[#data + 1] = ypos -- text y-coordinate
for _, c in ipairs(chars) do
data[#data + 1] = c
end
data[#data + 1] = 0 -- end string signal
end
-- text_xwidth
-- text equally spaced but within [x1, x2] coordinate interval
-- <132> <ay: FLOAT> <x1: DIM> <x2: DIM> <y: DIM> <c: CHARS>
function gaCanvas:encode_Text_xwidth(txt, x1, x2, ypos, ay) --> err
if type(txt)~= "table" then return "[ArgErr] 'txt' object table expected" end
if type(x1) ~= "number" then return "[ArgErr] 'x1' number expected" end
if type(x2) ~= "number" then return "[ArgErr] 'x2' number expected" end
if type(ypos) ~= "number" then return "[ArgErr] 'ypos' number expected" end
if ay == nil then
ay = 0
elseif type(ay) ~= "number" then
return "[ArgErr] 'ay' number expected"
end
local chars = assert(txt.codepoint, "[InternalErr] no 'codepoint' field in txt")
if #chars == 0 then return "[InternalErr] 'txt' has no chars" end
if x1 > x2 then x1, x2 = x2, x1 end -- reorder coordinates
local data = self._data
data[#data + 1] = 132
data[#data + 1] = ay -- anchor relative y-coordinate
data[#data + 1] = x1 -- left limit of the text box
data[#data + 1] = x2 -- right limit of the text box
data[#data + 1] = ypos -- text y-coordinate
for _, c in ipairs(chars) do
data[#data + 1] = c
end
data[#data + 1] = 0 -- end string signal
end
-- experimental code section
-- new opcodes under assessment
-- [start_text_group] 140
function gaCanvas:start_text_group() --> err
local data = self._data
data[#data + 1] = 140
end
-- [gtext] 141
function gaCanvas:gtext(chars) --> err
if type(chars) ~= "table" then return "[ArgErr] 'chars' table expected" end
local data = self._data
data[#data + 1] = 141
for _, c in ipairs(chars) do
data[#data + 1] = c
end
data[#data + 1] = 0 -- end string signal
end
-- [gtext_spaced] 142 gap string
function gaCanvas:gtext_spaced(gap, chars) --> err
if type(gap) ~= "number" then return "[ArgErr] 'gap' number expected" end
if type(chars) ~= "table" then return "[ArgErr] 'chars' table expected" end
local data = self._data
data[#data + 1] = 142
data[#data + 1] = gap
for _, c in ipairs(chars) do
data[#data + 1] = c
end
data[#data + 1] = 0 -- end string signal
end
-- [gtext_space] 143 gap
function gaCanvas:gtext_gap(gap) --> err
if type(gap) ~= "number" then return "[ArgErr] 'gap' number expected" end
local data = self._data
data[#data + 1] = 143
data[#data + 1] = gap
end
-- [end_text_group] 149 ax ay x y
function gaCanvas:end_text_group(xpos, ypos, ax, ay) --> err
if type(xpos) ~= "number" then return "[ArgErr] 'xpos' number expected" end
if type(ypos) ~= "number" then return "[ArgErr] 'ypos' number expected" end
if type(ax) ~= "number" then return "[ArgErr] 'ax' number expected" end
if type(ay) ~= "number" then return "[ArgErr] 'ay' number expected" end
local data = self._data
data[#data + 1] = 149
data[#data + 1] = ax -- anchor relative x-coordinate
data[#data + 1] = ay -- anchor relative y-coordinate
data[#data + 1] = xpos -- text x-coordinate
data[#data + 1] = ypos -- text y-coordinate
end
-- amazing...
function gaCanvas:to_string() --> string
end
function gaCanvas:get_bbox()
end
function gaCanvas:check() --> boolean, err
end
return gaCanvas
|