pdf_renderer.cpp

Go to the documentation of this file.
00001 /*
00002  * File Name: pdf_renderer.cpp
00003  */
00004 
00005 /*
00006  * This file is part of uds-plugin-pdf.
00007  *
00008  * uds-plugin-pdf is free software: you can redistribute it and/or modify
00009  * it under the terms of the GNU General Public License as published by
00010  * the Free Software Foundation, either version 2 of the License, or
00011  * (at your option) any later version.
00012  *
00013  * uds-plugin-pdf is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016  * GNU General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU General Public License
00019  * along with this program. If not, see <http://www.gnu.org/licenses/>.
00020  */
00021 
00022 /**
00023  * Copyright (C) 2008 iRex Technologies B.V.
00024  * All rights reserved.
00025  */
00026 
00027 #include "pdf_renderer.h"
00028 #include "pdf_doc_controller.h"
00029 #include "pdf_anchor.h"
00030 #include "pdf_render_task.h"
00031 #include "pdf_library.h"
00032 #include "log.h"
00033 
00034 namespace pdf
00035 {
00036 
00037 SplashColor PDFRenderer::background_color = {255, 255, 255, 0};
00038 
00039 PDFRenderer::PDFRenderer()
00040 : doc_controller(0)
00041 , splash_output_dev(0)
00042 , text_output_dev(0)
00043 , thumbnail_output_dev(0)
00044 , view_attr()
00045 , cur_render_attr()
00046 , render_mutex()
00047 {
00048 }
00049 
00050 PDFRenderer::~PDFRenderer(void)
00051 {
00052     destroy();
00053 }
00054 
00055 bool PDFRenderer::initialize(PDFController* doc)
00056 {
00057     //Destroy the pre-built renderer.
00058     destroy();
00059 
00060     doc_controller = doc;
00061     //assert(doc_controller);
00062     if (doc_controller == 0)
00063     {
00064         ERRORPRINTF("Null Document Controller");
00065         return false;
00066     }
00067 
00068     SplashColorMode mode = splashModeMono8;
00069 
00070     // Create splash output device
00071     splash_output_dev = new SplashOutputDev(mode, 4, gFalse, PDFRenderer::background_color);
00072     if (splash_output_dev == 0)
00073     {
00074         ERRORPRINTF("Cannot Open Splash Output Device");
00075         return false;
00076     }
00077 
00078     // Start output device with the document
00079     splash_output_dev->startDoc(doc_controller->get_pdf_doc()->getXRef());
00080     
00081     // Create text output device
00082     text_output_dev = new TextOutputDev(NULL, gTrue, gFalse, gFalse);
00083     if (text_output_dev == 0)
00084     {
00085         ERRORPRINTF("Cannot Open Text Output Device");
00086         return false;
00087     }
00088 
00089     // Create thumbnail output device
00090     thumbnail_output_dev = new SplashOutputDev(mode, 4, gFalse, PDFRenderer::background_color);
00091     if (thumbnail_output_dev == 0)
00092     {
00093         ERRORPRINTF("Cannot Open Thumbnail Output Device");
00094         return false;
00095     }
00096 
00097     thumbnail_output_dev->startDoc(doc_controller->get_pdf_doc()->getXRef());
00098 
00099     init_pages_index_table();
00100 
00101     return true;
00102 
00103 }
00104 
00105 void PDFRenderer::destroy()
00106 {
00107     delete splash_output_dev;
00108     splash_output_dev = 0;
00109 
00110     delete text_output_dev;
00111     text_output_dev = 0;
00112 
00113     delete thumbnail_output_dev;
00114     thumbnail_output_dev = 0;
00115 }
00116 
00117 void PDFRenderer::init_pages_index_table()
00118 {
00119     // clear all of the pages.
00120     doc_controller->clear_cache();
00121 
00122     unsigned int num = doc_controller->page_count();
00123     for (unsigned int i = 1; i <= num; ++i)
00124     {
00125         // generate a page instance by current render setting
00126         gen_page(i);
00127     }
00128 }
00129 
00130 void PDFRenderer::calc_real_zoom(int page_number,
00131                                  const PDFRenderAttributes &origin_attr,
00132                                  PDFRenderAttributes &real_attr)
00133 {
00134     if (doc_controller == 0)
00135     {
00136         ERRORPRINTF("Serious Problem, Null Document Controller");
00137         return;
00138     }
00139 
00140     real_attr = origin_attr;
00141     double real_zoom = origin_attr.get_zoom_setting();
00142 
00143     if (real_zoom < 0)
00144     {
00145         double crop_width = doc_controller->get_page_crop_width(page_number);
00146         double crop_height = doc_controller->get_page_crop_height(page_number);
00147 
00148         int display_width = doc_controller->get_display_width();
00149         int display_height = doc_controller->get_display_height();
00150 
00151         // special zoom definition
00152         double zoom_v = 0.0f, zoom_h = 0.0f;
00153         if (real_zoom == PLUGIN_ZOOM_TO_PAGE ||
00154             real_zoom == PLUGIN_ZOOM_TO_WIDTH)
00155         {
00156             if (real_attr.get_rotate() == Clockwise_Degrees_90 ||
00157                 real_attr.get_rotate() == Clockwise_Degrees_270)
00158             {
00159                 std::swap(crop_height, crop_width);
00160             }
00161 
00162             zoom_v = static_cast<double>(display_height) * 100 / crop_height;
00163             zoom_h = static_cast<double>(display_width) * 100 / crop_width;
00164 
00165             if (real_zoom == PLUGIN_ZOOM_TO_PAGE)
00166             {
00167                 // caculate by page
00168                 real_zoom = min(zoom_v, zoom_h);
00169             }
00170             else
00171             {
00172                 // caculate by width
00173                 real_zoom = zoom_h;
00174             }
00175         }
00176         else if (real_zoom == PLUGIN_ZOOM_TO_CROP_BY_PAGE ||
00177                  real_zoom == PLUGIN_ZOOM_TO_CROP_BY_WIDTH)
00178         {
00179             // try to get the content area from the page
00180             PagePtr page = doc_controller->get_page(page_number);
00181             if (page != 0 && is_render_area_valid(page->get_content_area()))
00182             {
00183                 // calculate the real zoom by the content area
00184                 PluginRectangle rect;
00185                 get_content_area_in_pixel(page->get_content_area(),
00186                                           crop_width, crop_height, rect);
00187 
00188                 crop_width = rect.width;
00189                 crop_height = rect.height;
00190                 if (real_attr.get_rotate() == Clockwise_Degrees_90 ||
00191                     real_attr.get_rotate() == Clockwise_Degrees_270)
00192                 {
00193                     std::swap(crop_height, crop_width);
00194                 }
00195 
00196                 zoom_v = static_cast<double>(display_height) * 100 / crop_height;
00197                 zoom_h = static_cast<double>(display_width) * 100 / crop_width;
00198 
00199                 if (real_zoom == PLUGIN_ZOOM_TO_CROP_BY_PAGE)
00200                 {
00201                     real_zoom = min(zoom_v, zoom_h);
00202                 }
00203                 else
00204                 {
00205                     real_zoom = zoom_h;
00206                 }
00207             }
00208         }
00209     }
00210 
00211     real_attr.set_real_zoom_value(real_zoom);
00212 }
00213 
00214 bool PDFRenderer::render_cover_page(const int width, const int height
00215                                     , PluginBitmapAttributes *output)
00216 {
00217     if (doc_controller->page_count() < 1)
00218     {
00219         // return false if it is an empty document
00220         return false;
00221     }
00222 
00223     int cover_num = 1;
00224     // 1. calculate the zoom, fit for best
00225     double crop_width = doc_controller->get_page_crop_width(cover_num);
00226     double crop_height = doc_controller->get_page_crop_height(cover_num);
00227 
00228     double zoom = min(static_cast<double>(width) / crop_width,
00229         static_cast<double>(height) / crop_height);
00230 
00231     // 2. render the splash bitmap of cover
00232     // lock when rendering
00233     ScopeMutex m(&render_mutex);
00234 
00235     RenderRet ret = doc_controller->get_pdf_doc()->displayPage(
00236         get_thumbnail_output_dev()
00237         , cover_num
00238         , zoom * get_view_attr().get_device_dpi_h()
00239         , zoom * get_view_attr().get_device_dpi_v()
00240         , 0
00241         , gFalse //useMediaBox, TODO.
00242         , gFalse  //crop, TODO.
00243         , gFalse  //doLinks, TODO.
00244     );
00245 
00246     if (ret == Render_Error || ret == Render_Invalid)
00247     {
00248         ERRORPRINTF("Error in rendering cover page:%d\n", cover_num);
00249         return false;
00250     }
00251 
00252     SplashBitmap *cover_map = get_thumbnail_output_dev()->takeBitmap();
00253     if (cover_map != 0)
00254     {
00255         memcpy((void*)output->data, cover_map->getDataPtr(),
00256             cover_map->getRowSize() * cover_map->getHeight());
00257         delete cover_map;
00258         return true;
00259     }
00260 
00261     return false;
00262 }
00263 
00264 void PDFRenderer::post_prerender_task(const size_t page_number,
00265                                       const PDFRenderAttributes &page_attr)
00266 {
00267     PDFRenderAttributes real_attr;
00268     // calculate the real render attributes
00269     calc_real_zoom(page_number, page_attr, real_attr);
00270 
00271     // try to get the page from cache
00272     PagePtr page = doc_controller->get_page(page_number);
00273     PDFRenderTask *task = 0;
00274     if (page)
00275     {
00276         if (!(page->get_render_attr() == page_attr) ||
00277             page->get_render_status() == PDFPage::RENDER_STOP)
00278         {
00279             // if reset the render attributes or the previous rendering stops
00280             // generate a new prerender task
00281             task = gen_render_task(page, real_attr);
00282         }
00283     }
00284     else
00285     {
00286         // append new prerender task and create a new page
00287         task = gen_render_task(page_number, real_attr);
00288     }
00289 
00290     if (task != 0)
00291     {
00292         PDFLibrary::instance().thread_add_render_task(task, true, false);
00293     }
00294 }
00295 
00296 void PDFRenderer::post_prerender_hyperlinks_task(PagePtr page)
00297 {
00298     if (page == 0)
00299     {
00300         ERRORPRINTF("Invalid page when posting hyperlink rendering task");
00301         return;
00302     }
00303 
00304     Links *links = page->get_links();
00305 
00306     if (links == 0)
00307     {
00308         return;
00309     }
00310 
00311     int link_num = links->getNumLinks();
00312     if (link_num <= 0 ||
00313         link_num > doc_controller->get_prerender_policy()->get_allowed_hyperlinks_number())
00314     {
00315         return;
00316     }
00317 
00318     link_num = doc_controller->get_prerender_policy()->get_allowed_hyperlinks_number();
00319     PDFRenderAttributes real_attr;
00320     for (int i = 0; i < link_num; i++)
00321     {
00322         int goto_page_num = page->get_goto_page_of_link(i);
00323         if (goto_page_num <= 0)
00324         {
00325             //WARNPRINTF("The %d th link is invalid.", i);
00326             continue;
00327         }
00328 
00329         // calculate the real render attributes
00330         calc_real_zoom(goto_page_num, page->get_render_attr(), real_attr);
00331 
00332         // generate render task
00333         PagePtr goto_page = doc_controller->get_page(goto_page_num);
00334         PDFRenderTask* task = 0;
00335         if (goto_page)
00336         {
00337             if (!(goto_page->get_render_attr() == page->get_render_attr()) ||
00338                 goto_page->get_render_status() == PDFPage::RENDER_STOP)
00339             {
00340                 task = gen_render_task(goto_page, real_attr);
00341             }
00342         }
00343         else
00344         {
00345             // append new prerender task and create a new page
00346             task = gen_render_task(goto_page_num, real_attr);
00347         }
00348 
00349         if (task != 0)
00350         {
00351             // add the hyperlinked page into request list
00352             doc_controller->get_prerender_policy()->get_requests().append_request(goto_page_num);
00353             PDFLibrary::instance().thread_add_render_task(task, true, false);
00354         }
00355     }
00356 }
00357 
00358 void PDFRenderer::post_render_task(int page_num,
00359                                    const PDFRenderAttributes &render_attr,
00360                                    PluginRenderResultImpl *render_res,
00361                                    const unsigned int ref_id)
00362 {
00363     if (page_num <= 0 || page_num > static_cast<int>(doc_controller->page_count()))
00364     {
00365         handle_page_ready(render_res, 0, TASK_RENDER_INVALID_PAGE);
00366         return;
00367     }
00368 
00369     // set the current displaying page
00370     int last_page = doc_controller->get_cur_page_num();
00371     doc_controller->set_cur_page_num(page_num);
00372 
00373     PDFRenderAttributes real_attr;
00374     calc_real_zoom(page_num, render_attr, real_attr);
00375     // update global render setting
00376     cur_render_attr = real_attr;
00377 
00378     // generate request queue of rendering based on current page and last page
00379     // at this moment, the previous request queue would be cleared.
00380     std::vector<size_t> requests;
00381     doc_controller->get_prerender_policy()->generate_requests_list(page_num,
00382                                                                    last_page,
00383                                                                    doc_controller->page_count(),
00384                                                                    requests);
00385 
00386     // estimate whether the requested page is cached
00387     PagePtr page = doc_controller->get_page(page_num);
00388     PDFRenderTask* render_task = 0;
00389     bool abort_current_task = true;
00390     if (page)
00391     {
00392         {
00393             ScopeMutex m(&(doc_controller->get_pages_cache().get_mutex()));
00394             if (page->get_render_attr() == cur_render_attr)
00395             {
00396                 if (page->get_render_status() == PDFPage::RENDER_DONE &&
00397                     render_res != 0)
00398                 {
00399                     // update the reference to this page, make it ready to display
00400                     // need NOT generate a new render task
00401                     page->set_ref_id(ref_id);
00402                     render_res->set_page(page);
00403                 }
00404                 else
00405                 {
00406                     // create a new render task
00407                     render_task = gen_render_task(page,
00408                                                   real_attr,
00409                                                   render_res,
00410                                                   ref_id);
00411                     if (page->get_render_status() == PDFPage::RENDER_RUNNING)
00412                     {
00413                         abort_current_task = false;
00414                     }
00415                 }
00416             }
00417             else
00418             {
00419                 render_task = gen_render_task(page,
00420                                               real_attr,
00421                                               render_res,
00422                                               ref_id);
00423             }
00424         }
00425     }
00426     else
00427     {
00428         // the page is not cached, create a new Render task
00429         render_task = gen_render_task(page_num,
00430                                       real_attr,
00431                                       render_res,
00432                                       ref_id);
00433     }
00434 
00435     if (render_task != 0)
00436     {
00437         LOGPRINTF("PDF tries to render page:%d zoom:%f\n\n",
00438                   page_num,
00439                   real_attr.get_zoom_setting());
00440 
00441         // cancel all of the render tasks in the queue
00442         PDFLibrary::instance().thread_cancel_render_tasks(render_task->get_user_data());
00443 
00444         // add task for normally rendering
00445         PDFLibrary::instance().thread_add_render_task(render_task,
00446                                                       false,
00447                                                       abort_current_task);
00448     }
00449     else if (page != 0 && render_res != 0 &&
00450              page->get_render_status() == PDFPage::RENDER_DONE)
00451     {
00452         // if the page is ready, return it to UDS
00453         handle_page_ready(render_res, page, TASK_RENDER_DONE);
00454     }
00455 
00456     // prerender the pages, start from 1 in the requests list because
00457     // the normal rendering page is always located at 0.
00458     for (size_t idx = 1; idx < requests.size(); ++idx)
00459     {
00460         int dst_page = requests.at(idx);
00461         post_prerender_task(dst_page, render_attr);
00462     }
00463 }
00464 
00465 void PDFRenderer::handle_page_ready(PluginRenderResultImpl *render_res,
00466                                     PagePtr page,
00467                                     RenderStatus stat)
00468 {
00469     switch (stat)
00470     {
00471     case TASK_RENDER_DONE:
00472         {
00473             if (page == 0)
00474             {
00475                 ERRORPRINTF("Invalid page when handling render result");
00476                 return;
00477             }
00478 
00479             if (render_res != 0)
00480             {
00481                 // It's rendering a certain page, 
00482                 // prerender the pages for hyperlinks on this page.
00483                 post_prerender_hyperlinks_task(page);
00484             }
00485         }
00486         break;
00487     case TASK_RENDER_OOM:
00488         {
00489             // notify uds if it is out of memory
00490         }
00491         break;
00492     case TASK_RENDER_INVALID_PAGE:
00493         {
00494             // notify uds it is an invalid page
00495         }
00496         break;
00497     default:
00498         break;
00499     }
00500 
00501     if (render_res != 0)
00502     {
00503         // notify uds that the page has been done, only for rendered result
00504         doc_controller->sig_page_ready.broadcast(render_res, stat);
00505     }
00506 }
00507 
00508 PDFRenderTask* PDFRenderer::gen_render_task(int page_num,
00509                                             const PDFRenderAttributes &page_attr,
00510                                             PluginRenderResultImpl *render_res,
00511                                             int ref_id)
00512 {
00513     return (new PDFRenderTask(page_num,
00514                               page_attr,
00515                               doc_controller,
00516                               render_res,
00517                               ref_id));
00518 }
00519 
00520 PDFRenderTask* PDFRenderer::gen_render_task(PagePtr page,
00521                                             const PDFRenderAttributes &page_attr,
00522                                             PluginRenderResultImpl *render_res,
00523                                             int ref_id)
00524 {
00525     return (new PDFRenderTask(page,
00526                               page_attr,
00527                               doc_controller,
00528                               render_res,
00529                               ref_id));
00530 }
00531 
00532 PagePtr PDFRenderer::gen_page(int page_num, const PDFRenderAttributes &attr)
00533 {
00534     PagePtr page = new PDFPage(page_num, attr);
00535     page->set_doc_controller(doc_controller);
00536 
00537     // put the page into cache
00538     doc_controller->pages_cache.add_page(page);
00539 
00540     return page;
00541 }
00542 
00543 PagePtr PDFRenderer::gen_page(int page_num)
00544 {
00545     // construct a default page
00546     PagePtr page = new PDFPage(page_num, cur_render_attr);
00547     page->set_doc_controller(doc_controller);
00548 
00549     // put the page into cache
00550     doc_controller->pages_cache.add_page(page);
00551 
00552     return page;
00553 }
00554 
00555 } //namespace pdf
00556 
Generated by  doxygen 1.6.2-20100208