File: | content/handlers/text/textplain.c |
Warning: | line 274, column 13 The left operand of '==' is a garbage value |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | ||||
2 | * Copyright 2006 James Bursa <bursa@users.sourceforge.net> | ||||
3 | * Copyright 2006 Adrian Lees <adrianl@users.sourceforge.net> | ||||
4 | * | ||||
5 | * This file is part of NetSurf, http://www.netsurf-browser.org/ | ||||
6 | * | ||||
7 | * NetSurf is free software; you can redistribute it and/or modify | ||||
8 | * it under the terms of the GNU General Public License as published by | ||||
9 | * the Free Software Foundation; version 2 of the License. | ||||
10 | * | ||||
11 | * NetSurf is distributed in the hope that it will be useful, | ||||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
14 | * GNU General Public License for more details. | ||||
15 | * | ||||
16 | * You should have received a copy of the GNU General Public License | ||||
17 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
18 | */ | ||||
19 | |||||
20 | /** | ||||
21 | * \file | ||||
22 | * | ||||
23 | * plain text content handling implementation. | ||||
24 | */ | ||||
25 | |||||
26 | #include <string.h> | ||||
27 | #include <parserutils/input/inputstream.h> | ||||
28 | |||||
29 | #include "utils/errors.h" | ||||
30 | #include "utils/corestrings.h" | ||||
31 | #include "utils/http.h" | ||||
32 | #include "utils/log.h" | ||||
33 | #include "utils/messages.h" | ||||
34 | #include "utils/utils.h" | ||||
35 | #include "utils/utf8.h" | ||||
36 | #include "utils/nsoption.h" | ||||
37 | #include "netsurf/content.h" | ||||
38 | #include "netsurf/keypress.h" | ||||
39 | #include "netsurf/browser_window.h" | ||||
40 | #include "netsurf/plotters.h" | ||||
41 | #include "netsurf/layout.h" | ||||
42 | #include "content/content_protected.h" | ||||
43 | #include "content/content_factory.h" | ||||
44 | #include "content/hlcache.h" | ||||
45 | #include "content/textsearch.h" | ||||
46 | #include "content/handlers/css/utils.h" | ||||
47 | #include "desktop/selection.h" | ||||
48 | #include "desktop/gui_internal.h" | ||||
49 | |||||
50 | #include "text/textplain.h" | ||||
51 | |||||
52 | struct textplain_line { | ||||
53 | size_t start; | ||||
54 | size_t length; | ||||
55 | }; | ||||
56 | |||||
57 | /** | ||||
58 | * plain text content | ||||
59 | */ | ||||
60 | typedef struct textplain_content { | ||||
61 | struct content base; | ||||
62 | |||||
63 | lwc_string *encoding; | ||||
64 | void *inputstream; | ||||
65 | char *utf8_data; | ||||
66 | size_t utf8_data_size; | ||||
67 | size_t utf8_data_allocated; | ||||
68 | unsigned long physical_line_count; | ||||
69 | struct textplain_line *physical_line; | ||||
70 | int formatted_width; | ||||
71 | struct browser_window *bw; | ||||
72 | |||||
73 | struct selection *sel; /** Selection state */ | ||||
74 | |||||
75 | } textplain_content; | ||||
76 | |||||
77 | |||||
78 | #define CHUNK32768 32768 /* Must be a power of 2 */ | ||||
79 | #define MARGIN4 4 | ||||
80 | |||||
81 | #define TAB_WIDTH8 8 /* must be power of 2 currently */ | ||||
82 | #define TEXT_SIZE10 * (1 << (10)) 10 * PLOT_STYLE_SCALE(1 << (10)) /* Unscaled text size in pt */ | ||||
83 | |||||
84 | static plot_font_style_t textplain_style = { | ||||
85 | .family = PLOT_FONT_FAMILY_MONOSPACE, | ||||
86 | .size = TEXT_SIZE10 * (1 << (10)), | ||||
87 | .weight = 400, | ||||
88 | .flags = FONTF_NONE, | ||||
89 | .background = 0xffffff, | ||||
90 | .foreground = 0x000000, | ||||
91 | }; | ||||
92 | |||||
93 | static int textplain_tab_width = 256; /* try for a sensible default */ | ||||
94 | |||||
95 | static lwc_string *textplain_default_charset; | ||||
96 | |||||
97 | |||||
98 | /** | ||||
99 | * Clean up after the text content handler | ||||
100 | */ | ||||
101 | static void textplain_fini(void) | ||||
102 | { | ||||
103 | if (textplain_default_charset != NULL((void*)0)) { | ||||
104 | lwc_string_unref(textplain_default_charset){ lwc_string *__lwc_s = (textplain_default_charset); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL" , "content/handlers/text/textplain.c", 104, __extension__ __PRETTY_FUNCTION__ )); __lwc_s->refcnt--; if ((__lwc_s->refcnt == 0) || (( __lwc_s->refcnt == 1) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy(__lwc_s); }; | ||||
105 | textplain_default_charset = NULL((void*)0); | ||||
106 | } | ||||
107 | } | ||||
108 | |||||
109 | |||||
110 | /** | ||||
111 | * Work around feature in libparserutils | ||||
112 | * | ||||
113 | * if the client provides an encoding up front, but does not provide a | ||||
114 | * charset detection callback, then libparserutils will replace the | ||||
115 | * provided encoding with UTF-8. This breaks our input handling. | ||||
116 | * | ||||
117 | * Avoid this by providing a callback that does precisely nothing, | ||||
118 | * thus preserving whatever charset information we decided on in | ||||
119 | * textplain_create. | ||||
120 | */ | ||||
121 | static parserutils_error | ||||
122 | textplain_charset_hack(const uint8_t *data, | ||||
123 | size_t len, | ||||
124 | uint16_t *mibenum, | ||||
125 | uint32_t *source) | ||||
126 | { | ||||
127 | return PARSERUTILS_OK; | ||||
128 | } | ||||
129 | |||||
130 | |||||
131 | /** | ||||
132 | * setup plain text render. | ||||
133 | * | ||||
134 | * \param[in] c content object. | ||||
135 | * \param[in] encoding the encoding of the content. | ||||
136 | * \return NSERROR_OK else appropriate error code. | ||||
137 | */ | ||||
138 | static nserror | ||||
139 | textplain_create_internal(textplain_content *c, lwc_string *encoding) | ||||
140 | { | ||||
141 | char *utf8_data; | ||||
142 | parserutils_inputstream *stream; | ||||
143 | parserutils_error error; | ||||
144 | |||||
145 | textplain_style.size = (nsoption_int(font_size)(nsoptions[NSOPTION_font_size].value.i) * PLOT_STYLE_SCALE(1 << (10))) / 10; | ||||
146 | |||||
147 | utf8_data = malloc(CHUNK32768); | ||||
148 | if (utf8_data == NULL((void*)0)) | ||||
149 | goto no_memory; | ||||
150 | |||||
151 | error = parserutils_inputstream_create(lwc_string_data(encoding)({((encoding != ((void*)0)) ? (void) (0) : __assert_fail ("encoding != NULL" , "content/handlers/text/textplain.c", 151, __extension__ __PRETTY_FUNCTION__ )); (const char *)((encoding)+1);}), 0, | ||||
152 | textplain_charset_hack, &stream); | ||||
153 | if (error == PARSERUTILS_BADENCODING) { | ||||
154 | /* Fall back to Windows-1252 */ | ||||
155 | error = parserutils_inputstream_create("Windows-1252", 0, | ||||
156 | textplain_charset_hack, &stream); | ||||
157 | } | ||||
158 | if (error != PARSERUTILS_OK) { | ||||
159 | free(utf8_data); | ||||
160 | goto no_memory; | ||||
161 | } | ||||
162 | |||||
163 | c->encoding = lwc_string_ref(encoding)({lwc_string *__lwc_s = (encoding); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL", "content/handlers/text/textplain.c" , 163, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt ++; __lwc_s;}); | ||||
164 | c->inputstream = stream; | ||||
165 | c->utf8_data = utf8_data; | ||||
166 | c->utf8_data_size = 0; | ||||
167 | c->utf8_data_allocated = CHUNK32768; | ||||
168 | c->physical_line = 0; | ||||
169 | c->physical_line_count = 0; | ||||
170 | c->formatted_width = 0; | ||||
171 | c->bw = NULL((void*)0); | ||||
172 | c->sel = selection_create((struct content *)c); | ||||
173 | |||||
174 | return NSERROR_OK; | ||||
175 | |||||
176 | no_memory: | ||||
177 | content_broadcast_error(&c->base, NSERROR_NOMEM, NULL((void*)0)); | ||||
178 | |||||
179 | return NSERROR_NOMEM; | ||||
180 | } | ||||
181 | |||||
182 | |||||
183 | /** | ||||
184 | * Create a CONTENT_TEXTPLAIN. | ||||
185 | */ | ||||
186 | static nserror | ||||
187 | textplain_create(const content_handler *handler, | ||||
188 | lwc_string *imime_type, | ||||
189 | const struct http_parameter *params, | ||||
190 | llcache_handle *llcache, | ||||
191 | const char *fallback_charset, | ||||
192 | bool_Bool quirks, | ||||
193 | struct content **c) | ||||
194 | { | ||||
195 | textplain_content *text; | ||||
196 | nserror error; | ||||
197 | lwc_string *encoding; | ||||
198 | |||||
199 | text = calloc(1, sizeof(textplain_content)); | ||||
200 | if (text == NULL((void*)0)) { | ||||
201 | return NSERROR_NOMEM; | ||||
202 | } | ||||
203 | |||||
204 | error = content__init(&text->base, handler, imime_type, params, | ||||
205 | llcache, fallback_charset, quirks); | ||||
206 | if (error != NSERROR_OK) { | ||||
207 | free(text); | ||||
208 | return error; | ||||
209 | } | ||||
210 | |||||
211 | error = http_parameter_list_find_item(params, corestring_lwc_charset, | ||||
212 | &encoding); | ||||
213 | if (error != NSERROR_OK) { | ||||
214 | encoding = lwc_string_ref(textplain_default_charset)({lwc_string *__lwc_s = (textplain_default_charset); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL" , "content/handlers/text/textplain.c", 214, __extension__ __PRETTY_FUNCTION__ )); __lwc_s->refcnt++; __lwc_s;}); | ||||
215 | } | ||||
216 | |||||
217 | error = textplain_create_internal(text, encoding); | ||||
218 | if (error != NSERROR_OK) { | ||||
219 | lwc_string_unref(encoding){ lwc_string *__lwc_s = (encoding); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL", "content/handlers/text/textplain.c" , 219, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt --; if ((__lwc_s->refcnt == 0) || ((__lwc_s->refcnt == 1 ) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy (__lwc_s); }; | ||||
220 | free(text); | ||||
221 | return error; | ||||
222 | } | ||||
223 | |||||
224 | lwc_string_unref(encoding){ lwc_string *__lwc_s = (encoding); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL", "content/handlers/text/textplain.c" , 224, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt --; if ((__lwc_s->refcnt == 0) || ((__lwc_s->refcnt == 1 ) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy (__lwc_s); }; | ||||
225 | |||||
226 | *c = (struct content *) text; | ||||
227 | |||||
228 | return NSERROR_OK; | ||||
229 | } | ||||
230 | |||||
231 | |||||
232 | /** | ||||
233 | * copy utf8 encoded data | ||||
234 | */ | ||||
235 | static bool_Bool | ||||
236 | textplain_copy_utf8_data(textplain_content *c, const uint8_t *buf, size_t len) | ||||
237 | { | ||||
238 | if (c->utf8_data_size + len >= c->utf8_data_allocated) { | ||||
239 | /* Compute next multiple of chunk above the required space */ | ||||
240 | size_t allocated; | ||||
241 | char *utf8_data; | ||||
242 | |||||
243 | allocated = (c->utf8_data_size + len + CHUNK32768 - 1) & ~(CHUNK32768 - 1); | ||||
244 | utf8_data = realloc(c->utf8_data, allocated); | ||||
245 | if (utf8_data == NULL((void*)0)) | ||||
246 | return false0; | ||||
247 | |||||
248 | c->utf8_data = utf8_data; | ||||
249 | c->utf8_data_allocated = allocated; | ||||
250 | } | ||||
251 | |||||
252 | memcpy(c->utf8_data + c->utf8_data_size, buf, len); | ||||
253 | c->utf8_data_size += len; | ||||
254 | |||||
255 | return true1; | ||||
256 | } | ||||
257 | |||||
258 | |||||
259 | /** | ||||
260 | * drain input | ||||
261 | */ | ||||
262 | static bool_Bool | ||||
263 | textplain_drain_input(textplain_content *c, | ||||
264 | parserutils_inputstream *stream, | ||||
265 | parserutils_error terminator) | ||||
266 | { | ||||
267 | static const uint8_t *u_fffd = (const uint8_t *) "\xef\xbf\xbd"; | ||||
268 | const uint8_t *ch; | ||||
269 | size_t chlen, offset = 0; | ||||
270 | |||||
271 | while (parserutils_inputstream_peek(stream, offset, &ch, &chlen) != | ||||
272 | terminator) { | ||||
273 | /* Replace all instances of NUL with U+FFFD */ | ||||
274 | if (chlen == 1 && *ch == 0) { | ||||
| |||||
275 | if (offset > 0) { | ||||
276 | /* Obtain pointer to start of input data */ | ||||
277 | parserutils_inputstream_peek(stream, 0, | ||||
278 | &ch, &chlen); | ||||
279 | /* Copy from it up to the start of the NUL */ | ||||
280 | if (textplain_copy_utf8_data(c, ch, | ||||
281 | offset) == false0) | ||||
282 | return false0; | ||||
283 | } | ||||
284 | |||||
285 | /* Emit U+FFFD */ | ||||
286 | if (textplain_copy_utf8_data(c, u_fffd, 3) == false0) | ||||
287 | return false0; | ||||
288 | |||||
289 | /* Advance inputstream past the NUL we just read */ | ||||
290 | parserutils_inputstream_advance(stream, offset + 1); | ||||
291 | /* Reset the read offset */ | ||||
292 | offset = 0; | ||||
293 | } else { | ||||
294 | /* Accumulate input */ | ||||
295 | offset += chlen; | ||||
296 | |||||
297 | if (offset > CHUNK32768) { | ||||
298 | /* Obtain pointer to start of input data */ | ||||
299 | parserutils_inputstream_peek(stream, 0, | ||||
300 | &ch, &chlen); | ||||
301 | |||||
302 | /* Emit the data we've read */ | ||||
303 | if (textplain_copy_utf8_data(c, ch, | ||||
304 | offset) == false0) | ||||
305 | return false0; | ||||
306 | |||||
307 | /* Advance the inputstream */ | ||||
308 | parserutils_inputstream_advance(stream, offset); | ||||
309 | /* Reset the read offset */ | ||||
310 | offset = 0; | ||||
311 | } | ||||
312 | } | ||||
313 | } | ||||
314 | |||||
315 | if (offset > 0) { | ||||
316 | /* Obtain pointer to start of input data */ | ||||
317 | parserutils_inputstream_peek(stream, 0, &ch, &chlen); | ||||
318 | /* Emit any data remaining */ | ||||
319 | if (textplain_copy_utf8_data(c, ch, offset) == false0) | ||||
320 | return false0; | ||||
321 | |||||
322 | /* Advance the inputstream past the data we've read */ | ||||
323 | parserutils_inputstream_advance(stream, offset); | ||||
324 | } | ||||
325 | |||||
326 | return true1; | ||||
327 | } | ||||
328 | |||||
329 | |||||
330 | /** | ||||
331 | * Process data for CONTENT_TEXTPLAIN. | ||||
332 | */ | ||||
333 | static bool_Bool | ||||
334 | textplain_process_data(struct content *c, const char *data, unsigned int size) | ||||
335 | { | ||||
336 | textplain_content *text = (textplain_content *) c; | ||||
337 | parserutils_inputstream *stream = text->inputstream; | ||||
338 | parserutils_error error; | ||||
339 | |||||
340 | error = parserutils_inputstream_append(stream, | ||||
341 | (const uint8_t *) data, size); | ||||
342 | if (error != PARSERUTILS_OK) { | ||||
343 | goto no_memory; | ||||
344 | } | ||||
345 | |||||
346 | if (textplain_drain_input(text, stream, PARSERUTILS_NEEDDATA) == false0) | ||||
347 | goto no_memory; | ||||
348 | |||||
349 | return true1; | ||||
350 | |||||
351 | no_memory: | ||||
352 | content_broadcast_error(c, NSERROR_NOMEM, NULL((void*)0)); | ||||
353 | return false0; | ||||
354 | } | ||||
355 | |||||
356 | |||||
357 | /** | ||||
358 | * Convert a CONTENT_TEXTPLAIN for display. | ||||
359 | */ | ||||
360 | static bool_Bool textplain_convert(struct content *c) | ||||
361 | { | ||||
362 | textplain_content *text = (textplain_content *) c; | ||||
363 | parserutils_inputstream *stream = text->inputstream; | ||||
364 | parserutils_error error; | ||||
365 | |||||
366 | error = parserutils_inputstream_append(stream, NULL((void*)0), 0); | ||||
367 | if (error != PARSERUTILS_OK) { | ||||
368 | return false0; | ||||
369 | } | ||||
370 | |||||
371 | if (textplain_drain_input(text, stream, PARSERUTILS_EOF) == false0) | ||||
372 | return false0; | ||||
373 | |||||
374 | parserutils_inputstream_destroy(stream); | ||||
375 | text->inputstream = NULL((void*)0); | ||||
376 | |||||
377 | content_set_ready(c); | ||||
378 | content_set_done(c); | ||||
379 | content_set_status(c, messages_get("Done")); | ||||
380 | |||||
381 | return true1; | ||||
382 | } | ||||
383 | |||||
384 | |||||
385 | /** | ||||
386 | * Calculate the line height, in pixels | ||||
387 | * | ||||
388 | * \return Line height, in pixels | ||||
389 | */ | ||||
390 | static float textplain_line_height(void) | ||||
391 | { | ||||
392 | /* Size is in points, so convert to pixels. | ||||
393 | * Then use a constant line height of 1.2 x font size. | ||||
394 | */ | ||||
395 | return FIXTOFLT(FDIV((FMUL(FLTTOFIX(1.2), FMUL(nscss_screen_dpi, INTTOFIX((textplain_style.size / PLOT_STYLE_SCALE))))), F_72))((float) ((css_divide_fixed((((css_multiply_fixed((((css_fixed ) ((1.2) * (float) (1 << 10)))), ((css_multiply_fixed(( nscss_screen_dpi), ((css_int_to_fixed((textplain_style.size / (1 << (10))))))))))))), (0x00012000)))) / (float) (1 << 10)); | ||||
396 | } | ||||
397 | |||||
398 | |||||
399 | /** | ||||
400 | * Reformat a CONTENT_TEXTPLAIN to a new width. | ||||
401 | */ | ||||
402 | static void textplain_reformat(struct content *c, int width, int height) | ||||
403 | { | ||||
404 | textplain_content *text = (textplain_content *) c; | ||||
405 | char *utf8_data = text->utf8_data; | ||||
406 | size_t utf8_data_size = text->utf8_data_size; | ||||
407 | unsigned long line_count = 0; | ||||
408 | struct textplain_line *line = text->physical_line; | ||||
409 | struct textplain_line *line1; | ||||
410 | size_t i, space, col; | ||||
411 | size_t columns = 80; | ||||
412 | int character_width; | ||||
413 | size_t line_start; | ||||
414 | nserror res; | ||||
415 | |||||
416 | NSLOG(netsurf, INFO, "content %p w:%d h:%d", c, width, height)do { if (NSLOG_LEVEL_INFO >= NSLOG_LEVEL_VERBOSE) { static nslog_entry_context_t _nslog_ctx = { &__nslog_category_netsurf , NSLOG_LEVEL_INFO, "content/handlers/text/textplain.c", sizeof ("content/handlers/text/textplain.c") - 1, __PRETTY_FUNCTION__ , sizeof(__PRETTY_FUNCTION__) - 1, 416, }; nslog__log(&_nslog_ctx , "content %p w:%d h:%d", c, width, height); } } while(0); | ||||
417 | |||||
418 | /* compute available columns (assuming monospaced font) - use 8 | ||||
419 | * characters for better accuracy | ||||
420 | */ | ||||
421 | res = guit->layout->width(&textplain_style, | ||||
422 | "ABCDEFGH", 8, | ||||
423 | &character_width); | ||||
424 | if (res != NSERROR_OK) { | ||||
425 | return; | ||||
426 | } | ||||
427 | |||||
428 | columns = (width - MARGIN4 - MARGIN4) * 8 / character_width; | ||||
429 | textplain_tab_width = (TAB_WIDTH8 * character_width) / 8; | ||||
430 | |||||
431 | text->formatted_width = width; | ||||
432 | |||||
433 | text->physical_line_count = 0; | ||||
434 | |||||
435 | if (!line) { | ||||
436 | text->physical_line = line = | ||||
437 | malloc(sizeof(struct textplain_line) * (1024 + 3)); | ||||
438 | if (!line) | ||||
439 | goto no_memory; | ||||
440 | } | ||||
441 | |||||
442 | line[line_count++].start = line_start = 0; | ||||
443 | space = 0; | ||||
444 | i = 0; | ||||
445 | col = 0; | ||||
446 | while (i < utf8_data_size) { | ||||
447 | size_t csize; /* number of bytes in character */ | ||||
448 | uint32_t chr; | ||||
449 | bool_Bool term; | ||||
450 | size_t next_col; | ||||
451 | parserutils_error perror; | ||||
452 | |||||
453 | perror = parserutils_charset_utf8_to_ucs4((const uint8_t *)utf8_data + i, utf8_data_size - i, &chr, &csize); | ||||
454 | if (perror != PARSERUTILS_OK) { | ||||
455 | chr = 0xfffd; | ||||
456 | } | ||||
457 | |||||
458 | term = (chr == '\n' || chr == '\r'); | ||||
459 | |||||
460 | next_col = col + 1; | ||||
461 | |||||
462 | if (chr == '\t') { | ||||
463 | next_col = (next_col + TAB_WIDTH8 - 1) & ~(TAB_WIDTH8 - 1); | ||||
464 | } | ||||
465 | |||||
466 | if (term || next_col >= columns) { | ||||
467 | if (line_count % 1024 == 0) { | ||||
468 | line1 = realloc(line, | ||||
469 | sizeof(struct textplain_line) * | ||||
470 | (line_count + 1024 + 3)); | ||||
471 | if (!line1) | ||||
472 | goto no_memory; | ||||
473 | text->physical_line = line = line1; | ||||
474 | } | ||||
475 | |||||
476 | if (term) { | ||||
477 | line[line_count-1].length = i - line_start; | ||||
478 | |||||
479 | /* skip second char of CR/LF or LF/CR pair */ | ||||
480 | if (i + 1 < utf8_data_size && | ||||
481 | utf8_data[i+1] != utf8_data[i] && | ||||
482 | (utf8_data[i+1] == '\n' || | ||||
483 | utf8_data[i+1] == '\r')) { | ||||
484 | i++; | ||||
485 | } | ||||
486 | } else { | ||||
487 | if (space) { | ||||
488 | /* break at last space in line */ | ||||
489 | i = space; | ||||
490 | line[line_count-1].length = (i + 1) - line_start; | ||||
491 | } else | ||||
492 | line[line_count-1].length = i - line_start; | ||||
493 | } | ||||
494 | |||||
495 | line[line_count++].start = line_start = i + 1; | ||||
496 | col = 0; | ||||
497 | space = 0; | ||||
498 | } else { | ||||
499 | col++; | ||||
500 | if (chr == ' ') | ||||
501 | space = i; | ||||
502 | } | ||||
503 | i += csize; | ||||
504 | } | ||||
505 | line[line_count-1].length = i - line[line_count-1].start; | ||||
506 | line[line_count].start = utf8_data_size; | ||||
507 | |||||
508 | text->physical_line_count = line_count; | ||||
509 | c->width = width; | ||||
510 | c->height = line_count * textplain_line_height() + MARGIN4 + MARGIN4; | ||||
511 | |||||
512 | return; | ||||
513 | |||||
514 | no_memory: | ||||
515 | NSLOG(netsurf, INFO, "out of memory (line_count %lu)", line_count)do { if (NSLOG_LEVEL_INFO >= NSLOG_LEVEL_VERBOSE) { static nslog_entry_context_t _nslog_ctx = { &__nslog_category_netsurf , NSLOG_LEVEL_INFO, "content/handlers/text/textplain.c", sizeof ("content/handlers/text/textplain.c") - 1, __PRETTY_FUNCTION__ , sizeof(__PRETTY_FUNCTION__) - 1, 515, }; nslog__log(&_nslog_ctx , "out of memory (line_count %lu)", line_count); } } while(0); | ||||
516 | return; | ||||
517 | } | ||||
518 | |||||
519 | |||||
520 | /** | ||||
521 | * Destroy a CONTENT_TEXTPLAIN and free all resources it owns. | ||||
522 | */ | ||||
523 | |||||
524 | static void textplain_destroy(struct content *c) | ||||
525 | { | ||||
526 | textplain_content *text = (textplain_content *) c; | ||||
527 | |||||
528 | lwc_string_unref(text->encoding){ lwc_string *__lwc_s = (text->encoding); ((__lwc_s != ((void *)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL", "content/handlers/text/textplain.c" , 528, __extension__ __PRETTY_FUNCTION__)); __lwc_s->refcnt --; if ((__lwc_s->refcnt == 0) || ((__lwc_s->refcnt == 1 ) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy (__lwc_s); }; | ||||
529 | |||||
530 | if (text->inputstream != NULL((void*)0)) { | ||||
531 | parserutils_inputstream_destroy(text->inputstream); | ||||
532 | } | ||||
533 | |||||
534 | if (text->physical_line != NULL((void*)0)) { | ||||
535 | free(text->physical_line); | ||||
536 | } | ||||
537 | |||||
538 | if (text->utf8_data != NULL((void*)0)) { | ||||
539 | free(text->utf8_data); | ||||
540 | } | ||||
541 | |||||
542 | if (text->sel != NULL((void*)0)) { | ||||
543 | selection_destroy(text->sel); | ||||
544 | } | ||||
545 | } | ||||
546 | |||||
547 | |||||
548 | static nserror textplain_clone(const struct content *old, struct content **newc) | ||||
549 | { | ||||
550 | const textplain_content *old_text = (textplain_content *) old; | ||||
551 | textplain_content *text; | ||||
552 | nserror error; | ||||
553 | const uint8_t *data; | ||||
554 | size_t size; | ||||
555 | |||||
556 | text = calloc(1, sizeof(textplain_content)); | ||||
557 | if (text == NULL((void*)0)) | ||||
| |||||
558 | return NSERROR_NOMEM; | ||||
559 | |||||
560 | error = content__clone(old, &text->base); | ||||
561 | if (error != NSERROR_OK) { | ||||
562 | content_destroy(&text->base); | ||||
563 | return error; | ||||
564 | } | ||||
565 | |||||
566 | /* Simply replay create/process/convert */ | ||||
567 | error = textplain_create_internal(text, old_text->encoding); | ||||
568 | if (error
| ||||
569 | content_destroy(&text->base); | ||||
570 | return error; | ||||
571 | } | ||||
572 | |||||
573 | data = content__get_source_data(&text->base, &size); | ||||
574 | if (size > 0) { | ||||
575 | if (textplain_process_data(&text->base, | ||||
576 | (const char *)data, | ||||
577 | size) == false0) { | ||||
578 | content_destroy(&text->base); | ||||
579 | return NSERROR_NOMEM; | ||||
580 | } | ||||
581 | } | ||||
582 | |||||
583 | if (old->status == CONTENT_STATUS_READY || | ||||
584 | old->status == CONTENT_STATUS_DONE) { | ||||
585 | if (textplain_convert(&text->base) == false0) { | ||||
586 | content_destroy(&text->base); | ||||
587 | return NSERROR_CLONE_FAILED; | ||||
588 | } | ||||
589 | } | ||||
590 | |||||
591 | return NSERROR_OK; | ||||
592 | } | ||||
593 | |||||
594 | |||||
595 | static content_type textplain_content_type(void) | ||||
596 | { | ||||
597 | return CONTENT_TEXTPLAIN; | ||||
598 | } | ||||
599 | |||||
600 | |||||
601 | /** | ||||
602 | * Return byte offset within UTF8 textplain content. | ||||
603 | * | ||||
604 | * given the co-ordinates of a point within a textplain content. 'dir' | ||||
605 | * specifies the direction in which to search (-1 = above-left, +1 = | ||||
606 | * below-right) if the co-ordinates are not contained within a line. | ||||
607 | * | ||||
608 | * \param[in] c content of type CONTENT_TEXTPLAIN | ||||
609 | * \param[in] x x ordinate of point | ||||
610 | * \param[in] y y ordinate of point | ||||
611 | * \param[in] dir direction of search if not within line | ||||
612 | * \return byte offset of character containing (or nearest to) point | ||||
613 | */ | ||||
614 | static size_t | ||||
615 | textplain_offset_from_coords(struct content *c, int x, int y, int dir) | ||||
616 | { | ||||
617 | textplain_content *textc = (textplain_content *) c; | ||||
618 | float line_height = textplain_line_height(); | ||||
619 | struct textplain_line *line; | ||||
620 | const char *text; | ||||
621 | unsigned nlines; | ||||
622 | size_t length; | ||||
623 | int idx; | ||||
624 | |||||
625 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/handlers/text/textplain.c", 625, __extension__ __PRETTY_FUNCTION__ )); | ||||
626 | |||||
627 | y = (int)((float)(y - MARGIN4) / line_height); | ||||
628 | x -= MARGIN4; | ||||
629 | |||||
630 | nlines = textc->physical_line_count; | ||||
631 | if (!nlines) | ||||
632 | return 0; | ||||
633 | |||||
634 | if (y <= 0) y = 0; | ||||
635 | else if ((unsigned)y >= nlines) | ||||
636 | y = nlines - 1; | ||||
637 | |||||
638 | line = &textc->physical_line[y]; | ||||
639 | text = textc->utf8_data + line->start; | ||||
640 | length = line->length; | ||||
641 | idx = 0; | ||||
642 | |||||
643 | while (x > 0) { | ||||
644 | size_t next_offset = 0; | ||||
645 | int width = INT_MAX2147483647; | ||||
646 | |||||
647 | while (next_offset < length && text[next_offset] != '\t') { | ||||
648 | next_offset = utf8_next(text, length, next_offset); | ||||
649 | } | ||||
650 | |||||
651 | if (next_offset < length) { | ||||
652 | guit->layout->width(&textplain_style, | ||||
653 | text, | ||||
654 | next_offset, | ||||
655 | &width); | ||||
656 | } | ||||
657 | |||||
658 | if (x <= width) { | ||||
659 | int pixel_offset; | ||||
660 | size_t char_offset; | ||||
661 | |||||
662 | guit->layout->position(&textplain_style, | ||||
663 | text, next_offset, x, | ||||
664 | &char_offset, &pixel_offset); | ||||
665 | |||||
666 | idx += char_offset; | ||||
667 | break; | ||||
668 | } | ||||
669 | |||||
670 | x -= width; | ||||
671 | length -= next_offset; | ||||
672 | text += next_offset; | ||||
673 | idx += next_offset; | ||||
674 | |||||
675 | /* check if it's within the tab */ | ||||
676 | width = textplain_tab_width - (width % textplain_tab_width); | ||||
677 | if (x <= width) break; | ||||
678 | |||||
679 | x -= width; | ||||
680 | length--; | ||||
681 | text++; | ||||
682 | idx++; | ||||
683 | } | ||||
684 | |||||
685 | return line->start + idx; | ||||
686 | } | ||||
687 | |||||
688 | |||||
689 | /** | ||||
690 | * Handle mouse clicks and movements in a TEXTPLAIN content window. | ||||
691 | * | ||||
692 | * \param c content of type textplain | ||||
693 | * \param bw browser window | ||||
694 | * \param mouse mouse state on action | ||||
695 | * \param x coordinate of mouse | ||||
696 | * \param y coordinate of mouse | ||||
697 | */ | ||||
698 | static nserror | ||||
699 | textplain_mouse_action(struct content *c, | ||||
700 | struct browser_window *bw, | ||||
701 | browser_mouse_state mouse, | ||||
702 | int x, int y) | ||||
703 | { | ||||
704 | textplain_content *text = (textplain_content *) c; | ||||
705 | browser_pointer_shape pointer = BROWSER_POINTER_DEFAULT; | ||||
706 | union content_msg_data msg_data; | ||||
707 | const char *status = 0; | ||||
708 | size_t idx; | ||||
709 | int dir = 0; | ||||
710 | |||||
711 | browser_window_set_drag_type(bw, DRAGGING_NONE, NULL((void*)0)); | ||||
712 | |||||
713 | idx = textplain_offset_from_coords(c, x, y, dir); | ||||
714 | if (selection_click(text->sel, text->bw, mouse, idx)) { | ||||
715 | |||||
716 | if (selection_dragging(text->sel)) { | ||||
717 | browser_window_set_drag_type(bw, | ||||
718 | DRAGGING_SELECTION, NULL((void*)0)); | ||||
719 | status = messages_get("Selecting"); | ||||
720 | } | ||||
721 | |||||
722 | } else { | ||||
723 | if (mouse & (BROWSER_MOUSE_DRAG_1 | BROWSER_MOUSE_DRAG_2)) { | ||||
724 | browser_window_page_drag_start(bw, x, y); | ||||
725 | pointer = BROWSER_POINTER_MOVE; | ||||
726 | } | ||||
727 | } | ||||
728 | |||||
729 | msg_data.explicit_status_text = status; | ||||
730 | content_broadcast(c, CONTENT_MSG_STATUS, &msg_data); | ||||
731 | |||||
732 | msg_data.pointer = pointer; | ||||
733 | content_broadcast(c, CONTENT_MSG_POINTER, &msg_data); | ||||
734 | |||||
735 | return NSERROR_OK; | ||||
736 | } | ||||
737 | |||||
738 | |||||
739 | /** | ||||
740 | * Handle mouse tracking (including drags) in a TEXTPLAIN content window. | ||||
741 | * | ||||
742 | * \param c content of type textplain | ||||
743 | * \param bw browser window | ||||
744 | * \param mouse state of mouse buttons and modifier keys | ||||
745 | * \param x coordinate of mouse | ||||
746 | * \param y coordinate of mouse | ||||
747 | */ | ||||
748 | static nserror | ||||
749 | textplain_mouse_track(struct content *c, | ||||
750 | struct browser_window *bw, | ||||
751 | browser_mouse_state mouse, | ||||
752 | int x, int y) | ||||
753 | { | ||||
754 | textplain_content *text = (textplain_content *) c; | ||||
755 | |||||
756 | if (browser_window_get_drag_type(bw) == DRAGGING_SELECTION && !mouse) { | ||||
757 | int dir = -1; | ||||
758 | size_t idx; | ||||
759 | |||||
760 | if (selection_dragging_start(text->sel)) | ||||
761 | dir = 1; | ||||
762 | |||||
763 | idx = textplain_offset_from_coords(c, x, y, dir); | ||||
764 | selection_track(text->sel, mouse, idx); | ||||
765 | |||||
766 | browser_window_set_drag_type(bw, DRAGGING_NONE, NULL((void*)0)); | ||||
767 | } | ||||
768 | |||||
769 | switch (browser_window_get_drag_type(bw)) { | ||||
770 | |||||
771 | case DRAGGING_SELECTION: { | ||||
772 | int dir = -1; | ||||
773 | size_t idx; | ||||
774 | |||||
775 | if (selection_dragging_start(text->sel)) dir = 1; | ||||
776 | |||||
777 | idx = textplain_offset_from_coords(c, x, y, dir); | ||||
778 | selection_track(text->sel, mouse, idx); | ||||
779 | } | ||||
780 | break; | ||||
781 | |||||
782 | default: | ||||
783 | textplain_mouse_action(c, bw, mouse, x, y); | ||||
784 | break; | ||||
785 | } | ||||
786 | |||||
787 | return NSERROR_OK; | ||||
788 | } | ||||
789 | |||||
790 | |||||
791 | /** | ||||
792 | * Handle keypresses. | ||||
793 | * | ||||
794 | * \param c content of type CONTENT_TEXTPLAIN | ||||
795 | * \param key The UCS4 character codepoint | ||||
796 | * \return true if key handled, false otherwise | ||||
797 | */ | ||||
798 | static bool_Bool textplain_keypress(struct content *c, uint32_t key) | ||||
799 | { | ||||
800 | textplain_content *text = (textplain_content *) c; | ||||
801 | struct selection *sel = text->sel; | ||||
802 | |||||
803 | switch (key) { | ||||
804 | case NS_KEY_COPY_SELECTION: | ||||
805 | selection_copy_to_clipboard(sel); | ||||
806 | return true1; | ||||
807 | |||||
808 | case NS_KEY_CLEAR_SELECTION: | ||||
809 | selection_clear(sel, true1); | ||||
810 | return true1; | ||||
811 | |||||
812 | case NS_KEY_SELECT_ALL: | ||||
813 | selection_select_all(sel); | ||||
814 | return true1; | ||||
815 | |||||
816 | case NS_KEY_ESCAPE: | ||||
817 | /* if there's no selection, leave Escape for the caller */ | ||||
818 | return selection_clear(sel, true1); | ||||
819 | } | ||||
820 | |||||
821 | return false0; | ||||
822 | } | ||||
823 | |||||
824 | |||||
825 | /** | ||||
826 | * Redraw a text string with highlighting | ||||
827 | * (for selection/search) | ||||
828 | * | ||||
829 | * \param utf8_text pointer to UTF-8 text string | ||||
830 | * \param utf8_len length of string, in bytes | ||||
831 | * \param offset byte offset within textual representation | ||||
832 | * \param x x ordinate at which to plot text | ||||
833 | * \param y y ordinate at which to plot text | ||||
834 | * \param clip pointer to current clip rectangle | ||||
835 | * \param height height of text string | ||||
836 | * \param scale current display scale (1.0 = 100%) | ||||
837 | * \param text Content being redrawn. | ||||
838 | * \param sel Selection context | ||||
839 | * \param search Search context | ||||
840 | * \param ctx current redraw context | ||||
841 | * \return true iff successful and redraw should proceed | ||||
842 | */ | ||||
843 | static bool_Bool | ||||
844 | text_draw(const char *utf8_text, | ||||
845 | size_t utf8_len, | ||||
846 | size_t offset, | ||||
847 | int x, | ||||
848 | int y, | ||||
849 | const struct rect *clip, | ||||
850 | int height, | ||||
851 | float scale, | ||||
852 | textplain_content *text, | ||||
853 | const struct selection *sel, | ||||
854 | const struct redraw_context *ctx) | ||||
855 | { | ||||
856 | bool_Bool highlighted = false0; | ||||
857 | plot_font_style_t plot_fstyle; | ||||
858 | nserror res; | ||||
859 | |||||
860 | /* Need scaled text size to pass to plotters */ | ||||
861 | plot_fstyle = textplain_style; | ||||
862 | plot_fstyle.size *= scale; | ||||
863 | |||||
864 | /* is this box part of a selection? */ | ||||
865 | if (ctx->interactive == true1) { | ||||
866 | unsigned len = utf8_len; | ||||
867 | unsigned start_idx; | ||||
868 | unsigned end_idx; | ||||
869 | |||||
870 | /* first try the browser window's current selection */ | ||||
871 | if (selection_highlighted(sel, | ||||
872 | offset, | ||||
873 | offset + len, | ||||
874 | &start_idx, | ||||
875 | &end_idx)) { | ||||
876 | highlighted = true1; | ||||
877 | } | ||||
878 | |||||
879 | /* what about the current search operation, if any? */ | ||||
880 | if (!highlighted && | ||||
881 | (text->base.textsearch.context != NULL((void*)0)) && | ||||
882 | content_textsearch_ishighlighted(text->base.textsearch.context, | ||||
883 | offset, | ||||
884 | offset + len, | ||||
885 | &start_idx, | ||||
886 | &end_idx)) { | ||||
887 | highlighted = true1; | ||||
888 | } | ||||
889 | |||||
890 | /* \todo make search terms visible within selected text */ | ||||
891 | if (highlighted) { | ||||
892 | struct rect r; | ||||
893 | unsigned endtxt_idx = end_idx; | ||||
894 | bool_Bool clip_changed = false0; | ||||
895 | bool_Bool text_visible = true1; | ||||
896 | int startx, endx; | ||||
897 | plot_style_t pstyle_fill_hback = *plot_style_fill_white; | ||||
898 | plot_font_style_t fstyle_hback = plot_fstyle; | ||||
899 | |||||
900 | if (end_idx > utf8_len) { | ||||
901 | /* adjust for trailing space, not present in | ||||
902 | * utf8_text | ||||
903 | */ | ||||
904 | assert(end_idx == utf8_len + 1)((end_idx == utf8_len + 1) ? (void) (0) : __assert_fail ("end_idx == utf8_len + 1" , "content/handlers/text/textplain.c", 904, __extension__ __PRETTY_FUNCTION__ )); | ||||
905 | endtxt_idx = utf8_len; | ||||
906 | } | ||||
907 | |||||
908 | res = guit->layout->width(&textplain_style, | ||||
909 | utf8_text, | ||||
910 | start_idx, | ||||
911 | &startx); | ||||
912 | if (res != NSERROR_OK) { | ||||
913 | startx = 0; | ||||
914 | } | ||||
915 | |||||
916 | res = guit->layout->width(&textplain_style, | ||||
917 | utf8_text, | ||||
918 | endtxt_idx, | ||||
919 | &endx); | ||||
920 | if (res != NSERROR_OK) { | ||||
921 | endx = 0; | ||||
922 | } | ||||
923 | |||||
924 | if (scale != 1.0) { | ||||
925 | startx *= scale; | ||||
926 | endx *= scale; | ||||
927 | } | ||||
928 | |||||
929 | /* draw any text preceding highlighted portion */ | ||||
930 | if (start_idx > 0) { | ||||
931 | res = ctx->plot->text(ctx, | ||||
932 | &plot_fstyle, | ||||
933 | x, | ||||
934 | y + (int)(height * 0.75 * scale), | ||||
935 | utf8_text, | ||||
936 | start_idx); | ||||
937 | if (res != NSERROR_OK) { | ||||
938 | return false0; | ||||
939 | } | ||||
940 | } | ||||
941 | |||||
942 | pstyle_fill_hback.fill_colour = textplain_style.foreground; | ||||
943 | |||||
944 | /* highlighted portion */ | ||||
945 | r.x0 = x + startx; | ||||
946 | r.y0 = y; | ||||
947 | r.x1 = x + endx; | ||||
948 | r.y1 = y + height * scale; | ||||
949 | res = ctx->plot->rectangle(ctx, &pstyle_fill_hback, &r); | ||||
950 | if (res != NSERROR_OK) { | ||||
951 | return false0; | ||||
952 | } | ||||
953 | |||||
954 | if (start_idx > 0) { | ||||
955 | int px0 = max(x + startx, clip->x0)(((x + startx)>(clip->x0))?(x + startx):(clip->x0)); | ||||
956 | int px1 = min(x + endx, clip->x1)(((x + endx)<(clip->x1))?(x + endx):(clip->x1)); | ||||
957 | |||||
958 | if (px0 < px1) { | ||||
959 | r.x0 = px0; | ||||
960 | r.y0 = clip->y0; | ||||
961 | r.x1 = px1; | ||||
962 | r.y1 = clip->y1; | ||||
963 | res = ctx->plot->clip(ctx, &r); | ||||
964 | if (res != NSERROR_OK) { | ||||
965 | return false0; | ||||
966 | } | ||||
967 | |||||
968 | clip_changed = true1; | ||||
969 | } else { | ||||
970 | text_visible = false0; | ||||
971 | } | ||||
972 | } | ||||
973 | |||||
974 | fstyle_hback.background = | ||||
975 | pstyle_fill_hback.fill_colour; | ||||
976 | fstyle_hback.foreground = colour_to_bw_furthest(((((((pstyle_fill_hback.fill_colour & 0x0000ff) * 77) >> 8) + (((pstyle_fill_hback.fill_colour & 0x00ff00) * 151) >> 16) + (((pstyle_fill_hback.fill_colour & 0xff0000 ) * 30) >> 24)) > (0xff / 2)) ? 0x000000 : 0xffffff) | ||||
977 | pstyle_fill_hback.fill_colour)((((((pstyle_fill_hback.fill_colour & 0x0000ff) * 77) >> 8) + (((pstyle_fill_hback.fill_colour & 0x00ff00) * 151) >> 16) + (((pstyle_fill_hback.fill_colour & 0xff0000 ) * 30) >> 24)) > (0xff / 2)) ? 0x000000 : 0xffffff); | ||||
978 | |||||
979 | if (text_visible && | ||||
980 | (ctx->plot->text(ctx, | ||||
981 | &fstyle_hback, | ||||
982 | x, | ||||
983 | y + (int)(height * 0.75 * scale), | ||||
984 | utf8_text, | ||||
985 | endtxt_idx) != NSERROR_OK)) { | ||||
986 | return false0; | ||||
987 | } | ||||
988 | |||||
989 | /* draw any text succeeding highlighted portion */ | ||||
990 | if (endtxt_idx < utf8_len) { | ||||
991 | int px0 = max(x + endx, clip->x0)(((x + endx)>(clip->x0))?(x + endx):(clip->x0)); | ||||
992 | if (px0 < clip->x1) { | ||||
993 | |||||
994 | r.x0 = px0; | ||||
995 | r.y0 = clip->y0; | ||||
996 | r.x1 = clip->x1; | ||||
997 | r.y1 = clip->y1; | ||||
998 | res = ctx->plot->clip(ctx, &r); | ||||
999 | if (res != NSERROR_OK) { | ||||
1000 | return false0; | ||||
1001 | } | ||||
1002 | |||||
1003 | clip_changed = true1; | ||||
1004 | |||||
1005 | res = ctx->plot->text(ctx, | ||||
1006 | &plot_fstyle, | ||||
1007 | x, | ||||
1008 | y + (int)(height * 0.75 * scale), | ||||
1009 | utf8_text, | ||||
1010 | utf8_len); | ||||
1011 | if (res != NSERROR_OK) { | ||||
1012 | return false0; | ||||
1013 | } | ||||
1014 | } | ||||
1015 | } | ||||
1016 | |||||
1017 | if (clip_changed && | ||||
1018 | (ctx->plot->clip(ctx, clip) != NSERROR_OK)) { | ||||
1019 | return false0; | ||||
1020 | } | ||||
1021 | } | ||||
1022 | } | ||||
1023 | |||||
1024 | if (!highlighted) { | ||||
1025 | res = ctx->plot->text(ctx, | ||||
1026 | &plot_fstyle, | ||||
1027 | x, | ||||
1028 | y + (int) (height * 0.75 * scale), | ||||
1029 | utf8_text, | ||||
1030 | utf8_len); | ||||
1031 | if (res != NSERROR_OK) { | ||||
1032 | return false0; | ||||
1033 | } | ||||
1034 | } | ||||
1035 | return true1; | ||||
1036 | } | ||||
1037 | |||||
1038 | |||||
1039 | /** | ||||
1040 | * Draw a CONTENT_TEXTPLAIN using the current set of plotters (plot). | ||||
1041 | * | ||||
1042 | * x, y, clip_[xy][01] are in target coordinates. | ||||
1043 | * | ||||
1044 | * \param c content of type CONTENT_TEXTPLAIN | ||||
1045 | * \param data redraw data for this content redraw | ||||
1046 | * \param clip current clip region | ||||
1047 | * \param ctx current redraw context | ||||
1048 | * \return true if successful, false otherwise | ||||
1049 | */ | ||||
1050 | static bool_Bool | ||||
1051 | textplain_redraw(struct content *c, | ||||
1052 | struct content_redraw_data *data, | ||||
1053 | const struct rect *clip, | ||||
1054 | const struct redraw_context *ctx) | ||||
1055 | { | ||||
1056 | textplain_content *text = (textplain_content *) c; | ||||
1057 | struct browser_window *bw = text->bw; | ||||
1058 | char *utf8_data = text->utf8_data; | ||||
1059 | long lineno; | ||||
1060 | int x = data->x; | ||||
1061 | int y = data->y; | ||||
1062 | unsigned long line_count = text->physical_line_count; | ||||
1063 | float line_height = textplain_line_height(); | ||||
1064 | float scaled_line_height = line_height * data->scale; | ||||
1065 | long line0 = (clip->y0 - y * data->scale) / scaled_line_height - 1; | ||||
1066 | long line1 = (clip->y1 - y * data->scale) / scaled_line_height + 1; | ||||
1067 | struct textplain_line *line = text->physical_line; | ||||
1068 | size_t length; | ||||
1069 | plot_style_t *plot_style_highlight; | ||||
1070 | nserror res; | ||||
1071 | |||||
1072 | if (line0 < 0) | ||||
1073 | line0 = 0; | ||||
1074 | if (line1 < 0) | ||||
1075 | line1 = 0; | ||||
1076 | if (line_count < (unsigned long) line0) | ||||
1077 | line0 = line_count; | ||||
1078 | if (line_count < (unsigned long) line1) | ||||
1079 | line1 = line_count; | ||||
1080 | if (line1 < line0) | ||||
1081 | line1 = line0; | ||||
1082 | |||||
1083 | res = ctx->plot->rectangle(ctx, plot_style_fill_white, clip); | ||||
1084 | if (res != NSERROR_OK) { | ||||
1085 | return false0; | ||||
1086 | } | ||||
1087 | |||||
1088 | if (!line) | ||||
1089 | return true1; | ||||
1090 | |||||
1091 | /* choose a suitable background colour for any highlighted text */ | ||||
1092 | if ((data->background_colour & 0x808080) == 0x808080) | ||||
1093 | plot_style_highlight = plot_style_fill_black; | ||||
1094 | else | ||||
1095 | plot_style_highlight = plot_style_fill_white; | ||||
1096 | |||||
1097 | /* Set up font plot style */ | ||||
1098 | textplain_style.background = data->background_colour; | ||||
1099 | |||||
1100 | x = (x + MARGIN4) * data->scale; | ||||
1101 | y = (y + MARGIN4) * data->scale; | ||||
1102 | for (lineno = line0; lineno != line1; lineno++) { | ||||
1103 | const char *text_d = utf8_data + line[lineno].start; | ||||
1104 | int tab_width = textplain_tab_width * data->scale; | ||||
1105 | size_t offset = 0; | ||||
1106 | int tx = x; | ||||
1107 | |||||
1108 | if (!tab_width) tab_width = 1; | ||||
1109 | |||||
1110 | length = line[lineno].length; | ||||
1111 | if (!length) | ||||
1112 | continue; | ||||
1113 | |||||
1114 | while (offset < length) { | ||||
1115 | size_t next_offset = offset; | ||||
1116 | int width; | ||||
1117 | int ntx; | ||||
1118 | nserror res; | ||||
1119 | |||||
1120 | while ((next_offset < length) && | ||||
1121 | (text_d[next_offset] != '\t')) { | ||||
1122 | next_offset = utf8_next(text_d, | ||||
1123 | length, | ||||
1124 | next_offset); | ||||
1125 | } | ||||
1126 | |||||
1127 | if (!text_draw(text_d + offset, | ||||
1128 | next_offset - offset, | ||||
1129 | line[lineno].start + offset, | ||||
1130 | tx, | ||||
1131 | y + (lineno * scaled_line_height), | ||||
1132 | clip, | ||||
1133 | line_height, | ||||
1134 | data->scale, | ||||
1135 | text, | ||||
1136 | text->sel, | ||||
1137 | ctx)) { | ||||
1138 | return false0; | ||||
1139 | } | ||||
1140 | |||||
1141 | if (next_offset >= length) | ||||
1142 | break; | ||||
1143 | |||||
1144 | res = guit->layout->width(&textplain_style, | ||||
1145 | &text_d[offset], | ||||
1146 | next_offset - offset, | ||||
1147 | &width); | ||||
1148 | /* locate end of string and align to next tab position */ | ||||
1149 | if (res == NSERROR_OK) { | ||||
1150 | tx += (int)(width * data->scale); | ||||
1151 | } | ||||
1152 | |||||
1153 | ntx = x + ((1 + (tx - x) / tab_width) * tab_width); | ||||
1154 | |||||
1155 | /* if the tab character lies within the | ||||
1156 | * selection, if any, then we must draw it as | ||||
1157 | * a filled rectangle so that it's consistent | ||||
1158 | * with background of the selected text | ||||
1159 | */ | ||||
1160 | |||||
1161 | if (bw) { | ||||
1162 | unsigned tab_ofst = line[lineno].start + next_offset; | ||||
1163 | struct selection *sel = text->sel; | ||||
1164 | bool_Bool highlighted = false0; | ||||
1165 | |||||
1166 | unsigned start_idx, end_idx; | ||||
1167 | if (selection_highlighted(sel, | ||||
1168 | tab_ofst, | ||||
1169 | tab_ofst + 1, | ||||
1170 | &start_idx, | ||||
1171 | &end_idx)) { | ||||
1172 | highlighted = true1; | ||||
1173 | } | ||||
1174 | |||||
1175 | |||||
1176 | if (!highlighted && | ||||
1177 | (c->textsearch.context != NULL((void*)0))) { | ||||
1178 | unsigned start_idx, end_idx; | ||||
1179 | if (content_textsearch_ishighlighted( | ||||
1180 | c->textsearch.context, | ||||
1181 | tab_ofst, | ||||
1182 | tab_ofst + 1, | ||||
1183 | &start_idx, | ||||
1184 | &end_idx)) { | ||||
1185 | highlighted = true1; | ||||
1186 | } | ||||
1187 | } | ||||
1188 | |||||
1189 | if (highlighted) { | ||||
1190 | struct rect rect; | ||||
1191 | rect.x0 = tx; | ||||
1192 | rect.y0 = y + (lineno * scaled_line_height); | ||||
1193 | rect.x1 = ntx; | ||||
1194 | rect.y1 = rect.y0 + scaled_line_height; | ||||
1195 | res = ctx->plot->rectangle(ctx, | ||||
1196 | plot_style_highlight, | ||||
1197 | &rect); | ||||
1198 | if (res != NSERROR_OK) { | ||||
1199 | return false0; | ||||
1200 | } | ||||
1201 | } | ||||
1202 | } | ||||
1203 | |||||
1204 | offset = next_offset + 1; | ||||
1205 | tx = ntx; | ||||
1206 | } | ||||
1207 | } | ||||
1208 | |||||
1209 | return true1; | ||||
1210 | } | ||||
1211 | |||||
1212 | |||||
1213 | /** | ||||
1214 | * Handle a window containing a CONTENT_TEXTPLAIN being opened. | ||||
1215 | */ | ||||
1216 | static nserror | ||||
1217 | textplain_open(struct content *c, | ||||
1218 | struct browser_window *bw, | ||||
1219 | struct content *page, | ||||
1220 | struct object_params *params) | ||||
1221 | { | ||||
1222 | textplain_content *text = (textplain_content *) c; | ||||
1223 | |||||
1224 | text->bw = bw; | ||||
1225 | |||||
1226 | /* text selection */ | ||||
1227 | selection_init(text->sel); | ||||
1228 | |||||
1229 | return NSERROR_OK; | ||||
1230 | } | ||||
1231 | |||||
1232 | |||||
1233 | /** | ||||
1234 | * Handle a window containing a CONTENT_TEXTPLAIN being closed. | ||||
1235 | */ | ||||
1236 | static nserror textplain_close(struct content *c) | ||||
1237 | { | ||||
1238 | textplain_content *text = (textplain_content *) c; | ||||
1239 | |||||
1240 | text->bw = NULL((void*)0); | ||||
1241 | |||||
1242 | return NSERROR_OK; | ||||
1243 | } | ||||
1244 | |||||
1245 | |||||
1246 | /** | ||||
1247 | * Return an textplain content's selection context | ||||
1248 | */ | ||||
1249 | static char *textplain_get_selection(struct content *c) | ||||
1250 | { | ||||
1251 | textplain_content *text = (textplain_content *) c; | ||||
1252 | |||||
1253 | return selection_get_copy(text->sel); | ||||
1254 | } | ||||
1255 | |||||
1256 | |||||
1257 | /** | ||||
1258 | * Convert a character offset within a line of text into the | ||||
1259 | * horizontal co-ordinate | ||||
1260 | * | ||||
1261 | * The conversion takes into account the font being used and any tabs | ||||
1262 | * in the text | ||||
1263 | * | ||||
1264 | * \param text line of text | ||||
1265 | * \param offset char offset within text | ||||
1266 | * \param length line length | ||||
1267 | * \return x ordinate | ||||
1268 | */ | ||||
1269 | static int | ||||
1270 | textplain_coord_from_offset(const char *text, size_t offset, size_t length) | ||||
1271 | { | ||||
1272 | int x = 0; | ||||
1273 | |||||
1274 | while (offset > 0) { | ||||
1275 | size_t next_offset = 0; | ||||
1276 | int tx; | ||||
1277 | |||||
1278 | while (next_offset < offset && text[next_offset] != '\t') { | ||||
1279 | next_offset = utf8_next(text, length, next_offset); | ||||
1280 | } | ||||
1281 | |||||
1282 | guit->layout->width(&textplain_style, text, next_offset, &tx); | ||||
1283 | |||||
1284 | x += tx; | ||||
1285 | |||||
1286 | if (next_offset >= offset) | ||||
1287 | break; | ||||
1288 | |||||
1289 | /* align to next tab boundary */ | ||||
1290 | next_offset++; | ||||
1291 | x = (1 + (x / textplain_tab_width)) * textplain_tab_width; | ||||
1292 | offset -= next_offset; | ||||
1293 | text += next_offset; | ||||
1294 | length -= next_offset; | ||||
1295 | } | ||||
1296 | |||||
1297 | return x; | ||||
1298 | } | ||||
1299 | |||||
1300 | |||||
1301 | /** | ||||
1302 | * Retrieve number of lines in content | ||||
1303 | * | ||||
1304 | * \param[in] c Content to retrieve line count from | ||||
1305 | * \return Number of lines | ||||
1306 | */ | ||||
1307 | static unsigned long textplain_line_count(struct content *c) | ||||
1308 | { | ||||
1309 | textplain_content *text = (textplain_content *) c; | ||||
1310 | |||||
1311 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/handlers/text/textplain.c", 1311, __extension__ __PRETTY_FUNCTION__ )); | ||||
1312 | |||||
1313 | return text->physical_line_count; | ||||
1314 | } | ||||
1315 | |||||
1316 | |||||
1317 | /** | ||||
1318 | * Return a pointer to the requested line of text. | ||||
1319 | * | ||||
1320 | * \param[in] c content of type CONTENT_TEXTPLAIN | ||||
1321 | * \param[in] lineno line number | ||||
1322 | * \param[out] poffset receives byte offset of line start within text | ||||
1323 | * \param[out] plen receives length of returned line | ||||
1324 | * \return pointer to text, or NULL if invalid line number | ||||
1325 | */ | ||||
1326 | static char * | ||||
1327 | textplain_get_line(struct content *c, | ||||
1328 | unsigned lineno, | ||||
1329 | size_t *poffset, | ||||
1330 | size_t *plen) | ||||
1331 | { | ||||
1332 | textplain_content *text = (textplain_content *) c; | ||||
1333 | struct textplain_line *line; | ||||
1334 | |||||
1335 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/handlers/text/textplain.c", 1335, __extension__ __PRETTY_FUNCTION__ )); | ||||
1336 | |||||
1337 | if (lineno >= text->physical_line_count) | ||||
1338 | return NULL((void*)0); | ||||
1339 | line = &text->physical_line[lineno]; | ||||
1340 | |||||
1341 | *poffset = line->start; | ||||
1342 | *plen = line->length; | ||||
1343 | return text->utf8_data + line->start; | ||||
1344 | } | ||||
1345 | |||||
1346 | |||||
1347 | /** | ||||
1348 | * Find line number of byte in text | ||||
1349 | * | ||||
1350 | * Given a byte offset within the text, return the line number | ||||
1351 | * of the line containing that offset. | ||||
1352 | * | ||||
1353 | * \param[in] c content of type CONTENT_TEXTPLAIN | ||||
1354 | * \param[in] offset byte offset within textual representation | ||||
1355 | * \return line number, or -1 if offset invalid (larger than size) | ||||
1356 | */ | ||||
1357 | static int textplain_find_line(struct content *c, unsigned offset) | ||||
1358 | { | ||||
1359 | textplain_content *text = (textplain_content *) c; | ||||
1360 | struct textplain_line *line; | ||||
1361 | int nlines; | ||||
1362 | int lineno = 0; | ||||
1363 | |||||
1364 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/handlers/text/textplain.c", 1364, __extension__ __PRETTY_FUNCTION__ )); | ||||
1365 | |||||
1366 | line = text->physical_line; | ||||
1367 | nlines = text->physical_line_count; | ||||
1368 | |||||
1369 | if (offset > text->utf8_data_size) { | ||||
1370 | return -1; | ||||
1371 | } | ||||
1372 | |||||
1373 | /* \todo - implement binary search here */ | ||||
1374 | while (lineno < nlines && line[lineno].start < offset) { | ||||
1375 | lineno++; | ||||
1376 | } | ||||
1377 | if (line[lineno].start > offset) { | ||||
1378 | lineno--; | ||||
1379 | } | ||||
1380 | |||||
1381 | return lineno; | ||||
1382 | } | ||||
1383 | |||||
1384 | |||||
1385 | /** | ||||
1386 | * Finds all occurrences of a given string in a textplain content | ||||
1387 | * | ||||
1388 | * \param c the content to be searched | ||||
1389 | * \param context The search context to add the entry to. | ||||
1390 | * \param pattern the string pattern to search for | ||||
1391 | * \param p_len pattern length | ||||
1392 | * \param case_sens whether to perform a case sensitive search | ||||
1393 | * \return NSERROR_OK on success else error code on faliure | ||||
1394 | */ | ||||
1395 | static nserror | ||||
1396 | textplain_textsearch_find(struct content *c, | ||||
1397 | struct textsearch_context *context, | ||||
1398 | const char *pattern, | ||||
1399 | int p_len, | ||||
1400 | bool_Bool case_sens) | ||||
1401 | { | ||||
1402 | int nlines = textplain_line_count(c); | ||||
1403 | int line; | ||||
1404 | nserror res = NSERROR_OK; | ||||
1405 | |||||
1406 | for(line = 0; line < nlines; line++) { | ||||
1407 | size_t offset, length; | ||||
1408 | const char *text; | ||||
1409 | |||||
1410 | text = textplain_get_line(c, line, &offset, &length); | ||||
1411 | if (text) { | ||||
1412 | while (length > 0) { | ||||
1413 | unsigned match_length; | ||||
1414 | size_t start_idx; | ||||
1415 | const char *new_text; | ||||
1416 | const char *pos; | ||||
1417 | |||||
1418 | pos = content_textsearch_find_pattern( | ||||
1419 | text, | ||||
1420 | length, | ||||
1421 | pattern, | ||||
1422 | p_len, | ||||
1423 | case_sens, | ||||
1424 | &match_length); | ||||
1425 | if (!pos) | ||||
1426 | break; | ||||
1427 | |||||
1428 | /* found string in line => add to list */ | ||||
1429 | start_idx = offset + (pos - text); | ||||
1430 | res = content_textsearch_add_match(context, | ||||
1431 | start_idx, | ||||
1432 | start_idx + match_length, | ||||
1433 | NULL((void*)0), | ||||
1434 | NULL((void*)0)); | ||||
1435 | if (res != NSERROR_OK) { | ||||
1436 | return res; | ||||
1437 | } | ||||
1438 | |||||
1439 | new_text = pos + match_length; | ||||
1440 | offset += (new_text - text); | ||||
1441 | length -= (new_text - text); | ||||
1442 | text = new_text; | ||||
1443 | } | ||||
1444 | } | ||||
1445 | } | ||||
1446 | |||||
1447 | return res; | ||||
1448 | } | ||||
1449 | |||||
1450 | |||||
1451 | /** | ||||
1452 | * Given a range of byte offsets within a UTF8 textplain content, | ||||
1453 | * return a box that fully encloses the text | ||||
1454 | * | ||||
1455 | * \param[in] c content of type CONTENT_TEXTPLAIN | ||||
1456 | * \param[in] start byte offset of start of text range | ||||
1457 | * \param[in] end byte offset of end | ||||
1458 | * \param[out] r rectangle to be completed | ||||
1459 | */ | ||||
1460 | static void | ||||
1461 | textplain_coords_from_range(struct content *c, | ||||
1462 | unsigned start, | ||||
1463 | unsigned end, | ||||
1464 | struct rect *r) | ||||
1465 | { | ||||
1466 | textplain_content *text = (textplain_content *) c; | ||||
1467 | float line_height = textplain_line_height(); | ||||
1468 | char *utf8_data; | ||||
1469 | struct textplain_line *line; | ||||
1470 | unsigned lineno = 0; | ||||
1471 | unsigned nlines; | ||||
1472 | |||||
1473 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/handlers/text/textplain.c", 1473, __extension__ __PRETTY_FUNCTION__ )); | ||||
1474 | assert(start <= end)((start <= end) ? (void) (0) : __assert_fail ("start <= end" , "content/handlers/text/textplain.c", 1474, __extension__ __PRETTY_FUNCTION__ )); | ||||
1475 | assert(end <= text->utf8_data_size)((end <= text->utf8_data_size) ? (void) (0) : __assert_fail ("end <= text->utf8_data_size", "content/handlers/text/textplain.c" , 1475, __extension__ __PRETTY_FUNCTION__)); | ||||
1476 | |||||
1477 | utf8_data = text->utf8_data; | ||||
1478 | nlines = text->physical_line_count; | ||||
1479 | line = text->physical_line; | ||||
1480 | |||||
1481 | /* find start */ | ||||
1482 | lineno = textplain_find_line(c, start); | ||||
1483 | |||||
1484 | r->y0 = (int)(MARGIN4 + lineno * line_height); | ||||
1485 | |||||
1486 | if (lineno + 1 <= nlines || line[lineno + 1].start >= end) { | ||||
1487 | /* \todo - it may actually be more efficient just to | ||||
1488 | * run forwards most of the time | ||||
1489 | */ | ||||
1490 | |||||
1491 | /* find end */ | ||||
1492 | lineno = textplain_find_line(c, end); | ||||
1493 | |||||
1494 | r->x0 = 0; | ||||
1495 | r->x1 = text->formatted_width; | ||||
1496 | } else { | ||||
1497 | /* single line */ | ||||
1498 | const char *text = utf8_data + line[lineno].start; | ||||
1499 | |||||
1500 | r->x0 = textplain_coord_from_offset(text, | ||||
1501 | start - line[lineno].start, | ||||
1502 | line[lineno].length); | ||||
1503 | |||||
1504 | r->x1 = textplain_coord_from_offset(text, | ||||
1505 | end - line[lineno].start, | ||||
1506 | line[lineno].length); | ||||
1507 | } | ||||
1508 | |||||
1509 | r->y1 = (int)(MARGIN4 + (lineno + 1) * line_height); | ||||
1510 | } | ||||
1511 | |||||
1512 | |||||
1513 | /** | ||||
1514 | * Return a pointer to the raw UTF-8 data, as opposed to the reformatted | ||||
1515 | * text to fit the window width. Thus only hard newlines are preserved | ||||
1516 | * in the saved/copied text of a selection. | ||||
1517 | * | ||||
1518 | * \param[in] c content of type CONTENT_TEXTPLAIN | ||||
1519 | * \param[in] start starting byte offset within UTF-8 text | ||||
1520 | * \param[in] end ending byte offset | ||||
1521 | * \param[out] plen receives validated length | ||||
1522 | * \return pointer to text, or NULL if no text | ||||
1523 | */ | ||||
1524 | static char * | ||||
1525 | textplain_get_raw_data(struct content *c, | ||||
1526 | unsigned start, | ||||
1527 | unsigned end, | ||||
1528 | size_t *plen) | ||||
1529 | { | ||||
1530 | textplain_content *text = (textplain_content *) c; | ||||
1531 | size_t utf8_size; | ||||
1532 | |||||
1533 | assert(c != NULL)((c != ((void*)0)) ? (void) (0) : __assert_fail ("c != NULL", "content/handlers/text/textplain.c", 1533, __extension__ __PRETTY_FUNCTION__ )); | ||||
1534 | |||||
1535 | utf8_size = text->utf8_data_size; | ||||
1536 | |||||
1537 | /* any text at all? */ | ||||
1538 | if (!utf8_size) return NULL((void*)0); | ||||
1539 | |||||
1540 | /* clamp to valid offset range */ | ||||
1541 | if (start >= utf8_size) start = utf8_size; | ||||
1542 | if (end >= utf8_size) end = utf8_size; | ||||
1543 | |||||
1544 | *plen = end - start; | ||||
1545 | |||||
1546 | return text->utf8_data + start; | ||||
1547 | } | ||||
1548 | |||||
1549 | |||||
1550 | /** | ||||
1551 | * get bounds of a free text search match | ||||
1552 | */ | ||||
1553 | static nserror | ||||
1554 | textplain_textsearch_bounds(struct content *c, | ||||
1555 | unsigned start_idx, | ||||
1556 | unsigned end_idx, | ||||
1557 | struct box *start_box, | ||||
1558 | struct box *end_box, | ||||
1559 | struct rect *bounds) | ||||
1560 | { | ||||
1561 | textplain_coords_from_range(c, start_idx, end_idx, bounds); | ||||
1562 | |||||
1563 | return NSERROR_OK; | ||||
1564 | } | ||||
1565 | |||||
1566 | |||||
1567 | /** | ||||
1568 | * invalidate a region based on offsets into the text cauing a redraw | ||||
1569 | */ | ||||
1570 | static nserror | ||||
1571 | textplain_textselection_redraw(struct content *c, | ||||
1572 | unsigned start_idx, | ||||
1573 | unsigned end_idx) | ||||
1574 | { | ||||
1575 | struct rect r; | ||||
1576 | |||||
1577 | if (end_idx <= start_idx) { | ||||
1578 | return NSERROR_BAD_PARAMETER; | ||||
1579 | } | ||||
1580 | |||||
1581 | textplain_coords_from_range(c, start_idx, end_idx, &r); | ||||
1582 | |||||
1583 | content__request_redraw(c, r.x0, r.y0, r.x1 - r.x0, r.y1 - r.y0); | ||||
1584 | |||||
1585 | return NSERROR_OK; | ||||
1586 | } | ||||
1587 | |||||
1588 | static nserror | ||||
1589 | textplain_textselection_copy(struct content *c, | ||||
1590 | unsigned start_idx, | ||||
1591 | unsigned end_idx, | ||||
1592 | struct selection_string *selstr) | ||||
1593 | { | ||||
1594 | const char *text; | ||||
1595 | size_t length; | ||||
1596 | bool_Bool res = false0; | ||||
1597 | |||||
1598 | text = textplain_get_raw_data(c, start_idx, end_idx, &length); | ||||
1599 | if (text != NULL((void*)0)) { | ||||
1600 | res = selection_string_append(text, length, false0, NULL((void*)0), selstr); | ||||
1601 | } | ||||
1602 | if (res == false0) { | ||||
1603 | return NSERROR_NOMEM; | ||||
1604 | } | ||||
1605 | return NSERROR_OK; | ||||
1606 | } | ||||
1607 | |||||
1608 | |||||
1609 | /** | ||||
1610 | * Retrieve the index of the end of the text | ||||
1611 | * | ||||
1612 | * \param[in] c Content to retrieve size of | ||||
1613 | * \return Size, in bytes, of data | ||||
1614 | */ | ||||
1615 | static nserror | ||||
1616 | textplain_textselection_get_end(struct content *c, unsigned *end_idx) | ||||
1617 | { | ||||
1618 | textplain_content *text = (textplain_content *)c; | ||||
1619 | |||||
1620 | *end_idx = text->utf8_data_size; | ||||
1621 | return NSERROR_OK; | ||||
1622 | } | ||||
1623 | |||||
1624 | |||||
1625 | /** | ||||
1626 | * plain text content handler table | ||||
1627 | */ | ||||
1628 | static const content_handler textplain_content_handler = { | ||||
1629 | .fini = textplain_fini, | ||||
1630 | .create = textplain_create, | ||||
1631 | .process_data = textplain_process_data, | ||||
1632 | .data_complete = textplain_convert, | ||||
1633 | .reformat = textplain_reformat, | ||||
1634 | .destroy = textplain_destroy, | ||||
1635 | .mouse_track = textplain_mouse_track, | ||||
1636 | .mouse_action = textplain_mouse_action, | ||||
1637 | .keypress = textplain_keypress, | ||||
1638 | .redraw = textplain_redraw, | ||||
1639 | .open = textplain_open, | ||||
1640 | .close = textplain_close, | ||||
1641 | .get_selection = textplain_get_selection, | ||||
1642 | .clone = textplain_clone, | ||||
1643 | .type = textplain_content_type, | ||||
1644 | .textsearch_find = textplain_textsearch_find, | ||||
1645 | .textsearch_bounds = textplain_textsearch_bounds, | ||||
1646 | .textselection_redraw = textplain_textselection_redraw, | ||||
1647 | .textselection_copy = textplain_textselection_copy, | ||||
1648 | .textselection_get_end = textplain_textselection_get_end, | ||||
1649 | .no_share = true1, | ||||
1650 | }; | ||||
1651 | |||||
1652 | |||||
1653 | /* exported interface documented in html/textplain.h */ | ||||
1654 | nserror textplain_init(void) | ||||
1655 | { | ||||
1656 | lwc_error lerror; | ||||
1657 | nserror error; | ||||
1658 | |||||
1659 | lerror = lwc_intern_string("Windows-1252", | ||||
1660 | SLEN("Windows-1252")(sizeof(("Windows-1252")) - 1), | ||||
1661 | &textplain_default_charset); | ||||
1662 | if (lerror != lwc_error_ok) { | ||||
1663 | return NSERROR_NOMEM; | ||||
1664 | } | ||||
1665 | |||||
1666 | error = content_factory_register_handler("text/plain", | ||||
1667 | &textplain_content_handler); | ||||
1668 | if (error != NSERROR_OK) { | ||||
1669 | lwc_string_unref(textplain_default_charset){ lwc_string *__lwc_s = (textplain_default_charset); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL" , "content/handlers/text/textplain.c", 1669, __extension__ __PRETTY_FUNCTION__ )); __lwc_s->refcnt--; if ((__lwc_s->refcnt == 0) || (( __lwc_s->refcnt == 1) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy(__lwc_s); }; | ||||
1670 | } | ||||
1671 | |||||
1672 | error = content_factory_register_handler("application/json", | ||||
1673 | &textplain_content_handler); | ||||
1674 | if (error != NSERROR_OK) { | ||||
1675 | lwc_string_unref(textplain_default_charset){ lwc_string *__lwc_s = (textplain_default_charset); ((__lwc_s != ((void*)0)) ? (void) (0) : __assert_fail ("__lwc_s != NULL" , "content/handlers/text/textplain.c", 1675, __extension__ __PRETTY_FUNCTION__ )); __lwc_s->refcnt--; if ((__lwc_s->refcnt == 0) || (( __lwc_s->refcnt == 1) && (__lwc_s->insensitive == __lwc_s))) lwc_string_destroy(__lwc_s); }; | ||||
1676 | } | ||||
1677 | |||||
1678 | return error; | ||||
1679 | } | ||||
1680 | |||||
1681 | |||||
1682 | |||||
1683 |
1 | /* |
2 | * This file is part of LibParserUtils. |
3 | * Licensed under the MIT License, |
4 | * http://www.opensource.org/licenses/mit-license.php |
5 | * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org> |
6 | */ |
7 | |
8 | #ifndef parserutils_input_inputstream_h_ |
9 | #define parserutils_input_inputstream_h_ |
10 | |
11 | #ifdef __cplusplus |
12 | extern "C" |
13 | { |
14 | #endif |
15 | |
16 | #include <stdbool.h> |
17 | #ifndef NDEBUG |
18 | #include <stdio.h> |
19 | #endif |
20 | #include <stdlib.h> |
21 | #include <inttypes.h> |
22 | |
23 | #include <parserutils/errors.h> |
24 | #include <parserutils/functypes.h> |
25 | #include <parserutils/types.h> |
26 | #include <parserutils/charset/utf8.h> |
27 | #include <parserutils/utils/buffer.h> |
28 | |
29 | /** |
30 | * Type of charset detection function |
31 | */ |
32 | typedef parserutils_error (*parserutils_charset_detect_func)( |
33 | const uint8_t *data, size_t len, |
34 | uint16_t *mibenum, uint32_t *source); |
35 | |
36 | /** |
37 | * Input stream object |
38 | */ |
39 | typedef struct parserutils_inputstream |
40 | { |
41 | parserutils_buffer *utf8; /**< Buffer containing UTF-8 data */ |
42 | |
43 | uint32_t cursor; /**< Byte offset of current position */ |
44 | |
45 | bool_Bool had_eof; /**< Whether EOF has been reached */ |
46 | } parserutils_inputstream; |
47 | |
48 | /* Create an input stream */ |
49 | parserutils_error parserutils_inputstream_create(const char *enc, |
50 | uint32_t encsrc, parserutils_charset_detect_func csdetect, |
51 | parserutils_inputstream **stream); |
52 | /* Destroy an input stream */ |
53 | parserutils_error parserutils_inputstream_destroy( |
54 | parserutils_inputstream *stream); |
55 | |
56 | /* Append data to an input stream */ |
57 | parserutils_error parserutils_inputstream_append( |
58 | parserutils_inputstream *stream, |
59 | const uint8_t *data, size_t len); |
60 | /* Insert data into stream at current location */ |
61 | parserutils_error parserutils_inputstream_insert( |
62 | parserutils_inputstream *stream, |
63 | const uint8_t *data, size_t len); |
64 | |
65 | /* Slow form of css_inputstream_peek. */ |
66 | parserutils_error parserutils_inputstream_peek_slow( |
67 | parserutils_inputstream *stream, |
68 | size_t offset, const uint8_t **ptr, size_t *length); |
69 | |
70 | /** |
71 | * Look at the character in the stream that starts at |
72 | * offset bytes from the cursor |
73 | * |
74 | * \param stream Stream to look in |
75 | * \param offset Byte offset of start of character |
76 | * \param ptr Pointer to location to receive pointer to character data |
77 | * \param length Pointer to location to receive character length (in bytes) |
78 | * \return PARSERUTILS_OK on success, |
79 | * _NEEDDATA on reaching the end of available input, |
80 | * _EOF on reaching the end of all input, |
81 | * _BADENCODING if the input cannot be decoded, |
82 | * _NOMEM on memory exhaustion, |
83 | * _BADPARM if bad parameters are passed. |
84 | * |
85 | * Once the character pointed to by the result of this call has been advanced |
86 | * past (i.e. parserutils_inputstream_advance has caused the stream cursor to |
87 | * pass over the character), then no guarantee is made as to the validity of |
88 | * the data pointed to. Thus, any attempt to dereference the pointer after |
89 | * advancing past the data it points to is a bug. |
90 | */ |
91 | static inline parserutils_error parserutils_inputstream_peek( |
92 | parserutils_inputstream *stream, size_t offset, |
93 | const uint8_t **ptr, size_t *length) |
94 | { |
95 | parserutils_error error = PARSERUTILS_OK; |
96 | const parserutils_buffer *utf8; |
97 | const uint8_t *utf8_data; |
98 | size_t len, off, utf8_len; |
99 | |
100 | if (stream == NULL((void*)0) || ptr == NULL((void*)0) || length == NULL((void*)0)) |
101 | return PARSERUTILS_BADPARM; |
102 | |
103 | #ifndef NDEBUG |
104 | #ifdef VERBOSE_INPUTSTREAM |
105 | fprintf(stdoutstdout, "Peek: len: %zu cur: %u off: %zu\n", |
106 | stream->utf8->length, stream->cursor, offset); |
107 | #endif |
108 | #ifdef RANDOMISE_INPUTSTREAM |
109 | parserutils_buffer_randomise(stream->utf8); |
110 | #endif |
111 | #endif |
112 | |
113 | utf8 = stream->utf8; |
114 | utf8_data = utf8->data; |
115 | utf8_len = utf8->length; |
116 | off = stream->cursor + offset; |
117 | |
118 | #define IS_ASCII(x) (((x) & 0x80) == 0) |
119 | |
120 | if (off < utf8_len) { |
121 | if (IS_ASCII(utf8_data[off])) { |
122 | /* Early exit for ASCII case */ |
123 | (*length) = 1; |
124 | (*ptr) = (utf8_data + off); |
125 | return PARSERUTILS_OK; |
126 | } else { |
127 | error = parserutils_charset_utf8_char_byte_length( |
128 | utf8_data + off, &len); |
129 | |
130 | if (error == PARSERUTILS_OK) { |
131 | (*length) = len; |
132 | (*ptr) = (utf8_data + off); |
133 | return PARSERUTILS_OK; |
134 | } else if (error != PARSERUTILS_NEEDDATA) { |
135 | return error; |
136 | } |
137 | } |
138 | } |
139 | |
140 | #undef IS_ASCII |
141 | |
142 | return parserutils_inputstream_peek_slow(stream, offset, ptr, length); |
143 | } |
144 | |
145 | /** |
146 | * Advance the stream's current position |
147 | * |
148 | * \param stream The stream whose position to advance |
149 | * \param bytes The number of bytes to advance |
150 | */ |
151 | static inline void parserutils_inputstream_advance( |
152 | parserutils_inputstream *stream, size_t bytes) |
153 | { |
154 | if (stream == NULL((void*)0)) |
155 | return; |
156 | |
157 | #if !defined(NDEBUG) && defined(VERBOSE_INPUTSTREAM) |
158 | fprintf(stdoutstdout, "Advance: len: %zu cur: %u bytes: %zu\n", |
159 | stream->utf8->length, stream->cursor, bytes); |
160 | #endif |
161 | |
162 | if (bytes > stream->utf8->length - stream->cursor) |
163 | bytes = stream->utf8->length - stream->cursor; |
164 | |
165 | if (stream->cursor == stream->utf8->length) |
166 | return; |
167 | |
168 | stream->cursor += bytes; |
169 | } |
170 | |
171 | /* Read the document charset */ |
172 | const char *parserutils_inputstream_read_charset( |
173 | parserutils_inputstream *stream, uint32_t *source); |
174 | /* Change the document charset */ |
175 | parserutils_error parserutils_inputstream_change_charset( |
176 | parserutils_inputstream *stream, |
177 | const char *enc, uint32_t source); |
178 | |
179 | #ifdef __cplusplus |
180 | } |
181 | #endif |
182 | |
183 | #endif |
184 |