xref: /haiku/src/add-ons/translators/rtf/convert.cpp (revision 268f99dd7dc4bd7474a8bd2742d3f1ec1de6752a)
1 /*
2  * Copyright 2004-2009, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "convert.h"
8 
9 #include <algorithm>
10 #include <set>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 
15 #include <Application.h>
16 #include <ByteOrder.h>
17 #include <File.h>
18 #include <Font.h>
19 #include <fs_attr.h>
20 #include <TextView.h>
21 #include <TranslatorFormats.h>
22 #include <TypeConstants.h>
23 
24 #include <AutoDeleter.h>
25 
26 #include "Stack.h"
27 
28 
29 #define READ_BUFFER_SIZE 2048
30 
31 
32 struct conversion_context {
conversion_contextconversion_context33 	conversion_context()
34 	{
35 		Reset();
36 	}
37 
38 	void Reset();
39 
40 	int32	section;
41 	int32	page;
42 	int32	start_page;
43 	int32	first_line_indent;
44 	bool	new_line;
45 };
46 
47 
48 class TextOutput : public RTF::Worker {
49 	public:
50 		TextOutput(RTF::Header &start, BDataIO *stream, bool processRuns);
51 		~TextOutput();
52 
53 		size_t Length() const;
54 		void *FlattenedRunArray(int32 &size);
55 
56 	protected:
57 		virtual void Group(RTF::Group *group);
58 		virtual void GroupEnd(RTF::Group *group);
59 		virtual void Command(RTF::Command *command);
60 		virtual void Text(RTF::Text *text);
61 
62 	private:
63 		void PrepareTextRun(text_run *current);
64 
65 		BDataIO				*fTarget;
66 		int32				fOffset;
67 		conversion_context	fContext;
68 		Stack<text_run *>	fGroupStack;
69 		bool				fProcessRuns;
70 		BList				fRuns;
71 		text_run			*fCurrentRun;
72 		BApplication		*fApplication;
73 };
74 
75 
76 void
Reset()77 conversion_context::Reset()
78 {
79 	section = 1;
80 	page = 1;
81 	start_page = page;
82 	first_line_indent = 0;
83 	new_line = true;
84 }
85 
86 
87 //	#pragma mark -
88 
89 
90 static size_t
write_text(conversion_context & context,const char * text,size_t length,BDataIO * target=NULL)91 write_text(conversion_context &context, const char *text, size_t length,
92 	BDataIO *target = NULL)
93 {
94 	size_t prefix = 0;
95 	if (context.new_line) {
96 		prefix = context.first_line_indent;
97 		context.new_line = false;
98 	}
99 
100 	if (target == NULL)
101 		return prefix + length;
102 
103 	for (uint32 i = 0; i < prefix; i++) {
104 		write_text(context, " ", 1, target);
105 	}
106 
107 	ssize_t written = target->Write(text, length);
108 	if (written < B_OK)
109 		throw (status_t)written;
110 	else if ((size_t)written != length)
111 		throw (status_t)B_IO_ERROR;
112 
113 	return prefix + length;
114 }
115 
116 
117 static size_t
write_text(conversion_context & context,const char * text,BDataIO * target=NULL)118 write_text(conversion_context &context, const char *text,
119 	BDataIO *target = NULL)
120 {
121 	return write_text(context, text, strlen(text), target);
122 }
123 
124 
125 static size_t
next_line(conversion_context & context,const char * prefix,BDataIO * target)126 next_line(conversion_context &context, const char *prefix,
127 	BDataIO *target)
128 {
129 	size_t length = strlen(prefix);
130 	context.new_line = true;
131 
132 	if (target != NULL) {
133 		ssize_t written = target->Write(prefix, length);
134 		if (written < B_OK)
135 			throw (status_t)written;
136 		else if ((size_t)written != length)
137 			throw (status_t)B_IO_ERROR;
138 	}
139 
140 	return length;
141 }
142 
143 
144 static size_t
write_unicode_char(conversion_context & context,uint32 c,BDataIO * target)145 write_unicode_char(conversion_context &context, uint32 c,
146 	BDataIO *target)
147 {
148 	size_t length = 1;
149 	char bytes[4];
150 
151 	if (c < 0x80)
152 		bytes[0] = c;
153 	else if (c < 0x800) {
154 		bytes[0] = 0xc0 | (c >> 6);
155 		bytes[1] = 0x80 | (c & 0x3f);
156 		length = 2;
157 	} else if (c < 0x10000) {
158 		bytes[0] = 0xe0 | (c >> 12);
159 		bytes[1] = 0x80 | ((c >> 6) & 0x3f);
160 		bytes[2] = 0x80 | (c & 0x3f);
161 		length = 3;
162 	} else if (c <= 0x10ffff) {
163 		bytes[0] = 0xf0 | (c >> 18);
164 		bytes[1] = 0x80 | ((c >> 12) & 0x3f);
165 		bytes[2] = 0x80 | ((c >> 6) & 0x3f);
166 		bytes[3] = 0x80 | (c & 0x3f);
167 		length = 4;
168 	}
169 
170 	return write_text(context, bytes, length, target);
171 }
172 
173 
174 static size_t
process_command(conversion_context & context,RTF::Command * command,BDataIO * target)175 process_command(conversion_context &context, RTF::Command *command,
176 	BDataIO *target)
177 {
178 	const char *name = command->Name();
179 
180 	if (!strcmp(name, "par") || !strcmp(name, "line")) {
181 		// paragraph ended
182 		return next_line(context, "\n", target);
183 	}
184 	if (!strcmp(name, "sect")) {
185 		// section ended
186 		context.section++;
187 		return next_line(context, "\n", target);
188 	}
189 	if (!strcmp(name, "page")) {
190 		// we just insert two carriage returns for a page break
191 		context.page++;
192 		return next_line(context, "\n\n", target);
193 	}
194 	if (!strcmp(name, "tab")) {
195 		return write_text(context, "\t", target);
196 	}
197 	if (!strcmp(name, "'")) {
198 		return write_unicode_char(context, command->Option(), target);
199 	}
200 
201 	if (!strcmp(name, "pard")) {
202 		// reset paragraph
203 		context.first_line_indent = 0;
204 		return 0;
205 	}
206 	if (!strcmp(name, "fi") || !strcmp(name, "cufi")) {
207 		// "cufi" first line indent in 1/100 space steps
208 		// "fi" is most probably specified in 1/20 pts
209 		// Currently, we don't differentiate between the two...
210 		context.first_line_indent = (command->Option() + 50) / 100;
211 		if (context.first_line_indent < 0)
212 			context.first_line_indent = 0;
213 		if (context.first_line_indent > 8)
214 			context.first_line_indent = 8;
215 
216 		return 0;
217 	}
218 
219 	// document variables
220 
221 	if (!strcmp(name, "sectnum")) {
222 		char buffer[64];
223 		snprintf(buffer, sizeof(buffer), "%" B_PRId32, context.section);
224 		return write_text(context, buffer, target);
225 	}
226 	if (!strcmp(name, "pgnstarts")) {
227 		context.start_page = command->HasOption() ? command->Option() : 1;
228 		return 0;
229 	}
230 	if (!strcmp(name, "pgnrestart")) {
231 		context.page = context.start_page;
232 		return 0;
233 	}
234 	if (!strcmp(name, "chpgn")) {
235 		char buffer[64];
236 		snprintf(buffer, sizeof(buffer), "%" B_PRId32, context.page);
237 		return write_text(context, buffer, target);
238 	}
239 	return 0;
240 }
241 
242 
243 static void
set_font_face(BFont & font,uint16 face,bool on)244 set_font_face(BFont &font, uint16 face, bool on)
245 {
246 	// Special handling for B_REGULAR_FACE, since BFont::SetFace(0)
247 	// just doesn't do anything
248 
249 	if (font.Face() == B_REGULAR_FACE && on)
250 		font.SetFace(face);
251 	else if ((font.Face() & ~face) == 0 && !on)
252 		font.SetFace(B_REGULAR_FACE);
253 	else if (on)
254 		font.SetFace(font.Face() | face);
255 	else
256 		font.SetFace(font.Face() & ~face);
257 }
258 
259 
260 static bool
text_runs_are_equal(text_run * a,text_run * b)261 text_runs_are_equal(text_run *a, text_run *b)
262 {
263 	if (a == NULL && b == NULL)
264 		return true;
265 
266 	if (a == NULL || b == NULL)
267 		return false;
268 
269 	return a->offset == b->offset
270 		&& *(uint32*)&a->color == *(uint32*)&b->color
271 		&& a->font == b->font;
272 }
273 
274 
275 static text_run *
copy_text_run(text_run * run)276 copy_text_run(text_run *run)
277 {
278 	static const rgb_color kBlack = {0, 0, 0, 255};
279 
280 	text_run *newRun = new text_run();
281 	if (newRun == NULL)
282 		throw (status_t)B_NO_MEMORY;
283 
284 	if (run != NULL) {
285 		newRun->offset = run->offset;
286 		newRun->font = run->font;
287 		newRun->color = run->color;
288 	} else {
289 		newRun->offset = 0;
290 		newRun->color = kBlack;
291 	}
292 
293 	return newRun;
294 }
295 
296 
297 #if 0
298 void
299 dump_text_run(text_run *run)
300 {
301 	if (run == NULL)
302 		return;
303 
304 	printf("run: offset = %ld, color = {%d,%d,%d}, font = ",
305 		run->offset, run->color.red, run->color.green, run->color.blue);
306 	run->font.PrintToStream();
307 }
308 #endif
309 
310 
311 //	#pragma mark -
312 
313 
TextOutput(RTF::Header & start,BDataIO * stream,bool processRuns)314 TextOutput::TextOutput(RTF::Header &start, BDataIO *stream, bool processRuns)
315 	: RTF::Worker(start),
316 	fTarget(stream),
317 	fOffset(0),
318 	fProcessRuns(processRuns),
319 	fCurrentRun(NULL),
320 	fApplication(NULL)
321 {
322 	// This is not nice, but it's the only we can provide all features on command
323 	// line tools that don't create a BApplication - without a BApplication, we
324 	// could not support any text styles (colors and fonts)
325 
326 	if (processRuns && be_app == NULL)
327 		fApplication = new BApplication("application/x-vnd.Haiku-RTFTranslator");
328 }
329 
330 
~TextOutput()331 TextOutput::~TextOutput()
332 {
333 	delete fApplication;
334 }
335 
336 
337 size_t
Length() const338 TextOutput::Length() const
339 {
340 	return (size_t)fOffset;
341 }
342 
343 
344 void *
FlattenedRunArray(int32 & _size)345 TextOutput::FlattenedRunArray(int32 &_size)
346 {
347 	// are there any styles?
348 	if (fRuns.CountItems() == 0) {
349 		_size = 0;
350 		return NULL;
351 	}
352 
353 	// create array
354 
355 	text_run_array *array = (text_run_array *)malloc(sizeof(text_run_array)
356 		+ sizeof(text_run) * (fRuns.CountItems() - 1));
357 	if (array == NULL)
358 		throw (status_t)B_NO_MEMORY;
359 
360 	array->count = fRuns.CountItems();
361 
362 	for (int32 i = 0; i < array->count; i++) {
363 		text_run *run = (text_run *)fRuns.RemoveItem((int32)0);
364 		array->runs[i] = *run;
365 		delete run;
366 	}
367 
368 	void *flattenedRunArray = BTextView::FlattenRunArray(array, &_size);
369 
370 	free(array);
371 
372 	return flattenedRunArray;
373 }
374 
375 
376 void
PrepareTextRun(text_run * run)377 TextOutput::PrepareTextRun(text_run *run)
378 {
379 	if (run != NULL && fOffset == run->offset)
380 		return;
381 
382 	text_run *newRun = copy_text_run(run);
383 
384 	newRun->offset = fOffset;
385 
386 	fRuns.AddItem(newRun);
387 	fCurrentRun = newRun;
388 }
389 
390 
391 void
Group(RTF::Group * group)392 TextOutput::Group(RTF::Group *group)
393 {
394 	if (group->Destination() != RTF::TEXT_DESTINATION) {
395 		Skip();
396 		return;
397 	}
398 
399 	if (!fProcessRuns)
400 		return;
401 
402 	// We only push a copy of the run on the stack because the current
403 	// run may still be changed in the new group -- later, we'll just
404 	// see if that was the case, and either use the copied one then,
405 	// or throw it away
406 	text_run *run = NULL;
407 	if (fCurrentRun != NULL)
408 		run = copy_text_run(fCurrentRun);
409 
410 	fGroupStack.Push(run);
411 }
412 
413 
414 void
GroupEnd(RTF::Group * group)415 TextOutput::GroupEnd(RTF::Group *group)
416 {
417 	if (!fProcessRuns)
418 		return;
419 
420 	text_run *last = NULL;
421 	fGroupStack.Pop(&last);
422 
423 	// has the style been changed?
424 	if (!text_runs_are_equal(last, fCurrentRun)) {
425 		if (fCurrentRun != NULL && last != NULL
426 			&& fCurrentRun->offset == fOffset) {
427 			// replace the current one, we don't need it anymore
428 			fCurrentRun->color = last->color;
429 			fCurrentRun->font = last->font;
430 			delete last;
431 		} else if (last) {
432 			// adopt the text_run from the previous group
433 			last->offset = fOffset;
434 			fRuns.AddItem(last);
435 			fCurrentRun = last;
436 		}
437 	} else
438 		delete last;
439 }
440 
441 
442 void
Command(RTF::Command * command)443 TextOutput::Command(RTF::Command *command)
444 {
445 	if (!fProcessRuns) {
446 		fOffset += process_command(fContext, command, fTarget);
447 		return;
448 	}
449 
450 	const char *name = command->Name();
451 
452 	if (!strcmp(name, "cf")) {
453 		// foreground color
454 		PrepareTextRun(fCurrentRun);
455 		fCurrentRun->color = Start().Color(command->Option());
456 	} else if (!strcmp(name, "b")
457 		|| !strcmp(name, "embo") || !strcmp(name, "impr")) {
458 		// bold style ("emboss" and "engrave" are currently the same, too)
459 		PrepareTextRun(fCurrentRun);
460 		set_font_face(fCurrentRun->font, B_BOLD_FACE, command->Option() != 0);
461 	} else if (!strcmp(name, "i")) {
462 		// bold style
463 		PrepareTextRun(fCurrentRun);
464 		set_font_face(fCurrentRun->font, B_ITALIC_FACE, command->Option() != 0);
465 	} else if (!strcmp(name, "ul")) {
466 		// bold style
467 		PrepareTextRun(fCurrentRun);
468 		set_font_face(fCurrentRun->font, B_UNDERSCORE_FACE, command->Option() != 0);
469 	} else if (!strcmp(name, "fs")) {
470 		// font size in half points
471 		PrepareTextRun(fCurrentRun);
472 		fCurrentRun->font.SetSize(command->Option() / 2.0);
473 	} else if (!strcmp(name, "plain")) {
474 		// reset font to plain style
475 		PrepareTextRun(fCurrentRun);
476 		fCurrentRun->font = be_plain_font;
477 	} else if (!strcmp(name, "f")) {
478 		// font number
479 		RTF::Group *fonts = Start().FindGroup("fonttbl");
480 		if (fonts == NULL)
481 			return;
482 
483 		PrepareTextRun(fCurrentRun);
484 		BFont font;
485 			// missing font info will be replaced by the default font
486 
487 		RTF::Command *info;
488 		for (int32 index = 0; (info = fonts->FindDefinition("f", index))
489 			!= NULL; index++) {
490 			if (info->Option() != command->Option())
491 				continue;
492 
493 			// ToDo: really try to choose font by name and serif/sans-serif
494 			// ToDo: the font list should be built before once
495 
496 			// For now, it only differentiates fixed fonts from proportional ones
497 			if (fonts->FindDefinition("fmodern", index) != NULL)
498 				font = be_fixed_font;
499 		}
500 
501 		font_family family;
502 		font_style style;
503 		font.GetFamilyAndStyle(&family, &style);
504 
505 		fCurrentRun->font.SetFamilyAndFace(family, fCurrentRun->font.Face());
506 	} else
507 		fOffset += process_command(fContext, command, fTarget);
508 }
509 
510 
511 void
Text(RTF::Text * text)512 TextOutput::Text(RTF::Text *text)
513 {
514 	fOffset += write_text(fContext, text->String(), text->Length(), fTarget);
515 }
516 
517 
518 //	#pragma mark -
519 
520 
521 status_t
convert_to_stxt(RTF::Header & header,BDataIO & target)522 convert_to_stxt(RTF::Header &header, BDataIO &target)
523 {
524 	// count text bytes
525 
526 	size_t textSize = 0;
527 
528 	try {
529 		TextOutput counter(header, NULL, false);
530 
531 		counter.Work();
532 		textSize = counter.Length();
533 	} catch (status_t status) {
534 		return status;
535 	}
536 
537 	// put out header
538 
539 	TranslatorStyledTextStreamHeader stxtHeader;
540 	stxtHeader.header.magic = 'STXT';
541 	stxtHeader.header.header_size = sizeof(TranslatorStyledTextStreamHeader);
542 	stxtHeader.header.data_size = 0;
543 	stxtHeader.version = 100;
544 	status_t status = swap_data(B_UINT32_TYPE, &stxtHeader, sizeof(stxtHeader),
545 		B_SWAP_HOST_TO_BENDIAN);
546 	if (status != B_OK)
547 		return status;
548 
549 	ssize_t written = target.Write(&stxtHeader, sizeof(stxtHeader));
550 	if (written < B_OK)
551 		return written;
552 	if (written != sizeof(stxtHeader))
553 		return B_IO_ERROR;
554 
555 	TranslatorStyledTextTextHeader textHeader;
556 	textHeader.header.magic = 'TEXT';
557 	textHeader.header.header_size = sizeof(TranslatorStyledTextTextHeader);
558 	textHeader.header.data_size = textSize;
559 	textHeader.charset = B_UNICODE_UTF8;
560 	status = swap_data(B_UINT32_TYPE, &textHeader, sizeof(textHeader),
561 		B_SWAP_HOST_TO_BENDIAN);
562 	if (status != B_OK)
563 		return status;
564 
565 	written = target.Write(&textHeader, sizeof(textHeader));
566 	if (written < B_OK)
567 		return written;
568 	if (written != sizeof(textHeader))
569 		return B_IO_ERROR;
570 
571 	// put out main text
572 
573 	void *flattenedRuns = NULL;
574 	int32 flattenedSize = 0;
575 
576 	try {
577 		TextOutput output(header, &target, true);
578 
579 		output.Work();
580 		flattenedRuns = output.FlattenedRunArray(flattenedSize);
581 	} catch (status_t status) {
582 		return status;
583 	}
584 
585 	BPrivate::MemoryDeleter _(flattenedRuns);
586 
587 	// put out styles
588 
589 	TranslatorStyledTextStyleHeader styleHeader;
590 	styleHeader.header.magic = 'STYL';
591 	styleHeader.header.header_size = sizeof(TranslatorStyledTextStyleHeader);
592 	styleHeader.header.data_size = flattenedSize;
593 	styleHeader.apply_offset = 0;
594 	styleHeader.apply_length = textSize;
595 
596 	status = swap_data(B_UINT32_TYPE, &styleHeader, sizeof(styleHeader),
597 		B_SWAP_HOST_TO_BENDIAN);
598 	if (status != B_OK)
599 		return status;
600 
601 	written = target.Write(&styleHeader, sizeof(styleHeader));
602 	if (written < B_OK)
603 		return written;
604 	if (written != sizeof(styleHeader))
605 		return B_IO_ERROR;
606 
607 	// output actual style information
608 	written = target.Write(flattenedRuns, flattenedSize);
609 
610 	if (written < B_OK)
611 		return written;
612 	if (written != flattenedSize)
613 		return B_IO_ERROR;
614 
615 	return B_OK;
616 }
617 
618 
619 status_t
convert_to_plain_text(RTF::Header & header,BPositionIO & target)620 convert_to_plain_text(RTF::Header &header, BPositionIO &target)
621 {
622 	// put out main text
623 
624 	void *flattenedRuns = NULL;
625 	int32 flattenedSize = 0;
626 
627 	// TODO: this is not really nice, we should adopt the BPositionIO class
628 	//	from Dano/Zeta which has meta data support
629 	BFile *file = dynamic_cast<BFile *>(&target);
630 
631 	try {
632 		TextOutput output(header, &target, file != NULL);
633 
634 		output.Work();
635 		flattenedRuns = output.FlattenedRunArray(flattenedSize);
636 	} catch (status_t status) {
637 		return status;
638 	}
639 
640 	if (file == NULL) {
641 		// we can't write the styles
642 		return B_OK;
643 	}
644 
645 	// put out styles
646 
647 	ssize_t written = file->WriteAttr("styles", B_RAW_TYPE, 0, flattenedRuns,
648 		flattenedSize);
649 	if (written >= B_OK && written != flattenedSize)
650 		file->RemoveAttr("styles");
651 
652 	free(flattenedRuns);
653 	return B_OK;
654 }
655 
656 struct color_compare
657 {
operator ()color_compare658 	bool operator()(const rgb_color& left, const rgb_color& right) const
659 	{
660 		return (*(const uint32 *)&left) < (*(const uint32 *)&right);
661 	}
662 };
663 
convert_styled_text_to_rtf(BPositionIO * source,BPositionIO * target)664 status_t convert_styled_text_to_rtf(
665 	BPositionIO* source, BPositionIO* target)
666 {
667 	if (source->Seek(0, SEEK_SET) != 0)
668 		return B_ERROR;
669 
670 	const ssize_t kstxtsize = sizeof(TranslatorStyledTextStreamHeader);
671 	const ssize_t ktxtsize = sizeof(TranslatorStyledTextTextHeader);
672 	TranslatorStyledTextStreamHeader stxtheader;
673 	TranslatorStyledTextTextHeader txtheader;
674 	char buffer[READ_BUFFER_SIZE];
675 
676 	// Read STXT and TEXT headers
677 	if (source->Read(&stxtheader, kstxtsize) != kstxtsize)
678 		return B_ERROR;
679 	if (source->Read(&txtheader, ktxtsize) != ktxtsize
680 		|| swap_data(B_UINT32_TYPE, &txtheader,
681 			sizeof(TranslatorStyledTextTextHeader),
682 			B_SWAP_BENDIAN_TO_HOST) != B_OK)
683 		return B_ERROR;
684 
685 	// source now points to the beginning of the plain text section
686 	BString plainText;
687 	ssize_t nread = 0, nreed = 0, ntotalread = 0;
688 	nreed = std::min((size_t)READ_BUFFER_SIZE,
689 		(size_t)txtheader.header.data_size - ntotalread);
690 	nread = source->Read(buffer, nreed);
691 	while (nread > 0) {
692 		plainText << buffer;
693 
694 		ntotalread += nread;
695 		nreed = std::min((size_t)READ_BUFFER_SIZE,
696 			(size_t)txtheader.header.data_size - ntotalread);
697 		nread = source->Read(buffer, nreed);
698 	}
699 
700 	if ((ssize_t)txtheader.header.data_size != ntotalread)
701 		return B_NO_TRANSLATOR;
702 
703 	BString rtfFile =
704 		"{\\rtf1\\ansi";
705 
706 	ssize_t read = 0;
707 	TranslatorStyledTextStyleHeader stylHeader;
708 	read = source->Read(buffer, sizeof(stylHeader));
709 
710 	if (read < 0)
711 		return B_ERROR;
712 
713 	if (read != sizeof(stylHeader) && read != 0)
714 		return B_NO_TRANSLATOR;
715 
716 	if (read == sizeof(stylHeader)) { // There is a STYL section
717 		memcpy(&stylHeader, buffer, sizeof(stylHeader));
718 		if (swap_data(B_UINT32_TYPE, &stylHeader, sizeof(stylHeader),
719 			B_SWAP_BENDIAN_TO_HOST) != B_OK) {
720 			return B_ERROR;
721 		}
722 
723 		if (stylHeader.header.magic != 'STYL'
724 			|| stylHeader.header.header_size != sizeof(stylHeader)) {
725 			return B_NO_TRANSLATOR;
726 		}
727 
728 		uint8 unflattened[stylHeader.header.data_size];
729 		source->Read(unflattened, stylHeader.header.data_size);
730 		text_run_array *styles = BTextView::UnflattenRunArray(unflattened);
731 
732 		// RTF needs us to mention font and color names in advance so
733 		// we collect them in sets
734 		std::set<rgb_color, color_compare> colorTable;
735 		std::set<BString> fontTable;
736 
737 		font_family out;
738 		for (int i = 0; i < styles->count; i++) {
739 			colorTable.insert(styles->runs[i].color);
740 			styles->runs[i].font.GetFamilyAndStyle(&out, NULL);
741 			fontTable.insert(BString(out));
742 		}
743 
744 		// Now we write them to the file
745 		std::set<BString>::iterator it;
746 		uint32 count = 0;
747 
748 		rtfFile << "{\\fonttbl";
749 		for (it = fontTable.begin(); it != fontTable.end(); it++) {
750 			rtfFile << "{\\f" << count << " " << *it << ";}";
751 			count++;
752 		}
753 		rtfFile << "}{\\colortbl";
754 
755 		std::set<rgb_color, color_compare>::iterator cit;
756 		for (cit = colorTable.begin(); cit != colorTable.end(); cit++) {
757 			rtfFile << "\\red" << cit->red
758 				<< "\\green" << cit->green
759 				<< "\\blue" << cit->blue
760 				<< ";";
761 		}
762 		rtfFile << "}";
763 
764 		// Now we put out the actual text with styling information run by run
765 		for (int i = 0; i < styles->count; i++) {
766 			// Find font and color indices
767 			styles->runs[i].font.GetFamilyAndStyle(&out, NULL);
768 			int fontIndex = std::distance(fontTable.begin(),
769 				fontTable.find(BString(out)));
770 			int colorIndex = std::distance(colorTable.begin(),
771 				colorTable.find(styles->runs[i].color));
772 			rtfFile << "\\pard\\plain\\f" << fontIndex << "\\cf" << colorIndex;
773 
774 			// Apply various font styles
775 			uint16 fontFace = styles->runs[i].font.Face();
776 			if (fontFace & B_ITALIC_FACE)
777 				rtfFile << "\\i";
778 			if (fontFace & B_UNDERSCORE_FACE)
779 				rtfFile << "\\ul";
780 			if (fontFace & B_BOLD_FACE)
781 				rtfFile << "\\b";
782 
783 			// RTF font size unit is half-points, but BFont::Size() returns
784 			// points
785 			rtfFile << "\\fs"
786 				<< static_cast<int>(styles->runs[i].font.Size() * 2);
787 
788 			int length;
789 			if (i < styles->count - 1) {
790 				length = styles->runs[i + 1].offset - styles->runs[i].offset;
791 			} else {
792 				length = plainText.Length() - styles->runs[i].offset;
793 			}
794 
795 			BString segment;
796 			plainText.CopyInto(segment, styles->runs[i].offset, length);
797 
798 			// Escape control structures
799 			segment.CharacterEscape("\\{}", '\\');
800 			segment.ReplaceAll("\n", "\\line");
801 
802 			rtfFile << " " << segment;
803 		}
804 
805 		BTextView::FreeRunArray(styles);
806 
807 		rtfFile << "}";
808 	} else {
809 		// There is no STYL section
810 		// Just use a generic preamble
811 		rtfFile << "{\\fonttbl\\f0 Noto Sans;}\\f0\\pard " << plainText
812 			<< "}";
813 	}
814 
815 	target->Write(rtfFile.String(), rtfFile.Length());
816 
817 	return B_OK;
818 }
819 
820 
convert_plain_text_to_rtf(BPositionIO & source,BPositionIO & target)821 status_t convert_plain_text_to_rtf(
822 	BPositionIO& source, BPositionIO& target)
823 {
824 	BString rtfFile =
825 		"{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard ";
826 
827 	BFile* fileSource = (BFile*)&source;
828 	off_t size;
829 	fileSource->GetSize(&size);
830 	char* sourceBuf = (char*)malloc(size);
831 	fileSource->Read((void*)sourceBuf, size);
832 
833 	BString sourceTxt = sourceBuf;
834 	sourceTxt.CharacterEscape("\\{}", '\\');
835 	sourceTxt.ReplaceAll("\n", " \\par ");
836 	rtfFile << sourceTxt << " }";
837 
838 	BFile* fileTarget = (BFile*)&target;
839 	fileTarget->Write((const void*)rtfFile, rtfFile.Length());
840 
841 	return B_OK;
842 }
843