// Copyright © 2011, 2014, 2015, 2018 Richard Kettlewell. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #include #include "Document.h" #include "Utils.h" #include "Errors.h" #include #include #include #include // HTML support --------------------------------------------------------------- void Document::quoteHtml(std::ostream &os, const std::string &s) { // We need the string in UTF-32 in order to quote it correctly std::u32string u; toUnicode(u, s); // SGML-quote anything that might be interpreted as a delimiter, and anything // outside of ASCII for(auto w: u) { switch(w) { default: if(w >= 127) { case '&': case '<': case '"': case '\'': os << "&#" << w << ";"; break; } else os << (char)w; } } } void Document::Node::renderHtmlOpenTag(std::ostream &os, const char *name, ...) const { va_list ap; os << '<' << name; if(style.size()) os << " class=" << style; char buffer[64]; if(fgcolor != -1) { snprintf(buffer, sizeof buffer, "#%06x", fgcolor); os << " color=\"" << buffer << "\""; } if(bgcolor != -1) { snprintf(buffer, sizeof buffer, "#%06x", bgcolor); os << " bgcolor=\"" << buffer << "\""; } va_start(ap, name); const char *attributeName, *attributeValue; while((attributeName = va_arg(ap, const char *))) { attributeValue = va_arg(ap, const char *); os << " " << attributeName << "=\""; quoteHtml(os, attributeValue); os << "\""; } os << '>'; va_end(ap); } void Document::Node::renderHtmlCloseTag(std::ostream &os, const char *name, bool newline) const { os << ""; if(newline) os << '\n'; } void Document::LinearContainer::renderHtmlContents(std::ostream &os, Attachments *as) const { for(Node *node: nodes) node->renderHtml(os, as); } void Document::String::renderHtml(std::ostream &os, Attachments *) const { Document::quoteHtml(os, text); } void Document::LinearContainer::renderHtml(std::ostream &os, Attachments *as) const { renderHtmlOpenTag(os, "div", (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, "div"); } void Document::Paragraph::renderHtml(std::ostream &os, Attachments *as) const { renderHtmlOpenTag(os, "p", (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, "p"); } void Document::Verbatim::renderHtml(std::ostream &os, Attachments *as) const { renderHtmlOpenTag(os, "pre", (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, "pre"); } void Document::List::renderHtml(std::ostream &os, Attachments *as) const { switch(type) { case OrderedList: renderHtmlOpenTag(os, "ol", (char *)nullptr); break; case UnorderedList: renderHtmlOpenTag(os, "ul", (char *)nullptr); break; } renderHtmlContents(os, as); switch(type) { case OrderedList: renderHtmlCloseTag(os, "ol"); break; case UnorderedList: renderHtmlCloseTag(os, "ul"); break; } } void Document::ListEntry::renderHtml(std::ostream &os, Attachments *as) const { renderHtmlOpenTag(os, "li", (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, "li"); } void Document::Heading::renderHtml(std::ostream &os, Attachments *as) const { if(level > 6) throw std::runtime_error("heading level too high"); char tag[64]; snprintf(tag, sizeof tag, "h%d", level); renderHtmlOpenTag(os, tag, (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, tag); } void Document::Cell::renderHtml(std::ostream &os, Attachments *as) const { const char *const tag = heading ? "th" : "td"; char ws[64], hs[64]; snprintf(ws, sizeof ws, "%d", w); snprintf(hs, sizeof hs, "%d", h); if(w > 1 && h > 1) renderHtmlOpenTag(os, tag, "colspan", ws, "rowspan", hs, (char *)nullptr); else if(w > 1) renderHtmlOpenTag(os, tag, "colspan", ws, (char *)nullptr); else if(h > 1) renderHtmlOpenTag(os, tag, "rowspan", hs, (char *)nullptr); else renderHtmlOpenTag(os, tag, (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, tag); } void Document::Table::renderHtml(std::ostream &os, Attachments *as) const { renderHtmlOpenTag(os, "table", (char *)nullptr); const int w = width(), h = height(); for(int row = 0; row < h; ++row) { renderHtmlOpenTag(os, "tr", (char *)nullptr); bool heading = false; for(int col = 0; col < w;) { int skip = 0; for(const Cell *cell: cells) { if(cell->y == row && cell->x == col) { heading |= cell->heading; cell->renderHtml(os, as); skip = cell->w; break; } } if(!skip) { if(!occupied(col, row)) { const char *tag = heading ? "th" : "td"; renderHtmlOpenTag(os, tag, (char *)nullptr); renderHtmlCloseTag(os, tag); } skip = 1; } col += skip; } renderHtmlCloseTag(os, "tr"); } renderHtmlCloseTag(os, "table"); } void Document::Image::renderHtml(std::ostream &os, Attachments *as) const { std::string url; if(as) { as->images.push_back(this); url = "cid:" + ident(); } else { std::stringstream ss; ss << "data:" << type << ";base64,"; write_base64(ss, content); url = ss.str(); } renderHtmlOpenTag(os, "p", (char *)nullptr); renderHtmlOpenTag(os, "img", "src", url.c_str(), (char *)nullptr); renderHtmlCloseTag(os, "p"); } void Document::RootContainer::renderHtml(std::ostream &os, Attachments *as) const { renderHtmlOpenTag(os, "body", (char *)nullptr); renderHtmlContents(os, as); renderHtmlCloseTag(os, "body"); } void Document::renderHtml(std::ostream &os, Attachments *as) const { os << "\n"; os << "\n"; os << ""; quoteHtml(os, title); os << "\n"; if(htmlStyleSheet.size()) { os << "\n"; } os << "\n"; content.renderHtml(os, as); os << "\n"; }