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
32struct conversion_context {
33	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
48class 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
76void
77conversion_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
90static size_t
91write_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
117static size_t
118write_text(conversion_context &context, const char *text,
119	BDataIO *target = NULL)
120{
121	return write_text(context, text, strlen(text), target);
122}
123
124
125static size_t
126next_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
144static size_t
145write_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
174static size_t
175process_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
243static void
244set_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
260static bool
261text_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
275static text_run *
276copy_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
298void
299dump_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
314TextOutput::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
331TextOutput::~TextOutput()
332{
333	delete fApplication;
334}
335
336
337size_t
338TextOutput::Length() const
339{
340	return (size_t)fOffset;
341}
342
343
344void *
345TextOutput::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
376void
377TextOutput::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
391void
392TextOutput::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
414void
415TextOutput::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
442void
443TextOutput::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
511void
512TextOutput::Text(RTF::Text *text)
513{
514	fOffset += write_text(fContext, text->String(), text->Length(), fTarget);
515}
516
517
518//	#pragma mark -
519
520
521status_t
522convert_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
619status_t
620convert_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
656struct color_compare
657{
658	bool operator()(const rgb_color& left, const rgb_color& right) const
659	{
660		return (*(const uint32 *)&left) < (*(const uint32 *)&right);
661	}
662};
663
664status_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
821status_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