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:
parent
e815584a4f
commit
9932029846
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "convert.h"
|
#include "convert.h"
|
||||||
|
#include "Stack.h"
|
||||||
|
|
||||||
#include <TranslatorFormats.h>
|
#include <TranslatorFormats.h>
|
||||||
|
|
||||||
@ -16,9 +17,30 @@
|
|||||||
#include <Font.h>
|
#include <Font.h>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.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 {
|
class AppServerConnection {
|
||||||
public:
|
public:
|
||||||
AppServerConnection();
|
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()
|
AppServerConnection::AppServerConnection()
|
||||||
:
|
:
|
||||||
fApplication(NULL)
|
fApplication(NULL)
|
||||||
@ -52,25 +88,129 @@ AppServerConnection::~AppServerConnection()
|
|||||||
|
|
||||||
|
|
||||||
static size_t
|
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();
|
const char *name = command->Name();
|
||||||
|
|
||||||
if (!strcmp(name, "\n")
|
if (!strcmp(name, "par")) {
|
||||||
|| !strcmp(name, "par")
|
// paragraph ended
|
||||||
|| !strcmp(name, "sect")) {
|
return next_line(context, "\n", target);
|
||||||
if (text != NULL)
|
}
|
||||||
strlcpy(text, "\n", size);
|
if (!strcmp(name, "sect")) {
|
||||||
return 1;
|
// 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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static text_run *
|
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)
|
if (run != NULL && offset == run->offset)
|
||||||
return run;
|
return run;
|
||||||
|
|
||||||
@ -79,29 +219,82 @@ get_style(BList &runs, text_run *run, int32 offset)
|
|||||||
throw (status_t)B_NO_MEMORY;
|
throw (status_t)B_NO_MEMORY;
|
||||||
|
|
||||||
// take over previous styles
|
// take over previous styles
|
||||||
if (run != NULL)
|
if (run != NULL) {
|
||||||
*newRun = *run;
|
newRun->font = run->font;
|
||||||
|
newRun->color = run->color;
|
||||||
|
} else
|
||||||
|
newRun->color = kBlack;
|
||||||
|
|
||||||
newRun->offset = offset;
|
newRun->offset = offset;
|
||||||
|
|
||||||
runs.AddItem(newRun);
|
runs.AddItem(newRun);
|
||||||
return 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 *
|
static text_run_array *
|
||||||
get_text_run_array(RTF::Header &header)
|
get_text_run_array(RTF::Header &header) throw (status_t)
|
||||||
{
|
{
|
||||||
// collect styles
|
// collect styles
|
||||||
|
|
||||||
RTF::Iterator iterator(header, RTF::TEXT_DESTINATION);
|
RTF::Iterator iterator(header, RTF::TEXT_DESTINATION);
|
||||||
|
RTF::Group *lastParent = &header;
|
||||||
|
Stack<group_context> stack;
|
||||||
|
conversion_context context;
|
||||||
text_run *current = NULL;
|
text_run *current = NULL;
|
||||||
int32 offset = 0;
|
int32 offset = 0;
|
||||||
BList runs;
|
BList runs;
|
||||||
|
|
||||||
while (iterator.HasNext()) {
|
while (iterator.HasNext()) {
|
||||||
RTF::Element *element = iterator.Next();
|
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)) {
|
if (RTF::Text *text = dynamic_cast<RTF::Text *>(element)) {
|
||||||
offset += text->Length();
|
offset += write_text(context, text->String(), text->Length());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,20 +302,63 @@ get_text_run_array(RTF::Header &header)
|
|||||||
if (command == NULL)
|
if (command == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!strcmp(command->Name(), "cf")) {
|
const char *name = command->Name();
|
||||||
|
|
||||||
|
if (!strcmp(name, "cf")) {
|
||||||
// foreground color
|
// foreground color
|
||||||
current = get_style(runs, current, offset);
|
current = get_style(runs, current, offset);
|
||||||
current->color = header.Color(command->Option());
|
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
|
// bold style
|
||||||
current = get_style(runs, current, offset);
|
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 = get_style(runs, current, offset);
|
||||||
current->font.SetFace(B_BOLD_FACE);
|
BFont font;
|
||||||
else
|
// missing font info will be replaced by the default font
|
||||||
current->font.SetFace(B_REGULAR_FACE);
|
|
||||||
|
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
|
} else
|
||||||
offset += get_text_for_command(command, NULL, 0);
|
offset += process_command(context, command, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// are there any styles?
|
// are there any styles?
|
||||||
@ -152,30 +388,20 @@ status_t
|
|||||||
write_plain_text(RTF::Header &header, BDataIO &target)
|
write_plain_text(RTF::Header &header, BDataIO &target)
|
||||||
{
|
{
|
||||||
RTF::Iterator iterator(header, RTF::TEXT_DESTINATION);
|
RTF::Iterator iterator(header, RTF::TEXT_DESTINATION);
|
||||||
|
conversion_context context;
|
||||||
|
|
||||||
|
try {
|
||||||
while (iterator.HasNext()) {
|
while (iterator.HasNext()) {
|
||||||
RTF::Element *element = iterator.Next();
|
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)) {
|
if (RTF::Text *text = dynamic_cast<RTF::Text *>(element)) {
|
||||||
string = text->String();
|
write_text(context, text->String(), text->Length(), &target);
|
||||||
size = text->Length();
|
|
||||||
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(element)) {
|
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(element)) {
|
||||||
size = get_text_for_command(command, buffer, sizeof(buffer));
|
process_command(context, command, &target);
|
||||||
if (size != 0)
|
|
||||||
string = buffer;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (size == 0)
|
} catch (status_t status) {
|
||||||
continue;
|
return status;
|
||||||
|
|
||||||
ssize_t written = target.Write(string, size);
|
|
||||||
if (written < B_OK)
|
|
||||||
return written;
|
|
||||||
if ((size_t)written != size)
|
|
||||||
return B_IO_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return B_OK;
|
return B_OK;
|
||||||
@ -193,14 +419,20 @@ convert_to_stxt(RTF::Header &header, BDataIO &target)
|
|||||||
size_t textSize = 0;
|
size_t textSize = 0;
|
||||||
|
|
||||||
RTF::Iterator iterator(header, RTF::TEXT_DESTINATION);
|
RTF::Iterator iterator(header, RTF::TEXT_DESTINATION);
|
||||||
|
conversion_context context;
|
||||||
|
|
||||||
|
try {
|
||||||
while (iterator.HasNext()) {
|
while (iterator.HasNext()) {
|
||||||
RTF::Element *element = iterator.Next();
|
RTF::Element *element = iterator.Next();
|
||||||
if (RTF::Text *text = dynamic_cast<RTF::Text *>(element)) {
|
if (RTF::Text *text = dynamic_cast<RTF::Text *>(element)) {
|
||||||
textSize += text->Length();
|
textSize += write_text(context, text->String(), text->Length(), NULL);
|
||||||
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(element)) {
|
} else if (RTF::Command *command = dynamic_cast<RTF::Command *>(element)) {
|
||||||
textSize += get_text_for_command(command, NULL, 0);
|
textSize += process_command(context, command, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (status_t status) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
// put out header
|
// put out header
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user