ultimatepp/uppsrc/RichText/EncodeRTF.cpp
mdelfede d2b54f7989 changed svn layout
git-svn-id: svn://ultimatepp.org/upp/trunk@281 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2008-06-07 22:31:27 +00:00

658 lines
19 KiB
C++

#include "RichText.h"
#include <plugin/png/png.h>
NAMESPACE_UPP
static int GetParaHeight(const Array<RichPara::Part>& parts)
{
int ht = 0;
for(int i = 0; i < parts.GetCount(); i++) {
int pht = 0;
const RichPara::Part& part = parts[i];
if(part.object)
pht = part.object.GetSize().cy;
else if(part.field)
pht = GetParaHeight(part.fieldpart);
else
pht = tabs(part.format.GetHeight());
if(pht > ht)
ht = pht;
}
return ht;
}
class RTFEncoder {
public:
RTFEncoder(Stream& stream, const RichText& richtext, byte charset);
void Run();
private:
void FacesAddFormat(const RichPara::CharFormat& format);
void GetFaces();
void GetTxtFaces(const RichTxt& txt);
void PutHeader();
void PutDocument();
void PutTxt(const RichTxt& txt, int nesting, int dot_width);
void PutTable(const RichTable& table, int nesting, int dot_width);
void PutParts(const Array<RichPara::Part>& parts,
RichPara::CharFormat& base, int bpart, int boff, int epart, int eoff);
void Begin() { stream.Put('{'); }
void Begin(const char *cmd) { Begin(); Command(cmd); }
void Begin(const char *cmd, int param) { Begin(); Command(cmd, param); }
void End() { stream.Put('}'); }
void Command(const char *cmd);
void Command(const char *cmd, int param);
void Space() { stream.Put(' '); }
void PutText(const char *text);
void PutObject(const RichObject& object);
void PutTabs(const Vector<RichPara::Tab>& tabs);
void PutBinHex(const byte *data, int count);
void PutBinHex(const String& s) { PutBinHex(s, s.GetLength()); }
bool PutParaFormat(const RichPara::Format& pf, const RichPara::Format& difpf);
bool PutCharFormat(const RichPara::CharFormat& cf, const RichPara::CharFormat& difcf, bool pn);
struct Group;
friend struct Group;
struct Group {
Group(RTFEncoder *owner) : owner(owner) { owner->Begin(); }
Group(RTFEncoder *owner, const char *cmd) : owner(owner) { owner->Begin(cmd); }
Group(RTFEncoder *owner, const char *cmd, int param) : owner(owner) { owner->Begin(cmd, param); }
~Group() { owner->End(); }
RTFEncoder *owner;
};
private:
Stream& stream;
const RichText& richtext;
byte charset;
RichPara::CharFormat charfmt;
RichPara::Format parafmt;
Uuid oldstyle;
int old_ht;
int para_ht;
Index<int> used_faces;
enum { SYMBOL_INDEX = 1, WINGDINGS_INDEX = 2 };
VectorMap<Color, int> used_ink, used_paper;
Index<Color> phys_colors;
Index<Uuid> styleid;
};
void EncodeRTF(Stream& stream, const RichText& richtext, byte charset)
{
RTFEncoder(stream, richtext, charset).Run();
}
String EncodeRTF(const RichText& richtext, byte charset)
{
StringStream out;
EncodeRTF(out, richtext, charset);
String s = out.GetResult();
LOG("EncodeRTF <<<<<\n" << s << "\n>>>>> EncodeRTF");
return s;
}
String EncodeRTF(const RichText& richtext)
{
return EncodeRTF(richtext, GetDefaultCharset() == CHARSET_UTF8 ? GetLNGCharset(GetSystemLNG())
: GetDefaultCharset());
}
RTFEncoder::RTFEncoder(Stream& stream, const RichText& richtext, byte charset)
: stream(stream)
, richtext(richtext)
, charset(charset)
{
for(int i = 0; i < richtext.GetStyleCount(); i++)
styleid.Add(richtext.GetStyleId(i));
}
void RTFEncoder::Run()
{
GetFaces();
Group docgrp(this, "rtf");
PutHeader();
PutDocument();
}
void RTFEncoder::FacesAddFormat(const RichPara::CharFormat& format)
{
used_faces.FindAdd(format.GetFace());
if(used_ink.Find(format.ink) < 0) {
Color i(format.ink.GetR(), format.ink.GetG(), format.ink.GetB());
int x = used_ink.Get(i, -1);
if(x < 0)
x = phys_colors.FindAdd(i);
used_ink.Add(format.ink, x);
}
if(used_paper.Find(format.paper) < 0) {
Color p(format.paper.GetR(), format.paper.GetG(), format.paper.GetB());
int x = used_paper.Get(p, -1);
if(x < 0)
x = phys_colors.FindAdd(p);
used_paper.Add(format.paper, x);
}
}
void RTFEncoder::GetFaces()
{
used_faces.Add(Font::ARIAL); // default font
used_faces.Add(Font::SYMBOL); // used for bullets
#ifdef PLATFORM_WIN32
used_faces.Add(Font::WINGDINGS); // used for bullets
#endif
phys_colors.Add(Null);
used_ink.Add(Null, 0);
used_paper.Add(Null, 0);
used_ink.Add(Black(), 0);
used_paper.Add(White(), 0);
GetTxtFaces(richtext);
for(int i = 0; i < richtext.GetStyleCount(); i++)
FacesAddFormat(richtext.GetStyle(i).format);
}
void RTFEncoder::GetTxtFaces(const RichTxt& rt)
{
for(int i = 0; i < rt.GetPartCount(); i++) {
if(rt.IsTable(i)) {
const RichTable& table = rt.GetTable(i);
const RichTable::Format& tfmt = table.GetFormat();
phys_colors.FindAdd(tfmt.framecolor);
phys_colors.FindAdd(tfmt.gridcolor);
for(int r = 0; r < table.GetRows(); r++)
for(int c = 0; c < table.GetColumns(); c++) {
const RichCell& cell = table.cell[r][c];
phys_colors.FindAdd(cell.format.color);
phys_colors.FindAdd(cell.format.bordercolor);
GetTxtFaces(cell.text);
}
}
else {
const RichPara& para = rt.Get(i, richtext.GetStyles());
FacesAddFormat(para.format);
for(int p = 0; p < para.part.GetCount(); p++) {
const RichPara::Part& part = para.part[p];
if(part.IsText())
FacesAddFormat(part.format);
}
}
}
}
void RTFEncoder::Command(const char *cmd)
{
stream.Put('\\');
stream.Put(cmd);
}
void RTFEncoder::Command(const char *cmd, int param)
{
stream.Put('\\');
stream.Put(cmd);
stream.Put(IntStr(param));
}
void RTFEncoder::PutText(const char *text)
{
while(*text) {
if(*text == '{' || *text == '}' || *text == '\\')
stream.Put('\\');
stream.Put(*text++);
}
}
void RTFEncoder::PutBinHex(const byte *b, int count)
{
enum { BLOCK = 32 };
for(int l = count; l > 0; l -= BLOCK) {
stream.PutCrLf();
for(const byte *e = b + min<int>(l, BLOCK); b < e; b++) {
static const char binhex[] = "0123456789abcdef";
stream.Put(binhex[*b >> 4]);
stream.Put(binhex[*b & 15]);
}
}
}
bool RTFEncoder::PutCharFormat(const RichPara::CharFormat& cf, const RichPara::CharFormat& difcf, bool pn)
{
int64 pos = stream.GetPos();
int pn2 = (pn ? 0 : 2);
bool f;
int t;
if(cf.GetFace() != difcf.GetFace())
Command("pnf" + pn2, used_faces.Find(cf.GetFace()));
if((t = DotPoints(2 * tabs(cf.GetHeight()))) != DotPoints(2 * tabs(difcf.GetHeight())))
Command("pnfs" + pn2, t);
if(!pn && dword(t = cf.sscript) != difcf.sscript)
Command(t == 0 ? "nosupersub" : t == 1 ? "super" : "sub");
if((f = cf.IsBold()) != difcf.IsBold()) Command((f ? "pnb" : "pnb0") + pn2);
if((f = cf.IsItalic()) != difcf.IsItalic()) Command((f ? "pni" : "pni0") + pn2);
if((f = cf.IsUnderline()) != difcf.IsUnderline()) Command((f ? "pnul" : "pnul0") + pn2);
if((f = cf.IsStrikeout()) != difcf.IsStrikeout()) Command((f ? "pnstrike" : "pnstrike0") + pn2);
if((f = cf.capitals) != difcf.capitals) Command((f ? "pncaps" : "pncaps0") + pn2);
if((t = used_ink.Get(cf.ink)) != used_ink.Get(difcf.ink)) Command("pncf" + pn2, t);
if((t = used_paper.Get(cf.paper)) != used_paper.Get(difcf.paper)) Command("pncb" + pn2, t);
#ifdef PLATFORM_WIN32 //zapoznamkoval Fidler kdyz chtel zkompilovat pod Linuxem...
if(!pn && cf.language != difcf.language) Command("lang", GetLanguageLCID(cf.language));
#endif
// todo: link
return stream.GetPos() != pos;
}
void RTFEncoder::PutObject(const RichObject& object)
{
#ifdef PLATFORM_WIN32
#ifndef PLATFORM_WINCE
Size log_size = object.GetPixelSize(), out_size = object.GetSize();
if(log_size.cx <= 0 || log_size.cy <= 0) log_size = out_size;
// Size scale = out_size * 100 / log_size;
Group pict_grp(this, "pict");
Command("picw", log_size.cx);
Command("pich", log_size.cy);
Command("picwgoal", DotTwips(out_size.cx));
Command("pichgoal", DotTwips(out_size.cy));
// Command("picscalex", scale.cx);
// Command("picscaley", scale.cy);
if(object.GetTypeName() == "PING") {
Command("pngblip");
PutBinHex(object.GetData());
}
else {
Command("wmetafile", 8);
WinMetaFileDraw wmd(log_size.cx, log_size.cy);
object.Paint(wmd, log_size);
WinMetaFile wmf = wmd.Close();
HENHMETAFILE hemf = wmf.GetHEMF();
int size = GetWinMetaFileBits(hemf, 0, 0, MM_ANISOTROPIC, ScreenHDC());
if(size > 0) {
Buffer<byte> buffer(size);
GetWinMetaFileBits(hemf, size, buffer, MM_ANISOTROPIC, ScreenHDC());
PutBinHex(buffer, size);
}
}
#endif
#endif
}
bool RTFEncoder::PutParaFormat(const RichPara::Format& pf, const RichPara::Format& difpf)
{
int64 pos = stream.GetPos();
if(pf.align != difpf.align)
switch(pf.align) {
case ALIGN_NULL:
case ALIGN_LEFT: Command("ql"); break;
case ALIGN_CENTER: Command("qc"); break;
case ALIGN_RIGHT: Command("qr"); break;
case ALIGN_JUSTIFY: Command("qj"); break;
default: NEVER();
}
int oind = difpf.indent, olm = difpf.lm;
if(difpf.bullet != RichPara::BULLET_NONE) {
olm += oind;
oind = -oind;
}
int nind = pf.indent, nlm = pf.lm;
if(pf.bullet != RichPara::BULLET_NONE) {
nlm += nind;
nind = -nind;
}
if(nind != oind) Command("fi", DotTwips(nind));
if(nlm != olm) Command("li", DotTwips(nlm));
if(pf.rm != difpf.rm) Command("ri", DotTwips(pf.rm));
if(pf.newpage)
Command("pagebb");
if(pf.before != difpf.before) Command("sb", DotTwips(pf.before));
if(pf.after != difpf.after) Command("sa", DotTwips(pf.after));
if(pf.tab != difpf.tab) PutTabs(pf.tab);
return stream.GetPos() != pos;
}
void RTFEncoder::PutTabs(const Vector<RichPara::Tab>& tabs)
{
for(int i = 0; i < tabs.GetCount(); i++) {
RichPara::Tab t = tabs[i];
switch(t.align) {
case ALIGN_NULL:
case ALIGN_LEFT: break;
case ALIGN_CENTER: Command("tqc"); break;
case ALIGN_RIGHT: Command("tqr"); break;
default: NEVER();
}
switch(t.fillchar) {
case 0: break;
case 1: Command("tldot"); break;
case 2: Command("tlhyph"); break;
case 3: Command("tlul"); break;
default: NEVER();
}
Command("tx", DotTwips(t.pos));
}
}
void RTFEncoder::PutParts(const Array<RichPara::Part>& parts,
RichPara::CharFormat& base, int bpart, int boff, int epart, int eoff)
{
if(epart >= parts.GetCount()) {
epart = parts.GetCount() - 1;
eoff = INT_MAX;
}
ASSERT(boff >= 0 && bpart >= 0);
for(int p = bpart; p <= epart; p++) {
const RichPara::Part& part = parts[p];
if(part.IsText()) {
WString px = part.text;
if(p == epart && px.GetLength() > eoff)
px.Trim(eoff);
if(p == bpart)
px.Remove(0, boff);
if(!px.IsEmpty()) {
if(PutCharFormat(part.format, base, false) || p == 0)
Space();
base = part.format;
PutText(FromUnicode(px, charset));
}
}
else if(part.object)
PutObject(part.object);
}
}
void RTFEncoder::PutHeader()
{
static const short ansicpg[][2] = {
{ CHARSET_WIN1250, 1250 },
{ CHARSET_WIN1251, 1251 },
{ CHARSET_WIN1252, 1252 },
{ CHARSET_WIN1253, 1253 },
{ CHARSET_WIN1254, 1254 },
{ CHARSET_WIN1255, 1255 },
{ CHARSET_WIN1256, 1256 },
{ CHARSET_WIN1257, 1257 },
{ CHARSET_WIN1258, 1258 },
};
int i;
for(i = __countof(ansicpg); --i >= 0;)
if(charset == ansicpg[i][0]) {
Command("ansicpg", ansicpg[i][1]);
break;
}
Command("deff", 0);
{
Group ftbl(this, "fonttbl");
for(int i = 0; i < used_faces.GetCount(); i++) {
Group fnt(this, "f", i);
int fn = used_faces[i];
dword info = Font::GetFaceInfo(fn);
if(info & Font::SYMBOLTYPE)
Command("ftech");
else if(info & Font::FIXEDPITCH)
Command("fmodern");
else if(fn == Font::ROMAN)
Command("froman");
else if(fn == Font::ARIAL
#ifdef PLATFORM_WIN32
|| fn == Font::TAHOMA
#endif
)
Command("fswiss");
Space();
stream.Put(Font::GetFaceName(fn));
stream.Put(';');
}
}
if(phys_colors.GetCount() > 1) {
Group ctbl(this, "colortbl");
stream.Put(';');
for(int i = 1; i < phys_colors.GetCount(); i++) {
Color rgb = phys_colors[i];
Command("red", rgb.GetR());
Command("green", rgb.GetG());
Command("blue", rgb.GetB());
stream.Put(';');
}
}
Begin("stylesheet");
RichPara::CharFormat empcfmt;
RichPara::Format emppfmt;
for(i = 0; i < richtext.GetStyleCount(); i++) {
const RichStyle& style = richtext.GetStyle(i);
Begin("s", i);
PutParaFormat(style.format, emppfmt);
PutCharFormat(style.format, empcfmt, false);
Command("sbasedon", 222);
if(style.next != richtext.GetStyleId(i))
Command("snext", styleid.Find(style.next));
Space();
for(const char *n = style.name; *n; n++)
stream.Put(*n == ';' ? '_' : *n);
stream.Put(';');
End();
}
End();
}
void RTFEncoder::PutTable(const RichTable& table, int nesting, int dot_width)
{
Vector<int> vspan_counts;
const RichTable::Format& tfmt = table.GetFormat();
Vector<int> column_pos;
column_pos.SetCount(tfmt.column.GetCount() + 1);
column_pos[0] = tfmt.lm;
int sum = 0;
for(int c = 0; c < tfmt.column.GetCount(); c++)
sum += tfmt.column[c];
int rem_width = dot_width - tfmt.lm - tfmt.rm;
for(int c = 0; c < tfmt.column.GetCount(); c++) {
int part = iscale(tfmt.column[c], rem_width, max(sum, 1));
sum -= tfmt.column[c];
rem_width -= part;
column_pos[c + 1] = column_pos[c] + part;
}
for(int r = 0; r < table.GetRows(); r++) {
String rowfmt;
if(nesting)
rowfmt << "\\*\\nesttableprops";
rowfmt << "\\trowd"
"\\trleft" << DotTwips(tfmt.lm)
// trbrdr[ltrbv]
;
Vector<int> cellindex;
Rect dflt_margin(600, 600, 600, 600);
for(int c = 0; c < table.GetColumns(); c++) {
const RichCell& cell = table.cell[r][c];
dflt_margin.left = min(dflt_margin.left, cell.format.margin.left);
dflt_margin.top = min(dflt_margin.top, cell.format.margin.top);
dflt_margin.right = min(dflt_margin.right, cell.format.margin.right);
dflt_margin.bottom = min(dflt_margin.bottom, cell.format.margin.bottom);
c += cell.hspan;
}
/*
rowfmt <<
"\\trpaddl" << DotTwips(dflt_margin.left) << "\\trpaddfl3"
"\\trpaddt" << DotTwips(dflt_margin.top) << "\\trpaddft3"
"\\trpaddr" << DotTwips(dflt_margin.right) << "\\trpaddfr3"
"\\trpaddb" << DotTwips(dflt_margin.bottom) << "\\trpaddfb3";
*/
for(int c = 0; c < table.GetColumns(); c++) {
const RichCell& cell = table.cell[r][c];
/*
if(cell.format.margin.left != dflt_margin.left)
rowfmt << "\\clpadl" << DotTwips(cell.format.margin.left) << "\\clpadfl3";
if(cell.format.margin.top != dflt_margin.top)
rowfmt << "\\clpadt" << DotTwips(cell.format.margin.top) << "\\clpadft3";
if(cell.format.margin.right != dflt_margin.right)
rowfmt << "\\clpadr" << DotTwips(cell.format.margin.right) << "\\clpadfr3";
if(cell.format.margin.bottom != dflt_margin.bottom)
rowfmt << "\\clpadb" << DotTwips(cell.format.margin.bottom) << "\\clpadfb3";
*/
switch(cell.format.align) {
case ALIGN_TOP: rowfmt << "\\clvertalt"; break;
default:
case ALIGN_CENTER: rowfmt << "\\clvertalc"; break;
case ALIGN_BOTTOM: rowfmt << "\\clvertalb"; break;
}
cellindex.Add(c);
if(cell.hspan)
c += cell.hspan;
int cell_end = column_pos[c + 1];
if(cell.vspan) {
vspan_counts.At(c, 0) = cell.vspan;
rowfmt << "\\clvmgf";
}
else if(c < vspan_counts.GetCount() && vspan_counts[c] > 0)
rowfmt << "\\clvmrg";
if(!IsNull(cell.format.color))
rowfmt << "\\clcbpat" << phys_colors.Find(cell.format.color);
rowfmt << "\\cellx" << DotTwips(cell_end);
}
Begin();
Command("pard");
Command("intbl");
parafmt = RichPara::Format();
charfmt = RichPara::CharFormat();
if(nesting)
Command("itap", nesting + 1);
if(!nesting)
stream.Put(rowfmt);
for(int c = 0; c < cellindex.GetCount(); c++) {
int cx = cellindex[c];
const RichCell& cell = table.cell[r][cx];
int cell_wd = column_pos[cx + cell.hspan + 1] - column_pos[cx];
PutTxt(cell.text, nesting + 1, cell_wd);
Command(nesting ? "nestcell" : "cell");
}
stream.Put(rowfmt);
Command(nesting ? "nestrow" : "row");
End();
}
}
void RTFEncoder::PutTxt(const RichTxt& txt, int nesting, int dot_width)
{
for(int i = 0; i < txt.GetPartCount(); i++) {
if(txt.IsTable(i))
PutTable(txt.GetTable(i), nesting, dot_width);
else {
const RichPara& para = txt.Get(i, richtext.GetStyles());
Uuid pstyle = txt.GetParaStyle(i);
int para_ht = GetParaHeight(para.part);
int first_part = 0, first_ind = 0;
bool hdiff = (para.format.bullet != RichPara::BULLET_NONE
&& para.format.bullet != RichPara::BULLET_TEXT && para_ht != para_ht);
if(pstyle != oldstyle
|| para.format.bullet != parafmt.bullet || para.format.bullet == RichPara::BULLET_TEXT
|| hdiff || para.format.tab != parafmt.tab || para.format.newpage || parafmt.newpage) {
Command("s", styleid.Find(pstyle));
oldstyle = pstyle;
parafmt = richtext.GetStyle(pstyle).format;
Command("pard");
if(nesting)
Command("intbl");
if(nesting > 1)
Command("itap", nesting);
parafmt = RichPara::Format();
para_ht = 0;
}
if(para.format.bullet == RichPara::BULLET_TEXT) {
int epart = 0, eoff = 0;
while(epart < para.part.GetCount() && (eoff = para.part[epart].text.Find('\t')) < 0)
epart++;
RichPara::CharFormat rcf = charfmt;
rcf.Height(0);
Begin("pntext");
PutParts(para.part, rcf, 0, 0, epart, eoff);
stream.Put('\t');
End();
Begin("*\\pn");
Command("pnhang");
Command("pnql");
rcf = charfmt;
if(epart > 0 || eoff > 0)
PutCharFormat(para.part[0].format, rcf, true);
Begin("pntxta");
End();
Begin("pntxtb");
Space();
PutParts(para.part, rcf, 0, 0, epart, eoff);
stream.Put('\t');
End();
End();
}
else if(para.format.bullet != RichPara::BULLET_NONE) {
int sym_face = SYMBOL_INDEX;
byte sym_char = 0xB7;
switch(para.format.bullet) {
default:
NEVER();
case RichPara::BULLET_ROUND: sym_face = SYMBOL_INDEX; sym_char = 0xB7; break;
case RichPara::BULLET_ROUNDWHITE: sym_face = WINGDINGS_INDEX; sym_char = 0xA1; break;
case RichPara::BULLET_BOX: sym_face = WINGDINGS_INDEX; sym_char = 'n'; break;
case RichPara::BULLET_BOXWHITE: sym_face = WINGDINGS_INDEX; sym_char = 'o'; break;
}
Begin("pntext");
// Command("tqr");
// Command("tx", DotTwips(para.format.indent));
// Command("qr");
Command("f", sym_face);
Command("fs", DotPoints(2 * para_ht));
Space();
stream.Put(sym_char);
stream.Put('\t');
End();
Begin("*\\pn");
Command("pnlvlblt");
Command("pnf", sym_face);
Command("pnfs", DotPoints(2 * para_ht));
Command("pnql");
Begin("pntxtb");
Space();
stream.Put(sym_char);
// stream.Put('\t');
End();
Begin("pntxta");
End();
End();
}
PutParaFormat(para.format, parafmt);
para_ht = para_ht;
parafmt = para.format;
PutParts(para.part, charfmt, first_part, first_ind, para.part.GetCount(), 0);
if(i + 1 < txt.GetPartCount())
Command("par");
}
}
}
void RTFEncoder::PutDocument()
{
charfmt.Height(PointDots(0));
charfmt.language = 0;
old_ht = DotPoints(2 * tabs(charfmt.GetHeight()));
para_ht = 0;
oldstyle = Uuid::Create();
PutTxt(richtext, 0, 3600);
}
END_UPP_NAMESPACE