#include "RichBook.h" #pragma hdrstop #include #include NAMESPACE_UPP struct FieldTypeVarCls : public RichPara::FieldType { virtual Array Evaluate(const String& param, VectorMap& vars, const RichPara::CharFormat& fmt); }; Array FieldTypeVarCls::Evaluate(const String& dest, VectorMap& vars, const RichPara::CharFormat& linkfmt) { RichPara para; RichPara::CharFormat fmt = linkfmt; int f = vars.Find(dest); if(f < 0) { fmt.ink = LtRed(); fmt.paper = WhiteGray(); para.Cat(dest, fmt); } else para.Cat(StdFormat(vars[f]), fmt); return para.part; } RichPara::FieldType& GLOBAL_V(FieldTypeVarCls, FieldTypeVar); Id FieldTypeVarID() { static Id ftv("VAR"); return ftv; } INITBLOCK { RichPara::Register(FieldTypeVarID(), FieldTypeVar()); } /* RichPara::FieldType& FieldTypeIndexEntry() { } Id FieldTypeIndexEntryID() { } String EncodeIndexEntry(const RichIndexEntry& idx) { } RichIndexEntry DecodeIndexEntry(String encoded_indexentry) { } */ RichBookSection::RichBookSection() { // pagesize = pg; columns = 1; firstpage = 1; margin = Rect(0, 0, 0, 0); header_space = 200; footer_space = 200; column_space = 200; // footer[1] = "- # -"; } Rect RichBookSection::GetPageRect(Size pagesize) const { return Rect(pagesize).Deflated(margin); } Rect RichBookSection::GetTextRect(Size pagesize, int colindex) const { Rect textarea = GetPageRect(pagesize).Deflated(0, header_space, 0, footer_space); if(columns <= 1) return textarea; int colwd = (textarea.Width() - column_space * (columns - 1)) / columns; int offset = iscale(textarea.Width() - colwd, colindex, columns - 1); textarea.left += offset; textarea.right = textarea.left + colwd; return textarea; } Size RichBookSection::GetTextSize(Size pagesize) const { return GetTextRect(pagesize, 0).Size(); } class RichSectionPageDraw : public PageDraw { public: RichSectionPageDraw(Size drawingsize, Gate2 progress, int& progress_pos); virtual Draw& Page(int page); virtual Draw& Info(); Vector Close(); private: Gate2 progress; int& progress_pos; Size drawingsize; Vector pages; DrawingDraw ddraw; DrawingDraw info; int currentpage; }; RichSectionPageDraw::RichSectionPageDraw(Size dsz, Gate2 progress, int& progress_pos) : progress(progress) , progress_pos(progress_pos) { currentpage = -1; info.Create(drawingsize = dsz); } Draw& RichSectionPageDraw::Page(int page) { if(page != currentpage) { ASSERT(ddraw.GetCloffLevel() == 0); progress(progress_pos + page, -1); if(currentpage >= 0) pages.At(currentpage) = ddraw; ddraw.Create(drawingsize); ddraw.DrawDrawing(drawingsize, pages.At(currentpage = page)); } return ddraw; } Draw& RichSectionPageDraw::Info() { return Page(0); } Vector RichSectionPageDraw::Close() { if(currentpage >= 0) { ASSERT(ddraw.GetCloffLevel() == 0); pages.At(currentpage) = ddraw; } return pages; } static String ExpandHFText(const char *text, int pageno) { String out; for(char c; c = *text++;) if(c == '#') switch(*text++) { case 'r': out.Cat(FormatIntRoman(pageno, false)); break; case 'R': out.Cat(FormatIntRoman(pageno, true)); break; case 'a': out.Cat(FormatIntAlpha(pageno, false)); break; case 'A': out.Cat(FormatIntAlpha(pageno, true)); break; default: text--; out.Cat(FormatInt(pageno)); break; } else out.Cat(c); return out; } Vector RichPrintSection::Print(Size pagesize, Gate2 progress, int& progress_pos) const { ASSERT(columns > 0); Size drawingsize = GetTextSize(pagesize); RichSectionPageDraw spdraw(drawingsize, progress, progress_pos); PaintInfo paintinfo; paintinfo.top = PageY(0, 0); paintinfo.bottom = PageY(32767, 0); paintinfo.indexentry = Null; text.Paint(spdraw, drawingsize, paintinfo); Vector in = spdraw.Close(); Vector out; for(int i = 0; i < in.GetCount(); i += columns) { int pageno = firstpage + out.GetCount(); DrawingDraw ddraw(pagesize); Rect pagerect = GetPageRect(pagesize); for(int c = 0; c < columns && c + i < in.GetCount(); c++) ddraw.DrawDrawing(GetTextRect(pagesize, c), in[i + c]); for(int hf = 0; hf < 3; hf++) { if(!IsNull(header[hf])) { String s = ExpandHFText(header[hf], pageno); Size sz = UPP::GetTextSize(s, charformat); int x; switch(hf) { case 0: x = pagerect.left; break; case 1: x = (pagerect.left + pagerect.right - sz.cx) >> 1; break; case 2: x = pagerect.right - sz.cx; break; } ddraw.DrawText(x, pagerect.top, s, charformat); } if(!IsNull(footer[hf])) { String s = ExpandHFText(footer[hf], pageno); Size sz = UPP::GetTextSize(s, charformat); int x; switch(hf) { case 0: x = pagerect.left; break; case 1: x = (pagerect.left + pagerect.right - sz.cx) >> 1; break; case 2: x = pagerect.right - sz.cx; break; } ddraw.DrawText(x, pagerect.bottom - sz.cy, s, charformat); } } out.Add() = ddraw; } return out; } String GetLangSuffix(int language = GetCurrentLanguage()) { String lt = LNGAsText(language); String out; out << '$' << (char)ToLower(lt[0]) << (char)ToLower(lt[1]) << '-' << (char)ToLower(lt[3]) << (char)ToLower(lt[4]); return out; } void MakeStdToc(RichText& out, const RichToc& toc, int print_width) { VectorMap< String, ArrayMap > subparts; String suffix = GetLangSuffix(); for(int i = 0; i < toc.entries.GetCount(); i++) { const RichTocEntry& e = toc.entries[i]; RichPara::Format pfmt = out.GetStyle(out.GetStyleId("Toc" + FormatInt(e.level))).format; RichPara para; para.format = pfmt; WString name = e.text; String tname = e.topic, subpart; int th = tname.Find('#'); if(th >= 0) { subpart = tname.Mid(th); tname.Trim(th); } tname.Cat(suffix); Topic tp = GetTopic(tname); if(!IsNull(name)) para.Cat(name, pfmt); else { if(!IsNull(subpart)) { int sp = subparts.Find(tname); if(sp < 0) { sp = subparts.GetCount(); ArrayMap& spmap = subparts.Add(tname); RichText rt = ParseQTF(tp.text); for(int p = 0; p < rt.GetPartCount(); p++) if(rt.IsPara(p)) { RichPara rp = rt.Get(p); // if(*rp.format.label == '#') spmap.AddPick("#" + rp.format.label, rp); } } para <<= subparts[sp].Get(subpart, RichPara()); Array objs; String packed = para.Pack(para.format, objs); para.Unpack(packed, objs, pfmt); } else if(!IsNull(tp.title)) para.Cat(FromUtf8(tp.title), pfmt); } if(para.part.IsEmpty()) { pfmt.ink = LtRed(); pfmt.paper = WhiteGray(); para.Cat((WString)(tname + subpart), pfmt); } para.Cat("\t", pfmt); para.Cat(FieldTypeVarID(), String().Cat() << tname << subpart << "$page", pfmt); out.Cat(para); } RLOG("MakeStdToc:\n" << AsQTF(out)); } void MakeOnlineToc(RichText& out, const RichToc& toc) { String suffix = GetLangSuffix(); VectorMap< String, ArrayMap > subparts; for(int i = 0; i < toc.entries.GetCount(); i++) { const RichTocEntry& e = toc.entries[i]; RichPara::Format pfmt = out.GetStyle(out.GetStyleId("Topics" + FormatInt(e.level))).format; RichPara para; para.format = pfmt; WString name = e.text; String tname = e.topic, subpart; int th = tname.Find('#'); if(th >= 0) { subpart = tname.Mid(th); tname.Trim(th); } tname.Cat(suffix); Topic tp = GetTopic(tname); pfmt.link = tname; if(!IsNull(name)) para.Cat(name, pfmt); else { if(!IsNull(subpart)) { int sp = subparts.Find(tname); if(sp < 0) { sp = subparts.GetCount(); ArrayMap& spmap = subparts.Add(tname); RichText rt = ParseQTF(tp.text); for(int p = 0; p < rt.GetPartCount(); p++) if(rt.IsPara(p)) { RichPara rp = rt.Get(p); // if(*rp.format.label == '#') spmap.AddPick("#" + rp.format.label, rp); } } para <<= subparts[sp].Get(subpart, RichPara()); Array objs; String packed = para.Pack(para.format, objs); para.Unpack(packed, objs, pfmt); } else if(!IsNull(tp.title)) para.Cat(FromUtf8(tp.title), pfmt); } if(para.part.IsEmpty()) { pfmt.ink = LtRed(); pfmt.paper = WhiteGray(); para.Cat((WString)(tname + subpart), pfmt); } out.Cat(para); } RLOG("MakeOnlineToc:\n" << AsQTF(out)); } void MakeTocTopics(RichText& out, const RichToc& toc) { String suffix = GetLangSuffix(); VectorMap< String, VectorMap > topic_tocrefs; for(int i = 0; i < toc.entries.GetCount(); i++) { String tname = toc.entries[i].topic; int th = tname.Find('#'); if(th >= 0) topic_tocrefs.GetAdd(tname.Left(th)).GetAdd(tname.Mid(th), i); } Index done; for(int i = 0; i < toc.entries.GetCount(); i++) { const RichTocEntry& e = toc.entries[i]; WString name = e.text; String tname = e.topic; int th = tname.Find('#'); if(th >= 0) tname.Trim(th); int tr = topic_tocrefs.Find(tname); tname << suffix; if(done.Find(tname) >= 0) continue; done.Add(tname); Topic tp = GetTopic(tname); RichPara title_para; title_para.format = out.GetStyle(out.GetStyleId("Heading" + FormatInt(e.level))).format; if(IsNull(name) && IsNull(name = FromUtf8(tp.title))) { name = (WString)tname; title_para.format.ink = LtRed(); title_para.format.paper = WhiteGray(); } title_para.format.label = tname; title_para.Cat(name, title_para.format); out.Cat(title_para); RichText data = ParseQTF(tp.text); int pcount = out.GetPartCount(); out.CatPick(data); for(int t = pcount; t < out.GetPartCount(); t++) if(out.IsPara(t)) { RichPara p = out.Get(t); String lbl = "#" + p.format.label; int tx; if(tr >= 0 && (tx = topic_tocrefs[tr].Find(lbl)) >= 0) { const RichTocEntry& ex = toc.entries[topic_tocrefs[tr][tx]]; Array objs; String pack = p.Pack(p.format, objs); p.format = out.GetStyle(out.GetStyleId("Heading" + FormatInt(ex.level))).format; p.Unpack(pack, objs, p.format); p.format.label = tname + lbl; out.Set(t, p, out.GetStyles()); } } } } //RichText MakeStdIndex( RichBook::RichBook(Size pg) : page_size(pg) { RichStyle toc1, toc2, toc3, toc4; RichPara::Tab& t1tab = toc1.format.tab.Add(); t1tab.pos = pg.cx - 300; t1tab.align = ALIGN_RIGHT; t1tab.fillchar = 1; toc2 = toc3 = toc4 = toc1; t1tab.fillchar = 0; toc1.name = "Toc1"; (Font&)toc1.format = Arial(90).Bold(); toc1.format.indent = 100; toc1.format.before = 100; toc1.format.number[0] = RichPara::NUMBER_1; toc2.name = "Toc2"; (Font&)toc2.format = Arial(84).Bold(); toc2.format.indent = 200; toc2.format.lm = 200; toc2.format.number[0] = toc2.format.number[1] = RichPara::NUMBER_1; toc3.name = "Toc3"; (Font&)toc3.format = Arial(84); toc3.format.indent = 300; toc3.format.lm = 300; toc3.format.number[0] = toc3.format.number[1] = toc3.format.number[2] = RichPara::NUMBER_1; toc4.name = "Toc4"; toc4.format.number[0] = toc4.format.number[1] = toc4.format.number[2] = toc4.format.number[3] = RichPara::NUMBER_1; RichStyle head1, head2, head3, head4; head1.name = "Heading1"; (Font&)head1.format = Arial(144).Bold(); head1.format.newpage = true; head1.format.before = 300; head1.format.after = 100; head1.format.indent = 150; head1.format.number[0] = RichPara::NUMBER_1; head2.name = "Heading2"; (Font&)head2.format = Arial(120).Bold(); head2.format.before = 200; head2.format.after = 100; head2.format.indent = 200; head2.format.number[0] = head2.format.number[1] = RichPara::NUMBER_1; head3.name = "Heading3"; (Font&)head3.format = Arial(100).Bold(); head3.format.before = 100; head3.format.after = 50; head3.format.indent = 250; head3.format.number[0] = head3.format.number[1] = head3.format.number[2] = RichPara::NUMBER_1; head4 = head3; head4.name = "Heading4"; head4.format.number[0] = head4.format.number[1] = head4.format.number[2] = head4.format.number[3] = RichPara::NUMBER_1; default_styles.Add(Uuid::Create(), toc1); default_styles.Add(Uuid::Create(), toc2); default_styles.Add(Uuid::Create(), toc3); default_styles.Add(Uuid::Create(), toc4); default_styles.Add(Uuid::Create(), head1); default_styles.Add(Uuid::Create(), head2); default_styles.Add(Uuid::Create(), head3); default_styles.Add(Uuid::Create(), head4); } RichPrintSection& RichBook::AddSection() { RichText init; init.OverrideStyles(default_styles); return sections.Add(new RichPrintSection(default_section, init)); } Vector RichBook::Print(Size pagesize, Gate2 progress) { Vector out; int pos = 0; VectorMap vars; vars.GetAdd("$book") = 1; bool stable = false; enum { MAX_PASSES = 10 }; int pass; for(pass = 1; !stable && pass <= MAX_PASSES; pass++) { if(progress(pass, 0)) throw AbortExc(); stable = true; int pageno = 1; Index chkdup; for(int s = 0; s < sections.GetCount(); s++) { RichPrintSection& psec = sections[s]; if(psec.text.GetPartCount() > 0 && psec.text.IsPara(0)) { RichPara p0 = psec.text.Get(0); if(p0.format.newpage) { p0.format.newpage = false; psec.text.Set(0, p0, psec.text.GetStyles()); } } Size textsize = psec.GetTextSize(pagesize); PageY begin; pageno = Nvl(psec.firstpage, pageno); psec.text.EvaluateFields(vars); int numpages = psec.text.GetHeight(textsize).page / psec.columns + 1; Vector posinfo = psec.text.GetValPos(textsize, RichText::LABELS); for(int i = 0; i < posinfo.GetCount(); i++) { String key = posinfo[i].data.ToString() + "$page"; if(chkdup.Find(key) >= 0) { if(pass == 1) RLOG("Duplicate: " << key); continue; } chkdup.Add(key); int oldpage = vars.Get(key, Value()); int newpage = pageno + posinfo[i].py.page; if(newpage != oldpage) { if(pass > 2) RLOG("Unstable: " << key << ": " << oldpage << " -> " << newpage); vars.GetAdd(key) = newpage; stable = false; } } pageno += numpages; } } for(int i = 0; i < sections.GetCount(); i++) out.AppendPick(sections[i].Print(pagesize, progress, pos)); #ifdef _DEBUG RichText symtext; Vector order = GetSortOrder(vars.GetKeys()); for(int o = 0; o < order.GetCount(); o++) { RichPara para; para.Cat(NFormat("<%s> %vt", vars.GetKey(order[o]), vars[order[o]]), para.format); symtext.Cat(para); } RichPrintSection symsec(default_section, symtext); out.AppendPick(symsec.Print(pagesize, progress, pos)); #endif return out; } void CreateHelpBook(RichBook& book) { Progress progress(t_("Typesetting page %d...")); DocReport report; Vector pages = book.Print(report.GetPageSize(), progress); progress.SetText(t_("Printing page %d...")); for(int i = 0; i < pages.GetCount(); i++) { if(progress.SetCanceled(i, pages.GetCount())) throw AbortExc(); if(i) report.NextPage(); report.DrawDrawing(pages[i].GetSize(), pages[i]); } report.Perform(); } String CreateHelpPDF(RichBook& book) { PdfDraw pdf(210 * 6000 / 254, 297 * 6000 / 254); Rect margin = Rect(pdf.GetPagePixels()).Deflated(236); Progress progress(t_("Typesetting...")); Vector pages = book.Print(margin.Size(), progress); progress.SetText(t_("Exporting PDF...")); progress.SetTotal(pages.GetCount()); for(int i = 0; i < pages.GetCount(); i++) { if(progress.StepCanceled()) throw AbortExc(); pdf.StartPage(); pdf.DrawDrawing(margin, pages[i]); pdf.EndPage(); } return pdf.Finish(); } String CreateHelpRTF(RichBook& book, byte charset) { RichText richtext; for(int i = 0; i < book.sections.GetCount(); i++) richtext.CatPick(book.sections[i].text); return EncodeRTF(richtext, charset); } RichBookHtml::RichBookHtml() { } void RichBookHtml::AddFile(String url, String data) { } void RichBookHtml::AddImage(String url, String data) { } String RichBookHtml::GetURLFile(String url) const { String out; return out; } void CreateHelpHtml(RichBook& book, RichBookHtml& html) { } GLOBAL_VAR(Callback1, DefaultBook) void CreateDefaultBook() { RichBook book; DefaultBook()(book); CreateHelpBook(book); } void CreateDefaultPDF() { RichBook book; DefaultBook()(book); try { String data = CreateHelpPDF(book); FileSelector fsel; fsel.Type(t_("PDF documents (*.pdf)"), "*.pdf") .DefaultExt("pdf") .AllFilesType(); static String recent; fsel <<= recent; if(fsel.ExecuteSaveAs(t_("PDF export"))) { recent = ~fsel; if(!SaveFile(recent, data)) throw Exc(NFormat("Error saving file '%s' (%d B).", recent, data.GetLength())); } } catch(Exc e) { ShowExc(e); } } void CreateDefaultRTF() { RichBook book; DefaultBook()(book); try { String data = CreateHelpRTF(book, GetDefaultCharset()); FileSelector fsel; fsel.Type(t_("Rich text documents (*.rtf)"), "*.rtf") .DefaultExt("rtf") .AllFilesType(); static String recent; fsel <<= recent; if(fsel.ExecuteSaveAs(t_("Save manual as"))) { recent = ~fsel; if(!SaveFile(recent, data)) throw Exc(NFormat("Error saving file '%s' (%d B).", recent, data.GetLength())); } } catch(Exc e) { ShowExc(e); } } void CreateDefaultHTML() { RichBook book; DefaultBook()(book); try { RichBookHtml html; CreateHelpHtml(book, html); } catch(Exc e) { ShowExc(e); } } void ManualMenu() { MenuBar menu; bool isdb = DefaultBook(); menu.Add(isdb, t_("Print manual"), callback(&CreateDefaultBook)); menu.Add(isdb, t_("PDF export"), callback(&CreateDefaultPDF)); menu.Add(isdb, t_("RTF export"), callback(&CreateDefaultRTF)); menu.Add(isdb, t_("HTML export"), callback(&CreateDefaultHTML)); menu.Execute(); } void HelpBookMenu(Bar& bar) { bar.Add(t_("Save manual"), CtrlImg::save(), callback(&ManualMenu)); } END_UPP_NAMESPACE