1if not modules then modules = { } end modules ['mlib-svg'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to mlib-ctx.mkiv",
5 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6 copyright = "PRAGMA ADE / ConTeXt Development Team",
7 license = "see context related readme files",
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
93local rawget, rawset, type, tonumber, tostring, next, setmetatable = rawget, rawset, type, tonumber, tostring, next, setmetatable
94
95local P, S, R, C, Ct, Cs, Cc, Cp, Cg, Cf, Carg = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Ct, lpeg.Cs, lpeg.Cc, lpeg.Cp, lpeg.Cg, lpeg.Cf, lpeg.Carg
96
97local lpegmatch, lpegpatterns = lpeg.match, lpeg.patterns
98local sqrt, abs = math.sqrt, math.abs
99local concat, setmetatableindex, sortedhash = table.concat, table.setmetatableindex, table.sortedhash
100local gmatch, gsub, find, match = string.gmatch, string.gsub, string.find, string.match
101local formatters, fullstrip = string.formatters, string.fullstrip
102local utfsplit, utfbyte = utf.split, utf.byte
103
104local xmlconvert, xmlcollected, xmlcount, xmlfirst, xmlroot = xml.convert, xml.collected, xml.count, xml.first, xml.root
105local xmltext, xmltextonly = xml.text, xml.textonly
106local css = xml.css or { }
107
108local function xmlinheritattributes(c,pa)
109 if not c.special then
110 local at = c.at
111 local dt = c.dt
112 if at and dt then
113 if pa then
114 setmetatableindex(at,pa)
115 end
116 for i=1,#dt do
117 local dti = dt[i]
118 if type(dti) == "table" then
119 xmlinheritattributes(dti,at)
120 end
121 end
122 end
123 end
124end
125
126xml.inheritattributes = xmlinheritattributes
127
128
129
130metapost = metapost or { }
131local metapost = metapost
132local context = context
133
134local report = logs.reporter("metapost","svg")
135
136local trace = false trackers.register("metapost.svg", function(v) trace = v end)
137local trace_text = false trackers.register("metapost.svg.text", function(v) trace_text = v end)
138local trace_path = false trackers.register("metapost.svg.path", function(v) trace_path = v end)
139local trace_result = false trackers.register("metapost.svg.result", function(v) trace_result = v end)
140local trace_colors = false trackers.register("metapost.svg.colors", function(v) trace_colors = v end)
141local trace_fonts = false trackers.register("metapost.svg.fonts", function(v) trace_fonts = v end)
142
143
144
145local s_draw_image_start <const> = "draw image ("
146local s_draw_image_stop <const> = ") ;"
147
148local ignoredopacity = 1
149
150local svghash = false do
151
152 local svglast = 0
153 local svglist = false
154
155 local function checkhash(t,k)
156 local n = svglast + 1
157 svglast = n
158 svglist[n] = k
159 t[k] = n
160 return n
161 end
162
163 function metapost.startsvghashing()
164 svglast = 0
165 svglist = { }
166 svghash = setmetatableindex(checkhash)
167 end
168
169 function metapost.stopsvghashing()
170 svglast = 0
171 svglist = false
172 svghash = false
173 end
174
175 interfaces.implement {
176 name = "svghashed",
177 arguments = "integer",
178 actions = function(n)
179 local t = svglist and svglist[n]
180 if t then
181 context(t)
182 end
183 end
184 }
185
186end
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201local a2c do
202
203 local pi, sin, cos, tan, asin, abs = math.pi, math.sin, math.cos, math.tan, math.asin, math.abs
204
205 local d120 = (pi * 120) / 180
206 local pi2 = 2 * pi
207
208 a2c = function(x1, y1, rx, ry, angle, large, sweep, x2, y2, f1, f2, cx, cy)
209
210 if (rx == 0 or ry == 0) or (x1 == x2 and y1 == y2) then
211 return { x1, y1, x2, y2, x2, y2 }
212 end
213
214 local recursive = f1
215 local rad = pi / 180 * angle
216 local res = nil
217 local cosrad = cos(-rad)
218 local sinrad = sin(-rad)
219
220 if not recursive then
221
222 x1, y1 = x1 * cosrad - y1 * sinrad, x1 * sinrad + y1 * cosrad
223 x2, y2 = x2 * cosrad - y2 * sinrad, x2 * sinrad + y2 * cosrad
224
225 local x = (x1 - x2) / 2
226 local y = (y1 - y2) / 2
227 local xx = x * x
228 local yy = y * y
229 local h = xx / (rx * rx) + yy / (ry * ry)
230
231 if h > 1 then
232 h = sqrt(h)
233 rx = h * rx
234 ry = h * ry
235 end
236
237 local rx2 = rx * rx
238 local ry2 = ry * ry
239 local ry2xx = ry2 * xx
240 local rx2yy = rx2 * yy
241 local total = rx2yy + ry2xx
242
243 local k = total == 0 and 0 or sqrt(abs((rx2 * ry2 - rx2yy - ry2xx) / total))
244
245 if large == sweep then
246 k = -k
247 end
248
249 cx = k * rx * y / ry + (x1 + x2) / 2
250 cy = k * -ry * x / rx + (y1 + y2) / 2
251
252 f1 = (y1 - cy) / ry
253 f2 = (y2 - cy) / ry
254
255 f1 = asin((f1 < -1.0 and -1.0) or (f1 > 1.0 and 1.0) or f1)
256 f2 = asin((f2 < -1.0 and -1.0) or (f2 > 1.0 and 1.0) or f2)
257
258 if x1 < cx then f1 = pi - f1 end
259 if x2 < cx then f2 = pi - f2 end
260
261 if f1 < 0 then f1 = pi2 + f1 end
262 if f2 < 0 then f2 = pi2 + f2 end
263
264 if sweep ~= 0 and f1 > f2 then f1 = f1 - pi2 end
265 if sweep == 0 and f2 > f1 then f2 = f2 - pi2 end
266
267 end
268
269 if abs(f2 - f1) > d120 then
270 local f2old = f2
271 local x2old = x2
272 local y2old = y2
273 f2 = f1 + d120 * ((sweep ~= 0 and f2 > f1) and 1 or -1)
274 x2 = cx + rx * cos(f2)
275 y2 = cy + ry * sin(f2)
276 res = a2c(x2, y2, rx, ry, angle, 0, sweep, x2old, y2old, f2, f2old, cx, cy)
277 end
278
279 local c1 = cos(f1)
280 local s1 = sin(f1)
281 local c2 = cos(f2)
282 local s2 = sin(f2)
283
284 local t = tan((f2 - f1) / 4)
285 local hx = 4 * rx * t / 3
286 local hy = 4 * ry * t / 3
287
288 local r = { x1 - hx * s1, y1 + hy * c1, x2 + hx * s2, y2 - hy * c2, x2, y2, unpack(res or { }) }
289
290 if not recursive then
291 cosrad = cos(rad)
292 sinrad = sin(rad)
293
294
295 for i0=1,#r,2 do
296 local i1 = i0 + 1
297 local x = r[i0]
298 local y = r[i1]
299 r[i0] = x * cosrad - y * sinrad
300 r[i1] = x * sinrad + y * cosrad
301 end
302 end
303
304 return r
305 end
306
307end
308
309
310
311local p_digit = lpegpatterns.digit
312local p_hexdigit = lpegpatterns.hexdigit
313local p_space = lpegpatterns.whitespace
314
315local factors = {
316 ["pt"] = 1.25,
317 ["mm"] = 3.543307,
318 ["cm"] = 35.43307,
319 ["px"] = 1,
320 ["pc"] = 15,
321 ["in"] = 90,
322 ["em"] = 12 * 1.25,
323 ["ex"] = 8 * 1.25,
324 ["%"] = 0.1,
325 ["bp"] = 1,
326}
327
328metapost.svgfactors = factors
329
330local percentage_r = 1/100
331local percentage_x = percentage_r
332local percentage_y = percentage_r
333
334local asnumber, asnumber_r, asnumber_x, asnumber_y, asnumber_vx, asnumber_vy
335local asnumber_vx_t, asnumber_vy_t
336local p_number, p_separator, p_optseparator, p_numbers, p_fournumbers, p_path
337local p_number_n, p_number_x, p_number_vx, p_number_y, p_number_vy, p_number_r
338
339do
340
341
342
343 local p_command_x = C(S("Hh"))
344 local p_command_y = C(S("Vv"))
345 local p_command_xy = C(S("CcLlMmQqSsTt"))
346 local p_command_a = C(S("Aa"))
347 local p_command = C(S("Zz"))
348
349 p_optseparator = S("\t\n\r ,")^0
350 p_separator = S("\t\n\r ,")^1
351 p_number = (S("+-")^0 * (p_digit^0 * P(".") * p_digit^1 + p_digit^1 * P(".") + p_digit^1))
352 * (P("e") * S("+-")^0 * p_digit^1)^-1
353
354 local function convert (n) n = tonumber(n) return n end
355 local function convert_p (n,u) n = tonumber(n) if u == true then return n / 100 else return n end end
356 local function convert_r (n,u) n = tonumber(n) if u == true then return percentage_r * n elseif u then return u * n else return n end end
357 local function convert_x (n,u) n = tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end
358 local function convert_y (n,u) n = tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end
359 local function convert_vx(n,u) n = tonumber(n) if u == true then return percentage_x * n elseif u then return u * n else return n end end
360 local function convert_vy(n,u) n = - tonumber(n) if u == true then return percentage_y * n elseif u then return u * n else return n end end
361
362 local p_unit = (P("p") * S("txc") + P("e") * S("xm") + S("mc") * P("m") + P("in")) / factors
363 local p_percent = P("%") * Cc(true)
364
365 local c_number_n = C(p_number)
366 local c_number_u = C(p_number) * (p_percent + p_unit)^-1
367
368 p_number_n = c_number_n / convert
369 p_number_u = c_number_u / convert
370 p_number_x = c_number_u / convert_x
371 p_number_vx = c_number_u / convert_vx
372 p_number_y = c_number_u / convert_y
373 p_number_vy = c_number_u / convert_vy
374 p_number_r = c_number_u / convert_r
375 p_number_p = c_number_u / convert_p
376
377 asnumber = function(s) return s and lpegmatch(p_number, s) or 0 end
378 asnumber_r = function(s) return s and lpegmatch(p_number_r, s) or 0 end
379 asnumber_p = function(s) return s and lpegmatch(p_number_p, s) or 0 end
380 asnumber_x = function(s) return s and lpegmatch(p_number_x, s) or 0 end
381 asnumber_y = function(s) return s and lpegmatch(p_number_y, s) or 0 end
382 asnumber_vx = function(s) return s and lpegmatch(p_number_vx,s) or 0 end
383 asnumber_vy = function(s) return s and lpegmatch(p_number_vy,s) or 0 end
384
385 local p_number_vx_t = Ct { (p_number_vx + p_separator)^1 }
386 local p_number_vy_t = Ct { (p_number_vy + p_separator)^1 }
387
388 local zerotable = { 0 }
389
390 asnumber_vx_t = function(s) return s and lpegmatch(p_number_vx_t,s) or zerotable end
391 asnumber_vy_t = function(s) return s and lpegmatch(p_number_vy_t,s) or zerotable end
392
393
394 local p_numbersep = p_number_u + p_separator
395 p_numbers = p_optseparator * P("(") * p_numbersep^0 * p_optseparator * P(")")
396 p_fournumbers = p_numbersep^4
397 p_path = Ct ( (
398 p_command_xy * (p_optseparator * p_number_vx *
399 p_optseparator * p_number_vy )^1
400 + p_command_x * (p_optseparator * p_number_vx )^1
401 + p_command_y * (p_optseparator * p_number_vy )^1
402 + p_command_a * (p_optseparator * p_number_vx *
403 p_optseparator * p_number_vy *
404 p_optseparator * p_number_r *
405 p_optseparator * p_number_n *
406 p_optseparator * p_number_n *
407 p_optseparator * p_number_vx *
408 p_optseparator * p_number_vy )^1
409 + p_command
410 + p_separator
411 )^1 )
412
413
414end
415
416
417
418
419
420
421
422
423
424
425local colormap = false
426
427local function prepared(t)
428 if type(t) == "table" then
429 local mapping = t.mapping or { }
430 local mapper = t.mapper
431 local colormap = setmetatableindex(mapping)
432 if mapper then
433 setmetatableindex(colormap,function(t,k)
434 local v = mapper(k)
435 t[k] = v or k
436 return v
437 end)
438 end
439 return colormap
440 else
441 return false
442 end
443end
444
445local colormaps = setmetatableindex(function(t,k)
446 local v = false
447 if type(k) == "string" then
448 v = prepared(table.load(k))
449 elseif type(k) == "table" then
450 v = prepared(k)
451 k = k.name or k
452 end
453 t[k] = v
454 return v
455end)
456
457function metapost.svgcolorremapper(colormap)
458 return colormaps[colormap]
459end
460
461
462
463local colorcomponents, withcolor, thecolor, usedcolors do
464
465 local svgcolors = {
466 aliceblue = 0xF0F8FF, antiquewhite = 0xFAEBD7, aqua = 0x00FFFF, aquamarine = 0x7FFFD4,
467 azure = 0xF0FFFF, beige = 0xF5F5DC, bisque = 0xFFE4C4, black = 0x000000,
468 blanchedalmond = 0xFFEBCD, blue = 0x0000FF, blueviolet = 0x8A2BE2, brown = 0xA52A2A,
469 burlywood = 0xDEB887, cadetblue = 0x5F9EA0, hartreuse = 0x7FFF00, chocolate = 0xD2691E,
470 coral = 0xFF7F50, cornflowerblue = 0x6495ED, cornsilk = 0xFFF8DC, crimson = 0xDC143C,
471 cyan = 0x00FFFF, darkblue = 0x00008B, darkcyan = 0x008B8B, darkgoldenrod = 0xB8860B,
472 darkgray = 0xA9A9A9, darkgreen = 0x006400, darkgrey = 0xA9A9A9, darkkhaki = 0xBDB76B,
473 darkmagenta = 0x8B008B, darkolivegreen = 0x556B2F, darkorange = 0xFF8C00, darkorchid = 0x9932CC,
474 darkred = 0x8B0000, darksalmon = 0xE9967A, darkseagreen = 0x8FBC8F, darkslateblue = 0x483D8B,
475 darkslategray = 0x2F4F4F, darkslategrey = 0x2F4F4F, darkturquoise = 0x00CED1, darkviolet = 0x9400D3,
476 deeppink = 0xFF1493, deepskyblue = 0x00BFFF, dimgray = 0x696969, dimgrey = 0x696969,
477 dodgerblue = 0x1E90FF, firebrick = 0xB22222, floralwhite = 0xFFFAF0, forestgreen = 0x228B22,
478 fuchsia = 0xFF00FF, gainsboro = 0xDCDCDC, ghostwhite = 0xF8F8FF, gold = 0xFFD700,
479 goldenrod = 0xDAA520, gray = 0x808080, green = 0x008000, greenyellow = 0xADFF2F,
480 grey = 0x808080, honeydew = 0xF0FFF0, hotpink = 0xFF69B4, indianred = 0xCD5C5C,
481 indigo = 0x4B0082, ivory = 0xFFFFF0, khaki = 0xF0E68C, lavender = 0xE6E6FA,
482 lavenderblush = 0xFFF0F5, lawngreen = 0x7CFC00, lemonchiffon = 0xFFFACD, lightblue = 0xADD8E6,
483 lightcoral = 0xF08080, lightcyan = 0xE0FFFF, lightgoldenrodyellow = 0xFAFAD2, lightgray = 0xD3D3D3,
484 lightgreen = 0x90EE90, lightgrey = 0xD3D3D3, lightpink = 0xFFB6C1, lightsalmon = 0xFFA07A,
485 lightseagreen = 0x20B2AA, lightskyblue = 0x87CEFA, lightslategray = 0x778899, lightslategrey = 0x778899,
486 lightsteelblue = 0xB0C4DE, lightyellow = 0xFFFFE0, lime = 0x00FF00, limegreen = 0x32CD32,
487 linen = 0xFAF0E6, magenta = 0xFF00FF, maroon = 0x800000, mediumaquamarine = 0x66CDAA,
488 mediumblue = 0x0000CD, mediumorchid = 0xBA55D3, mediumpurple = 0x9370DB, mediumseagreen = 0x3CB371,
489 mediumslateblue = 0x7B68EE, mediumspringgreen = 0x00FA9A, mediumturquoise = 0x48D1CC, mediumvioletred = 0xC71585,
490 midnightblue = 0x191970, mintcream = 0xF5FFFA, mistyrose = 0xFFE4E1, moccasin = 0xFFE4B5,
491 navajowhite = 0xFFDEAD, navy = 0x000080, oldlace = 0xFDF5E6, olive = 0x808000,
492 olivedrab = 0x6B8E23, orange = 0xFFA500, orangered = 0xFF4500, orchid = 0xDA70D6,
493 palegoldenrod = 0xEEE8AA, palegreen = 0x98FB98, paleturquoise = 0xAFEEEE, palevioletred = 0xDB7093,
494 papayawhip = 0xFFEFD5, peachpuff = 0xFFDAB9, peru = 0xCD853F, pink = 0xFFC0CB,
495 plum = 0xDDA0DD, powderblue = 0xB0E0E6, purple = 0x800080, red = 0xFF0000,
496 rosybrown = 0xBC8F8F, royalblue = 0x4169E1, saddlebrown = 0x8B4513, salmon = 0xFA8072,
497 sandybrown = 0xF4A460, seagreen = 0x2E8B57, seashell = 0xFFF5EE, sienna = 0xA0522D,
498 silver = 0xC0C0C0, skyblue = 0x87CEEB, slateblue = 0x6A5ACD, slategray = 0x708090,
499 slategrey = 0x708090, snow = 0xFFFAFA, springgreen = 0x00FF7F, steelblue = 0x4682B4,
500 tan = 0xD2B48C, teal = 0x008080, thistle = 0xD8BFD8, tomato = 0xFF6347,
501 turquoise = 0x40E0D0, violet = 0xEE82EE, wheat = 0xF5DEB3, white = 0xFFFFFF,
502 whitesmoke = 0xF5F5F5, yellow = 0xFFFF00, yellowgreen = 0x9ACD32,
503 }
504
505 local f_rgb = formatters[' withcolor svgcolor(%.3N,%.3N,%.3N)']
506 local f_cmyk = formatters[' withcolor svgcmyk(%.3N,%.3N,%.3N,%.3N)']
507 local f_gray = formatters[' withcolor svggray(%.3N)']
508 local f_rgba = formatters[' withcolor svgcolor(%.3N,%.3N,%.3N) withopacity %.3N']
509 local f_graya = formatters[' withcolor svggray(%.3N) withopacity %.3N']
510 local f_name = formatters[' withcolor "%s"']
511 local f_svgrgb = formatters['svgcolor(%.3N,%.3N,%.3N)']
512 local f_svgcmyk = formatters['svgcmyk(%.3N,%.3N,%.3N,%.3N)']
513 local f_svggray = formatters['svggray(%.3N)']
514 local f_svgname = formatters['"%s"']
515
516 local triplets = setmetatableindex(function(t,k)
517
518 local v = svgcolors[k]
519 if v then
520 v = { ((v>>16)&0xFF)/0xFF, ((v>>8)&0xFF)/0xFF, ((v>>0)&0xFF)/0xFF }
521 else
522 v = false
523 end
524 t[k] = v
525 return v
526 end)
527
528 local p_fraction = C(p_number) * C("%")^-1 / function(a,b) return tonumber(a) / (b and 100 or 255) end
529 local p_angle = C(p_number) * P("deg")^0 / function(a) return tonumber(a) end
530 local p_percent = C(p_number) * P("%") / function(a) return tonumber(a) / 100 end
531 local p_absolute = C(p_number) / tonumber
532
533 local p_left = P("(")
534 local p_right = P(")")
535 local p_a = P("a")^-1
536 local p_r_a_color = p_left
537 * (p_fraction * p_separator^-1)^-3
538 * p_absolute^0
539 * p_right
540 local p_c_k_color = p_left
541 * (p_absolute + p_separator^-1)^-4
542 * p_right
543 local p_h_a_color = p_left
544 * p_angle
545 * p_separator * p_percent
546 * p_separator * p_percent
547 * p_separator^0 * p_absolute^0
548 * p_right
549
550 local colors = attributes.colors
551 local colorvalues = colors.values
552 local colorindex = attributes.list[attributes.private('color')]
553 local hsvtorgb = colors.hsvtorgb
554 local hwbtorgb = colors.hwbtorgb
555 local forcedmodel = colors.forcedmodel
556
557 local p_splitcolor =
558 P("#") * C(p_hexdigit*p_hexdigit)^1 / function(r,g,b)
559 if not r then
560 return "gray", 0
561 elseif not (g and b) then
562 return "gray",
563 (r == "00" and 0) or (r == "ff" and 1) or (tonumber(r,16)/255)
564 else
565 return "rgb",
566 (r == "00" and 0) or (r == "ff" and 1) or (tonumber(r,16)/255),
567 (g == "00" and 0) or (g == "ff" and 1) or (tonumber(g,16)/255),
568 (b == "00" and 0) or (b == "ff" and 1) or (tonumber(b,16)/255)
569 end
570 end
571 + P("rgb") * p_a
572 * p_r_a_color / function(r,g,b,a)
573 return "rgb", r or 0, g or 0, b or 0, a or false
574 end
575 + P("cmyk")
576 * p_c_k_color / function(c,m,y,k)
577 return "cmyk", c or 0, m or 0, y or 0, k or 0
578 end
579 + P("hsl") * p_a
580 * p_h_a_color / function(h,s,l,a)
581 local r, g, b = hsvtorgb(h,s,l,a)
582 return "rgb", r or 0, g or 0, b or 0, a or false
583 end
584 + P("hwb") * p_a
585 * p_h_a_color / function(h,w,b,a)
586 local r, g, b = hwbtorgb(h,w,b)
587 return "rgb", r or 0, g or 0, b or 0, a or false
588 end
589
590 function metapost.svgsplitcolor(color)
591 if type(color) == "string" then
592 local what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color)
593 if not what then
594 local t = triplets[color]
595 if t then
596 what, s1, s2, s3 = "rgb", t[1], t[2], t[3]
597 end
598 end
599 return what, s1, s2, s3, s4
600 else
601 return "gray", 0, false
602 end
603 end
604
605 local function registeredcolor(name)
606 local color = colorindex[name]
607 if color then
608 local v = colorvalues[color]
609 local t = forcedmodel(v[1])
610 if t == 2 then
611 return "gray", v[2]
612 elseif t == 3 then
613 return "rgb", v[3], v[4], v[5]
614 elseif t == 4 then
615 return "cmyk", v[6], v[7], v[8], v[9]
616 else
617
618 end
619 end
620 end
621
622
623
624 local function validcolor(color)
625 if usedcolors then
626 usedcolors[color] = usedcolors[color] + 1
627 end
628 if colormap then
629 local c = colormap[color]
630 local t = type(c)
631 if t == "table" then
632 local what = t[1]
633 if what == "rgb" then
634 return
635 what,
636 tonumber(t[2]) or 0,
637 tonumber(t[3]) or 0,
638 tonumber(t[4]) or 0,
639 tonumber(t[5]) or false
640 elseif what == "cmyk" then
641 return
642 what,
643 tonumber(t[2]) or 0,
644 tonumber(t[3]) or 0,
645 tonumber(t[4]) or 0,
646 tonumber(t[5]) or 0
647 elseif what == "gray" then
648 return
649 what,
650 tonumber(t[2]) or 0,
651 tonumber(t[3]) or false
652 end
653 elseif t == "string" then
654 color = c
655 end
656 end
657 if color == "#000000" then
658 return "rgb", 0, 0, 0
659 elseif color == "#ffffff" then
660 return "rgb", 1, 1, 1
661 else
662 local what, s1, s2, s3, s4 = registeredcolor(color)
663 if not what then
664 what, s1, s2, s3, s4 = lpegmatch(p_splitcolor,color)
665
666 if not what then
667 local t = triplets[color]
668 if t then
669 s1, s2, s3 = t[1], t[2], t[3]
670 what = "rgb"
671 end
672 end
673 end
674 return what, s1, s2, s3, s4
675 end
676 end
677
678 colorcomponents = function(color)
679 local what, s1, s2, s3, s4 = validcolor(color)
680 return s1, s2, s3, s4
681 end
682
683 withcolor = function(color)
684 local what, s1, s2, s3, s4 = validcolor(color)
685 if what == "rgb" then
686 if s4 then
687 if s1 == s2 and s1 == s3 then
688 return f_graya(s1,s4)
689 else
690 return f_rgba(s1,s2,s3,s4)
691 end
692 else
693 if s1 == s2 and s1 == s3 then
694 return f_gray(s1)
695 else
696 return f_rgb(s1,s2,s3)
697 end
698 end
699 elseif what == "cmyk" then
700 return f_cmyk(s1,s2,s3,s4)
701 elseif what == "gray" then
702 if s2 then
703 return f_graya(s1,s2)
704 else
705 return f_gray(s1)
706 end
707 end
708 return f_name(color)
709 end
710
711 thecolor = function(color)
712 local what, s1, s2, s3, s4 = validcolor(color)
713 if what == "rgb" then
714 if s4 then
715 if s1 == s2 and s1 == s3 then
716 return f_svggraya(s1,s4)
717 else
718 return f_svgrgba(s1,s2,s3,s4)
719 end
720 else
721 if s1 == s2 and s1 == s3 then
722 return f_svggray(s1)
723 else
724 return f_svgrgb(s1,s2,s3)
725 end
726 end
727 elseif what == "cmyk" then
728 return f_cmyk(s1,s2,s3,s4)
729 elseif what == "gray" then
730 if s2 then
731 return f_svggraya(s1,s2)
732 else
733 return f_svggray(s1)
734 end
735 end
736 return f_svgname(color)
737 end
738
739end
740
741
742
743local grabpath, grablist do
744
745 local f_moveto = formatters['(%N,%N)']
746 local f_curveto_z = formatters['controls(%N,%N)and(%N,%N)..(%N,%N)']
747 local f_curveto_n = formatters['..controls(%N,%N)and(%N,%N)..(%N,%N)']
748 local f_lineto_z = formatters['(%N,%N)']
749 local f_lineto_n = formatters['--(%N,%N)']
750
751 local m = { __index = function() return 0 end }
752
753
754
755
756 grabpath = function(str)
757 local p = lpegmatch(p_path,str) or { }
758 local np = #p
759 local all = { entries = np, closed = false, curve = false }
760 if np == 0 then
761 return all
762 end
763 setmetatable(p,m)
764 local t = { }
765 local n = 0
766
767 local a = 0
768 local i = 0
769 local last = "M"
770 local prev = last
771 local kind = "L"
772 local x, y = 0, 0
773 local x1, y1 = 0, 0
774 local x2, y2 = 0, 0
775 local rx, ry = 0, 0
776 local ar, al = 0, 0
777 local as, ac = 0, nil
778 local mx, my = 0, 0
779 while i < np do
780 i = i + 1
781 local pi = p[i]
782 if type(pi) ~= "number" then
783 last = pi
784 i = i + 1
785 pi = p[i]
786 end
787
788 if last == "c" then
789 x1 = x + pi
790 i = i + 1 ; y1 = y + p[i]
791 i = i + 1 ; x2 = x + p[i]
792 i = i + 1 ; y2 = y + p[i]
793 i = i + 1 ; x = x + p[i]
794 i = i + 1 ; y = y + p[i]
795 goto curveto
796 elseif last == "l" then
797 x = x + pi
798 i = i + 1 ; y = y + p[i]
799 goto lineto
800 elseif last == "h" then
801 x = x + pi
802 goto lineto
803 elseif last == "v" then
804 y = y + pi
805 goto lineto
806 elseif last == "a" then
807 x1 = x
808 y1 = y
809 rx = pi
810 i = i + 1 ; ry = p[i]
811 i = i + 1 ; ar = p[i]
812 i = i + 1 ; al = p[i]
813 i = i + 1 ; as = p[i]
814 i = i + 1 ; x = x + p[i]
815 i = i + 1 ; y = y + p[i]
816 goto arc
817 elseif last == "s" then
818 if prev == "C" then
819 x1 = 2 * x - x2
820 y1 = 2 * y - y2
821 else
822 x1 = x
823 y1 = y
824 end
825 x2 = x + pi
826 i = i + 1 ; y2 = y + p[i]
827 i = i + 1 ; x = x + p[i]
828 i = i + 1 ; y = y + p[i]
829 goto curveto
830 elseif last == "m" then
831 if n > 0 then
832 a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
833 end
834 x = x + pi
835 i = i + 1 ; y = y + p[i]
836 goto moveto
837 elseif last == "z" then
838 goto close
839
840 elseif last == "C" then
841 x1 = pi
842 i = i + 1 ; y1 = p[i]
843 i = i + 1 ; x2 = p[i]
844 i = i + 1 ; y2 = p[i]
845 i = i + 1 ; x = p[i]
846 i = i + 1 ; y = p[i]
847 goto curveto
848 elseif last == "L" then
849 x = pi
850 i = i + 1 ; y = p[i]
851 goto lineto
852 elseif last == "H" then
853 x = pi
854 goto lineto
855 elseif last == "V" then
856 y = pi
857 goto lineto
858 elseif last == "A" then
859 x1 = x
860 y1 = y
861 rx = pi
862 i = i + 1 ; ry = p[i]
863 i = i + 1 ; ar = p[i]
864 i = i + 1 ; al = p[i]
865 i = i + 1 ; as = p[i]
866 i = i + 1 ; x = p[i]
867 i = i + 1 ; y = p[i]
868 goto arc
869 elseif last == "S" then
870 if prev == "C" then
871 x1 = 2 * x - x2
872 y1 = 2 * y - y2
873 else
874 x1 = x
875 y1 = y
876 end
877 x2 = pi
878 i = i + 1 ; y2 = p[i]
879 i = i + 1 ; x = p[i]
880 i = i + 1 ; y = p[i]
881 goto curveto
882 elseif last == "M" then
883 if n > 0 then
884 a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
885 end
886 x = pi ;
887 i = i + 1 ; y = p[i]
888 goto moveto
889 elseif last == "Z" then
890 goto close
891
892 elseif last == "q" then
893 x1 = x + pi
894 i = i + 1 ; y1 = y + p[i]
895 i = i + 1 ; x2 = x + p[i]
896 i = i + 1 ; y2 = y + p[i]
897 goto quadratic
898 elseif last == "t" then
899 if prev == "C" then
900 x1 = 2 * x - x1
901 y1 = 2 * y - y1
902 else
903 x1 = x
904 y1 = y
905 end
906 x2 = x + pi
907 i = i + 1 ; y2 = y + p[i]
908 goto quadratic
909 elseif last == "Q" then
910 x1 = pi
911 i = i + 1 ; y1 = p[i]
912 i = i + 1 ; x2 = p[i]
913 i = i + 1 ; y2 = p[i]
914 goto quadratic
915 elseif last == "T" then
916 if prev == "C" then
917 x1 = 2 * x - x1
918 y1 = 2 * y - y1
919 else
920 x1 = x
921 y1 = y
922 end
923 x2 = pi
924 i = i + 1 ; y2 = p[i]
925 goto quadratic
926 else
927 goto continue
928 end
929 ::moveto::
930 n = n + 1 ; t[n] = f_moveto(x,y)
931 last = last == "M" and "L" or "l"
932 prev = "M"
933 mx = x
934 my = y
935 goto continue
936 ::lineto::
937 n = n + 1 ; t[n] = (n > 0 and f_lineto_n or f_lineto_z)(x,y)
938 prev = "L"
939 goto continue
940 ::curveto::
941 n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(x1,y1,x2,y2,x,y)
942 prev = "C"
943 goto continue
944 ::arc::
945 ac = a2c(x1,y1,rx,ry,ar,al,as,x,y)
946 for i=1,#ac,6 do
947 n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(
948 ac[i],ac[i+1],ac[i+2],ac[i+3],ac[i+4],ac[i+5]
949 )
950 end
951 prev = "A"
952 goto continue
953 ::quadratic::
954 n = n + 1 ; t[n] = (n > 0 and f_curveto_n or f_curveto_z)(
955 x + 2/3 * (x1-x ), y + 2/3 * (y1-y ),
956 x2 + 2/3 * (x1-x2), y2 + 2/3 * (y1-y2),
957 x2, y2
958 )
959 x = x2
960 y = y2
961 prev = "C"
962 goto continue
963 ::close::
964
965 n = n + 1 ; t[n] = "--cycle"
966 if n > 0 then
967 a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
968 end
969 if i == np then
970 break
971 else
972 i = i - 1
973 end
974 kind = prev
975 prev = "Z"
976
977 x = mx
978 y = my
979 ::continue::
980 end
981 if n > 0 then
982 a = a + 1 ; all[a] = concat(t,"",1,n) ; n = 0
983 end
984 if prev == "Z" then
985 all.closed = true
986 end
987 all.curve = (kind == "C" or kind == "A")
988 return all, p
989 end
990
991
992
993
994 grablist = function(p)
995 local np = #p
996 if np == 0 then
997 return nil
998 end
999 local t = { }
1000 local n = 0
1001 local a = 0
1002 local i = 0
1003 local last = "M"
1004 local prev = last
1005 local kind = "L"
1006 local x, y = 0, 0
1007 local x1, y1 = 0, 0
1008 local x2, y2 = 0, 0
1009 local rx, ry = 0, 0
1010 local ar, al = 0, 0
1011 local as, ac = 0, nil
1012 local mx, my = 0, 0
1013 while i < np do
1014 i = i + 1
1015 local pi = p[i]
1016 if type(pi) ~= "number" then
1017 last = pi
1018 i = i + 1
1019 pi = p[i]
1020 end
1021
1022 if last == "c" then
1023 x1 = x + pi
1024 i = i + 1 ; y1 = y + p[i]
1025 i = i + 1 ; x2 = x + p[i]
1026 i = i + 1 ; y2 = y + p[i]
1027 i = i + 1 ; x = x + p[i]
1028 i = i + 1 ; y = y + p[i]
1029 goto curveto
1030 elseif last == "l" then
1031 x = x + pi
1032 i = i + 1 ; y = y + p[i]
1033 goto lineto
1034 elseif last == "h" then
1035 x = x + pi
1036 goto lineto
1037 elseif last == "v" then
1038 y = y + pi
1039 goto lineto
1040 elseif last == "a" then
1041 x1 = x
1042 y1 = y
1043 rx = pi
1044 i = i + 1 ; ry = p[i]
1045 i = i + 1 ; ar = p[i]
1046 i = i + 1 ; al = p[i]
1047 i = i + 1 ; as = p[i]
1048 i = i + 1 ; x = x + p[i]
1049 i = i + 1 ; y = y + p[i]
1050 goto arc
1051 elseif last == "s" then
1052 if prev == "C" then
1053 x1 = 2 * x - x2
1054 y1 = 2 * y - y2
1055 else
1056 x1 = x
1057 y1 = y
1058 end
1059 x2 = x + pi
1060 i = i + 1 ; y2 = y + p[i]
1061 i = i + 1 ; x = x + p[i]
1062 i = i + 1 ; y = y + p[i]
1063 goto curveto
1064 elseif last == "m" then
1065 x = x + pi
1066 i = i + 1 ; y = y + p[i]
1067 goto moveto
1068 elseif last == "z" then
1069 goto close
1070
1071 elseif last == "C" then
1072 x1 = pi
1073 i = i + 1 ; y1 = p[i]
1074 i = i + 1 ; x2 = p[i]
1075 i = i + 1 ; y2 = p[i]
1076 i = i + 1 ; x = p[i]
1077 i = i + 1 ; y = p[i]
1078 goto curveto
1079 elseif last == "L" then
1080 x = pi
1081 i = i + 1 ; y = p[i]
1082 goto lineto
1083 elseif last == "H" then
1084 x = pi
1085 goto lineto
1086 elseif last == "V" then
1087 y = pi
1088 goto lineto
1089 elseif last == "A" then
1090 x1 = x
1091 y1 = y
1092 rx = pi
1093 i = i + 1 ; ry = p[i]
1094 i = i + 1 ; ar = p[i]
1095 i = i + 1 ; al = p[i]
1096 i = i + 1 ; as = p[i]
1097 i = i + 1 ; x = p[i]
1098 i = i + 1 ; y = p[i]
1099 goto arc
1100 elseif last == "S" then
1101 if prev == "C" then
1102 x1 = 2 * x - x2
1103 y1 = 2 * y - y2
1104 else
1105 x1 = x
1106 y1 = y
1107 end
1108 x2 = pi
1109 i = i + 1 ; y2 = p[i]
1110 i = i + 1 ; x = p[i]
1111 i = i + 1 ; y = p[i]
1112 goto curveto
1113 elseif last == "M" then
1114 x = pi ;
1115 i = i + 1 ; y = p[i]
1116 goto moveto
1117 elseif last == "Z" then
1118 goto close
1119
1120 elseif last == "q" then
1121 x1 = x + pi
1122 i = i + 1 ; y1 = y + p[i]
1123 i = i + 1 ; x2 = x + p[i]
1124 i = i + 1 ; y2 = y + p[i]
1125 goto quadratic
1126 elseif last == "t" then
1127 if prev == "C" then
1128 x1 = 2 * x - x1
1129 y1 = 2 * y - y1
1130 else
1131 x1 = x
1132 y1 = y
1133 end
1134 x2 = x + pi
1135 i = i + 1 ; y2 = y + p[i]
1136 goto quadratic
1137 elseif last == "Q" then
1138 x1 = pi
1139 i = i + 1 ; y1 = p[i]
1140 i = i + 1 ; x2 = p[i]
1141 i = i + 1 ; y2 = p[i]
1142 goto quadratic
1143 elseif last == "T" then
1144 if prev == "C" then
1145 x1 = 2 * x - x1
1146 y1 = 2 * y - y1
1147 else
1148 x1 = x
1149 y1 = y
1150 end
1151 x2 = pi
1152 i = i + 1 ; y2 = p[i]
1153 goto quadratic
1154 else
1155 goto continue
1156 end
1157 ::moveto::
1158 n = n + 1 ; t[n] = x
1159 n = n + 1 ; t[n] = y
1160 last = last == "M" and "L" or "l"
1161 prev = "M"
1162 mx = x
1163 my = y
1164 goto continue
1165 ::lineto::
1166 n = n + 1 ; t[n] = x
1167 n = n + 1 ; t[n] = y
1168 prev = "L"
1169 goto continue
1170 ::curveto::
1171 n = n + 1 ; t[n] = x
1172 n = n + 1 ; t[n] = y
1173 prev = "C"
1174 goto continue
1175 ::arc::
1176 ac = a2c(x1,y1,rx,ry,ar,al,as,x,y)
1177 for i=1,#ac,6 do
1178 n = n + 1 ; t[n] = ac[i+4]
1179 n = n + 1 ; t[n] = ac[i+5]
1180 end
1181 prev = "A"
1182 goto continue
1183 ::quadratic::
1184 n = n + 1 ; t[n] = x2
1185 n = n + 1 ; t[n] = y2
1186 x = x2
1187 y = y2
1188 prev = "C"
1189 goto continue
1190 ::close::
1191 n = n + 1 ; t[n] = mx
1192 n = n + 1 ; t[n] = my
1193 if i == np then
1194 break
1195 end
1196 kind = prev
1197 prev = "Z"
1198 x = mx
1199 y = my
1200 ::continue::
1201 end
1202 return t
1203 end
1204
1205end
1206
1207
1208
1209local s_wrapped_start <const> = "draw image ("
1210local f_wrapped_stop = formatters[") shifted (0,%N) scaled %N ;"]
1211
1212local handletransform, handleviewbox do
1213
1214 local sind = math.sind
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227 local f_rotatedaround = formatters["rotatedaround((%N,%N),%N) "]
1228 local f_rotated = formatters["rotated(%N) "]
1229 local f_shifted = formatters["shifted(%N,%N) "]
1230 local f_slanted_x = formatters["xslanted(%N) "]
1231 local f_slanted_y = formatters["yslanted(%N) "]
1232 local f_scaled = formatters["scaled(%N) "]
1233 local f_xyscaled = formatters["xyscaled(%N,%N) "]
1234 local f_matrix = formatters["transformed bymatrix(%N,%N,%N,%N,%N,%N) "]
1235 local s_transform_start <const> = "draw image ( "
1236 local f_transform_stop = formatters[") %s ; "]
1237
1238 local transforms = { }
1239 local noftransforms = 0
1240
1241 local function rotate(r,x,y)
1242 if r then
1243 noftransforms = noftransforms + 1
1244 if x then
1245 transforms[noftransforms] = f_rotatedaround(x,-(y or x),-r)
1246 else
1247 transforms[noftransforms] = f_rotated(-r)
1248 end
1249 end
1250 end
1251
1252 local function translate(x,y)
1253 if x == 0 then x = false end
1254 if y == 0 then y = false end
1255 if y then
1256 noftransforms = noftransforms + 1
1257 transforms[noftransforms] = f_shifted(x or 0,-y)
1258 elseif x then
1259 noftransforms = noftransforms + 1
1260 transforms[noftransforms] = f_shifted(x,0)
1261 end
1262 end
1263
1264 local function scale(x,y)
1265 if x == 1 then x = false end
1266 if y == 1 then y = false end
1267 if y then
1268 noftransforms = noftransforms + 1
1269 transforms[noftransforms] = f_xyscaled(x or 1,y)
1270 elseif x then
1271 noftransforms = noftransforms + 1
1272 transforms[noftransforms] = f_scaled(x)
1273 end
1274 end
1275
1276 local function skew(x,y)
1277
1278
1279 if x then
1280 noftransforms = noftransforms + 1
1281 transforms[noftransforms] = f_slanted_x(sind(-x))
1282 end
1283 if y then
1284 noftransforms = noftransforms + 1
1285 transforms[noftransforms] = f_slanted_y(sind(-y))
1286 end
1287 end
1288
1289 local function matrix(rx,sx,sy,ry,tx,ty)
1290 if not ty then
1291 ty = 0
1292 end
1293 if not tx then
1294 tx = 0
1295 end
1296 if not sx then
1297 sx = 0
1298 end
1299 if not sy then
1300 sy = 0
1301 end
1302 if not rx then
1303 rx = 1
1304 end
1305 if not ry then
1306 ry = 1
1307 end
1308 noftransforms = noftransforms + 1
1309
1310
1311 transforms[noftransforms] = f_matrix(rx, -sy, -sx, ry, tx, -ty)
1312 end
1313
1314 local p_transform = (
1315 lpegpatterns.whitespace^0 * (
1316 P("translate") * (p_numbers / translate)
1317 + P("scale") * (p_numbers / scale)
1318 + P("rotate") * (p_numbers / rotate)
1319 + P("matrix") * (p_numbers / matrix)
1320 + P("skew") * (p_numbers / skew)
1321 + P("translateX") * (p_numbers / translate)
1322 + P("translateY") * (Cc(false) * p_numbers / translate)
1323 + P("scaleX") * (p_numbers / translate)
1324 + P("scaleY") * (Cc(false) * p_numbers / translate)
1325 + P("skewX") * (p_numbers / skew)
1326 + P("skewY") * (Cc(false) * p_numbers / skew)
1327 )
1328 )^1
1329
1330
1331
1332 local function combined()
1333 if noftransforms == 1 then
1334 return transforms[1]
1335 elseif noftransforms == 2 then
1336 return transforms[2] .. transforms[1]
1337 elseif noftransforms == 3 then
1338 return transforms[3] .. transforms[2] .. transforms[1]
1339 else
1340
1341 local m = noftransforms + 1
1342 for i=1,noftransforms//2 do
1343 local j = m - i
1344 transforms[i], transforms[j] = transforms[j], transforms[i]
1345 end
1346 return concat(transforms,"",1,noftransforms)
1347 end
1348 end
1349
1350 handletransform = function(at)
1351 local t = at.transform
1352 if t then
1353 noftransforms = 0
1354 lpegmatch(p_transform,t)
1355 if noftransforms > 0 then
1356
1357 return s_transform_start, f_transform_stop(combined()), t
1358 end
1359 end
1360 end
1361
1362 handletransformstring = function(t)
1363 if t then
1364 noftransforms = 0
1365 lpegmatch(p_transform,t)
1366 return noftransforms > 0 and combined()
1367 end
1368 end
1369
1370 handleviewbox = function(v)
1371 if v then
1372 local x, y, w, h = lpegmatch(p_fournumbers,v)
1373 if h then
1374 return x, y, w, h
1375 end
1376 end
1377 end
1378
1379end
1380
1381local dashed do
1382
1383
1384
1385 local f_dashed_n = formatters[" dashed dashpattern (%s ) "]
1386 local f_dashed_y = formatters[" dashed dashpattern (%s ) shifted (%N,0) "]
1387
1388 local p_number = p_optseparator/"" * p_number_r
1389 local p_on = Cc(" on ") * p_number
1390 local p_off = Cc(" off ") * p_number
1391 local p_dashed = Cs((p_on * p_off^-1)^1)
1392
1393 dashed = function(s,o)
1394 if not find(s,",") then
1395
1396 s = s .. " " .. s
1397 end
1398 return (o and f_dashed_y or f_dashed_n)(lpegmatch(p_dashed,s),o)
1399 end
1400
1401end
1402
1403do
1404
1405 local handlers = { }
1406 local process = false
1407 local root = false
1408 local result = false
1409 local r = false
1410 local definitions = false
1411 local classstyles = false
1412 local tagstyles = false
1413
1414 local tags = {
1415 ["a"] = true,
1416
1417
1418
1419
1420
1421
1422
1423 ["circle"] = true,
1424 ["clipPath"] = true,
1425
1426
1427 ["defs"] = true,
1428
1429 ["ellipse"] = true,
1430
1431
1432
1433
1434
1435
1436
1437
1438 ["g"] = true,
1439
1440
1441
1442 ["image"] = true,
1443 ["line"] = true,
1444 ["linearGradient"] = true,
1445 ["marker"] = true,
1446
1447
1448
1449
1450 ["path"] = true,
1451 ["pattern"] = true,
1452 ["polygon"] = true,
1453 ["polyline"] = true,
1454 ["radialGradient"] = true,
1455 ["rect"] = true,
1456
1457
1458 ["stop"] = true,
1459 ["style"] = true,
1460 ["svg"] = true,
1461
1462 ["symbol"] = true,
1463 ["text"] = true,
1464
1465
1466 ["tspan"] = true,
1467 ["use"] = true,
1468
1469
1470 }
1471
1472 local usetags = {
1473 ["circle"] = true,
1474 ["ellipse"] = true,
1475 ["g"] = true,
1476 ["image"] = true,
1477 ["line"] = true,
1478 ["path"] = true,
1479 ["polygon"] = true,
1480 ["polyline"] = true,
1481 ["rect"] = true,
1482
1483
1484 }
1485
1486 local pathtracer = {
1487 ["stroke"] = "darkred",
1488 ["stroke-opacity"] = ".5",
1489 ["stroke-width"] = ".5",
1490 ["fill"] = "darkgray",
1491 ["fill-opacity"] = ".75",
1492 }
1493
1494 local function handlechains(c)
1495 if tags[c.tg] then
1496 local at = c.at
1497 local dt = c.dt
1498 if at and dt then
1499
1500 local estyle = rawget(at,"style")
1501 if estyle and estyle ~= "" then
1502 for k, v in gmatch(estyle,"%s*([^:]+):%s*([^;]+);?") do
1503 at[k] = v
1504 end
1505 end
1506 local eclass = rawget(at,"class")
1507 if eclass and eclass ~= "" then
1508 for c in gmatch(eclass,"[^ ]+") do
1509 local s = classstyles[c]
1510 if s then
1511 for k, v in next, s do
1512 at[k] = v
1513 end
1514 end
1515 end
1516 end
1517 local tstyle = tagstyles[tag]
1518 if tstyle then
1519 for k, v in next, tstyle do
1520 at[k] = v
1521 end
1522 end
1523 if trace_path and pathtracer then
1524 for k, v in next, pathtracer do
1525 at[k] = v
1526 end
1527 end
1528 for i=1,#dt do
1529 local dti = dt[i]
1530 if type(dti) == "table" then
1531 handlechains(dti)
1532 end
1533 end
1534 end
1535 end
1536 end
1537
1538 local handlestyle do
1539
1540
1541
1542
1543
1544 local p_key = C((R("az","AZ","09","__","--")^1))
1545 local p_spec = P("{") * C((1-P("}"))^1) * P("}")
1546 local p_valid = Carg(1) * P(".") * p_key + Carg(2) * p_key
1547 local p_grab = ((p_valid * p_space^0 * p_spec / rawset) + p_space^1 + P(1))^1
1548
1549 local fontspecification = css.fontspecification
1550
1551 handlestyle = function(c)
1552 local s = xmltext(c)
1553 lpegmatch(p_grab,s,1,classstyles,tagstyles)
1554 for k, v in next, classstyles do
1555 local t = { }
1556 for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do
1557 if k == "font" then
1558 local s = fontspecification(v)
1559 for k, v in next, s do
1560 t["font-"..k] = v
1561 end
1562 else
1563 t[k] = v
1564 end
1565 end
1566 classstyles[k] = t
1567 end
1568 for k, v in next, tagstyles do
1569 local t = { }
1570 for k, v in gmatch(v,"%s*([^:]+):%s*([^;]+);?") do
1571 if k == "font" then
1572 local s = fontspecification(v)
1573 for k, v in next, s do
1574 t["font-"..k] = v
1575 end
1576 else
1577 t[k] = v
1578 end
1579 end
1580 tagstyles[k] = t
1581 end
1582 end
1583
1584 function handlers.style()
1585
1586 end
1587
1588 end
1589
1590
1591
1592
1593 local function locate(id,c)
1594 if id == none then
1595 return
1596 end
1597 local res = definitions[id]
1598 local ref
1599 if res then
1600 return res
1601 end
1602 ref = gsub(id,"^url%(#(.-)%)$","%1")
1603 ref = gsub(ref,"^#","")
1604
1605 res = xmlfirst(root,"**[@id='"..ref.."']")
1606 if res then
1607 definitions[id] = res
1608 return res
1609 end
1610
1611
1612 ref = url.hashed(id)
1613 if not ref.nosheme and ref.scheme == "file" then
1614 local filename = ref.filename
1615 local fragment = ref.fragment
1616 if filename and filename ~= "" then
1617 local fullname = resolvers.findbinfile(filename)
1618 if lfs.isfile(fullname) then
1619 report("loading use file: %s",fullname)
1620 local root = xml.load(fullname)
1621 res = xmlfirst(root,"**[@id='"..fragment.."']")
1622 if res then
1623 xmlinheritattributes(res,c)
1624 setmetatableindex(res.at,c.at)
1625 definitions[id] = res
1626 return res
1627 end
1628 end
1629 end
1630 end
1631 return res
1632 end
1633
1634
1635
1636 local function handleclippath(at)
1637 local clippath = at["clip-path"]
1638
1639 if not clippath then
1640 return
1641 end
1642
1643 local spec = definitions[clippath] or locate(clippath)
1644
1645
1646 if not spec then
1647 local index = match(clippath,"(%d+)")
1648 if index then
1649 spec = xmlfirst(root,"clipPath["..tostring(tonumber(index) or 0).."]")
1650 end
1651 end
1652
1653
1654 if not spec then
1655 report("unknown clip %a",clippath)
1656 return
1657 elseif spec.tg ~= "clipPath" then
1658 report("bad clip %a",clippath)
1659 return
1660 end
1661
1662 ::again::
1663 for c in xmlcollected(spec,"/(path|use|g)") do
1664 local tg = c.tg
1665 if tg == "use" then
1666 local ca = c.at
1667 local id = ca["xlink:href"]
1668 if id then
1669 spec = locate(id)
1670 if spec then
1671 local sa = spec.at
1672 setmetatableindex(sa,ca)
1673 if spec.tg == "path" then
1674 local d = sa.d
1675 if d then
1676 local p = grabpath(d)
1677 p.evenodd = sa["clip-rule"] == "evenodd"
1678 p.close = true
1679 return p, clippath
1680 else
1681 return
1682 end
1683 else
1684 goto again
1685 end
1686 end
1687 end
1688
1689 elseif tg == "path" then
1690 local ca = c.at
1691 local d = rawget(ca,"d")
1692 if d then
1693 local p = grabpath(d)
1694 p.evenodd = ca["clip-rule"] == "evenodd"
1695 p.close = true
1696 local transform = rawget(ca,"transform")
1697 if transform then
1698 transform = handletransformstring(transform)
1699 end
1700 return p, clippath, transform
1701 else
1702 return
1703 end
1704 else
1705
1706 end
1707 end
1708 end
1709
1710
1711
1712 local s_rotation_start <const> = "draw image ( "
1713 local f_rotation_stop = formatters[") rotatedaround((0,0),-angle((%N,%N))) ;"]
1714 local f_rotation_angle = formatters[") rotatedaround((0,0),-%N) ;"]
1715
1716 local s_offset_start <const> = "draw image ( "
1717 local f_offset_stop = formatters[") shifted (%N,%N) ;"]
1718 local s_size_start <const> = "draw image ( "
1719 local f_size_stop = formatters[") xysized (%N,%N) ;"]
1720
1721 local handleoffset, handlesize do
1722
1723 handleoffset = function(at)
1724 local x = asnumber_vx(rawget(at,"x"))
1725 local y = asnumber_vy(rawget(at,"y"))
1726 if x ~= 0 or y ~= 0 then
1727 return s_offset_start, f_offset_stop(x,y)
1728 end
1729 end
1730
1731 handlesize = function(at)
1732 local width = asnumber_x(rawget(at,"width"))
1733 local height = asnumber_y(rawget(at,"height"))
1734 if width == 0 or height == 0 then
1735
1736 elseif width == 1 and height == 1 then
1737
1738 else
1739 return s_size_start, f_size_stop(width,height)
1740 end
1741 end
1742
1743 end
1744
1745 function handlers.symbol(c)
1746 local at = c.at
1747
1748 local boffset, eoffset = handleoffset(at)
1749 local bsize, esize = handlesize(at)
1750 local btransform, etransform, transform = handletransform(at)
1751
1752 if boffset then
1753 r = r + 1 result[r] = boffset
1754 end
1755 if btransform then
1756 r = r + 1 result[r] = btransform
1757 end
1758 if bsize then
1759 r = r + 1 ; result[r] = bsize
1760 end
1761
1762
1763
1764
1765
1766
1767 process(c,"/*")
1768
1769
1770
1771
1772
1773 if esize then
1774 r = r + 1 result[r] = esize
1775 end
1776 if etransform then
1777 r = r + 1 ; result[r] = etransform
1778 end
1779 if eoffset then
1780 r = r + 1 result[r] = eoffset
1781 end
1782 end
1783
1784
1785
1786 local s_shade_linear = ' withshademethod "linear" '
1787 local s_shade_circular = ' withshademethod "circular" '
1788 local f_color = formatters[' withcolor "%s"']
1789 local f_opacity = formatters[' withopacity %N']
1790 local f_pen = formatters[' withpen pencircle scaled %N']
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809 local function pattern(id)
1810 local c = definitions[id]
1811 if c and c.tg == "pattern" then
1812
1813 local _r = r
1814 local _result = result
1815 r = 0
1816 result = { }
1817
1818
1819
1820
1821
1822 local at = c.at
1823
1824 local width = asnumber_x(rawget(at,"width"))
1825 local height = asnumber_y(rawget(at,"height"))
1826 if width == 0 or height == 0 then
1827
1828 width = nil
1829 height = nil
1830 elseif width == 1 and height == 1 then
1831
1832 width = nil
1833 height = nil
1834 else
1835
1836 end
1837
1838 local boffset, eoffset = handleoffset(at)
1839
1840 local btransform, etransform, transform = handletransform(at)
1841
1842 if boffset then
1843 r = r + 1 result[r] = boffset
1844 end
1845 if btransform then
1846 r = r + 1 result[r] = btransform
1847 end
1848
1849
1850
1851
1852 local _x = at.x at.x = 0
1853 local _y = at.y at.y = 0
1854 local _w = at.width at.width = 0
1855 local _h = at.height at.height = 0
1856
1857 process(c,"/*")
1858
1859 at.x = _x
1860 at.y = _y
1861 at.width = _w
1862 at.height = _h
1863
1864
1865
1866
1867 if etransform then
1868 r = r + 1 ; result[r] = etransform
1869 end
1870 if eoffset then
1871 r = r + 1 result[r] = eoffset
1872 end
1873
1874 local okay
1875 if width and height then
1876 okay = formatters[" withpattern image ( % t )\n withpatternscale(%N,%N)"](result,width,height)
1877 else
1878 okay = formatters[" withpattern image ( % t )"](result)
1879 end
1880 r = _r
1881 result = _result
1882 return okay
1883 end
1884 end
1885
1886 local gradient do
1887
1888 local f_shade_step = formatters['withshadestep ( withshadefraction %N withshadecolors (%s,%s) )']
1889 local f_shade_step_opacity = formatters['withshadestep ( withshadefraction %N withshadecolors (%s,%s) withshadeopacity %N )']
1890 local f_shade_center = formatters['withshadecenter (%N,%N)']
1891 local f_shade_center_f = formatters['withshadecenterfraction (%N,%N)']
1892 local f_shade_radius = formatters['withshaderadius (%N,%N) ']
1893 local f_shade_radius_f = formatters['withshaderadiusfraction %N']
1894 local f_shade_center_one = formatters['withshadecenterone (%N,%N)']
1895 local f_shade_center_two = formatters['withshadecentertwo (%N,%N)']
1896 local f_shade_center_one_f = formatters['withshadecenteronefraction (%N,%N)']
1897 local f_shade_center_two_f = formatters['withshadecentertwofraction (%N,%N)']
1898
1899 gradient = function(id)
1900 local spec = definitions[id]
1901 if spec then
1902 local kind = spec.tg
1903 local shade = nil
1904 local n = 1
1905 local a = spec.at
1906
1907 local gu = rawget(a, "gradientUnits")
1908 local gt = rawget(a, "gradientTransform")
1909 local sm = rawget(a, "spreadMethod")
1910
1911 local userspace = gu == "userSpaceOnUse"
1912
1913 if kind == "linearGradient" then
1914 shade = { s_shade_linear }
1915
1916 local x1 = rawget(a,"x1")
1917 local y1 = rawget(a,"y1")
1918 local x2 = rawget(a,"x2")
1919 local y2 = rawget(a,"y2")
1920 if x1 and y1 then
1921 n = n + 1 ; shade[n] = f_shade_center_one_f(asnumber_p(x1),1-asnumber_p(y1))
1922 end
1923 if x2 and y2 then
1924 n = n + 1 ; shade[n] = f_shade_center_two_f(asnumber_p(x2),1-asnumber_p(y2))
1925 end
1926
1927 elseif kind == "radialGradient" then
1928 shade = { s_shade_circular }
1929
1930 local cx = rawget(a,"cx")
1931 local cy = rawget(a,"cy")
1932 local r = rawget(a,"r" )
1933 local fx = rawget(a,"fx")
1934 local fy = rawget(a,"fy")
1935
1936 if userspace then
1937 if cx and cy then
1938 n = n + 1 ; shade[n] = f_shade_center(asnumber_p(cx),asnumber_p(cy))
1939 end
1940 if fx and fy then
1941 n = n + 1 ; shade[n] = f_shade_center_one(asnumber_p(fx),-asnumber_p(fy))
1942 end
1943 if r then
1944 n = n + 1 ; shade[n] = f_shade_radius(asnumber_p(r))
1945 end
1946 if fx and fy then
1947
1948 end
1949 else
1950 if cx and cy then
1951 n = n + 1 ; shade[n] = f_shade_center_f(asnumber_p(cx),1-asnumber_p(cy))
1952 end
1953 if fx and fy then
1954 n = n + 1 ; shade[n] = f_shade_center_one_f(asnumber_p(fx),1-asnumber_p(fy))
1955 end
1956 if r then
1957 n = n + 1 ; shade[n] = f_shade_radius_f(asnumber_p(r))
1958 end
1959 if fx and fy then
1960
1961 end
1962 end
1963 else
1964 return
1965 end
1966 local colora, colorb
1967
1968 for c in xmlcollected(spec,"/stop") do
1969 local a = c.at
1970 local offset = rawget(a,"offset")
1971 local colorb = rawget(a,"stop-color")
1972
1973 if not colora then
1974 colora = colorb
1975 end
1976
1977
1978local fraction = offset and asnumber_p(offset)
1979 if not fraction then
1980
1981 fraction = xmlcount(spec,"/stop")/100
1982 end
1983 if colora and colorb and colora ~= "" and colorb ~= "" then
1984 n = n + 1
1985
1986
1987
1988 if userspace then
1989 shade[n] = f_shade_step(fraction,thecolor(colora),thecolor(colorb))
1990 else
1991 shade[n] = f_shade_step(fraction,thecolor(colora),thecolor(colorb))
1992 end
1993
1994 end
1995 colora = colorb
1996 end
1997 return concat(shade,"\n ")
1998 end
1999 end
2000
2001 end
2002
2003 local function drawproperties(stroke,at,opacity)
2004 local p = at["stroke-width"]
2005 if p then
2006 p = f_pen(asnumber_r(p))
2007 end
2008 local d = at["stroke-dasharray"]
2009 if d == "none" then
2010 d = nil
2011 elseif d then
2012 local o = at["stroke-dashoffset"]
2013 if o and o ~= "none" then
2014 o = asnumber_r(o)
2015 else
2016 o = false
2017 end
2018 d = dashed(d,o)
2019 end
2020 local c = withcolor(stroke)
2021 local o = at["stroke-opacity"] or (opacity and at["opacity"])
2022 if o == "none" then
2023 o = nil
2024 elseif o == "transparent" then
2025 o = f_opacity(0)
2026 elseif o then
2027 o = asnumber_r(o)
2028 if o == ignoredopacity then
2029 o = nil
2030 elseif o then
2031 o = f_opacity(o)
2032 else
2033 o = nil
2034 end
2035 end
2036 return p, d, c, o
2037 end
2038
2039 local s_opacity_start <const> = "draw image ("
2040 local f_opacity_content = formatters["setgroup currentpicture to boundingbox currentpicture withopacity %N;"]
2041 local s_opacity_stop <const> = ") ;"
2042
2043 local function sharedopacity(at)
2044 local o = at["opacity"]
2045 if o and o ~= "none" then
2046 o = asnumber_r(o)
2047 if o == ignoredopacity then
2048 return
2049 end
2050 if o then
2051 return s_opacity_start, f_opacity_content(o), s_opacity_stop
2052 end
2053 end
2054 end
2055
2056
2057
2058 local function fillproperties(fill,at,opacity)
2059 local o = at["fill-opacity"] or (opacity and at["opacity"])
2060 local c = nil
2061 if c ~= "none" then
2062 c = gradient(fill)
2063 if not c then
2064 c = pattern(fill)
2065 if c then
2066 if o and o ~= "none" then
2067 o = asnumber_r(o)
2068 if o ~= ignoredopacity then
2069 return c, f_opacity(o), "pattern"
2070 end
2071 end
2072 return c, false, "pattern"
2073 else
2074 c = withcolor(fill)
2075 end
2076 end
2077 end
2078 if not o and fill == "transparent" then
2079 return nil, f_opacity(0), true
2080 elseif o and o ~= "none" then
2081 o = asnumber_r(o)
2082 if o == ignoredopacity then
2083 return c
2084 end
2085 if o then
2086 return c, f_opacity(o), (o == 1 and "invisible")
2087 end
2088 end
2089 return c
2090 end
2091
2092 local viewport do
2093
2094 local s_viewport_start <const> = "draw image ("
2095 local s_viewport_stop <const> = ") ;"
2096 local f_viewport_shift = formatters["currentpicture := currentpicture shifted (%N,%N);"]
2097 local f_viewport_scale = formatters["currentpicture := currentpicture xysized (%N,%N);"]
2098 local f_viewport_clip = formatters["clip currentpicture to (unitsquare xyscaled (%N,%N));"]
2099
2100 viewport = function(x,y,w,h,noclip,scale)
2101 r = r + 1 ; result[r] = s_viewport_start
2102 return function()
2103 local okay = w ~= 0 and h ~= 0
2104 if okay and scale then
2105 r = r + 1 ; result[r] = f_viewport_scale(w,h)
2106 end
2107 if x ~= 0 or y ~= 0 then
2108 r = r + 1 ; result[r] = f_viewport_shift(-x,y)
2109 end
2110 if okay and not noclip then
2111 r = r + 1 ; result[r] = f_viewport_clip(w,-h)
2112 end
2113
2114 r = r + 1 ; result[r] = s_viewport_stop
2115 end
2116 end
2117
2118 end
2119
2120
2121
2122
2123 function handledefinitions(c)
2124 for c in xmlcollected(c,"defs/*") do
2125 local a = c.at
2126 if a then
2127 local id = rawget(a,"id")
2128 if id then
2129 definitions["#" .. id ] = c
2130 definitions["url(#" .. id .. ")"] = c
2131 end
2132 end
2133 end
2134 for c in xmlcollected(c,"(symbol|radialGradient|linearGradient)") do
2135 local id = rawget(c.at,"id")
2136 if id then
2137 definitions["#" .. id ] = c
2138 definitions["url(#" .. id .. ")"] = c
2139 end
2140 end
2141 end
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158 local uselevel = 0
2159
2160 function handlers.use(c)
2161 local at = c.at
2162 local id = rawget(at,"href") or rawget(at,"xlink:href")
2163 local res = locate(id,c)
2164 if res then
2165 uselevel = uselevel + 1
2166 local boffset, eoffset = handleoffset(at)
2167 local btransform, etransform, transform = handletransform(at)
2168
2169 if boffset then
2170 r = r + 1 result[r] = boffset
2171 end
2172
2173
2174
2175 if btransform then
2176 r = r + 1 result[r] = btransform
2177 end
2178
2179 local _transform = transform
2180 local _clippath = clippath
2181 at["transform"] = false
2182
2183
2184setmetatableindex(res.at,at)
2185
2186 local tg = res.tg
2187
2188 process(res,".")
2189
2190
2191
2192
2193 at["transform"] = _transform
2194
2195
2196 if etransform then
2197 r = r + 1 ; result[r] = etransform
2198 end
2199
2200 if eoffset then
2201 r = r + 1 result[r] = eoffset
2202 end
2203
2204 uselevel = uselevel - 1
2205 else
2206 report("use: unknown definition %a",id)
2207 end
2208 end
2209
2210 local f_no_draw = formatters[' nodraw (%s)']
2211 local f_do_draw = formatters[' draw (%s)']
2212 local f_no_fill_c = formatters[' nofill closedcurve(%s)']
2213 local f_do_fill_c = formatters[' fill closedcurve(%s)']
2214 local f_eo_fill_c = formatters[' eofill closedcurve(%s)']
2215 local f_no_fill_l = formatters[' nofill closedlines(%s)']
2216 local f_do_fill_l = formatters[' fill closedlines(%s)']
2217 local f_eo_fill_l = formatters[' eofill closedlines(%s)']
2218 local f_closed_draw = formatters[' draw closedcurve(%s)']
2219 local f_do_fill = f_do_fill_c
2220 local f_eo_fill = f_eo_fill_c
2221 local f_no_fill = f_no_fill_c
2222 local s_clip_start <const> = 'save p ; picture p ; p := image ('
2223 local f_clip_stop_c = formatters[') ; clip p to closedcurve(%s) %s ; draw p ;']
2224 local f_clip_stop_l = formatters[') ; clip p to closedlines(%s) %s ; draw p ;']
2225 local f_clip_stop = f_clip_stop_c
2226 local f_eoclip_stop_c = formatters[') ; eoclip p to closedcurve(%s) %s ; draw p ;']
2227 local f_eoclip_stop_l = formatters[') ; eoclip p to closedlines(%s) %s ; draw p ;']
2228 local f_eoclip_stop = f_eoclip_stop_c
2229
2230
2231
2232 local function flushobject(object,at,c,o)
2233 local btransform, etransform = handletransform(at)
2234 local cpath, _, ctransform = handleclippath(at)
2235
2236 if cpath then
2237 r = r + 1 ; result[r] = s_clip_start
2238 end
2239
2240 if btransform then
2241 r = r + 1 ; result[r] = btransform
2242 end
2243
2244 r = r + 1 ; result[r] = f_do_draw(object)
2245
2246 if c then
2247 r = r + 1 ; result[r] = c
2248 end
2249
2250 if o then
2251 r = r + 1 ; result[r] = o
2252 end
2253
2254 if etransform then
2255 r = r + 1 ; result[r] = etransform
2256 end
2257
2258 r = r + 1 ; result[r] = " ;"
2259
2260 if cpath then
2261 local f_done = cpath.evenodd
2262 if cpath.curve then
2263 f_done = f_done and f_eoclip_stop_c or f_clip_stop_c
2264 else
2265 f_done = f_done and f_eoclip_stop_l or f_clip_stop_l
2266 end
2267 r = r + 1 ; result[r] = f_done(cpath[1],ctransform or "")
2268 end
2269 end
2270
2271 do
2272
2273 local flush
2274
2275 local f_linecap = formatters[" interim linecap := %s ;"]
2276 local f_linejoin = formatters[" interim linejoin := %s ;"]
2277 local f_miterlimit = formatters[" interim miterlimit := %s ;"]
2278
2279 local s_begingroup <const> = "begingroup;"
2280 local s_endgroup <const> = "endgroup;"
2281
2282 local linecaps = { butt = "butt", square = "squared", round = "rounded" }
2283 local linejoins = { miter = "mitered", bevel = "beveled", round = "rounded" }
2284
2285 local function startlineproperties(at)
2286 local cap = at["stroke-linecap"]
2287 local join = at["stroke-linejoin"]
2288 local limit = at["stroke-miterlimit"]
2289 cap = cap and linecaps [cap]
2290 join = join and linejoins[join]
2291 limit = limit and asnumber_r(limit)
2292 if cap or join or limit then
2293 r = r + 1 ; result[r] = s_begingroup
2294 if cap then
2295 r = r + 1 ; result[r] = f_linecap(cap)
2296 end
2297 if join then
2298 r = r + 1 ; result[r] = f_linejoin(join)
2299 end
2300 if limit then
2301 r = r + 1 ; result[r] = f_miterlimit(limit)
2302 end
2303 return function()
2304 at["stroke-linecap"] = false
2305 at["stroke-linejoin"] = false
2306 at["stroke-miterlimit"] = false
2307 r = r + 1 ; result[r] = s_endgroup
2308 at["stroke-linecap"] = cap
2309 at["stroke-linejoin"] = join
2310 at["stroke-miterlimit"] = limit
2311 end
2312 end
2313 end
2314
2315
2316
2317 function handlers.marker()
2318
2319 end
2320
2321
2322
2323
2324
2325 local function makemarker(where,c,x1,y1,x2,y2,x3,y3,parentat)
2326 local at = c.at
2327 local refx = rawget(at,"refX")
2328 local refy = rawget(at,"refY")
2329 local width = rawget(at,"markerWidth")
2330 local height = rawget(at,"markerHeight")
2331 local units = rawget(at,"markerUnits")
2332 local view = rawget(at,"viewBox")
2333 local orient = rawget(at,"orient")
2334
2335 local units = units and asnumber(units) or 1
2336
2337 local angx = 0
2338 local angy = 0
2339 local angle = 0
2340
2341 if where == "beg" then
2342 if orient == "auto" then
2343
2344 angx = abs(x2 - x3)
2345 angy = abs(y2 - y3)
2346 elseif orient == "auto-start-reverse" then
2347
2348 angx = -abs(x2 - x3)
2349 angy = -abs(y2 - y3)
2350 elseif orient then
2351 angle = asnumber_r(orient)
2352 end
2353 elseif where == "end" then
2354
2355 if orient == "auto" or orient == "auto-start-reverse" then
2356 angx = abs(x1 - x2)
2357 angy = abs(y1 - y2)
2358 elseif orient then
2359 angle = asnumber_r(orient)
2360 end
2361 elseif orient then
2362 angle = asnumber_r(orient)
2363 end
2364
2365
2366 refx = asnumber_x(refx)
2367 refy = asnumber_y(refy)
2368
2369 width = (width and asnumber_x(width) or 3) * units
2370 height = (height and asnumber_y(height) or 3) * units
2371
2372 local x = 0
2373 local y = 0
2374 local w = width
2375 local h = height
2376
2377
2378
2379 r = r + 1 ; result[r] = s_offset_start
2380
2381 local wrapupviewport
2382
2383 local xpct, ypct, rpct
2384 if view then
2385 x, y, w, h = handleviewbox(view)
2386 end
2387
2388 if width ~= 0 then
2389 w = width
2390 end
2391 if height ~= 0 then
2392 h = height
2393 end
2394
2395 if h then
2396 xpct = percentage_x
2397 ypct = percentage_y
2398 rpct = percentage_r
2399 percentage_x = w / 100
2400 percentage_y = h / 100
2401 percentage_r = (sqrt(w^2 + h^2) / sqrt(2)) / 100
2402 wrapupviewport = viewport(x,y,w,h,true,true)
2403 end
2404
2405
2406
2407 local hasref = refx ~= 0 or refy ~= 0
2408 local hasrot = angx ~= 0 or angy ~= 0 or angle ~= 0
2409
2410 local btransform, etransform, transform = handletransform(at)
2411
2412 if btransform then
2413 r = r + 1 ; result[r] = btransform
2414 end
2415
2416 if hasrot then
2417 r = r + 1 ; result[r] = s_rotation_start
2418 end
2419
2420 if hasref then
2421 r = r + 1 ; result[r] = s_offset_start
2422 end
2423
2424 local _transform = transform
2425 at["transform"] = false
2426
2427 handlers.g(c)
2428
2429 at["transform"] = _transform
2430
2431 if hasref then
2432 r = r + 1 ; result[r] = f_offset_stop(-refx,refy)
2433 end
2434
2435 if hasrot then
2436 if angle ~= 0 then
2437 r = r + 1 ; result[r] = f_rotation_angle(angle)
2438 else
2439 r = r + 1 ; result[r] = f_rotation_stop(angx,angy)
2440 end
2441 end
2442
2443 if etransform then
2444 r = r + 1 ; result[r] = etransform
2445 end
2446
2447 if h then
2448 percentage_x = xpct
2449 percentage_y = ypct
2450 percentage_r = rpct
2451 if wrapupviewport then
2452 wrapupviewport()
2453 end
2454 end
2455 r = r + 1 ; result[r] = f_offset_stop(x2,y2)
2456
2457 end
2458
2459
2460
2461 local function addmarkers(list,begmarker,midmarker,endmarker,at)
2462 local n = #list
2463 if n > 3 then
2464 if begmarker then
2465 local m = locate(begmarker)
2466 if m then
2467 makemarker("beg",m,false,false,list[1],list[2],list[3],list[4],at)
2468 end
2469 end
2470 if midmarker then
2471 local m = locate(midmarker)
2472 if m then
2473 for i=3,n-2,2 do
2474 makemarker("mid",m,list[i-2],list[i-1],list[i],list[i+1],list[i+2],list[i+3],at)
2475 end
2476 end
2477 end
2478 if endmarker then
2479 local m = locate(endmarker)
2480 if m then
2481 makemarker("end",m,list[n-3],list[n-2],list[n-1],list[n],false,false,at)
2482 end
2483 end
2484 else
2485
2486 end
2487 end
2488
2489 local function flush(shape,dofill,at,list,begmarker,midmarker,endmarker)
2490
2491 local fill = dofill and (at["fill"] or "black")
2492 local stroke = at["stroke"] or "none"
2493
2494 local btransform, etransform = handletransform(at)
2495 local cpath, _, ctransform = handleclippath(at)
2496
2497 if cpath then
2498 r = r + 1 ; result[r] = s_clip_start
2499 end
2500
2501 local has_stroke = stroke and stroke ~= "none"
2502 local has_fill = fill and fill ~= "none"
2503
2504 local bopacity, copacity, eopacity
2505 if has_stroke and has_fill then
2506 bopacity, copacity, eopacity = sharedopacity(at)
2507 end
2508
2509 if copacity then
2510 r = r + 1 ; result[r] = bopacity
2511 end
2512
2513 if has_fill then
2514 local color, opacity, option = fillproperties(fill,at,not has_stroke)
2515 local f_xx_fill = at["fill-rule"] == "evenodd" and f_eo_fill or f_do_fill
2516 if btransform then
2517 r = r + 1 ; result[r] = btransform
2518 end
2519 if option == "pattern" then
2520 r = r + 1 result[r] = f_closed_draw(shape)
2521 else
2522 r = r + 1 result[r] = f_xx_fill(shape)
2523 end
2524 if color then
2525 r = r + 1 ; result[r] = color
2526 end
2527 if opacity then
2528 r = r + 1 ; result[r] = opacity
2529 end
2530 r = r + 1 ; result[r] = etransform or " ;"
2531 end
2532
2533 if has_stroke then
2534 local wrapup = startlineproperties(at)
2535 local pen, dashing, color, opacity = drawproperties(stroke,at,not has_fill)
2536 if btransform then
2537 r = r + 1 ; result[r] = btransform
2538 end
2539 r = r + 1 ; result[r] = f_do_draw(shape)
2540 if pen then
2541 r = r + 1 ; result[r] = pen
2542 end
2543 if dashing then
2544 r = r + 1 ; result[r] = dashing
2545 end
2546 if color then
2547 r = r + 1 ; result[r] = color
2548 end
2549 if opacity then
2550 r = r + 1 ; result[r] = opacity
2551 end
2552 r = r + 1 ; result[r] = etransform or " ;"
2553
2554 if list then
2555 addmarkers(list,begmarker,midmarker,endmarker,at)
2556 end
2557
2558 if wrapup then
2559 wrapup()
2560 end
2561 end
2562
2563 if copacity then
2564 r = r + 1 ; result[r] = copacity
2565 r = r + 1 ; result[r] = eopacity
2566 end
2567
2568 if cpath then
2569 r = r + 1 ; result[r] = (cpath.evenodd and f_eoclip_stop or f_clip_stop)(cpath[1],ctransform)
2570 end
2571
2572 end
2573
2574 local f_rectangle = formatters['unitsquare xyscaled (%N,%N) shifted (%N,%N)']
2575 local f_rounded = formatters['roundedsquarexy(%N,%N,%N,%N) shifted (%N,%N)']
2576 local f_line = formatters['((%N,%N)--(%N,%N))']
2577 local f_ellipse = formatters['(fullcircle xyscaled (%N,%N) shifted (%N,%N))']
2578 local f_circle = formatters['(fullcircle scaled %N shifted (%N,%N))']
2579
2580 function handlers.line(c)
2581 local at = c.at
2582 local x1 = rawget(at,"x1")
2583 local y1 = rawget(at,"y1")
2584 local x2 = rawget(at,"x2")
2585 local y2 = rawget(at,"y2")
2586
2587 x1 = x1 and asnumber_vx(x1) or 0
2588 y1 = y1 and asnumber_vy(y1) or 0
2589 x2 = x2 and asnumber_vx(x2) or 0
2590 y2 = y2 and asnumber_vy(y2) or 0
2591
2592 flush(f_line(x1,y1,x2,y2),false,at)
2593 end
2594
2595 function handlers.rect(c)
2596 local at = c.at
2597 local width = rawget(at,"width")
2598 local height = rawget(at,"height")
2599 local x = rawget(at,"x")
2600 local y = rawget(at,"y")
2601 local rx = rawget(at,"rx")
2602 local ry = rawget(at,"ry")
2603
2604 width = width and asnumber_x(width) or 0
2605 height = height and asnumber_y(height) or 0
2606 x = x and asnumber_vx(x) or 0
2607 y = y and asnumber_vy(y) or 0
2608
2609 y = y - height
2610
2611 if rx then rx = asnumber_x(rx) end
2612 if ry then ry = asnumber_y(ry) end
2613
2614 if rx or ry then
2615 if not rx then rx = ry end
2616 if not ry then ry = rx end
2617 flush(f_rounded(width,height,rx,ry,x,y),true,at)
2618 else
2619 flush(f_rectangle(width,height,x,y),true,at)
2620 end
2621 end
2622
2623 function handlers.ellipse(c)
2624 local at = c.at
2625 local cx = rawget(at,"cx")
2626 local cy = rawget(at,"cy")
2627 local rx = rawget(at,"rx")
2628 local ry = rawget(at,"ry")
2629
2630 cx = cx and asnumber_vx(cx) or 0
2631 cy = cy and asnumber_vy(cy) or 0
2632 rx = rx and asnumber_r (rx) or 0
2633 ry = ry and asnumber_r (ry) or 0
2634
2635 flush(f_ellipse(2*rx,2*ry,cx,cy),true,at)
2636 end
2637
2638 function handlers.circle(c)
2639 local at = c.at
2640 local cx = rawget(at,"cx")
2641 local cy = rawget(at,"cy")
2642 local r = rawget(at,"r")
2643
2644 cx = cx and asnumber_vx(cx) or 0
2645 cy = cy and asnumber_vy(cy) or 0
2646 r = r and asnumber_r (r) or 0
2647
2648 flush(f_circle(2*r,cx,cy),true,at)
2649 end
2650
2651 local f_lineto_z = formatters['(%N,%N)']
2652 local f_lineto_n = formatters['--(%N,%N)']
2653
2654 local p_pair = p_optseparator * p_number_vx * p_optseparator * p_number_vy
2655 local p_open = Cc("(")
2656 local p_close = Carg(1) * P(true) / function(s) return s end
2657 local p_polyline = Cs(p_open * (p_pair / f_lineto_z) * (p_pair / f_lineto_n)^0 * p_close)
2658 local p_polypair = Ct(p_pair^0)
2659
2660 local function poly(c,final)
2661 local at = c.at
2662 local points = rawget(at,"points")
2663 if points then
2664 local path = lpegmatch(p_polyline,points,1,final)
2665 local list = nil
2666 local begmarker = rawget(at,"marker-start")
2667 local midmarker = rawget(at,"marker-mid")
2668 local endmarker = rawget(at,"marker-end")
2669 if begmarker or midmarker or endmarker then
2670 list = lpegmatch(p_polypair,points)
2671 end
2672 flush(path,true,at,list,begmarker,midmarker,endmarker)
2673 end
2674 end
2675
2676 function handlers.polyline(c) poly(c, ")") end
2677 function handlers.polygon (c) poly(c,"--cycle)") end
2678
2679 local s_image_start <const> = "draw image ("
2680 local s_image_stop <const> = ") ;"
2681
2682 function handlers.path(c)
2683 local at = c.at
2684 local d = rawget(at,"d")
2685 if d then
2686 local shape, l = grabpath(d)
2687 local fill = at["fill"] or "black"
2688 local stroke = at["stroke"] or "none"
2689 local n = #shape
2690
2691 local btransform, etransform = handletransform(at)
2692 local cpath = handleclippath(at)
2693 if cpath then
2694 r = r + 1 ; result[r] = s_clip_start
2695 end
2696
2697
2698
2699 if fill and fill ~= "none" then
2700 local color, opacity, option = fillproperties(fill,at)
2701 local f_xx_fill = at["fill-rule"] == "evenodd"
2702 if option == "pattern" then
2703 f_xx_fill = f_closed_draw
2704 elseif shape.closed then
2705 f_xx_fill = f_xx_fill and f_eo_fill or f_do_fill
2706 elseif shape.curve then
2707 f_xx_fill = f_xx_fill and f_eo_fill_c or f_do_fill_c
2708 else
2709 f_xx_fill = f_xx_fill and f_eo_fill_l or f_do_fill_l
2710 end
2711 if n == 1 then
2712 if btransform then
2713 r = r + 1 ; result[r] = btransform
2714 end
2715 r = r + 1 result[r] = f_xx_fill(shape[1])
2716 if color then
2717 r = r + 1 ; result[r] = color
2718 end
2719 if opacity then
2720 r = r + 1 ; result[r] = opacity
2721 end
2722 r = r + 1 ; result[r] = etransform or " ;"
2723 else
2724 r = r + 1 ; result[r] = btransform or s_image_start
2725 for i=1,n do
2726 if i == n then
2727 r = r + 1 ; result[r] = f_xx_fill(shape[i])
2728 if color then
2729 r = r + 1 ; result[r] = color
2730 end
2731 if opacity then
2732 r = r + 1 ; result[r] = opacity
2733 end
2734 else
2735 r = r + 1 ; result[r] = f_no_fill(shape[i])
2736 end
2737 r = r + 1 ; result[r] = " ;"
2738 end
2739 r = r + 1 ; result[r] = etransform or s_image_stop
2740 end
2741 end
2742
2743 if stroke and stroke ~= "none" then
2744 local begmarker = rawget(at,"marker-start")
2745 local midmarker = rawget(at,"marker-mid")
2746 local endmarker = rawget(at,"marker-end")
2747 if begmarker or midmarker or endmarker then
2748 list = grablist(l)
2749 end
2750 local wrapup = startlineproperties(at)
2751 local pen, dashing, color, opacity = drawproperties(stroke,at)
2752 if n == 1 and not list then
2753 if btransform then
2754 r = r + 1 ; result[r] = btransform
2755 end
2756 r = r + 1 result[r] = f_do_draw(shape[1])
2757 if pen then
2758 r = r + 1 ; result[r] = pen
2759 end
2760 if dashing then
2761 r = r + 1 ; result[r] = dashing
2762 end
2763 if color then
2764 r = r + 1 ; result[r] = color
2765 end
2766 if opacity then
2767 r = r + 1 ; result[r] = opacity
2768 end
2769 r = r + 1 result[r] = etransform or " ;"
2770 else
2771 r = r + 1 result[r] = btransform or s_draw_image_start
2772 for i=1,n do
2773 r = r + 1 result[r] = f_do_draw(shape[i])
2774 if pen then
2775 r = r + 1 ; result[r] = pen
2776 end
2777 if dashing then
2778 r = r + 1 ; result[r] = dashing
2779 end
2780 if color then
2781 r = r + 1 ; result[r] = color
2782 end
2783 if opacity then
2784 r = r + 1 ; result[r] = opacity
2785 end
2786 r = r + 1 ; result[r] = " ;"
2787 end
2788 if list then
2789 addmarkers(list,begmarker,midmarker,endmarker,at)
2790 end
2791 r = r + 1 ; result[r] = etransform or s_draw_image_stop
2792 end
2793 if wrapup then
2794 wrapup()
2795 end
2796 end
2797
2798 if cpath then
2799 r = r + 1 ; result[r] = f_clip_stop(cpath[1],"")
2800 end
2801
2802 end
2803 end
2804
2805 end
2806
2807
2808
2809 do
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819 local f_image = formatters[ [[svgembeddedfigure(%i) xysized (%N,%N) shifted (%N,%N)]] ]
2820
2821
2822
2823 function handlers.image(c)
2824 local at = c.at
2825 local im = rawget(at,"xlink:href")
2826 if im then
2827 local kind, data = match(im,"^data:image/([a-z]+);base64,(.*)$")
2828 if kind == "png" then
2829
2830 elseif kind == "jpeg" then
2831 kind = "jpg"
2832 else
2833 kind = false
2834 end
2835 if kind and data then
2836 local w = rawget(at,"width")
2837 local h = rawget(at,"height")
2838 local x = rawget(at,"x")
2839 local y = rawget(at,"y")
2840 w = w and asnumber_x(w)
2841 h = h and asnumber_y(h)
2842 x = x and asnumber_vx(x) or 0
2843 y = y and asnumber_vy(y) or 0
2844 local data = basexx.decode64(data)
2845
2846 local index = images.storedata("svg", {
2847 kind = kind,
2848 data = data,
2849 info = graphics.identifiers[kind](data,"string"),
2850 })
2851
2852 if not w or not h then
2853 if info then
2854
2855 local xsize = info.xsize
2856 local ysize = info.ysize
2857 if not w then
2858 if not h then
2859 w = xsize
2860 h = ysize
2861 else
2862 w = (h / ysize) * xsize
2863 end
2864 else
2865 h = (w / xsize) * ysize
2866 end
2867 end
2868 end
2869
2870 if not w then w = h or 1 end
2871 if not h then h = w or 1 end
2872
2873
2874 flushobject(f_image(index,w,h,x,y - h),at)
2875 else
2876
2877 end
2878 end
2879 end
2880
2881 end
2882
2883
2884
2885 do
2886
2887 function handlers.a(c)
2888 process(c,"/*")
2889 end
2890
2891 function handlers.g(c)
2892 local at = c.at
2893
2894 local btransform, etransform, transform = handletransform(at)
2895 local cpath, clippath, ctransform = handleclippath(at)
2896
2897 if cpath then
2898 r = r + 1 ; result[r] = s_clip_start
2899 end
2900
2901 if btransform then
2902 r= r + 1 result[r] = btransform
2903 end
2904
2905 local _transform = transform
2906 local _clippath = clippath
2907 at["transform"] = false
2908 at["clip-path"] = false
2909
2910 process(c,"/!(defs|symbol)")
2911
2912 at["transform"] = _transform
2913 at["clip-path"] = _clippath
2914
2915 if etransform then
2916 r = r + 1 ; result[r] = etransform
2917 end
2918
2919 if cpath then
2920 local f_done = cpath.evenodd
2921 if cpath.curve then
2922 f_done = f_done and f_eoclip_stop_c or f_clip_stop_c
2923 else
2924 f_done = f_done and f_eoclip_stop_l or f_clip_stop_l
2925 end
2926 r = r + 1 ; result[r] = f_done(cpath[1],ctransform or "")
2927 end
2928 end
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945 do
2946
2947 local s_start <const> = "\\svgstart "
2948 local s_stop <const> = "\\svgstop "
2949 local f_set = formatters["\\svgset{%N}{%N}"]
2950 local f_color_c = formatters["\\svgcolorc{%.3N}{%.3N}{%.3N}{"]
2951 local f_color_o = formatters["\\svgcoloro{%.3N}{"]
2952 local f_color_b = formatters["\\svgcolorb{%.3N}{%.3N}{%.3N}{%.3N}{"]
2953 local f_poscode = formatters["\\svgpcode{%N}{%N}{%s}"]
2954 local f_poschar = formatters["\\svgpchar{%N}{%N}{%s}"]
2955 local f_posspace = formatters["\\svgpspace{%N}{%N}"]
2956 local f_code = formatters["\\svgcode{%s}"]
2957 local f_char = formatters["\\svgchar{%s}"]
2958 local s_space <const> = "\\svgspace "
2959 local f_size = formatters["\\svgsize{%0.6f}"]
2960 local f_font = formatters["\\svgfont{%s}{%s}{%s}"]
2961 local f_hashed = formatters["\\svghashed{%s}"]
2962
2963
2964
2965 local anchors = {
2966 ["start"] = "drt",
2967 ["end"] = "dlft",
2968 ["middle"] = "d",
2969 }
2970
2971
2972
2973 local f_text_normal_svg = formatters['(onetimetextext.%s("%s") shifted (%N,%N))']
2974 local f_text_simple_svg = formatters['onetimetextext.%s("%s")']
2975 local f_mapped_normal_svg = formatters['(svgtext("%s") shifted (%N,%N))']
2976 local f_mapped_simple_svg = formatters['svgtext("%s")']
2977
2978 local cssfamily = css.family
2979 local cssstyle = css.style
2980 local cssweight = css.weight
2981 local csssize = css.size
2982
2983 local usedfonts = setmetatableindex(function(t,k)
2984 local v = setmetatableindex("table")
2985 t[k] = v
2986 return v
2987 end)
2988
2989
2990
2991 local function checkedfamily(name)
2992 if find(name,"^.-verdana.-$") then
2993 name = "verdana"
2994 end
2995 return name
2996 end
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014 local defaultsize = 10
3015
3016 local sensitive = {
3017 ["#"] = true,
3018 ["$"] = true,
3019 ["%"] = true,
3020 ["&"] = true,
3021 ["\\"] = true,
3022 ["{"] = true,
3023 ["|"] = true,
3024 ["}"] = true,
3025 ["~"] = true,
3026 }
3027
3028
3029
3030 local function validdelta(usedscale,d)
3031 if d then
3032 local value, unit = match(d,"^([%A]-)(%a+)")
3033 value = tonumber(value) or 0
3034 if not unit then
3035 return value .. "bp"
3036 elseif unit == "ex" or unit == "em" then
3037 return (usedscale * value) .. unit
3038 else
3039 return value .. "bp"
3040 end
3041 else
3042 return "0bp"
3043 end
3044 end
3045
3046 local cleanfontname = fonts.names.cleanname
3047
3048 local x_family = false
3049 local x_weight = false
3050 local x_style = false
3051
3052 local function collect(parent,t,c,x,y,size,scale,family,tx,ty,tdx,tdy)
3053 if c.special then
3054 return nil
3055 end
3056 local dt = c.dt
3057 local nt = #dt
3058 local at = c.at
3059 local tg = c.tg
3060 local ax = rawget(at,"x")
3061 local ay = rawget(at,"y")
3062 local v_opacity = tonumber(at["fill-opacity"])
3063 local v_fill = at["fill"]
3064 local v_family = at["font-family"]
3065 local v_style = at["font-style"]
3066 local v_weight = at["font-weight"]
3067 local v_size = at["font-size"]
3068 local v_lineheight = at["line-height"]
3069
3070 ax = ax and asnumber_vx(ax) or x
3071 ay = ay and asnumber_vy(ay) or y
3072
3073 if v_family then v_family = cssfamily(v_family) end
3074 if v_style then v_style = cssstyle (v_style) end
3075 if v_weight then v_weight = cssweight(v_weight) end
3076 if v_size then v_size = csssize (v_size,factors,size/100) or tonumber(v_size) end
3077
3078 if not v_family then v_family = family end
3079 if not v_weight then v_weight = "normal" end
3080 if not v_style then v_style = "normal" end
3081
3082 if v_family then
3083 v_family = cleanfontname(v_family)
3084 v_family = checkedfamily(v_family)
3085 end
3086
3087 usedfonts[v_family][v_weight][v_style] = true
3088
3089 local lh = v_lineheight and asnumber_vx(v_lineheight) or false
3090
3091 ax = ax - x
3092 ay = ay - y
3093
3094 local usedsize = v_size or defaultsize
3095 local usedscale = usedsize / defaultsize
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106 local newfont = v_family ~= x_family or v_weight ~= x_weight or v_style ~= x_style
3107 if newfont then
3108 x_family = v_family
3109 x_weight = v_weight
3110 x_style = v_style
3111 t[#t+1] = f_font(v_family,v_weight,v_style)
3112 t[#t+1] = "{"
3113 end
3114 t[#t+1] = f_size(usedscale)
3115 t[#t+1] = "{"
3116
3117 if trace_fonts then
3118
3119 report("element : %s",c.tg)
3120 report(" font family : %s",v_family)
3121 report(" font weight : %s",v_weight)
3122 report(" font style : %s",v_style)
3123 report(" parent size : %s",size)
3124
3125 report(" used size : %s",v_size or defaultsize)
3126 end
3127
3128 local ecolored = v_fill ~= "" and v_fill or false
3129 local opacity = v_opacity ~= ignoredopacity and v_opacity or false
3130
3131
3132
3133 if ecolored then
3134 local r, g, b = colorcomponents(v_fill)
3135 if r and g and b then
3136 if opacity then
3137 t[#t+1] = f_color_b(r,g,b,opacity)
3138 else
3139 t[#t+1] = f_color_c(r,g,b)
3140 end
3141 elseif opacity then
3142 t[#t+1] = f_color_o(opacity)
3143 else
3144 ecolored = false
3145 end
3146 elseif opacity then
3147 t[#t+1] = f_color_o(opacity)
3148 end
3149
3150 local hasa = ax ~= 0 or ay ~= 0
3151 if hasa then
3152
3153 t[#t+1] = f_set(ax or 0,ay or 0)
3154 t[#t+1] = "{"
3155 end
3156 for i=1,nt do
3157 local di = dt[i]
3158 if type(di) == "table" then
3159
3160 if #di.dt > 0 then
3161 collect(tg,t,di,x,y,usedsize,usedscale,v_family)
3162 end
3163 else
3164
3165 if i == 1 then
3166 di = gsub(di,"^%s+","")
3167 end
3168 if i == nt then
3169 di = gsub(di,"%s+$","")
3170 end
3171 local chars = utfsplit(di)
3172 if svghash then
3173
3174 di = f_hashed(svghash[di])
3175 else
3176 if tx or ty or tdx or tdy then
3177 local txi, tyi, tdxi, tdyi
3178 for i=1,#chars do
3179 txi = tx and (tx [i] or txi )
3180 tyi = ty and (ty [i] or tyi )
3181 tdxi = tdx and (tdx[i] or tdxi) or 0
3182 tdyi = tdy and (tdy[i] or tdyi) or 0
3183 local dx = (txi and (txi - x) or 0) + tdxi
3184 local dy = (tyi and (tyi - y) or 0) + tdyi
3185 local ci = chars[i]
3186 if ci == " " then
3187 chars[i] = f_posspace(dx, dy)
3188 elseif sensitive[ci] then
3189 chars[i] = f_poscode(dx, dy, utfbyte(ci))
3190 else
3191 chars[i] = f_poschar(dx, dy, ci)
3192 end
3193 end
3194 di = "{" .. concat(chars) .. "}"
3195 t[#t+1] = di
3196 else
3197
3198
3199
3200
3201 for i=1,#chars do
3202 local ci = chars[i]
3203 if ci == " " then
3204 chars[i] = s_space
3205 elseif sensitive[ci] then
3206 chars[i] = f_code(utfbyte(ci))
3207 else
3208 chars[i] = f_char(ci)
3209
3210 end
3211 end
3212 di = concat(chars)
3213 t[#t+1] = di
3214 end
3215 end
3216 end
3217 end
3218 if hasa then
3219 if t[#t] == "{" then
3220 t[#t] = nil
3221 t[#t] = nil
3222 else
3223 t[#t+1] = "}"
3224 end
3225 end
3226
3227 if opacity or ecolored then
3228 t[#t+1] = "}"
3229 end
3230
3231 t[#t+1] = "}"
3232
3233 if newfont then
3234 t[#t+1] = "}"
3235 end
3236
3237 return t
3238 end
3239
3240
3241
3242
3243
3244 local textlevel = 0
3245
3246 function handlers.text(c)
3247 if textlevel == 0 then
3248 x_family = v_family
3249 x_weight = v_weight
3250 x_style = v_style
3251 end
3252
3253 textlevel = textlevel + 1
3254
3255 local only = fullstrip(xmltextonly(c))
3256 local at = c.at
3257 local x = rawget(at,"x")
3258 local y = rawget(at,"y")
3259
3260 local dx = rawget(at,"dx")
3261 local dy = rawget(at,"dy")
3262
3263 local tx = asnumber_vx_t(x)
3264 local ty = asnumber_vy_t(y)
3265
3266 local tdx = asnumber_vx_t(dx)
3267 local tdy = asnumber_vy_t(dy)
3268
3269 x = tx[1] or 0
3270 y = ty[1] or 0
3271
3272 dx = tdx[1] or 0
3273 dy = tdy[1] or 0
3274
3275 local v_fill = at["fill"]
3276 if not v_fill or v_fill == "none" then
3277 v_fill = "black"
3278 end
3279 local color, opacity, option = fillproperties(v_fill,at)
3280 local anchor = anchors[at["text-anchor"] or "start"] or "drt"
3281 local remap = metapost.remappedtext(only)
3282
3283
3284 if remap then
3285 if x == 0 and y == 0 then
3286 only = f_mapped_simple_svg(remap.index)
3287 else
3288 only = f_mapped_normal_svg(remap.index,x,y)
3289 end
3290 flushobject(only,at,color,opacity)
3291 if trace_text then
3292 report("text: %s",only)
3293 end
3294 elseif option == "invisible" then
3295 if trace_text then
3296 report("invisible text: %s",only)
3297 end
3298 else
3299 local scale = 1
3300 local textid = 0
3301 local result = { }
3302 local nx = #tx
3303 local ny = #ty
3304 local ndx = #tdx
3305 local ndy = #tdy
3306
3307 local t = { }
3308 t[#t+1] = s_start
3309 if nx > 1 or ny > 1 or ndx > 1 or ndy > 1 then
3310 collect(tg,t,c,x,y,defaultsize,1,"serif",tx,ty,tdx,tdy)
3311 else
3312 collect(tg,t,c,x,y,defaultsize,1,"serif")
3313 end
3314 t[#t+1] = s_stop
3315 t = concat(t)
3316 if x == 0 and y == 0 then
3317 t = f_text_simple_svg(anchor,t)
3318 else
3319 t = f_text_normal_svg(anchor,t,x,y)
3320 end
3321
3322 flushobject(t,at,false,false)
3323 if trace_text then
3324 report("text: %s",result)
3325 end
3326 end
3327
3328 textlevel = textlevel - 1
3329 end
3330
3331 function metapost.reportsvgfonts()
3332 for family, weights in sortedhash(usedfonts) do
3333 for weight, styles in sortedhash(weights) do
3334 for style in sortedhash(styles) do
3335 report("used font: %s-%s-%s",family,weight,style)
3336 end
3337 end
3338 end
3339 end
3340
3341 statistics.register("used svg fonts",function()
3342 if next(usedfonts) then
3343
3344 logs.startfilelogging(report,"used svg fonts")
3345 local t = { }
3346 for family, weights in sortedhash(usedfonts) do
3347 for weight, styles in sortedhash(weights) do
3348 for style in sortedhash(styles) do
3349 report("%s-%s-%s",family,weight,style)
3350 t[#t+1] = formatters["%s-%s-%s"](family,weight,style)
3351 end
3352 end
3353 end
3354 logs.stopfilelogging()
3355 return concat(t," ")
3356 end
3357 end)
3358
3359 end
3360
3361 function handlers.svg(c,x,y,w,h,noclip,notransform,normalize)
3362 local at = c.at
3363
3364 local wrapupviewport
3365 local bhacked
3366 local ehacked
3367 local wd = w
3368
3369 local xpct, ypct, rpct
3370
3371 local btransform, etransform, transform = handletransform(at)
3372
3373 if trace then
3374 report("view: %s, xpct %N, ypct %N","before",percentage_x,percentage_y)
3375 end
3376
3377 local viewbox = at.viewBox
3378
3379 if viewbox then
3380 x, y, w, h = handleviewbox(viewbox)
3381 if trace then
3382 report("viewbox: x %N, y %N, width %N, height %N",x,y,w,h)
3383 end
3384 end
3385 if not w or not h or w == 0 or h == 0 then
3386 noclip = true
3387 end
3388 if h then
3389
3390
3391
3392
3393
3394
3395 xpct = percentage_x
3396 ypct = percentage_y
3397 rpct = percentage_r
3398 percentage_x = w / 100
3399 percentage_y = h / 100
3400 percentage_r = (sqrt(w^2 + h^2) / sqrt(2)) / 100
3401 if trace then
3402 report("view: %s, xpct %N, ypct %N","inside",percentage_x,percentage_y)
3403 end
3404 wrapupviewport = viewport(x,y,w,h,noclip)
3405 end
3406
3407
3408
3409 if v and normalize and w and wd and w ~= wd and w > 0 and wd > 0 then
3410 bhacked = s_wrapped_start
3411 ehacked = f_wrapped_stop(y or 0,wd/w)
3412 end
3413 if btransform then
3414 r = r + 1 ; result[r] = btransform
3415 end
3416 if bhacked then
3417 r = r + 1 ; result[r] = bhacked
3418 end
3419 local boffset, eoffset = handleoffset(at)
3420 if boffset then
3421 r = r + 1 result[r] = boffset
3422 end
3423
3424 at["transform"] = false
3425 at["viewBox"] = false
3426
3427 process(c,"/!(defs|symbol)")
3428
3429 at["transform"] = transform
3430 at["viewBox"] = viewbox
3431
3432 if eoffset then
3433 r = r + 1 result[r] = eoffset
3434 end
3435 if ehacked then
3436 r = r + 1 ; result[r] = ehacked
3437 end
3438 if etransform then
3439 r = r + 1 ; result[r] = etransform
3440 end
3441 if h then
3442
3443
3444
3445
3446 percentage_x = xpct
3447 percentage_y = ypct
3448 percentage_r = rpct
3449 if wrapupviewport then
3450 wrapupviewport()
3451 end
3452 end
3453 if trace then
3454 report("view: %s, xpct %N, ypct %N","after",percentage_x,percentage_y)
3455 end
3456 end
3457
3458 end
3459
3460 process = function(x,p)
3461 for c in xmlcollected(x,p) do
3462 local tg = c.tg
3463 local h = handlers[c.tg]
3464 if h then
3465 h(c)
3466 end
3467 end
3468 end
3469
3470
3471
3472
3473 function metapost.svgtomp(specification,pattern,notransform,normalize)
3474 local mps = ""
3475 local svg = specification.data
3476 images.resetstore("svg")
3477 if type(svg) == "string" then
3478 svg = xmlconvert(svg)
3479 end
3480 if svg then
3481 local c = xmlfirst(svg,pattern or "/svg")
3482 if c then
3483 root = svg
3484 result = { }
3485 r = 0
3486 definitions = { }
3487 tagstyles = { }
3488 classstyles = { }
3489 colormap = specification.colormap
3490 usedcolors = trace_colors and setmetatableindex("number") or false
3491 for s in xmlcollected(c,"style") do
3492 handlestyle(c)
3493 end
3494 handlechains(c)
3495 xmlinheritattributes(c)
3496 handledefinitions(c)
3497 handlers.svg (
3498 c,
3499 specification.x,
3500 specification.y,
3501 specification.width,
3502 specification.height,
3503 specification.noclip,
3504 notransform,
3505 normalize,
3506 specification.remap
3507 )
3508 if trace_result == "file" then
3509 io.savedata(
3510 tex.jobname .. "-svg-to-mp.tex",
3511 "\\startMPpage[instance=doublefun]\n" .. concat(result,"\n") .. "\n\\stopMPpage\n"
3512 )
3513 elseif trace_result then
3514 report("result graphic:\n %\n t",result)
3515 end
3516 if usedcolors and next(usedcolors) then
3517 report("graphic %a uses colors: %s",specification.id or "unknown",table.sequenced(usedcolors))
3518 end
3519 mps = concat(result," ")
3520 root = false
3521 result = false
3522 r = false
3523 definitions = false
3524 tagstyles = false
3525 classstyles = false
3526 colormap = false
3527 else
3528 report("missing svg root element")
3529 end
3530 else
3531 report("bad svg blob")
3532 end
3533 return mps
3534 end
3535
3536end
3537
3538
3539
3540
3541
3542do
3543
3544 local bpfactor = number.dimenfactors.bp
3545
3546 function metapost.includesvgfile(filename,offset)
3547 local fullname = resolvers.findbinfile(filename)
3548 if lfs.isfile(fullname) then
3549 context.startMPcode("doublefun")
3550 context('draw lmt_svg [ filename = "%s", offset = %N ] ;',filename,(offset or 0)*bpfactor)
3551 context.stopMPcode()
3552 end
3553 end
3554
3555 function metapost.includesvgbuffer(name,offset)
3556 context.startMPcode("doublefun")
3557 context('draw lmt_svg [ buffer = "%s", offset = %N ] ;',name or "",(offset or 0)*bpfactor)
3558 context.stopMPcode()
3559 end
3560
3561 interfaces.implement {
3562 name = "includesvgfile",
3563 actions = metapost.includesvgfile,
3564 arguments = { "string", "dimension" },
3565 }
3566
3567 interfaces.implement {
3568 name = "includesvgbuffer",
3569 actions = metapost.includesvgbuffer,
3570 arguments = { "string", "dimension" },
3571 }
3572
3573 function metapost.showsvgpage(data)
3574 local dd = data.data
3575 if not dd then
3576 local filename = data.filename
3577 local fullname = filename and resolvers.findbinfile(filename)
3578 dd = fullname and table.load(fullname)
3579 end
3580 if type(dd) == "table" then
3581 local comment = data.comment
3582 local offset = data.pageoffset
3583 local index = data.index
3584 local first = math.max(index or 1,1)
3585 local last = math.min(index or #dd,#dd)
3586 for i=first,last do
3587 local d = setmetatableindex( {
3588 data = dd[i],
3589 comment = comment and i or false,
3590 pageoffset = offset or nil,
3591 }, data)
3592 metapost.showsvgpage(d)
3593 end
3594 elseif data.method == "code" then
3595 context.startMPcode(doublefun)
3596 context(metapost.svgtomp(data))
3597 context.stopMPcode()
3598 else
3599 context.startMPpage { instance = "doublefun", offset = data.pageoffset or nil }
3600 context(metapost.svgtomp(data))
3601 local comment = data.comment
3602 if comment then
3603 context("draw boundingbox currentpicture withcolor .6red ;")
3604 context('draw textext.bot("\\strut\\tttf %s") ysized (10pt) shifted center bottomboundary currentpicture ;',comment)
3605 end
3606 context.stopMPpage()
3607 end
3608 end
3609
3610 function metapost.typesvgpage(data)
3611 local dd = data.data
3612 if not dd then
3613 local fn = data.filename
3614 dd = fn and table.load(fn)
3615 end
3616 if type(dd) == "table" then
3617 local index = data.index
3618 if index and index > 0 and index <= #dd then
3619 data = dd[index]
3620 else
3621 data = nil
3622 end
3623 end
3624 if type(data) == "string" and data ~= "" then
3625 buffers.assign("svgpage",data)
3626 context.typebuffer ({ "svgpage" }, { option = "XML", strip = "yes" })
3627 end
3628 end
3629
3630 function metapost.svgtopdf(data,...)
3631 local mps = metapost.svgtomp(data,...)
3632 if mps then
3633
3634 local pdf = metapost.simple("metafun",mps,true,false,"svg")
3635 if pdf then
3636 return pdf
3637 else
3638
3639 end
3640 else
3641
3642 end
3643 end
3644
3645end
3646
3647do
3648
3649 local runner = sandbox.registerrunner {
3650 name = "otfsvg2pdf",
3651 program = "context",
3652 template = "--batchmode --purgeall --runs=2 %filename%",
3653 reporter = report_svg,
3654 }
3655
3656
3657
3658
3659
3660 local decompress = gzip.decompress
3661 local compress = gzip.compress
3662
3663 function metapost.svgshapestopdf(svgshapes,pdftarget,report_svg)
3664 local texname = "temp-otf-svg-to-pdf.tex"
3665 local pdfname = "temp-otf-svg-to-pdf.pdf"
3666 local tucname = "temp-otf-svg-to-pdf.tuc"
3667 local nofshapes = #svgshapes
3668 local pdfpages = { filename = pdftarget }
3669 local pdfpage = 0
3670 local t = { }
3671 local n = 0
3672
3673 os.remove(texname)
3674 os.remove(pdfname)
3675 os.remove(tucname)
3676
3677 if report_svg then
3678 report_svg("processing %i svg containers",nofshapes)
3679 statistics.starttiming(pdfpages)
3680 end
3681
3682
3683
3684
3685
3686 n = n + 1 ; t[n] = "\\starttext"
3687 n = n + 1 ; t[n] = "\\setupMPpage[alternative=offset,instance=doublefun]"
3688
3689 for i=1,nofshapes do
3690 local entry = svgshapes[i]
3691 local data = entry.data
3692 if decompress then
3693 data = decompress(data) or data
3694 end
3695 local specification = {
3696 data = xmlconvert(data),
3697 x = 0,
3698 y = 1000,
3699 width = 1000,
3700 height = 1000,
3701 noclip = true,
3702 }
3703 for index=entry.first,entry.last do
3704 if not pdfpages[index] then
3705 pdfpage = pdfpage + 1
3706 pdfpages[index] = pdfpage
3707 local pattern = "/svg[@id='glyph" .. index .. "']"
3708 n = n + 1 ; t[n] = "\\startMPpage"
3709 n = n + 1 ; t[n] = metapost.svgtomp(specification,pattern,true,true) or ""
3710 n = n + 1 ; t[n] = "\\stopMPpage"
3711 end
3712 end
3713 end
3714 n = n + 1 ; t[n] = "\\stoptext"
3715 io.savedata(texname,concat(t,"\n"))
3716 runner { filename = texname }
3717 os.remove(pdftarget)
3718 file.copy(pdfname,pdftarget)
3719 if report_svg then
3720 statistics.stoptiming(pdfpages)
3721 report_svg("svg conversion time %s",statistics.elapsedseconds(pdfpages))
3722 end
3723 os.remove(texname)
3724 os.remove(pdfname)
3725 os.remove(tucname)
3726 return pdfpages
3727 end
3728
3729 function metapost.svgshapestomp(svgshapes,report_svg)
3730 local nofshapes = #svgshapes
3731 local mpshapes = { }
3732 if report_svg then
3733 report_svg("processing %i svg containers",nofshapes)
3734 statistics.starttiming(mpshapes)
3735 end
3736 for i=1,nofshapes do
3737 local entry = svgshapes[i]
3738 local data = entry.data
3739 if decompress then
3740 data = decompress(data) or data
3741 end
3742 local specification = {
3743 data = xmlconvert(data),
3744 x = 0,
3745 y = 1000,
3746 width = 1000,
3747 height = 1000,
3748 noclip = true,
3749 }
3750 for index=entry.first,entry.last do
3751 if not mpshapes[index] then
3752 local pattern = "/svg[@id='glyph" .. index .. "']"
3753 local mpcode = metapost.svgtomp(specification,pattern,true,true) or ""
3754 if mpcode ~= "" and compress then
3755 mpcode = compress(mpcode) or mpcode
3756 end
3757 mpshapes[index] = mpcode
3758 end
3759 end
3760 end
3761 if report_svg then
3762 statistics.stoptiming(mpshapes)
3763 report_svg("svg conversion time %s",statistics.elapsedseconds(mpshapes))
3764 end
3765 return mpshapes
3766 end
3767
3768 function metapost.svgglyphtomp(fontname,unicode)
3769 if fontname and unicode then
3770 local id = fonts.definers.internal { name = fontname }
3771 if id then
3772 local tfmdata = fonts.hashes.identifiers[id]
3773 if tfmdata then
3774 local properties = tfmdata.properties
3775 local svg = properties.svg
3776 local hash = svg and svg.hash
3777 local timestamp = svg and svg.timestamp
3778 if hash then
3779 local svgfile = containers.read(fonts.handlers.otf.svgcache,hash)
3780 local svgshapes = svgfile and svgfile.svgshapes
3781 if svgshapes then
3782 if type(unicode) == "string" then
3783 unicode = utfbyte(unicode)
3784 end
3785 local chardata = tfmdata.characters[unicode]
3786 local index = chardata and chardata.index
3787 if index then
3788 for i=1,#svgshapes do
3789 local entry = svgshapes[i]
3790 if index >= entry.first and index <= entry.last then
3791 local data = entry.data
3792 if data then
3793 local root = xml.convert(gzip.decompress(data) or data)
3794 return metapost.svgtomp (
3795 {
3796 data = root,
3797 x = 0,
3798 y = 1000,
3799 width = 1000,
3800 height = 1000,
3801 noclip = true,
3802 },
3803 "/svg[@id='glyph" .. index .. "']",
3804 true,
3805 true
3806 )
3807 end
3808 end
3809 end
3810 end
3811 end
3812 end
3813 end
3814 end
3815 end
3816 end
3817
3818end
3819 |