gosu 0.13.3 → 0.14.0.pre2

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/Gosu/Audio.hpp +15 -11
  3. data/Gosu/Font.hpp +24 -20
  4. data/Gosu/Fwd.hpp +1 -1
  5. data/Gosu/Graphics.hpp +8 -9
  6. data/Gosu/ImageData.hpp +1 -1
  7. data/Gosu/Input.hpp +1 -1
  8. data/Gosu/Math.hpp +0 -18
  9. data/Gosu/Text.hpp +22 -30
  10. data/Gosu/TextInput.hpp +13 -0
  11. data/Gosu/Utility.hpp +2 -0
  12. data/Gosu/Window.hpp +3 -3
  13. data/README.md +3 -4
  14. data/ext/gosu/extconf.rb +7 -9
  15. data/lib/gosu/swig_patches.rb +1 -4
  16. data/rdoc/gosu.rb +34 -9
  17. data/src/Audio.cpp +6 -6
  18. data/src/AudioImpl.cpp +2 -2
  19. data/src/Bitmap.cpp +1 -2
  20. data/src/BitmapIO.cpp +21 -2
  21. data/src/BlockAllocator.cpp +1 -1
  22. data/src/Channel.cpp +7 -1
  23. data/src/ClipRectStack.hpp +4 -1
  24. data/src/Color.cpp +2 -1
  25. data/src/DirectoriesWin.cpp +1 -1
  26. data/src/DrawOp.hpp +8 -4
  27. data/src/DrawOpQueue.hpp +13 -24
  28. data/src/FileUnix.cpp +3 -1
  29. data/src/Font.cpp +92 -96
  30. data/src/GosuGLView.cpp +59 -31
  31. data/src/GosuGLView.hpp +14 -0
  32. data/src/GosuViewController.cpp +21 -21
  33. data/src/{GosuViewController.h → GosuViewController.hpp} +2 -4
  34. data/src/Graphics.cpp +71 -38
  35. data/src/GraphicsImpl.hpp +12 -29
  36. data/src/Image.cpp +5 -7
  37. data/src/Input.cpp +7 -5
  38. data/src/InputUIKit.cpp +19 -37
  39. data/src/Macro.cpp +10 -2
  40. data/src/MarkupParser.cpp +241 -0
  41. data/src/MarkupParser.hpp +61 -0
  42. data/src/Math.cpp +1 -1
  43. data/src/OffScreenTarget.cpp +99 -0
  44. data/src/OffScreenTarget.hpp +23 -0
  45. data/src/OggFile.hpp +10 -0
  46. data/src/RenderState.hpp +0 -2
  47. data/src/Resolution.cpp +2 -2
  48. data/src/RubyGosu.cxx +457 -244
  49. data/src/TexChunk.cpp +8 -6
  50. data/src/Text.cpp +58 -345
  51. data/src/TextBuilder.cpp +138 -0
  52. data/src/TextBuilder.hpp +55 -0
  53. data/src/TextInput.cpp +27 -10
  54. data/src/Texture.cpp +22 -17
  55. data/src/Texture.hpp +19 -20
  56. data/src/TimingApple.cpp +5 -7
  57. data/src/TimingUnix.cpp +1 -4
  58. data/src/TimingWin.cpp +4 -1
  59. data/src/TrueTypeFont.cpp +282 -0
  60. data/src/TrueTypeFont.hpp +66 -0
  61. data/src/TrueTypeFontApple.cpp +65 -0
  62. data/src/TrueTypeFontUnix.cpp +91 -0
  63. data/src/TrueTypeFontWin.cpp +82 -0
  64. data/src/Utility.cpp +40 -0
  65. data/src/Window.cpp +7 -6
  66. data/src/WindowUIKit.cpp +9 -4
  67. data/src/stb_truetype.h +4589 -0
  68. data/src/utf8proc.c +755 -0
  69. data/src/utf8proc.h +699 -0
  70. data/src/utf8proc_data.h +14386 -0
  71. metadata +23 -16
  72. data/src/FormattedString.cpp +0 -237
  73. data/src/FormattedString.hpp +0 -47
  74. data/src/GosuAppDelegate.cpp +0 -30
  75. data/src/GosuAppDelegate.h +0 -8
  76. data/src/GosuGLView.h +0 -8
  77. data/src/TextApple.cpp +0 -212
  78. data/src/TextTTFWin.cpp +0 -197
  79. data/src/TextUnix.cpp +0 -280
  80. data/src/TextWin.cpp +0 -191
