Extended support of RTF features. Ie. the conversion/group context is now

maintained, and should work more or less correctly.
Monospaced fonts can now be chosen (the font table is partially parsed),
italics works, and many other things.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@10567 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2005-01-03 08:38:48 +00:00
parent e815584a4f
commit 9932029846

View File

@ -5,6 +5,7 @@
#include "convert.h"
#include "Stack.h"
#include <TranslatorFormats.h>
@ -16,9 +17,30 @@
#include <Font.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
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<group_context> 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<RTF::Group *>(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<RTF::Text *>(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<RTF::Text *>(element)) {
string = text->String();
size = text->Length();
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(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<RTF::Text *>(element)) {
write_text(context, text->String(), text->Length(), &target);
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(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<RTF::Text *>(element)) {
textSize += text->Length();
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(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<RTF::Text *>(element)) {
textSize += write_text(context, text->String(), text->Length(), NULL);
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(element)) {
textSize += process_command(context, command, NULL);
}
}
} catch (status_t status) {
return status;
}
// put out header