/* Render HTML page, write out as PNG Heavily based on KDE HTML thumbnail creator Copyright (C) 2003 Simon MacMullen Copyright (C) 2004-2007 Hauke Goos-Habermann Copyright (C) 2007 Florent Bruneau Copyright (C) 2007 Alex Osborne 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 2 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include //<-- use this for Mandriva //#include //<-- use this for other distributions #include "khtml2png.h" /** **name KHTML2PNG(const QString& path, const QString& id, int m_width, int m_height) **description Start a new instance **parameter path: URL to open **parameter id: ID for autodetection **parameter width: Width of the screenshot (if id is empty) **parameter height: Height of the screenshot (if id is empty) **/ KHTML2PNG::KHTML2PNG(const KCmdLineArgs* const args) :KApplication(), m_html(0), pix(0) { const QString width = args->getOption("width"); const QString height = args->getOption("height"); const QString scaledWidth = args->getOption("scaled-width"); const QString scaledHeight = args->getOption("scaled-height"); autoDetectId = args->getOption("auto"); timeoutMillis = args->getOption("time").toUInt() * 1000; show = !args->isSet("disable-window"); rect = QRect(0, 0, width.isEmpty() ? -1 : width.toInt(), height.isEmpty() ? -1 : height.toInt()); if (!scaledWidth.isEmpty()) { scaled.setWidth(scaledWidth.toInt()); } if (!scaledHeight.isEmpty()) { scaled.setHeight(scaledHeight.toInt()); } detectionCompleted = false; loadingCompleted = false; filename = args->arg(1); killPopup = !args->isSet("disable-popupkiller"); init(args->arg(0), !args->isSet("disable-js"), !args->isSet("disable-java"), !args->isSet("disable-plugins"), !args->isSet("disable-redirect")); } /** **name ~KHTML2PNG() **description: Destructor **/ KHTML2PNG::~KHTML2PNG() { if (m_html) { delete m_html; } if (pix) { delete pix; } } /** **name eventFilter(QObject *o, QEvent *e) **description Intercept QMessageBoxes creation and delete them in order keep a non-modal interface **/ bool KHTML2PNG::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::ChildInserted && killPopup) { QChildEvent *ce = (QChildEvent*)e; if (ce->child()->inherits("QDialog")) { o->removeChild(ce->child()); ((QDialog*)(ce->child()))->setModal(false); ce->child()->deleteLater(); } } return false; } /** **name grabChildWidgets( QWidget * w ) **description Creates a screenshot with all widgets of a window. **parameter w: Pointer to the window widget. **returns: QPixmap with the screenshot. **/ QPixmap *KHTML2PNG::grabChildWidgets(QWidget* w) const { /* This solution was taken from: http://lists.kde.org/?l=kde-devel&m=108664293315286&w=2 */ w->repaint(false); QPixmap *res = new QPixmap(w->width(), w->height()); if (w->rect().isEmpty()) { return res; } res->fill(w, QPoint(0, 0)); ::bitBlt(res, QPoint(0, 0), w, w->rect(), Qt::CopyROP, true); const QObjectList *children = w->children(); if (!children) { return res; } QPainter p(res, true); QObjectListIterator it(*children); QObject *child; while ((child = it.current()) != 0) { ++it; if (child->isWidgetType() && ((QWidget *)child)->geometry().intersects(w->rect()) && !child->inherits("QDialog")) { // those conditions aren't quite right, it's possible // to have a grandchild completely outside its // grandparent, but partially inside its parent. no // point in optimizing for that. const QPoint childpos = ((QWidget *)child)->pos(); const QPixmap * const cpm = grabChildWidgets( (QWidget *)child ); if (cpm->isNull()) { // Some child pixmap failed - abort and reset res->resize(0, 0); delete cpm; break; } p.drawPixmap(childpos, *cpm); p.flush(); delete cpm; } } return res; } /** **name resizeClipper(const int width, const int height) **description Try to resize the khtmlview so that the visible area will have at least the given size **/ void KHTML2PNG::resizeClipper(const int width, const int height) { const int x = width + m_html->view()->width() - m_html->view()->clipper()->width(); const int y = height + m_html->view()->height() - m_html->view()->clipper()->height(); m_html->view()->resize(x, y); } /** **name slotCompleted() **description Searches for the position of a HTML element to use as screenshot size marker or sets the m_completed variable. **/ void KHTML2PNG::completed() { loadingCompleted = true; if (!detectionCompleted && !autoDetectId.isEmpty()) { //search for the HTML element DOM::Node markerNode = m_html->htmlDocument().all().namedItem(autoDetectId); if (!markerNode.isNull()) { //get its position rect = m_html->htmlDocument().all().namedItem(autoDetectId).getRect(); if (rect.isEmpty()) { rect = QRect(0, 0, rect.right(), rect.bottom()); } resizeClipper(rect.right() + 200, rect.bottom() + 200); rect = m_html->htmlDocument().all().namedItem(autoDetectId).getRect(); if (rect.isEmpty()) { rect = QRect(0, 0, rect.right(), rect.bottom()); } detectionCompleted = true; } else { fprintf(stderr, "ERROR: Can't find a HTML element with the ID \"%s\" in the current page.\n", autoDetectId.latin1()); autoDetectId = QString::null; } } doRendering(); if (save()) { quit(); } exit(1); } /** **name openURLRequest(const KURL &url, const KParts::URLArgs & ) **description Used to change the chosen url (needed for navigation on the page e.g. clicking on links). **parameter url: the URL to the HTML document **parameter URLArgs: standard parameter for KParts **/ void KHTML2PNG::openURLRequest(const KURL &url, const KParts::URLArgs & ) { m_html->openURL(url.url()); } /** **name init(const QString& path) **description Creates the needed KHTMLPart object for the browser and connects signals and slots. **parameter path: URL to open **/ void KHTML2PNG::init(const QString& path, const bool js, const bool java, const bool plugins, const bool redirect) { m_html = new KHTMLPart; m_html->view()->installEventFilter(this); //set some basic settings m_html->setJScriptEnabled(js); m_html->setJavaEnabled(java); m_html->setPluginsEnabled(plugins); m_html->setMetaRefreshEnabled(redirect); m_html->setOnlyLocalReferences(false); m_html->setAutoloadImages(true); m_html->view()->setResizePolicy(QScrollView::Manual); m_html->view()->setHScrollBarMode(QScrollView::AlwaysOff); m_html->view()->setVScrollBarMode(QScrollView::AlwaysOff); //this is needed for navigation on the page e.g. clicking on links connect(m_html->browserExtension(), SIGNAL(openURLRequestDelayed(const KURL&, const KParts::URLArgs&)),this, SLOT(openURLRequest(const KURL&, const KParts::URLArgs&))); connect(m_html, SIGNAL(completed()),this,SLOT(completed())); //at the beginning the loading isn't completely loadingCompleted = false; //show the window m_html->view()->move(0, 0); if (show) { m_html->view()->showMaximized(); } processEvents(200); xVisible = m_html->view()->clipper()->width() - 20; yVisible = m_html->view()->clipper()->height() - 20; // set a maximum time before we just snapshot whatever we've got loaded so far QTimer *timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(completed())); timer->start(timeoutMillis, false); m_html->openURL(path); } /** **name doRendering() **description Take a snapshot of the browser window. **/ void KHTML2PNG::doRendering() { int yLimit = rect.bottom(); int xLimit = rect.right(); pix = new QPixmap(rect.width(), rect.height()); pix->fill(); if (autoDetectId.isEmpty()) { if (rect.width() != m_html->view()->clipper()->width() || rect.height() != m_html->view()->clipper()->height()) { m_html->view()->resize(rect.width(), rect.height()); processEvents(200); resizeClipper(rect.width(), rect.height()); } int bottom = m_html->htmlDocument().getRect().bottom(); if (bottom < yLimit) { yLimit = bottom; } int right = m_html->htmlDocument().getRect().right(); if (right < xLimit) { xLimit = right; } } xVisible = (rect.width() < xVisible ? rect.width() : xVisible); yVisible = (rect.height() < yVisible ? rect.height() : yVisible); const QPoint clipperPos = m_html->view()->clipper()->pos(); for (int yPos = rect.top() ; yPos <= yLimit ; yPos += yVisible) { for (int xPos = rect.left() ; xPos <= xLimit ; xPos += xVisible) { m_html->view()->move(-xPos - clipperPos.x(), -yPos - clipperPos.y()); m_html->view()->repaint(); processEvents(200); //capture the part of the screen const QPixmap* const temp = show ? grabChildWidgets(m_html->view()->clipper()) : new QPixmap(QPixmap::grabWidget(m_html->view()->clipper())); QRect pos = temp->rect(); pos.setLeft(pos.left() + xPos); pos.setTop(pos.top() + yPos); ::bitBlt(pix, QPoint(xPos - rect.left(), yPos - rect.top()), temp, pos, Qt::CopyROP, true); delete temp; } } } /** **name save(const QString& file) **description Save the snapshot in a file **parameter file: filename **returns true, if the saving was sucessfully otherwise false. **/ bool KHTML2PNG::save() const { QString format = filename.section('.', -1).stripWhiteSpace().upper(); QImage image; if (format == "JPG" || format == "JPE") { format = "JPEG"; } image = pix->convertToImage(); if (scaled.isEmpty() && (scaled.width() > 0 || scaled.height() > 0)) { image = image.smoothScale(scaled, QImage::ScaleMax); } else if (!scaled.isEmpty()) { image = image.smoothScale(scaled, QImage::ScaleMin); } return image.save(filename, format); } /** **name options **description Array with command line options and descriptions **/ static KCmdLineOptions options[] = { { "w", 0, 0}, { "width ", "Width of canvas on which to render html", "800" }, { "h", 0, 0}, { "height ", "Height of canvas on which to render html", "600" }, { "sw", 0, 0}, { "scaled-width ", "Width of image to produce", "" }, { "sh", 0, 0}, { "scaled-height ", "Height of image to produce", "" }, { "t", 0, 0}, { "time