ultimatepp/uppsrc/CtrlCore/ParseRTF.cpp

1300 lines
34 KiB
C++

#include "CtrlCore.h"
namespace Upp {
#define LLOG(x) // DLOG(x)
static int TwipDotsLim(int twips) { return minmax<int>(TwipDots(twips), 0, MAX_DOTS); }
static String FromCString(const char *p, const char **endptr = NULL)
{
if(endptr) {
const char *e = p;
if(*e == '\"')
e++;
while(*e && *e != '\"')
if(*e++ == '\\' && *e)
e++;
if(*e == '\"')
e++;
*endptr = e;
}
try {
CParser parser(p);
return parser.ReadOneString();
}
catch(Exc e) {
return Null;
}
}
class RTFParser
{
public:
RTFParser(const char *rtf);
RichText Run();
private:
enum TOKEN { T_EOF, T_TEXT, T_COMMAND, T_GROUP, T_END_GROUP };
void Flush(bool force, int itap);
void OpenTable(int level);
void FlushTable(int level);
TOKEN Fetch();
void Skip();
TOKEN Token() { if(!is_full) Fetch(); return token; }
bool PassIf(bool c) { is_full &= !c; return c; }
bool PassText() { return PassIf(Token() == T_TEXT); }
bool PassGroup() { return PassIf(Token() == T_GROUP); }
bool PassEndGroup() { return PassIf(Token() == T_END_GROUP || token == T_EOF); }
bool PassEndGroup(int level);
bool PassCmd(const char *cmd) { return PassIf(Token() == T_COMMAND && command == cmd); }
bool PassQ(const char *cmd) { return PassIf(command == cmd); }
void SkipGroup() { SkipGroup(stack.GetCount()); }
void SkipGroup(int level);
int Level() const { return stack.GetCount(); }
void ReadItem();
void ReadItemGroup(int level);
void ReadItemGroup() { ReadItemGroup(Level()); }
void ReadText();
void ReadText(const WString& text);
void ReadChar(word ch) { ReadText(WString(ch, 1)); }
void ReadCommand();
void ReadHeader();
void ReadFaceTable();
void ReadColorTable();
void ReadCharSet();
void ReadMisc();
void ReadField();
bool ReadField(const char *def);
void ReadPict();
void ReadShape();
void ReadParaStyle();
void ReadTableStyle();
void DefaultParaStyle();
void ReadCharStyle();
void ReadCellBorder(int& width);
String ReadBinHex(char& odd) const;
private:
const char *rtf;
TOKEN token;
bool is_full;
bool next_command;
WString text;
String command;
int command_arg;
struct CellInfo {
CellInfo();
RichCell::Format format;
Rect cellmarginunits;
int shading;
Color shading_fore;
Color shading_back;
int end_dots;
};
struct Cell {
Cell();
CellInfo info;
RichTxt text;
bool merge_first;
bool merge;
int nbegin;
Size span;
};
struct Face : Moveable<Face> {
int face;
byte charset;
};
struct TableState {
TableState() : textcol(0), stylecol(0) { cells.Add(); }
RichTable::Format tableformat;
Vector< Array<Cell> > cells;
int textcol;
int stylecol;
};
CellInfo& CellInfoAt(int i);
Cell& CellAt(TableState& ts, int i);
void SetCellMargin(Cell& cell, int Rect::*mbr);
struct State {
String dest;
RichPara::Format format;
RichPara::CharFormat charformat;
WithDeepCopy< Array<CellInfo> > cellinfo;
int trgaph;
Rect rowmargin;
Rect rowmarginunits;
Rect rowspacing;
Rect rowspacingunits;
Rect cellmarginunits;
int uc_value;
int left_margin;
int right_margin;
int first_indent;
bool in_table;
int itap;
bool nestprop;
bool new_dest;
byte charset;
};
Array<State> stack;
Array<TableState> table_stack;
State state;
RichPara::CharFormat plain_format;
RichPara::Format pard_format;
CellInfo std_cell_info;
byte plain_charset;
byte default_charset;
int default_font;
Alignment tab_align;
byte tab_fill;
Vector<Face> face_table;
Vector<Color> color_table;
int paper_width;
int left_margin;
int right_margin;
RichText output;
RichPara para;
};
RichText ParseRTF(const char *rtf) { return RTFParser(rtf).Run(); }
RTFParser::CellInfo::CellInfo()
: cellmarginunits(0, 0, 0, 0)
, shading(0)
, shading_fore(Black())
, shading_back(White())
, end_dots(0)
{
}
RTFParser::Cell::Cell()
: merge_first(false)
, merge(false)
, nbegin(0)
, span(0, 0)
{
}
RTFParser::RTFParser(const char *rtf)
: rtf(rtf)
{
#ifdef _DEBUG
_DBG_
SaveFile(ConfigFile("rtfparser.rtf"), rtf);
LOG(rtf);
#endif
is_full = false;
next_command = false;
default_font = 0;
plain_charset = default_charset = state.charset = CHARSET_WIN1250;
state.uc_value = 1;
state.new_dest = false;
plain_format.Face(Font::ARIAL).Height(100);
std_cell_info.format.align = ALIGN_TOP;
std_cell_info.format.margin = Rect(25, 25, 25, 25);
DefaultParaStyle();
state.charformat = plain_format;
tab_align = ALIGN_LEFT;
tab_fill = 0;
paper_width = 5100;
left_margin = right_margin = 750;
}
RichText RTFParser::Run()
{
if(!PassGroup() || !PassCmd("rtf") || command_arg != 1 && !IsNull(command_arg))
return pick(output);
while(Token() != T_EOF)
ReadItem();
Flush(false, 1);
FlushTable(0);
return pick(output);
}
void RTFParser::FlushTable(int level)
{
while(table_stack.GetCount() > level) {
TableState& child = table_stack.Top();
while(!child.cells.IsEmpty() && child.cells.Top().IsEmpty())
child.cells.Drop();
if(child.cells.IsEmpty()) {
table_stack.Drop();
continue;
}
Index<int> dot_index;
// int pos = child.tableformat.lm;
dot_index.Add(child.tableformat.lm);
for(int r = 0; r < child.cells.GetCount(); r++) {
Array<Cell>& rw = child.cells[r];
for(int c = 0; c < rw.GetCount(); c++)
dot_index.FindAdd(rw[c].info.end_dots);
}
Vector<int> dot_order = dot_index.PickKeys();
Sort(dot_order);
RichTable table;
if(table_stack.GetCount() == 1)
child.tableformat.rm = max(paper_width - left_margin - right_margin - dot_order.Top(), 0);
// child.tableformat.before = state.format.before;
// child.tableformat.after = state.format.after;
table.SetFormat(child.tableformat);
for(int c = 1; c < dot_order.GetCount(); c++)
table.AddColumn(dot_order[c] - dot_order[c - 1]);
dot_index = pick(dot_order);
int tbl_border = Null, tbl_grid = Null;
Color clr_border = Null, clr_grid = Null;
for(int r = 0; r < child.cells.GetCount(); r++) {
Array<Cell>& rw = child.cells[r];
int pos = child.tableformat.lm;
for(int c = 0; c < rw.GetCount(); c++) {
Cell& cell = rw[c];
if(cell.merge) {
pos = cell.info.end_dots;
continue;
}
cell.span.cy = 0;
if(cell.merge_first) {
for(int m = r + 1; m < child.cells.GetCount(); m++) {
const Array<Cell>& mrw = child.cells[m];
int mc = mrw.GetCount();
while(--mc >= 0 && mrw[mc].info.end_dots > cell.info.end_dots)
;
if(mc >= 0 && mrw[mc].info.end_dots == cell.info.end_dots && mrw[mc].merge)
cell.span.cy++;
else
break;
}
}
cell.nbegin = dot_index.Find(pos);
cell.span.cx = max(0, dot_index.Find(pos = cell.info.end_dots) - cell.nbegin - 1);
if(cell.span.cx < 0) {
cell.merge = true;
continue;
}
bool outer_border[] = {
cell.nbegin == 0,
r == 0,
cell.nbegin + cell.span.cx + 2 >= dot_index.GetCount(),
r + cell.span.cy + 1 >= child.cells.GetCount(),
};
int border_width[] = {
cell.info.format.border.left,
cell.info.format.border.top,
cell.info.format.border.right,
cell.info.format.border.bottom,
};
for(int b = 0; b < __countof(border_width); b++) {
int& out_wd = (outer_border[b] ? tbl_border : tbl_grid);
Color& out_co = (outer_border[b] ? clr_border : clr_grid);
if(IsNull(cell.info.format.bordercolor) || border_width[b] <= 0
|| !IsNull(out_co) && out_co != cell.info.format.bordercolor)
out_wd = 0;
else if(IsNull(out_wd) || border_width[b] < out_wd) {
out_wd = border_width[b];
out_co = cell.info.format.bordercolor;
}
}
if(cell.info.shading > 0) {
Color zero = White();
Color one = Nvl(cell.info.shading_fore, Black());
int r = zero.GetR() + iscale(one.GetR() - zero.GetR(), cell.info.shading, 10000);
int g = zero.GetG() + iscale(one.GetG() - zero.GetG(), cell.info.shading, 10000);
int b = zero.GetB() + iscale(one.GetB() - zero.GetB(), cell.info.shading, 10000);
cell.info.format.color = Color(r, g, b);
}
}
}
RichTable::Format tf = table.GetFormat();
tf.frame = Nvl(tbl_border, 0);
tf.framecolor = (tf.frame > 0 ? clr_border : Color(Null));
tf.grid = Nvl(tbl_grid, 0);
tf.gridcolor = (tf.grid > 0 ? clr_grid : Color(Null));
table.SetFormat(tf);
for(int r = 0; r < child.cells.GetCount(); r++) {
Array<Cell>& rw = child.cells[r];
// int pos = child.tableformat.lm;
for(int c = 0; c < rw.GetCount(); c++) {
Cell& cell = rw[c];
if(cell.merge)
continue;
if(cell.span.cx || cell.span.cy)
table.SetSpan(r, cell.nbegin, cell.span.cy, cell.span.cx);
bool outer_border[] = {
cell.nbegin == 0,
r == 0,
cell.nbegin + cell.span.cx + 2 >= dot_index.GetCount(),
r + cell.span.cy + 1 >= child.cells.GetCount(),
};
int *border_width[] = {
&cell.info.format.border.left,
&cell.info.format.border.top,
&cell.info.format.border.right,
&cell.info.format.border.bottom,
};
for(int b = 0; b < __countof(border_width); b++) {
int tbl_wd = (outer_border[b] ? tbl_border : tbl_grid);
// Color tbl_co = (outer_border[b] ? clr_border : clr_grid);
if(*border_width[b] <= tbl_wd)
*border_width[b] = 0;
}
table.SetFormat(r, cell.nbegin, cell.info.format);
cell.text.Normalize();
table.SetPick(r, cell.nbegin, pick(cell.text));
}
}
table.Normalize();
table_stack.Drop();
if(table_stack.IsEmpty())
output.CatPick(pick(table));
else {
TableState& par = table_stack.Top();
CellAt(par, par.textcol).text.CatPick(pick(table));
}
}
}
void RTFParser::Flush(bool force, int itap)
{
if(!para.part.IsEmpty() || force) {
int fi = state.first_indent, li = state.left_margin, ri = state.right_margin;
if(state.format.bullet != RichPara::BULLET_NONE) {
Swap(li, fi);
// li += fi;
// fi = -fi;
}
state.format.indent = minmax<int>(fi, 0, MAX_DOTS);
state.format.lm = minmax<int>(li, 0, MAX_DOTS);
state.format.rm = minmax<int>(ri, 0, MAX_DOTS);
para.format = state.format;
if(state.in_table) {
FlushTable(itap);
OpenTable(itap);
TableState& ts = table_stack[itap - 1];
CellAt(ts, ts.textcol).text.Cat(para, output.GetStyles());
}
else {
FlushTable(0);
output.Cat(para);
}
para.part.Clear();
}
else
FlushTable(itap);
}
RTFParser::TOKEN RTFParser::Fetch()
{
is_full = true;
text.Clear();
if(next_command)
{
next_command = false;
return token = T_COMMAND;
}
command = Null;
command_arg = Null;
int skip = 0;
while(*rtf && *rtf != '{' && *rtf != '}')
{
int c = 0, nskip = max(skip - 1, 0);
if((byte)*rtf < ' ')
rtf++;
else if(*rtf != '\\')
c = ToUnicode(*rtf++, state.charset);
else
switch(rtf++, *rtf++)
{
case 0: {
rtf--;
break;
}
case '{':
case '}':
case '\\': {
c = rtf[-1];
break;
}
case '~': {
c = 160;
break;
}
case '|':
case '-':
case '_':
case ':': {
command = String(rtf - 1, 1);
LLOG("Command " << command);
if(text.IsEmpty())
return token = T_COMMAND;
next_command = true;
return token = T_TEXT;
}
case '\'': {
int c1 = ctoi(*rtf);
if(c1 < 16) {
int c2 = ctoi(*++rtf);
if(c2 < 16) {
c1 = c1 * 16 + c2;
rtf++;
}
c = ToUnicode(c1, state.charset);
}
break;
}
default: {
if(IsAlpha(*--rtf) || *rtf == '*' && rtf[1] == '\\' && IsAlpha(rtf[2])) {
if(*rtf == '*') {
rtf += 2;
state.new_dest = true;
LLOG("NewDest");
}
const char *b = rtf;
while(IsAlpha(*++rtf))
;
command = String(b, rtf);
if(IsDigit(*rtf) || *rtf == '-')
command_arg = strtol(rtf, (char **)&rtf, 10);
if(*rtf == ' ')
rtf++;
if(command == "uc")
state.uc_value = command_arg;
else if(command == "u") {
c = command_arg;
nskip = state.uc_value;
}
else { // command - quit reading text
LLOG("Command " << command);
if(text.IsEmpty())
return token = T_COMMAND;
next_command = true;
return token = T_TEXT;
}
}
break;
}
}
if(c && !skip) {
text.Cat(c);
if(text.GetCount() >= 2) {
char16 h[2];
h[0] = text[text.GetCount() - 2];
h[1] = text[text.GetCount() - 1];
wchar c = ReadSurrogatePair(h, h + 2);
if(c) {
text.TrimLast(2);
text.Cat(c);
}
}
}
skip = nskip;
}
if(!text.IsEmpty()) {
LLOG("TEXT, size: " << text.GetCount());
return token = T_TEXT;
}
if(*rtf == '{') {
stack.Add(state);
rtf++;
return token = T_GROUP;
}
if(*rtf == '}') {
if(!stack.IsEmpty()) {
state = stack.Top();
stack.Drop();
}
rtf++;
return token = T_END_GROUP;
}
return token = T_EOF;
}
bool RTFParser::PassEndGroup(int level)
{
if(Token() == T_EOF)
return true;
if(PassQ("pict")) { // insert anchored images too
Flush(false, 1);
ReadPict();
Flush(false, 1);
para.part.Clear();
output.Cat(para);
}
if(token != T_END_GROUP)
return false;
is_full = false;
return Level() < level;
}
void RTFParser::Skip()
{
LLOG("Skip");
bool is_group = (token == T_GROUP || token == T_COMMAND && state.new_dest);
is_full = false;
if(is_group)
SkipGroup();
}
void RTFParser::SkipGroup(int level)
{
while(!PassEndGroup(level))
is_full = false;
}
void RTFParser::ReadItem()
{
const char *p = rtf;
if(token == T_COMMAND)
ReadCommand();
else if(token == T_TEXT)
ReadText();
if(rtf == p && is_full) {
is_full = false;
if(token == T_COMMAND && state.new_dest && command != "shppict") {
LLOG("SkipGroup new_dest " << command);
SkipGroup();
}
state.new_dest = false;
}
}
void RTFParser::ReadItemGroup(int level)
{
while(!PassEndGroup(level))
ReadItem();
}
void RTFParser::ReadText()
{
if(!IsNull(text))
ReadText(text);
}
void RTFParser::ReadText(const WString& text)
{
if(!IsNull(state.dest))
return;
LLOG("Output text: <" << FromUnicode(text, state.charset) << ">, " << state.charformat);
para.Cat(text, state.charformat);
}
void RTFParser::ReadCommand()
{
if(Token() == T_COMMAND) ReadHeader();
if(Token() == T_COMMAND) ReadMisc();
if(Token() == T_COMMAND) ReadParaStyle();
if(Token() == T_COMMAND) ReadTableStyle();
if(Token() == T_COMMAND) ReadCharStyle();
}
void RTFParser::ReadHeader()
{
if(PassCmd("deff"))
default_font = command_arg;
else if(PassQ("fonttbl")) {
state.dest = command;
ReadFaceTable();
}
else if(PassQ("colortbl")) {
state.dest = command;
ReadColorTable();
}
else if(PassQ("stylesheet") || PassQ("list") || PassQ("listoverride") || PassQ("info")) {
state.dest = command;
SkipGroup();
}
else if(Token() == T_COMMAND)
ReadCharSet();
}
void RTFParser::ReadCharSet()
{
if(PassQ("ansi")) {}
else if(PassQ("mac")) {}
else if(PassQ("pc")) {}
else if(PassQ("pca")) {}
else if(PassQ("ansicpg")) {
static const struct {
int ansicpg;
byte charset;
}
charsets[] =
{
{ 1250, CHARSET_WIN1250 },
{ 1251, CHARSET_WIN1251 },
{ 1252, CHARSET_WIN1252 },
{ 1253, CHARSET_WIN1253 },
{ 1254, CHARSET_WIN1254 },
{ 1255, CHARSET_WIN1255 },
{ 1256, CHARSET_WIN1256 },
{ 1257, CHARSET_WIN1257 },
};
for(int c = 0; c < __countof(charsets); c++)
if(charsets[c].ansicpg == command_arg) {
default_charset = state.charset = charsets[c].charset;
break;
}
}
}
void RTFParser::ReadFaceTable()
{
int fx = 0;
while(!PassEndGroup()) {
if(!PassGroup()) {
Skip();
continue;
}
Face n;
n.face = Font::ARIAL;
n.charset = default_charset;
String facename;
while(!PassEndGroup()) {
if(PassCmd("f"))
fx = command_arg;
else if(PassCmd("fnil"))
;
else if(PassCmd("froman"))
n.face = Font::ROMAN;
else if(PassCmd("fswiss"))
n.face = Font::ARIAL;
else if(PassCmd("fmodern"))
n.face = Font::ARIAL;
else if(PassCmd("ftech"))
#ifdef PLATFORM_WIN32
n.face = Font::SYMBOL;
#else
n.face = Font::ARIAL;
#endif
else if(PassCmd("fcharset")) {
switch(command_arg) {
case 0: n.charset = CHARSET_WIN1252; break; // ANSI
case 1: n.charset = default_charset; break; // Default
case 2: n.charset = CHARSET_WIN1252; break; // Symbol
case 3: break; // Invalid
case 77: break; // Mac
case 128: break; // Shift Jis
case 129: break; // Hangul
case 130: break; // Johab
case 134: break; // GB2312
case 136: break; // Big5
case 161: n.charset = CHARSET_WIN1253; break; // Greek
case 162: n.charset = CHARSET_WIN1254; break; // Turkish
case 163: break; // Vietnamese
case 177: n.charset = CHARSET_WIN1255; break; // Hebrew
case 178: break; // Arabic
case 179: break; // Arabic Traditional
case 180: break; // Arabic user
case 181: break; // Hebrew user
case 186: break; // Baltic
case 204: n.charset = CHARSET_WIN1251; break; // Russian
case 222: break; // Thai
case 238: n.charset = CHARSET_WIN1250; break; // Eastern European
case 254: break; // PC 437
case 255: n.charset = CHARSET_WIN1252; break; // OEM
}
}
else if(PassText())
facename = text.ToString();
/*
else if(PassGroup()) {
int level = Level();
if(PassCmd("falt") && PassText() && f < 0)
f = Font::FindFaceNameIndex(FromUnicode(text, charset));
SkipGroup(level);
}*/ //Cxl 2005-11-29 - "Arial CE" makes mess here!
else
Skip();
}
if(fx >= 0 && fx < MAX_FONTS) {
facename.TrimEnd(";");
int fi = Font::FindFaceNameIndex(facename);
if(fi >= 0 && (Font::GetFaceInfo(fi) & Font::FIXEDPITCH))
n.face = Font::MONOSPACE;
if(default_font == fx) {
plain_format.Face(n.face);
plain_charset = n.charset;
}
Face dflt;
dflt.face = Font::ARIAL;
dflt.charset = default_charset;
face_table.At(fx++, dflt) = n;
}
}
}
void RTFParser::ReadColorTable()
{
int r = Null, g = Null, b = Null;
for(; !PassEndGroup(); Skip())
if(PassCmd("red"))
r = command_arg;
else if(PassCmd("green"))
g = command_arg;
else if(PassCmd("blue"))
b = command_arg;
else if(PassText())
{
Color c = Null;
if(!IsNull(r) || !IsNull(g) || !IsNull(b))
c = Color(Nvl(r, 0), Nvl(g, 0), Nvl(b, 0));
color_table.Add(c);
}
}
void RTFParser::ReadMisc()
{
if(PassQ("field"))
ReadField();
else if(PassQ("nonshppict"))
SkipGroup();
else if(PassQ("pict"))
ReadPict();
else if(PassQ("shpinst"))
ReadShape();
else if(PassQ("endash"))
ReadChar(0x2013);
else if(PassQ("emdash"))
ReadChar(0x2014);
else if(PassQ("tab"))
ReadText(WString(9, 1));
else if(PassQ("enspace"))
ReadText(WString(" ")); // todo
else if(PassQ("emspace"))
ReadText(WString(" ")); // todo
else if(PassQ("bullet"))
ReadChar(0x2022);
else if(PassQ("lquote"))
ReadChar(0x2018);
else if(PassQ("rquote"))
ReadChar(0x2019);
else if(PassQ("ldblquote"))
ReadChar(0x201C);
else if(PassQ("rdblquote"))
ReadChar(0x201D);
}
void RTFParser::ReadField()
{
bool ign_rslt = false;
int level = Level();
while(!PassEndGroup(level))
if(PassGroup() && Level() == level + 1) {
if(PassCmd("fldinst")) {
WString source;
for(; !PassEndGroup(); Skip())
if(PassText())
source.Cat(text);
if(ReadField(FromUnicode(source, state.charset)))
ign_rslt = true;
continue;
}
else if(PassCmd("fldrslt")) {
if(!ign_rslt)
ReadItemGroup();
}
}
else
Skip();
}
bool RTFParser::ReadField(const char *p)
{
Index<String> symdef;
while(*p)
if((byte)*p <= ' ')
p++;
else if(*p == '\"')
symdef.Add(FromCString(p, &p));
else {
const char *b = p;
while(*++p && *p != ' ')
;
symdef.Add(String(b, p));
}
if(symdef.IsEmpty())
return false;
if(symdef[0] == "SYMBOL" && symdef.GetCount() >= 2 && IsDigit(*symdef[1])) {
int code = atoi(symdef[1]);
int face = -1;
int height = 0;
int f = symdef.Find("\\f");
if(f >= 0 && f + 1 < symdef.GetCount())
face = Font::FindFaceNameIndex(symdef[f + 1]);
f = symdef.Find("\\s");
if(f >= 0 && f + 1 < symdef.GetCount())
height = PointDots(fround(2 * Atof(symdef[f + 1]))) >> 1;
if(face < 0)
#ifdef PLATFORM_WIN32
face = Font::SYMBOL;
#else
face = Font::ARIAL;
#endif
if(height <= 0 || height >= MAX_DOT_HEIGHT)
height = state.charformat.GetHeight();
if(code >= 0 && code < 255) {
state.charformat.Face(face).Height(height);
ReadText(WString(ToUnicode(code, state.charset), 1));
return true;
}
}
return false;
}
void RTFParser::DefaultParaStyle()
{
state.format = pard_format;
state.first_indent = state.left_margin = state.right_margin = 0;
// state.cellformat = std_cell_format;
state.in_table = false;
state.itap = 1;
state.nestprop = false;
state.trgaph = 2;
state.rowmargin = Rect(25, 25, 25, 25);
state.cellmarginunits = state.rowmarginunits = Rect(0, 0, 0, 0);
state.rowspacing = Rect(0, 0, 0, 0);
state.rowspacingunits = Rect(0, 0, 0, 0);
state.charset = plain_charset;
}
void RTFParser::ReadParaStyle()
{
if(PassQ("par"))
Flush(true, state.itap);
else if(PassQ("cell")) {
Flush(false, 1);
if(!table_stack.IsEmpty())
table_stack[0].textcol++;
}
else if(PassQ("nestcell")) {
Flush(false, state.itap);
if(state.itap <= table_stack.GetCount())
table_stack[state.itap - 1].textcol++;
}
else if(PassQ("pard"))
DefaultParaStyle();
else if(PassQ("pntext"))
SkipGroup();
else if(PassQ("pn")) {
SkipGroup();
state.format.bullet = RichPara::BULLET_ROUND;
}
else if(PassQ("pagebb"))
state.format.newpage = (command_arg != 0);
else if(PassQ("ql"))
state.format.align = ALIGN_LEFT;
else if(PassQ("qc"))
state.format.align = ALIGN_CENTER;
else if(PassQ("qr"))
state.format.align = ALIGN_RIGHT;
else if(PassQ("qj"))
state.format.align = ALIGN_JUSTIFY;
else if(PassQ("fi"))
state.first_indent = TwipDotsLim(command_arg);
else if(PassQ("li"))
state.left_margin = TwipDotsLim(command_arg);
else if(PassQ("ri"))
state.right_margin = TwipDotsLim(command_arg);
else if(PassQ("sb"))
state.format.before = TwipDotsLim(command_arg);
else if(PassQ("sa"))
state.format.after = TwipDotsLim(command_arg);
else if(PassQ("widctlpar"))
state.format.orphan = true;
else if(PassQ("nowidctlpar"))
state.format.orphan = false;
else if(PassQ("tql"))
tab_align = ALIGN_LEFT;
else if(PassQ("tqc"))
tab_align = ALIGN_CENTER;
else if(PassQ("tqr"))
tab_align = ALIGN_RIGHT;
else if(PassQ("tqdec"))
tab_align = ALIGN_RIGHT; // todo
else if(PassQ("tldot"))
tab_fill = 0;
else if(PassQ("tlhyph"))
tab_fill = 0;
else if(PassQ("tlul"))
tab_fill = 0;
else if(PassQ("tlth"))
tab_fill = 0;
else if(PassQ("tleq"))
tab_fill = 0;
else if(PassQ("tx") || PassQ("tb")) { // todo: bar tab ?
int pos = TwipDotSize(command_arg);
RichPara::Tab& tab = state.format.tab.Add();
tab.align = tab_align;
tab.fillchar = tab_fill;
tab.pos = pos;
state.format.SortTabs();
}
else if(PassQ("intbl"))
state.in_table = true;
else if(PassQ("itap")) {
state.itap = minmax(command_arg, 1, 10);
if(table_stack.GetCount() < state.itap)
OpenTable(state.itap);
}
}
void RTFParser::ReadCharStyle()
{
if(PassQ("plain")) {
state.charformat = plain_format;
state.charset = plain_charset;
}
else if(PassQ("b"))
state.charformat.Bold(command_arg != 0);
else if(PassQ("i"))
state.charformat.Italic(command_arg != 0);
else if(PassQ("ul") || PassQ("uld") || PassQ("uldb")
|| PassQ("uldash") || PassQ("uldashd") || PassQ("uldashdd")
|| PassQ("ulth") || PassQ("ulw") || PassQ("ulwave"))
state.charformat.Underline(command_arg != 0);
else if(PassQ("ulnone"))
state.charformat.Underline(false);
else if(PassQ("strike") || PassQ("strikedl"))
state.charformat.Strikeout(command_arg != 0);
else if(PassQ("caps") || PassQ("scaps"))
state.charformat.capitals = (command_arg != 0);
else if(PassQ("super") || PassQ("up"))
state.charformat.sscript = 1;
else if(PassQ("sub") || PassQ("dn"))
state.charformat.sscript = 2;
else if(PassQ("nosupersub"))
state.charformat.sscript = 0;
else if(PassQ("f") && command_arg >= 0 && command_arg < face_table.GetCount()) {
LLOG("font = " << command_arg << ", face = " << face_table[command_arg].face
<< ", charset = " << face_table[command_arg].charset);
state.charformat.Face(face_table[command_arg].face);
state.charset = face_table[command_arg].charset;
}
else if(PassQ("fs"))
state.charformat.Height(PointDotHeight(command_arg));
else if(PassQ("cf") && command_arg >= 0 && command_arg < color_table.GetCount())
state.charformat.ink = Nvl(color_table[command_arg], Black);
else if(PassQ("cb") && command_arg >= 0 && command_arg < color_table.GetCount())
state.charformat.paper = color_table[command_arg];
else if(PassQ("lang"))
{} // state.language = ...
}
void RTFParser::ReadShape()
{
int level = Level();
while(!PassEndGroup(level))
if(PassCmd("shppict")) {
LLOG("* shppict");
state.new_dest = false;
ReadItemGroup();
}
else
is_full = false;
}
void RTFParser::ReadPict()
{
LLOG("* ReadPict");
Size log_size(1, 1), out_size(1, 1), scaling(100, 100);
Rect crop(0, 0, 0, 0);
enum BLIPTYPE { UNK_BLIP, WMF_BLIP, PNG_BLIP, JPEG_BLIP, DIB_BLIP };
BLIPTYPE blip_type = UNK_BLIP;
String blip_data;
char odd = 0;
while(!PassEndGroup()) {
if(PassText())
blip_data.Cat(ReadBinHex(odd));
else if(Token() == T_COMMAND) {
if(PassQ("picw")) log_size.cx = minmax<int>(command_arg, 0, 30000);
else if(PassQ("pich")) log_size.cy = minmax<int>(command_arg, 0, 30000);
else if(PassQ("picwgoal")) out_size.cx = TwipDotSize(command_arg);
else if(PassQ("pichgoal")) out_size.cy = TwipDotSize(command_arg);
else if(PassQ("picscalex")) scaling.cx = minmax<int>(command_arg, 1, 1000);
else if(PassQ("picscaley")) scaling.cy = minmax<int>(command_arg, 1, 1000);
else if(PassQ("piccropl")) crop.left = TwipDotSize(command_arg);
else if(PassQ("piccropt")) crop.top = TwipDotSize(command_arg);
else if(PassQ("piccropr")) crop.right = TwipDotSize(command_arg);
else if(PassQ("piccropb")) crop.bottom = TwipDotSize(command_arg);
else if(PassQ("emfblip")) blip_type = WMF_BLIP;
else if(PassQ("pngblip")) blip_type = PNG_BLIP;
else if(PassQ("jpegblip")) blip_type = JPEG_BLIP;
else if(PassQ("wmetafile")) blip_type = WMF_BLIP;
else if(PassQ("dibitmap")) blip_type = DIB_BLIP;
else {
LLOG("Command skip " << command);
Skip();
}
}
else {
LLOG("Non command skip");
Skip();
}
}
Size final_size = minmax(iscale(out_size, scaling, Size(100, 100)), Size(1, 1), Size(30000, 30000));
Size drawing_size;
DrawingDraw dd;
RichObject object;
LLOG("Pict format " << (int)blip_type << ", data size: " << blip_data.GetCount());
if(blip_data.IsEmpty())
return;
#ifdef GUI_WIN
#ifndef PLATFORM_WINCE
if(blip_type == WMF_BLIP) {
log_size = min(log_size, GetFitSize(log_size, final_size));
dd.Create(drawing_size = log_size);
WinMetaFile wmf(blip_data);
wmf.Paint(dd, log_size);
object = CreateDrawingObject(dd, out_size, final_size);
}
else
#endif
#endif
if(blip_type == DIB_BLIP || blip_type == PNG_BLIP || blip_type == JPEG_BLIP) {
Image image = StreamRaster::LoadStringAny(blip_data);
LLOG("Image size: " << image.GetSize());
object = CreatePNGObject(image, out_size, final_size);
}
if(object) {
LLOG("object (" << object.GetTypeName() << ", " << object.Write().GetLength() << " B), pixel size "
<< object.GetPixelSize() << ", final size " << object.GetSize());
para.Cat(object, state.charformat);
}
}
String RTFParser::ReadBinHex(char& odd) const
{
int t = odd;
byte v = ctoi(odd);
String out;
for(const wchar *s = text.Begin(); *s; s++) {
byte w = (byte)(*s >= '0' && *s <= '9' ? *s - '0'
: *s >= 'A' && *s <= 'F' ? *s - 'A' + 10
: *s >= 'a' && *s <= 'f' ? *s - 'a' + 10
: 255);
if(w < 16) {
if(v >= 16) {
t = *s;
v = w;
}
else
{
out.Cat(16 * v + w);
v = 255;
}
}
}
odd = (v < 16 ? t : 0);
return out;
}
void RTFParser::OpenTable(int level)
{
if(table_stack.GetCount() < level) {
TableState& ts = table_stack.At(level - 1);
ts.stylecol = 0;
// state.cellformat = std_cell_format;
}
}
void RTFParser::ReadCellBorder(int& width)
{
if(Token() == T_COMMAND && !memcmp(command, "brdr", 4))
is_full = false;
if(PassCmd("brdrw"))
width = TwipDots(command_arg);
}
RTFParser::Cell& RTFParser::CellAt(TableState& ts, int i)
{
Array<Cell>& top = ts.cells.Top();
int p = top.GetCount();
if(p <= i)
top.SetCountR(i + 1);
for(int n = p; n <= i; n++)
top[n].info = CellInfoAt(n);
return top[i];
}
RTFParser::CellInfo& RTFParser::CellInfoAt(int i)
{
return state.cellinfo.At(i, std_cell_info);
}
void RTFParser::SetCellMargin(Cell& out, int Rect::*mbr)
{
if(out.info.cellmarginunits.*mbr == 0) {
out.info.format.margin.*mbr = state.trgaph;
if(state.rowmarginunits.*mbr == 3)
out.info.format.margin.*mbr = state.rowmargin.*mbr;
if(state.rowspacingunits.*mbr == 3)
out.info.format.margin.*mbr += state.rowspacing.*mbr;
}
}
void RTFParser::ReadTableStyle()
{
if(PassQ("nesttableprops")) {
state.nestprop = true;
return;
}
if(PassQ("nonesttables")) {
SkipGroup();
return;
}
int itap = (state.nestprop ? state.itap : 1);
if(PassQ("trowd")) {
OpenTable(itap);
table_stack[itap - 1].stylecol = 0;
return;
}
if(PassQ("row") && table_stack.GetCount() >= 1) {
TableState& ts0 = table_stack[0];
ts0.textcol = ts0.stylecol = 0;
ts0.cells.Add();
return;
}
if(PassQ("nestrow") && table_stack.GetCount() >= state.itap) {
TableState& ts = table_stack[state.itap - 1];
ts.textcol = ts.stylecol = 0;
ts.cells.Add();
return;
}
if(itap > table_stack.GetCount())
return;
TableState& ts = table_stack[itap - 1];
if(PassQ("trgaph"))
state.trgaph = TwipDotsLim(command_arg);
else if(PassQ("trql")) {}
else if(PassQ("trqr")) {}
else if(PassQ("trqc")) {}
else if(PassQ("trleft")) {
ts.tableformat.lm = TwipDotsLim(command_arg);
}
else if(PassQ("trbrdrl")) {}
else if(PassQ("trbrdrt")) {}
else if(PassQ("trbrdrr")) {}
else if(PassQ("trbrdrb")) {}
else if(PassQ("trbrdrv")) {}
else if(PassQ("trftsWidth")) {}
else if(PassQ("trautofit")) {}
else if(PassQ("trpaddl"))
state.rowmargin.left = TwipDotsLim(command_arg);
else if(PassQ("trpaddt"))
state.rowmargin.top = TwipDotsLim(command_arg);
else if(PassQ("trpaddr"))
state.rowmargin.right = TwipDotsLim(command_arg);
else if(PassQ("trpaddb"))
state.rowmargin.bottom = TwipDotsLim(command_arg);
else if(PassQ("trpaddfl"))
state.rowmarginunits.left = command_arg;
else if(PassQ("trpaddft"))
state.rowmarginunits.top = command_arg;
else if(PassQ("trpaddfr"))
state.rowmarginunits.right = command_arg;
else if(PassQ("trpaddfb"))
state.rowmarginunits.bottom = command_arg;
else if(PassQ("trspdl"))
state.rowspacing.left = TwipDotsLim(command_arg);
else if(PassQ("trspdt"))
state.rowspacing.top = TwipDotsLim(command_arg);
else if(PassQ("trspdr"))
state.rowspacing.right = TwipDotsLim(command_arg);
else if(PassQ("trspdb"))
state.rowspacing.bottom = TwipDotsLim(command_arg);
else if(PassQ("trspdfl"))
state.rowspacingunits.left = command_arg;
else if(PassQ("trspdft"))
state.rowspacingunits.top = command_arg;
else if(PassQ("trspdfr"))
state.rowspacingunits.right = command_arg;
else if(PassQ("trspdfb"))
state.rowspacingunits.bottom = command_arg;
else if(PassQ("clpadl"))
CellInfoAt(ts.stylecol).format.margin.left = TwipDotsLim(command_arg);
else if(PassQ("clpadt"))
CellInfoAt(ts.stylecol).format.margin.top = TwipDotsLim(command_arg);
else if(PassQ("clpadr"))
CellInfoAt(ts.stylecol).format.margin.right = TwipDotsLim(command_arg);
else if(PassQ("clpadb"))
CellInfoAt(ts.stylecol).format.margin.bottom = TwipDotsLim(command_arg);
else if(PassQ("clpadfl"))
state.cellmarginunits.left = command_arg;
else if(PassQ("clpadft"))
state.cellmarginunits.top = command_arg;
else if(PassQ("clpadfr"))
state.cellmarginunits.right = command_arg;
else if(PassQ("clpadfb"))
state.cellmarginunits.bottom = command_arg;
else if(PassQ("clbrdrl"))
ReadCellBorder(CellInfoAt(ts.stylecol).format.border.left);
else if(PassQ("clbrdrt"))
ReadCellBorder(CellInfoAt(ts.stylecol).format.border.top);
else if(PassQ("clbrdrr"))
ReadCellBorder(CellInfoAt(ts.stylecol).format.border.right);
else if(PassQ("clbrdrb"))
ReadCellBorder(CellInfoAt(ts.stylecol).format.border.bottom);
else if(PassQ("cltxlrtb")) {}
else if(PassQ("clshdng"))
CellInfoAt(ts.stylecol).shading = command_arg;
else if(PassQ("clcbpat")) {
if(command_arg >= 0 && command_arg < color_table.GetCount())
CellInfoAt(ts.stylecol).format.color = color_table[command_arg];
}
else if(PassQ("clvmrg"))
CellAt(ts, ts.stylecol).merge = true;
else if(PassQ("clvmgf"))
CellAt(ts, ts.stylecol).merge_first = true;
else if(PassQ("clftsWidth")) {}
else if(PassQ("clwWidth")) {}
else if(PassQ("cellx")) {
int sx = ts.stylecol++;
Cell& newcell = CellAt(ts, sx);
newcell.info.end_dots = TwipDotsLim(command_arg);
SetCellMargin(newcell, &Rect::left);
SetCellMargin(newcell, &Rect::top);
SetCellMargin(newcell, &Rect::right);
SetCellMargin(newcell, &Rect::bottom);
CellInfoAt(sx) = newcell.info;
//CellFormat(sx) = std_cell_format;
}
else if(PassQ("clvertalt"))
CellInfoAt(ts.stylecol).format.align = ALIGN_TOP;
else if(PassQ("clvertalc"))
CellInfoAt(ts.stylecol).format.align = ALIGN_CENTER;
else if(PassQ("clvertalb"))
CellInfoAt(ts.stylecol).format.align = ALIGN_BOTTOM;
}
}