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
@@ -4,21 +4,11 @@
4
4
  #include <Gosu/Graphics.hpp>
5
5
  #include <Gosu/Platform.hpp>
6
6
 
7
- #if defined(GOSU_IS_WIN)
8
- #ifndef NOMINMAX
9
- #define NOMINMAX
10
- #endif
11
- #include <windows.h>
12
- #include <GL/gl.h>
13
- #elif defined(GOSU_IS_IPHONE)
7
+ #if defined(GOSU_IS_IPHONE) || defined(GOSU_IS_OPENGLES)
14
8
  #include <OpenGLES/ES1/gl.h>
15
9
  #include <OpenGLES/ES1/glext.h>
16
- #elif defined(GOSU_IS_MAC)
17
- #include <OpenGL/gl.h>
18
- #elif defined GOSU_IS_OPENGLES
19
- #include <GLES/gl.h>
20
10
  #else
21
- #include <GL/gl.h>
11
+ #include <SDL_opengl.h>
22
12
  #endif
23
13
 
24
14
  #include <algorithm>
@@ -51,6 +41,13 @@ namespace Gosu
51
41
 
52
42
  namespace Gosu
53
43
  {
44
+ enum QueueMode
45
+ {
46
+ QM_RENDER_TO_SCREEN,
47
+ QM_RENDER_TO_TEXTURE,
48
+ QM_RECORD_MACRO,
49
+ };
50
+
54
51
  class Texture;
55
52
  class TexChunk;
56
53
  class ClipRectStack;
@@ -99,25 +96,11 @@ namespace Gosu
99
96
  y = out[1] / out[3];
100
97
  }
101
98
 
102
- inline void multiply_bitmap_alpha(Bitmap& bmp, Color::Channel alpha)
103
- {
104
- for (int y = 0; y < bmp.height(); ++y) {
105
- for (int x = 0; x < bmp.width(); ++x) {
106
- Color c = bmp.get_pixel(x, y);
107
- c.set_alpha(c.alpha() * alpha / 255);
108
- bmp.set_pixel(x, y, c);
109
- }
110
- }
111
- }
112
-
113
- #ifdef GOSU_IS_IPHONE
99
+ #ifdef GOSU_IS_IPHONE
114
100
  int clip_rect_base_factor();
115
- #else
101
+ #else
116
102
  inline int clip_rect_base_factor() { return 1; }
117
- #endif
118
-
119
- bool is_entity(const std::string& name);
120
- const Bitmap& entity_bitmap(const std::string& name);
103
+ #endif
121
104
 
122
105
  void ensure_current_context();
123
106
  }
@@ -21,8 +21,8 @@ Gosu::Image::Image(const string& filename, unsigned flags)
21
21
  Image(bmp, flags).data_.swap(data_);
22
22
  }
23
23
 
24
- Gosu::Image::Image(const string& filename, unsigned src_x, unsigned src_y, unsigned src_width,
25
- unsigned src_height, unsigned flags)
24
+ Gosu::Image::Image(const string& filename, unsigned src_x, unsigned src_y,
25
+ unsigned src_width, unsigned src_height, unsigned flags)
26
26
  {
27
27
  // Forward.
28
28
  Bitmap bmp;
@@ -36,8 +36,8 @@ Gosu::Image::Image(const Bitmap& source, unsigned flags)
36
36
  Image(source, 0, 0, source.width(), source.height(), flags).data_.swap(data_);
37
37
  }
38
38
 
39
- Gosu::Image::Image(const Bitmap& source, unsigned src_x, unsigned src_y, unsigned src_width,
40
- unsigned src_height, unsigned flags)
39
+ Gosu::Image::Image(const Bitmap& source, unsigned src_x, unsigned src_y,
40
+ unsigned src_width, unsigned src_height, unsigned flags)
41
41
  : data_(Graphics::create_image(source, src_x, src_y, src_width, src_height, flags))
42
42
  {
43
43
  }
