diff --git a/src/add-ons/translators/rtftranslator/convert.cpp b/src/add-ons/translators/rtftranslator/convert.cpp index a996e08515..8982f9da6c 100644 --- a/src/add-ons/translators/rtftranslator/convert.cpp +++ b/src/add-ons/translators/rtftranslator/convert.cpp @@ -5,6 +5,7 @@ #include "convert.h" +#include "Stack.h" #include @@ -16,9 +17,30 @@ #include #include +#include #include +struct conversion_context { + conversion_context() + { + Reset(); + } + + void Reset(); + + int32 section; + int32 page; + int32 start_page; + int32 first_line_indent; + bool new_line; +}; + +struct group_context { + RTF::Group *group; + text_run *run; +}; + class AppServerConnection { public: AppServerConnection(); @@ -29,6 +51,20 @@ class AppServerConnection { }; +void +conversion_context::Reset() +{ + section = 1; + page = 1; + start_page = page; + first_line_indent = 0; + new_line = true; +} + + +// #pragma mark - + + AppServerConnection::AppServerConnection() : fApplication(NULL) @@ -52,25 +88,129 @@ AppServerConnection::~AppServerConnection() static size_t -get_text_for_command(RTF::Command *command, char *text, size_t size) +write_text(conversion_context &context, const char *text, size_t length, + BDataIO *target = NULL) throw (status_t) +{ + size_t prefix = 0; + if (context.new_line) { + prefix = context.first_line_indent; + context.new_line = false; + } + + if (target == NULL) + return prefix + length; + + for (uint32 i = 0; i < prefix; i++) { + write_text(context, " ", 1, target); + } + + ssize_t written = target->Write(text, length); + if (written < B_OK) + throw (status_t)written; + else if ((size_t)written != length) + throw (status_t)B_IO_ERROR; + + return prefix + length; +} + + +static size_t +write_text(conversion_context &context, const char *text, + BDataIO *target = NULL) throw (status_t) +{ + return write_text(context, text, strlen(text), target); +} + + +static size_t +next_line(conversion_context &context, const char *prefix, + BDataIO *target) throw (status_t) +{ + size_t length = strlen(prefix); + context.new_line = true; + + if (target != NULL) { + ssize_t written = target->Write(prefix, length); + if (written < B_OK) + throw (status_t)written; + else if ((size_t)written != length) + throw (status_t)B_IO_ERROR; + } + + return length; +} + + +static size_t +process_command(conversion_context &context, RTF::Command *command, + BDataIO *target) throw (status_t) { const char *name = command->Name(); - if (!strcmp(name, "\n") - || !strcmp(name, "par") - || !strcmp(name, "sect")) { - if (text != NULL) - strlcpy(text, "\n", size); - return 1; + if (!strcmp(name, "par")) { + // paragraph ended + return next_line(context, "\n", target); + } + if (!strcmp(name, "sect")) { + // section ended + context.section++; + return next_line(context, "\n", target); + } + if (!strcmp(name, "page")) { + // we just insert two carriage returns for a page break + context.page++; + return next_line(context, "\n\n", target); + } + if (!strcmp(name, "tab")) { + return write_text(context, "\t", target); + } + if (!strcmp(name, "pard")) { + // reset paragraph + context.first_line_indent = 0; + return 0; + } + if (!strcmp(name, "fi") || !strcmp(name, "cufi")) { + // "cufi" first line indent in 1/100 space steps + // "fi" is most probably specified in 1/20 pts + // Currently, we don't differentiate between the two... + context.first_line_indent = (command->Option() + 50) / 100; + if (context.first_line_indent < 0) + context.first_line_indent = 0; + if (context.first_line_indent > 8) + context.first_line_indent = 8; + + return 0; } + // document variables + + if (!strcmp(name, "sectnum")) { + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%ld", context.section); + return write_text(context, buffer, target); + } + if (!strcmp(name, "pgnstarts")) { + context.start_page = command->HasOption() ? command->Option() : 1; + return 0; + } + if (!strcmp(name, "pgnrestart")) { + context.page = context.start_page; + return 0; + } + if (!strcmp(name, "chpgn")) { + char buffer[64]; + snprintf(buffer, sizeof(buffer), "%ld", context.page); + return write_text(context, buffer, target); + } return 0; } static text_run * -get_style(BList &runs, text_run *run, int32 offset) +get_style(BList &runs, text_run *run, int32 offset) throw (status_t) { + static const rgb_color kBlack = {0, 0, 0, 255}; + if (run != NULL && offset == run->offset) return run; @@ -79,29 +219,82 @@ get_style(BList &runs, text_run *run, int32 offset) throw (status_t)B_NO_MEMORY; // take over previous styles - if (run != NULL) - *newRun = *run; + if (run != NULL) { + newRun->font = run->font; + newRun->color = run->color; + } else + newRun->color = kBlack; newRun->offset = offset; + runs.AddItem(newRun); return newRun; } +static void +set_font_face(BFont &font, uint16 face, bool on) +{ + // Special handling for B_REGULAR_FACE, since BFont::SetFace(0) + // just doesn't do anything + + if (font.Face() == B_REGULAR_FACE && on) + font.SetFace(face); + else if ((font.Face() & ~face) == 0 && !on) + font.SetFace(B_REGULAR_FACE); + else if (on) + font.SetFace(font.Face() | face); + else + font.SetFace(font.Face() & ~face); +} + + static text_run_array * -get_text_run_array(RTF::Header &header) +get_text_run_array(RTF::Header &header) throw (status_t) { // collect styles RTF::Iterator iterator(header, RTF::TEXT_DESTINATION); + RTF::Group *lastParent = &header; + Stack stack; + conversion_context context; text_run *current = NULL; int32 offset = 0; BList runs; while (iterator.HasNext()) { RTF::Element *element = iterator.Next(); + + if (RTF::Group *group = dynamic_cast(element)) { + // add new group context + group_context newContext = {lastParent, current}; + stack.Push(newContext); + + lastParent = group; + continue; + } + + if (element->Parent() != lastParent) { + // a group has been closed + + lastParent = element->Parent(); + + group_context lastContext; + while (stack.Pop(&lastContext)) { + // find current group context + if (lastContext.group == lastParent) + break; + } + + if (lastContext.run != NULL && current != NULL) { + // has the style been changed? + if (lastContext.run->offset != current->offset) + current = get_style(runs, lastContext.run, offset); + } + } + if (RTF::Text *text = dynamic_cast(element)) { - offset += text->Length(); + offset += write_text(context, text->String(), text->Length()); continue; } @@ -109,20 +302,63 @@ get_text_run_array(RTF::Header &header) if (command == NULL) continue; - if (!strcmp(command->Name(), "cf")) { + const char *name = command->Name(); + + if (!strcmp(name, "cf")) { // foreground color current = get_style(runs, current, offset); current->color = header.Color(command->Option()); - } else if (!strcmp(command->Name(), "b")) { + } else if (!strcmp(name, "b") + || !strcmp(name, "embo") || !strcmp(name, "impr")) { + // bold style ("emboss" and "engrave" are currently the same, too) + current = get_style(runs, current, offset); + set_font_face(current->font, B_BOLD_FACE, command->Option() != 0); + } else if (!strcmp(name, "i")) { // bold style current = get_style(runs, current, offset); + set_font_face(current->font, B_ITALIC_FACE, command->Option() != 0); + } else if (!strcmp(name, "ul")) { + // bold style + current = get_style(runs, current, offset); + set_font_face(current->font, B_UNDERSCORE_FACE, command->Option() != 0); + } else if (!strcmp(name, "fs")) { + // font size in half points + current = get_style(runs, current, offset); + current->font.SetSize(command->Option() / 2.0); + } else if (!strcmp(name, "plain")) { + // reset font to plain style + current = get_style(runs, current, offset); + current->font = be_plain_font; + } else if (!strcmp(name, "f")) { + // font number + RTF::Group *fonts = header.FindGroup("fonttbl"); + if (fonts == NULL) + continue; - if (command->Option() != 0) - current->font.SetFace(B_BOLD_FACE); - else - current->font.SetFace(B_REGULAR_FACE); + current = get_style(runs, current, offset); + BFont font; + // missing font info will be replaced by the default font + + RTF::Command *info; + for (int32 index = 0; (info = fonts->FindDefinition("f", index)) != NULL; index++) { + if (info->Option() != command->Option()) + continue; + + // ToDo: really try to choose font by name and serif/sans-serif + // ToDo: the font list should be built before once + + // For now, it only differentiates fixed fonts from proportional ones + if (fonts->FindDefinition("fmodern", index) != NULL) + font = be_fixed_font; + } + + font_family family; + font_style style; + font.GetFamilyAndStyle(&family, &style); + + current->font.SetFamilyAndFace(family, current->font.Face()); } else - offset += get_text_for_command(command, NULL, 0); + offset += process_command(context, command, NULL); } // are there any styles? @@ -152,30 +388,20 @@ status_t write_plain_text(RTF::Header &header, BDataIO &target) { RTF::Iterator iterator(header, RTF::TEXT_DESTINATION); + conversion_context context; - while (iterator.HasNext()) { - RTF::Element *element = iterator.Next(); - char buffer[1024]; - const char *string = NULL; - size_t size = 0; - - if (RTF::Text *text = dynamic_cast(element)) { - string = text->String(); - size = text->Length(); - } else if (RTF::Command *command = dynamic_cast(element)) { - size = get_text_for_command(command, buffer, sizeof(buffer)); - if (size != 0) - string = buffer; + try { + while (iterator.HasNext()) { + RTF::Element *element = iterator.Next(); + + if (RTF::Text *text = dynamic_cast(element)) { + write_text(context, text->String(), text->Length(), &target); + } else if (RTF::Command *command = dynamic_cast(element)) { + process_command(context, command, &target); + } } - - if (size == 0) - continue; - - ssize_t written = target.Write(string, size); - if (written < B_OK) - return written; - if ((size_t)written != size) - return B_IO_ERROR; + } catch (status_t status) { + return status; } return B_OK; @@ -193,13 +419,19 @@ convert_to_stxt(RTF::Header &header, BDataIO &target) size_t textSize = 0; RTF::Iterator iterator(header, RTF::TEXT_DESTINATION); - while (iterator.HasNext()) { - RTF::Element *element = iterator.Next(); - if (RTF::Text *text = dynamic_cast(element)) { - textSize += text->Length(); - } else if (RTF::Command *command = dynamic_cast(element)) { - textSize += get_text_for_command(command, NULL, 0); + conversion_context context; + + try { + while (iterator.HasNext()) { + RTF::Element *element = iterator.Next(); + if (RTF::Text *text = dynamic_cast(element)) { + textSize += write_text(context, text->String(), text->Length(), NULL); + } else if (RTF::Command *command = dynamic_cast(element)) { + textSize += process_command(context, command, NULL); + } } + } catch (status_t status) { + return status; } // put out header