rays 0.3.11 → 0.3.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/sdl/font.cpp CHANGED
@@ -1,6 +1,13 @@
1
1
  #include "../font.h"
2
2
 
3
3
 
4
+ #ifdef WASM
5
+ #include <stdlib.h>
6
+ #include <memory>
7
+ #include <emscripten.h>
8
+ #endif
9
+ #include <SDL.h>
10
+ #include <SDL_ttf.h>
4
11
  #include "rays/exception.h"
5
12
 
6
13
 
@@ -11,22 +18,324 @@ namespace Rays
11
18
  struct RawFont::Data
12
19
  {
13
20
 
14
- String path;
21
+ String name;
22
+
23
+ coord size = 0;
24
+
25
+ virtual ~Data ()
26
+ {
27
+ }
28
+
29
+ virtual void draw_string (SDL_Surface* target, const char* str, coord x, coord y)
30
+ {
31
+ }
32
+
33
+ virtual coord get_width (const char* str)
34
+ {
35
+ return 0;
36
+ }
37
+
38
+ virtual coord get_height (coord* ascent, coord* descent, coord* leading)
39
+ {
40
+ if (ascent) *ascent = 0;
41
+ if (descent) *descent = 0;
42
+ if (leading) *leading = 0;
43
+ return 0;
44
+ }
45
+
46
+ virtual bool is_valid () const
47
+ {
48
+ return false;
49
+ }
50
+
51
+ virtual Data* dup (coord size) const
52
+ {
53
+ return NULL;
54
+ }
15
55
 
16
56
  };// RawFont::Data
17
57
 
18
58
 
