mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 22:02:49 -06:00
1070 lines
24 KiB
C++
1070 lines
24 KiB
C++
#include "RichText.h"
|
|
|
|
namespace Upp {
|
|
|
|
Color (*QTFColor[])() = {
|
|
Black, LtGray, White, Red, Green, Blue, LtRed, WhiteGray, LtCyan, Yellow
|
|
};
|
|
|
|
Color NullColorF()
|
|
{
|
|
return Null;
|
|
}
|
|
|
|
static Color (*QTFColorl[])() = {
|
|
/*a*/White, /*b*/Blue, /*c*/Cyan, /*d*/White, /*e*/White, /*f*/White, /*g*/ Green, /*h*/White,
|
|
/*i*/White, /*j*/White, /*k*/Black, /*l*/LtGray, /*m*/Magenta, /*n*/NullColorF, /*o*/Brown, /*p*/White,
|
|
/*q*/White, /*r*/Red, /*s*/White, /*t*/White, /*u*/White, /*v*/White, /*w*/WhiteGray, /*x*/White,
|
|
/*y*/Yellow, /*z*/ White
|
|
};
|
|
|
|
static Color (*QTFColorL[])() = {
|
|
/*A*/White, /*B*/LtBlue, /*C*/LtCyan, /*D*/White, /*E*/White, /*F*/White, /*G*/LtGreen, /*H*/White,
|
|
/*I*/White, /*J*/White, /*K*/Gray, /*L*/WhiteGray, /*M*/LtMagenta, /*N*/NullColorF, /*O*/Brown, /*P*/White,
|
|
/*Q*/White, /*R*/LtRed, /*S*/White, /*T*/White, /*U*/White, /*V*/White, /*W*/White, /*X*/White,
|
|
/*Y*/LtYellow, /*Z*/White
|
|
};
|
|
|
|
int QTFFontHeight[] = {
|
|
50, 67, 84, 100, 134, 167, 200, 234, 300, 400
|
|
};
|
|
|
|
class RichQtfParser {
|
|
void *context;
|
|
const char *term;
|
|
WString text;
|
|
RichPara paragraph;
|
|
RichTable tablepart;
|
|
bool istable;
|
|
bool breakpage;
|
|
int accesskey;
|
|
|
|
struct PFormat : public RichPara::Format {
|
|
byte charset;
|
|
};
|
|
|
|
struct Tab {
|
|
RichCell::Format format;
|
|
int vspan, hspan;
|
|
RichTxt text;
|
|
RichTable table;
|
|
int cell;
|
|
Vector<int> rown;
|
|
|
|
void Old() { RichTable::Format fmt; fmt.grid = 10; table.SetFormat(fmt); }
|
|
|
|
Tab() { cell = 0; vspan = hspan = 0; }
|
|
};
|
|
|
|
PFormat format;
|
|
Array<PFormat> fstack;
|
|
Vector<Uuid> styleid;
|
|
Vector<int> stylenext;
|
|
Array<Tab> table;
|
|
bool oldtab;
|
|
|
|
bool Key(int c) { if(*term == c) { term++; return true; } return false; }
|
|
bool Key2(int c, int d);
|
|
bool Key2(int c) { return Key2(c, c); }
|
|
int GetNumber();
|
|
int ReadNumber();
|
|
String GetText(int delim);
|
|
String GetText2(int delim1, int delim2);
|
|
Color GetColor();
|
|
void Number2(int& a, int& b);
|
|
|
|
void Flush();
|
|
void SetFormat();
|
|
void FlushStyles();
|
|
void Error(const char *s);
|
|
|
|
void ReadObject();
|
|
|
|
RichTable& Table();
|
|
void TableFormat(bool bw = false);
|
|
void FinishCell();
|
|
void FinishTable();
|
|
void FinishOldTable();
|
|
void S(int& x, int a);
|
|
|
|
void EndPart();
|
|
|
|
void Cat(int chr);
|
|
|
|
public:
|
|
struct Exc {};
|
|
|
|
RichText target;
|
|
|
|
void Parse(const char *qtf, int accesskey);
|
|
|
|
RichQtfParser(void *context);
|
|
};
|
|
|
|
void init_s_nodeqtf();
|
|
|
|
RichQtfParser::RichQtfParser(void *context_)
|
|
: context(context_)
|
|
{
|
|
format.Face(Font::ARIAL);
|
|
format.Height(100);
|
|
format.charset = GetDefaultCharset();
|
|
format.language = 0;
|
|
breakpage = false;
|
|
istable = false;
|
|
oldtab = false;
|
|
init_s_nodeqtf();
|
|
}
|
|
|
|
bool RichQtfParser::Key2(int c, int d)
|
|
{
|
|
if(term[0] == c && term[1] == d) {
|
|
term += 2;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int RichQtfParser::GetNumber()
|
|
{
|
|
int n = 0;
|
|
int sgn = 1;
|
|
if(*term == '-') {
|
|
sgn = -1;
|
|
term++;
|
|
}
|
|
while(IsDigit(*term))
|
|
n = n * 10 + *term++ - '0';
|
|
return sgn * n;
|
|
}
|
|
|
|
String RichQtfParser::GetText(int delim) {
|
|
String s;
|
|
for(;;) {
|
|
if(*term == '\0') return s;
|
|
if(*term == '`') {
|
|
term++;
|
|
if(*term == '\0') return s;
|
|
s.Cat(*term++);
|
|
}
|
|
else
|
|
if(*term == delim) {
|
|
term++;
|
|
return s;
|
|
}
|
|
else
|
|
s.Cat(*term++);
|
|
}
|
|
}
|
|
|
|
String RichQtfParser::GetText2(int delim1, int delim2) {
|
|
String s;
|
|
for(;;) {
|
|
if(*term == '\0') return s;
|
|
if(*term == '`') {
|
|
term++;
|
|
if(*term == '\0') return s;
|
|
s.Cat(*term++);
|
|
}
|
|
else
|
|
if(term[0] == delim1 && term[1] == delim2) {
|
|
term += 2;
|
|
return s;
|
|
}
|
|
else
|
|
s.Cat(*term++);
|
|
}
|
|
}
|
|
|
|
int RichQtfParser::ReadNumber()
|
|
{
|
|
if(!IsDigit(*term))
|
|
Error("Expected number");
|
|
return GetNumber();
|
|
}
|
|
|
|
void RichQtfParser::Number2(int& a, int& b)
|
|
{
|
|
a = -1;
|
|
b = -1;
|
|
if(IsDigit(*term))
|
|
a = GetNumber();
|
|
if(*term == '/') {
|
|
term++;
|
|
b = GetNumber();
|
|
}
|
|
}
|
|
|
|
Color RichQtfParser::GetColor()
|
|
{
|
|
int c = *term++;
|
|
if(c == '(') {
|
|
byte r = GetNumber();
|
|
if(Key(')')) {
|
|
r &= 255;
|
|
return Color(r, r, r);
|
|
}
|
|
Key('.');
|
|
byte g = GetNumber();
|
|
Key('.');
|
|
byte b = GetNumber();
|
|
Key(')');
|
|
return Color(r & 255, g & 255, b & 255);
|
|
}
|
|
else
|
|
if(c >= '0' && c <= '9')
|
|
return QTFColor[c - '0']();
|
|
else
|
|
if(c >= 'a' && c <= 'z')
|
|
return QTFColorl[c - 'a']();
|
|
else
|
|
if(c >= 'A' && c <= 'Z')
|
|
return QTFColorL[c - 'A']();
|
|
else
|
|
return Red;
|
|
}
|
|
|
|
void RichQtfParser::SetFormat()
|
|
{
|
|
paragraph.format = format;
|
|
}
|
|
|
|
void RichQtfParser::Flush() {
|
|
if(text.GetLength()) {
|
|
ASSERT(!istable);
|
|
paragraph.Cat(text, format);
|
|
text.Clear();
|
|
}
|
|
}
|
|
|
|
void RichQtfParser::EndPart()
|
|
{
|
|
if(istable) {
|
|
if(paragraph.GetCount() == 0 && text.GetCount() == 0)
|
|
if(table.GetCount())
|
|
table.Top().text.CatPick(pick(tablepart));
|
|
else
|
|
target.CatPick(pick(tablepart));
|
|
else {
|
|
paragraph.part.Clear();
|
|
text.Clear();
|
|
}
|
|
}
|
|
else {
|
|
Flush();
|
|
if(table.GetCount())
|
|
table.Top().text.Cat(paragraph, target.GetStyles());
|
|
else {
|
|
if(breakpage)
|
|
paragraph.format.newpage = true;
|
|
target.Cat(paragraph);
|
|
}
|
|
paragraph.part.Clear();
|
|
SetFormat();
|
|
breakpage = false;
|
|
}
|
|
istable = false;
|
|
}
|
|
|
|
void RichQtfParser::ReadObject()
|
|
{
|
|
Flush();
|
|
RichObject obj;
|
|
if(*term == '#') {
|
|
term++;
|
|
#ifdef CPU_64
|
|
obj = *(RichObject *)stou64(term, &term);
|
|
#else
|
|
obj = *(RichObject *)stou(term, &term);
|
|
#endif
|
|
term++;
|
|
}
|
|
else {
|
|
String type;
|
|
while(IsAlNum(*term) || *term == '_')
|
|
type.Cat(*term++);
|
|
Size sz;
|
|
Key(':');
|
|
sz.cx = ReadNumber();
|
|
bool keepratio = false;
|
|
if(Key('&'))
|
|
keepratio = true;
|
|
else
|
|
Key('*');
|
|
sz.cy = ReadNumber();
|
|
int yd = 0;
|
|
if(Key('/'))
|
|
yd = GetNumber();
|
|
while(*term && (byte)*term < 32)
|
|
term++;
|
|
String odata;
|
|
if(Key('`'))
|
|
while(*term) {
|
|
if(*term == '`') {
|
|
term++;
|
|
if(*term == '`')
|
|
odata.Cat('`');
|
|
else
|
|
break;
|
|
}
|
|
else
|
|
if((byte)*term >= 32)
|
|
odata.Cat(*term);
|
|
term++;
|
|
}
|
|
else
|
|
if(Key('(')) {
|
|
const char *b = term;
|
|
while(*term && *term != ')')
|
|
term++;
|
|
odata = Base64Decode(b, term);
|
|
if(*term == ')')
|
|
term++;
|
|
}
|
|
else {
|
|
StringBuffer data;
|
|
for(;;) {
|
|
while(*term < 32 && *term > 0) term++;
|
|
if((byte)*term >= ' ' && (byte)*term <= 127 || *term == '\0') break;
|
|
byte seven = *term++;
|
|
for(int i = 0; i < 7; i++) {
|
|
while((byte)*term < 32 && (byte)*term > 0) term++;
|
|
if((byte)*term >= ' ' && (byte)*term <= 127 || *term == '\0') break;
|
|
data.Cat((*term++ & 0x7f) | ((seven << 7) & 0x80));
|
|
seven >>= 1;
|
|
}
|
|
}
|
|
odata = data;
|
|
}
|
|
obj.Read(type, odata, sz, context);
|
|
obj.KeepRatio(keepratio);
|
|
obj.SetYDelta(yd);
|
|
}
|
|
paragraph.Cat(obj, format);
|
|
}
|
|
|
|
int NoLow(int c) {
|
|
return (byte)c >= 32 ? c : 0;
|
|
}
|
|
|
|
void RichQtfParser::Error(const char *s) {
|
|
RichPara::CharFormat ef;
|
|
(Font&) ef = Arial(84).Bold().Underline();
|
|
ef.ink = Red;
|
|
WString e;
|
|
e << "ERROR: " << s;
|
|
if(*term)
|
|
e << ": " << Filter(String(term, min<int>((int)strlen(term), 20)), NoLow).ToWString();
|
|
else
|
|
e << ".";
|
|
paragraph.Cat(e, ef);
|
|
target.Cat(paragraph);
|
|
FlushStyles();
|
|
throw Exc();
|
|
}
|
|
|
|
void RichQtfParser::FlushStyles()
|
|
{
|
|
for(int i = 0; i < styleid.GetCount(); i++)
|
|
if(stylenext[i] >= 0 && stylenext[i] < styleid.GetCount()) {
|
|
RichStyle s = target.GetStyle(styleid[i]);
|
|
s.next = styleid[stylenext[i]];
|
|
target.SetStyle(styleid[i], s);
|
|
}
|
|
}
|
|
|
|
RichTable& RichQtfParser::Table()
|
|
{
|
|
if(table.GetCount() == 0)
|
|
Error("Not in table");
|
|
return table.Top().table;
|
|
}
|
|
|
|
void RichQtfParser::S(int& x, int a)
|
|
{
|
|
if(a >= 0)
|
|
x = a;
|
|
}
|
|
|
|
void RichQtfParser::TableFormat(bool bw)
|
|
{
|
|
RichTable& tab = Table();
|
|
RichTable::Format tabformat = tab.GetFormat();
|
|
Tab& t = table.Top();
|
|
int a, b;
|
|
for(;;) {
|
|
if(bw && IsDigit(*term)) {
|
|
t.hspan = GetNumber();
|
|
}
|
|
else
|
|
if(*term == '\0')
|
|
Error("Unexpected end of text in cell format");
|
|
else
|
|
switch(*term++) {
|
|
case ' ':
|
|
tab.SetFormat(tabformat);
|
|
return;
|
|
case ';': break;
|
|
case '<': tabformat.lm = ReadNumber(); break;
|
|
case '>': tabformat.rm = ReadNumber(); break;
|
|
case 'B': tabformat.before = ReadNumber(); break;
|
|
case 'A': tabformat.after = ReadNumber(); break;
|
|
case 'f': tabformat.frame = ReadNumber(); break;
|
|
case '_':
|
|
case 'F': tabformat.framecolor = GetColor(); break;
|
|
case 'g': tabformat.grid = ReadNumber(); break;
|
|
case 'G': tabformat.gridcolor = GetColor(); break;
|
|
case 'h': tabformat.header = GetNumber(); break;
|
|
case '~': tabformat.frame = tabformat.grid = 0; break;
|
|
case '^': t.format.align = ALIGN_TOP; break;
|
|
case '=': t.format.align = ALIGN_CENTER; break;
|
|
case 'v': t.format.align = ALIGN_BOTTOM; break;
|
|
case 'l': Number2(a, b); S(t.format.border.left, a); S(t.format.margin.left, b); break;
|
|
case 'r': Number2(a, b); S(t.format.border.right, a); S(t.format.margin.right, b); break;
|
|
case 't': Number2(a, b); S(t.format.border.top, a); S(t.format.margin.top, b); break;
|
|
case 'b': Number2(a, b); S(t.format.border.bottom, a); S(t.format.margin.bottom, b); break;
|
|
case 'H': t.format.minheight = ReadNumber(); break;
|
|
case '@': t.format.color = GetColor(); break;
|
|
case 'R': t.format.bordercolor = GetColor(); break;
|
|
case '!': t.format = RichCell::Format(); break;
|
|
case 'o': t.format.round = true; break;
|
|
case 'k': t.format.keep = true; break;
|
|
case 'K': tabformat.keep = true; break;
|
|
case 'P': tabformat.newpage = true; break;
|
|
case 'T':
|
|
tabformat.newhdrftr = true;
|
|
tabformat.header_qtf = GetText2('^', '^');
|
|
tabformat.footer_qtf = GetText2('^', '^');
|
|
break;
|
|
case 'a':
|
|
Number2(a, b);
|
|
if(a >= 0)
|
|
t.format.border.left = t.format.border.right = t.format.border.top = t.format.border.bottom = a;
|
|
if(b >= 0)
|
|
t.format.margin.left = t.format.margin.right = t.format.margin.top = t.format.margin.bottom = b;
|
|
break; //!!cell all lines
|
|
case '*':
|
|
tabformat.frame = tabformat.grid =
|
|
t.format.border.left = t.format.border.right = t.format.border.top = t.format.border.bottom =
|
|
t.format.margin.left = t.format.margin.right = t.format.margin.top = t.format.margin.bottom = 0;
|
|
break;
|
|
case '-': t.hspan = GetNumber(); break;
|
|
case '+':
|
|
case '|': t.vspan = GetNumber(); break;
|
|
default:
|
|
Error("Invalid cell format");
|
|
}
|
|
}
|
|
}
|
|
|
|
void RichQtfParser::FinishCell()
|
|
{
|
|
EndPart();
|
|
RichTable& t = Table();
|
|
Tab& b = table.Top();
|
|
int i, j;
|
|
if(oldtab) {
|
|
i = b.rown.GetCount() - 1;
|
|
j = b.rown.Top();
|
|
b.rown.Top()++;
|
|
}
|
|
else {
|
|
i = b.cell / t.GetColumns();
|
|
j = b.cell % t.GetColumns();
|
|
}
|
|
t.SetPick(i, j, pick(b.text));
|
|
b.text.Clear();
|
|
t.SetFormat(i, j, b.format);
|
|
t.SetSpan(i, j, b.vspan, b.hspan);
|
|
if(oldtab && b.rown.GetCount() > 1 && j + 1 < b.rown[0])
|
|
b.format = t.GetFormat(0, j + 1);
|
|
else {
|
|
b.cell++;
|
|
b.vspan = 0;
|
|
b.hspan = oldtab;
|
|
}
|
|
b.format.keep = false;
|
|
b.format.round = false;
|
|
}
|
|
|
|
void RichQtfParser::FinishTable()
|
|
{
|
|
FinishCell();
|
|
while(table.Top().cell % Table().GetColumns())
|
|
FinishCell();
|
|
tablepart = pick(Table());
|
|
istable = true;
|
|
table.Drop();
|
|
}
|
|
|
|
void RichQtfParser::FinishOldTable()
|
|
{
|
|
FinishCell();
|
|
Index<int> pos;
|
|
Vector<int> srow;
|
|
RichTable& t = Table();
|
|
Tab& b = table.Top();
|
|
for(int i = 0; i < t.GetRows(); i++) {
|
|
int& s = srow.Add();
|
|
s = 0;
|
|
int nx = b.rown[i];
|
|
for(int j = 0; j < nx; j++)
|
|
s += t.GetSpan(i, j).cx;
|
|
int xn = 0;
|
|
for(int j = 0; j < nx; j++) {
|
|
pos.FindAdd(xn * 10000 / s);
|
|
xn += t.GetSpan(i, j).cx;
|
|
}
|
|
}
|
|
Vector<int> h = pos.PickKeys();
|
|
if(h.GetCount() == 0)
|
|
Error("table");
|
|
Sort(h);
|
|
pos = pick(h);
|
|
pos.Add(10000);
|
|
RichTable tab;
|
|
tab.SetFormat(t.GetFormat());
|
|
for(int i = 0; i < pos.GetCount() - 1; i++) {
|
|
tab.AddColumn(pos[i + 1] - pos[i]);
|
|
}
|
|
for(int i = 0; i < t.GetRows(); i++) {
|
|
int s = srow[i];
|
|
int nx = b.rown[i];
|
|
int xn = 0;
|
|
int xi = 0;
|
|
for(int j = 0; j < nx; j++) {
|
|
Size span = t.GetSpan(i, j);
|
|
xn += span.cx;
|
|
int nxi = pos.Find(xn * 10000 / s);
|
|
tab.SetPick(i, xi, t.GetPick(i, j));
|
|
tab.SetFormat(i, xi, t.GetFormat(i, j));
|
|
tab.SetSpan(i, xi, max(span.cy - 1, 0), nxi - xi - 1);
|
|
xi = nxi;
|
|
}
|
|
}
|
|
table.Drop();
|
|
if(table.GetCount())
|
|
table.Top().text.CatPick(pick(tab));
|
|
else
|
|
target.CatPick(pick(tab));
|
|
oldtab = false;
|
|
}
|
|
|
|
void RichQtfParser::Cat(int chr)
|
|
{
|
|
if(accesskey && ToUpper(ToAscii(chr)) == LOBYTE(accesskey)) {
|
|
Flush();
|
|
format.Underline(!format.IsUnderline());
|
|
text.Cat(chr);
|
|
Flush();
|
|
format.Underline(!format.IsUnderline());
|
|
accesskey = 0;
|
|
}
|
|
else if(chr >= ' ') {
|
|
text.Cat(chr);
|
|
}
|
|
}
|
|
|
|
extern bool s_nodeqtf[128];
|
|
|
|
int GetRichTextScreenStdFontHeight()
|
|
{
|
|
static int gh = 67;
|
|
ONCELOCK {
|
|
for(int i = 0; i < 1000; i++) {
|
|
int h = GetRichTextStdScreenZoom() * i;
|
|
if(h > 0 && StdFont(h).GetCy() == StdFont().GetCy()) {
|
|
gh = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return gh;
|
|
}
|
|
|
|
void RichQtfParser::Parse(const char *qtf, int _accesskey)
|
|
{
|
|
accesskey = _accesskey;
|
|
term = qtf;
|
|
while(*term) {
|
|
if(Key('[')) {
|
|
Flush();
|
|
fstack.Add(format);
|
|
for(;;) {
|
|
int c = *term;
|
|
if(!c)
|
|
Error("Unexpected end of text");
|
|
term++;
|
|
if(c == ' ' || c == '\n') break;
|
|
switch(c) {
|
|
case 's': {
|
|
Uuid id;
|
|
c = *term;
|
|
if(Key('\"') || Key('\''))
|
|
id = target.GetStyleId(GetText(c));
|
|
else {
|
|
int i = ReadNumber();
|
|
if(i >= 0 && i < styleid.GetCount())
|
|
id = styleid[i];
|
|
else
|
|
id = RichStyle::GetDefaultId();
|
|
}
|
|
const RichStyle& s = target.GetStyle(id);
|
|
bool p = format.newpage;
|
|
int lng = format.language;
|
|
(RichPara::Format&) format = s.format;
|
|
format.styleid = id;
|
|
format.language = lng;
|
|
format.newpage = p;
|
|
break;
|
|
}
|
|
case '/': format.Italic(!format.IsItalic()); break;
|
|
case '*': format.Bold(!format.IsBold()); break;
|
|
case '_': format.Underline(!format.IsUnderline()); break;
|
|
case 'T': format.NonAntiAliased(!format.IsNonAntiAliased()); break;
|
|
case '-': format.Strikeout(!format.IsStrikeout()); break;
|
|
case 'c': format.capitals = !format.capitals; break;
|
|
case 'd': format.dashed = !format.dashed; break;
|
|
case '`': format.sscript = format.sscript == 1 ? 0 : 1; break;
|
|
case ',': format.sscript = format.sscript == 2 ? 0 : 2; break;
|
|
case '^': format.link = GetText('^'); break;
|
|
case 'I': format.indexentry = ToUtf32(GetText(';')); break;
|
|
case '+': format.Height(GetNumber()); break;
|
|
case '@': format.ink = GetColor(); break;
|
|
case '$': format.paper = GetColor(); break;
|
|
case 'A': format.Face(Font::ARIAL); break;
|
|
case 'R': format.Face(Font::ROMAN); break;
|
|
case 'C': format.Face(Font::COURIER); break;
|
|
case 'G': format.Face(Font::STDFONT); break;
|
|
case 'S':
|
|
#ifdef PLATFORM_WIN32
|
|
format.Face(Font::SYMBOL);
|
|
#endif
|
|
break;
|
|
case '.': {
|
|
int n = GetNumber();
|
|
if(n >= Font::GetFaceCount())
|
|
Error("Invalid face number");
|
|
format.Face(n); break;
|
|
}
|
|
case '!': {
|
|
String fn = GetText('!');
|
|
int i = Font::FindFaceNameIndex(fn);
|
|
if(i < 0)
|
|
i = Font::ARIAL;
|
|
format.Face(i);
|
|
}
|
|
break;
|
|
case '{': {
|
|
String cs = GetText('}');
|
|
if(cs.GetLength() == 1) {
|
|
int c = *cs;
|
|
if(c == '_')
|
|
format.charset = CHARSET_UTF8;
|
|
if(c >= '0' && c <= '8')
|
|
format.charset = c - '0' + CHARSET_WIN1250;
|
|
if(c >= 'A' && c <= 'Z')
|
|
format.charset = c - '0' + CHARSET_ISO8859_1;
|
|
}
|
|
else {
|
|
for(int i = 0; i < CharsetCount(); i++)
|
|
if(stricmp(CharsetName(i), cs) == 0) {
|
|
format.charset = i;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case '%': {
|
|
String h;
|
|
if(*term == '-') {
|
|
format.language = 0;
|
|
term++;
|
|
}
|
|
else
|
|
if(*term == '%') {
|
|
format.language = LNG_ENGLISH;
|
|
term++;
|
|
}
|
|
else {
|
|
while(*term && h.GetLength() < 5)
|
|
h.Cat(*term++);
|
|
format.language = LNGFromText(h);
|
|
}
|
|
break;
|
|
}
|
|
case 'g':
|
|
format.Face(Font::STDFONT);
|
|
format.Height(GetRichTextScreenStdFontHeight());
|
|
break;
|
|
default:
|
|
if(c >= '0' && c <= '9') {
|
|
format.Height(QTFFontHeight[c - '0']);
|
|
break;
|
|
}
|
|
switch(c) {
|
|
case ':': format.label = GetText(':'); break;
|
|
case '<': format.align = ALIGN_LEFT; break;
|
|
case '>': format.align = ALIGN_RIGHT; break;
|
|
case '=': format.align = ALIGN_CENTER; break;
|
|
case '#': format.align = ALIGN_JUSTIFY; break;
|
|
case 'l': format.lm = GetNumber(); break;
|
|
case 'r': format.rm = GetNumber(); break;
|
|
case 'i': format.indent = GetNumber(); break;
|
|
case 'b': format.before = GetNumber(); break;
|
|
case 'a': format.after = GetNumber(); break;
|
|
case 'P': format.newpage = !format.newpage; break;
|
|
case 'F': format.firstonpage = !format.firstonpage; break;
|
|
case 'k': format.keep = !format.keep; break;
|
|
case 'K': format.keepnext = !format.keepnext; break;
|
|
case 'H': format.ruler = GetNumber(); break;
|
|
case 'h': format.rulerink = GetColor(); break;
|
|
case 'L': format.rulerstyle = GetNumber(); break;
|
|
case 'Q': format.orphan = !format.orphan; break;
|
|
case 'n': format.before_number = GetText(';'); break;
|
|
case 'm': format.after_number = GetText(';'); break;
|
|
case 'N': {
|
|
memset8(format.number, 0, sizeof(format.number));
|
|
format.reset_number = false;
|
|
int i = 0;
|
|
while(i < 8) {
|
|
int c;
|
|
if(Key('-'))
|
|
c = RichPara::NUMBER_NONE;
|
|
else
|
|
if(Key('1'))
|
|
c = RichPara::NUMBER_1;
|
|
else
|
|
if(Key('0'))
|
|
c = RichPara::NUMBER_0;
|
|
else
|
|
if(Key('a'))
|
|
c = RichPara::NUMBER_a;
|
|
else
|
|
if(Key('A'))
|
|
c = RichPara::NUMBER_A;
|
|
else
|
|
if(Key('i'))
|
|
c = RichPara::NUMBER_i;
|
|
else
|
|
if(Key('I'))
|
|
c = RichPara::NUMBER_I;
|
|
else
|
|
break;
|
|
format.number[i++] = c;
|
|
}
|
|
if(Key('!'))
|
|
format.reset_number = true;
|
|
break;
|
|
}
|
|
case 'o': format.bullet = RichPara::BULLET_ROUND;
|
|
format.indent = 150; break;
|
|
case 'O':
|
|
if(Key('_'))
|
|
format.bullet = RichPara::BULLET_NONE;
|
|
else {
|
|
int c = *term++;
|
|
if(!c)
|
|
Error("Unexpected end of text");
|
|
format.bullet =
|
|
c == '1' ? RichPara::BULLET_ROUNDWHITE :
|
|
c == '2' ? RichPara::BULLET_BOX :
|
|
c == '3' ? RichPara::BULLET_BOXWHITE :
|
|
c == '9' ? RichPara::BULLET_TEXT :
|
|
RichPara::BULLET_ROUND;
|
|
}
|
|
break;
|
|
case 'p':
|
|
switch(*term++) {
|
|
case 0: Error("Unexpected end of text");
|
|
case 'h': format.linespacing = RichPara::LSP15; break;
|
|
case 'd': format.linespacing = RichPara::LSP20; break;
|
|
case 'w': format.linespacing = RichPara::LSP115; break;
|
|
default: format.linespacing = RichPara::LSP10;
|
|
}
|
|
break;
|
|
case 't':
|
|
if(*term == 'P') {
|
|
term++;
|
|
format.newhdrftr = true;
|
|
format.header_qtf = GetText2('^', '^');
|
|
format.footer_qtf = GetText2('^', '^');
|
|
}
|
|
else
|
|
if(IsDigit(*term))
|
|
format.tabsize = ReadNumber();
|
|
break;
|
|
case '~': {
|
|
if(Key('~'))
|
|
format.tab.Clear();
|
|
else {
|
|
RichPara::Tab tab;
|
|
Key('<');
|
|
if(Key('>'))
|
|
tab.align = ALIGN_RIGHT;
|
|
if(Key('='))
|
|
tab.align = ALIGN_CENTER;
|
|
if(Key('.'))
|
|
tab.fillchar = 1;
|
|
if(Key('-'))
|
|
tab.fillchar = 2;
|
|
if(Key('_'))
|
|
tab.fillchar = 3;
|
|
int rightpos = Key('>') ? RichPara::TAB_RIGHTPOS : 0;
|
|
tab.pos = rightpos | ReadNumber();
|
|
format.tab.Add(tab);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
SetFormat();
|
|
}
|
|
else
|
|
if(Key(']')) {
|
|
Flush();
|
|
if(fstack.GetCount()) {
|
|
format = fstack.Top();
|
|
fstack.Drop();
|
|
}
|
|
else
|
|
Error("Unmatched ']'");
|
|
}
|
|
else
|
|
if(Key2('{')) {
|
|
if(oldtab)
|
|
Error("{{ in ++ table");
|
|
if(text.GetLength() || paragraph.GetCount())
|
|
EndPart();
|
|
table.Add();
|
|
int r = IsDigit(*term) ? ReadNumber() : 1;
|
|
Table().AddColumn(r);
|
|
while(Key(':'))
|
|
Table().AddColumn(ReadNumber());
|
|
if(breakpage) {
|
|
RichTable& tab = Table();
|
|
RichTable::Format tabformat = tab.GetFormat();
|
|
tabformat.newpage = true;
|
|
tab.SetFormat(tabformat);
|
|
breakpage = false;
|
|
}
|
|
TableFormat();
|
|
SetFormat();
|
|
}
|
|
else
|
|
if(Key2('}')) {
|
|
if(oldtab)
|
|
Error("}} in ++ table");
|
|
FinishTable();
|
|
}
|
|
else
|
|
if(Key2('+'))
|
|
if(oldtab)
|
|
FinishOldTable();
|
|
else {
|
|
Flush();
|
|
if(text.GetLength() || paragraph.GetCount())
|
|
EndPart();
|
|
Tab& b = table.Add();
|
|
b.rown.Add(0);
|
|
b.hspan = 1;
|
|
b.Old();
|
|
oldtab = true;
|
|
}
|
|
else
|
|
if(Key2('|'))
|
|
FinishCell();
|
|
else
|
|
if(Key2('-')) {
|
|
FinishCell();
|
|
table.Top().rown.Add(0);
|
|
}
|
|
else
|
|
if(Key2(':')) {
|
|
if(!oldtab)
|
|
FinishCell();
|
|
TableFormat(oldtab);
|
|
}
|
|
else
|
|
if(Key2('^')) {
|
|
EndPart();
|
|
breakpage = true;
|
|
}
|
|
else
|
|
if(Key2('@')) {
|
|
ReadObject();
|
|
}
|
|
else
|
|
if(Key2('@', '$')) {
|
|
String xu;
|
|
while(isxdigit(*term))
|
|
xu.Cat(*term++);
|
|
int c = stou(~xu, NULL, 16);
|
|
if(c >= 32)
|
|
Cat(c);
|
|
if(*term == ';')
|
|
term++;
|
|
SetFormat();
|
|
}
|
|
else
|
|
if(Key2('^', 'H'))
|
|
target.SetHeaderQtf(GetText2('^', '^'));
|
|
else
|
|
if(Key2('^', 'F'))
|
|
target.SetFooterQtf(GetText2('^', '^'));
|
|
else
|
|
if(Key2('{', ':')) {
|
|
Flush();
|
|
String field = GetText(':');
|
|
String param = GetText(':');
|
|
Id fid(field);
|
|
if(RichPara::fieldtype().Find(fid) >= 0)
|
|
paragraph.Cat(fid, param, format);
|
|
Key('}');
|
|
}
|
|
else
|
|
if(Key('&')) {
|
|
SetFormat();
|
|
EndPart();
|
|
}
|
|
else
|
|
if(Key2('$')) {
|
|
Flush();
|
|
int i = GetNumber();
|
|
Uuid id;
|
|
RichStyle style;
|
|
style.format = format;
|
|
if(Key(','))
|
|
stylenext.At(i, 0) = GetNumber();
|
|
else
|
|
stylenext.At(i, 0) = i;
|
|
if(Key('#')) {
|
|
String xu;
|
|
while(isxdigit(*term))
|
|
xu.Cat(*term++);
|
|
if(xu.GetLength() != 32)
|
|
Error("Invalid UUID !");
|
|
id = ScanUuid(xu);
|
|
}
|
|
else
|
|
if(i)
|
|
id = Uuid::Create();
|
|
else
|
|
id = RichStyle::GetDefaultId();
|
|
if(Key(':'))
|
|
style.name = GetText(']');
|
|
if(fstack.GetCount()) {
|
|
format = fstack.Top();
|
|
fstack.Drop();
|
|
}
|
|
target.SetStyle(id, style);
|
|
styleid.At(i, RichStyle::GetDefaultId()) = id;
|
|
if(id == RichStyle::GetDefaultId()) {
|
|
bool p = format.newpage;
|
|
int lng = format.language;
|
|
(RichPara::Format&) format = style.format;
|
|
format.styleid = id;
|
|
format.language = lng;
|
|
format.newpage = p;
|
|
}
|
|
}
|
|
else
|
|
if(*term == '_') {
|
|
SetFormat();
|
|
text.Cat(160);
|
|
term++;
|
|
}
|
|
else
|
|
if(Key2('-', '|')) {
|
|
SetFormat();
|
|
text.Cat(9);
|
|
}
|
|
else
|
|
if(*term == '\1') {
|
|
if(istable)
|
|
EndPart();
|
|
SetFormat();
|
|
const char *b = ++term;
|
|
for(; *term && *term != '\1'; term++)
|
|
if((byte)*term < 32) {
|
|
text.Cat(ToUnicode(b, (int)(term - b), format.charset));
|
|
if(*term == '\n')
|
|
EndPart();
|
|
if(*term == '\t')
|
|
text.Cat(9);
|
|
b = term + 1;
|
|
}
|
|
text.Cat(ToUnicode(b, (int)(term - b), format.charset));
|
|
if(*term == '\1')
|
|
term++;
|
|
}
|
|
else {
|
|
if(!Key('`')) Key('\\');
|
|
if((byte)*term >= ' ') {
|
|
SetFormat();
|
|
do {
|
|
if(istable)
|
|
EndPart();
|
|
if(format.charset == CHARSET_UTF8) {
|
|
bool ok = true;
|
|
wchar c = FetchUtf8(term, ok);
|
|
if(ok)
|
|
Cat(c);
|
|
else
|
|
Error("Invalid UTF-8 sequence");
|
|
}
|
|
else
|
|
Cat(ToUnicode((byte)*term++, format.charset));
|
|
}
|
|
while((byte)*term >= 128 || s_nodeqtf[(byte)*term]);
|
|
}
|
|
else
|
|
if(*term)
|
|
term++;
|
|
}
|
|
}
|
|
if(oldtab)
|
|
FinishOldTable();
|
|
else
|
|
while(table.GetCount())
|
|
FinishTable();
|
|
EndPart();
|
|
FlushStyles();
|
|
}
|
|
|
|
bool ParseQTF(RichText& txt, const char *qtf, int accesskey, void *context)
|
|
{
|
|
RichQtfParser p(context);
|
|
try {
|
|
p.Parse(qtf, accesskey);
|
|
}
|
|
catch(RichQtfParser::Exc) {
|
|
return false;
|
|
}
|
|
txt = pick(p.target);
|
|
return true;
|
|
}
|
|
|
|
RichText ParseQTF(const char *qtf, int accesskey, void *context)
|
|
{
|
|
RichQtfParser p(context);
|
|
try {
|
|
p.Parse(qtf, accesskey);
|
|
}
|
|
catch(RichQtfParser::Exc) {}
|
|
return pick(p.target);
|
|
}
|
|
|
|
String QtfRichObject::ToString() const
|
|
{
|
|
return String("@@#").Cat() << uintptr_t(&obj) << ";";
|
|
}
|
|
|
|
QtfRichObject::QtfRichObject(const RichObject& o)
|
|
: obj(o)
|
|
{}
|
|
|
|
}
|