ultimatepp/uppsrc/CtrlCore/ParseRTF.cpp
Mirek Fidler 34ff691308 sizeof(wchar) is changed to 4 (32 bits) to support non BMP unicode characters
This might bring some incompatibilities in the code that expects wchar to be 16 bit, which
  escpecially involves dealing with Win32 (and to lesser extend MacOS) APIs, so if your application
  is doing that, please check all instances of WCHAR (UniChar on MacOS) or even wchar
  especially type casts.

  To support host APIs, char16 is introduced (but there is no 16-bit String varian).

  Use ToSystemCharsetW, FromSystemCharsetW to convert texts to Win32 API.

- Support of drawing non-BMP characters in GUI
- Vastly improved character font replacement code (when drawing characters missing with requested font, replacement font is used)
- Last instances of Win32 ANSI calls (those ending with A) are removed
- UTF handling routines are refactored and their's naming is unified
- RTF is now being able to handle non-BMP characters (RTF is used as clipboard format for RichText)

Other minor changes:

- fixed TryRealloc issue
- improved MemoryCheck
- Removed MemoryAlloc48/MemoryFree48
- In theide Background parsing should less often cause delays in the main thread
2021-12-02 12:03:19 +01:00

1292 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 _DEBUG0
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(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;
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()) {
String s = FromUnicode(text, charset);
if(!s.IsEmpty() && *s.Last() == ';')
s.Trim(s.GetLength() - 1);
if(!s.IsEmpty())
f = Font::FindFaceNameIndex(s);
}
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) {
// if(f < 0) // Cxl 2005-11-29
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;
}
}