59
+ struct SDLFontData : public RawFont::Data
60
+ {
61
+
62
+ TTF_Font* font = NULL;
63
+
64
+ String path;
65
+
66
+ SDLFontData (const char* path, coord size)
67
+ {
68
+ if (!path)
69
+ argument_error(__FILE__, __LINE__);
70
+
71
+ font = TTF_OpenFont(path, (int) size);
72
+ if (!font)
73
+ rays_error(__FILE__, __LINE__, "failed to open font: %s", TTF_GetError());
74
+
75
+ this->path = path;
76
+ this->size = size;
77
+
78
+ const char* family = TTF_FontFaceFamilyName(font);
79
+ if (family) this->name = family;
80
+ }
81
+
82
+ ~SDLFontData () override
83
+ {
84
+ if (font) TTF_CloseFont(font);
85
+ }
86
+
87
+ void draw_string (SDL_Surface* target, const char* str, coord x, coord y) override
88
+ {
89
+ SDL_Surface* surface = TTF_RenderUTF8_Blended(font, str, {255, 255, 255, 255});
90
+ if (!surface)
91
+ rays_error(__FILE__, __LINE__, "TTF_RenderUTF8_Blended failed: %s", TTF_GetError());
92
+
93
+ SDL_Rect dst = {(int) x, (int) y, surface->w, surface->h};
94
+ SDL_FillRect(target, &dst, 0);
95
+ SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
96
+ SDL_BlitSurface(surface, NULL, target, &dst);
97
+ SDL_FreeSurface(surface);
98
+ }
99
+
100
+ coord get_width (const char* str) override
101
+ {
102
+ int w = 0, h = 0;
103
+ if (TTF_SizeUTF8(font, str, &w, &h) < 0)
104
+ rays_error(__FILE__, __LINE__, "TTF_SizeUTF8 failed: %s", TTF_GetError());
105
+
106
+ return (coord) w;
107
+ }
108
+
109
+ coord get_height (coord* ascent, coord* descent, coord* leading) override
110
+ {
111
+ int asc = TTF_FontAscent(font);
112
+ int desc = -TTF_FontDescent(font);// TTF returns negative descent
113
+ int skip = TTF_FontLineSkip(font);
114
+
115
+ if (ascent) *ascent = (coord) asc;
116
+ if (descent) *descent = (coord) desc;
117
+ if (leading) *leading = (coord) (skip - asc - desc);
118
+
119
+ return (coord) (asc + desc);
120
+ }
121
+
122
+ bool is_valid () const override
123
+ {
124
+ return font;
125
+ }
126
+
127
+ Data* dup (coord size) const override
128
+ {
129
+ return new SDLFontData(path.c_str(), size);
130
+ }
131
+
132
+ };// SDLData
133
+
134
+
135
+ #ifdef WASM
136
+ // Browser-based font implementation: render text via an offscreen
137
+ // HTMLCanvasElement and copy the pixels into an SDL_Surface.
138
+
139
+ EM_JS(void, rays_wasm_font_init_, (),
140
+ {
141
+ if (Module._raysFontCanvas) return;
142
+ Module._raysFontCanvas = document.createElement('canvas');
143
+ Module._raysFontContext = Module._raysFontCanvas.getContext('2d');
144
+ });
145
+
146
+ EM_JS(double, rays_wasm_font_text_width_, (
147
+ const char* str, const char* name, double size),
148
+ {
149
+ const s = UTF8ToString(str);
150
+ const n = UTF8ToString(name);
151
+ const c = Module._raysFontContext;
152
+ c.font = `${size}px "${n}"`;
153
+ return c.measureText(s).width;
154
+ });
155
+
156
+ EM_JS(void, rays_wasm_font_get_metrics_, (
157
+ const char* name, double size,
158
+ double* out_ascent, double* out_descent, double* out_leading),
159
+ {
160
+ const n = UTF8ToString(name);
161
+ const c = Module._raysFontContext;
162
+ c.font = `${size}px "${n}"`;
163
+ const m = c.measureText('Mgjpqy');
164
+ const asc = Math.max(
165
+ m. fontBoundingBoxAscent ?? 0,
166
+ m.actualBoundingBoxAscent ?? size * 0.8);
167
+ const dsc = Math.max(
168
+ m. fontBoundingBoxDescent ?? 0,
169
+ m.actualBoundingBoxDescent ?? size * 0.2);
170
+
171
+ setValue(out_ascent, asc, 'double');
172
+ setValue(out_descent, dsc, 'double');
173
+ setValue(out_leading, size * 0.2, 'double');
174
+ });
175
+
176
+ EM_JS(void*, rays_wasm_font_render_, (
177
+ const char* str, const char* name, double size,
178
+ int* out_width, int* out_height, double* out_ascent, double* out_descent),
179
+ {
180
+ const s = UTF8ToString(str);
181
+ const n = UTF8ToString(name);
182
+ const c = Module._raysFontContext;
183
+ const cv = Module._raysFontCanvas;
184
+ c.font = `${size}px "${n}"`;
185
+ const m = c.measureText(s);
186
+ const asc = Math.max(
187
+ m. fontBoundingBoxAscent ?? 0,
188
+ m.actualBoundingBoxAscent ?? size * 0.8);
189
+ const dsc = Math.max(
190
+ m. fontBoundingBoxDescent ?? 0,
191
+ m.actualBoundingBoxDescent ?? size * 0.2);
192
+ const w = Math.max(1, Math.ceil(m.width));
193
+ const h = Math.max(1, Math.ceil(asc + dsc));
194
+
195
+ if (cv.width < w) cv.width = w;
196
+ if (cv.height < h) cv.height = h;
197
+
198
+ c.clearRect(0, 0, cv.width, cv.height);
199
+ c.font = `${size}px "${n}"`;// re-apply after canvas resize
200
+ c.fillStyle = 'white';
201
+ c.textBaseline = 'alphabetic';
202
+ c.fillText(s, 0, asc);
203
+
204
+ const data = c.getImageData(0, 0, w, h).data;
205
+ const ptr = _malloc(w * h * 4);
206
+ HEAPU8.set(data, ptr);
207
+ setValue(out_width, w, 'i32');
208
+ setValue(out_height, h, 'i32');
209
+ setValue(out_ascent, asc, 'double');
210
+ setValue(out_descent, dsc, 'double');
211
+ return ptr;
212
+ });
213
+
214
+ EM_JS(const char*, rays_wasm_font_families_, (),
215
+ {
216
+ const list = [
217
+ 'sans-serif', 'serif', 'monospace', 'cursive', 'fantasy',
218
+ 'system-ui', 'ui-sans-serif', 'ui-serif', 'ui-monospace',
219
+ 'Arial', 'Helvetica', 'Times New Roman', 'Times', 'Courier New',
220
+ 'Courier', 'Verdana', 'Georgia', 'Trebuchet MS', 'Comic Sans MS',
221
+ 'Impact', 'Tahoma', 'Lucida Sans Unicode', 'Lucida Console',
222
+ 'Palatino', 'Garamond', 'Book Antiqua', 'Hiragino Sans',
223
+ 'Hiragino Kaku Gothic ProN', 'Hiragino Mincho ProN',
224
+ 'Yu Gothic', 'Yu Mincho', 'Meiryo', 'MS Gothic', 'MS Mincho'
225
+ ];
226
+ const joined = list.join('\0') + '\0';
227
+ const len = lengthBytesUTF8(joined) + 1;
228
+ const ptr = _malloc(len);
229
+ stringToUTF8(joined, ptr, len);
230
+ return ptr;
231
+ });
232
+
233
+
234
+ struct CanvasFontData : public RawFont::Data
235
+ {
236
+
237
+ CanvasFontData (const char* name, coord size)
238
+ {
239
+ rays_wasm_font_init_();
240
+
241
+ this->name = name && *name != '\0' ? name : "sans-serif";
242
+ this->size = size;
243
+ }
244
+
245
+ void draw_string (SDL_Surface* target, const char* str, coord x, coord y) override
246
+ {
247
+ int w = 0, h = 0;
248
+ double ascent = 0, descent = 0;
249
+ std::shared_ptr<void> pixels(
250
+ rays_wasm_font_render_(
251
+ str, name.c_str(), (double) size,
252
+ &w, &h, &ascent, &descent),
253
+ free);
254
+ if (!pixels || w <= 0 || h <= 0) return;
255
+
256
+ SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
257
+ pixels.get(), w, h, 32, w * 4,
258
+ 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
259
+ if (!surface)
260
+ rays_error(__FILE__, __LINE__, "SDL_CreateRGBSurfaceFrom failed: %s", SDL_GetError());
261
+
262
+ SDL_Rect dest = {(int) x, (int) y, w, h};
263
+ SDL_FillRect(target, &dest, 0);
264
+ SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
265
+ SDL_BlitSurface(surface, NULL, target, &dest);
266
+ SDL_FreeSurface(surface);
267
+ }
268
+
269
+ coord get_width (const char* str) override
270
+ {
271
+ return (coord) rays_wasm_font_text_width_(str, name.c_str(), (double) size);
272
+ }
273
+
274
+ coord get_height (coord* ascent, coord* descent, coord* leading) override
275
+ {
276
+ double asc = 0, desc = 0, lead = 0;
277
+ rays_wasm_font_get_metrics_(name.c_str(), (double) size, &asc, &desc, &lead);
278
+
279
+ if (ascent) *ascent = (coord) asc;
280
+ if (descent) *descent = (coord) desc;
281
+ if (leading) *leading = (coord) lead;
282
+
283
+ return (coord) (asc + desc);
284
+ }
285
+
286
+ bool is_valid () const override
287
+ {
288
+ return size > 0 && !name.empty();
289
+ }
290
+
291
+ Data* dup (coord size) const override
292
+ {
293
+ return new CanvasFontData(name.c_str(), size);
294
+ }
295
+
296
+ };// CanvasData
297
+ #endif
298
+
299
+
300
+
19
301
  const FontFamilyMap&