@@ -1,6 +1,3 @@
1
- # SWIG workarounds
2
- # These are offloaded into a separate file because rb_eval_string() is weird on Ruby 1.8.
3
-
4
1
  # Exceptions in Window callbacks often get lost, this is especially annoying in draw/update.
5
2
  # It is not clear whether this is a SWIG issue or if some stack frame is not exception
6
3
  # compatible, but I just call protected_update etc. in the Ruby wrapper so I can add this
@@ -66,7 +63,7 @@ module Gosu
66
63
  end
67
64
  end
68
65
 
69
- # SWIG won't let me rename my method to '[]='.
66
+ # SWIG will not let me rename my method to '[]=', so use alias here.
70
67
  class Gosu::Font
71
68
  alias []= set_image
72
69
  end
@@ -679,9 +679,20 @@ module Gosu
679
679
  # def input.filter(text_in)
680
680
  # text_in.upcase.gsub(/[^A-Z0-9]/, '')
681
681
  # end
682
- def filter text_in
683
- text_in
684
- end
682
+ def filter; end
683
+
684
+ ##
685
+ # Replaces the current selection (if any) and inserts the given string at the current caret position.
686
+ # The filter method will not be applied before appending the string.
687
+ def insert_text(str); end
688
+
689
+ ##
690
+ # Deletes the current selection, if any, or the next character.
691
+ def delete_forward; end
692
+
693
+ ##
694
+ # Deletes the current selection, if any, or the previous character.
695
+ def delete_backward; end
685
696
  end
686
697
 
687
698
  ##
@@ -1024,21 +1035,35 @@ module Gosu
1024
1035
  def clip_to(x, y, w, h); end
1025
1036
 
1026
1037
  ##
1027
- # Records all drawing operations inside the block as a reusable "image". This method can be used to speed rendering of multiple static images, e.g., a fixed tile map.
1038
+ # Records all drawing operations inside the block as a macro (a special {Gosu::Image}) that can be reused later on.
1039
+ # This is useful for rendering larger groups of images (e.g. a tiled map in a game) and then rendering this group with a single call to {Gosu::Image#draw}.
1028
1040
  #
1029
- # @note Because the returned object is not a true image---it's implemented using vertex buffers and is not backed by a texture---there are restrictions on how it can be used.
1041
+ # @note Because the returned object is not backed by a bitmap texture, there are restrictions on how it can be used. For example, you can not use any color other than {Gosu::Color#WHITE} when drawing the image.
1030
1042
  #
1031
- # @note The width and height of the returned object will be the same values you passed to {record}, regardless of the area you draw on. It is important to pass accurate values if you plan on using {Gosu::Image#draw_as_quad} or {Gosu::Image#draw_rot} with the result later.
1043
+ # @note The width and height of the returned image will be the values you passed to {record}, regardless of the area you draw on.
1044
+ # It is important to pass accurate values if you plan on calling {Gosu::Image#draw_as_quad} or {Gosu::Image#draw_rot} on the result later.
1032
1045
  #
1033
1046
  # @return [Gosu::Image] the recorded drawing operations.
1034
- # @param width [Float] the width of the recorded image.
1035
- # @param height [Float] the height of the recorded image.
1047
+ # @param width [Integer] the width of the recorded image.
1048
+ # @param height [Integer] the height of the recorded image.
1036
1049
  # @yield rendering code.
1037
1050
  #
1038
- # @see Window#draw
1051
+ # @see Window#render
1039
1052
  # @see Gosu::Image
1040
1053
  def record(width, height); end
1041
1054
 
1055
+ ##
1056
+ # Records all drawing operations inside the block and returns the result as a new {Gosu::Image}.
1057
+ #
1058
+ # @return [Gosu::Image] the rendered drawing operations.
1059
+ # @param width [Integer] the width of the recorded image.
1060
+ # @param height [Integer] the height of the recorded image.
1061
+ # @yield rendering code.
1062
+ #
1063
+ # @see Window#record
1064
+ # @see Gosu::Image
1065
+ def render(width, height); end
1066
+
1042
1067
  ##
