E-MailRelay
gstringwrap.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gstringwrap.cpp
19///
20
21#include "gdef.h"
22#include "gstringwrap.h"
23#include "gimembuf.h"
24#include <string>
25#include <sstream>
26#include <algorithm>
27
28#ifdef emit
29#undef emit
30#endif
31
32namespace G
33{
34 namespace StringWrapImp
35 {
36 struct Config
37 {
38 std::string_view prefix_first ;
39 std::string_view prefix_other ;
40 std::size_t width_first ;
41 std::size_t width_other ;
42 bool preserve_spaces ;
43 } ;
44 class WordWrapper
45 {
46 public:
47 WordWrapper( std::ostream & , Config ) ;
48 void emit( const std::string & word , std::size_t newlines , const std::string & prespace ) ;
49
50 public:
51 ~WordWrapper() = default ;
52 WordWrapper( const WordWrapper & ) = delete ;
53 WordWrapper( WordWrapper && ) = delete ;
54 WordWrapper & operator=( const WordWrapper & ) = delete ;
55 WordWrapper & operator=( WordWrapper && ) = delete ;
56
57 private:
58 std::string_view prefix() const ;
59
60 private:
61 std::size_t m_lines {0U} ;
62 std::size_t m_w {0U} ;
63 std::ostream & m_out ;
64 Config m_config ;
65 } ;
66 void wrapImp( std::istream & , WordWrapper & ) ;
67 }
68}
69
70// ==
71
72G::StringWrapImp::WordWrapper::WordWrapper( std::ostream & out , Config config ) :
73 m_out(out) ,
74 m_config(config)
75{
76}
77
78std::string_view G::StringWrapImp::WordWrapper::prefix() const
79{
80 return m_lines ? m_config.prefix_other : m_config.prefix_first ;
81}
82
83void G::StringWrapImp::WordWrapper::emit( const std::string & word , std::size_t newlines , const std::string & prespace )
84{
85 // emit words up to the configured maximum width
86 std::size_t wordsize = StringWrap::wordsize( word ) ;
87 std::size_t spacesize = m_config.preserve_spaces ? prespace.size() : 1U ;
88 std::size_t width = ( newlines || m_lines > 1 ) ? m_config.width_other : m_config.width_first ;
89 bool start_new_line = newlines || m_w == 0 || (m_w+spacesize+wordsize) > width ;
90 if( start_new_line )
91 {
92 // emit a blank line for each of the counted newlines
93 bool first_line = m_w == 0 ;
94 for( std::size_t i = 1U ; i < newlines ; i++ )
95 {
96 m_out << (first_line?"":"\n") << prefix() ;
97 first_line = false ;
98 m_w = prefix().size() ;
99 m_lines++ ;
100 }
101
102 // emit the first word
103 m_out << (first_line?"":"\n") << prefix() << word ;
104 m_w = prefix().size() + wordsize ;
105 m_lines++ ;
106 }
107 else
108 {
109 if( m_config.preserve_spaces )
110 m_out << (prespace.empty()?std::string(1U,' '):prespace) << word ;
111 else
112 m_out << " " << word ;
113 m_w += (spacesize+wordsize) ;
114 }
115}
116
117// ==
118
119std::string G::StringWrap::wrap( const std::string & text_in ,
120 const std::string & prefix_first , const std::string & prefix_other ,
121 std::size_t width_first , std::size_t width_other ,
122 bool preserve_spaces )
123{
124 StringWrapImp::Config config {
125 prefix_first ,
126 prefix_other ,
127 width_first , width_other?width_other:width_first ,
128 preserve_spaces
129 } ;
130
131 std::ostringstream out ;
132 StringWrapImp::WordWrapper wrapper( out , config ) ;
133
134 imembuf buf( text_in.data() , text_in.size() ) ;
135 std::istream in( &buf ) ;
136
137 StringWrapImp::wrapImp( in , wrapper ) ;
138
139 return out.str() ;
140}
141
142void G::StringWrapImp::wrapImp( std::istream & in , WordWrapper & ww )
143{
144 // extract words while counting newlines within the intervening spaces
145 std::string word ;
146 std::size_t newlines = 0U ;
147 std::string prespace ;
148 char c = 0 ;
149 while( in.get(c) )
150 {
151 if( c == ' ' || c == '\n' )
152 {
153 // spit out the previous word (if any)
154 if( !word.empty() )
155 {
156 ww.emit( word , newlines , prespace ) ;
157 newlines = 0U ;
158 prespace.clear() ;
159 }
160
161 // start the new word
162 word.clear() ;
163 if( c == '\n' )
164 {
165 newlines++ ;
166 prespace.clear() ;
167 }
168 else
169 {
170 prespace.append( 1U , c ) ;
171 }
172 }
173 else
174 {
175 word.append( 1U , c ) ;
176 }
177 }
178 // spit out the trailing word (if any)
179 if( !word.empty() )
180 ww.emit( word , newlines , prespace ) ;
181}
182
183std::size_t G::StringWrap::wordsize( const std::string & s )
184{
185 // (do the UTF-8 parsing ourselves to avoid a dependency on G::Convert::u8parse())
186 const unsigned char * p = reinterpret_cast<const unsigned char*>( s.data() ) ;
187 std::size_t n = s.size() ;
188 std::size_t result = 0U ;
189 std::size_t d = 0U ;
190 for( std::size_t i = 0U ; i < n ; i += d , p += d )
191 {
192 result++ ;
193 if( ( p[0] & 0x80U ) == 0U )
194 {
195 d = 1U ;
196 }
197 else if( ( p[0] & 0xE0U ) == 0xC0U && (i+1U) < n &&
198 ( p[1] & 0xC0 ) == 0x80U )
199 {
200 d = 2U ;
201 }
202 else if( ( p[0] & 0xF0U ) == 0xE0U && (i+2U) < n &&
203 ( p[1] & 0xC0 ) == 0x80U &&
204 ( p[2] & 0xC0 ) == 0x80U )
205 {
206 d = 3U ;
207 }
208 else if( ( p[0] & 0xF8U ) == 0xF0U && (i+3U) < n &&
209 ( p[1] & 0xC0 ) == 0x80U &&
210 ( p[2] & 0xC0 ) == 0x80U &&
211 ( p[3] & 0xC0 ) == 0x80U )
212 {
213 d = 4U ;
214 }
215 else
216 {
217 d = 1U ;
218 }
219 }
220 return result ;
221}
222
static std::size_t wordsize(const std::string &)
Returns the number of characters in UTF-8 text.
static std::string wrap(const std::string &text, const std::string &prefix_first, const std::string &prefix_other, std::size_t width_first=70U, std::size_t width_other=0U, bool preserve_spaces=false)
Does word-wrapping of UTF-8 text.
An input streambuf that takes its data from a fixed-size const buffer.
Definition: gimembuf.h:52
Low-level classes.
Definition: garg.h:36