20
302
  get_font_families ()
21
303
  {
304
+ #ifdef WASM
305
+ static const FontFamilyMap MAP = []()
306
+ {
307
+ rays_wasm_font_init_();
308
+ FontFamilyMap map;
309
+ const char* head = rays_wasm_font_families_();
310
+ const char* base = head;
311
+ while (head && *head)
312
+ {
313
+ String name(head);
314
+ if (!name.empty())
315
+ {
316
+ FontFamilyMap::mapped_type array;
317
+ array.emplace_back(name);
318
+ map[name] = array;
319
+ }
320
+ head += name.size() + 1;
321
+ }
322
+ free((void*) base);
323
+ return map;
324
+ }();
325
+ return MAP;
326
+ #else
22
327
  static const FontFamilyMap MAP;
23
328
  return MAP;
329
+ #endif
24
330
  }
25
331
 
332
+
26
333
  RawFont
27
334
  RawFont_load (const char* path, coord size)
28
335
  {
29
- return RawFont();
336
+ RawFont rawfont;
337
+ rawfont.self.reset(new SDLFontData(path, size));
338
+ return rawfont;
30
339
  }
31
340
 
32
341
 
@@ -34,12 +343,28 @@ namespace Rays
34
343
  {
35
344
  }
36
345
 
346
+ static RawFont::Data*
347
+ create_data (const char* name, coord size)
348
+ {
349
+ #ifdef WASM
350
+ return new CanvasFontData(name, size);
351
+ #else
352
+ if (name)
353
+ not_implemented_error(__FILE__, __LINE__);
354
+ return new SDLFontData("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", size);
355
+ #endif
356
+ }
357
+
37
358
  RawFont::RawFont (const char* name, coord size)
