scarpe-components 0.2.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +85 -0
  4. data/README.md +2 -2
  5. data/assets/bootstrap-themes/bootstrap-cerulean.css +12229 -0
  6. data/assets/bootstrap-themes/bootstrap-cosmo.css +11810 -0
  7. data/assets/bootstrap-themes/bootstrap-cyborg.css +12210 -0
  8. data/assets/bootstrap-themes/bootstrap-darkly.css +12153 -0
  9. data/assets/bootstrap-themes/bootstrap-flatly.css +12126 -0
  10. data/assets/bootstrap-themes/bootstrap-icons.min.css +5 -0
  11. data/assets/bootstrap-themes/bootstrap-journal.css +12099 -0
  12. data/assets/bootstrap-themes/bootstrap-litera.css +12211 -0
  13. data/assets/bootstrap-themes/bootstrap-lumen.css +12369 -0
  14. data/assets/bootstrap-themes/bootstrap-lux.css +11928 -0
  15. data/assets/bootstrap-themes/bootstrap-materia.css +13184 -0
  16. data/assets/bootstrap-themes/bootstrap-minty.css +12177 -0
  17. data/assets/bootstrap-themes/bootstrap-morph.css +12750 -0
  18. data/assets/bootstrap-themes/bootstrap-pulse.css +11890 -0
  19. data/assets/bootstrap-themes/bootstrap-quartz.css +12622 -0
  20. data/assets/bootstrap-themes/bootstrap-sandstone.css +12201 -0
  21. data/assets/bootstrap-themes/bootstrap-simplex.css +12186 -0
  22. data/assets/bootstrap-themes/bootstrap-sketchy.css +12451 -0
  23. data/assets/bootstrap-themes/bootstrap-slate.css +12492 -0
  24. data/assets/bootstrap-themes/bootstrap-solar.css +12149 -0
  25. data/assets/bootstrap-themes/bootstrap-spacelab.css +12266 -0
  26. data/assets/bootstrap-themes/bootstrap-superhero.css +12216 -0
  27. data/assets/bootstrap-themes/bootstrap-united.css +12077 -0
  28. data/assets/bootstrap-themes/bootstrap-vapor.css +12549 -0
  29. data/assets/bootstrap-themes/bootstrap-yeti.css +12325 -0
  30. data/assets/bootstrap-themes/bootstrap-zephyr.css +12283 -0
  31. data/assets/bootstrap-themes/bootstrap.bundle.min.js +7 -0
  32. data/lib/scarpe/components/asset_server.rb +219 -0
  33. data/lib/scarpe/components/base64.rb +23 -5
  34. data/lib/scarpe/components/calzini/alert.rb +49 -0
  35. data/lib/scarpe/components/calzini/art_drawables.rb +227 -0
  36. data/lib/scarpe/components/calzini/border.rb +38 -0
  37. data/lib/scarpe/components/calzini/button.rb +37 -0
  38. data/lib/scarpe/components/calzini/misc.rb +136 -0
  39. data/lib/scarpe/components/calzini/para.rb +237 -0
  40. data/lib/scarpe/components/calzini/slots.rb +109 -0
  41. data/lib/scarpe/components/calzini.rb +236 -0
  42. data/lib/scarpe/components/errors.rb +24 -0
  43. data/lib/scarpe/components/file_helpers.rb +1 -0
  44. data/lib/scarpe/components/html.rb +134 -0
  45. data/lib/scarpe/components/minitest_export_reporter.rb +83 -0
  46. data/lib/scarpe/components/minitest_import_runnable.rb +98 -0
  47. data/lib/scarpe/components/minitest_result.rb +127 -0
  48. data/lib/scarpe/components/modular_logger.rb +5 -5
  49. data/lib/scarpe/components/print_logger.rb +22 -3
  50. data/lib/scarpe/components/process_helpers.rb +37 -0
  51. data/lib/scarpe/components/promises.rb +14 -14
  52. data/lib/scarpe/components/segmented_file_loader.rb +36 -17
  53. data/lib/scarpe/components/string_helpers.rb +10 -0
  54. data/lib/scarpe/components/tiranti.rb +167 -0
  55. data/lib/scarpe/components/unit_test_helpers.rb +48 -6
  56. data/lib/scarpe/components/version.rb +2 -2
  57. metadata +48 -4
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scarpe::Components::Calzini
4
+ def arc_element(props, &block)
5
+ dc = props["draw_context"] || {}
6
+ rotate = dc["rotate"]
7
+ HTML.render do |h|
8
+ h.div(id: html_id, style: arc_style(props)) do
9
+ h.svg(width: props["width"], height: props["height"]) do
10
+ h.path(d: arc_path(props), transform: "rotate(#{rotate}, #{props["width"] / 2}, #{props["height"] / 2})")
11
+ end
12
+ block.call(h) if block_given?
13
+ end
14
+ end
15
+ end
16
+
17
+ def rect_element(props)
18
+ dc = props["draw_context"] || {}
19
+ rotate = dc["rotate"]
20
+ HTML.render do |h|
21
+ h.div(id: html_id, style: drawable_style(props)) do
22
+ width = props["width"].to_i
23
+ height = props["height"].to_i
24
+ if props["curve"]
25
+ width += 2 * props["curve"].to_i
26
+ height += 2 * props["curve"].to_i
27
+ end
28
+ h.svg(width:, height:) do
29
+ attrs = { x: props["left"], y: props["top"], width: props["width"], height: props["height"], style: rect_svg_style(props) }
30
+ attrs[:rx] = props["curve"] if props["curve"]
31
+
32
+ h.rect(**attrs, transform: "rotate(#{rotate} #{width / 2} #{height / 2})")
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def line_element(props)
39
+ HTML.render do |h|
40
+ h.div(id: html_id, style: line_div_style(props)) do
41
+ h.svg(width: props["x2"], height: props["y2"]) do
42
+ h.line(x1: props["left"], y1: props["top"], x2: props["x2"], y2: props["y2"], style: line_svg_style(props))
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def star_element(props, &block)
49
+ dc = props["draw_context"] || {}
50
+ fill = first_color_of(props["fill"], dc["fill"], "black")
51
+ stroke = first_color_of(props["stroke"], dc["stroke"], "black")
52
+ HTML.render do |h|
53
+ h.div(id: html_id, style: star_style(props)) do
54
+ h.svg(width: props["outer"], height: props["outer"], style: "fill:#{fill}") do
55
+ h.polygon(points: star_points(props), style: "stroke:#{stroke};stroke-width:2")
56
+ end
57
+ block.call(h) if block_given?
58
+ end
59
+ end
60
+ end
61
+
62
+ def oval_element(props, &block)
63
+ dc = props["draw_context"] || {}
64
+ fill = first_color_of(props["fill"], dc["fill"], "black")
65
+ stroke = first_color_of(props["stroke"], dc["stroke"], "black")
66
+ strokewidth = props["strokewidth"] || dc["strokewidth"] || "2"
67
+ radius = props["radius"]
68
+ width = radius * 2
69
+ height = props["height"] || radius * 2 # If there's a height, it's an oval, if not, circle
70
+ center = props["center"] || false
71
+ HTML.render do |h|
72
+ h.div(id: html_id, style: oval_style(props)) do
73
+ h.svg(width: width, height: height, style: "fill:#{fill};") do
74
+ h.ellipse(
75
+ cx: center ? radius : 0,
76
+ cy: center ? height / 2 : 0,
77
+ rx: width ? width / 2 : radius,
78
+ ry: height ? height / 2 : radius,
79
+ style: "stroke:#{stroke};stroke-width:#{strokewidth};",
80
+ )
81
+ end
82
+ block.call(h) if block_given?
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def arc_style(props)
90
+ drawable_style(props).merge({
91
+ left: "#{props["left"]}px",
92
+ top: "#{props["top"]}px",
93
+ width: "#{props["width"]}px",
94
+ height: "#{props["height"]}px",
95
+ })
96
+ end
97
+
98
+ def arc_path(props)
99
+ center_x = props["width"] / 2
100
+ center_y = props["height"] / 2
101
+ radius_x = props["width"] / 2
102
+ radius_y = props["height"] / 2
103
+ start_angle_degrees = radians_to_degrees(props["angle1"]) % 360
104
+ end_angle_degrees = radians_to_degrees(props["angle2"]) % 360
105
+ large_arc_flag = (end_angle_degrees - start_angle_degrees) % 360 > 180 ? 1 : 0
106
+
107
+ "M#{center_x} #{center_y} L#{props["width"]} #{center_y} " \
108
+ "A#{radius_x} #{radius_y} 0 #{large_arc_flag} 0 " \
109
+ "#{center_x + radius_x * Math.cos(degrees_to_radians(end_angle_degrees))} " \
110
+ "#{center_y + radius_y * Math.sin(degrees_to_radians(end_angle_degrees))} Z"
111
+ end
112
+
113
+ def line_div_style(props)
114
+ drawable_style(props).merge({
115
+ left: "#{props["left"]}px",
116
+ top: "#{props["top"]}px",
117
+ })
118
+ end
119
+
120
+ def line_svg_style(props)
121
+ {
122
+
123
+ "stroke": first_color_of(props["stroke"], (props["draw_context"] || {})["stroke"], "black"),
124
+ "stroke-width": "4",
125
+ }.compact
126
+ end
127
+
128
+ def rect_svg_style(props)
129
+ {
130
+ stroke: first_color_of(props["stroke"], (props["draw_context"] || {})["stroke"]),
131
+ #"stroke-width": "1",
132
+ fill: first_color_of(props["fill"], (props["draw_context"] || {})["fill"]),
133
+ }.compact
134
+ end
135
+
136
+ def star_style(props)
137
+ drawable_style(props).merge({
138
+ width: dimensions_length(props["width"]),
139
+ height: dimensions_length(props["height"]),
140
+ }).compact
141
+ end
142
+
143
+ def oval_style(props)
144
+ drawable_style(props).merge({
145
+ width: dimensions_length(props["width"]),
146
+ height: dimensions_length(props["height"]),
147
+ })
148
+ end
149
+
150
+ def star_points(props)
151
+ angle = 2 * Math::PI / props["points"]
152
+ coordinates = []
153
+
154
+ props["points"].times do |i|
155
+ outer_angle = i * angle
156
+ inner_angle = outer_angle + angle / 2
157
+
158
+ coordinates.concat(star_get_coordinates(outer_angle, inner_angle, props))
159
+ end
160
+
161
+ coordinates.join(",")
162
+ end
163
+
164
+ def star_get_coordinates(outer_angle, inner_angle, props)
165
+ outer_x = props["outer"] / 2 + Math.cos(outer_angle) * props["outer"] / 2
166
+ outer_y = props["outer"] / 2 + Math.sin(outer_angle) * props["outer"] / 2
167
+
168
+ inner_x = props["outer"] / 2 + Math.cos(inner_angle) * props["inner"] / 2
169
+ inner_y = props["outer"] / 2 + Math.sin(inner_angle) * props["inner"] / 2
170
+
171
+ [outer_x, outer_y, inner_x, inner_y]
172
+ end
173
+
174
+ def arrow_element(props)
175
+ left = props["left"]
176
+ top = props["top"]
177
+ width = props["width"]
178
+ end_x = left + width
179
+ end_y = top
180
+ stroke_width = width / 2
181
+ dc = props["draw_context"] || {}
182
+ fill = first_color_of(props["fill"], dc["fill"], "black")
183
+ stroke = first_color_of(props["stroke"], dc["stroke"], "black")
184
+ rotate = dc["rotate"]
185
+
186
+ stroke_width = width / 4
187
+
188
+ HTML.render do |h|
189
+ h.div(id: html_id, style: arrow_div_style(props)) do
190
+ h.svg do
191
+ h.defs do
192
+ h.marker(
193
+ id: "head",
194
+ viewBox: "0 0 70 70",
195
+ markerWidth: stroke_width.to_s,
196
+ markerHeight: stroke_width.to_s,
197
+ refX: "5",
198
+ refY: "5",
199
+ orient: "auto-start-reverse",
200
+ ) do
201
+ h.path(d: "M 0 0 L 10 5 L 0 10 z", fill: fill)
202
+ end
203
+ end
204
+
205
+ h.line(
206
+ x2: left.to_s,
207
+ y2: top.to_s,
208
+ x1: end_x.to_s,
209
+ y1: end_y.to_s,
210
+ fill: fill,
211
+ stroke: stroke,
212
+ "stroke-width" => stroke_width.to_s,
213
+ "marker-end" => "url(#head)",
214
+ transform: "rotate(#{rotate}, #{left + width / 2}, #{top})",
215
+ )
216
+ end
217
+ end
218
+ end
219
+ end
220
+ def arrow_div_style(props)
221
+ drawable_style(props).merge({
222
+ position: "absolute",
223
+ left: "#{props["left"]}px",
224
+ top: "#{props["top"]}px",
225
+ })
226
+ end
227
+ end
@@ -0,0 +1,38 @@
1
+ module Scarpe::Components::Calzini
2
+ def border_element(props)
3
+ HTML.render do |h|
4
+ h.div(id: html_id, style: style(props))
5
+ end
6
+ end
7
+
8
+ private
9
+
10
+ def style(props)
11
+ styles = {
12
+ height: :inherit,
13
+ width: :inherit,
14
+ position: :absolute,
15
+ top: 0,
16
+ left: 0,
17
+ 'box-sizing': 'border-box'
18
+ }
19
+
20
+ bc = props["stroke"]
21
+
22
+ border_style_hash = case bc
23
+ when Range
24
+ { "border-image": "linear-gradient(45deg, #{bc.first}, #{bc.last})" }
25
+ when Array
26
+ { "border-color": "rgba(#{bc.join(", ")})" }
27
+ else
28
+ { "border-color": bc }
29
+ end
30
+ styles = styles.merge(
31
+ "border-style": "solid",
32
+ "border-width": "#{props["strokewidth"]}px",
33
+ "border-radius": "#{props["curve"]}px",
34
+ ).merge(border_style_hash)
35
+
36
+ styles.compact
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scarpe::Components::Calzini
4
+ def button_element(props)
5
+ HTML.render do |h|
6
+ button_props = {
7
+ id: html_id,
8
+ onclick: handler_js_code("click"),
9
+ onmouseover: handler_js_code("hover"),
10
+ style: button_style(props),
11
+ class: props["html_class"],
12
+ title: props["tooltip"],
13
+ }.compact
14
+ h.button(**button_props) do
15
+ props["text"]
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def button_style(props)
23
+ styles = drawable_style(props)
24
+
25
+ styles[:"background-color"] = props["color"] if props["color"]
26
+ styles[:"padding-top"] = props["padding_top"] if props["padding_top"]
27
+ styles[:"padding-bottom"] = props["padding_bottom"] if props["padding_bottom"]
28
+ styles[:color] = props["text_color"] if props["text_color"]
29
+
30
+ styles[:"font-size"] = props["font_size"] if props["font_size"]
31
+ styles[:"font-size"] = dimensions_length(text_size(props["size"])) if props["size"]
32
+
33
+ styles[:"font-family"] = props["font"] if props["font"]
34
+
35
+ styles
36
+ end
37
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scarpe::Components::Calzini
4
+ def check_element(props)
5
+ HTML.render do |h|
6
+ h.input type: :checkbox,
7
+ id: html_id,
8
+ onclick: handler_js_code("click"),
9
+ value: props["text"],
10
+ checked: props["checked"],
11
+ style: drawable_style(props)
12
+ end
13
+ end
14
+
15
+ def edit_box_element(props)
16
+ oninput = handler_js_code("change", "this.value")
17
+
18
+ HTML.render do |h|
19
+ h.textarea(id: html_id, oninput: oninput,onmouseover: handler_js_code("hover"), style: edit_box_style(props),title: props["tooltip"]) { props["text"] }
20
+ end
21
+ end
22
+
23
+ def edit_line_element(props)
24
+ oninput = handler_js_code("change", "this.value")
25
+
26
+ HTML.render do |h|
27
+ h.input(id: html_id, oninput: oninput, onmouseover: handler_js_code("hover"), value: props["text"], style: edit_line_style(props),title: props["tooltip"])
28
+ end
29
+ end
30
+
31
+ def image_element(props)
32
+ style = drawable_style(props)
33
+
34
+ if props["click"]
35
+ HTML.render do |h|
36
+ h.a(id: html_id, href: props["click"]) { h.img(id: html_id, src: props["url"], style:) }
37
+ end
38
+ else
39
+ HTML.render do |h|
40
+ h.img(id: html_id, src: props["url"], style:)
41
+ end
42
+ end
43
+ end
44
+
45
+ def list_box_element(props)
46
+ onchange = handler_js_code("change", "this.options[this.selectedIndex].value")
47
+
48
+ # Is this useful at all? Is it overridden below completely?
49
+ option_attrs = { value: nil, selected: false }
50
+
51
+ HTML.render do |h|
52
+ h.select(id: html_id, onchange:, style: list_box_style(props)) do
53
+ (props["items"] || []).each do |item|
54
+ option_attrs = {
55
+ value: item,
56
+ }
57
+ if item == props["choose"]
58
+ option_attrs[:selected] = "true"
59
+ end
60
+ h.option(**option_attrs) do
61
+ item
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def radio_element(props)
69
+ # This is wrong - need to default to the parent slot -- maybe its linkable ID?
70
+ group_name = props["group"] || "no_group"
71
+
72
+ HTML.render do |h|
73
+ h.input(
74
+ type: :radio,
75
+ id: html_id,
76
+ onclick: handler_js_code("click"),
77
+ name: group_name,
78
+ value: props["text"],
79
+ checked: props["checked"],
80
+ style: drawable_style(props),
81
+ )
82
+ end
83
+ end
84
+
85
+ def video_element(props)
86
+ HTML.render do |h|
87
+ h.video(id: html_id, style: drawable_style(props), controls: true) do
88
+ h.source(src: @url, type: props["format"])
89
+ end
90
+ end
91
+ end
92
+
93
+ def progress_element(props)
94
+ HTML.render do |h|
95
+ h.progress(
96
+ id: html_id,
97
+ style: drawable_style(props),
98
+ role: "progressbar",
99
+ "aria-valuenow": props["fraction"],
100
+ "aria-valuemin": 0.0,
101
+ "aria-valuemax": 1.0,
102
+ max: 1,
103
+ value: props["fraction"],
104
+ )
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def edit_box_style(props)
111
+ drawable_style(props).merge({
112
+ height: dimensions_length(props["height"]),
113
+ width: dimensions_length(props["width"]),
114
+ font: props["font"]? parse_font(props) : nil
115
+ }.compact)
116
+ end
117
+
118
+ def edit_line_style(props)
119
+ styles = drawable_style(props)
120
+
121
+ styles[:font] = props["font"]? parse_font(props) : nil
122
+ styles[:width] = dimensions_length(props["width"]) if props["width"]
123
+ styles[:color] = rgb_to_hex(props["stroke"])
124
+
125
+ styles
126
+ end
127
+
128
+ def list_box_style(props)
129
+ styles = drawable_style(props)
130
+
131
+ styles[:height] = dimensions_length(props["height"]) if props["height"]
132
+ styles[:width] = dimensions_length(props["width"]) if props["width"]
133
+
134
+ styles
135
+ end
136
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Scarpe::Components::Calzini
4
+ def para_element(props, &block)
5
+ # Align requires an extra wrapping div.
6
+
7
+ # Stacking strikethrough with underline requires multiple elements.
8
+ # We handle this by making strikethrough part of the main element,
9
+ # but using an extra wrapping element for underline.
10
+
11
+ tag = props["tag"] || "p"
12
+
13
+ para_styles, extra_styles = para_style(props)
14
+
15
+ HTML.render do |h|
16
+ if extra_styles.empty?
17
+ h.send(tag, id: html_id, style: para_styles, &block)
18
+ else
19
+ h.div(id: html_id, style: extra_styles.merge(width: "100%")) do
20
+ h.send(tag, style: para_styles, &block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def para_style(props)
29
+
30
+ ds = drawable_style(props)
31
+ s1, s2 = text_specific_styles(props)
32
+ [ds.merge(s1), s2]
33
+ end
34
+
35
+ def text_specific_styles(props)
36
+ # Shoes3 allows align: right on TextDrawables like em(), but it does
37
+ # nothing. We can ignore it or (maybe in future?) warn if we see it.
38
+
39
+ strikethrough = props["strikethrough"]
40
+ strikethrough = nil if strikethrough == "" || strikethrough == "none"
41
+ s1 = {
42
+ "font-variant": props["font_variant"],
43
+ "color": rgb_to_hex(props["stroke"]),
44
+ "background-color": rgb_to_hex(props["fill"]),
45
+ "font-size": para_font_size(props),
46
+ "font-family": props["family"],
47
+ "text-decoration-line": strikethrough ? "line-through" : nil,
48
+ "text-decoration-color": props["strikecolor"] ? rgb_to_hex(props["strikecolor"]) : nil,
49
+ "font-weight": props["font_weight"]? props["font_weight"] : nil,
50
+ :'font-style' => case props["emphasis"]
51
+ when "normal"
52
+ "normal"
53
+ when "oblique"
54
+ "oblique"
55
+ when "italic"
56
+ "italic"
57
+ else
58
+ nil
59
+ end,
60
+ :'letter-spacing' => props["kerning"] ? "#{props["kerning"]}px" : nil,
61
+ :'vertical-align' => props["rise"] ? "#{props["rise"]}px" : nil
62
+ }.compact
63
+
64
+ s2 = {}
65
+ if props["align"] && props["align"] != ""
66
+ s2[:"text-align"] = props["align"]
67
+ end
68
+
69
+ unless [nil, "none"].include?(props["underline"])
70
+ undercolor = rgb_to_hex props["undercolor"]
71
+ s2["text-decoration-color"] = undercolor if undercolor
72
+ end
73
+
74
+ # [nil, "none", "single", "double", "low", "error"]
75
+ case props["underline"]
76
+ when nil, "none"
77
+ # Do nothing
78
+ when "single"
79
+ s2["text-decoration-line"] = "underline"
80
+ when "double"
81
+ s2["text-decoration-line"] = "underline"
82
+ s2["text-decoration-style"] = "double"
83
+ when "error"
84
+ s2["text-decoration-line"] = "underline"
85
+ s2["text-decoration-style"] = "wavy"
86
+ when "low"
87
+ s2["text-decoration-line"] = "underline"
88
+ s2["text-underline-offset"] = "0.3rem"
89
+ else
90
+ # This should normally be unreachable
91
+ raise Shoes::Errors::InvalidAttributeValueError, "Unexpected underline type #{props["underline"].inspect}!"
92
+ end
93
+
94
+ [s1, s2]
95
+ end
96
+
97
+
98
+ def parse_font(props)
99
+
100
+ def contains_number?(str)
101
+ !!(str =~ /\d/)
102
+ end
103
+ def contains_only_numbers?(string)
104
+ /^\d+\z/ =~ string
105
+ end
106
+
107
+
108
+ input = props["font"]
109
+ regex = /\s+(?=(?:[^']*'[^']*')*[^']*$)(?![^']*,[^']*')/
110
+ result = input.split(regex)
111
+
112
+ fs = "normal"
113
+ fv = "normal"
114
+ fw = "normal"
115
+ fss = "medium"
116
+ ff = "Arial"
117
+
118
+ fos = ["italic", "oblique"]
119
+ fov = ["small-caps", "initial", "inherit"]
120
+ fow = ["bold", "bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900"]
121
+ foss = ["xx-small", "x-small", "small","large", "x-large", "xx-large", "smaller", "larger"]
122
+
123
+ result.each do |i|
124
+ if fos.include?(i)
125
+ fs = i
126
+ next
127
+ elsif fov.include?(i)
128
+ fv = i
129
+ next
130
+ elsif fow.include?(i)
131
+ fw = i
132
+ next
133
+ elsif foss.include?(i)
134
+ fss = i
135
+ next
136
+ else
137
+ if contains_number?(i)
138
+
139
+ if contains_only_numbers?(i)
140
+ fss = i + "px"
141
+ else
142
+ fss = i
143
+ end
144
+
145
+ elsif i != "normal" && i != "medium" && i.strip != ""
146
+
147
+ if ff == "Arial"
148
+
149
+ ff = i
150
+
151
+ else
152
+
153
+ ff = ff+ i
154
+
155
+ end
156
+ end
157
+ end
158
+
159
+ end
160
+
161
+
162
+ "#{fs} #{fv} #{fw} #{fss} #{ff}"
163
+
164
+ end
165
+
166
+ def para_font_size(props)
167
+ return nil unless props["size"]
168
+
169
+ sz = props["size"].to_s
170
+ font_size = SIZES[sz.to_sym] || sz.to_i
171
+
172
+ dimensions_length(font_size)
173
+ end
174
+
175
+ public
176
+
177
+ # The text element is used to render the equivalent of Shoes cText,
178
+ # which includes em, strong, span, link and so on. We use a
179
+ # "content" tag for it which alternates plaintext with a hash of
180
+ # properties.
181
+ def text_drawable_element(prop_array)
182
+ out = String.new # Need unfrozen string
183
+
184
+ # Each item should be a String or a property Hash
185
+ # :items, :html_id, :tag, :props
186
+ prop_array.each do |item|
187
+ if item.is_a?(String)
188
+ out << item.gsub("\n", "<br/>")
189
+ else
190
+ s, extra = text_drawable_style(item[:props])
191
+ out << HTML.render do |h|
192
+ if extra.empty?
193
+ h.send(
194
+ item[:tag] || "span",
195
+ class: "id_#{item[:html_id]}",
196
+ style: s,
197
+ **text_drawable_attrs(item[:props])
198
+ ) do
199
+ text_drawable_element(item[:items])
200
+ end
201
+ else
202
+ h.span(class: "id_#{item[:html_id]}", style: extra) do
203
+ h.send(
204
+ item[:tag] || "span",
205
+ class: "id_#{item[:html_id]}",
206
+ style: s,
207
+ **text_drawable_attrs(item[:props])
208
+ ) do
209
+ text_drawable_element(item[:items])
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
215
+ end
216
+
217
+ out
218
+ end
219
+
220
+ private
221
+
222
+ def text_drawable_attrs(props)
223
+ {
224
+ # These properties will normally only be set by link()
225
+ href: props["click"],
226
+ onclick: props["has_block"] ? handler_js_code("click") : nil,
227
+ }.compact
228
+ end
229
+
230
+ def text_drawable_style(props)
231
+ s, extra_s = text_specific_styles(props)
232
+
233
+ # Add hover styles, perhaps with CSS pseudo-class
234
+
235
+ [drawable_style(props).merge(s), extra_s]
236
+ end
237
+ end