#include "CtrlCore.h" #define LLOG(x) #include namespace Upp { static int GetParaHeight(const Array& 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, Size dot_page_size, const Rect& dot_margins, void *context); 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& 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 wchar *text); void PutObject(const RichObject& object); void PutTabs(const Vector& tabs); void PutBinHex(const byte *data, int count); void PutBinHex(const String& s) { PutBinHex(s, s.GetLength()); } void PutBorder(StringBuffer& rowfmt, int wd, Color c, const char *clpar); 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; Size dot_page_size; Rect dot_margins; RichPara::CharFormat charfmt; RichPara::Format parafmt; Uuid oldstyle; int old_ht; int para_ht; Index used_faces; enum { SYMBOL_INDEX = 1, WINGDINGS_INDEX = 2 }; VectorMap used_ink, used_paper; Index phys_colors; Index styleid; }; void EncodeRTF(Stream& stream, const RichText& richtext, byte charset, Size dot_page_size, const Rect& dot_margins, void *context) { RTFEncoder(stream, richtext, charset, dot_page_size, dot_margins, context).Run(); } String EncodeRTF(const RichText& richtext, byte charset, Size dot_page_size, const Rect& dot_margins, void *context) { StringStream out; EncodeRTF(out, richtext, charset, dot_page_size, dot_margins, context); String s = out.GetResult(); LLOG("EncodeRTF <<<<<\n" << s << "\n>>>>> EncodeRTF"); return s; } String EncodeRTF(const RichText& richtext, byte charset, int dot_page_width) { int dot_page_height = iscale(dot_page_width, 1414, 1000); Rect margins(472, 472, 472, 472); return EncodeRTF(richtext, charset, Size(dot_page_width + margins.left + margins.right, dot_page_height + margins.top + margins.bottom), margins); } String EncodeRTF(const RichText& richtext) { return EncodeRTF(richtext, GetDefaultCharset() == CHARSET_UTF8 ? GetLNGCharset(GetSystemLNG()) : GetDefaultCharset()); } RTFEncoder::RTFEncoder(Stream& stream_, const RichText& richtext_, byte charset_, Size dot_page_size_, const Rect& dot_margins_, void *context_) : stream(stream_) , richtext(richtext_) , charset(charset_) , dot_page_size(dot_page_size_) , dot_margins(dot_margins_) { for(int i = 0; i < richtext.GetStyleCount(); i++) styleid.Add(richtext.GetStyleId(i)); } void RTFEncoder::Run() { GetFaces(); Group docgrp(this, "rtf", 1); Command("ansi"); 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 #ifdef PLATFORM_WIN32 used_faces.Add(Font::SYMBOL); // used for bullets 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[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 wchar *text) { for(; *text; text++) if(*text >= 128) { if(*text >= 65536) { Vector h = ToUtf16(*text); stream << "\\uc0 \\u" << (unsigned)h[0] << "\\uc1 \\u" << (unsigned)h[1]; } else stream.Put(Format("\\u%d?", (int16)*text)); } else { if(*text == '{' || *text == '}' || *text == '\\') stream.Put('\\'); stream.Put((byte)*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(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 if(!pn && cf.language != difcf.language) Command("lang", GetLanguageLCID(cf.language)); #endif // todo: link return stream.GetPos() != pos; } void RTFEncoder::PutObject(const RichObject& object) { Size log_size = object.GetPhysicalSize(); // / 6; Size out_size = object.GetSize(); Size out_size_01mm = iscale(out_size, 254, 60); if(log_size.cx <= 0 || log_size.cy <= 0) log_size = out_size / 6; Group pict_grp(this, "pict"); Command("picw", out_size_01mm.cx); Command("pich", out_size_01mm.cy); Command("picwgoal", DotTwips(out_size.cx)); Command("pichgoal", DotTwips(out_size.cy)); Command("pngblip"); RichObjectPaintInfo pi; pi.ink = charfmt.ink; PutBinHex(PNGEncoder().SaveString(object.ToImage(out_size, pi))); } 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); Command("fi", DotTwips(nind)); // we always put number last as quick patch to "space at bullet" problem return stream.GetPos() != pos; } void RTFEncoder::PutTabs(const Vector& 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& 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(px); } } else if(part.object) PutObject(part.object); } } void RTFEncoder::PutHeader() { struct CodePage { byte charset; short ansicpg; short fontcpg; }; static const CodePage ansicpg[] = { { CHARSET_WIN1250, 1250, 238 }, { CHARSET_WIN1251, 1251, 204 }, { CHARSET_WIN1252, 1252, 0 }, { CHARSET_WIN1253, 1253, 161 }, { CHARSET_WIN1254, 1254, 162 }, { CHARSET_WIN1255, 1255, 177 }, { CHARSET_WIN1256, 1256, 178 }, { CHARSET_WIN1257, 1257, 186 }, { CHARSET_WIN1258, 1258, 163 }, }; int fontcpg = Null; for(int i = __countof(ansicpg); --i >= 0;) if(charset == ansicpg[i].charset) { Command("ansicpg", ansicpg[i].ansicpg); fontcpg = ansicpg[i].fontcpg; 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]; int cs = fontcpg; dword info = Font::GetFaceInfo(fn); #ifdef PLATFORM_WIN32 if(fn == Font::SYMBOL) { Command("ftech"); cs = 2; } else #endif 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"); if(!IsNull(fontcpg)) Command("fcharset", cs); 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(int 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(); Command("paperw", DotTwips(dot_page_size.cx)); Command("paperh", DotTwips(dot_page_size.cy)); Command("margl", DotTwips(dot_margins.left)); Command("margr", DotTwips(dot_margins.right)); Command("margt", DotTwips(dot_margins.top)); Command("margb", DotTwips(dot_margins.bottom)); } void RTFEncoder::PutTable(const RichTable& table, int nesting, int dot_width) { Vector vspan_counts; const RichTable::Format& tfmt = table.GetFormat(); Vector 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++) { StringBuffer rowfmt; if(nesting) rowfmt << "\\*\\nesttableprops"; rowfmt << "\\trowd" "\\trleft" << DotTwips(tfmt.lm) // trbrdr[ltrbv] ; Vector cellindex; Rect dflt_margin(600, 600, 600, 600); for(int c = 0; c < table.GetColumns(); c++) { const RichCell& cell = table[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"; */ bool istop = (r == 0); bool isbottom = (r == table.GetRows() - 1); for(int c = 0; c < table.GetColumns(); c++) { const RichCell& cell = table[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; } bool isleft = (c == 0); cellindex.Add(c); if(cell.hspan) c += cell.hspan; bool isright = (c + 1 == table.GetColumns()); 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); int lb, tb, rb, bb; Color lc, tc, rc, bc; const RichTable::Format& tf = table.GetFormat(); lc = tc = rc = bc = tf.gridcolor; lb = tb = rb = bb = (!IsNull(tf.gridcolor) ? tf.grid : 0); int fw = (!IsNull(tf.framecolor) ? tf.frame : 0); if(isleft) { lb = fw; lc = tf.framecolor; } if(isright) { rb = fw; rc = tf.framecolor; } if(istop) { tb = fw; tc = tf.framecolor; } if(isbottom) { bb = fw; bc = tf.framecolor; } if(!IsNull(cell.format.bordercolor)) { if(cell.format.border.left >= max(lb, 1)) { lb = cell.format.border.left; lc = cell.format.bordercolor; } if(cell.format.border.top >= max(tb, 1)) { tb = cell.format.border.top; tc = cell.format.bordercolor; } if(cell.format.border.right >= max(rb, 1)) { rb = cell.format.border.right; rc = cell.format.bordercolor; } if(cell.format.border.bottom >= max(bb, 1)) { bb = cell.format.border.bottom; bc = cell.format.bordercolor; } } PutBorder(rowfmt, lb, lc, "clbrdrl"); PutBorder(rowfmt, tb, tc, "clbrdrt"); PutBorder(rowfmt, rb, rc, "clbrdrr"); PutBorder(rowfmt, bb, bc, "clbrdrb"); rowfmt << "\\cellx" << DotTwips(cell_end); } Begin(); Command("pard"); Command("intbl"); parafmt = RichPara::Format(); charfmt = RichPara::CharFormat(); if(nesting) Command("itap", nesting + 1); String fmtstr = rowfmt; if(!nesting) stream.Put(fmtstr); for(int c = 0; c < cellindex.GetCount(); c++) { int cx = cellindex[c]; const RichCell& cell = table[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"); } if(nesting) stream.Put(fmtstr); Command(nesting ? "nestrow" : "row"); End(); } oldstyle = Null; } void RTFEncoder::PutBorder(StringBuffer& rowfmt, int wd, Color c, const char *clpar) { if(wd > 0) { rowfmt << "\\" << clpar << "\\brdrs\\brdrw" << wd << "\\brdrcf" << phys_colors.Find(Nvl(c, Black())) << " "; } } 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 new_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 && new_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(); // bool isul = charfmt.IsUnderline(); // charfmt = RichPara::CharFormat(); // charfmt.Underline(isul); 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 * new_para_ht)); Space(); stream.Put(sym_char); stream.Put('\t'); End(); Begin("*\\pn"); Command("pnlvlblt"); Command("pnf", sym_face); Command("pnfs", DotPoints(2 * new_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 = new_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, dot_page_size.cx - dot_margins.left - dot_margins.right); } }