359
+ : self(create_data(name, size))
38
360
  {
39
361
  }
40
362
 
41
363
  RawFont::RawFont (const This& obj, coord size)
42
364
  {
365
+ if (!obj) return;
366
+
367
+ self.reset(obj.self->dup(size));
43
368
  }
44
369
 
45
370
  RawFont::~RawFont ()
@@ -48,39 +373,63 @@ namespace Rays
48
373
 
49
374
  void
50
375
  RawFont::draw_string (
51
- void* context_, coord context_height,
376
+ void* context, coord context_height,
52
377
  const char* str, coord x, coord y) const
53
378
  {
379
+ SDL_Surface* target = (SDL_Surface*) context;
380
+
381
+ if (!target)
382
+ argument_error(__FILE__, __LINE__);
383
+ if (!str)
384
+ argument_error(__FILE__, __LINE__);
385
+
386
+ if (*str == '\0') return;
387
+
388
+ if (!*this)
389
+ invalid_state_error(__FILE__, __LINE__);
390
+
391
+ self->draw_string(target, str, x, y);
54
392
  }
55
393
 
56
394
  String
57
395
  RawFont::name () const
58
396
  {
59
- return "";
397
+ if (!*this) return "";
398
+ return self->name;
60
399
  }
61
400
 
62
401
  coord
63
402
  RawFont::size () const
64
403
  {
65
- return 0;
404
+ if (!*this) return 0;
405
+ return self->size;
66
406
  }
67
407
 
68
408
  coord
69
409
  RawFont::get_width (const char* str) const
70
410
  {
71
- if (!str || *str == '\0') return 0;
72
- return 0;
411
+ if (!str)
412
+ argument_error(__FILE__, __LINE__);
413
+ if (!*this)
414
+ invalid_state_error(__FILE__, __LINE__);
415
+
416
+ if (*str == '\0') return 0;
417
+
418
+ return self->get_width(str);
73
419
  }
74
420
 
75
421
  coord
76
422
  RawFont::get_height (coord* ascent, coord* descent, coord* leading) const
77
423
  {
78
- return 0;
424
+ if (!*this)
425
+ invalid_state_error(__FILE__, __LINE__);
426
+
427
+ return self->get_height(ascent, descent, leading);
79
428
  }
80
429
 
81
430
  RawFont::operator bool () const
82
431
  {
83
- return true;
432
+ return self && self->is_valid();
84
433
  }
85
434
 
86
435
  bool
data/src/sdl/rays.cpp CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
 
4
4
  #include <SDL.h>
5
+ #include <SDL_ttf.h>
5
6
  #include "rays/exception.h"
6
7
  #include "rays/debug.h"
7
8
  #include "../renderer.h"
@@ -28,6 +29,9 @@ namespace Rays
28
29
  if (SDL_Init(SDL_INIT_VIDEO) < 0)
29
30
  rays_error(__FILE__, __LINE__, SDL_GetError());
30
31
 
32
+ if (TTF_Init() < 0)
33
+ rays_error(__FILE__, __LINE__, "TTF_Init failed: %s", TTF_GetError());
34
+
31
35
  Renderer_init();
32
36
 
33
37
  global::initialized = true;
@@ -41,6 +45,7 @@ namespace Rays
41
45
 
42
46
  Renderer_fin();