1043
1068
  # Rotates all drawing operations inside the block.
1044
1069
  #
@@ -45,7 +45,7 @@ static bool cur_song_looping;
45
45
 
46
46
  struct Gosu::Sample::SampleData
47
47
  {
48
- ALuint buffer, source;
48
+ ALuint buffer;
49
49
 
50
50
  SampleData(AudioFile& audio_file)
51
51
  {
@@ -114,7 +114,7 @@ Gosu::Channel Gosu::Sample::play(double volume, double speed, bool looping) cons
114
114
 
115
115
  Gosu::Channel Gosu::Sample::play_pan(double pan, double volume, double speed, bool looping) const
116
116
  {
117
- if (!data) return Channel(NO_CHANNEL, 0);
117
+ if (!data) return Channel();
118
118
 
119
119
  Channel channel = allocate_channel();
120
120
 
@@ -133,8 +133,8 @@ Gosu::Channel Gosu::Sample::play_pan(double pan, double volume, double speed, bo
133
133
 
134
134
  class Gosu::Song::BaseData
135
135
  {
136
- BaseData(const BaseData&);
137
- BaseData& operator=(const BaseData&);
136
+ BaseData(const BaseData&) = delete;
137
+ BaseData& operator=(const BaseData&) = delete;
138
138
 
139
139
  double volume_;
140
140
 
@@ -169,7 +169,7 @@ class Gosu::Song::ModuleData : public BaseData
169
169
  {
170
170
  AVAudioPlayer* player;
171
171
 
172
- void apply_volume()
172
+ void apply_volume() override
173
173
  {
174
174
  player.volume = volume();
175
175
  }
@@ -203,7 +203,7 @@ public:
203
203
  bool paused() const override
204
204
  {
205
205
  return !player.playing;
206
- };
206
+ }
207
207
 
208
208
  void stop() override
209
209
  {
@@ -72,7 +72,7 @@ Gosu::Channel Gosu::allocate_channel()
72
72
  }
73
73
 
74
74
  // No free channel, return an object that is immediately expired.
75
- return Channel(NO_CHANNEL, 0);
75
+ return Channel();
76
76
  }
77
77
 
78
78
  bool Gosu::channel_expired(int channel, int token)
@@ -82,7 +82,7 @@ bool Gosu::channel_expired(int channel, int token)
82
82
 
83
83
  ALuint Gosu::al_source_for_channel(int channel)
84
84
  {
85
- if (channel <= 0 || channel >= CHANNELS) {
85
+ if (channel < 0 || channel >= CHANNELS) {
86
86
  throw invalid_argument("No such channel: " + to_string(channel));
87
87
  }
88
88
  return _sources[channel];
@@ -13,8 +13,7 @@ void Gosu::Bitmap::swap(Bitmap& other)
13
13
 
14
14
  void Gosu::Bitmap::resize(unsigned width, unsigned height, Color c)
15
15
  {
16
- if (width == w && height == h)
17
- return;
16
+ if (width == w && height == h) return;
18
17
 
19
18
  Bitmap temp(width, height, c);
20
19
  temp.insert(*this, 0, 0);
@@ -9,8 +9,18 @@
9
9
  #define STBI_NO_STDIO
10
10
  #define STBI_NO_LINEAR
11
11
 
12
+ // Disable comma warnings in stb headers.
13
+ #ifdef __GNUC__
14
+ #pragma GCC diagnostic push
15
+ #pragma GCC diagnostic ignored "-Wcomma"
16
+ #endif
17
+
12
18
  #include "stb_image.h"
13
19
 
20
+ #ifdef __GNUC__
21
+ #pragma GCC diagnostic pop
22
+ #endif
23
+
14
24
  using namespace std;
15
25
 
16
26
  namespace
@@ -80,9 +90,19 @@ void Gosu::load_image_file(Gosu::Bitmap& bitmap, Reader input)
80
90
  }
81
91
  }
82
92
 
93
+ // Disable comma warnings in stb headers.
94
+ #ifdef __GNUC__
95
+ #pragma GCC diagnostic push
96
+ #pragma GCC diagnostic ignored "-Wcomma"
97
+ #endif
98
+
83
99
  #define STB_IMAGE_WRITE_IMPLEMENTATION
84
100
  #include "stb_image_write.h"
85
101
 
102
+ #ifdef __GNUC__
103
+ #pragma GCC diagnostic pop
104
+ #endif
105
+
86
106
  void Gosu::save_image_file(const Gosu::Bitmap& bitmap, const string& filename)
87
107
  {
88
108
  int ok;
@@ -102,8 +122,7 @@ void Gosu::save_image_file(const Gosu::Bitmap& bitmap, const string& filename)
102
122
 
103
123
  static void stbi_write_to_writer(void* context, void* data, int size)
104
124
  {
105
- Gosu::Writer* writer = reinterpret_cast<Gosu::Writer*>(context);
106
- writer->write(data, size);
125
+ reinterpret_cast<Gosu::Writer*>(context)->write(data, size);
107
126
  }
108
127
 
109
128
  void Gosu::save_image_file(const Gosu::Bitmap& bitmap, Gosu::Writer writer,
@@ -111,7 +111,7 @@ bool Gosu::BlockAllocator::alloc(unsigned a_width, unsigned a_height, Block& b)
111
111
 
112
112
  void Gosu::BlockAllocator::block(unsigned left, unsigned top, unsigned width, unsigned height)
113
113
  {
114
- pimpl->blocks.push_back(Block(left, top, width, height));
114
+ pimpl->blocks.emplace_back(left, top, width, height);
115
115
  }
116
116
 
117
117
  void Gosu::BlockAllocator::free(unsigned left, unsigned top, unsigned width, unsigned height)
@@ -3,7 +3,8 @@
3
3
  using namespace std;
4
4
 
5
5
  // Returns the current state of a source
6
- static ALint state(int& channel) {
6
+ static ALint state(int& channel)
7
+ {
7
8
  ALint state;
8
9
  alGetSourcei(Gosu::al_source_for_channel(channel), AL_SOURCE_STATE, &state);
9
10
  if (state != AL_PLAYING && state != AL_PAUSED) {
@@ -12,6 +13,11 @@ static ALint state(int& channel) {
12
13
  return state;
13
14
  }
14
15
 
16
+ Gosu::Channel::Channel()
17
+ : channel(NO_CHANNEL), token(0)
18
+ {
19
+ }
20
+
15
21
  Gosu::Channel::Channel(int channel, int token)
16
22
  : channel(channel), token(token)
17
23
  {
@@ -40,7 +40,10 @@ class Gosu::ClipRectStack
40
40
  // TODO: Doesn't this affect Retina Macs as well?
41
41
  // TODO: This should be handled by a global transform.
42
42
  int fac = clip_rect_base_factor();
43
- result.x *= fac, result.y *= fac, result.width *= fac, result.height *= fac;
43
+ result.x *= fac;
44
+ result.y *= fac;
45
+ result.width *= fac;
46
+ result.height *= fac;
44
47
 
45
48
  // Normal clipping.
46
49
  effective_rect = result;
@@ -1,5 +1,6 @@
1
1
  #include <Gosu/Color.hpp>
2
2
  #include <Gosu/Math.hpp>
3
+ #include <cmath>
3
4
  #include <algorithm>
4
5
 
5
6
  namespace
@@ -61,7 +62,7 @@ Gosu::Color Gosu::Color::from_ahsv(Channel alpha, double h, double s, double v)
61
62
  s = clamp(s, 0.0, 1.0);
62
63
  v = clamp(v, 0.0, 1.0);
63
64
 
64
- int sector = h / 60;
65
+ int sector = static_cast<int>(h / 60);
65
66
  double factorial = h / 60 - sector;
66
67
 
67
68
  double p = v * (1 - s);
@@ -46,7 +46,7 @@ string Gosu::resource_prefix()
46
46
  if (result.empty()) {
47
47
  result = exe_filename();
48
48
  auto last_delim = result.find_last_of("\\/");
49
- result.resize(last_delim == result.npos ? 0 : last_delim + 1);
49
+ result.resize(last_delim == string::npos ? 0 : last_delim + 1);
50
50
  }
51
51
  return result;
52
52
  }
@@ -147,10 +147,14 @@ namespace Gosu
147
147
  RenderState va_render_state = render_state;
148
148
  va_render_state.transform = 0;
149
149
 
150
- result[0].tex_coords[0] = left, result[0].tex_coords[1] = top;
151
- result[1].tex_coords[0] = right, result[1].tex_coords[1] = top;
152
- result[2].tex_coords[0] = right, result[2].tex_coords[1] = bottom;
153
- result[3].tex_coords[0] = left, result[3].tex_coords[1] = bottom;
150
+ result[0].tex_coords[0] = left;
151
+ result[0].tex_coords[1] = top;
152
+ result[1].tex_coords[0] = right;
153
+ result[1].tex_coords[1] = top;
154
+ result[2].tex_coords[0] = right;
155
+ result[2].tex_coords[1] = bottom;
156
+ result[3].tex_coords[0] = left;
157
+ result[3].tex_coords[1] = bottom;
154
158
 
155
159
  if (vas.empty() || !(vas.back().render_state == va_render_state)) {
156
160
  vas.push_back(VertexArray());
@@ -15,32 +15,25 @@ class Gosu::DrawOpQueue
15
15
  {
16
16
  TransformStack transform_stack;
17
17
  ClipRectStack clip_rect_stack;
18
- bool rec;
18
+ QueueMode queue_mode = QM_RENDER_TO_SCREEN;
19
19
 
20
20
  std::vector<DrawOp> ops;
21
21
  std::vector<std::function<void ()>> gl_blocks;
22
22
 
23
23
  public:
24
- DrawOpQueue()
25
- : rec(false)
24
+ DrawOpQueue(QueueMode mode)
25
+ : queue_mode(mode)
26
26
  {
27
27
  }
28
28
 
29
- bool recording() const
29
+ QueueMode mode() const
30
30
  {
31
- return rec;
32
- }
33
-
34
- void set_recording()
35
- {
36
- rec = true;
31
+ return queue_mode;
37
32
  }
38
33
 
39
34
  void schedule_draw_op(DrawOp op)
40
35
  {
41
- if (clip_rect_stack.clipped_world_away()) {
42
- return;
43
- }
36
+ if (clip_rect_stack.clipped_world_away()) return;
44
37
 
45
38
  #ifdef GOSU_IS_OPENGLES
46
39
  // No triangles, no lines supported
@@ -57,9 +50,7 @@ public:
57
50
  void gl(std::function<void ()> gl_block, ZPos z)
58
51
  {
59
52
  // TODO: Document this case: Clipped-away GL blocks are *not* being run.
60
- if (clip_rect_stack.clipped_world_away()) {
61
- return;
62
- }
53
+ if (clip_rect_stack.clipped_world_away()) return;
63
54
 
64
55
  int complement_of_block_index = ~(int)gl_blocks.size();
65
56
  gl_blocks.push_back(gl_block);
@@ -76,7 +67,7 @@ public:
76
67
 
77
68
  void begin_clipping(double x, double y, double width, double height, double screen_height)
78
69
  {
79
- if (recording()) {
70
+ if (mode() == QM_RECORD_MACRO) {
80
71
  throw std::logic_error("Clipping is not allowed while creating a macro");
81
72
  }
82
73
 
@@ -119,9 +110,9 @@ public:
119
110
  transform_stack.pop();
120
111
  }
121
112
 
122
- void perform_draw_ops_andCode()
113
+ void perform_draw_ops_and_code()
123
114
  {
124
- if (recording()) {
115
+ if (mode() == QM_RECORD_MACRO) {
125
116
  throw std::logic_error("Flushing to the screen is not allowed while recording a macro");
126
117
  }
127
118
 
@@ -131,9 +122,7 @@ public:
131
122
  RenderStateManager manager;
132
123
 
133
124
  #ifdef GOSU_IS_OPENGLES
134
- if (ops.empty()) {
135
- return;
136
- }
125
+ if (ops.empty()) return;
137
126
 
138
127
  auto current = ops.begin(), last = ops.end() - 1;
139
128
  for (; current != last; ++current) {
@@ -141,12 +130,12 @@ public:
141
130
  current->perform(&*(current + 1));
142
131
  }
143
132
  manager.set_render_state(last->render_state);
144
- last->perform(0);
133
+ last->perform(nullptr);
145
134
  #else
146
135
  for (const auto& op : ops) {
147
136
  manager.set_render_state(op.render_state);
148
137
  if (op.vertices_or_block_index >= 0) {
149
- op.perform(0);
138
+ op.perform(nullptr);
150
139
  }
151
140
  else {
152
141
  // GL code
@@ -44,6 +44,8 @@ Gosu::File::File(const string& filename, FileMode mode)
44
44
  case FM_ALTER:
45
45
  flags = O_RDWR | O_CREAT;
46
46
  break;
47
+ default:
48
+ throw invalid_argument("Unknown file mode: " + to_string(mode));
47
49
  }
48
50
 
49
51
  // TODO: Locking flags?
@@ -66,7 +68,7 @@ Gosu::File::~File()
66
68
  size_t Gosu::File::size() const
67
69
  {
68
70
  // TODO: Error checking?
69
- return lseek(pimpl->fd, 0, SEEK_END);
71
+ return static_cast<size_t>(lseek(pimpl->fd, 0, SEEK_END));
70
72
  }
71
73
 
72
74
  void Gosu::File::resize(size_t new_size)
@@ -1,4 +1,4 @@
1
- #include "FormattedString.hpp"
1
+ #include "MarkupParser.hpp"
2
2
  #include "GraphicsImpl.hpp"
3
3
  #include <Gosu/Font.hpp>
4
4
  #include <Gosu/Graphics.hpp>
@@ -6,129 +6,119 @@
6
6
  #include <Gosu/Math.hpp>
7
7
  #include <Gosu/Text.hpp>
8
8
  #include <Gosu/Utility.hpp>
9
+ #include "utf8proc.h"
9
10
  #include <array>
10
11
  #include <cassert>
11
12
  #include <map>
12
13
  using namespace std;
13
14
 
15
+ static const int FONT_RENDER_SCALE = 2;
16
+
14
17
  struct Gosu::Font::Impl
15
18
  {
16
19
  string name;
17
- unsigned height, flags;
20
+ int height;
21
+ unsigned base_flags;
18
22
 
19
- // Unicode planes of 2^16 characters each. On Windows, where wchar_t is only 16 bits wide, only
20
- // the first plane will ever be touched.
21
- struct CharInfo
22
- {
23
- unique_ptr<Image> image;
24
- double factor;
25
- };
26
- typedef array<CharInfo, 65536> Plane;
27
- unique_ptr<Plane> planes[16][FF_COMBINATIONS];
28
-
29
- map<string, shared_ptr<Image>> entity_cache;
23
+ // The most common characters are stored directly in an array for maximum performance.
24
+ // (This is the start of the Basic Multilingual Plane, up until the part where right-to-left
25
+ // languages begin, which don't really work with Gosu yet.)
26
+ array<array<Image, 0x58f>, FF_COMBINATIONS> fast_glyphs;
27
+ // Everything else is looked up through a map...
28
+ array<map<utf8proc_int32_t, Image>, FF_COMBINATIONS> other_glyphs;
30
29
 
31
- CharInfo& char_info(wchar_t wc, unsigned flags)
30
+ const Image& image(char32_t codepoint, unsigned font_flags)
32
31
  {
33
- size_t plane_index = wc / 65536;
34
- size_t char_index = wc % 65536;
35
-
36
- if (plane_index >= 16) throw invalid_argument("Unicode plane out of reach");
37
- if (flags >= FF_COMBINATIONS) throw invalid_argument("Font flags out of range");
38
-
39
- if (!planes[plane_index][flags].get()) {
40
- planes[plane_index][flags].reset(new Plane);
32
+ Image* image;
33
+ if (codepoint < fast_glyphs.size()) {
34
+ image = &fast_glyphs[font_flags][codepoint];
41
35
  }
42
- return (*planes[plane_index][flags])[char_index];
43
- }
44
-
45
- const Image& image_at(const FormattedString& fs, unsigned i)
46
- {
47
- if (const char* entity = fs.entity_at(i)) {
48
- shared_ptr<Image>& ptr = entity_cache[entity];
49
- if (!ptr) {
50
- ptr.reset(new Image(entity_bitmap(fs.entity_at(i)), IF_SMOOTH));
51
- }
52
- return *ptr;
36
+ else {
37
+ image = &other_glyphs[font_flags][codepoint];
53
38
  }
54
39
 
55
- wchar_t wc = fs.char_at(i);
56
- unsigned flags = fs.flags_at(i);
57
- CharInfo& info = char_info(wc, flags);
58
-
59
- if (info.image.get()) return *info.image;
60
-
61
- string char_string = wstring_to_utf8(wstring(1, wc));
62
- // TODO: Would be nice to have.
63
- // if (is_formatting_char(wc))
64
- // char_string.clear();
65
- unsigned char_width = Gosu::text_width(char_string, name, height, flags);
40
+ // If this codepoint has not been rendered before, do it now.
41
+ if (image->width() == 0 && image->height() == 0) {
42
+ auto scaled_height = height * FONT_RENDER_SCALE;
43
+
44
+ u32string string(1, codepoint);
45
+ Bitmap bitmap(scaled_height, scaled_height);
46
+ auto required_width = ceil(draw_text(bitmap, 0, 0, Color::WHITE, string,
47
+ name, scaled_height, font_flags));
48
+ if (required_width > bitmap.width()) {
49
+ // If the character was wider than high, we need to render it again.
50
+ Bitmap(required_width, scaled_height).swap(bitmap);
51
+ draw_text(bitmap, 0, 0, Color::WHITE, string,
52
+ name, scaled_height, font_flags);
53
+ }
54
+
55
+ *image = Image(bitmap, 0, 0, required_width, scaled_height);
56
+ }
66
57
 
67
- Bitmap bitmap(char_width, height, 0x00ffffff);
68
- draw_text(bitmap, char_string, 0, 0, Color::WHITE, name, height, flags);
69
- info.image.reset(new Image(bitmap));
70
- info.factor = 0.5;
71
- return *info.image;
72
- }
73
-
74
- double factor_at(const FormattedString& fs, unsigned index)
75
- {
76
- if (fs.entity_at(index)) return 1;
77
- return char_info(fs.char_at(index), fs.flags_at(index)).factor;
58
+ return *image;
78
59
  }
79
60
  };
80
61
 
81
- Gosu::Font::Font(unsigned font_height, const string& font_name, unsigned font_flags)
62
+ Gosu::Font::Font(int font_height, const string& font_name, unsigned font_flags)
82
63
  : pimpl(new Impl)
83
64
  {
84
65
  pimpl->name = font_name;
85
- pimpl->height = font_height * 2;
86
- pimpl->flags = font_flags;
66
+ pimpl->height = font_height;
67
+ pimpl->base_flags = font_flags;
87
68
  }
88
69
 
89
- string Gosu::Font::name() const
70
+ const string& Gosu::Font::name() const
90
71
  {
91
72
  return pimpl->name;
92
73
  }
93
74
 
94
- unsigned Gosu::Font::height() const
75
+ int Gosu::Font::height() const
95
76
  {
96
- return pimpl->height / 2;
77
+ return pimpl->height;
97
78
  }
98
79
 
99
80
  unsigned Gosu::Font::flags() const
100
81
  {
101
- return pimpl->flags;
82
+ return pimpl->base_flags;
102
83
  }
103
84
 
104
85
  double Gosu::Font::text_width(const string& text, double scale_x) const
105
86
  {
106
- wstring wtext = utf8_to_wstring(text);
107
- FormattedString fs(wtext.c_str(), flags());
108
- double result = 0;
109
- for (unsigned i = 0; i < fs.length(); ++i) {
110
- const Image& image = pimpl->image_at(fs, i);
111
- double factor = pimpl->factor_at(fs, i);
112
- result += image.width() * factor;
113
- }
114
- return result * scale_x;
87
+ int width = 0;
88
+
89
+ // Split the text into lines (split_words = false) because Font doesn't implement word-wrapping.
90
+ MarkupParser(text.c_str(), pimpl->base_flags, false, [&](vector<FormattedString>&& line) {
91
+ int line_width = 0;
92
+ for (auto& part : line) {
93
+ for (auto codepoint : part.text) {
94
+ line_width += pimpl->image(codepoint, part.flags).width();
95
+ }
96
+ }
97
+ width = max(width, line_width);
98
+ }).parse();
99
+
100
+ return scale_x * width / FONT_RENDER_SCALE;
115
101
  }
116
102
 
117
103
  void Gosu::Font::draw(const string& text, double x, double y, ZPos z,
118
104
  double scale_x, double scale_y, Color c, AlphaMode mode) const
119
105
  {
120
- wstring wtext = utf8_to_wstring(text);
121
- FormattedString fs(wtext.c_str(), flags());
106
+ double current_y = y;
122
107
 
123
- for (unsigned i = 0; i < fs.length(); ++i) {
124
- const Image& image = pimpl->image_at(fs, i);
125
- double factor = pimpl->factor_at(fs, i);
126
- Color color = fs.entity_at(i)
127
- ? Color(fs.color_at(i).alpha() * c.alpha() / 255, 255, 255, 255)
128
- : multiply(fs.color_at(i), c);
129
- image.draw(x, y, z, scale_x * factor, scale_y * factor, color, mode);
130
- x += image.width() * scale_x * factor;
131
- }
108
+ // Split the text into lines (split_words = false) because Font doesn't implement word-wrapping.
109
+ MarkupParser(text.c_str(), pimpl->base_flags, false, [&](vector<FormattedString>&& line) {
110
+ double current_x = x;
111
+ for (auto& part : line) {
112
+ for (auto codepoint : part.text) {
113
+ auto& image = pimpl->image(codepoint, part.flags);
114
+ image.draw(current_x, current_y, z,
115
+ scale_x / FONT_RENDER_SCALE, scale_y / FONT_RENDER_SCALE,
116
+ c, mode);
117
+ current_x += scale_x * image.width() / FONT_RENDER_SCALE;
118
+ }
119
+ }
120
+ current_y += scale_y * height();
121
+ }).parse();
132
122
  }
133
123
 
134
124
  void Gosu::Font::draw_rel(const string& text, double x, double y, ZPos z,
@@ -140,25 +130,31 @@ void Gosu::Font::draw_rel(const string& text, double x, double y, ZPos z,
140
130
  draw(text, x, y, z, scale_x, scale_y, c, mode);
141
131
  }
142
132
 
143
- void Gosu::Font::set_image(wchar_t wc, const Image& image)
133
+ void Gosu::Font::draw_rot(const string& text, double x, double y, ZPos z, double angle,
134
+ double scale_x, double scale_y, Color c, AlphaMode mode) const
144
135
  {
145
- for (unsigned flags = 0; flags < FF_COMBINATIONS; ++flags) {
146
- set_image(wc, flags, image);
147
- }
136
+ Graphics::transform(rotate(angle, x, y), [&] {
137
+ draw(text, x, y, z, scale_x, scale_y, c, mode);
138
+ });
148
139
  }
149
140
 
150
- void Gosu::Font::set_image(wchar_t wc, unsigned font_flags, const Image& image)
141
+ void Gosu::Font::set_image(std::string codepoint, unsigned font_flags, const Gosu::Image& image)
151
142
  {
152
- Impl::CharInfo& ci = pimpl->char_info(wc, font_flags);
153
- if (ci.image.get()) throw logic_error("Cannot set image for the same character twice");
154
- ci.image.reset(new Image(image));
155
- ci.factor = 1.0;
143
+ auto utc4 = utf8_to_composed_utc4(codepoint);
144
+ if (utc4.length() != 1) {
145
+ throw invalid_argument("Could not compose '" + codepoint + "' into a single codepoint");
146
+ }
147
+
148
+ if (utc4[0] < pimpl->fast_glyphs[font_flags].size()) {
149
+ pimpl->fast_glyphs[font_flags][utc4[0]] = image;
150
+ } else {
151
+ pimpl->other_glyphs[font_flags][utc4[0]] = image;
152
+ }
156
153
  }
157
154
 
158
- void Gosu::Font::draw_rot(const string& text, double x, double y, ZPos z, double angle,
159
- double scale_x, double scale_y, Color c, AlphaMode mode) const
155
+ void Gosu::Font::set_image(std::string codepoint, const Gosu::Image& image)
160
156
  {
161
- Graphics::transform(rotate(angle, x, y), [&] {
162
- draw(text, x, y, z, scale_x, scale_y, c, mode);
163
- });
157
+ for (unsigned font_flags = 0; font_flags < FF_COMBINATIONS; ++font_flags) {
158
+ set_image(codepoint, font_flags, image);
159
+ }
164
160
  }