1 // ----------------------------------------------------------------------------
2 // outputencoder.cxx  --  output charset conversion
3 //
4 // Copyright (C) 2012
5 //		Andrej Lajovic, S57LN
6 //
7 // This file is part of fldigi.
8 //
9 // Fldigi is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // Fldigi is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
21 // ----------------------------------------------------------------------------
22 
23 #include <cstring>
24 #include <iostream>
25 #include <string>
26 #include <tiniconv.h>
27 #include <outputencoder.h>
28 
29 #include "config.h"
30 #include "debug.h"
31 
32 using namespace std;
33 
34 /*
35  OutputEncoder accepts UTF-8 strings at input, converts them to the
36  selected encoding and outputs them one character at a time.
37 */
38 
39 
40 /*
41  Constructor. Look up tiniconv.h for the list of possible values of
42  charset_in.
43 */
OutputEncoder(const int charset_out,unsigned int buffer_size)44 OutputEncoder::OutputEncoder(const int charset_out, unsigned int buffer_size)
45 {
46 	this->buffer_size = buffer_size;
47 	buffer = new unsigned char[buffer_size];
48 	encoding_ptr = buffer;
49 	pop_ptr = buffer;
50 	set_output_encoding(charset_out);
51 }
52 
53 
54 /*
55  Destructor.
56 */
~OutputEncoder(void)57 OutputEncoder::~OutputEncoder(void)
58 {
59 	delete[] buffer;
60 }
61 
62 
63 /*
64  Set output encoding. Look up tiniconv.h for the list of possible values of
65  charset_in.
66 */
set_output_encoding(const int charset_out)67 void OutputEncoder::set_output_encoding(const int charset_out)
68 {
69 	tiniconv_init(TINICONV_CHARSET_UTF_8, charset_out,	TINICONV_OPTION_IGNORE_OUT_ILSEQ, &ctx);
70 }
71 
72 
73 /*
74  Push input data into the encoder.
75 */
push(string s)76 void OutputEncoder::push(string s)
77 {
78 	int available = buffer_size - (encoding_ptr - buffer);
79 	int consumed_in;
80 	int consumed_out;
81 
82 	int status = tiniconv_convert(&ctx,
83 		(unsigned char*)s.data(), s.length(), &consumed_in,
84 		encoding_ptr, available, &consumed_out);
85 	if (status != TINICONV_CONVERT_OK) {
86 		LOG_ERROR("Error %s",
87 			status == TINICONV_CONVERT_IN_TOO_SMALL  ? "input too small" :
88 			status == TINICONV_CONVERT_OUT_TOO_SMALL ? "output too small" :
89 			status == TINICONV_CONVERT_IN_ILSEQ ? "input illegal sequence" :
90 			status == TINICONV_CONVERT_OUT_ILSEQ ? "output illegal sequence" :
91 			"unknown error");
92 		return;
93 	}
94 
95 	encoding_ptr += consumed_out;
96 
97 	if (consumed_in < (int)s.length())
98 	{
99 		// All input data was not consumed, possibly because the
100 		// output buffer was too small. Try to vacuum the buffer,
101 		// i.e., remove the data that was already pop()ed.
102 		memmove(buffer, pop_ptr, buffer + buffer_size - pop_ptr);
103 		encoding_ptr -= (pop_ptr - buffer);
104 		pop_ptr = buffer;
105 
106 		// Now try again; fingers crossed. We don't check for
107 		// success anymore, because there is nothing that we can do
108 		// if the buffer is still too small.
109 		int available = buffer_size - (encoding_ptr - buffer);
110 		tiniconv_convert(&ctx,
111 			(unsigned char*)s.data()+consumed_in, s.length()-consumed_in, &consumed_in,
112 			encoding_ptr, available, &consumed_out);
113 
114 		encoding_ptr += consumed_out;
115 	}
116 }
117 
118 
119 /*
120  Pop a single character of the encoded data.
121  Returns -1 in case there is no data available.
122 */
pop(void)123 const unsigned int OutputEncoder::pop(void)
124 {
125 	if (pop_ptr == encoding_ptr)
126 		return(-1);
127 
128 	unsigned int c = *pop_ptr++;
129 
130 	// Note that by only advancing pop_ptr, we leave stale data at the
131 	// beginning of the buffer, so sooner or later it will clutter up.
132 	// If there is no data left to send, both encoding_ptr and pop_ptr
133 	// can be safely reset to the beginning of the buffer; we handle
134 	// this trivial case here. More thorough vacuuming will be performed
135 	// in push() if the need arises.
136 	if (pop_ptr == encoding_ptr)
137 		pop_ptr = encoding_ptr = buffer;
138 
139 	return(c);
140 }
141 
142 // return next character to be pop'd, do not advance pointers;
143 
peek(void)144 const unsigned int OutputEncoder::peek(void)
145 {
146 	if (pop_ptr == encoding_ptr) return -1;
147 	return *pop_ptr;
148 }
149 
150