#include "htmlrenderer.hpp" #include "renderhelpers.hpp" #include "textstyleinstance.hpp" #include "gumbo.h" #include "kristall.hpp" #include #include #include #include #include static void* malloc_wrapper(void*, size_t size) { return malloc(size); } static void free_wrapper(void*, void* ptr) { free(ptr); } static GumboOptions const gumbo_options = { &malloc_wrapper, &free_wrapper, // memory management nullptr, // user pointer 4, // tab width false, // stop on first error -1, // maximum numbers of errors (-1 = infinite) GUMBO_TAG_LAST, GUMBO_NAMESPACE_HTML }; static void destroyGumboOutput(GumboOutput * output) { gumbo_destroy_output(&gumbo_options, output); } static const char* find_title(const GumboNode* root) { assert(root->type == GUMBO_NODE_ELEMENT); if(root->v.element.children.length < 2) return nullptr; const GumboVector* root_children = &root->v.element.children; GumboNode* head = nullptr; for (size_t i = 0; i < root_children->length; ++i) { GumboNode* child = (GumboNode*)root_children->data[i]; if (child->type == GUMBO_NODE_ELEMENT and child->v.element.tag == GUMBO_TAG_HEAD) { head = child; break; } } if(head == nullptr) return nullptr; GumboVector* head_children = &head->v.element.children; for (size_t i = 0; i < head_children->length; ++i) { GumboNode* child = (GumboNode*)head_children->data[i]; if (child->type == GUMBO_NODE_ELEMENT and child->v.element.tag == GUMBO_TAG_TITLE) { if (child->v.element.children.length != 1) { return ""; } GumboNode* title_text = (GumboNode*)child->v.element.children.data[0]; if(title_text->type == GUMBO_NODE_TEXT or title_text->type == GUMBO_NODE_WHITESPACE) return title_text->v.text.text; return nullptr; } } return nullptr; } struct RenderState { QString & stream; QUrl root_url; DocumentOutlineModel & outline; //! when non-null, we're inside a header element and accumulate the text to //! compute the outline. QString * header_text; int header_count; }; static char const * getAttribute(GumboElement const & element, char const * attrib_name) { for(size_t i = 0; i < element.attributes.length; i++) { auto const attrib = static_cast(element.attributes.data[i]); if(strcmp(attrib->name, attrib_name) == 0) return attrib->value; } return nullptr; } static void renderRecursive(RenderState & state, GumboNode const & node, int nesting = 0) { auto & stream = state.stream; auto & outline = state.outline; switch(node.type) { /** Document node. v will be a GumboDocument. */ case GUMBO_NODE_DOCUMENT: { qWarning() << "Detected embedded document"; break; } /** Element node.v will be a GumboElement. */ case GUMBO_NODE_ELEMENT: { auto const & element = node.v.element; // qDebug() << "begin node(" << gumbo_normalized_tagname(element.tag) << ")"; bool process_header = false; QString header_text; // Fetch the original `id` attribute if any char const * const header_id = getAttribute(element, "id"); QString const anchor = (header_id != nullptr) ? QString(header_id) : QString("header-%1").arg(state.header_count); switch(element.tag) { // Stripped tags case GUMBO_TAG_STYLE: case GUMBO_TAG_SCRIPT: case GUMBO_TAG_UNKNOWN: return; case GUMBO_TAG_BR: stream += "
"; return; case GUMBO_TAG_HR: // This is not nice, but better than the original
tag. // This will have the primary font color instead of *some* color. stream += "

" + QString(" ").repeated(50) + "

"; return; case GUMBO_TAG_NAV: { if(kristall::globals().options.strip_nav) return; stream += "