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*)⌖
839 fileTarget->Write((const void*)rtfFile, rtfFile.Length());
840
841 return B_OK;
842 }
843