@@ -158,6 +158,63 @@ std::tstring normalise_utf8(std::tstring_view input)
158158 return result;
159159}
160160
161+ bool is_char_whitespace (TCHAR c)
162+ {
163+ // U+00A0 and U+202F are non-breaking spaces.
164+ // U+180E was classified as a space when microsoft first defined isspace, but was later removed from the standard.
165+ return (_istspace (c) > 0 ) && (c != L' \u00A0 ' ) && (c != L' \u202F ' ) && (c != L' \u180E ' );
166+ }
167+
168+ size_t find_first_whitespace (const std::tstring_view str, size_t pos)
169+ {
170+ // match behavior of std::string_view::find_first_of
171+ if (pos >= str.length () || str.empty ()) return std::tstring_view::npos;
172+
173+ const auto it = std::find_if (std::next (str.begin (), pos), str.end (), is_char_whitespace);
174+
175+ if (it == str.end ()) return std::tstring_view::npos;
176+
177+ return it - str.begin ();
178+ }
179+
180+ size_t find_first_nonwhitespace (const std::tstring_view str, size_t pos)
181+ {
182+ // match behavior of std::string_view::find_first_not_of
183+ if (pos >= str.length () || str.empty ()) return std::tstring_view::npos;
184+
185+ const auto it = std::find_if_not (std::next (str.begin (), pos), str.end (), is_char_whitespace);
186+
187+ if (it == str.end ()) return std::tstring_view::npos;
188+
189+ return it - str.begin ();
190+ }
191+
192+ size_t find_last_whitespace (const std::tstring_view str, size_t pos)
193+ {
194+ if (str.empty ()) return std::tstring_view::npos;
195+
196+ size_t offset = 0 ;
197+ if (pos != std::tstring_view::npos && pos < str.length ()) offset = str.length () - pos - 1 ;
198+
199+ const auto it = std::find_if (std::next (str.rbegin (), offset), str.rend (), is_char_whitespace);
200+ if (it == str.rend ()) return std::tstring_view::npos;
201+
202+ return str.rend () - it - 1 ;
203+ }
204+
205+ size_t find_last_nonwhitespace (const std::tstring_view str, size_t pos)
206+ {
207+ if (str.empty ()) return std::tstring_view::npos;
208+
209+ size_t offset = 0 ;
210+ if (pos != std::tstring_view::npos && pos < str.length ()) offset = str.length () - pos - 1 ;
211+
212+ const auto it = std::find_if_not (std::next (str.rbegin (), offset), str.rend (), is_char_whitespace);
213+ if (it == str.rend ()) return std::tstring_view::npos;
214+
215+ return str.rend () - it - 1 ;
216+ }
217+
161218bool hr_success (HRESULT result, const char * filename, int line_number)
162219{
163220 const bool success = (result == S_OK );
@@ -210,4 +267,121 @@ MVTF_TEST(win32_string_narrow_to_wide_handles_ascii)
210267 const std::wstring output = std::wstring (output_buffer.data (), output_chars);
211268 ASSERT (output == L" test string!\n with a newline :O" );
212269}
270+
271+ MVTF_TEST (win32_is_char_whitespace_true_for_breaking_whitespace)
272+ {
273+ ASSERT (is_char_whitespace (L' \t ' ));
274+ ASSERT (is_char_whitespace (L' \n ' ));
275+ ASSERT (is_char_whitespace (L' \v ' ));
276+ ASSERT (is_char_whitespace (L' \f ' ));
277+ ASSERT (is_char_whitespace (L' \r ' ));
278+ ASSERT (is_char_whitespace (L' ' ));
279+
280+ ASSERT (is_char_whitespace (L' \u0085 ' )); // Next line
281+ ASSERT (is_char_whitespace (L' \u1680 ' )); // Ogham space mark
282+
283+ ASSERT (is_char_whitespace (L' \u2000 ' )); // En quad
284+ ASSERT (is_char_whitespace (L' \u2001 ' )); // Em quad
285+ ASSERT (is_char_whitespace (L' \u2002 ' )); // En space
286+ ASSERT (is_char_whitespace (L' \u2003 ' )); // Em space
287+ ASSERT (is_char_whitespace (L' \u2004 ' )); // Three-per-em space
288+ ASSERT (is_char_whitespace (L' \u2005 ' )); // Four-per-em space
289+ ASSERT (is_char_whitespace (L' \u2006 ' )); // Six-per-em space
290+ ASSERT (is_char_whitespace (L' \u2007 ' )); // Figure space
291+ ASSERT (is_char_whitespace (L' \u2008 ' )); // Punctuation space
292+ ASSERT (is_char_whitespace (L' \u2009 ' )); // Thin space
293+ ASSERT (is_char_whitespace (L' \u200A ' )); // Hair space
294+
295+ ASSERT (is_char_whitespace (L' \u2028 ' )); // Line separator
296+ ASSERT (is_char_whitespace (L' \u2029 ' )); // Paragraph separator
297+ ASSERT (is_char_whitespace (L' \u205F ' )); // Medium mathematical space
298+ ASSERT (is_char_whitespace (L' \u3000 ' )); // Ideographic space
299+
300+ ASSERT (!is_char_whitespace (L' \u00A0 ' )); // Non-breaking space
301+ ASSERT (!is_char_whitespace (L' \u202F ' )); // Narrow non-breaking space
302+ ASSERT (!is_char_whitespace (L' \u180E ' )); // Mongolian vowel separator
303+ ASSERT (!is_char_whitespace (L' A' ));
304+ ASSERT (!is_char_whitespace (L' 1' ));
305+ ASSERT (!is_char_whitespace (L' -' ));
306+ }
307+
308+ MVTF_TEST (win32_find_first_whitespace_gives_correct_indices)
309+ {
310+ std::tstring_view input = _T (" Test string.\u3000 Second sentence." );
311+ ASSERT (find_first_whitespace (input) == 4 );
312+ ASSERT (find_first_whitespace (input, 4 ) == 4 );
313+ ASSERT (find_first_whitespace (input, 5 ) == 12 );
314+ ASSERT (find_first_whitespace (input, 23 ) == std::tstring_view::npos);
315+ ASSERT (find_first_whitespace (input, 100 ) == std::tstring_view::npos);
316+ }
317+
318+ MVTF_TEST (win32_find_first_whitespace_empty_string)
319+ {
320+ ASSERT (find_first_whitespace (_T (" " )) == std::tstring_view::npos);
321+ }
322+
323+ MVTF_TEST (win32_find_first_whitespace_no_whitespace)
324+ {
325+ ASSERT (find_last_whitespace (_T (" abcdef" )) == std::tstring_view::npos);
326+ }
327+
328+ MVTF_TEST (win32_find_first_nonwhitespace_gives_correct_indices)
329+ {
330+ std::tstring_view input = _T (" \u3000 Test string. " );
331+ ASSERT (find_first_nonwhitespace (input) == 4 );
332+ ASSERT (find_first_nonwhitespace (input, 4 ) == 4 );
333+ ASSERT (find_first_nonwhitespace (input, 10 ) == 12 );
334+ ASSERT (find_first_nonwhitespace (input, 20 ) == std::tstring_view::npos);
335+ ASSERT (find_first_nonwhitespace (input, 100 ) == std::tstring_view::npos);
336+ }
337+
338+ MVTF_TEST (win32_find_first_nonwhitespace_empty_string)
339+ {
340+ ASSERT (find_first_nonwhitespace (_T (" " )) == std::tstring_view::npos);
341+ }
342+
343+ MVTF_TEST (win32_find_first_nonwhitespace_no_nonwhitespace)
344+ {
345+ ASSERT (find_last_nonwhitespace (_T (" " )) == std::tstring_view::npos);
346+ }
347+
348+ MVTF_TEST (win32_find_last_whitespace_gives_correct_indices)
349+ {
350+ std::tstring_view input = _T (" Test string.\u3000 Second sentence." );
351+ ASSERT (find_last_whitespace (input) == 19 );
352+ ASSERT (find_last_whitespace (input, 19 ) == 19 );
353+ ASSERT (find_last_whitespace (input, 15 ) == 12 );
354+ ASSERT (find_last_whitespace (input, 2 ) == std::tstring_view::npos);
355+ ASSERT (find_last_whitespace (input, 100 ) == 19 );
356+ }
357+
358+ MVTF_TEST (win32_find_last_whitespace_empty_string)
359+ {
360+ ASSERT (find_last_whitespace (_T (" " )) == std::tstring_view::npos);
361+ }
362+
363+ MVTF_TEST (win32_find_last_whitespace_no_whitespace)
364+ {
365+ ASSERT (find_last_whitespace (_T (" abcdef" )) == std::tstring_view::npos);
366+ }
367+
368+ MVTF_TEST (win32_find_last_nonwhitespace_gives_correct_indices)
369+ {
370+ std::tstring_view input = _T (" \u3000 Test string. " );
371+ ASSERT (find_last_nonwhitespace (input) == 18 );
372+ ASSERT (find_last_nonwhitespace (input, 18 ) == 18 );
373+ ASSERT (find_last_nonwhitespace (input, 10 ) == 7 );
374+ ASSERT (find_last_nonwhitespace (input, 3 ) == std::tstring_view::npos);
375+ ASSERT (find_last_nonwhitespace (input, 100 ) == 18 );
376+ }
377+
378+ MVTF_TEST (win32_find_last_nonwhitespace_empty_string)
379+ {
380+ ASSERT (find_last_nonwhitespace (_T (" " )) == std::tstring_view::npos);
381+ }
382+
383+ MVTF_TEST (win32_find_last_nonwhitespace_no_nonwhitespace)
384+ {
385+ ASSERT (find_last_nonwhitespace (_T (" " )) == std::tstring_view::npos);
386+ }
213387#endif
0 commit comments