@@ -45,9 +45,7 @@ Gosu::Image::Image(const Bitmap& source, unsigned src_x, unsigned src_y, unsigne
45
45
  Gosu::Image::Image(unique_ptr<ImageData>&& data)
46
46
  : data_(data.release())
47
47
  {
48
- if (this->data_.get() == nullptr) {
49
- throw invalid_argument("Gosu::Image cannot be initialized with nullptr");
50
- }
48
+ if (!data_) throw invalid_argument("Gosu::Image cannot be initialized with nullptr");
51
49
  }
52
50
 
53
51
  unsigned Gosu::Image::width() const
@@ -79,19 +79,21 @@ struct Gosu::Input::Impl
79
79
  int x, y, window_x, window_y;
80
80
  SDL_GetWindowPosition(window, &window_x, &window_y);
81
81
  SDL_GetGlobalMouseState(&x, &y);
82
- mouse_x = x - window_x, mouse_y = y - window_y;
82
+ mouse_x = x - window_x;
83
+ mouse_y = y - window_y;
83
84
  #else
84
85
  int x, y;
85
86
  SDL_GetMouseState(&x, &y);
86
- mouse_x = x, mouse_y = y;
87
+ mouse_x = x;
88
+ mouse_y = y;
87
89
  #endif
88
90
  }
89
91
 
90
92
  void set_mouse_position(double x, double y)
91
93
  {
92
94
  SDL_WarpMouseInWindow(window,
93
- (x - mouse_offset_x) / mouse_scale_x,
94
- (y - mouse_offset_y) / mouse_scale_y);
95
+ static_cast<int>((x - mouse_offset_x) / mouse_scale_x),
96
+ static_cast<int>((y - mouse_offset_y) / mouse_scale_y));
95
97
 
96
98
  #if SDL_VERSION_ATLEAST(2, 0, 4) && !defined(GOSU_IS_X)
97
99
  // On systems where we have a working GetGlobalMouseState, we can warp the mouse and
@@ -329,7 +331,7 @@ string Gosu::Input::id_to_char(Button btn)
329
331
  // Convert to lower case to be consistent with previous versions of Gosu.
330
332
  // German umlauts are already reported in lower-case by SDL, anyway.
331
333
  // (This should handle Turkish i/I just fine because it uses the current locale.)
332
- wname[0] = (wchar_t) towlower((int) wname[0]);
334
+ wname[0] = (wchar_t) towlower((wint_t) wname[0]);
333
335
  return wstring_to_utf8(wname);
334
336
  }
335
337
 
@@ -7,19 +7,10 @@
7
7
  #import <UIKit/UIKit.h>
8
8
  using namespace std;
9
9
 
10
- struct Gosu::TextInput::Impl {};
11
- Gosu::TextInput::TextInput() {}
12
- Gosu::TextInput::~TextInput() {}
13
- string Gosu::TextInput::text() const { return ""; }
14
- void Gosu::TextInput::set_text(const string& text) {}
15
- unsigned Gosu::TextInput::caret_pos() const { return 0; }
16
- void Gosu::TextInput::set_caret_pos(unsigned) {}
17
- unsigned Gosu::TextInput::selection_start() const { return 0; }
18
- void Gosu::TextInput::set_selection_start(unsigned) {}
19
-
20
10
  struct Gosu::Input::Impl
