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 { 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 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 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 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 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 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 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 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 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 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 * 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 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 331 TextOutput::~TextOutput() 332 { 333 delete fApplication; 334 } 335 336 337 size_t 338 TextOutput::Length() const 339 { 340 return (size_t)fOffset; 341 } 342 343 344 void * 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 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 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 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 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 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 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 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 { 658 bool operator()(const rgb_color& left, const rgb_color& right) const 659 { 660 return (*(const uint32 *)&left) < (*(const uint32 *)&right); 661 } 662 }; 663 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 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