43
47
 
48
+ //TTF_Quit();
44
49
  SDL_Quit();
45
50
 
46
51
  global::initialized = false;
data/src/texture.h CHANGED
@@ -19,6 +19,8 @@ namespace Rays
19
19
  class Texture
20
20
  {
21
21
 
22
+ typedef Texture This;
23
+
22
24
  public:
23
25
 
24
26
  Texture ();
@@ -53,6 +55,10 @@ namespace Rays
53
55
 
54
56
  bool operator ! () const;
55
57
 
58
+ friend bool operator == (const This& lhs, const This& rhs);
59
+
60
+ friend bool operator != (const This& lhs, const This& rhs);
61
+
56
62
  struct Data;
57
63
 
58
64
  Xot::PSharedImpl<Data> self;
data/test/test_painter.rb CHANGED
@@ -193,43 +193,54 @@ class TestPainter < Test::Unit::TestCase
193
193
  end
194
194
 
195
195
  def test_clip_accessor()
196
- pa = painter
197
- pa.clip = [1, 2, 3, 4]
198
- assert_equal [1, 2, 3, 4], pa.clip.to_a
199
- pa.clip = [5, 6, 7, 8]
200
- assert_equal [5, 6, 7, 8], pa.clip.to_a
201
- pa.clip 1, 2, 3, 4
202
- assert_equal [1, 2, 3, 4], pa.clip.to_a
203
- pa.push clip: [5, 6, 7, 8] do |_|
196
+ pa = painter
197
+ pa.clip = [1, 2, 3, 4]
198
+ assert_equal [1, 2, 3, 4], pa.clip.to_a
199
+ pa.clip = [5, 6, 7, 8]
200
+ assert_equal [5, 6, 7, 8], pa.clip.to_a
201
+ pa.clip 1, 2, 3, 4
202
+ assert_equal [1, 2, 3, 4], pa.clip.to_a
203
+ pa.push clip: [5, 6, 7, 8] do |_|
204
204
  assert_equal [5, 6, 7, 8], pa.clip.to_a
205
205
  end
206
- assert_equal [1, 2, 3, 4], pa.clip.to_a
206
+ assert_equal [1, 2, 3, 4], pa.clip.to_a
207
207
  end
208
208
 
209
209
  def test_font_accessor()
210
- pa = painter
210
+ pa = painter
211
211
  f10, f20 = font(nil, 10), font(nil, 20)
212
- pa.font = f10
213
- assert_equal f10, pa.font
214
- pa.font = f20
215
- assert_equal f20, pa.font
216
- pa.font f10
217
- assert_equal f10, pa.font
218
- pa.push font: f20 do |_|
212
+ pa.font = f10
213
+ assert_equal f10, pa.font
214
+ pa.font = f20
215
+ assert_equal f20, pa.font
216
+ pa.font f10
217
+ assert_equal f10, pa.font
218
+ pa.push font: f20 do |_|
219
219
  assert_equal f20, pa.font
220
220
  end
221
- assert_equal f10, pa.font
221
+ assert_equal f10, pa.font
222
222
  end
223
223
 
224
224
  def test_font_name_size()
225
225
  pa = painter
226
- pa.font "Arial", 10
227
- assert_equal "Arial", pa.font.name
228
- assert_equal 10, pa.font.size
229
- pa.font nil
230
- assert_not_equal "Arial", pa.font.name
231
- pa.font nil, 20
232
- assert_equal 20, pa.font.size
226
+ pa.font "Arial", 10
227
+ assert_equal "Arial", pa.font.name
228
+ assert_equal 10, pa.font.size
229
+ pa.font nil
230
+ assert_not_equal "Arial", pa.font.name
231
+ assert_not_equal 10, pa.font.size
232
+ pa.font nil, 20
233
+ assert_not_equal "Arial", pa.font.name
234
+ assert_equal 20, pa.font.size
235
+ end
236
+
237
+ def test_debug_accessor()
238
+ pa = painter
239
+ assert_false pa.debug?
240
+ pa.debug = true
241
+ assert_true pa.debug?
242
+ pa.debug = false
243
+ assert_false pa.debug?
233
244
  end
234
245
 
235
246
  def test_color_by_name()