21
11
  {
22
- UIView* view;
12
+ UIView* view = nil;
13
+ TextInput* text_input = nullptr;
23
14
  float mouse_x, mouse_y;
24
15
  float scale_x, scale_y;
25
16
  float update_interval;
@@ -32,7 +23,7 @@ struct Gosu::Input::Impl
32
23
  CGPoint point = [ui_touch locationInView:view];
33
24
 
34
25
  return (Touch) {
35
- .id = (__bridge void*) ui_touch,
26
+ .id = (__bridge void*)ui_touch,
36
27
  .x = (float)point.x * scale_x,
37
28
  .y = (float)point.y * scale_y,
38
29
  };
@@ -53,33 +44,22 @@ Gosu::Input::~Input()
53
44
  {
54
45
  }
55
46
 
56
- void Gosu::Input::feed_touch_event(int type, void* touches)
47
+ void Gosu::Input::feed_touch_event(function<void (Touch)>& callback, void* touches)
57
48
  {
58
49
  NSSet* ui_touches = (__bridge NSSet*) touches;
59
50
 
60
51
  pimpl->current_touches_vector.reset();
61
52
 
62
- function<void (Touch)>* callback = nullptr;
63
-
64
- if (type == 0) {
53
+ if (&callback == &on_touch_began) {
65
54
  [pimpl->current_touches_set unionSet:ui_touches];
66
- callback = &on_touch_began;
67
- }
68
- else if (type == 1) {
69
- callback = &on_touch_moved;
70
- }
71
- else if (type == 2) {
72
- [pimpl->current_touches_set minusSet:ui_touches];
73
- callback = &on_touch_ended;
74
55
  }
75
- else if (type == 3) {
56
+ else if (&callback == &on_touch_ended || &callback == &on_touch_cancelled) {
76
57
  [pimpl->current_touches_set minusSet:ui_touches];
77
- callback = &on_touch_cancelled;
78
58
  }
79
59
 
80
- if (callback && *callback) {
60
+ if (callback) {
81
61
  for (UITouch* ui_touch in ui_touches) {
82
- (*callback)(pimpl->translate_touch(ui_touch));
62
+ callback(pimpl->translate_touch(ui_touch));
83
63
  }
84
64
  }
85
65
  }
@@ -152,9 +132,7 @@ double Gosu::Input::accelerometer_z() const
152
132
 
153
133
  void Gosu::Input::update()
154
134
  {
155
- // Check for dead touches and remove from vector if
156
- // necessary
157
-
135
+ // Check for dead touches and remove from vector if necessary.
158
136
  NSMutableSet* dead_touches = nil;
159
137
 
160
138
  for (UITouch* touch in pimpl->current_touches_set) {
@@ -166,9 +144,7 @@ void Gosu::Input::update()
166
144
  }
167
145
 
168
146
  // Something was deleted, we will need the set.
169
- if (!dead_touches) {
170
- dead_touches = [NSMutableSet new];
171
- }
147
+ if (!dead_touches) dead_touches = [NSMutableSet new];
172
148
  [dead_touches addObject:touch];
173
149
  }
174
150
 
@@ -187,12 +163,18 @@ void Gosu::Input::update()
187
163
 
188
164
  Gosu::TextInput* Gosu::Input::text_input() const
189
165
  {
190
- return nullptr;
166
+ return pimpl->text_input;
191
167
  }
192
168
 
193
- void Gosu::Input::set_text_input(TextInput* input)
169
+ void Gosu::Input::set_text_input(TextInput* text_input)
194
170
  {
195
- throw "NYI";
171
+ if (text_input) {
172
+ pimpl->text_input = text_input;
173
+ [pimpl->view becomeFirstResponder];
174
+ } else {
175
+ [pimpl->view resignFirstResponder];
176
+ pimpl->text_input = nullptr;
177
+ }
196
178
  }
197
179
 
198
180
  #endif
@@ -1,5 +1,6 @@
1
1
  #include "Macro.hpp"
2
2
  #include "DrawOpQueue.hpp"
3
+ #include <Gosu/Image.hpp>
3
4
  #include <cmath>
4
5
  #include <algorithm>
5
6
  #include <functional>
@@ -58,7 +59,8 @@ struct Gosu::Macro::Impl
58
59
  Float c[8];
59
60
 
60
61
  // Rows 1, 2
61
- c[2] = x1, c[5] = y1;
62
+ c[2] = x1;
63
+ c[5] = y1;
62
64
 
63
65
  // The logic below assumes x2 != x4, i.e. row7 can be used to eliminate
64
66
  // the leftmost value in row 8 and afterwards the values in rows 3 & 4.
@@ -221,7 +223,13 @@ const Gosu::GLTexInfo* Gosu::Macro::gl_tex_info() const
221
223
 
222
224
  Gosu::Bitmap Gosu::Macro::to_bitmap() const
223
225
  {
224
- throw logic_error("Gosu::Macro cannot be rendered as Gosu::Bitmap yet");
226
+ return Gosu::Graphics::render(pimpl->width, pimpl->height, [this] {
227
+ draw(0, 0, Color::WHITE,
228
+ pimpl->width, 0, Color::WHITE,
229
+ 0, pimpl->height, Color::WHITE,
230
+ pimpl->width, pimpl->height, Color::WHITE,
231
+ 0, AM_DEFAULT);
232
+ }).data().to_bitmap();
225
233
  }
226
234
 
227
235
  unique_ptr<Gosu::ImageData> Gosu::Macro::subimage(int x, int y, int width, int height) const
@@ -0,0 +1,241 @@
1
+ #include "MarkupParser.hpp"
2
+ #include <Gosu/Utility.hpp>
3
+ #include "utf8proc.h"
4
+
5
+ #include <cstring>
6
+ using namespace std;
7
+
8
+ // Helper method for allowing CJK text to have line breaks in the absence of whitespace.
9
+ static bool should_allow_break_after_codepoint(utf8proc_int32_t cp)
10
+ {
11
+ return (cp >= 0x3040 && cp <= 0x3096) || // Hiragana
12
+ (cp >= 0x30a0 && cp <= 0x30fa) || // Katakana
13
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified Ideographs
14
+ (cp >= 0x3400 && cp <= 0x4db5) || // CJK Unified Ideographs Extension A
15
+ (cp >= 0xac00 && cp <= 0xd7af); // Precomposed Hangul syllables
16
+ }
17
+
18
+ unsigned Gosu::MarkupParser::flags() const
19
+ {
20
+ unsigned flags = 0;
21
+ if (b > 0) flags |= Gosu::FF_BOLD;
22
+ if (i > 0) flags |= Gosu::FF_ITALIC;
23
+ if (u > 0) flags |= Gosu::FF_UNDERLINE;
24
+ return flags;
25
+ }
26
+
27
+ bool Gosu::MarkupParser::match_and_skip(const char* chars, size_t length)
28
+ {
29
+ if (strncmp(markup, chars, length) != 0) return false;
30
+
31
+ // Finish building the current substring (if any) before matching.
32
+ add_current_substring();
33
+ // Skip chars.
34
+ markup += length;
35
+ return true;
36
+ }
37
+
38
+ bool Gosu::MarkupParser::parse_markup()
39
+ {
40
+ // Open and close bold text spans.
41
+ if (match_and_skip("<b>")) {
42
+ b += 1;
43
+ return true;
44
+ }
45
+ if (match_and_skip("</b>")) {
46
+ b -= 1;
47
+ return true;
48
+ }
49
+
50
+ // Open and close underlined text spans.
51
+ if (match_and_skip("<u>")) {
52
+ u += 1;
53
+ return true;
54
+ }
55
+ if (match_and_skip("</u>")) {
56
+ u -= 1;
57
+ return true;
58
+ }
59
+
60
+ // Open and close italic text spans.
61
+ if (match_and_skip("<i>")) {
62
+ i += 1;
63
+ return true;
64
+ }
65
+ if (match_and_skip("</i>")) {
66
+ i -= 1;
67
+ return true;
68
+ }
69
+
70
+ // Reset to previous color.
71
+ if (match_and_skip("</c>")) {
72
+ if (c.size() > 1) {
73
+ c.pop_back();
74
+ }
75
+ else {
76
+ // If the user pops the color stack empty, go back to the default color.
77
+ c[0] = Color::WHITE;
78
+ }
79
+ return true;
80
+ }
81
+
82
+ // Leave the trickiest case for last - changing the current text color.
83
+ if (strncmp(markup, "<c=", 3) == 0) {
84
+ // Count hex chars following the <c= string.
85
+ const char* hex = markup + 3;
86
+ const char* valid_hex_chars = "0123456789ABCDEFabcdef";
87
+ size_t hex_chars = strspn(hex, valid_hex_chars);
88
+
89
+ if (*(hex + hex_chars) != '>' || (hex_chars != 3 && hex_chars != 6 && hex_chars != 8)) {
90
+ // Does not match <c=[0-9A-Fa-f]{3,6,8}> -> not considered valid markup.
91
+ return false;
92
+ }
93
+
94
+ add_current_substring();
95
+
96
+ auto argb = strtoul(hex, nullptr, 16);
97
+
98
+ if (hex_chars == 3) {
99
+ // Expand 0xrgb to 0xFFrrggbb:
100
+ auto r = argb >> 8 & 0x7;
101
+ auto g = argb >> 4 & 0x7;
102
+ auto b = argb >> 0 & 0x7;
103
+ argb = 0xff000000 | r << 20 | r << 16 | g << 12 | g << 8 | b << 4 | b << 0;
104
+ }
105
+ else if (hex_chars == 6) {
106
+ // Expand 0xrrggbb to 0xFFrrggbb:
107
+ argb = 0xff000000 | argb;
108
+ }
109
+
110
+ c.emplace_back(argb);
111
+
112
+ markup += (4 + hex_chars);
113
+ return true;
114
+ }
115
+
116
+ // Everything else is not considered markup.
117
+ return false;
118
+ }
119
+
120
+ bool Gosu::MarkupParser::parse_escape_entity()
121
+ {
122
+ // These are not entities (images) but escapes for markup characters.
123
+ if (match_and_skip("&lt;")) {
124
+ add_composed_substring(u32string(1, '<'));
125
+ return true;
126
+ }
127
+ if (match_and_skip("&gt;")) {
128
+ add_composed_substring(u32string(1, '>'));
129
+ return true;
130
+ }
131
+ if (match_and_skip("&amp;")) {
132
+ add_composed_substring(u32string(1, '&'));
133
+ return true;
134
+ }
135
+
136
+ // These are the only recognized entities - disregard anything else.
137
+ return false;
138
+ }
139
+
140
+ void Gosu::MarkupParser::add_current_substring()
141
+ {
142
+ if (!substring.empty()) {
143
+ add_composed_substring(utf8_to_composed_utc4(substring));
144
+ substring.clear();
145
+ }
146
+ }
147
+
148
+ void Gosu::MarkupParser::add_composed_substring(u32string&& substring)
149
+ {
150
+ FormattedString fstr;
151
+ fstr.text = substring;
152
+ fstr.flags = flags();
153
+ fstr.color = c.back();
154
+
155
+ if (! substrings.empty() && substrings.back().can_be_merged_with(fstr)) {
156
+ substrings.back().text.append(move(fstr.text));
157
+ }
158
+ else {
159
+ substrings.emplace_back(move(fstr));
160
+ }
161
+ }
162
+
163
+ void Gosu::MarkupParser::flush_to_consumer()
164
+ {
165
+ if (! substrings.empty()) {
166
+ consumer(move(substrings));
167
+ substrings.clear();
168
+ }
169
+ }
170
+
171
+ Gosu::MarkupParser::MarkupParser(const char* markup, unsigned base_flags, bool split_words,
172
+ function<void (vector<FormattedString>)> consumer)
173
+ : markup(markup), consumer(move(consumer))
174
+ {
175
+ word_state = (split_words ? ADDING_WORD : IGNORE_WORDS);
176
+
177
+ b = (base_flags & FF_BOLD) ? 1 : 0;
178
+ i = (base_flags & FF_ITALIC) ? 1 : 0;
179
+ u = (base_flags & FF_UNDERLINE) ? 1 : 0;
180
+ }
181
+
182
+ void Gosu::MarkupParser::parse()
183
+ {
184
+ auto end_of_markup = markup + strlen(markup);
185
+
186
+ while (markup < end_of_markup) {
187
+ if (*markup == '<' && parse_markup()) {
188
+ continue;
189
+ }
190
+ if (*markup == '&' && parse_escape_entity()) {
191
+ continue;
192
+ }
193
+
194
+ // The newline character always terminates the current line, regardless of whether this
195
+ // parser is trying to split words.
196
+ if (*markup == '\n') {
197
+ // Explicitly add the trailing \n to the current substring so that the consumer can
198
+ // distinguish between line breaks and word breaks in split_words mode.
199
+ ++markup;
200
+ add_current_substring();
201
+ flush_to_consumer();
202
+ // Avoid incrementing ++markup again.
203
+ continue;
204
+ }
205
+
206
+ utf8proc_int32_t codepoint;
207
+ auto len = utf8proc_iterate((utf8proc_uint8_t*) markup, end_of_markup - markup, &codepoint);
208
+ // Cancel parsing when invalid UTF-8 is encountered.
209
+ if (len < 1) break;
210
+
211
+ auto* properties = utf8proc_get_property(codepoint);
212
+ // Also check the BiDi class to filter out non-breaking spaces.
213
+ bool whitespace_except_newline = properties->category == UTF8PROC_CATEGORY_ZS &&
214
+ properties->bidi_class == UTF8PROC_BIDI_CLASS_WS;
215
+
216
+ if (whitespace_except_newline && word_state == ADDING_WORD) {
217
+ // We are in word-parsing mode, and this is was the end of a word.
218
+ add_current_substring();
219
+ flush_to_consumer();
220
+ word_state = ADDING_WHITESPACE;
221
+ } else if (!whitespace_except_newline && word_state == ADDING_WHITESPACE) {
222
+ // We are in word-parsing mode, and this is was the start of a word.
223
+ add_current_substring();
224
+ flush_to_consumer();
225
+ word_state = ADDING_WORD;
226
+ }
227
+
228
+ substring.append(markup, len);
229
+ markup += len;
230
+
231
+ if (word_state != IGNORE_WORDS && should_allow_break_after_codepoint(codepoint)) {
232
+ // Flush each individual CJK character out as a word so that the TextBuilder can insert
233
+ // line breaks as needed.
234
+ add_current_substring();
235
+ flush_to_consumer();
236
+ }
237
+ }
238
+
239
+ add_current_substring();
240
+ flush_to_consumer();
241
+ }