1 // Copyright (c) 2013, Thomas Goyne <plorkyeran@aegisub.org>
2 //
3 // Permission to use, copy, modify, and distribute this software for any
4 // purpose with or without fee is hereby granted, provided that the above
5 // copyright notice and this permission notice appear in all copies.
6 //
7 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 //
15 // Aegisub Project http://www.aegisub.org/
16 
17 #include "libaegisub/util.h"
18 #include "libaegisub/util_osx.h"
19 
20 #include <boost/locale/boundary.hpp>
21 #include <boost/locale/conversion.hpp>
22 #include <boost/range/distance.hpp>
23 #include <ctime>
24 
25 namespace {
26 const size_t bad_pos = (size_t)-1;
27 const std::pair<size_t, size_t> bad_match(bad_pos, bad_pos);
28 
29 template<typename Iterator>
advance_both(Iterator & folded,Iterator & raw)30 size_t advance_both(Iterator& folded, Iterator& raw) {
31 	size_t len;
32 	if (*folded == *raw) {
33 		len = folded->length();
34 		++folded;
35 	}
36 	else {
37 		// This character was changed by case folding, so refold it and eat the
38 		// appropriate number of characters from folded
39 		len = boost::locale::fold_case(raw->str()).size();
40 		for (size_t folded_consumed = 0; folded_consumed < len; ++folded)
41 			folded_consumed += folded->length();
42 	}
43 
44 	++raw;
45 	return len;
46 }
47 
find_range(std::string const & haystack,std::string const & needle,size_t start=0)48 std::pair<size_t, size_t> find_range(std::string const& haystack, std::string const& needle, size_t start = 0) {
49 	const size_t match_start = haystack.find(needle, start);
50 	if (match_start == std::string::npos)
51 		return bad_match;
52 	return {match_start, match_start + needle.size()};
53 }
54 
55 }
56 
57 namespace agi { namespace util {
58 
strftime(const char * fmt,const tm * tmptr)59 std::string strftime(const char *fmt, const tm *tmptr) {
60 	if (!tmptr) {
61 		time_t t = time(nullptr);
62 		tmptr = localtime(&t);
63 	}
64 
65 	char buff[65536];
66 	::strftime(buff, sizeof buff, fmt, tmptr);
67 	return buff;
68 }
69 
ifind(std::string const & haystack,std::string const & needle)70 std::pair<size_t, size_t> ifind(std::string const& haystack, std::string const& needle) {
71 	const auto folded_hs = boost::locale::fold_case(haystack);
72 	const auto folded_n = boost::locale::fold_case(needle);
73 	auto match = find_range(folded_hs, folded_n);
74 	if (match == bad_match || folded_hs == haystack)
75 		return match;
76 
77 	// We have a match, but the position is an index into the folded string
78 	// and we want an index into the unfolded string.
79 
80 	using namespace boost::locale::boundary;
81 	const ssegment_index haystack_characters(character, begin(haystack), end(haystack));
82 	const ssegment_index folded_characters(character, begin(folded_hs), end(folded_hs));
83 	const size_t haystack_char_count = boost::distance(haystack_characters);
84 	const size_t folded_char_count = boost::distance(folded_characters);
85 
86 	// As of Unicode 6.2, case folding can never reduce the number of
87 	// characters, and can only reduce the number of bytes with UTF-8 when
88 	// increasing the number of characters. As a result, iff the bytes and
89 	// characters are unchanged, no folds changed the size of any characters
90 	// and our indices are correct.
91 	if (haystack.size() == folded_hs.size() && haystack_char_count == folded_char_count)
92 		return match;
93 
94 	const auto map_folded_to_raw = [&]() -> std::pair<size_t, size_t> {
95 		size_t start = -1;
96 
97 		// Iterate over each pair of characters and refold each character which was
98 		// changed by folding, so that we can find the corresponding positions in
99 		// the unfolded string
100 		auto folded_it = begin(folded_characters);
101 		auto haystack_it = begin(haystack_characters);
102 		size_t folded_pos = 0;
103 
104 		while (folded_pos < match.first)
105 			folded_pos += advance_both(folded_it, haystack_it);
106 		// If we overshot the start then the match started in the middle of a
107 		// character which was folded to multiple characters
108 		if (folded_pos > match.first)
109 			return bad_match;
110 
111 		start = distance(begin(haystack), begin(*haystack_it));
112 
113 		while (folded_pos < match.second)
114 			folded_pos += advance_both(folded_it, haystack_it);
115 		if (folded_pos > match.second)
116 			return bad_match;
117 
118 		return {start, distance(begin(haystack), begin(*haystack_it))};
119 	};
120 
121 	auto ret = map_folded_to_raw();
122 	while (ret == bad_match) {
123 		// Found something, but it was an invalid match so retry from the next character
124 		match = find_range(folded_hs, folded_n, match.first + 1);
125 		if (match == bad_match) return match;
126 		ret = map_folded_to_raw();
127 	}
128 
129 	return ret;
130 }
131 
132 }
133 
134 #ifndef __APPLE__
135 namespace osx {
AppNapDisabler(std::string reason)136 AppNapDisabler::AppNapDisabler(std::string reason) { }
~AppNapDisabler()137 AppNapDisabler::~AppNapDisabler() { }
138 }
139 #endif
140 }
141