diff --git a/CMakeLists.txt b/CMakeLists.txt index e4410573a..ab7ad2396 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,377 +1,377 @@ project(okular) cmake_minimum_required(VERSION 2.8.12) set(QT_REQUIRED_VERSION "5.6.0") set(KF5_REQUIRED_VERSION "5.16.0") find_package(ECM 5.19.0 CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) # KDE Application Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "17") -set (KDE_APPLICATIONS_VERSION_MINOR "07") +set (KDE_APPLICATIONS_VERSION_MINOR "11") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") include(ECMInstallIcons) include(ECMSetupVersion) include(ECMOptionalAddSubdirectory) include(GenerateExportHeader) include(FeatureSummary) include(ECMAddAppIcon) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDEInstallDirs) include(KDECMakeSettings) include(ECMAddTests) include(ECMAddAppIcon) include(ECMPackageConfigHelpers) -ecm_setup_version(1.1.${KDE_APPLICATIONS_VERSION_MICRO} +ecm_setup_version(1.3.${KDE_APPLICATIONS_VERSION_MICRO} VARIABLE_PREFIX OKULAR VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/core/version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/Okular5ConfigVersion.cmake") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED COMPONENTS Core DBus Test Widgets PrintSupport Svg Qml Quick) find_package(Qt5 ${QT_REQUIRED_VERSION} OPTIONAL_COMPONENTS TextToSpeech) if (NOT Qt5TextToSpeech_FOUND) message(STATUS "Qt5TextToSpeech not found, speech features will be disabled") else() add_definitions(-DHAVE_SPEECH) endif() find_package(KF5 ${KF5_REQUIRED_VERSION} REQUIRED COMPONENTS Archive Bookmarks Completion Config ConfigWidgets CoreAddons DocTools IconThemes JS KIO Parts ThreadWeaver Wallet WindowSystem ) if(NOT WIN32) find_package(KF5 ${KF5_REQUIRED_VERSION} REQUIRED COMPONENTS Activities ) endif() find_package(Phonon4Qt5 CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules) find_package(ZLIB REQUIRED) add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS) add_definitions(-DTRANSLATION_DOMAIN="okular") include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${PHONON_INCLUDES} core/synctex ${ZLIB_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/core) add_subdirectory( mobile ) option(BUILD_COVERAGE "Build the project with gcov support" OFF) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.0.0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override" ) endif() endif() if(BUILD_COVERAGE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") endif() add_subdirectory( ui ) add_subdirectory( shell ) add_subdirectory( generators ) add_subdirectory( autotests ) add_subdirectory( conf/autotests ) add_subdirectory(doc) include(OkularConfigureChecks.cmake) if(NOT WIN32) set(MATH_LIB m) else(NOT WIN32) set(MATH_LIB) endif(NOT WIN32) # okularcore set(okularcore_SRCS core/action.cpp core/annotations.cpp core/area.cpp core/audioplayer.cpp core/bookmarkmanager.cpp core/chooseenginedialog.cpp core/document.cpp core/documentcommands.cpp core/fontinfo.cpp core/form.cpp core/generator.cpp core/generator_p.cpp core/misc.cpp core/movie.cpp core/observer.cpp core/debug.cpp core/page.cpp core/pagecontroller.cpp core/pagesize.cpp core/pagetransition.cpp core/rotationjob.cpp core/scripter.cpp core/sound.cpp core/sourcereference.cpp core/textdocumentgenerator.cpp core/textdocumentsettings.cpp core/textpage.cpp core/tilesmanager.cpp core/utils.cpp core/view.cpp core/fileprinter.cpp core/script/executor_kjs.cpp core/script/kjs_app.cpp core/script/kjs_console.cpp core/script/kjs_data.cpp core/script/kjs_document.cpp core/script/kjs_fullscreen.cpp core/script/kjs_field.cpp core/script/kjs_spell.cpp core/script/kjs_util.cpp core/synctex/synctex_parser.c core/synctex/synctex_parser_utils.c ) ki18n_wrap_ui(okularcore_SRCS conf/textdocumentsettings.ui ) install( FILES core/action.h core/annotations.h core/area.h core/document.h core/fontinfo.h core/form.h core/generator.h core/global.h core/page.h core/pagesize.h core/pagetransition.h core/sound.h core/sourcereference.h core/textdocumentgenerator.h core/textdocumentsettings.h core/textpage.h core/tile.h core/utils.h core/fileprinter.h core/observer.h ${CMAKE_CURRENT_BINARY_DIR}/core/version.h ${CMAKE_CURRENT_BINARY_DIR}/core/okularcore_export.h ${CMAKE_CURRENT_BINARY_DIR}/settings_core.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/okular/core COMPONENT Devel) install( FILES interfaces/configinterface.h interfaces/guiinterface.h interfaces/printinterface.h interfaces/saveinterface.h interfaces/viewerinterface.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/okular/interfaces COMPONENT Devel) ki18n_wrap_ui(okularcore_SRCS core/chooseenginewidget.ui ) kconfig_add_kcfg_files(okularcore_SRCS conf/settings_core.kcfgc ) add_library(okularcore SHARED ${okularcore_SRCS}) generate_export_header(okularcore BASE_NAME okularcore EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/core/okularcore_export.h") # Special handling for linking okularcore on OSX/Apple IF(APPLE) SET(OKULAR_IOKIT "-framework IOKit" CACHE STRING "Apple IOKit framework") ENDIF(APPLE) target_link_libraries(okularcore PRIVATE ${OKULAR_IOKIT} KF5::Archive KF5::JS KF5::JSApi KF5::KIOCore KF5::KIOWidgets KF5::I18n KF5::ThreadWeaver KF5::Wallet KF5::Bookmarks Phonon::phonon4qt5 ${MATH_LIB} ${ZLIB_LIBRARIES} PUBLIC # these are included from the installed headers KF5::CoreAddons KF5::XmlGui KF5::ConfigGui Qt5::PrintSupport Qt5::Widgets ) set_target_properties(okularcore PROPERTIES VERSION 7.0.0 SOVERSION 7 OUTPUT_NAME Okular5Core EXPORT_NAME Core) install(TARGETS okularcore EXPORT Okular5Targets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES conf/okular.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(FILES conf/okular_core.kcfg DESTINATION ${KDE_INSTALL_KCFGDIR}) install(FILES core/okularGenerator.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) # okularpart set(okularpart_conf_SRCS conf/preferencesdialog.cpp conf/dlgaccessibility.cpp conf/dlgdebug.cpp conf/dlgeditor.cpp conf/dlggeneral.cpp conf/dlgannotations.cpp conf/dlgperformance.cpp conf/dlgpresentation.cpp conf/editannottooldialog.cpp conf/editdrawingtooldialog.cpp conf/widgetannottools.cpp conf/widgetconfigurationtoolsbase.cpp conf/widgetdrawingtools.cpp ) set(okularpart_SRCS ${okularpart_conf_SRCS} part.cpp extensions.cpp ui/embeddedfilesdialog.cpp ui/annotwindow.cpp ui/annotationmodel.cpp ui/annotationpopup.cpp ui/annotationpropertiesdialog.cpp ui/annotationproxymodels.cpp ui/annotationtools.cpp ui/annotationwidgets.cpp ui/bookmarklist.cpp ui/debug_ui.cpp ui/drawingtoolactions.cpp ui/fileprinterpreview.cpp ui/findbar.cpp ui/formwidgets.cpp ui/guiutils.cpp ui/ktreeviewsearchline.cpp ui/latexrenderer.cpp ui/minibar.cpp ui/okmenutitle.cpp ui/pageitemdelegate.cpp ui/pagepainter.cpp ui/pagesizelabel.cpp ui/pageviewannotator.cpp ui/pageviewmouseannotation.cpp ui/pageview.cpp ui/magnifierview.cpp ui/pageviewutils.cpp ui/presentationsearchbar.cpp ui/presentationwidget.cpp ui/propertiesdialog.cpp ui/searchlineedit.cpp ui/searchwidget.cpp ui/sidebar.cpp ui/side_reviews.cpp ui/snapshottaker.cpp ui/thumbnaillist.cpp ui/toc.cpp ui/tocmodel.cpp ui/toolaction.cpp ui/videowidget.cpp ui/layers.cpp ) if (Qt5TextToSpeech_FOUND) set(okularpart_SRCS ${okularpart_SRCS} ui/tts.cpp) endif() ki18n_wrap_ui(okularpart_SRCS conf/dlgaccessibilitybase.ui conf/dlgeditorbase.ui conf/dlggeneralbase.ui conf/dlgannotationsbase.ui conf/dlgperformancebase.ui conf/dlgpresentationbase.ui ) kconfig_add_kcfg_files(okularpart_SRCS conf/settings.kcfgc ) add_library(okularpart SHARED ${okularpart_SRCS}) generate_export_header(okularpart BASE_NAME okularpart) target_link_libraries(okularpart okularcore ${MATH_LIB} Qt5::Svg Phonon::phonon4qt5 KF5::Archive KF5::Bookmarks KF5::I18n KF5::IconThemes KF5::ItemViews KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM KF5::Parts KF5::Solid KF5::Wallet KF5::WindowSystem ) set_target_properties(okularpart PROPERTIES PREFIX "") if (Qt5TextToSpeech_FOUND) target_link_libraries(okularpart Qt5::TextToSpeech) endif() install(TARGETS okularpart DESTINATION ${KDE_INSTALL_PLUGINDIR}) ########### install files ############### install(FILES okular.upd DESTINATION ${KDE_INSTALL_DATADIR}/kconf_update) install( FILES okular_part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES part.rc part-viewermode.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/okular ) install( FILES okular.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) ########### cmake files ################# set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/Okular5") ecm_configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/Okular5Config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/Okular5Config.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} PATH_VARS INCLUDE_INSTALL_DIR CMAKE_INSTALL_PREFIX ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Okular5Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Okular5ConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT Okular5Targets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE Okular5Targets.cmake NAMESPACE Okular::) ########### summary ################# feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/core/textpage.cpp b/core/textpage.cpp index 44dfa14d9..a8b13b280 100644 --- a/core/textpage.cpp +++ b/core/textpage.cpp @@ -1,2036 +1,2036 @@ /*************************************************************************** * Copyright (C) 2005 by Piotr Szymanski * * * * 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. * ***************************************************************************/ #include "textpage.h" #include "textpage_p.h" #include #include "area.h" #include "debug_p.h" #include "misc.h" #include "page.h" #include "page_p.h" #include #include #include using namespace Okular; class SearchPoint { public: SearchPoint() : offset_begin( -1 ), offset_end( -1 ) { } /** The TinyTextEntity containing the first character of the match. */ TextList::ConstIterator it_begin; /** The TinyTextEntity containing the last character of the match. */ TextList::ConstIterator it_end; /** The index of the first character of the match in (*it_begin)->text(). * Satisfies 0 <= offset_begin < (*it_begin)->text().length(). */ int offset_begin; /** One plus the index of the last character of the match in (*it_end)->text(). * Satisfies 0 < offset_end <= (*it_end)->text().length(). */ int offset_end; }; /* text comparison functions */ static bool CaseInsensitiveCmpFn( const QStringRef & from, const QStringRef & to ) { return from.compare( to, Qt::CaseInsensitive ) == 0; } static bool CaseSensitiveCmpFn( const QStringRef & from, const QStringRef & to ) { return from.compare( to, Qt::CaseSensitive ) == 0; } /** * Returns true iff segments [@p left1, @p right1] and [@p left2, @p right2] on the real line * overlap within @p threshold percent, i. e. iff the ratio of the length of the * intersection of the segments to the length of the shortest of the two input segments * is not smaller than the threshold. */ static bool segmentsOverlap(double left1, double right1, double left2, double right2, int threshold) { // check if one consumes another fully (speed optimization) if (left1 <= left2 && right1 >= right2) return true; if (left1 >= left2 && right1 <= right2) return true; // check if there is overlap above threshold if (right2 >= left1 && right1 >= left2) { double overlap = (right2 >= right1) ? right1 - left2 : right2 - left1; double length1 = right1 - left1, length2 = right2 - left2; return overlap * 100 >= threshold * qMin(length1, length2); } return false; } static bool doesConsumeY(const QRect& first, const QRect& second, int threshold) { return segmentsOverlap(first.top(), first.bottom(), second.top(), second.bottom(), threshold); } static bool doesConsumeY(const NormalizedRect& first, const NormalizedRect& second, int threshold) { return segmentsOverlap(first.top, first.bottom, second.top, second.bottom, threshold); } /* Rationale behind TinyTextEntity: instead of storing directly a QString for the text of an entity, we store the UTF-16 data and their length. This way, we save about 4 int's wrt a QString, and we can create a new string from that raw data (that's the only penalty of that). Even better, if the string we need to store has at most MaxStaticChars characters, then we store those in place of the QChar* that would be used (with new[] + free[]) for the data. */ class TinyTextEntity { static const int MaxStaticChars = sizeof( QChar * ) / sizeof( QChar ); public: TinyTextEntity( const QString &text, const NormalizedRect &rect ) : area( rect ) { Q_ASSERT_X( !text.isEmpty(), "TinyTextEntity", "empty string" ); Q_ASSERT_X( sizeof( d ) == sizeof( QChar * ), "TinyTextEntity", "internal storage is wider than QChar*, fix it!" ); length = text.length(); switch ( length ) { #if QT_POINTER_SIZE >= 8 case 4: d.qc[3] = text.at( 3 ).unicode(); // fall through case 3: d.qc[2] = text.at( 2 ).unicode(); - // fall through #endif + // fall through case 2: d.qc[1] = text.at( 1 ).unicode(); // fall through case 1: d.qc[0] = text.at( 0 ).unicode(); break; default: d.data = new QChar[ length ]; std::memcpy( d.data, text.constData(), length * sizeof( QChar ) ); } } ~TinyTextEntity() { if ( length > MaxStaticChars ) { delete [] d.data; } } inline QString text() const { return length <= MaxStaticChars ? QString::fromRawData( ( const QChar * )&d.qc[0], length ) : QString::fromRawData( d.data, length ); } inline NormalizedRect transformedArea( const QTransform &matrix ) const { NormalizedRect transformed_area = area; transformed_area.transform( matrix ); return transformed_area; } NormalizedRect area; private: Q_DISABLE_COPY( TinyTextEntity ) union { QChar *data; ushort qc[MaxStaticChars]; } d; int length; }; TextEntity::TextEntity( const QString &text, NormalizedRect *area ) : m_text( text ), m_area( area ), d( 0 ) { } TextEntity::~TextEntity() { delete m_area; } QString TextEntity::text() const { return m_text; } NormalizedRect* TextEntity::area() const { return m_area; } NormalizedRect TextEntity::transformedArea(const QTransform &matrix) const { NormalizedRect transformed_area = *m_area; transformed_area.transform( matrix ); return transformed_area; } TextPagePrivate::TextPagePrivate() : m_page( 0 ) { } TextPagePrivate::~TextPagePrivate() { qDeleteAll( m_searchPoints ); qDeleteAll( m_words ); } TextPage::TextPage() : d( new TextPagePrivate() ) { } TextPage::TextPage( const TextEntity::List &words ) : d( new TextPagePrivate() ) { TextEntity::List::ConstIterator it = words.constBegin(), itEnd = words.constEnd(); for ( ; it != itEnd; ++it ) { TextEntity *e = *it; if ( !e->text().isEmpty() ) d->m_words.append( new TinyTextEntity( e->text(), *e->area() ) ); delete e; } } TextPage::~TextPage() { delete d; } void TextPage::append( const QString &text, NormalizedRect *area ) { if ( !text.isEmpty() ) d->m_words.append( new TinyTextEntity( text.normalized(QString::NormalizationForm_KC), *area ) ); delete area; } struct WordWithCharacters { WordWithCharacters(TinyTextEntity *w, const TextList &c) : word(w), characters(c) { } inline QString text() const { return word->text(); } inline const NormalizedRect &area() const { return word->area; } TinyTextEntity *word; TextList characters; }; typedef QList WordsWithCharacters; /** * We will divide the whole page in some regions depending on the horizontal and * vertical spacing among different regions. Each region will have an area and an * associated WordsWithCharacters in sorted order. */ class RegionText { public: RegionText() { }; RegionText(const WordsWithCharacters &wordsWithCharacters, const QRect &area) : m_region_wordWithCharacters(wordsWithCharacters), m_area(area) { } inline QString string() const { QString res; foreach(const WordWithCharacters &word, m_region_wordWithCharacters) res += word.text(); return res; } inline WordsWithCharacters text() const { return m_region_wordWithCharacters; } inline QRect area() const { return m_area; } inline void setArea(const QRect &area) { m_area = area; } inline void setText(const WordsWithCharacters &wordsWithCharacters) { m_region_wordWithCharacters = wordsWithCharacters; } private: WordsWithCharacters m_region_wordWithCharacters; QRect m_area; }; RegularAreaRect * TextPage::textArea ( TextSelection * sel) const { if ( d->m_words.isEmpty() ) return new RegularAreaRect(); /** It works like this: There are two cursors, we need to select all the text between them. The coordinates are normalised, leftTop is (0,0) rightBottom is (1,1), so for cursors start (sx,sy) and end (ex,ey) we start with finding text rectangles under those points, if not we search for the first that is to the right to it in the same baseline, if none found, then we search for the first rectangle with a baseline under the cursor, having two points that are the best rectangles to both of the cursors: (rx,ry)x(tx,ty) for start and (ux,uy)x(vx,vy) for end, we do a 1. (rx,ry)x(1,ty) 2. (0,ty)x(1,uy) 3. (0,uy)x(vx,vy) To find the closest rectangle to cursor (cx,cy) we search for a rectangle that either contains the cursor or that has a left border >= cx and bottom border >= cy. */ RegularAreaRect * ret= new RegularAreaRect; const QTransform matrix = d->m_page ? d->m_page->rotationMatrix() : QTransform(); #if 0 int it = -1; int itB = -1; int itE = -1; // ending cursor is higher than start cursor, we need to find positions in reverse NormalizedRect tmp; NormalizedRect start; NormalizedRect end; NormalizedPoint startC = sel->start(); double startCx = startC.x; double startCy = startC.y; NormalizedPoint endC = sel->end(); double endCx = endC.x; double endCy = endC.y; if ( sel->direction() == 1 || ( sel->itB() == -1 && sel->direction() == 0 ) ) { #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "running first loop"; #endif const int count = d->m_words.count(); for ( it = 0; it < count; it++ ) { tmp = *d->m_words[ it ]->area(); if ( tmp.contains( startCx, startCy ) || ( tmp.top <= startCy && tmp.bottom >= startCy && tmp.left >= startCx ) || ( tmp.top >= startCy)) { /// we have found the (rx,ry)x(tx,ty) itB = it; #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "start is" << itB << "count is" << d->m_words.count(); #endif break; } } sel->itB( itB ); } itB = sel->itB(); #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "direction is" << sel->direction(); qCWarning(OkularCoreDebug) << "reloaded start is" << itB << "against" << sel->itB(); #endif if ( sel->direction() == 0 || ( sel->itE() == -1 && sel->direction() == 1 ) ) { #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "running second loop"; #endif for ( it = d->m_words.count() - 1; it >= itB; it-- ) { tmp = *d->m_words[ it ]->area(); if ( tmp.contains( endCx, endCy ) || ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) || ( tmp.bottom <= endCy ) ) { /// we have found the (ux,uy)x(vx,vy) itE = it; #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "ending is" << itE << "count is" << d->m_words.count(); qCWarning(OkularCoreDebug) << "conditions" << tmp.contains( endCx, endCy ) << " " << ( tmp.top <= endCy && tmp.bottom >= endCy && tmp.right <= endCx ) << " " << ( tmp.top >= endCy); #endif break; } } sel->itE( itE ); } #ifdef DEBUG_TEXTPAGE qCWarning(OkularCoreDebug) << "reloaded ending is" << itE << "against" << sel->itE(); #endif if ( sel->itB() != -1 && sel->itE() != -1 ) { start = *d->m_words[ sel->itB() ]->area(); end = *d->m_words[ sel->itE() ]->area(); NormalizedRect first, second, third; /// finding out if there is more than one baseline between them is a hard and discussable task /// we will create a rectangle (rx,0)x(tx,1) and will check how many times does it intersect the /// areas, if more than one -> we have a three or over line selection first = start; second.top = start.bottom; first.right = second.right = 1; third = end; third.left = second.left = 0; second.bottom = end.top; int selMax = qMax( sel->itB(), sel->itE() ); for ( it = qMin( sel->itB(), sel->itE() ); it <= selMax; ++it ) { tmp = *d->m_words[ it ]->area(); if ( tmp.intersects( &first ) || tmp.intersects( &second ) || tmp.intersects( &third ) ) ret->appendShape( d->m_words.at( it )->transformedArea( matrix ) ); } } #else const double scaleX = d->m_page->m_page->width(); const double scaleY = d->m_page->m_page->height(); NormalizedPoint startC = sel->start(); NormalizedPoint endC = sel->end(); NormalizedPoint temp; // if startPoint is right to endPoint swap them if(startC.x > endC.x) { temp = startC; startC = endC; endC = temp; } // minX,maxX,minY,maxY gives the bounding rectangle coordinates of the document const NormalizedRect boundingRect = d->m_page->m_page->boundingBox(); const QRect content = boundingRect.geometry(scaleX,scaleY); const double minX = content.left(); const double maxX = content.right(); const double minY = content.top(); const double maxY = content.bottom(); /** * We will now find out the TinyTextEntity for the startRectangle and TinyTextEntity for * the endRectangle. We have four cases: * * Case 1(a): both startpoint and endpoint are out of the bounding Rectangle and at one side, so the rectangle made of start * and endPoint are outof the bounding rect (do not intersect) * * Case 1(b): both startpoint and endpoint are out of bounding rect, but they are in different side, so is their rectangle * * Case 2(a): find the rectangle which contains start and endpoint and having some * TextEntity * * Case 2(b): if 2(a) fails (if startPoint and endPoint both are unchanged), then we check whether there is any * TextEntity within the rect made by startPoint and endPoint * * Case 3: Now, we may have two type of selection. * 1. startpoint is left-top of start_end and endpoint is right-bottom * 2. startpoint is left-bottom of start_end and endpoint is top-right * * Also, as 2(b) is passed, we might have it,itEnd or both unchanged, but the fact is that we have * text within them. so, we need to search for the best suitable textposition for start and end. * * Case 3(a): We search the nearest rectangle consisting of some * TinyTextEntity right to or bottom of the startPoint for selection 01. * And, for selection 02, we have to search for right and top * * Case 3(b): For endpoint, we have to find the point top of or left to * endpoint if we have selection 01. * Otherwise, the search will be left and bottom */ // we know that startC.x > endC.x, we need to decide which is top and which is bottom const NormalizedRect start_end = (startC.y < endC.y) ? NormalizedRect(startC.x, startC.y, endC.x, endC.y) : NormalizedRect(startC.x, endC.y, endC.x, startC.y); // Case 1(a) if(!boundingRect.intersects(start_end)) return ret; // case 1(b) /** note that, after swapping of start and end, we know that, start is always left to end. but, we cannot say start is positioned upper than end. **/ else { // if start is left to content rect take it to content rect boundary if(startC.x * scaleX < minX) startC.x = minX/scaleX; if(endC.x * scaleX > maxX) endC.x = maxX/scaleX; // if start is top to end (selection type 01) if(startC.y * scaleY < minY) startC.y = minY/scaleY; if(endC.y * scaleY > maxY) endC.y = maxY/scaleY; // if start is bottom to end (selection type 02) if(startC.y * scaleY > maxY) startC.y = maxY/scaleY; if(endC.y * scaleY < minY) endC.y = minY/scaleY; } TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); TextList::ConstIterator start = it, end = itEnd, tmpIt = it; //, tmpItEnd = itEnd; const MergeSide side = d->m_page ? (MergeSide)d->m_page->m_page->totalOrientation() : MergeRight; NormalizedRect tmp; //case 2(a) for ( ; it != itEnd; ++it ) { tmp = (*it)->area; if(tmp.contains(startC.x,startC.y)){ start = it; } if(tmp.contains(endC.x,endC.y)){ end = it; } } //case 2(b) it = tmpIt; if(start == it && end == itEnd) { for ( ; it != itEnd; ++it ) { // is there any text reactangle within the start_end rect tmp = (*it)->area; if(start_end.intersects(tmp)) break; } // we have searched every text entities, but none is within the rectangle created by start and end // so, no selection should be done if(it == itEnd) { return ret; } } it = tmpIt; bool selection_two_start = false; //case 3.a if(start == it) { bool flagV = false; NormalizedRect rect; // selection type 01 if(startC.y <= endC.y) { for ( ; it != itEnd; ++it ) { rect= (*it)->area; rect.isBottom(startC) ? flagV = false: flagV = true; if(flagV && rect.isRight(startC)) { start = it; break; } } } //selection type 02 else { selection_two_start = true; int distance = scaleX + scaleY + 100; int count = 0; for ( ; it != itEnd; ++it ) { rect= (*it)->area; if(rect.isBottomOrLevel(startC) && rect.isRight(startC)) { count++; QRect entRect = rect.geometry(scaleX,scaleY); int xdist, ydist; xdist = entRect.center().x() - startC.x * scaleX; ydist = entRect.center().y() - startC.y * scaleY; //make them positive if(xdist < 0) xdist = -xdist; if(ydist < 0) ydist = -ydist; if( (xdist + ydist) < distance) { distance = xdist+ ydist; start = it; } } } } } //case 3.b if(end == itEnd) { it = tmpIt; itEnd = itEnd-1; bool flagV = false; NormalizedRect rect; if(startC.y <= endC.y) { for ( ; itEnd >= it; itEnd-- ) { rect= (*itEnd)->area; rect.isTop(endC) ? flagV = false: flagV = true; if(flagV && rect.isLeft(endC)) { end = itEnd; break; } } } else { int distance = scaleX + scaleY + 100; for ( ; itEnd >= it; itEnd-- ) { rect= (*itEnd)->area; if(rect.isTopOrLevel(endC) && rect.isLeft(endC)) { QRect entRect = rect.geometry(scaleX,scaleY); int xdist, ydist; xdist = entRect.center().x() - endC.x * scaleX; ydist = entRect.center().y() - endC.y * scaleY; //make them positive if(xdist < 0) xdist = -xdist; if(ydist < 0) ydist = -ydist; if( (xdist + ydist) < distance) { distance = xdist+ ydist; end = itEnd; } } } } } /* if start and end in selection 02 are in the same column, and we start at an empty space we have to remove the selection of last character */ if(selection_two_start) { if(start > end) { start = start - 1; } } // if start is less than end swap them if(start > end) { it = start; start = end; end = it; } // removes the possibility of crash, in case none of 1 to 3 is true if(end == d->m_words.constEnd()) end--; for( ;start <= end ; start++) { ret->appendShape( (*start)->transformedArea( matrix ), side ); } #endif return ret; } RegularAreaRect* TextPage::findText( int searchID, const QString &query, SearchDirection direct, Qt::CaseSensitivity caseSensitivity, const RegularAreaRect *area ) { SearchDirection dir=direct; // invalid search request if ( d->m_words.isEmpty() || query.isEmpty() || ( area && area->isNull() ) ) return 0; TextList::ConstIterator start; int start_offset = 0; TextList::ConstIterator end; const QMap< int, SearchPoint* >::const_iterator sIt = d->m_searchPoints.constFind( searchID ); if ( sIt == d->m_searchPoints.constEnd() ) { // if no previous run of this search is found, then set it to start // from the beginning (respecting the search direction) if ( dir == NextResult ) dir = FromTop; else if ( dir == PreviousResult ) dir = FromBottom; } bool forward = true; switch ( dir ) { case FromTop: start = d->m_words.constBegin(); start_offset = 0; end = d->m_words.constEnd(); break; case FromBottom: start = d->m_words.constEnd(); start_offset = 0; end = d->m_words.constBegin(); forward = false; break; case NextResult: start = (*sIt)->it_end; start_offset = (*sIt)->offset_end; end = d->m_words.constEnd(); break; case PreviousResult: start = (*sIt)->it_begin; start_offset = (*sIt)->offset_begin; end = d->m_words.constBegin(); forward = false; break; }; RegularAreaRect* ret = 0; const TextComparisonFunction cmpFn = caseSensitivity == Qt::CaseSensitive ? CaseSensitiveCmpFn : CaseInsensitiveCmpFn; if ( forward ) { ret = d->findTextInternalForward( searchID, query, cmpFn, start, start_offset, end ); } else { ret = d->findTextInternalBackward( searchID, query, cmpFn, start, start_offset, end ); } return ret; } // hyphenated '-' must be at the end of a word, so hyphenation means // we have a '-' just followed by a '\n' character // check if the string contains a '-' character // if the '-' is the last entry static int stringLengthAdaptedWithHyphen(const QString &str, const TextList::ConstIterator &it, const TextList::ConstIterator &textListEnd) { int len = str.length(); // hyphenated '-' must be at the end of a word, so hyphenation means // we have a '-' just followed by a '\n' character // check if the string contains a '-' character // if the '-' is the last entry if ( str.endsWith( QLatin1Char('-') ) ) { // validity chek of it + 1 if ( ( it + 1 ) != textListEnd ) { // 1. if the next character is '\n' const QString &lookahedStr = (*(it+1))->text(); if (lookahedStr.startsWith(QLatin1Char('\n'))) { len -= 1; } else { // 2. if the next word is in a different line or not const NormalizedRect& hyphenArea = (*it)->area; const NormalizedRect& lookaheadArea = (*(it + 1))->area; // lookahead to check whether both the '-' rect and next character rect overlap if( !doesConsumeY( hyphenArea, lookaheadArea, 70 ) ) { len -= 1; } } } } // else if it is the second last entry - for example in pdf format else if (str.endsWith(QLatin1String("-\n"))) { len -= 2; } return len; } RegularAreaRect* TextPagePrivate::searchPointToArea(const SearchPoint* sp) { const QTransform matrix = m_page ? m_page->rotationMatrix() : QTransform(); RegularAreaRect* ret=new RegularAreaRect; for (TextList::ConstIterator it = sp->it_begin; ; it++) { const TinyTextEntity* curEntity = *it; ret->append( curEntity->transformedArea( matrix ) ); if (it == sp->it_end) { break; } } ret->simplify(); return ret; } RegularAreaRect* TextPagePrivate::findTextInternalForward( int searchID, const QString &_query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end) { // normalize query search all unicode (including glyphs) const QString query = _query.normalized(QString::NormalizationForm_KC); // j is the current position in our query // len is the length of the string in TextEntity // queryLeft is the length of the query we have left int j=0, queryLeft=query.length(); TextList::ConstIterator it = start; int offset = start_offset; TextList::ConstIterator it_begin = TextList::ConstIterator(); int offset_begin = 0; //dummy initial value to suppress compiler warnings while ( it != end ) { const TinyTextEntity* curEntity = *it; const QString& str = curEntity->text(); int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd()); if (offset >= len) { it++; offset = 0; continue; } if ( it_begin == TextList::ConstIterator() ) { it_begin = it; offset_begin = offset; } int min=qMin(queryLeft,len-offset); { #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << str.midRef(offset, min) << ":" << _query.midRef(j, min); #endif // we have equal (or less than) area of the query left as the length of the current // entity if ( !comparer( str.midRef( offset, min ), query.midRef( j, min ) ) ) { // we have not matched // this means we do not have a complete match // we need to get back to query start // and continue the search from this place #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tnot matched"; #endif j = 0; queryLeft=query.length(); it = it_begin; offset = offset_begin+1; it_begin = TextList::ConstIterator(); } else { // we have a match // move the current position in the query // to the position after the length of this string // we matched // subtract the length of the current entity from // the left length of the query #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tmatched"; #endif j += min; queryLeft -= min; if (queryLeft==0) { // save or update the search point for the current searchID QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt == m_searchPoints.end() ) { sIt = m_searchPoints.insert( searchID, new SearchPoint ); } SearchPoint* sp = *sIt; sp->it_begin = it_begin; sp->it_end = it; sp->offset_begin = offset_begin; sp->offset_end = offset + min; return searchPointToArea(sp); } it++; offset = 0; } } } // end of loop - it means that we've ended the textentities const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt != m_searchPoints.end() ) { SearchPoint* sp = *sIt; m_searchPoints.erase( sIt ); delete sp; } return 0; } RegularAreaRect* TextPagePrivate::findTextInternalBackward( int searchID, const QString &_query, TextComparisonFunction comparer, const TextList::ConstIterator &start, int start_offset, const TextList::ConstIterator &end) { // normalize query to search all unicode (including glyphs) const QString query = _query.normalized(QString::NormalizationForm_KC); // j is the current position in our query // len is the length of the string in TextEntity // queryLeft is the length of the query we have left int j=query.length(), queryLeft=query.length(); TextList::ConstIterator it = start; int offset = start_offset; TextList::ConstIterator it_begin = TextList::ConstIterator(); int offset_begin = 0; //dummy initial value to suppress compiler warnings while ( true ) { if (offset <= 0) { if ( it == end ) { break; } it--; } const TinyTextEntity* curEntity = *it; const QString& str = curEntity->text(); int len = stringLengthAdaptedWithHyphen(str, it, m_words.constEnd()); if (offset <= 0) { offset = len; } if ( it_begin == TextList::ConstIterator() ) { it_begin = it; offset_begin = offset; } int min=qMin(queryLeft,offset); { #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << str.midRef(offset-min, min) << " : " << _query.midRef(j-min, min); #endif // we have equal (or less than) area of the query left as the length of the current // entity // Note len is not str.length() so we can't use rightRef here if ( !comparer( str.midRef(offset-min, min ), query.midRef( j - min, min ) ) ) { // we have not matched // this means we do not have a complete match // we need to get back to query start // and continue the search from this place #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tnot matched"; #endif j = query.length(); queryLeft = query.length(); it = it_begin; offset = offset_begin-1; it_begin = TextList::ConstIterator(); } else { // we have a match // move the current position in the query // to the position after the length of this string // we matched // subtract the length of the current entity from // the left length of the query #ifdef DEBUG_TEXTPAGE qCDebug(OkularCoreDebug) << "\tmatched"; #endif j -= min; queryLeft -= min; if ( queryLeft == 0 ) { // save or update the search point for the current searchID QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt == m_searchPoints.end() ) { sIt = m_searchPoints.insert( searchID, new SearchPoint ); } SearchPoint* sp = *sIt; sp->it_begin = it; sp->it_end = it_begin; sp->offset_begin = offset - min; sp->offset_end = offset_begin; return searchPointToArea(sp); } offset = 0; } } } // end of loop - it means that we've ended the textentities const QMap< int, SearchPoint* >::iterator sIt = m_searchPoints.find( searchID ); if ( sIt != m_searchPoints.end() ) { SearchPoint* sp = *sIt; m_searchPoints.erase( sIt ); delete sp; } return 0; } QString TextPage::text(const RegularAreaRect *area) const { return text(area, AnyPixelTextAreaInclusionBehaviour); } QString TextPage::text(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const { if ( area && area->isNull() ) return QString(); TextList::ConstIterator it = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); QString ret; if ( area ) { for ( ; it != itEnd; ++it ) { if (b == AnyPixelTextAreaInclusionBehaviour) { if ( area->intersects( (*it)->area ) ) { ret += (*it)->text(); } } else { NormalizedPoint center = (*it)->area.center(); if ( area->contains( center.x, center.y ) ) { ret += (*it)->text(); } } } } else { for ( ; it != itEnd; ++it ) ret += (*it)->text(); } return ret; } static bool compareTinyTextEntityX(const WordWithCharacters &first, const WordWithCharacters &second) { QRect firstArea = first.area().roundedGeometry(1000,1000); QRect secondArea = second.area().roundedGeometry(1000,1000); return firstArea.left() < secondArea.left(); } static bool compareTinyTextEntityY(const WordWithCharacters &first, const WordWithCharacters &second) { const QRect firstArea = first.area().roundedGeometry(1000,1000); const QRect secondArea = second.area().roundedGeometry(1000,1000); return firstArea.top() < secondArea.top(); } /** * Sets a new world list. Deleting the contents of the old one */ void TextPagePrivate::setWordList(const TextList &list) { qDeleteAll(m_words); m_words = list; } /** * Remove all the spaces in between texts. It will make all the generators * same, whether they save spaces(like pdf) or not(like djvu). */ static void removeSpace(TextList *words) { TextList::Iterator it = words->begin(); const QString str(QLatin1Char(' ')); while ( it != words->end() ) { if((*it)->text() == str) { it = words->erase(it); } else { ++it; } } } /** * We will read the TinyTextEntity from characters and try to create words from there. * Note: characters might be already characters for some generators, but we will keep * the nomenclature characters for the generator produced data. The resulting * WordsWithCharacters memory has to be managed by the caller, both the * WordWithCharacters::word and WordWithCharacters::characters contents */ static WordsWithCharacters makeWordFromCharacters(const TextList &characters, int pageWidth, int pageHeight) { /** * We will traverse characters and try to create words from the TinyTextEntities in it. * We will search TinyTextEntity blocks and merge them until we get a * space between two consecutive TinyTextEntities. When we get a space * we can take it as a end of word. Then we store the word as a TinyTextEntity * and keep it in newList. * We create a RegionText named regionWord that contains the word and the characters associated with it and * a rectangle area of the element in newList. */ WordsWithCharacters wordsWithCharacters; TextList::ConstIterator it = characters.begin(), itEnd = characters.end(), tmpIt; int newLeft,newRight,newTop,newBottom; int index = 0; for( ; it != itEnd ; it++) { QString textString = (*it)->text(); QString newString; QRect lineArea = (*it)->area.roundedGeometry(pageWidth,pageHeight),elementArea; TextList wordCharacters; tmpIt = it; int space = 0; while (!space) { if (textString.length()) { newString.append(textString); // when textString is the start of the word if (tmpIt == it) { NormalizedRect newRect(lineArea,pageWidth,pageHeight); wordCharacters.append(new TinyTextEntity(textString.normalized (QString::NormalizationForm_KC), newRect)); } else { NormalizedRect newRect(elementArea,pageWidth,pageHeight); wordCharacters.append(new TinyTextEntity(textString.normalized (QString::NormalizationForm_KC), newRect)); } } ++it; /* we must have to put this line before the if condition of it==itEnd otherwise the last character can be missed */ if (it == itEnd) break; elementArea = (*it)->area.roundedGeometry(pageWidth,pageHeight); if (!doesConsumeY(elementArea, lineArea, 60)) { --it; break; } const int text_y1 = elementArea.top() , text_x1 = elementArea.left(), text_y2 = elementArea.y() + elementArea.height(), text_x2 = elementArea.x() + elementArea.width(); const int line_y1 = lineArea.top() ,line_x1 = lineArea.left(), line_y2 = lineArea.y() + lineArea.height(), line_x2 = lineArea.x() + lineArea.width(); space = elementArea.left() - lineArea.right(); if (space != 0) { it--; break; } newLeft = text_x1 < line_x1 ? text_x1 : line_x1; newRight = line_x2 > text_x2 ? line_x2 : text_x2; newTop = text_y1 > line_y1 ? line_y1 : text_y1; newBottom = text_y2 > line_y2 ? text_y2 : line_y2; lineArea.setLeft (newLeft); lineArea.setTop (newTop); lineArea.setWidth( newRight - newLeft ); lineArea.setHeight( newBottom - newTop ); textString = (*it)->text(); } // if newString is not empty, save it if (!newString.isEmpty()) { const NormalizedRect newRect(lineArea, pageWidth, pageHeight); TinyTextEntity *word = new TinyTextEntity(newString.normalized(QString::NormalizationForm_KC), newRect); wordsWithCharacters.append(WordWithCharacters(word, wordCharacters)); index++; } if(it == itEnd) break; } return wordsWithCharacters; } /** * Create Lines from the words and sort them */ QList< QPair > makeAndSortLines(const WordsWithCharacters &wordsTmp, int pageWidth, int pageHeight) { /** * We cannot assume that the generator will give us texts in the right order. * We can only assume that we will get texts in the page and their bounding * rectangle. The texts can be character, word, half-word anything. * So, we need to: ** * 1. Sort rectangles/boxes containing texts by y0(top) * 2. Create textline where there is y overlap between TinyTextEntity 's * 3. Within each line sort the TinyTextEntity 's by x0(left) */ QList< QPair > lines; /* Make a new copy of the TextList in the words, so that the wordsTmp and lines do not contain same pointers for all the TinyTextEntity. */ QList words = wordsTmp; // Step 1 qSort(words.begin(),words.end(),compareTinyTextEntityY); // Step 2 QList::Iterator it = words.begin(), itEnd = words.end(); //for every non-space texts(characters/words) in the textList for( ; it != itEnd ; it++) { const QRect elementArea = (*it).area().roundedGeometry(pageWidth,pageHeight); bool found = false; for( int i = 0 ; i < lines.length() ; i++) { /* the line area which will be expanded line_rects is only necessary to preserve the topmin and bottommax of all the texts in the line, left and right is not necessary at all */ QRect &lineArea = lines[i].second; const int text_y1 = elementArea.top() , text_y2 = elementArea.top() + elementArea.height() , text_x1 = elementArea.left(), text_x2 = elementArea.left() + elementArea.width(); const int line_y1 = lineArea.top() , line_y2 = lineArea.top() + lineArea.height(), line_x1 = lineArea.left(), line_x2 = lineArea.left() + lineArea.width(); /* if the new text and the line has y overlapping parts of more than 70%, the text will be added to this line */ if(doesConsumeY(elementArea,lineArea,70)) { WordsWithCharacters &line = lines[i].first; line.append(*it); const int newLeft = line_x1 < text_x1 ? line_x1 : text_x1; const int newRight = line_x2 > text_x2 ? line_x2 : text_x2; const int newTop = line_y1 < text_y1 ? line_y1 : text_y1; const int newBottom = text_y2 > line_y2 ? text_y2 : line_y2; lineArea = QRect( newLeft,newTop, newRight - newLeft, newBottom - newTop ); found = true; } if(found) break; } /* when we have found a new line create a new TextList containing only one element and append it to the lines */ if(!found) { WordsWithCharacters tmp; tmp.append((*it)); lines.append(QPair(tmp, elementArea)); } } // Step 3 for(int i = 0 ; i < lines.length() ; i++) { WordsWithCharacters &list = lines[i].first; qSort(list.begin(), list.end(), compareTinyTextEntityX); } return lines; } /** * Calculate Statistical information from the lines we made previously */ static void calculateStatisticalInformation(const QList &words, int pageWidth, int pageHeight, int *word_spacing, int *line_spacing, int *col_spacing) { /** * For the region, defined by line_rects and lines * 1. Make line statistical analysis to find the line spacing * 2. Make character statistical analysis to differentiate between * word spacing and column spacing. */ /** * Step 0 */ const QList< QPair > sortedLines = makeAndSortLines(words, pageWidth, pageHeight); /** * Step 1 */ QMap line_space_stat; for(int i = 0 ; i < sortedLines.length(); i++) { const QRect rectUpper = sortedLines.at(i).second; if(i+1 == sortedLines.length()) break; const QRect rectLower = sortedLines.at(i+1).second; int linespace = rectLower.top() - (rectUpper.top() + rectUpper.height()); if(linespace < 0) linespace =-linespace; if(line_space_stat.contains(linespace)) line_space_stat[linespace]++; else line_space_stat[linespace] = 1; } *line_spacing = 0; int weighted_count = 0; QMapIterator iterate_linespace(line_space_stat); while(iterate_linespace.hasNext()) { iterate_linespace.next(); *line_spacing += iterate_linespace.value() * iterate_linespace.key(); weighted_count += iterate_linespace.value(); } if (*line_spacing != 0) *line_spacing = (int) ( (double)*line_spacing / (double) weighted_count + 0.5); /** * Step 2 */ // We would like to use QMap instead of QHash as it will keep the keys sorted QMap hor_space_stat; QMap col_space_stat; QList< QList > space_rects; QList max_hor_space_rects; // Space in every line for(int i = 0 ; i < sortedLines.length() ; i++) { const WordsWithCharacters list = sortedLines.at(i).first; QList line_space_rects; int maxSpace = 0, minSpace = pageWidth; // for every TinyTextEntity element in the line WordsWithCharacters::ConstIterator it = list.begin(), itEnd = list.end(); QRect max_area1,max_area2; QString before_max, after_max; // for every line for( ; it != itEnd ; it++ ) { const QRect area1 = (*it).area().roundedGeometry(pageWidth,pageHeight); if( it+1 == itEnd ) break; const QRect area2 = (*(it+1)).area().roundedGeometry(pageWidth,pageHeight); int space = area2.left() - area1.right(); if(space > maxSpace) { max_area1 = area1; max_area2 = area2; maxSpace = space; before_max = (*it).text(); after_max = (*(it+1)).text(); } if(space < minSpace && space != 0) minSpace = space; //if we found a real space, whose length is not zero and also less than the pageWidth if(space != 0 && space != pageWidth) { // increase the count of the space amount if(hor_space_stat.contains(space)) hor_space_stat[space]++; else hor_space_stat[space] = 1; int left,right,top,bottom; left = area1.right(); right = area2.left(); top = area2.top() < area1.top() ? area2.top() : area1.top(); bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom(); QRect rect(left,top,right-left,bottom-top); line_space_rects.append(rect); } } space_rects.append(line_space_rects); if(hor_space_stat.contains(maxSpace)) { if(hor_space_stat[maxSpace] != 1) hor_space_stat[maxSpace]--; else hor_space_stat.remove(maxSpace); } if(maxSpace != 0) { if (col_space_stat.contains(maxSpace)) col_space_stat[maxSpace]++; else col_space_stat[maxSpace] = 1; //store the max rect of each line const int left = max_area1.right(); const int right = max_area2.left(); const int top = (max_area1.top() > max_area2.top()) ? max_area2.top() : max_area1.top(); const int bottom = (max_area1.bottom() < max_area2.bottom()) ? max_area2.bottom() : max_area1.bottom(); const QRect rect(left,top,right-left,bottom-top); max_hor_space_rects.append(rect); } else max_hor_space_rects.append(QRect(0,0,0,0)); } // All the between word space counts are in hor_space_stat *word_spacing = 0; weighted_count = 0; QMapIterator iterate(hor_space_stat); while (iterate.hasNext()) { iterate.next(); if(iterate.key() > 0) { *word_spacing += iterate.value() * iterate.key(); weighted_count += iterate.value(); } } if(weighted_count) *word_spacing = (int) ((double)*word_spacing / (double)weighted_count + 0.5); *col_spacing = 0; QMapIterator iterate_col(col_space_stat); while (iterate_col.hasNext()) { iterate_col.next(); if(iterate_col.value() > *col_spacing) *col_spacing = iterate_col.value(); } *col_spacing = col_space_stat.key(*col_spacing); // if there is just one line in a region, there is no point in dividing it if(sortedLines.length() == 1) *word_spacing = *col_spacing; } /** * Implements the XY Cut algorithm for textpage segmentation * The resulting RegionTextList will contain RegionText whose WordsWithCharacters::word and * WordsWithCharacters::characters are reused from wordsWithCharacters (i.e. no new nor delete happens in this function) */ static RegionTextList XYCutForBoundingBoxes(const QList &wordsWithCharacters, const NormalizedRect &boundingBox, int pageWidth, int pageHeight) { RegionTextList tree; QRect contentRect(boundingBox.geometry(pageWidth,pageHeight)); const RegionText root(wordsWithCharacters, contentRect); // start the tree with the root, it is our only region at the start tree.push_back(root); int i = 0; // while traversing the tree has not been ended while(i < tree.length()) { const RegionText node = tree.at(i); QRect regionRect = node.area(); /** * 1. calculation of projection profiles */ // allocate the size of proj profiles and initialize with 0 int size_proj_y = node.area().height(); int size_proj_x = node.area().width(); //dynamic memory allocation QVarLengthArray proj_on_xaxis(size_proj_x); QVarLengthArray proj_on_yaxis(size_proj_y); for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] = 0; for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] = 0; const QList list = node.text(); // Calculate tcx and tcy locally for each new region int word_spacing, line_spacing, column_spacing; calculateStatisticalInformation(list, pageWidth, pageHeight, &word_spacing, &line_spacing, &column_spacing); const int tcx = word_spacing * 2; const int tcy = line_spacing * 2; int maxX = 0 , maxY = 0; int avgX = 0; int count; // for every text in the region for(int j = 0 ; j < list.length() ; ++j ) { TinyTextEntity *ent = list.at(j).word; const QRect entRect = ent->area.geometry(pageWidth, pageHeight); // calculate vertical projection profile proj_on_xaxis1 for(int k = entRect.left() ; k <= entRect.left() + entRect.width() ; ++k) { if( ( k-regionRect.left() ) < size_proj_x && ( k-regionRect.left() ) >= 0 ) proj_on_xaxis[k - regionRect.left()] += entRect.height(); } // calculate horizontal projection profile in the same way for(int k = entRect.top() ; k <= entRect.top() + entRect.height() ; ++k) { if( ( k-regionRect.top() ) < size_proj_y && ( k-regionRect.top() ) >= 0 ) proj_on_yaxis[k - regionRect.top()] += entRect.width(); } } for( int j = 0 ; j < size_proj_y ; ++j ) { if (proj_on_yaxis[j] > maxY) maxY = proj_on_yaxis[j]; } avgX = count = 0; for( int j = 0 ; j < size_proj_x ; ++j ) { if(proj_on_xaxis[j] > maxX) maxX = proj_on_xaxis[j]; if(proj_on_xaxis[j]) { count++; avgX+= proj_on_xaxis[j]; } } if(count) avgX /= count; /** * 2. Cleanup Boundary White Spaces and removal of noise */ int xbegin = 0, xend = size_proj_x - 1; int ybegin = 0, yend = size_proj_y - 1; while(xbegin < size_proj_x && proj_on_xaxis[xbegin] <= 0) xbegin++; while(xend >= 0 && proj_on_xaxis[xend] <= 0) xend--; while(ybegin < size_proj_y && proj_on_yaxis[ybegin] <= 0) ybegin++; while(yend >= 0 && proj_on_yaxis[yend] <= 0) yend--; //update the regionRect int old_left = regionRect.left(), old_top = regionRect.top(); regionRect.setLeft(old_left + xbegin); regionRect.setRight(old_left + xend); regionRect.setTop(old_top + ybegin); regionRect.setBottom(old_top + yend); int tnx = (int)((double)avgX * 10.0 / 100.0 + 0.5), tny = 0; for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] -= tnx; for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] -= tny; /** * 3. Find the Widest gap */ int gap_hor = -1, pos_hor = -1; int begin = -1, end = -1; // find all hor_gaps and find the maximum between them for(int j = 1 ; j < size_proj_y ; ++j) { //transition from white to black if(begin >= 0 && proj_on_yaxis[j-1] <= 0 && proj_on_yaxis[j] > 0) end = j; //transition from black to white if(proj_on_yaxis[j-1] > 0 && proj_on_yaxis[j] <= 0) begin = j; if(begin > 0 && end > 0 && end-begin > gap_hor) { gap_hor = end - begin; pos_hor = (end + begin) / 2; begin = -1; end = -1; } } begin = -1, end = -1; int gap_ver = -1, pos_ver = -1; //find all the ver_gaps and find the maximum between them for(int j = 1 ; j < size_proj_x ; ++j) { //transition from white to black if(begin >= 0 && proj_on_xaxis[j-1] <= 0 && proj_on_xaxis[j] > 0){ end = j; } //transition from black to white if(proj_on_xaxis[j-1] > 0 && proj_on_xaxis[j] <= 0) begin = j; if(begin > 0 && end > 0 && end-begin > gap_ver) { gap_ver = end - begin; pos_ver = (end + begin) / 2; begin = -1; end = -1; } } int cut_pos_x = pos_ver, cut_pos_y = pos_hor; int gap_x = gap_ver, gap_y = gap_hor; /** * 4. Cut the region and make nodes (left,right) or (up,down) */ bool cut_hor = false, cut_ver = false; // For horizontal cut const int topHeight = cut_pos_y - (regionRect.top() - old_top); const QRect topRect(regionRect.left(), regionRect.top(), regionRect.width(), topHeight); const QRect bottomRect(regionRect.left(), regionRect.top() + topHeight, regionRect.width(), regionRect.height() - topHeight ); // For vertical Cut const int leftWidth = cut_pos_x - (regionRect.left() - old_left); const QRect leftRect(regionRect.left(), regionRect.top(), leftWidth, regionRect.height()); const QRect rightRect(regionRect.left() + leftWidth, regionRect.top(), regionRect.width() - leftWidth, regionRect.height()); if(gap_y >= gap_x && gap_y >= tcy) cut_hor = true; else if(gap_y >= gap_x && gap_y <= tcy && gap_x >= tcx) cut_ver = true; else if(gap_x >= gap_y && gap_x >= tcx) cut_ver = true; else if(gap_x >= gap_y && gap_x <= tcx && gap_y >= tcy) cut_hor = true; // no cut possible else { // we can now update the node rectangle with the shrinked rectangle RegionText tmpNode = tree.at(i); tmpNode.setArea(regionRect); tree.replace(i,tmpNode); i++; continue; } WordsWithCharacters list1,list2; // horizontal cut, topRect and bottomRect if(cut_hor) { for( int j = 0 ; j < list.length() ; ++j ) { const WordWithCharacters word = list.at(j); const QRect wordRect = word.area().geometry(pageWidth,pageHeight); if(topRect.intersects(wordRect)) list1.append(word); else list2.append(word); } RegionText node1(list1,topRect); RegionText node2(list2,bottomRect); tree.replace(i,node1); tree.insert(i+1,node2); } //vertical cut, leftRect and rightRect else if(cut_ver) { for( int j = 0 ; j < list.length() ; ++j ) { const WordWithCharacters word = list.at(j); const QRect wordRect = word.area().geometry(pageWidth,pageHeight); if(leftRect.intersects(wordRect)) list1.append(word); else list2.append(word); } RegionText node1(list1,leftRect); RegionText node2(list2,rightRect); tree.replace(i,node1); tree.insert(i+1,node2); } } return tree; } /** * Add spaces in between words in a line. It reuses the pointers passed in tree and might add new ones. You will need to take care of deleting them if needed */ WordsWithCharacters addNecessarySpace(RegionTextList tree, int pageWidth, int pageHeight) { /** * 1. Call makeAndSortLines before adding spaces in between words in a line * 2. Now add spaces between every two words in a line * 3. Finally, extract all the space separated texts from each region and return it */ // Only change the texts under RegionTexts, not the area for(int j = 0 ; j < tree.length() ; j++) { RegionText &tmpRegion = tree[j]; // Step 01 QList< QPair > sortedLines = makeAndSortLines(tmpRegion.text(), pageWidth, pageHeight); // Step 02 for(int i = 0 ; i < sortedLines.length() ; i++) { WordsWithCharacters &list = sortedLines[i].first; for(int k = 0 ; k < list.length() ; k++ ) { const QRect area1 = list.at(k).area().roundedGeometry(pageWidth,pageHeight); if( k+1 >= list.length() ) break; const QRect area2 = list.at(k+1).area().roundedGeometry(pageWidth,pageHeight); const int space = area2.left() - area1.right(); if(space != 0) { // Make a TinyTextEntity of string space and push it between it and it+1 const int left = area1.right(); const int right = area2.left(); const int top = area2.top() < area1.top() ? area2.top() : area1.top(); const int bottom = area2.bottom() > area1.bottom() ? area2.bottom() : area1.bottom(); const QString spaceStr(QStringLiteral(" ")); const QRect rect(QPoint(left,top),QPoint(right,bottom)); const NormalizedRect entRect(rect,pageWidth,pageHeight); TinyTextEntity *ent1 = new TinyTextEntity(spaceStr, entRect); TinyTextEntity *ent2 = new TinyTextEntity(spaceStr, entRect); WordWithCharacters word(ent1, QList() << ent2); list.insert(k+1, word); // Skip the space k++; } } } WordsWithCharacters tmpList; for(int i = 0 ; i < sortedLines.length() ; i++) { tmpList += sortedLines.at(i).first; } tmpRegion.setText(tmpList); } // Step 03 WordsWithCharacters tmp; for(int i = 0 ; i < tree.length() ; i++) { tmp += tree.at(i).text(); } return tmp; } /** * Correct the textOrder, all layout recognition works here */ void TextPagePrivate::correctTextOrder() { //m_page->m_page->width() and m_page->m_page->height() are in pixels at //100% zoom level, and thus depend on display DPI. We scale pageWidth and //pageHeight to remove the dependence. Otherwise bugs would be more difficult //to reproduce and Okular could fail in extreme cases like a large TV with low DPI. const double scalingFactor = 2000.0 / (m_page->m_page->width() + m_page->m_page->height()); const int pageWidth = (int) (scalingFactor * m_page->m_page->width() ); const int pageHeight = (int) (scalingFactor * m_page->m_page->height()); TextList characters = m_words; /** * Remove spaces from the text */ removeSpace(&characters); /** * Construct words from characters */ const QList wordsWithCharacters = makeWordFromCharacters(characters, pageWidth, pageHeight); /** * Make a XY Cut tree for segmentation of the texts */ const RegionTextList tree = XYCutForBoundingBoxes(wordsWithCharacters, m_page->m_page->boundingBox(), pageWidth, pageHeight); /** * Add spaces to the word */ const WordsWithCharacters listWithWordsAndSpaces = addNecessarySpace(tree, pageWidth, pageHeight); /** * Break the words into characters */ TextList listOfCharacters; foreach(const WordWithCharacters &word, listWithWordsAndSpaces) { delete word.word; listOfCharacters.append(word.characters); } setWordList(listOfCharacters); } TextEntity::List TextPage::words(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const { if ( area && area->isNull() ) return TextEntity::List(); TextEntity::List ret; if ( area ) { foreach (TinyTextEntity *te, d->m_words) { if (b == AnyPixelTextAreaInclusionBehaviour) { if ( area->intersects( te->area ) ) { ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); } } else { const NormalizedPoint center = te->area.center(); if ( area->contains( center.x, center.y ) ) { ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); } } } } else { foreach (TinyTextEntity *te, d->m_words) { ret.append( new TextEntity( te->text(), new Okular::NormalizedRect( te->area) ) ); } } return ret; } RegularAreaRect * TextPage::wordAt( const NormalizedPoint &p, QString *word ) const { TextList::ConstIterator itBegin = d->m_words.constBegin(), itEnd = d->m_words.constEnd(); TextList::ConstIterator it = itBegin; TextList::ConstIterator posIt = itEnd; for ( ; it != itEnd; ++it ) { if ( (*it)->area.contains( p.x, p.y ) ) { posIt = it; break; } } QString text; if ( posIt != itEnd ) { if ( (*posIt)->text().simplified().isEmpty() ) { return NULL; } // Find the first TinyTextEntity of the word while ( posIt != itBegin ) { --posIt; const QString itText = (*posIt)->text(); if ( itText.right(1).at(0).isSpace() ) { if (itText.endsWith(QLatin1String("-\n"))) { // Is an hyphenated word // continue searching the start of the word back continue; } if (itText == QLatin1String("\n") && posIt != itBegin ) { --posIt; if ((*posIt)->text().endsWith(QLatin1String("-"))) { // Is an hyphenated word // continue searching the start of the word back continue; } ++posIt; } ++posIt; break; } } RegularAreaRect *ret = new RegularAreaRect(); for ( ; posIt != itEnd; ++posIt ) { const QString itText = (*posIt)->text(); if ( itText.simplified().isEmpty() ) { break; } ret->appendShape( (*posIt)->area ); text += (*posIt)->text(); if (itText.right(1).at(0).isSpace()) { if (!text.endsWith(QLatin1String("-\n"))) { break; } } } if (word) { *word = text; } return ret; } else { return NULL; } } diff --git a/generators/chm/libokularGenerator_chmlib.json b/generators/chm/libokularGenerator_chmlib.json index 08906d6cd..c4a322664 100644 --- a/generators/chm/libokularGenerator_chmlib.json +++ b/generators/chm/libokularGenerator_chmlib.json @@ -1,107 +1,109 @@ { "KPlugin": { "Authors": [ { "Email": "niedakh@gmail.com", "Name": "Piotr Szymański", "Name[sr@ijekavian]": "Пјотр Шимањски", "Name[sr@ijekavianlatin]": "Pjotr Šimanjski", "Name[sr@latin]": "Pjotr Šimanjski", "Name[sr]": "Пјотр Шимањски", "Name[x-test]": "xxPiotr Szymańskixx" }, { "Email": "aacid@kde.org", "Name": "Albert Astals Cid", "Name[ia]": "Albert Astals Cid", "Name[sr@ijekavian]": "Алберт Асталс Сид", "Name[sr@ijekavianlatin]": "Albert Astals Sid", "Name[sr@latin]": "Albert Astals Sid", "Name[sr]": "Алберт Асталс Сид", "Name[x-test]": "xxAlbert Astals Cidxx" } ], "Copyright": "© 2005-2007 Piotr Szymański\n© 2008 Albert Astals Cid", "Copyright[et]": "© 2005-2007: Piotr Szymański\n© 2008: Albert Astals Cid", "Copyright[fi]": "© 2005–2007 Piotr Szymański\n© 2008 Albert Astals Cid", "Copyright[nn]": "© 2005–2007 Piotr Szymański\n© 2008 Albert Astals Cid", "Copyright[ru]": "© Piotr Szymański, 2005-2007\n© Albert Astals Cid, 2008", "Copyright[sr@ijekavian]": "© 2005—2007, Пјотр Шимањски\n© 2008, Алберт Асталс Сид", "Copyright[sr@ijekavianlatin]": "© 2005—2007, Pjotr Šimanjski\n© 2008, Albert Astals Sid", "Copyright[sr@latin]": "© 2005—2007, Pjotr Šimanjski\n© 2008, Albert Astals Sid", "Copyright[sr]": "© 2005—2007, Пјотр Шимањски\n© 2008, Алберт Асталс Сид", "Copyright[uk]": "© Piotr Szymański, 2005–2007\n© Albert Astals Cid, 2008", "Copyright[x-test]": "xx© 2005-2007 Piotr Szymański\n© 2008 Albert Astals Cidxx", "Description": "A Microsoft Windows help file renderer", "Description[ca@valencia]": "Un renderitzador de fitxers d'ajuda del Windows de Microsoft", "Description[ca]": "Un renderitzador de fitxers d'ajuda del Windows de Microsoft", "Description[cs]": "Nástroj pro zobrazování nápovědy Microsoft Windows", "Description[de]": "Darstellungsprogramm für Hilfedateien aus Microsoft Windows", "Description[el]": "Πρόγραμμα αποτύπωσης για αρχεία βοήθειας Microsoft Windows", "Description[es]": "Un visor de archivos de ayuda de Microsoft Windows", "Description[et]": "Microsoft Windowsi abifailide renderdaja", "Description[fi]": "Microsoft Windows -ohjetiedostohahmonnin", "Description[fr]": "Un système de rendu pour les fichiers d'aide Windows", + "Description[gl]": "Un visor de ficheiros de axuda de Microsoft Windows", "Description[ia]": "Un rendition de file de adjuta de Microsoft Windows", "Description[it]": "Un visualizzatore per file di aiuto di Microsoft Windows", "Description[ko]": "마이크로소프트 Windows 도움말 파일 표시기", "Description[nl]": "Een viewer voor Microsoft Windows-helpbestanden", "Description[nn]": "Ein framvisar for Microsoft Windows-hjelpefiler", "Description[pl]": "System zajmujący się wyświetlaniem plików pomocy Microsoft Windows", "Description[pt]": "Um visualizador de ficheiros de ajuda do Microsoft Windows", "Description[ru]": "Модуль поддержки формата CHM (справка Microsoft Windows)", "Description[sk]": "Vykresľovanie súborov pomocníka Microsoft Windows", "Description[sl]": "Izrisovalnik datotek za pomoč za Microsoft Windows", "Description[sr@ijekavian]": "Рендерер за фајлове Виндоузове помоћи", "Description[sr@ijekavianlatin]": "Renderer za fajlove Windowsove pomoći", "Description[sr@latin]": "Renderer za fajlove Windowsove pomoći", "Description[sr]": "Рендерер за фајлове Виндоузове помоћи", "Description[sv]": "Ett återgivningsprogram av Microsoft Windows hjälpfiler", "Description[tr]": "Microsoft Windows yardım dosyası oluşturucusu", "Description[uk]": "Відображення файлів довідки Microsoft Windows", "Description[x-test]": "xxA Microsoft Windows help file rendererxx", "Description[zh_CN]": "Microsoft Windows 帮助文件渲染器", "Description[zh_TW]": "Microsoft Windows 說明檔成像器", "Id": "okular_chm", "License": "GPL", "MimeTypes": [ "application/x-chm" ], "Name": "CHM Backend", "Name[ca@valencia]": "Dorsal CHM", "Name[ca]": "Dorsal CHM", "Name[cs]": "Podpora CHM", "Name[de]": "Anzeigemodul für CHM", "Name[el]": "Σύστημα υποστήριξης CHM", "Name[es]": "Motor para CHM", "Name[et]": "CHM-i taustaprogramm", "Name[fi]": "CHM-taustaosa", "Name[fr]": "Moteur CHM", + "Name[gl]": "Infraestrutura para CHM", "Name[ia]": "Retro-Administration de CHM", "Name[it]": "Backend CHM", "Name[ko]": "CHM 백엔드", "Name[nl]": "CHM-backend", "Name[nn]": "CHM-motor", "Name[pl]": "Silnik CHM", "Name[pt]": "Infra-Estrutura de CHM", "Name[ru]": "Модуль поддержки формата CHM", "Name[sl]": "Zaledje za CHM", "Name[sr@ijekavian]": "Позадина за ЦХМ", "Name[sr@ijekavianlatin]": "Pozadina za CHM", "Name[sr@latin]": "Pozadina za CHM", "Name[sr]": "Позадина за ЦХМ", "Name[sv]": "CHM-gränssnitt", "Name[tr]": "CHM Arka Ucu", "Name[uk]": "Модуль CHM", "Name[x-test]": "xxCHM Backendxx", "Name[zh_CN]": "CHM 后端", "Name[zh_TW]": "CHM 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.4" }, "X-KDE-Priority": 2, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/chm/org.kde.okular-chm.metainfo.xml b/generators/chm/org.kde.okular-chm.metainfo.xml index ee30f51d3..6a42501eb 100644 --- a/generators/chm/org.kde.okular-chm.metainfo.xml +++ b/generators/chm/org.kde.okular-chm.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-chm org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 CHM Documents Documentos CHM Documents CHM Documents CHM Dokumenty CHM CHM-Dokumente Έγγραφα CHM CHM Documents Documentos CHM CHM-dokumendid CHM-tiedostot Documents CHM + Documentos CHM Documenti CHM CHM 문서 CHM-documenten CHM-dokument Dokumenty CHM Documentos CHM CHM dokumenty Dokumenti CHM ЦХМ документи CHM dokumenti ЦХМ документи CHM dokumenti CHM-dokument CHM Belgeleri документи CHM xxCHM Documentsxx CHM 文档 CHM 文件 Adds support for reading CHM documents Amiesta sofitu pa la llectura de documentos CHM Afegeix la implementació per llegir documents CHM Afig la implementació per llegir documents CHM Přidává podporu pro čtení dokumentů CHM Bietet Unterstützung zum Lesen von CHM-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων CHM Adds support for reading CHM documents Permite la lectura de documentos CHM CHM-dokumentide lugemise toetus Lisää CHM-tiedostojen lukutuen Permet la lecture des documents CHM + Permite ler documentos CHM. Aggiunge il supporto per la lettura di documenti CHM CHM 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van CHM-documenten toe Legg til støtte for å lesa CHM-dokument Dodaje obsługę dokumnetów CHM Adiciona o suporte para a leitura de documentos CHM Pridá podporu pre čítanie CHM dokumentov Doda podporo za branje dokumentov CHM Подршка за читање ЦХМ докумената Podrška za čitanje CHM dokumenata Подршка за читање ЦХМ докумената Podrška za čitanje CHM dokumenata Lägger till stöd för att läsa CHM-dokument CHM belgelerini okuma desteği ekler Додає підтримку читання документів CHM xxAdds support for reading CHM documentsxx 增加对 CHM 文档的阅读支持 加入對讀取 CHM 文件的支援 application/x-chm https://okular.kde.org diff --git a/generators/comicbook/libokularGenerator_comicbook.json b/generators/comicbook/libokularGenerator_comicbook.json index 5d77fa72f..c4b5f74a2 100644 --- a/generators/comicbook/libokularGenerator_comicbook.json +++ b/generators/comicbook/libokularGenerator_comicbook.json @@ -1,103 +1,105 @@ { "KPlugin": { "Authors": [ { "Email": "tokoe@kde.org", "Name": "Tobias Koenig", "Name[nn]": "Tobias König", "Name[ru]": "Tobias König", "Name[sr@ijekavian]": "Тобијас Кениг", "Name[sr@ijekavianlatin]": "Tobijas Kenig", "Name[sr@latin]": "Tobijas Kenig", "Name[sr]": "Тобијас Кениг", "Name[x-test]": "xxTobias Koenigxx" } ], "Copyright": "© 2007-2008 Tobias Koenig", "Copyright[et]": "© 2007-2008: Tobias Koenig", "Copyright[fi]": "© 2007–2008 Tobias Koenig", "Copyright[nn]": "© 2007–2008 Tobias König", "Copyright[ru]": "© Tobias König, 2007-2008", "Copyright[sr@ijekavian]": "© 2007–2008, Тобијас Кениг", "Copyright[sr@ijekavianlatin]": "© 2007–2008, Tobijas Kenig", "Copyright[sr@latin]": "© 2007–2008, Tobijas Kenig", "Copyright[sr]": "© 2007–2008, Тобијас Кениг", "Copyright[uk]": "© Tobias Koenig, 2007–2008", "Copyright[x-test]": "xx© 2007-2008 Tobias Koenigxx", "Description": "A renderer for various comic book formats", "Description[ca@valencia]": "Un renderitzador per a diversos formats de ComicBook", "Description[ca]": "Un renderitzador per a diversos formats de ComicBook", "Description[cs]": "Vykreslovač různých formátů knih komiksů", "Description[de]": "Ein Renderer für verschiedene Comic-Book-Formate", "Description[el]": "Πρόγραμμα αποτύπωσης για διάφορους τύπους comic book", "Description[es]": "Un visor para diversos formatos de libros de cómics", "Description[et]": "Mitmesuguste koomiksivormingute renderdaja", "Description[fi]": "Eri sarjakuvakirjamuotojen hahmonnin", "Description[fr]": "Un système de rendu pour divers formats de livres de BD", + "Description[gl]": "Un visor de varios formatos de banda deseñada", "Description[ia]": "Un rendition pro varie formatos de libros comic", "Description[it]": "Un visualizzatore per vari formati di strisce a fumetti", "Description[ko]": "다양한 만화책 형식 렌더러", "Description[nl]": "Een viewer voor diverse ComicBook-formaten", "Description[nn]": "Ein gjengjevar for ulike teikneserieformat", "Description[pl]": "Wyświetlanie różnych formatów comic book.", "Description[pt]": "Um visualizador de diversos formatos de banda desenhada", "Description[ru]": "Модуль поддержки различных форматов комиксов", "Description[sk]": "Vykresľovanie rôznych formátov kníh komiksov", "Description[sl]": "Izrisovalnik za različne vrste stripov", "Description[sr@ijekavian]": "Рендерер различитих формата стрипова", "Description[sr@ijekavianlatin]": "Renderer različitih formata stripova", "Description[sr@latin]": "Renderer različitih formata stripova", "Description[sr]": "Рендерер различитих формата стрипова", "Description[sv]": "Ett återgivningsprogram för diverse format för serieböcker", "Description[tr]": "Çeşitli çizgi roman biçimleri için bir oluşturucu", "Description[uk]": "Програма для відображення коміксів у різних форматах", "Description[x-test]": "xxA renderer for various comic book formatsxx", "Description[zh_CN]": "多种漫画书格式的渲染器", "Description[zh_TW]": "多種漫畫書的成像器", "Id": "okular_comicbook", "License": "GPL", "MimeTypes": [ "application/x-cbz", "application/x-cbr", "application/x-cbt", "inode/directory" ], "Name": "ComicBook Backend", "Name[ca@valencia]": "Dorsal de ComicBook", "Name[ca]": "Dorsal de ComicBook", "Name[cs]": "Podpůrná vrstva ComicBook", "Name[de]": "Anzeigemodul für Comic-Book", "Name[el]": "Σύστημα υποστήριξης ComicBook", "Name[es]": "Motor para ComicBook", "Name[et]": "ComicBooki taustaprogramm", "Name[fi]": "ComicBook-taustaosa", "Name[fr]": "Moteur pour les livres de BD", + "Name[gl]": "Infraestrutura para ComicBook", "Name[ia]": "Retro-Administration de libros de comic", "Name[it]": "Backend ComicBook", "Name[ko]": "만화책 백엔드", "Name[nl]": "ComicBook-backend", "Name[nn]": "ComicBook-motor", "Name[pl]": "Moduł ComicBook", "Name[pt]": "Infra-Estrutura de ComicBook (BD)", "Name[ru]": "Модуль поддержки формата ComicBook", "Name[sk]": "Backend pre knihy komiksov", "Name[sl]": "Zaledje za stripe", "Name[sr@ijekavian]": "Позадина за комикбук", "Name[sr@ijekavianlatin]": "Pozadina za ComicBook", "Name[sr@latin]": "Pozadina za ComicBook", "Name[sr]": "Позадина за комикбук", "Name[sv]": "Comic Book-gränssnitt", "Name[tr]": "ComicBook Arka Ucu", "Name[uk]": "Модуль коміксів", "Name[x-test]": "xxComicBook Backendxx", "Name[zh_CN]": "漫画书后端", "Name[zh_TW]": "ComicBook 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.4" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/comicbook/org.kde.okular-comicbook.metainfo.xml b/generators/comicbook/org.kde.okular-comicbook.metainfo.xml index 5ebe35983..ddd534a6a 100644 --- a/generators/comicbook/org.kde.okular-comicbook.metainfo.xml +++ b/generators/comicbook/org.kde.okular-comicbook.metainfo.xml @@ -1,73 +1,75 @@ org.kde.okular-comicbook org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Comic Books Cómics Llibres de còmics Llibres de còmics Komiksy Comic-Buch Comic Books Comic Books Libros de cómics Koomiksid Sarjakuvakirjat Bandes dessinées + Libros de banda deseñada Fumetti 만화책 Stripboeken Teikneseriar Komiksy Banda Desenhada Knihy komiksov Stripi Стрипови Stripovi Стрипови Stripovi Comic Book Çizgiromanlar Комікси xxComic Booksxx 漫画书 漫畫書 Adds support for reading comic books Amiesta sofitu pa la llectura de cómics Afegeix la implementació per llegir llibres de còmics Afig la implementació per llegir llibres de còmics Přidává podporu pro čtení komiksů Bietet Unterstützung zum Lesen von Comic-Büchern Προσθέτει υποστήριξη για την ανάγνωση comic books Adds support for reading comic books Permite la lectura de libros de cómics Koomiksite lugemise toetus Lisää sarjakuvakirjojen lukutuen Permet la lecture des bandes dessinées + Permite ler libros de banda deseñada. Aggiunge il supporto per la lettura di fumetti 만화책 읽기 지원 추가 Voegt ondersteuning voor lezen van stripboeken toe Legg til støtte for å lesa teikneseriefiler Dodaje obsługę komiksów Adiciona o suporte para ler livros de banda desenhada Pridá podporu pre čítanie komiksov Doda podporo za branje stripov Подршка за читање стрипова Podrška za čitanje stripova Подршка за читање стрипова Podrška za čitanje stripova Lägger till stöd för att läsa Comic Book Çizgi roman okuma desteği ekler Додає підтримку читання коміксів xxAdds support for reading comic booksxx 增加对漫画书的阅读支持 加入讀取漫畫書的支援 application/x-cbr application/x-cbz application/x-cbt https://okular.kde.org diff --git a/generators/djvu/libokularGenerator_djvu.json b/generators/djvu/libokularGenerator_djvu.json index 1d0ea3319..a8f8d03cc 100644 --- a/generators/djvu/libokularGenerator_djvu.json +++ b/generators/djvu/libokularGenerator_djvu.json @@ -1,98 +1,100 @@ { "KPlugin": { "Authors": [ { "Email": "pino@kde.org", "Name": "Pino Toscano", "Name[sr@ijekavian]": "Пино Тоскано", "Name[sr@ijekavianlatin]": "Pino Toskano", "Name[sr@latin]": "Pino Toskano", "Name[sr]": "Пино Тоскано", "Name[x-test]": "xxPino Toscanoxx" } ], "Copyright": "© 2006-2008 Pino Toscano", "Copyright[et]": "© 2006-2008: Pino Toscano", "Copyright[fi]": "© 2006–2008 Pino Toscano", "Copyright[nn]": "© 2006–2008 Pino Toscano", "Copyright[ru]": "© Pino Toscano, 2006-2008", "Copyright[sr@ijekavian]": "© 2006–2008, Пино Тоскано", "Copyright[sr@ijekavianlatin]": "© 2006–2008, Pino Toskano", "Copyright[sr@latin]": "© 2006–2008, Pino Toskano", "Copyright[sr]": "© 2006–2008, Пино Тоскано", "Copyright[uk]": "© Pino Toscano, 2006–2008", "Copyright[x-test]": "xx© 2006-2008 Pino Toscanoxx", "Description": "DjVu backend based on DjVuLibre", "Description[ca@valencia]": "Dorsal pel DjVu basat en el DjVuLibre", "Description[ca]": "Dorsal pel DjVu basat en el DjVuLibre", "Description[cs]": "Implementace DjVu založena na DjVuLibre", "Description[de]": "Anzeigemodul für DjVu auf der Basis von DjVuLibre", "Description[el]": "Σύστημα υποστήριξης DjVu με βάση το DjVuLibre", "Description[es]": "Motor DjVu basado en DjVuLibre", "Description[et]": "DjVu taustaprogramm DjVuLibre alusel", "Description[fi]": "DjVuLibreen perustuva DjVu-taustaosa", "Description[fr]": "Moteur DjVu utilisant DjVuLibre", + "Description[gl]": "Infraestrutura de DjVu baseada en DjVuLibre.", "Description[ia]": "Retro-Administration de DjVu basate sur DjVuLibre.", "Description[it]": "Backend DjVu basato su DjVuLibre", "Description[ko]": "DjVuLibre 기반 DjVu 백엔드", "Description[nl]": "DjVu-backend gebaseerd op DjVuLibre", "Description[nn]": "DjVu-motor basert på DjVuLibre", "Description[pl]": "Silnik DjVu oparty na DjVuLibre", "Description[pt]": "Infra-estrutura do DjVu com base no DjVuLibre", "Description[ru]": "Модуль поддержки формата DjVu на основе библиотеки DjVuLibre", "Description[sk]": "DjVu backend postavený na DjVuLibre", "Description[sl]": "Zaledje za DjVu, ki temelji na DjVuLibre", "Description[sr@ijekavian]": "Позадина за ДјВу заснована на ДјВу‑либреу", "Description[sr@ijekavianlatin]": "Pozadina za DjVu zasnovana na DjVuLibreu", "Description[sr@latin]": "Pozadina za DjVu zasnovana na DjVuLibreu", "Description[sr]": "Позадина за ДјВу заснована на ДјВу‑либреу", "Description[sv]": "DjVu-gränssnitt baserat på DjVuLibre", "Description[tr]": "DjVuLibre tabanlı DjVu arka ucu", "Description[uk]": "Засіб обробки DjVu, заснований на DjVuLibre", "Description[x-test]": "xxDjVu backend based on DjVuLibrexx", "Description[zh_CN]": "基于 DjVuLibre 的 DjVu 后端", "Description[zh_TW]": "基於 DjVuLibre 的 DjVu 後端介面", "Id": "okular_djvu", "License": "GPL", "MimeTypes": [ "image/vnd.djvu" ], "Name": "DjVu Backend", "Name[ast]": "Backend DjVu", "Name[ca@valencia]": "Dorsal DjVu", "Name[ca]": "Dorsal DjVu", "Name[cs]": "Podpůrná vrstva DjVu", "Name[de]": "Anzeigemodul für DjVu", "Name[el]": "Σύστημα υποστήριξης djvu", "Name[es]": "Motor para DjVu", "Name[et]": "DjVu taustaprogramm", "Name[fi]": "DjVu-taustaosa", "Name[fr]": "Moteur DjVu", + "Name[gl]": "Infraestrutura para DjVu", "Name[ia]": "Retro-Administration de DjVu", "Name[it]": "Backend DjVu", "Name[ko]": "DjVu 백엔드", "Name[nl]": "DjVu-backend", "Name[nn]": "DjVu-motor", "Name[pl]": "Silnik DjVu", "Name[pt]": "Infra-Estrutura do DjVu", "Name[ru]": "Модуль поддержки формата DjVu", "Name[sl]": "Zaledje za DjVu", "Name[sr@ijekavian]": "Позадина за ДјВу", "Name[sr@ijekavianlatin]": "Pozadina za DjVu", "Name[sr@latin]": "Pozadina za DjVu", "Name[sr]": "Позадина за ДјВу", "Name[sv]": "DjVu-gränssnitt", "Name[tr]": "DjVu Arka Ucu", "Name[uk]": "Модуль DjVu", "Name[x-test]": "xxDjVu Backendxx", "Name[zh_CN]": "DjVu 后端", "Name[zh_TW]": "DjVu 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.2.3" }, "X-KDE-Priority": 2, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/djvu/org.kde.okular-djvu.metainfo.xml b/generators/djvu/org.kde.okular-djvu.metainfo.xml index a0b556738..32c0963a4 100644 --- a/generators/djvu/org.kde.okular-djvu.metainfo.xml +++ b/generators/djvu/org.kde.okular-djvu.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-djvu org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 DjVu Documents Documentos DjVU Documents DjVu Documents DjVu Dokumenty DjVu DjVu-Dokumente Έγγραφα DjVu DjVu Documents Documentos DjVu DjVu dokumendid DjVu-tiedostot Documents DjVu + Documentos DjVu Documenti DjVu DjVu 문서 DjVu-documenten DjVu-dokument Dokumenty DjVu Documentos DjVu DjVu dokumenty Dokumenti DjVu ДјВу документи DjVu dokumenti ДјВу документи DjVu dokumenti DjVu-dokument DvVu Belgeleri документи DjVu xxDjVu Documentsxx DjVu 文档 DjVu 文件 Adds support for reading DjVu documents Amiesta sofitu pa la llectura de documentos DjVU Afegeix la implementació per llegir documents DjVu Afig la implementació per llegir documents DjVu Přidává podporu pro čtení dokumentů DjVu Bietet Unterstützung zum Lesen von DjVu-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων DjVu Adds support for reading DjVu documents Permite la lectura de documentos DjVu DjVu dokumentide lugemise toetus Lisää DjVu-tiedostojen lukutuen Permet la lecture des documents DjVu + Permite ler documentos de DjVu. Aggiunge il supporto per la lettura di documenti DjVu DjVu 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van DjVu-documenten toe Legg til støtte for å lesa DjVu-dokument Dodaje obsługę czytania dokumentów DjVu Adiciona o suporte para a leitura de documentos DjVu Pridá podporu pre čítanie DjVu dokumentov Doda podporo za branje dokumentov DjVu Подршка за читање ДјВу докумената Podrška za čitanje DjVu dokumenata Подршка за читање ДјВу докумената Podrška za čitanje DjVu dokumenata Lägger till stöd för att läsa DjVu-dokument DjVu belgelerini okuma desteği ekler Додає підтримку читання документів DjVu xxAdds support for reading DjVu documentsxx 增加对 DjVu 文档的阅读支持 加入讀取 DjVu 文件的支援 image/vnd.djvu https://okular.kde.org diff --git a/generators/dvi/dviRenderer_draw.cpp b/generators/dvi/dviRenderer_draw.cpp index f83b416b9..85ac92931 100644 --- a/generators/dvi/dviRenderer_draw.cpp +++ b/generators/dvi/dviRenderer_draw.cpp @@ -1,713 +1,717 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- /* * Copyright (c) 1994 Paul Vojta. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * NOTE: * xdvi is based on prior work as noted in the modification history, below. */ /* * DVI previewer for X. * * Eric Cooper, CMU, September 1985. * * Code derived from dvi-imagen.c. * * Modification history: * 1/1986 Modified for X.10 --Bob Scheifler, MIT LCS. * 7/1988 Modified for X.11 --Mark Eichin, MIT * 12/1988 Added 'R' option, toolkit, magnifying glass * --Paul Vojta, UC Berkeley. * 2/1989 Added tpic support --Jeffrey Lee, U of Toronto * 4/1989 Modified for System V --Donald Richardson, Clarkson Univ. * 3/1990 Added VMS support --Scott Allendorf, U of Iowa * 7/1990 Added reflection mode --Michael Pak, Hebrew U of Jerusalem * 1/1992 Added greyscale code --Till Brychcy, Techn. Univ. Muenchen * and Lee Hetherington, MIT * 4/1994 Added DPS support, bounding box * --Ricardo Telichevesky * and Luis Miguel Silveira, MIT RLE. */ //#define DEBUG_RENDER 0 #include #include "debug_dvi.h" #include "dviRenderer.h" #include "dvi.h" #include "dviFile.h" #include "hyperlink.h" #include "debug_dvi.h" #include "psgs.h" //#include "renderedDviPagePixmap.h" #include "TeXFont.h" #include "textBox.h" #include "xdvi.h" #include #include #include /** Routine to print characters. */ void dviRenderer::set_char(unsigned int cmd, unsigned int ch) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "set_char #" << ch; #endif glyph *g; if (colorStack.isEmpty()) g = ((TeXFont *)(currinf.fontp->font))->getGlyph(ch, true, globalColor); else g = ((TeXFont *)(currinf.fontp->font))->getGlyph(ch, true, colorStack.top()); if (g == NULL) return; long dvi_h_sav = currinf.data.dvi_h; QImage pix = g->shrunkenCharacter; int x = ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))) - g->x2; int y = currinf.data.pxl_v - g->y2; // Draw the character. foreGroundPainter->drawImage(x, y, pix); // Are we drawing text for a hyperlink? And are hyperlinks // enabled? if (HTML_href != NULL) { // Now set up a rectangle which is checked against every mouse // event. if (line_boundary_encountered == true) { // Set up hyperlink Hyperlink dhl; dhl.baseline = currinf.data.pxl_v; dhl.box.setRect(x, y, pix.width(), pix.height()); dhl.linkText = *HTML_href; currentlyDrawnPage->hyperLinkList.push_back(dhl); } else { QRect dshunion = currentlyDrawnPage->hyperLinkList[currentlyDrawnPage->hyperLinkList.size()-1].box.united(QRect(x, y, pix.width(), pix.height())) ; currentlyDrawnPage->hyperLinkList[currentlyDrawnPage->hyperLinkList.size()-1].box = dshunion; } } // Are we drawing text for a source hyperlink? And are source // hyperlinks enabled? // If we are printing source hyperlinks are irrelevant, otherwise we // actually got a pointer to a RenderedDviPagePixmap. RenderedDviPagePixmap* currentDVIPage = dynamic_cast(currentlyDrawnPage); if (source_href != 0 && currentDVIPage) { // Now set up a rectangle which is checked against every mouse // event. if (line_boundary_encountered == true) { // Set up source hyperlinks Hyperlink dhl; dhl.baseline = currinf.data.pxl_v; dhl.box.setRect(x, y, pix.width(), pix.height()); if (source_href != NULL) dhl.linkText = *source_href; else dhl.linkText = QLatin1String(""); currentDVIPage->sourceHyperLinkList.push_back(dhl); } else { QRect dshunion = currentDVIPage->sourceHyperLinkList[currentDVIPage->sourceHyperLinkList.size()-1].box.united(QRect(x, y, pix.width(), pix.height())) ; currentDVIPage->sourceHyperLinkList[currentDVIPage->sourceHyperLinkList.size()-1].box = dshunion; } } // Code for DVI -> text functions (e.g. marking of text, full text // search, etc.). Set up the currentlyDrawnPage->textBoxList. TextBox link; link.box.setRect(x, y, pix.width(), pix.height()); link.text = QLatin1String(""); currentlyDrawnPage->textBoxList.push_back(link); switch(ch) { case 0x0b: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("ff"); break; case 0x0c: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("fi"); break; case 0x0d: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("fl"); break; case 0x0e: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("ffi"); break; case 0x0f: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("ffl"); break; case 0x7b: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('-'); break; case 0x7c: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("---"); break; case 0x7d: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("\""); break; case 0x7e: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('~'); break; case 0x7f: currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1String("@@"); // @@@ check! break; default: if ((ch >= 0x21) && (ch <= 0x7a)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QChar(ch); else currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('?'); break; } if (cmd == PUT1) currinf.data.dvi_h = dvi_h_sav; else currinf.data.dvi_h += (int)(currinf.fontp->scaled_size_in_DVI_units * dviFile->getCmPerDVIunit() * (1200.0 / 2.54)/16.0 * g->dvi_advance_in_units_of_design_size_by_2e20 + 0.5); word_boundary_encountered = false; line_boundary_encountered = false; } void dviRenderer::set_empty_char(unsigned int, unsigned int) { return; } void dviRenderer::set_vf_char(unsigned int cmd, unsigned int ch) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "dviRenderer::set_vf_char( cmd=" << cmd << ", ch=" << ch << " )"; #endif static unsigned char c; macro *m = &currinf.fontp->macrotable[ch]; if (m->pos == NULL) { qCCritical(OkularDviDebug) << "Character " << ch << " not defined in font " << currinf.fontp->fontname << endl; m->pos = m->end = &c; return; } long dvi_h_sav = currinf.data.dvi_h; struct drawinf oldinfo = currinf; currinf.data.w = 0; currinf.data.x = 0; currinf.data.y = 0; currinf.data.z = 0; currinf.fonttable = &(currinf.fontp->vf_table); currinf._virtual = currinf.fontp; quint8 *command_ptr_sav = command_pointer; quint8 *end_ptr_sav = end_pointer; command_pointer = m->pos; end_pointer = m->end; draw_part(currinf.fontp->scaled_size_in_DVI_units*(dviFile->getCmPerDVIunit() * 1200.0 / 2.54)/16.0, true); command_pointer = command_ptr_sav; end_pointer = end_ptr_sav; currinf = oldinfo; if (cmd == PUT1) currinf.data.dvi_h = dvi_h_sav; else currinf.data.dvi_h += (int)(currinf.fontp->scaled_size_in_DVI_units * dviFile->getCmPerDVIunit() * (1200.0 / 2.54)/16.0 * m->dvi_advance_in_units_of_design_size_by_2e20 + 0.5); } void dviRenderer::set_no_char(unsigned int cmd, unsigned int ch) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "dviRenderer::set_no_char( cmd=" << cmd << ", ch =" << ch << " )" ; #endif if (currinf._virtual) { currinf.fontp = currinf._virtual->first_font; if (currinf.fontp != NULL) { currinf.set_char_p = currinf.fontp->set_char_p; (this->*currinf.set_char_p)(cmd, ch); return; } } errorMsg = i18n("The DVI code set a character of an unknown font."); return; } void dviRenderer::draw_part(double current_dimconv, bool is_vfmacro) { #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) << "draw_part"; #endif qint32 RRtmp=0, WWtmp=0, XXtmp=0, YYtmp=0, ZZtmp=0; quint8 ch; currinf.fontp = NULL; currinf.set_char_p = &dviRenderer::set_no_char; int last_space_index = 0; bool space_encountered = false; bool after_space = false; for (;;) { space_encountered = false; ch = readUINT8(); if (ch <= (unsigned char) (SETCHAR0 + 127)) { (this->*currinf.set_char_p)(ch, ch); } else if (FNTNUM0 <= ch && ch <= (unsigned char) (FNTNUM0 + 63)) { currinf.fontp = currinf.fonttable->value(ch - FNTNUM0); if (currinf.fontp == NULL) { errorMsg = i18n("The DVI code referred to font #%1, which was not previously defined.", ch - FNTNUM0); return; } currinf.set_char_p = currinf.fontp->set_char_p; } else { qint32 a, b; switch (ch) { case SET1: case PUT1: (this->*currinf.set_char_p)(ch, readUINT8()); break; case SETRULE: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; } /* Be careful, dvicopy outputs rules with height = 0x80000000. We don't want any SIGFPE here. */ a = readUINT32(); b = readUINT32(); b = ((long) (b * current_dimconv)); if (a > 0 && b > 0) { int h = ((int) ROUNDUP(((long) (a * current_dimconv)), shrinkfactor * 65536)); int w = ((int) ROUNDUP(b, shrinkfactor * 65536)); if (colorStack.isEmpty()) foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, globalColor ); else foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, colorStack.top() ); } currinf.data.dvi_h += b; break; case PUTRULE: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; } a = readUINT32(); b = readUINT32(); a = ((long) (a * current_dimconv)); b = ((long) (b * current_dimconv)); if (a > 0 && b > 0) { int h = ((int) ROUNDUP(a, shrinkfactor * 65536)); int w = ((int) ROUNDUP(b, shrinkfactor * 65536)); if (colorStack.isEmpty()) foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, globalColor ); else foreGroundPainter->fillRect( ((int) ((currinf.data.dvi_h) / (shrinkfactor * 65536))), currinf.data.pxl_v - h + 1, w?w:1, h?h:1, colorStack.top() ); } break; case NOP: break; case BOP: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; } command_pointer += 11 * 4; currinf.data.dvi_h = 1200 << 16; // Reminder: DVI-coordinates start at (1",1") from top of page currinf.data.dvi_v = 1200; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); currinf.data.w = currinf.data.x = currinf.data.y = currinf.data.z = 0; break; case EOP: // Check if we are just at the end of a virtual font macro. if (is_vfmacro == false) { // This is really the end of a page, and not just the end // of a macro. Mark the end of the current word. word_boundary_encountered = true; line_boundary_encountered = true; // Sanity check for the dvi-file: The DVI-standard asserts // that at the end of a page, the stack should always be // empty. if (!stack.isEmpty()) { qCDebug(OkularDviDebug) << "DRAW: The stack was not empty when the EOP command was encountered."; errorMsg = i18n("The stack was not empty when the EOP command was encountered."); return; } } return; case PUSH: stack.push(currinf.data); break; case POP: if (stack.isEmpty()) { errorMsg = i18n("The stack was empty when a POP command was encountered."); return; } else currinf.data = stack.pop(); word_boundary_encountered = true; line_boundary_encountered = true; break; case RIGHT1: case RIGHT2: case RIGHT3: case RIGHT4: RRtmp = readINT(ch - RIGHT1 + 1); // A horizontal motion in the range 4 * font space [f] < h < // font space [f] will be treated as a kern that is not // indicated in the printouts that DVItype produces between // brackets. We allow a larger space in the negative // direction than in the positive one, because TEX makes // comparatively large backspaces when it positions // accents. (comments stolen from the source of dvitype) if ((is_vfmacro == false) && (currinf.fontp != 0) && ((RRtmp >= currinf.fontp->scaled_size_in_DVI_units/6) || (RRtmp <= -4*(currinf.fontp->scaled_size_in_DVI_units/6))) && (currentlyDrawnPage->textBoxList.size() > 0)) { //currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += ' '; space_encountered = true; } currinf.data.dvi_h += ((long) (RRtmp * current_dimconv)); break; case W1: case W2: case W3: case W4: WWtmp = readINT(ch - W0); currinf.data.w = ((long) (WWtmp * current_dimconv)); + // fallthrough case W0: if ((is_vfmacro == false) && (currinf.fontp != 0) && ((WWtmp >= currinf.fontp->scaled_size_in_DVI_units/6) || (WWtmp <= -4*(currinf.fontp->scaled_size_in_DVI_units/6))) && (currentlyDrawnPage->textBoxList.size() > 0) ) { //currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += ' '; space_encountered = true; } currinf.data.dvi_h += currinf.data.w; break; case X1: case X2: case X3: case X4: XXtmp = readINT(ch - X0); currinf.data.x = ((long) (XXtmp * current_dimconv)); + // fallthrough case X0: if ((is_vfmacro == false) && (currinf.fontp != 0) && ((XXtmp >= currinf.fontp->scaled_size_in_DVI_units/6) || (XXtmp <= -4*(currinf.fontp->scaled_size_in_DVI_units/6))) && (currentlyDrawnPage->textBoxList.size() > 0)) { //currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += ' '; space_encountered = true; } currinf.data.dvi_h += currinf.data.x; break; case DOWN1: case DOWN2: case DOWN3: case DOWN4: { qint32 DDtmp = readINT(ch - DOWN1 + 1); if ((is_vfmacro == false) && (currinf.fontp != 0) && (abs(DDtmp) >= 5*(currinf.fontp->scaled_size_in_DVI_units/6)) && (currentlyDrawnPage->textBoxList.size() > 0)) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; if (abs(DDtmp) >= 10*(currinf.fontp->scaled_size_in_DVI_units/6)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('\n'); } currinf.data.dvi_v += ((long) (DDtmp * current_dimconv))/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); } break; case Y1: case Y2: case Y3: case Y4: YYtmp = readINT(ch - Y0); currinf.data.y = ((long) (YYtmp * current_dimconv)); + // fallthrough case Y0: if ((is_vfmacro == false) && (currinf.fontp != 0) && (abs(YYtmp) >= 5*(currinf.fontp->scaled_size_in_DVI_units/6)) && (currentlyDrawnPage->textBoxList.size() > 0)) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; if (abs(YYtmp) >= 10*(currinf.fontp->scaled_size_in_DVI_units/6)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('\n'); } currinf.data.dvi_v += currinf.data.y/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); break; case Z1: case Z2: case Z3: case Z4: ZZtmp = readINT(ch - Z0); currinf.data.z = ((long) (ZZtmp * current_dimconv)); + // fallthrough case Z0: if ((is_vfmacro == false) && (currinf.fontp != 0) && (abs(ZZtmp) >= 5*(currinf.fontp->scaled_size_in_DVI_units/6)) && (currentlyDrawnPage->textBoxList.size() > 0)) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; if (abs(ZZtmp) >= 10*(currinf.fontp->scaled_size_in_DVI_units/6)) currentlyDrawnPage->textBoxList[currentlyDrawnPage->textBoxList.size()-1].text += QLatin1Char('\n'); } currinf.data.dvi_v += currinf.data.z/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); break; case FNT1: case FNT2: case FNT3: currinf.fontp = currinf.fonttable->value(readUINT(ch - FNT1 + 1)); if (currinf.fontp == NULL) { errorMsg = i18n("The DVI code referred to a font which was not previously defined."); return; } currinf.set_char_p = currinf.fontp->set_char_p; break; case FNT4: currinf.fontp = currinf.fonttable->value(readINT(ch - FNT1 + 1)); if (currinf.fontp == NULL) { errorMsg = i18n("The DVI code referred to a font which was not previously defined."); return; } currinf.set_char_p = currinf.fontp->set_char_p; break; case XXX1: case XXX2: case XXX3: case XXX4: if (is_vfmacro == false) { word_boundary_encountered = true; line_boundary_encountered = true; space_encountered = true; } a = readUINT(ch - XXX1 + 1); if (a > 0) { char *cmd = new char[a+1]; strncpy(cmd, (char *)command_pointer, a); command_pointer += a; cmd[a] = '\0'; applicationDoSpecial(cmd); delete [] cmd; } break; case FNTDEF1: case FNTDEF2: case FNTDEF3: case FNTDEF4: command_pointer += 12 + ch - FNTDEF1 + 1; { quint8 tempa = readUINT8(); quint8 tempb = readUINT8(); command_pointer += tempa + tempb; } break; case PRE: case POST: case POSTPOST: errorMsg = i18n("An illegal command was encountered."); return; break; default: errorMsg = i18n("The unknown op-code %1 was encountered.", ch); return; } /* end switch*/ } /* end else (ch not a SETCHAR or FNTNUM) */ #ifdef DEBUG_RENDER if (currentlyDrawnPage->textBoxList.size() > 0) qCDebug(OkularDviDebug) << "Element:" << currentlyDrawnPage->textBoxList.last().box << currentlyDrawnPage->textBoxList.last().text << " ? s:" << space_encountered << " / nl:" << line_boundary_encountered << " / w:" << word_boundary_encountered << ", " << last_space_index << "/" << currentlyDrawnPage->textBoxList.size(); #endif /* heuristic to properly detect newlines; a space is needed */ if (after_space && line_boundary_encountered && word_boundary_encountered) { if (currentlyDrawnPage->textBoxList.last().text.endsWith(QLatin1Char('\n'))) currentlyDrawnPage->textBoxList.last().text.chop(1); currentlyDrawnPage->textBoxList.last().text += QLatin1String(" \n"); after_space = false; } /* a "space" has been found and there is some (new) character in the array */ if (space_encountered && (currentlyDrawnPage->textBoxList.size() > last_space_index)) { for (int lidx = last_space_index+1; lidxtextBoxList.size(); ++lidx) { // merge two adjacent boxes which are part of the same word currentlyDrawnPage->textBoxList[lidx-1].box.setRight(currentlyDrawnPage->textBoxList[lidx].box.x()); } #ifdef DEBUG_RENDER QString lastword(currentlyDrawnPage->textBoxList[last_space_index].text); for (int lidx = last_space_index+1; lidxtextBoxList.size(); ++lidx) lastword += currentlyDrawnPage->textBoxList[lidx].text; qCDebug(OkularDviDebug) << "space encountered: '" << lastword << "'"; #endif last_space_index = currentlyDrawnPage->textBoxList.size(); after_space = true; } } /* end for */ } void dviRenderer::draw_page() { // Reset a couple of variables HTML_href = 0; source_href = 0; penWidth_in_mInch = 0.0; // Calling resize() here rather than clear() means that the memory // taken up by the vector is not freed. This is faster than // constantly allocating/freeing memory. currentlyDrawnPage->textBoxList.resize(0); RenderedDviPagePixmap* currentDVIPage = dynamic_cast(currentlyDrawnPage); if (currentDVIPage) { currentDVIPage->sourceHyperLinkList.resize(0); } #ifdef PERFORMANCE_MEASUREMENT // If this is the first time a page is drawn, take the time that is // elapsed till the kdvi_multipage was constructed, and print // it. Set the flag so that is message will not be printed again. if (performanceFlag == 0) { qCDebug(OkularDviDebug) << "Time elapsed till the first page is drawn: " << performanceTimer.restart() << "ms"; performanceFlag = 1; } #endif #ifdef DEBUG_RENDER qCDebug(OkularDviDebug) <<"draw_page"; #endif #if 0 if (!accessibilityBackground) { #endif foreGroundPainter->fillRect( foreGroundPainter->viewport(), PS_interface->getBackgroundColor(current_page) ); #if 0 } else { // In accessiblity mode use the custom background color foreGroundPainter->fillRect( foreGroundPainter->viewport(), accessibilityBackgroundColor ); } #endif // Render the PostScript background, if there is one. if (_postscript) { #if 0 // In accessiblity mode use the custom background color if (accessibilityBackground) { // Flag permanent is set to false because otherwise we would not be able to restore // the original background color. PS_interface->setBackgroundColor(current_page, accessibilityBackgroundColor, false); } else #endif PS_interface->restoreBackgroundColor(current_page); PS_interface->graphics(current_page, resolutionInDPI, dviFile->getMagnification(), foreGroundPainter); } // Now really write the text if (dviFile->page_offset.isEmpty() == true) return; if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = 0; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = 0; double fontPixelPerDVIunit = dviFile->getCmPerDVIunit() * 1200.0/2.54; draw_part(65536.0*fontPixelPerDVIunit, false); if (HTML_href != 0) { delete HTML_href; HTML_href = 0; } if (source_href != 0) { delete source_href; source_href = 0; } } diff --git a/generators/dvi/dviRenderer_prescan.cpp b/generators/dvi/dviRenderer_prescan.cpp index 688f317cc..d240aa997 100644 --- a/generators/dvi/dviRenderer_prescan.cpp +++ b/generators/dvi/dviRenderer_prescan.cpp @@ -1,803 +1,807 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // dviRenderer_prescan.cpp // // Part of KDVI - A DVI previewer for the KDE desktop environment // // (C) 2003--2004 Stefan Kebekus // Distributed under the GPL #include #include "dviRenderer.h" #include "dvi.h" #include "dviFile.h" #include "debug_dvi.h" #include "prebookmark.h" #include "psgs.h" #include "TeXFont.h" #include #include #include #include #include #include #include #include #include #include #include #include extern QPainter foreGroundPaint; extern void parse_special_argument(const QString& strg, const char* argument_name, int* variable); //#define DEBUG_PRESCAN void dviRenderer::prescan_embedPS(char *cp, quint8 *beginningOfSpecialCommand) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "dviRenderer::prescan_embedPS( cp = " << cp << " ) "; #endif // Encapsulated Postscript File if (qstrnicmp(cp, "PSfile=", 7) != 0) return; QString command = QString::fromLocal8Bit(cp+7); QString include_command = command.simplified(); // The line is supposed to start with "..ile=", and then comes the // filename. Figure out what the filename is and stow it away. Of // course, this does not work if the filename contains spaces // (already the simplified() above is wrong). If you have // files like this, go away. QString EPSfilename = include_command; EPSfilename.truncate(EPSfilename.indexOf(QLatin1Char(' '))); // Strip enclosing quotation marks which are included by some LaTeX // macro packages (but not by others). This probably means that // graphic files are no longer found if the filename really does // contain quotes, but we don't really care that much. if ((EPSfilename.at(0) == QLatin1Char('"')) && (EPSfilename.at(EPSfilename.length()-1) == QLatin1Char('"'))) EPSfilename = EPSfilename.mid(1,EPSfilename.length()-2); // Now locate the Gfx file on the hard disk... EPSfilename = ghostscript_interface::locateEPSfile(EPSfilename, baseURL); // If the file is neither a PostScript not a PDF file, we exit here. // The graphic file is later read when the page is rendered. QMimeDatabase db; QMimeType const mime_type = db.mimeTypeForFile(EPSfilename, QMimeDatabase::MatchContent); QString const & mime_type_name = mime_type.isValid() ? mime_type.name() : QString(); bool const is_ps_file = (mime_type_name == QLatin1String("application/postscript") || mime_type_name == QLatin1String("image/x-eps")); bool const is_pdf_file = (!is_ps_file && mime_type_name == QLatin1String("application/pdf")); if (!(is_ps_file || is_pdf_file)) return; QString originalFName = EPSfilename; embedPS_progress->setLabelText(i18n("Embedding %1", EPSfilename)); qApp->processEvents(); // If the EPSfilename really points to a PDF file, convert that file now. if (is_pdf_file) EPSfilename = dviFile->convertPDFtoPS(EPSfilename); if (!QFile::exists(EPSfilename)) { // Find the number of the page quint32 currentOffset = beginningOfSpecialCommand - dviFile->dvi_Data(); int page=0; for(; page < dviFile->total_pages; page++) if ((dviFile->page_offset[page] <= currentOffset) && (currentOffset <= dviFile->page_offset[page+1])) break; if (is_pdf_file) errorMsg += i18n("Page %1: The PDF file %2 could not be converted to PostScript.
", page+1, originalFName); else errorMsg += i18n("Page %1: The PostScript file %2 could not be found.
", page+1, originalFName); embedPS_progress->setValue(embedPS_progress->value()+1); qApp->processEvents(); return; } // Now parse the arguments. int llx = 0; int lly = 0; int urx = 0; int ury = 0; int rwi = 0; int rhi = 0; int angle = 0; // just to avoid ambiguities; the filename could contain keywords include_command = include_command.mid(include_command.indexOf(QLatin1Char(' '))); parse_special_argument(include_command, "llx=", &llx); parse_special_argument(include_command, "lly=", &lly); parse_special_argument(include_command, "urx=", &urx); parse_special_argument(include_command, "ury=", &ury); parse_special_argument(include_command, "rwi=", &rwi); parse_special_argument(include_command, "rhi=", &rhi); parse_special_argument(include_command, "angle=", &angle); int clip=include_command.indexOf(QStringLiteral(" clip")); // -1 if clip keyword is not present, >= 0 otherwise // Generate the PostScript commands to be included QString PS = QStringLiteral("ps: @beginspecial %1 @llx %2 @lly %3 @urx %4 @ury").arg(llx).arg(lly).arg(urx).arg(ury); if (rwi != 0) PS.append( QStringLiteral(" %1 @rwi").arg(rwi) ); if (rhi != 0) PS.append( QStringLiteral(" %1 @rhi").arg(rhi) ); if (angle != 0) PS.append( QStringLiteral(" %1 @angle").arg(angle) ); if (clip != -1) PS.append( QStringLiteral( " @clip" )); PS.append( QStringLiteral( " @setspecial\n" ) ); QFile file( EPSfilename ); if ( file.open( QIODevice::ReadOnly ) ) { QTextStream stream( &file ); while ( !stream.atEnd() ) { PS += stream.readLine().section( QLatin1Char('%'), 0, 0); PS += QLatin1Char('\n'); } file.close(); } PS.append( QStringLiteral("@endspecial") ); PS = PS.simplified(); _isModified = true; quint32 lengthOfOldSpecial = command_pointer - beginningOfSpecialCommand; quint32 lengthOfNewSpecial = PS.length()+5; QVector newDVI(dviFile->size_of_file + lengthOfNewSpecial-lengthOfOldSpecial); quint8 *commandPtrSav = command_pointer; quint8 *endPtrSav = end_pointer; end_pointer = newDVI.data() + dviFile->size_of_file + lengthOfNewSpecial-lengthOfOldSpecial; memcpy(newDVI.data(), dviFile->dvi_Data(), beginningOfSpecialCommand-dviFile->dvi_Data()); command_pointer = newDVI.data()+(beginningOfSpecialCommand-dviFile->dvi_Data()); command_pointer[0] = XXX4; command_pointer++; writeUINT32(PS.length()); memcpy(newDVI.data()+(beginningOfSpecialCommand-dviFile->dvi_Data())+5, PS.toLatin1().constData(), PS.length() ); memcpy(newDVI.data()+(beginningOfSpecialCommand-dviFile->dvi_Data())+lengthOfNewSpecial, beginningOfSpecialCommand+lengthOfOldSpecial, dviFile->size_of_file-(beginningOfSpecialCommand-dviFile->dvi_Data())-lengthOfOldSpecial ); // Adjust page pointers in the DVI file dviFile->size_of_file = dviFile->size_of_file + lengthOfNewSpecial-lengthOfOldSpecial; end_pointer = newDVI.data() + dviFile->size_of_file; quint32 currentOffset = beginningOfSpecialCommand-dviFile->dvi_Data(); for(int i=0; i < dviFile->total_pages; i++) { if (dviFile->page_offset[i] > currentOffset) { dviFile->page_offset[i] = dviFile->page_offset[i] + lengthOfNewSpecial-lengthOfOldSpecial; command_pointer = dviFile->page_offset[i] + newDVI.data() + 4*10 + 1; quint32 a = readUINT32(); if (a > currentOffset) { a = a + lengthOfNewSpecial-lengthOfOldSpecial; command_pointer = dviFile->page_offset[i] + newDVI.data() + 4*10 + 1; writeUINT32(a); } } } dviFile->beginning_of_postamble = dviFile->beginning_of_postamble + lengthOfNewSpecial - lengthOfOldSpecial; dviFile->page_offset[int(dviFile->total_pages)] = dviFile->beginning_of_postamble; command_pointer = newDVI.data() + dviFile->beginning_of_postamble + 1; quint32 a = readUINT32(); if (a > currentOffset) { a = a + lengthOfNewSpecial - lengthOfOldSpecial; command_pointer = newDVI.data() + dviFile->beginning_of_postamble + 1; writeUINT32(a); } command_pointer = newDVI.data() + dviFile->size_of_file - 1; while((*command_pointer == TRAILER) && (command_pointer > newDVI.data())) command_pointer--; command_pointer -= 4; writeUINT32(dviFile->beginning_of_postamble); command_pointer -= 4; command_pointer = commandPtrSav; end_pointer = endPtrSav; // Modify all pointers to point to the newly allocated memory command_pointer = newDVI.data() + (command_pointer - dviFile->dvi_Data()) + lengthOfNewSpecial-lengthOfOldSpecial; end_pointer = newDVI.data() + (end_pointer - dviFile->dvi_Data()) + lengthOfNewSpecial-lengthOfOldSpecial; dviFile->setNewData(newDVI); embedPS_progress->setValue(embedPS_progress->value()+1); qApp->processEvents(); return; } void dviRenderer::prescan_removePageSizeInfo(char *cp, quint8 *beginningOfSpecialCommand) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "dviRenderer::prescan_embedPS( cp = " << cp << " ) "; #endif // Encapsulated Postscript File if (qstrnicmp(cp, "papersize=", 10) != 0) return; for (quint8 *ptr=beginningOfSpecialCommand; ptrtotal_pages; page++) PS_interface->setBackgroundColor(page, col); return; } void dviRenderer::prescan_ParseHTMLAnchorSpecial(const QString& _cp) { QString cp = _cp; cp.truncate(cp.indexOf(QLatin1Char('"'))); Length l; l.setLength_in_inch(currinf.data.dvi_v/(resolutionInDPI*shrinkfactor)); anchorList[cp] = Anchor(current_page+1, l); } void dviRenderer::prescan_ParsePSHeaderSpecial(const QString& cp) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "PostScript-special, header " << cp; #endif QString _file = cp; // If the file is not found in the current directory, use kpsewhich // to find it. if (!QFile::exists(_file)) { // Otherwise, use kpsewhich to find the eps file. KProcess proc; proc << QStringLiteral("kpsewhich") << cp; proc.setOutputChannelMode(KProcess::SeparateChannels); proc.execute(); _file = QString::fromLocal8Bit(proc.readLine().trimmed()); } if (QFile::exists(_file)) PS_interface->PostScriptHeaderString->append( QStringLiteral(" (%1) run\n").arg(_file) ); } void dviRenderer::prescan_ParsePSBangSpecial(const QString& cp) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "PostScript-special, literal header " << cp; #endif PS_interface->PostScriptHeaderString->append( QStringLiteral( " @defspecial \n" ) ); PS_interface->PostScriptHeaderString->append( cp ); PS_interface->PostScriptHeaderString->append( QStringLiteral ( " @fedspecial \n" ) ); } void dviRenderer::prescan_ParsePSQuoteSpecial(const QString& cp) { #ifdef DEBUG_PRESCAN qCCritical(OkularDviDebug) << "PostScript-special, literal PostScript " << cp; #endif double PS_H = (currinf.data.dvi_h*300.0)/(65536*1200)-300; double PS_V = (currinf.data.dvi_v*300.0)/1200 - 300; PostScriptOutPutString->append( QStringLiteral(" %1 %2 moveto\n").arg(PS_H).arg(PS_V) ); PostScriptOutPutString->append( QStringLiteral(" @beginspecial @setspecial \n") ); PostScriptOutPutString->append( cp ); PostScriptOutPutString->append( QStringLiteral(" @endspecial \n") ); } void dviRenderer::prescan_ParsePSSpecial(const QString& cp) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "PostScript-special, direct PostScript " << cp; #endif // Unfortunately, in some TeX distribution the hyperref package uses // the dvips driver by default, rather than the hypertex driver. As // a result, the DVI files produced are full of PostScript that // specifies links and anchors, and KDVI would call the ghostscript // interpreter for every page which makes it really slow. This is a // major nuisance, so that we try to filter and interpret the // hypertex generated PostScript here. if (cp.startsWith(QLatin1String("ps:SDict begin"))) { // We suspect this may be hyperref generated nonsense. Let's check // for some known code that hyperref generates. if (cp == QLatin1String("ps:SDict begin H.S end")) return; // start of hyperref rectangle if (cp == QLatin1String("ps:SDict begin H.R end")) return; // end of hyperref rectangle if (cp.endsWith(QLatin1String("H.A end"))) return; // end of hyperref anchor if (cp.endsWith(QLatin1String("H.L end"))) return; // end of hyperref link if (cp.startsWith(QLatin1String("ps:SDict begin /product where{pop product(Distiller)"))) return; // hyperref tries to work around Distiller bug if (cp.startsWith(QLatin1String("ps:SDict begin [")) && cp.endsWith(QLatin1String(" pdfmark end"))) { // hyperref definition of link/anchor/bookmark/etc if (cp.contains(QStringLiteral("/DEST"))) { // The PostScript code defines an anchor QString anchorName = cp.section(QLatin1Char('('), 1, 1).section(QLatin1Char(')'), 0, 0); Length l; l.setLength_in_inch(currinf.data.dvi_v/(resolutionInDPI*shrinkfactor)); anchorList[anchorName] = Anchor(current_page+1, l); } // The PostScript code defines a bookmark if (cp.contains(QStringLiteral("/Dest")) && cp.contains(QStringLiteral("/Title"))) { const QString childrenNumberAndMoreStuff = cp.section(QLatin1Char('-'), 1, 1); // Contains from the - symbol to the end of cp, effectively containing the number of children and some stuff after it int indexOfFirstNonDigit = 0; foreach(const QChar &c, childrenNumberAndMoreStuff) { if (c.isDigit()) ++indexOfFirstNonDigit; else break; } prebookmarks.append(PreBookmark(PDFencodingToQString(cp.section(QLatin1Char('('), 2, 2).section(QLatin1Char(')'), 0, 0)), cp.section(QLatin1Char('('), 1, 1).section(QLatin1Char(')'), 0, 0), childrenNumberAndMoreStuff.leftRef(indexOfFirstNonDigit).toUInt() )); } return; } } double PS_H = (currinf.data.dvi_h*300.0)/(65536*1200)-300; double PS_V = (currinf.data.dvi_v*300.0)/1200 - 300; if (cp.indexOf(QStringLiteral("ps::[begin]"), 0, Qt::CaseInsensitive) == 0) { PostScriptOutPutString->append( QStringLiteral(" %1 %2 moveto\n").arg(PS_H).arg(PS_V) ); PostScriptOutPutString->append( QStringLiteral(" %1\n").arg(cp.mid(11)) ); } else { if (cp.indexOf(QStringLiteral("ps::[end]"), 0, Qt::CaseInsensitive) == 0) { PostScriptOutPutString->append( QStringLiteral(" %1\n").arg(cp.mid(9)) ); } else { if (cp.indexOf(QStringLiteral("ps::"), 0, Qt::CaseInsensitive) == 0) { PostScriptOutPutString->append( QStringLiteral(" %1\n").arg(cp.mid(4)) ); } else { PostScriptOutPutString->append( QStringLiteral(" %1 %2 moveto\n").arg(PS_H).arg(PS_V) ); PostScriptOutPutString->append( QStringLiteral(" %1\n").arg(cp.mid(3)) ); } } } } void dviRenderer::prescan_ParsePSFileSpecial(const QString& cp) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "epsf-special: psfile=" << cp; #endif QString include_command = cp.simplified(); // The line is supposed to start with "..ile=", and then comes the // filename. Figure out what the filename is and stow it away. Of // course, this does not work if the filename contains spaces // (already the simplified() above is wrong). If you have // files like this, go away. QString EPSfilename = include_command; EPSfilename.truncate(EPSfilename.indexOf(QLatin1Char(' '))); // Strip enclosing quotation marks which are included by some LaTeX // macro packages (but not by others). This probably means that // graphic files are no longer found if the filename really does // contain quotes, but we don't really care that much. if ((EPSfilename.at(0) == QLatin1Char('\"')) && (EPSfilename.at(EPSfilename.length()-1) == QLatin1Char('\"'))) { EPSfilename = EPSfilename.mid(1,EPSfilename.length()-2); } // If the file name ends in 'png', 'gif', 'jpg' or 'jpeg', we assume // that this is NOT a PostScript file, and we exit here. QString ending = EPSfilename.section(QLatin1Char('.'), -1).toLower(); if ((ending == QLatin1String("png")) || (ending == QLatin1String("gif")) || (ending == QLatin1String("jpg")) || (ending == QLatin1String("jpeg"))) { dviFile->numberOfExternalNONPSFiles++; return; } // Now assume that the graphics file *is* a PostScript file dviFile->numberOfExternalPSFiles++; // Now locate the Gfx file on the hard disk... EPSfilename = ghostscript_interface::locateEPSfile(EPSfilename, baseURL); // If the EPSfilename really points to a PDF file, convert that file now. if (ending == QLatin1String("pdf")) { QString convErrorMsg; EPSfilename = dviFile->convertPDFtoPS(EPSfilename, &convErrorMsg); if (convErrorMsg.isEmpty() != true) { emit error(convErrorMsg, -1); return; } } // Now parse the arguments. int llx = 0; int lly = 0; int urx = 0; int ury = 0; int rwi = 0; int rhi = 0; int angle = 0; // just to avoid ambiguities; the filename could contain keywords include_command = include_command.mid(include_command.indexOf(QLatin1Char(' '))); parse_special_argument(include_command, "llx=", &llx); parse_special_argument(include_command, "lly=", &lly); parse_special_argument(include_command, "urx=", &urx); parse_special_argument(include_command, "ury=", &ury); parse_special_argument(include_command, "rwi=", &rwi); parse_special_argument(include_command, "rhi=", &rhi); parse_special_argument(include_command, "angle=", &angle); int clip=include_command.indexOf(QStringLiteral(" clip")); // -1 if clip keyword is not present, >= 0 otherwise if (QFile::exists(EPSfilename)) { double PS_H = (currinf.data.dvi_h*300.0)/(65536*1200)-300; double PS_V = (currinf.data.dvi_v*300.0)/1200 - 300; PostScriptOutPutString->append( QStringLiteral(" %1 %2 moveto\n").arg(PS_H).arg(PS_V) ); PostScriptOutPutString->append( QStringLiteral("@beginspecial ") ); PostScriptOutPutString->append( QStringLiteral(" %1 @llx").arg(llx) ); PostScriptOutPutString->append( QStringLiteral(" %1 @lly").arg(lly) ); PostScriptOutPutString->append( QStringLiteral(" %1 @urx").arg(urx) ); PostScriptOutPutString->append( QStringLiteral(" %1 @ury").arg(ury) ); if (rwi != 0) PostScriptOutPutString->append( QStringLiteral(" %1 @rwi").arg(rwi) ); if (rhi != 0) PostScriptOutPutString->append( QStringLiteral(" %1 @rhi").arg(rhi) ); if (angle != 0) PostScriptOutPutString->append( QStringLiteral(" %1 @angle").arg(angle) ); if (clip != -1) PostScriptOutPutString->append( QStringLiteral(" @clip")); PostScriptOutPutString->append( QStringLiteral(" @setspecial \n") ); PostScriptOutPutString->append( QStringLiteral(" (%1) run\n").arg(EPSfilename) ); PostScriptOutPutString->append( QStringLiteral("@endspecial \n") ); } return; } void dviRenderer::prescan_ParseSourceSpecial(const QString& cp) { // if no rendering takes place, i.e. when the DVI file is first // loaded, generate a DVI_SourceFileAnchor. These anchors are used // in forward search, i.e. to relate references line // "src:123file.tex" to positions in the DVI file // extract the file name and the numeral part from the string qint32 j; for(j=0;jfilename); QString sourceFileName = QFileInfo(fi1.dir(), cp.mid(j).trimmed()).absoluteFilePath(); Length l; l.setLength_in_inch(currinf.data.dvi_v/(resolutionInDPI*shrinkfactor)); DVI_SourceFileAnchor sfa(sourceFileName, sourceLineNumber, current_page+1, l); sourceHyperLinkAnchors.push_back(sfa); } void dviRenderer::prescan_parseSpecials(char *cp, quint8 *) { QString special_command = QString::fromUtf8(cp); // Now to those specials which are only interpreted during the // prescan phase, and NOT during rendering. // PaperSize special if (qstrnicmp(cp, "papersize", 9) == 0) { prescan_ParsePapersizeSpecial(special_command.mid(9)); return; } // color special for background color if (qstrnicmp(cp, "background", 10) == 0) { prescan_ParseBackgroundSpecial(special_command.mid(10)); return; } // HTML anchor special if (qstrnicmp(cp, "html:", 9) == 0) { html_anchor_end(); return; } return; } void dviRenderer::prescan_setChar(unsigned int ch) { TeXFontDefinition *fontp = currinf.fontp; if (fontp == NULL) return; if (currinf.set_char_p == &dviRenderer::set_char) { glyph *g = ((TeXFont *)(currinf.fontp->font))->getGlyph(ch, true, globalColor); if (g == NULL) return; currinf.data.dvi_h += (int)(currinf.fontp->scaled_size_in_DVI_units * dviFile->getCmPerDVIunit() * (1200.0 / 2.54)/16.0 * g->dvi_advance_in_units_of_design_size_by_2e20 + 0.5); return; } if (currinf.set_char_p == &dviRenderer::set_vf_char) { macro *m = &currinf.fontp->macrotable[ch]; if (m->pos == NULL) return; currinf.data.dvi_h += (int)(currinf.fontp->scaled_size_in_DVI_units * dviFile->getCmPerDVIunit() * (1200.0 / 2.54)/16.0 * m->dvi_advance_in_units_of_design_size_by_2e20 + 0.5); return; } } void dviRenderer::prescan(parseSpecials specialParser) { #ifdef DEBUG_PRESCAN qCDebug(OkularDviDebug) << "dviRenderer::prescan( ... )"; #endif if (resolutionInDPI == 0.0) setResolution(100); qint32 RRtmp=0, WWtmp=0, XXtmp=0, YYtmp=0, ZZtmp=0; quint8 ch; double fontPixelPerDVIunit = dviFile->getCmPerDVIunit() * 1200.0/2.54; stack.clear(); currinf.fontp = NULL; currinf.set_char_p = &dviRenderer::set_no_char; for (;;) { ch = readUINT8(); if (ch <= (unsigned char) (SETCHAR0 + 127)) { prescan_setChar(ch); continue; } if (FNTNUM0 <= ch && ch <= (unsigned char) (FNTNUM0 + 63)) { currinf.fontp = currinf.fonttable->value(ch - FNTNUM0); if (currinf.fontp == NULL) { errorMsg = i18n("The DVI code referred to font #%1, which was not previously defined.", ch - FNTNUM0); return; } currinf.set_char_p = currinf.fontp->set_char_p; continue; } qint32 a, b; switch (ch) { case SET1: prescan_setChar(readUINT8()); break; case SETRULE: /* Be careful, dvicopy outputs rules with height = 0x80000000. We don't want any SIGFPE here. */ a = readUINT32(); b = readUINT32(); b = ((long) (b * 65536.0*fontPixelPerDVIunit)); currinf.data.dvi_h += b; break; case PUTRULE: a = readUINT32(); b = readUINT32(); break; case PUT1: case NOP: break; case BOP: command_pointer += 11 * 4; currinf.data.dvi_h = 1200 << 16; // Reminder: DVI-coordinates start at (1",1") from top of page currinf.data.dvi_v = 1200; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); currinf.data.w = currinf.data.x = currinf.data.y = currinf.data.z = 0; break; case PUSH: stack.push(currinf.data); break; case POP: if (stack.isEmpty()) return; else currinf.data = stack.pop(); break; case RIGHT1: case RIGHT2: case RIGHT3: case RIGHT4: RRtmp = readINT(ch - RIGHT1 + 1); currinf.data.dvi_h += ((long) (RRtmp * 65536.0*fontPixelPerDVIunit)); break; case W1: case W2: case W3: case W4: WWtmp = readINT(ch - W0); currinf.data.w = ((long) (WWtmp * 65536.0*fontPixelPerDVIunit)); + // fallthrough case W0: currinf.data.dvi_h += currinf.data.w; break; case X1: case X2: case X3: case X4: XXtmp = readINT(ch - X0); currinf.data.x = ((long) (XXtmp * 65536.0*fontPixelPerDVIunit)); + // fallthrough case X0: currinf.data.dvi_h += currinf.data.x; break; case DOWN1: case DOWN2: case DOWN3: case DOWN4: { qint32 DDtmp = readINT(ch - DOWN1 + 1); currinf.data.dvi_v += ((long) (DDtmp * 65536.0*fontPixelPerDVIunit))/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); } break; case Y1: case Y2: case Y3: case Y4: YYtmp = readINT(ch - Y0); currinf.data.y = ((long) (YYtmp * 65536.0*fontPixelPerDVIunit)); + // fallthrough case Y0: currinf.data.dvi_v += currinf.data.y/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); break; case Z1: case Z2: case Z3: case Z4: ZZtmp = readINT(ch - Z0); currinf.data.z = ((long) (ZZtmp * 65536.0*fontPixelPerDVIunit)); + // fallthrough case Z0: currinf.data.dvi_v += currinf.data.z/65536; currinf.data.pxl_v = int(currinf.data.dvi_v/shrinkfactor); break; case FNT1: case FNT2: case FNT3: case FNT4: currinf.fontp = currinf.fonttable->value(readUINT(ch - FNT1 + 1)); if (currinf.fontp == NULL) return; currinf.set_char_p = currinf.fontp->set_char_p; break; case XXX1: case XXX2: case XXX3: case XXX4: { quint8 *beginningOfSpecialCommand = command_pointer-1; a = readUINT(ch - XXX1 + 1); if (a > 0) { char *cmd = new char[a+1]; strncpy(cmd, (char *)command_pointer, a); command_pointer += a; cmd[a] = '\0'; (this->*specialParser)(cmd, beginningOfSpecialCommand); delete [] cmd; } } break; case FNTDEF1: case FNTDEF2: case FNTDEF3: case FNTDEF4: command_pointer += 12 + ch - FNTDEF1 + 1; command_pointer += readUINT8() + readUINT8(); break; default: return; } /* end switch */ } /* end for */ } diff --git a/generators/dvi/libokularGenerator_dvi.json b/generators/dvi/libokularGenerator_dvi.json index 1fc225aa1..37e6bff03 100644 --- a/generators/dvi/libokularGenerator_dvi.json +++ b/generators/dvi/libokularGenerator_dvi.json @@ -1,96 +1,98 @@ { "KPlugin": { "Authors": [ { "Email": "luigi.toscano@tiscali.it", "Name": "Luigi Toscano", "Name[sr@ijekavian]": "Луиђи Тоскано", "Name[sr@ijekavianlatin]": "Luiđi Toskano", "Name[sr@latin]": "Luiđi Toskano", "Name[sr]": "Луиђи Тоскано", "Name[x-test]": "xxLuigi Toscanoxx" } ], "Copyright": "© 2006 Luigi Toscano", "Copyright[et]": "© 2006: Luigi Toscano", "Copyright[ia]": "© 2006 Pino Toscano", "Copyright[ru]": "© Luigi Toscano, 2006", "Copyright[sr@ijekavian]": "© 2006, Луиђи Тоскано", "Copyright[sr@ijekavianlatin]": "© 2006, Luiđi Toskano", "Copyright[sr@latin]": "© 2006, Luiđi Toskano", "Copyright[sr]": "© 2006, Луиђи Тоскано", "Copyright[uk]": "© Luigi Toscano, 2006", "Copyright[x-test]": "xx© 2006 Luigi Toscanoxx", "Description": "A DVI file renderer", "Description[ca@valencia]": "Un renderitzador de fitxers DVI", "Description[ca]": "Un renderitzador de fitxers DVI", "Description[cs]": "Vykreslovač DVI souborů", "Description[de]": "Ein Renderer für DVI-Dateien", "Description[el]": "Πρόγραμμα αποτύπωσης για DVI αρχεία", "Description[es]": "Un visor de archivos DVI", "Description[et]": "DVI-faili renderdaja", "Description[fi]": "DVI-tiedostohahmonnin", "Description[fr]": "Système de rendu pour fichiers « DVI »", + "Description[gl]": "Un visor de ficheiros DVI", "Description[ia]": "Un renditor de file de DVI", "Description[it]": "Un visualizzatore di file DVI", "Description[ko]": "DVI 파일 렌더러", "Description[nl]": "Een DVI-bestandsweergever", "Description[nn]": "Ein gjengjevar for DVI-filer", "Description[pl]": "Wyświetlanie pliku DVI", "Description[pt]": "Um visualizador de ficheiros DVI", "Description[ru]": "Модуль поддержки формата DVI", "Description[sk]": "Vykresľovanie DVI súborov", "Description[sl]": "Izrisovalnik datotek DVI", "Description[sr@ijekavian]": "Рендерер ДВИ фајлова", "Description[sr@ijekavianlatin]": "Renderer DVI fajlova", "Description[sr@latin]": "Renderer DVI fajlova", "Description[sr]": "Рендерер ДВИ фајлова", "Description[sv]": "Ett återgivningsprogram för DVI-filer", "Description[tr]": "Bir DVI dosya oluşturucu", "Description[uk]": "Інструмент для показу файлів DVI", "Description[x-test]": "xxA DVI file rendererxx", "Description[zh_CN]": "DVI 文件渲染器", "Description[zh_TW]": "DVI 檔成像器", "Id": "okular_dvi", "License": "GPL", "MimeTypes": [ "application/x-dvi" ], "Name": "DVI Backend", "Name[ca@valencia]": "Dorsal de DVI", "Name[ca]": "Dorsal de DVI", "Name[cs]": "Implementace DVI", "Name[de]": "Anzeigemodul für DVI", "Name[el]": "Σύστημα υποστήριξης DVI", "Name[es]": "Motor para DVI", "Name[et]": "DVI taustaprogramm", "Name[fi]": "DVI-taustaosa", "Name[fr]": "Moteur DVI", + "Name[gl]": "Infraestrutura de DVI", "Name[ia]": "Retro-administration de DVI", "Name[it]": "Backend DVI", "Name[ko]": "DVI 백엔드", "Name[nl]": "DVI-backend", "Name[nn]": "DVI-motor", "Name[pl]": "Obsługa DVI", "Name[pt]": "Infra-Estrutura de DVI", "Name[ru]": "Модуль поддержки формата DVI", "Name[sl]": "Zaledje za DVI", "Name[sr@ijekavian]": "Позадина за ДВИ", "Name[sr@ijekavianlatin]": "Pozadina za DVI", "Name[sr@latin]": "Pozadina za DVI", "Name[sr]": "Позадина за ДВИ", "Name[sv]": "DVI-gränssnitt", "Name[tr]": "DVI Arka Ucu", "Name[uk]": "Модуль DVI", "Name[x-test]": "xxDVI Backendxx", "Name[zh_CN]": "DVI 后端", "Name[zh_TW]": "DVI 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.3.7" }, "X-KDE-Priority": 2, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/dvi/org.kde.okular-dvi.metainfo.xml b/generators/dvi/org.kde.okular-dvi.metainfo.xml index 288cc4813..bd18547e5 100644 --- a/generators/dvi/org.kde.okular-dvi.metainfo.xml +++ b/generators/dvi/org.kde.okular-dvi.metainfo.xml @@ -1,73 +1,75 @@ org.kde.okular-dvi org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 DVI Documents Documentos DVI Documents DVI Documents DVI Dokumenty DVI DVI-Dokumente Έγγραφα DVI DVI Documents Documentos DVI DVI dokumendid DVI-tiedostot Documents DVI + Documentos DVI Documenti DVI DVI 문서 DVI-documenten DVI-dokument Dokumenty DVI Documentos DVI DVI dokumenty Dokumenti DVI ДВИ документи DVI dokumenti ДВИ документи DVI dokumenti DVI-dokument DVI Belgeleri документи DVI xxDVI Documentsxx DVI 文档 DVI 文件 Adds support for reading DVI documents Amiesta sofitu pa la llectura de documentos DVI Afegeix la implementació per llegir documents DVI Afig la implementació per llegir documents DVI Přidává podporu pro čtení dokumentů DVI Bietet Unterstützung zum Lesen von DVI-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων DVI Adds support for reading DVI documents Permite la lectura de documentos DVI DVI dokumentide lugemise toetus Lisää DVI-tiedostojen lukutuen Permet la lecture des documents DVI + Permite ler documentos DVI. Aggiunge il supporto per la lettura di documenti DVI DVI 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van DVI-documenten toe Legg til støtte for å lesa DVI-dokument Dodaje obsługę dokumnetów DVI Adiciona o suporte para a leitura de documentos DVI Pridá podporu pre čítanie DVI dokumentov Doda podporo za branje dokumentov DVI Подршка за читање ДВИ докумената Podrška za čitanje DVI dokumenata Подршка за читање ДВИ докумената Podrška za čitanje DVI dokumenata Lägger till stöd för att läsa DVI-dokument DVI belgelerini okuma desteği ekler Додає підтримку читання документів DVI xxAdds support for reading DVI documentsxx 增加对 DVI 文档的阅读支持 加入讀取 DVI 文件的支援 application/x-dvi application/x-gzdvi application/x-dzdvi https://okular.kde.org diff --git a/generators/epub/libokularGenerator_epub.json b/generators/epub/libokularGenerator_epub.json index db80ba1d2..2e3ed61dc 100644 --- a/generators/epub/libokularGenerator_epub.json +++ b/generators/epub/libokularGenerator_epub.json @@ -1,96 +1,98 @@ { "KPlugin": { "Authors": [ { "Email": "elylevy@cs.huji.ac.il", "Name": "Ely Levy", "Name[sr@ijekavian]": "Ели Леви", "Name[sr@ijekavianlatin]": "Eli Levi", "Name[sr@latin]": "Eli Levi", "Name[sr]": "Ели Леви", "Name[x-test]": "xxEly Levyxx" } ], "Copyright": "© 2008 Ely Levy", "Copyright[et]": "© 2008: Ely Levy", "Copyright[ru]": "© Ely Levy, 2008", "Copyright[sr@ijekavian]": "© 2008, Ели Леви", "Copyright[sr@ijekavianlatin]": "© 2008, Eli Levi", "Copyright[sr@latin]": "© 2008, Eli Levi", "Copyright[sr]": "© 2008, Ели Леви", "Copyright[uk]": "© Ely Levy, 2008", "Copyright[x-test]": "xx© 2008 Ely Levyxx", "Description": "An EPub backend", "Description[ca@valencia]": "Un dorsal per l'EPub", "Description[ca]": "Un dorsal per l'EPub", "Description[cs]": "Podpůrná vrstva EPub", "Description[de]": "Ein Anzeigemodul für EPUB", "Description[el]": "Σύστημα υποστήριξης EPub", "Description[es]": "Un motor para EPub", "Description[et]": "EPubi taustaprogramm", "Description[fi]": "EPub-taustaosa", "Description[fr]": "Un moteur EPub", + "Description[gl]": "Unha infraestrutura para EPub", "Description[ia]": "Un retro-administration de EPub", "Description[it]": "Backend EPub", "Description[ko]": "EPub 백엔드", "Description[nl]": "Een EPub-backend", "Description[nn]": "Ein EPUB-motor", "Description[pl]": "Moduł EPub", "Description[pt]": "Uma infra-estrutura de EPub", "Description[ru]": "Модуль поддержки формата EPub", "Description[sk]": "EPub backend", "Description[sl]": "Zaledje za EPub", "Description[sr@ijekavian]": "Позадина за ЕПУБ", "Description[sr@ijekavianlatin]": "Pozadina za EPUB", "Description[sr@latin]": "Pozadina za EPUB", "Description[sr]": "Позадина за ЕПУБ", "Description[sv]": "Ett Epub-gränssnitt", "Description[tr]": "Bir EPub arka ucu", "Description[uk]": "Сервер EPub", "Description[x-test]": "xxAn EPub backendxx", "Description[zh_CN]": "EPub 后端", "Description[zh_TW]": "EPub 後端介面", "Id": "okular_epub", "License": "GPL", "MimeTypes": [ "application/epub+zip" ], "Name": "EPub Backend", "Name[ast]": "Backend d'EPub", "Name[ca@valencia]": "Dorsal EPub", "Name[ca]": "Dorsal EPub", "Name[cs]": "Podpůrná vrstva EPub", "Name[de]": "Anzeigemodul für EPUB", "Name[el]": "Σύστημα υποστήριξης EPub", "Name[es]": "Motor para EPub", "Name[et]": "EPubi taustaprogramm", "Name[fi]": "EPub-taustaosa", "Name[fr]": "Moteur EPub", + "Name[gl]": "Infraestrutura de EPub", "Name[ia]": "Retro-Administration de EPub", "Name[it]": "Backend EPub", "Name[ko]": "EPub 백엔드", "Name[nl]": "EPub-backend", "Name[nn]": "EPUB-motor", "Name[pl]": "Moduł EPub", "Name[pt]": "Infra-Estrutura de EPub", "Name[ru]": "Модуль поддержки формата EPub", "Name[sl]": "Zaledje za EPub", "Name[sr@ijekavian]": "Позадина за ЕПУБ", "Name[sr@ijekavianlatin]": "Pozadina za EPUB", "Name[sr@latin]": "Pozadina za EPUB", "Name[sr]": "Позадина за ЕПУБ", "Name[sv]": "Epub-gränssnitt", "Name[tr]": "EPub Arka Ucu", "Name[uk]": "Модуль EPub", "Name[x-test]": "xxEPub Backendxx", "Name[zh_CN]": "EPub 后端", "Name[zh_TW]": "EPub 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.2.3" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/epub/org.kde.okular-epub.metainfo.xml b/generators/epub/org.kde.okular-epub.metainfo.xml index 779c85d49..d1176e05d 100644 --- a/generators/epub/org.kde.okular-epub.metainfo.xml +++ b/generators/epub/org.kde.okular-epub.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-epub org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 EPub EPub EPub EPub EPub EPub EPub EPub EPub EPub EPub EPub + EPub EPub EPub EPub EPUB EPub EPub EPub EPub ЕПУБ EPUB ЕПУБ EPUB Epub EPub EPub xxEPubxx EPub EPub Adds support for reading E-Books Amiesta sofitu pa la llectura de llibros electrónicos Afegeix la implementació per llegir llibres electrònics Afig la implementació per llegir llibres electrònics Přidává podporu pro čtení e-booků Bietet Unterstützung zum Lesen von EPub-E-Books Προσθέτει υποστήριξη για την ανάγνωση E-Books Adds support for reading E-Books Permite la lectura de libros electrónicos E-raamatute lugemise toetus Lisää e-kirjojen lukutuen Permet la lecture des E-Books + Permite ler libros electrónicos. Aggiunge il supporto per la lettura di libri digitali in formato EPub 전자책 읽기 지원 추가 Voegt ondersteuning voor lezen van e-boeken toe Legg til støtte for å lesa e-bøker i EPUB-formatet Dodaje obsługę czytania ebooków Adiciona o suporte para a leitura de E-Books Pridá podporu pre čítanie e-kníh Doda podporo za branje e-knjig Подршка за читање е‑књига Podrška za čitanje e‑knjiga Подршка за читање е‑књига Podrška za čitanje e‑knjiga Lägger till stöd för att läsa e-böcker E-Kitapları okumak için destek ekler Додає підтримку читання електронних книг xxAdds support for reading E-Booksxx 增加对电子书的阅读支持 加入讀取電子書的支援 application/epub+zip https://okular.kde.org diff --git a/generators/fax/faxexpand.cpp b/generators/fax/faxexpand.cpp index 999ee64a8..c551c7d60 100644 --- a/generators/fax/faxexpand.cpp +++ b/generators/fax/faxexpand.cpp @@ -1,744 +1,746 @@ /* Expand one page of fax data Copyright (C) 1990, 1995 Frank D. Cringle. This file is part of viewfax - g3/g4 fax processing software. viewfax 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; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "faxexpand.h" #include "fax_debug.h" //Uncomment this for verbose debug output //#define DEBUG_FAX #define verbose false pagenode::pagenode() { } /* Note that NeedBits() only works for n <= 16 */ #define NeedBits(n) do { \ if (BitsAvail < (n)) { \ BitAcc |= *sp++ << BitsAvail; \ BitsAvail += 16; \ } \ } while (0) #define GetBits(n) (BitAcc & ((1<<(n))-1)) #define ClrBits(n) do { \ BitAcc >>= (n); \ BitsAvail -= (n); \ } while (0) #ifdef DEBUG_FAX #define DEBUG_SHOW putchar(BitAcc & (1 << t) ? '1' : '0') #define LOOKUP(wid,tab) do { \ int t; \ NeedBits(wid); \ TabEnt = tab + GetBits(wid); \ printf("%08lX/%d: %s%5d\t", BitAcc, BitsAvail, \ StateNames[TabEnt->State], TabEnt->Param); \ for (t = 0; t < TabEnt->Width; t++) \ DEBUG_SHOW; \ putchar('\n'); \ fflush(stdout); \ ClrBits(TabEnt->Width); \ } while (0) #define SETVAL(x) do { \ *pa++ = RunLength + (x); \ printf("SETVAL: %d\t%d\n", RunLength + (x), a0); \ a0 += x; \ RunLength = 0; \ } while (0) const char *StateNames[] = { "Null ", "Pass ", "Horiz ", "V0 ", "VR ", "VL ", "Ext ", "TermW ", "TermB ", "MakeUpW", "MakeUpB", "MakeUp ", "EOL ", }; #else #define LOOKUP(wid,tab) do { \ NeedBits(wid); \ TabEnt = tab + GetBits(wid); \ ClrBits(TabEnt->Width); \ } while (0) #define SETVAL(x) do { \ *pa++ = RunLength + (x); \ a0 += x; \ RunLength = 0; \ } while (0) #endif #define dumpruns(runs) do { \ printf("-------------------- %d\n", LineNum); \ for (pa = runs, a0 = 0; a0 < lastx; a0 += *pa++) \ printf("%4d %d\n", a0, *pa); \ } while (0) #define EndOfData(pn) (sp >= pn->data + pn->length/sizeof(*pn->data)) /* This macro handles coding errors in G3 data. We redefine it below for the G4 case */ #define SKIP_EOL do { \ while (!EndOfData(pn)) { \ NeedBits(11); \ if (GetBits(11) == 0) \ break; \ ClrBits(1); \ } \ ClrBits(11); \ goto EOL; \ } while (0) #define eol2lab EOL2: /* the line expanders are written as macros so that they can be reused (twice each) but still have direct access to the local variables of the "calling" function */ #define expand1d() do { \ while (a0 < lastx) { \ int done = 0; \ while (!done) { /* white first */ \ LOOKUP(12, WhiteTable); \ switch (TabEnt->State) { \ case S_EOL: \ EOLcnt = 1; \ goto EOL; \ case S_TermW: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpW: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ case S_Ext: \ unexpected("Extension code", LineNum); \ SKIP_EOL; \ break; \ default: \ unexpected("WhiteTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ done = a0 >= lastx; \ while (!done) { /* then black */ \ LOOKUP(13, BlackTable); \ switch (TabEnt->State) { \ case S_EOL: \ EOLcnt = 1; \ goto EOL; \ case S_TermB: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpB: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ case S_Ext: \ unexpected("Extension code", LineNum); \ SKIP_EOL; \ break; \ default: \ unexpected("BlackTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ } \ EOL: ; \ } while (0) #define CHECK_b1 do { \ if (pa != thisrun) while (b1 <= a0 && b1 < lastx) { \ b1 += pb[0] + pb[1]; \ pb += 2; \ } \ } while (0) #define expand2d(eolab) do { \ while (a0 < lastx) { \ LOOKUP(7, MainTable); \ switch (TabEnt->State) { \ case S_Pass: \ CHECK_b1; \ b1 += *pb++; \ RunLength += b1 - a0; \ a0 = b1; \ b1 += *pb++; \ break; \ case S_Horiz: \ if ((pa-run0)&1) { \ int done = 0; \ while (!done) { /* black first */ \ LOOKUP(13, BlackTable); \ switch (TabEnt->State) { \ case S_TermB: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpB: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("BlackTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ done = 0; \ while (!done) { /* then white */ \ LOOKUP(12, WhiteTable); \ switch (TabEnt->State) { \ case S_TermW: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpW: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("WhiteTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ } \ else { \ int done = 0; \ while (!done) { /* white first */ \ LOOKUP(12, WhiteTable); \ switch (TabEnt->State) { \ case S_TermW: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpW: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("WhiteTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ done = 0; \ while (!done) { /* then black */ \ LOOKUP(13, BlackTable); \ switch (TabEnt->State) { \ case S_TermB: \ SETVAL(TabEnt->Param); \ done = 1; \ break; \ case S_MakeUpB: \ case S_MakeUp: \ a0 += TabEnt->Param; \ RunLength += TabEnt->Param; \ break; \ default: \ unexpected("BlackTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ } \ CHECK_b1; \ break; \ case S_V0: \ CHECK_b1; \ SETVAL(b1 - a0); \ b1 += *pb++; \ break; \ case S_VR: \ CHECK_b1; \ SETVAL(b1 - a0 + TabEnt->Param); \ b1 += *pb++; \ break; \ case S_VL: \ CHECK_b1; \ SETVAL(b1 - a0 - TabEnt->Param); \ b1 -= *--pb; \ break; \ case S_Ext: \ *pa++ = lastx - a0; \ if (verbose) \ qCDebug(FAX_LOG) << "Line " << LineNum << ": extension code\n";\ SKIP_EOL; \ break; \ case S_EOL: \ *pa++ = lastx - a0; \ NeedBits(4); \ if (GetBits(4) && verbose) /* already seen 7 zeros */ \ qCDebug(FAX_LOG) << "Line " << LineNum << ": Bad EOL\n"; \ ClrBits(4); \ EOLcnt = 1; \ goto eolab; \ break; \ default: \ unexpected("MainTable", LineNum); \ SKIP_EOL; \ break; \ } \ } \ if (RunLength) { \ if (RunLength + a0 < lastx) { \ /* expect a final V0 */ \ NeedBits(1); \ if (!GetBits(1)) { \ unexpected("MainTable", LineNum); \ SKIP_EOL; \ } \ ClrBits(1); \ } \ SETVAL(0); \ } \ eol2lab ; \ } while (0) static void unexpected(const char *what, int LineNum) { if (verbose) qCCritical(FAX_LOG) << "Line " << LineNum << ": Unexpected state in " << what << endl; } /* Expand tiff modified huffman data (g3-1d without EOLs) */ void MHexpand(pagenode *pn, drawfunc df) { int a0; /* reference element */ int lastx; /* copy line width to register */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int RunLength; /* Length of current run */ t16bits *sp; /* pointer into compressed data */ pixnum *pa; /* pointer into new line */ int EOLcnt; /* number of consecutive EOLs */ int LineNum; /* line number */ pixnum *runs; /* list of run lengths */ struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; lastx = pn->size.width(); runs = (pixnum *) malloc(lastx * sizeof(pixnum)); for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX printf("\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); printf("-------------------- %d\n", LineNum); fflush(stdout); #endif RunLength = 0; pa = runs; a0 = 0; EOLcnt = 0; if (BitsAvail & 7) /* skip to byte boundary */ ClrBits(BitsAvail & 7); expand1d(); if (RunLength) SETVAL(0); if (a0 != lastx) { if (verbose) qCWarning(FAX_LOG) << "Line " << LineNum << ": length is " << a0 << " (expected "<< lastx << ")\n"; while (a0 > lastx) a0 -= *--pa; if (a0 < lastx) { if ((pa - runs) & 1) SETVAL(0); SETVAL(lastx - a0); } } (*df)(runs, LineNum++, pn); } free(runs); + (void)EOLcnt; // make gcc happy } /* Expand group-3 1-dimensional data */ void g31expand(pagenode *pn, drawfunc df) { int a0; /* reference element */ int lastx; /* copy line width to register */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int RunLength; /* Length of current run */ t16bits *sp; /* pointer into compressed data */ pixnum *pa; /* pointer into new line */ int EOLcnt; /* number of consecutive EOLs */ int LineNum; /* line number */ pixnum *runs; /* list of run lengths */ struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; lastx = pn->size.width(); runs = (pixnum *) malloc(lastx * sizeof(pixnum)); EOLcnt = 0; for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX fprintf(stderr,"\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); fprintf(stderr,"-------------------- %d\n", LineNum); fflush(stderr); #endif if (EOLcnt == 0) while (!EndOfData(pn)) { /* skip over garbage after a coding error */ NeedBits(11); if (GetBits(11) == 0) break; ClrBits(1); } for (EOLcnt = 1; !EndOfData(pn); EOLcnt++) { /* we have seen 11 zeros, which implies EOL, skip possible fill bits too */ while (1) { NeedBits(8); if (GetBits(8)) break; ClrBits(8); } while (GetBits(1) == 0) ClrBits(1); ClrBits(1); /* the eol flag */ NeedBits(11); if (GetBits(11)) break; ClrBits(11); } if (EOLcnt > 1 && EOLcnt != 6 && verbose) { qCCritical(FAX_LOG) << "Line " << LineNum << ": bad RTC (" << EOLcnt << " EOLs)\n"; } if (EOLcnt >= 6 || EndOfData(pn)) { free(runs); return; } RunLength = 0; pa = runs; a0 = 0; EOLcnt = 0; expand1d(); if (RunLength) SETVAL(0); if (a0 != lastx) { if (verbose) qCWarning(FAX_LOG) << "Line " << LineNum << ": length is " << a0 << " (expected "<< lastx << ")\n"; while (a0 > lastx) a0 -= *--pa; if (a0 < lastx) { if ((pa - runs) & 1) SETVAL(0); SETVAL(lastx - a0); } } (*df)(runs, LineNum++, pn); } free(runs); } /* Expand group-3 2-dimensional data */ void g32expand(pagenode *pn, drawfunc df) { int RunLength; /* Length of current run */ int a0; /* reference element */ int b1; /* next change on previous line */ int lastx = pn->size.width();/* copy line width to register */ pixnum *run0, *run1; /* run length arrays */ pixnum *thisrun, *pa, *pb; /* pointers into runs */ t16bits *sp; /* pointer into compressed data */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int EOLcnt; /* number of consecutive EOLs */ int refline = 0; /* 1D encoded reference line */ int LineNum; /* line number */ struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; /* allocate space for 2 runlength arrays */ run0 = (pixnum *) malloc(2 * ((lastx+5)&~1) * sizeof(pixnum)); run1 = run0 + ((lastx+5)&~1); run1[0] = lastx; run1[1] = 0; EOLcnt = 0; for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX printf("\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); printf("-------------------- %d\n", LineNum); fflush(stdout); #endif if (EOLcnt == 0) while (!EndOfData(pn)) { /* skip over garbage after a coding error */ NeedBits(11); if (GetBits(11) == 0) break; ClrBits(1); } for (EOLcnt = 1; !EndOfData(pn); EOLcnt++) { /* we have seen 11 zeros, which implies EOL, skip possible fill bits too */ while (1) { NeedBits(8); if (GetBits(8)) break; ClrBits(8); } while (GetBits(1) == 0) ClrBits(1); ClrBits(1); /* the eol flag */ NeedBits(12); refline = GetBits(1); /* 1D / 2D flag */ ClrBits(1); if (GetBits(11)) break; ClrBits(11); } if (EOLcnt > 1 && EOLcnt != 6 && verbose) qCCritical(FAX_LOG) << "Line " << LineNum << ": bad RTC (" << EOLcnt << " EOLs)\n"; if (EOLcnt >= 6 || EndOfData(pn)) { free(run0); return; } if (LineNum == 0 && refline == 0 && verbose) qCDebug(FAX_LOG) << "First line is 2-D encoded\n"; RunLength = 0; if (LineNum & 1) { pa = run1; pb = run0; } else { pa = run0; pb = run1; } thisrun = pa; EOLcnt = 0; a0 = 0; b1 = *pb++; if (refline) { expand1d(); } else { expand2d(EOL2); } if (RunLength) SETVAL(0); if (a0 != lastx) { if (verbose) qCWarning(FAX_LOG) << "Line " << LineNum << ": length is " << a0 << " (expected "<< lastx << ")\n"; while (a0 > lastx) a0 -= *--pa; if (a0 < lastx) { if ((pa - run0) & 1) SETVAL(0); SETVAL(lastx - a0); } } SETVAL(0); /* imaginary change at end of line for reference */ (*df)(thisrun, LineNum++, pn); } free(run0); } /* Redefine the "skip to eol" macro. We cannot recover from coding errors in G4 data */ #undef SKIP_EOL #undef eol2lab #define SKIP_EOL do { \ if (verbose) \ qCCritical(FAX_LOG) << "Line " << LineNum << ": G4 coding error\n"; \ free(run0); \ return; \ } while (0) #define eol2lab /* Expand group-4 data */ void g4expand(pagenode *pn, drawfunc df) { int RunLength; /* Length of current run */ int a0; /* reference element */ int b1; /* next change on previous line */ int lastx = pn->size.width();/* copy line width to register */ pixnum *run0, *run1; /* run length arrays */ pixnum *thisrun, *pa, *pb; /* pointers into runs */ t16bits *sp; /* pointer into compressed data */ t32bits BitAcc; /* bit accumulator */ int BitsAvail; /* # valid bits in BitAcc */ int LineNum; /* line number */ int EOLcnt; struct tabent *TabEnt; sp = pn->data; BitAcc = 0; BitsAvail = 0; /* allocate space for 2 runlength arrays */ run0 = (pixnum *) malloc(2 * ((lastx+5)&~1) * sizeof(pixnum)); run1 = run0 + ((lastx+5)&~1); run1[0] = lastx; /* initial reference line */ run1[1] = 0; for (LineNum = 0; LineNum < pn->rowsperstrip; ) { #ifdef DEBUG_FAX printf("\nBitAcc=%08lX, BitsAvail = %d\n", BitAcc, BitsAvail); printf("-------------------- %d\n", LineNum); fflush(stdout); #endif RunLength = 0; if (LineNum & 1) { pa = run1; pb = run0; } else { pa = run0; pb = run1; } thisrun = pa; a0 = 0; b1 = *pb++; expand2d(EOFB); if (a0 < lastx) { if ((pa - run0) & 1) SETVAL(0); SETVAL(lastx - a0); } SETVAL(0); /* imaginary change at end of line for reference */ (*df)(thisrun, LineNum++, pn); continue; EOFB: NeedBits(13); if (GetBits(13) != 0x1001 && verbose) qCCritical(FAX_LOG) << "Bad RTC\n"; break; } free(run0); + (void)EOLcnt; // make gcc happy } static const unsigned char zerotab[256] = { 0x88, 0x07, 0x16, 0x06, 0x25, 0x05, 0x15, 0x05, 0x34, 0x04, 0x14, 0x04, 0x24, 0x04, 0x14, 0x04, 0x43, 0x03, 0x13, 0x03, 0x23, 0x03, 0x13, 0x03, 0x33, 0x03, 0x13, 0x03, 0x23, 0x03, 0x13, 0x03, 0x52, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x32, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x42, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x32, 0x02, 0x12, 0x02, 0x22, 0x02, 0x12, 0x02, 0x61, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x41, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x51, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x41, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x31, 0x01, 0x11, 0x01, 0x21, 0x01, 0x11, 0x01, 0x70, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x60, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x50, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x40, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00, 0x30, 0x00, 0x10, 0x00, 0x20, 0x00, 0x10, 0x00 }; #define check(v) do { \ prezeros = zerotab[v]; \ postzeros = prezeros & 15; \ prezeros >>= 4; \ if (prezeros == 8) { \ zeros += 8; \ continue; \ } \ if (zeros + prezeros < 11) { \ empty = 0; \ zeros = postzeros; \ continue; \ } \ zeros = postzeros; \ if (empty) \ EOLcnt++; \ lines++; \ empty = 1; \ } while (0) /* count fax lines */ int G3count(pagenode *pn, int twoD) { t16bits *p = pn->data; t16bits *end = p + pn->length/sizeof(*p); int lines = 0; /* lines seen so far */ int zeros = 0; /* number of consecutive zero bits seen */ int EOLcnt = 0; /* number of consecutive EOLs seen */ int empty = 1; /* empty line */ int prezeros, postzeros; while (p < end && EOLcnt < 6) { t16bits bits = *p++; check(bits&255); if (twoD && (prezeros + postzeros == 7)) { if (postzeros || ((bits & 0x100) == 0)) zeros--; } check(bits>>8); if (twoD && (prezeros + postzeros == 7)) { if (postzeros || ((p < end) && ((*p & 1) == 0))) zeros--; } } return lines - EOLcnt; /* don't count trailing EOLs */ } diff --git a/generators/fax/libokularGenerator_fax.json b/generators/fax/libokularGenerator_fax.json index 6f8143250..95af059a4 100644 --- a/generators/fax/libokularGenerator_fax.json +++ b/generators/fax/libokularGenerator_fax.json @@ -1,99 +1,101 @@ { "KPlugin": { "Authors": [ { "Email": "tokoe@kde.org", "Name": "Tobias Koenig", "Name[nn]": "Tobias König", "Name[ru]": "Tobias König", "Name[sr@ijekavian]": "Тобијас Кениг", "Name[sr@ijekavianlatin]": "Tobijas Kenig", "Name[sr@latin]": "Tobijas Kenig", "Name[sr]": "Тобијас Кениг", "Name[x-test]": "xxTobias Koenigxx" } ], "Copyright": "© 2008 Tobias Koenig", "Copyright[et]": "© 2008: Tobias Koenig", "Copyright[nn]": "© 2008 Tobias König", "Copyright[ru]": "© Tobias König, 2008", "Copyright[sr@ijekavian]": "© 2008, Тобијас Кениг", "Copyright[sr@ijekavianlatin]": "© 2008, Tobijas Kenig", "Copyright[sr@latin]": "© 2008, Tobijas Kenig", "Copyright[sr]": "© 2008, Тобијас Кениг", "Copyright[uk]": "© Tobias Koenig, 2008", "Copyright[x-test]": "xx© 2008 Tobias Koenigxx", "Description": "A G3/G4 fax document backend", "Description[ca@valencia]": "Un dorsal per documents de fax G3/G4", "Description[ca]": "Un dorsal per documents de fax G3/G4", "Description[cs]": "Implementace dokumentu faxu A G3/G4", "Description[de]": "Ein Anzeigemodul für G3/G4-Faxe", "Description[el]": "Σύστημα υποστήριξης για έγγραφα τηλεμοιοτυπίας G3/G4", "Description[es]": "Un motor para documentos de fax G3/G4", "Description[et]": "G3/G4 faksidokumendi taustaprogramm", "Description[fi]": "G3/G4-faksitiedostotaustaosa", "Description[fr]": "Un moteur de document fax G3/G4", + "Description[gl]": "Unha infraestrutura de documentos de fax G3/G4", "Description[ia]": "UN retro-administration de documento fax G3/G4", "Description[it]": "Un backend per documenti fax G3/G4", "Description[ko]": "G3/G4 팩스 문서 백엔드", "Description[nl]": "Een backend voor een G3/G4 faxdocument", "Description[nn]": "Ein motor for G3/G4-faksdokument", "Description[pl]": "Obsługa dokumentów faksowych G3/G4", "Description[pt]": "Uma infra-estrutura de documentos de fax G3/G4", "Description[ru]": "Модуль поддержки формата факсов G3/G4", "Description[sk]": "Backend dokumentu faxu G3/G4", "Description[sl]": "Zaledje za dokumente faksov G3/G4", "Description[sr@ijekavian]": "Позадина за Г3 факс и Г4 факс документе", "Description[sr@ijekavianlatin]": "Pozadina za G4 Fax i G3 Fax dokumente", "Description[sr@latin]": "Pozadina za G4 Fax i G3 Fax dokumente", "Description[sr]": "Позадина за Г3 факс и Г4 факс документе", "Description[sv]": "Ett G3/G4 telefax-dokumentgränssnitt", "Description[tr]": "G3/G4 faks belgesi arka ucu", "Description[uk]": "Сервер документів факсів G3/G4", "Description[x-test]": "xxA G3/G4 fax document backendxx", "Description[zh_CN]": "G3/G4 传真文档后端", "Description[zh_TW]": "G3/G4 fax 文件後端介面", "Id": "okular_fax", "License": "GPL", "MimeTypes": [ "image/fax-g3", "image/g3fax" ], "Name": "Fax Backend", "Name[ast]": "Backend de fax", "Name[ca@valencia]": "Dorsal de fax", "Name[ca]": "Dorsal de fax", "Name[cs]": "Implementace faxu", "Name[de]": "Anzeigemodul für Faxe", "Name[el]": "Σύστημα υποστήριξης fax", "Name[es]": "Motor para fax", "Name[et]": "Faksi taustaprogramm", "Name[fi]": "Faksitaustaosa", "Name[fr]": "Moteur Fax", + "Name[gl]": "Infraestrutura de fax", "Name[ia]": "Retro-Administration de fax", "Name[it]": "Backend fax", "Name[ko]": "팩스 백엔드", "Name[nl]": "Faxbackend", "Name[nn]": "Faksmotor", "Name[pl]": "Obsługa faksu", "Name[ru]": "Модуль поддержки форматов факсов", "Name[sl]": "Zaledje za faks", "Name[sr@ijekavian]": "Позадина за факсове", "Name[sr@ijekavianlatin]": "Pozadina za faksove", "Name[sr@latin]": "Pozadina za faksove", "Name[sr]": "Позадина за факсове", "Name[sv]": "Telefax-gränssnitt", "Name[tr]": "Faks Arka Ucu", "Name[uk]": "Модуль факсів", "Name[x-test]": "xxFax Backendxx", "Name[zh_CN]": "传真后端", "Name[zh_TW]": "Fax 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.1" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/fax/org.kde.okular-fax.metainfo.xml b/generators/fax/org.kde.okular-fax.metainfo.xml index 724b6a65c..fc66b919e 100644 --- a/generators/fax/org.kde.okular-fax.metainfo.xml +++ b/generators/fax/org.kde.okular-fax.metainfo.xml @@ -1,72 +1,74 @@ org.kde.okular-fax org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Fax Fax Fax Fax Fax Fax Fax Fax Fax Faks Faksi Fax + Fax Fax 팩스 Fax Faks Fax Fax Fax Faks Факс Faks Факс Faks Telefax Faks факс xxFaxxx 传真 傳真 Adds support for reading Fax documents Amiesta sofitu pa la llectura de documentos de fax Afegeix la implementació per llegir documents de Fax Afig la implementació per llegir documents de Fax Přidává podporu pro čtení dokumentů faxu Bietet Unterstützung zum Lesen von Fax-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων τηλεμοιοτυπίας Adds support for reading Fax documents Permite la lectura de documentos de fax Faksidokumentide lugemise toetus Lisää faksitiedostojen lukutuen Permet la lecture des documents Fax + Permite ler documentos de fax. Aggiunge il supporto per la lettura di documenti Fax 팩스 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van faxdocumenten toe Legg til støtte for å lesa faksdokument Dodaje obsługę czytania faksów Adiciona o suporte para a leitura de documentos de Fax Pridá podporu pre čítanie faxových dokumentov Doda podporo za branje faksiranih dokumentov Подршка за читање факс докумената Podrška za čitanje faks dokumenata Подршка за читање факс докумената Podrška za čitanje faks dokumenata Lägger till stöd för att läsa telefax-dokument Faks belgelerini okuma desteği ekler Додає підтримку читання документів факсів xxAdds support for reading Fax documentsxx 增加对传真文档的阅读支持 加入讀取傳真文件的支援 image/fax-g3 image/g3fax https://okular.kde.org diff --git a/generators/fictionbook/libokularGenerator_fb.json b/generators/fictionbook/libokularGenerator_fb.json index dfb708fd4..aad318709 100644 --- a/generators/fictionbook/libokularGenerator_fb.json +++ b/generators/fictionbook/libokularGenerator_fb.json @@ -1,99 +1,101 @@ { "KPlugin": { "Authors": [ { "Email": "tokoe@kde.org", "Name": "Tobias Koenig", "Name[nn]": "Tobias König", "Name[ru]": "Tobias König", "Name[sr@ijekavian]": "Тобијас Кениг", "Name[sr@ijekavianlatin]": "Tobijas Kenig", "Name[sr@latin]": "Tobijas Kenig", "Name[sr]": "Тобијас Кениг", "Name[x-test]": "xxTobias Koenigxx" } ], "Copyright": "© 2007-2008 Tobias Koenig", "Copyright[et]": "© 2007-2008: Tobias Koenig", "Copyright[fi]": "© 2007–2008 Tobias Koenig", "Copyright[nn]": "© 2007–2008 Tobias König", "Copyright[ru]": "© Tobias König, 2007-2008", "Copyright[sr@ijekavian]": "© 2007–2008, Тобијас Кениг", "Copyright[sr@ijekavianlatin]": "© 2007–2008, Tobijas Kenig", "Copyright[sr@latin]": "© 2007–2008, Tobijas Kenig", "Copyright[sr]": "© 2007–2008, Тобијас Кениг", "Copyright[uk]": "© Tobias Koenig, 2007–2008", "Copyright[x-test]": "xx© 2007-2008 Tobias Koenigxx", "Description": "A renderer for FictionBook eBooks", "Description[ca@valencia]": "Un renderitzador per a llibres electrònics FictionBook", "Description[ca]": "Un renderitzador per a llibres electrònics FictionBook", "Description[cs]": "Renderer elektronických knih Fiction Book", "Description[de]": "Ein Renderer für FictionBook-eBooks", "Description[el]": "Πρόγραμμα αποτύπωσης για FictionBook eBooks", "Description[es]": "Un visor para libros electrónicos FictionBook", "Description[et]": "FictionBooki e-raamatute renderdaja", "Description[fi]": "FictionBook-e-kirjojen hahmonnin", "Description[fr]": "Système de rendu pour les livres électroniques FictionBook", + "Description[gl]": "Un visor de libros electrónicos FictionBook.", "Description[ia]": "Un rendition pro eBooks (Libros electronic) de FictionBook", "Description[it]": "Un visualizzatore per ebook FictionBook", "Description[ko]": "FictionBook 전자책 렌더러", "Description[nl]": "Een viewer voor FictionBooks eBooks", "Description[nn]": "Ein lesar for e-bøker av typen FictionBook", "Description[pl]": "Wyświetlanie eksiążek FictionBook", "Description[pt]": "Um visualizador de eBooks em FictionBook", "Description[ru]": "Модуль поддержки формата электронных книг FictionBook", "Description[sk]": "Vykresľovanie e-kníh FictionBook", "Description[sl]": "Izrisovalnik za e-knjige FictionBook", "Description[sr@ijekavian]": "Рендерер за фикшнбук електронске књиге", "Description[sr@ijekavianlatin]": "Renderer za FictionBook elektronske knjige", "Description[sr@latin]": "Renderer za FictionBook elektronske knjige", "Description[sr]": "Рендерер за фикшнбук електронске књиге", "Description[sv]": "Ett återgivningsprogram för Fiction Book e-böcker", "Description[tr]": "FictionBook e-kitapları için bir oluşturucu", "Description[uk]": "Програма для відображення електронної белетристики", "Description[x-test]": "xxA renderer for FictionBook eBooksxx", "Description[zh_CN]": "FictionBook 电子书渲染器", "Description[zh_TW]": "FictionBook 電子書成像器", "Id": "okular_fax", "License": "GPL", "MimeTypes": [ "application/x-fictionbook+xml" ], "Name": "Fiction Book Backend", "Name[ca@valencia]": "Dorsal per al Fiction Book", "Name[ca]": "Dorsal per al Fiction Book", "Name[cs]": "Podpůrná vrstva Fiction Book", "Name[de]": "Anzeigemodul für FictionBook", "Name[el]": "Σύστημα υποστήριξης Fiction Book", "Name[es]": "Motor para Fiction Book", "Name[et]": "Fiction Booki taustaprogramm", "Name[fi]": "FictionBook-taustaosa", "Name[fr]": "Moteur pour FictionBook", + "Name[gl]": "Infraestrutura para libros FictionBook", "Name[ia]": "Retro adminisration de Fiction Book", "Name[it]": "Backend Fiction Book", "Name[ko]": "Fiction Book 백엔드", "Name[nl]": "FictionBook-backend", "Name[nn]": "FictionBook-motor", "Name[pl]": "Obsługa Fiction Book", "Name[pt]": "Infra-Estrutura de Livros de Ficção", "Name[ru]": "Модуль поддержки формата FictionBook", "Name[sl]": "Zaledje za FictionBook", "Name[sr@ijekavian]": "Позадина за фикшнбук", "Name[sr@ijekavianlatin]": "Pozadina za FictionBook", "Name[sr@latin]": "Pozadina za FictionBook", "Name[sr]": "Позадина за фикшнбук", "Name[sv]": "Fiction Book-gränssnitt", "Name[tr]": "Kurgu Kitabı Arka Ucu", "Name[uk]": "Модуль белетристики", "Name[x-test]": "xxFiction Book Backendxx", "Name[zh_CN]": "Fiction Book 后端", "Name[zh_TW]": "Fiction Book 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.5" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/fictionbook/org.kde.okular-fb.metainfo.xml b/generators/fictionbook/org.kde.okular-fb.metainfo.xml index 677cfb481..ff3b013df 100644 --- a/generators/fictionbook/org.kde.okular-fb.metainfo.xml +++ b/generators/fictionbook/org.kde.okular-fb.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-fb org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook FictionBook + FictionBook FictionBook FictionBook Fictie boek FictionBook Fikcja FictionBook FictionBook FictionBook Фикшнбук FictionBook Фикшнбук FictionBook Fiction Book FictionBook FictionBook xxFictionBookxx FictionBook FictionBook Adds support for reading FictionBooks Amiesta sofitu pa la llectura de FictionBooks Afegeix la implementació per llegir FictionBooks Afig la implementació per llegir FictionBooks Přidává podporu pro čtení formátu FictionBook Bietet Unterstützung zum Lesen von FictionBooks Προσθέτει υποστήριξη για την ανάγνωση FictionBooks Adds support for reading FictionBooks Permite la lectura de libros en formato FictionBook FictionBooki lugemise toetus Lisää FictionBook-lukutuen Permet la lecture des fichiers FictionBooks + Permite ler libros FictionBook. Aggiunge il supporto per la lettura di documenti in formato FictionBooks FictionBook 읽기 지원 추가 Voegt ondersteuning voor lezen van fictie boeken toe Legg til støtte for å lesa e-bøker i FictionBook-formatet Dodaje obsługę czytania fikcji Adiciona o suporte para a leitura de livros no formato FictionBooks Pridá podporu pre čítanie FictionBook Doda podporo za branje dokumentov FictionBook Подршка за читање фикшнбук е‑књига Podrška za čitanje FictionBook e‑knjiga Подршка за читање фикшнбук е‑књига Podrška za čitanje FictionBook e‑knjiga Lägger till stöd för att läsa Fiction Book FictionBook'ları okumak için destek ekler Додає підтримку читання документів FictionBook (FB2) xxAdds support for reading FictionBooksxx 添加对 FictionBook 的阅读支持 加入讀取 FictionBooks 的支援 application/x-fictionbook+xml https://okular.kde.org diff --git a/generators/kimgio/libokularGenerator_kimgio.json b/generators/kimgio/libokularGenerator_kimgio.json index 35fb04ed8..78cb6d505 100644 --- a/generators/kimgio/libokularGenerator_kimgio.json +++ b/generators/kimgio/libokularGenerator_kimgio.json @@ -1,142 +1,144 @@ { "KPlugin": { "Authors": [ { "Email": "aacid@kde.org", "Name": "Albert Astals Cid", "Name[ia]": "Albert Astals Cid", "Name[sr@ijekavian]": "Алберт Асталс Сид", "Name[sr@ijekavianlatin]": "Albert Astals Sid", "Name[sr@latin]": "Albert Astals Sid", "Name[sr]": "Алберт Асталс Сид", "Name[x-test]": "xxAlbert Astals Cidxx" }, { "Email": "pino@kde.org", "Name": "Pino Toscano", "Name[sr@ijekavian]": "Пино Тоскано", "Name[sr@ijekavianlatin]": "Pino Toskano", "Name[sr@latin]": "Pino Toskano", "Name[sr]": "Пино Тоскано", "Name[x-test]": "xxPino Toscanoxx" }, { "Email": "tokoe@kde.org", "Name": "Tobias Koenig", "Name[nn]": "Tobias König", "Name[ru]": "Tobias König", "Name[sr@ijekavian]": "Тобијас Кениг", "Name[sr@ijekavianlatin]": "Tobijas Kenig", "Name[sr@latin]": "Tobijas Kenig", "Name[sr]": "Тобијас Кениг", "Name[x-test]": "xxTobias Koenigxx" } ], "Copyright": "© 2005, 2009 Albert Astals Cid\n© 2006-2007 Pino Toscano\n© 2006-2007 Tobias Koenig", "Copyright[et]": "© 2005, 2009: Albert Astals Cid\n© 2006-2007: Pino Toscano\n© 2006-2007: Tobias Koenig", "Copyright[fi]": "© 2005, 2009 Albert Astals Cid\n© 2006–2007 Pino Toscano\n© 2006–2007 Tobias Koenig", "Copyright[nn]": "© 2005, 2009 Albert Astals Cid\n© 2006–2007 Pino Toscano\n© 2006–2007 Tobias Koenig", "Copyright[ru]": "© Albert Astals Cid, 2005, 2009\n© Pino Toscano, 2006-2007\n© Tobias König, 2006-2007", "Copyright[sr@ijekavian]": "© 2005, 2009, Алберт Асталс Сид\n© 2006–2007, Пино Тоскано\n© 2006–2007, Тобијас Кениг", "Copyright[sr@ijekavianlatin]": "© 2005, 2009, Albert Astals Sid\n© 2006–2007, Pino Toskano\n© 2006–2007, Tobijas Kenig", "Copyright[sr@latin]": "© 2005, 2009, Albert Astals Sid\n© 2006–2007, Pino Toskano\n© 2006–2007, Tobijas Kenig", "Copyright[sr]": "© 2005, 2009, Алберт Асталс Сид\n© 2006–2007, Пино Тоскано\n© 2006–2007, Тобијас Кениг", "Copyright[sv]": "© 2005,2009 Albert Astals Cid\n© 2006-2007 Pino Toscano\n© 2006-2007 Tobias Koenig", "Copyright[uk]": "© Albert Astals Cid, 2005, 2009 \n© Pino Toscano, 2006–2007\n© Tobias Koenig, 2006–2007", "Copyright[x-test]": "xx© 2005, 2009 Albert Astals Cid\n© 2006-2007 Pino Toscano\n© 2006-2007 Tobias Koenigxx", "Description": "A simple image backend", "Description[ca@valencia]": "Un dorsal d'imatges senzill", "Description[ca]": "Un dorsal d'imatges senzill", "Description[cs]": "Jednoduchá podpůrná vrstva obrázků", "Description[de]": "Ein einfaches Anzeigemodul für Bilder", "Description[el]": "Ένα απλό σύστημα υποστήριξης εικόνων", "Description[es]": "Un sencillo motor para imágenes", "Description[et]": "Lihtne pildi taustaprogramm", "Description[fi]": "Yksinkertainen kuvataustaosa", "Description[fr]": "Un moteur simple pour image", + "Description[gl]": "Unha infraestrutura simple para imaxes", "Description[ia]": "Un simplice retroadministration de image", "Description[it]": "Un semplice backend per le immagini", "Description[ko]": "간단한 그림 백엔드", "Description[nl]": "Een eenvoudige afbeeldingsbackend", "Description[nn]": "Ein enkel biletmotor", "Description[pl]": "Silnik prostych obrazków", "Description[pt]": "Uma infra-estrutura de imagens simples", "Description[ru]": "Модуль поддержки форматов изображений", "Description[sk]": "Backend jednoduchých obrázkov", "Description[sl]": "Preprosto zaledje za slike", "Description[sr@ijekavian]": "Једноставна позадина за слике", "Description[sr@ijekavianlatin]": "Jednostavna pozadina za slike", "Description[sr@latin]": "Jednostavna pozadina za slike", "Description[sr]": "Једноставна позадина за слике", "Description[sv]": "Ett enkelt bildgränssnitt", "Description[tr]": "Basit bir görüntü arka ucu", "Description[uk]": "Проста програма для зображень", "Description[x-test]": "xxA simple image backendxx", "Description[zh_CN]": "简单的图像后端", "Description[zh_TW]": "簡易的圖片後端介面", "Id": "okular_kimgio", "License": "GPL", "MimeTypes": [ "image/bmp", "image/x-dds", "image/x-eps", "image/x-exr", "image/gif", "image/x-hdr", "image/x-ico", "image/jp2", "image/jpeg", "video/x-mng", "image/x-portable-bitmap", "image/x-pcx", "image/x-portable-graymap", "image/png", "image/x-portable-pixmap", "image/x-psd", "image/x-rgb", "image/x-tga", "image/tiff", "image/x-xbitmap", "image/x-xcf", "image/x-xpixmap" ], "Name": "Image Backend", "Name[ast]": "Backend d'imáxenes", "Name[ca@valencia]": "Dorsal d'imatges", "Name[ca]": "Dorsal d'imatges", "Name[cs]": "Podpůrná vrstva obrázků", "Name[de]": "Anzeigemodul für Bilder", "Name[el]": "Σύστημα υποστήριξης εικόνας", "Name[es]": "Motor para imágenes", "Name[et]": "Pildi taustaprogramm", "Name[fi]": "Kuvataustaosa", "Name[fr]": "Moteur image", + "Name[gl]": "Infraestrutura de imaxe", "Name[ia]": "Retro-Administration de Image", "Name[it]": "Backend immagini", "Name[ko]": "그림 백엔드", "Name[nl]": "Afbeeldings-backend", "Name[nn]": "Biletmotor", "Name[pl]": "Silnik obrazów", "Name[pt]": "Infra-Estrutura de Imagens", "Name[ru]": "Модуль поддержки форматов изображений", "Name[sk]": "Backend obrázkov", "Name[sl]": "Zaledje za slike", "Name[sr@ijekavian]": "Позадина за слике", "Name[sr@ijekavianlatin]": "Pozadina za slike", "Name[sr@latin]": "Pozadina za slike", "Name[sr]": "Позадина за слике", "Name[sv]": "Bildgränssnitt", "Name[tr]": "Resim Arka Ucu", "Name[uk]": "Модуль зображень", "Name[x-test]": "xxImage Backendxx", "Name[zh_CN]": "图像后端", "Name[zh_TW]": "圖片後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.2" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/kimgio/org.kde.okular-kimgio.metainfo.xml b/generators/kimgio/org.kde.okular-kimgio.metainfo.xml index 0b35a8267..934ab7c17 100644 --- a/generators/kimgio/org.kde.okular-kimgio.metainfo.xml +++ b/generators/kimgio/org.kde.okular-kimgio.metainfo.xml @@ -1,94 +1,96 @@ org.kde.okular-kimgio org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Images Imáxenes Imatges Imatges Obrázky Bilder Εικόνες Images Imágenes Pildid Kuvat Images + Imaxes Immagini 그림 Afbeeldingen Bilete Obrazy Imagens Obrázky Slike Слике Slike Слике Slike Bilder Resimler Зображення xxImagesxx 图片 影像 Adds support for reading many image formats Amiesta sofitu pa la llectura de munchos formatos d'imaxe Afegeix la implementació per llegir molts formats d'imatges Afig la implementació per llegir molts formats d'imatges Přidává podporu pro čtení formátů obrázků Bietet Unterstützung zum Lesen von vielen Bildformaten Προσθέτει υποστήριξη για την ανάγνωση πολλών τύπων αποθήκευσης εικόνων Adds support for reading many image formats Permite la lectura de diversos formatos de imagen Paljude pildivormingute lugemise toetus Lisää monien kuvatiedostomuotojen lukutuen Permet la lecture de nombreux formats d'images + Permite ler moitos formatos de imaxe. Aggiunge il supporto per la lettura di vari formati di immagini 다양한 그림 형식 읽기 지원 추가 Ondersteuning toevoegen om vele afbeeldingsformaten te lezen Legg til støtte for å lesa mange ulike biletformat Dodaje obsługę czytania wielu formatów obrazów Adiciona o suporte para ler muitos formatos de imagem Pridá podporu pre čítanie mnohých obrazových formátov Doda podporo za branje številnih slikovnih vrst Подршка за читање разних формата слика Podrška za čitanje raznih formata slika Подршка за читање разних формата слика Podrška za čitanje raznih formata slika Lägger till stöd för att läsa många bildformat Birçok resim biçimini okuma desteği ekler Додає підтримку читання зображень у багатьох форматах xxAdds support for reading many image formatsxx 支持多种图片格式的阅读 加入讀取許多影像格式的支援 image/bmp image/x-dds image/x-eps image/x-exr image/gif image/x-hdr image/x-ico image/jp2 image/jpeg video/x-mng image/x-portable-bitmap image/x-pcx image/x-portable-graymap image/png image/x-portable-pixmap image/x-psd image/x-rgb image/x-tga image/tiff image/x-xbitmap image/x-xcf image/x-xpixmap image/x-gzeps image/x-bzeps https://okular.kde.org diff --git a/generators/mobipocket/libokularGenerator_mobi.json b/generators/mobipocket/libokularGenerator_mobi.json index 2b2c79aae..9ffa9dd90 100644 --- a/generators/mobipocket/libokularGenerator_mobi.json +++ b/generators/mobipocket/libokularGenerator_mobi.json @@ -1,99 +1,101 @@ { "KPlugin": { "Authors": [ { "Email": "qbast@go2.pl", "Name": "Jakub Stachowski", "Name[ia]": " Jakub Stachowski", "Name[sr@ijekavian]": "Јакуб Стаховски", "Name[sr@ijekavianlatin]": "Jakub Stahovski", "Name[sr@latin]": "Jakub Stahovski", "Name[sr]": "Јакуб Стаховски", "Name[x-test]": "xxJakub Stachowskixx" } ], "Copyright": "© 2008-2009 Jakub Stachowski", "Copyright[et]": "© 2008-2009: Jakub Stachowski", "Copyright[fi]": "© 2008–2009 Jakub Stachowski", "Copyright[ia]": "© 2008-2009 Jakub Stachowski", "Copyright[nn]": "© 2008–2009 Jakub Stachowski", "Copyright[ru]": "© Jakub Stachowski, 2008-2009", "Copyright[sr@ijekavian]": "© 2008—2009, Јакуб Стаховски", "Copyright[sr@ijekavianlatin]": "© 2008—2009, Jakub Stahovski", "Copyright[sr@latin]": "© 2008—2009, Jakub Stahovski", "Copyright[sr]": "© 2008—2009, Јакуб Стаховски", "Copyright[uk]": "© Jakub Stachowski, 2008–2009", "Copyright[x-test]": "xx© 2008-2009 Jakub Stachowskixx", "Description": "A mobipocket backend", "Description[ca@valencia]": "Un dorsal pel Mobipocket", "Description[ca]": "Un dorsal pel Mobipocket", "Description[cs]": " Implementace Mobipocket", "Description[de]": "Ein Anzeigemodul für Mobipocket-Dateien", "Description[el]": "Σύστημα υποστήριξης mobipocket", "Description[es]": "Un motor para mobipocket", "Description[et]": "Mobipocketi taustaprogramm", "Description[fi]": "MobiPocket-taustaosa", "Description[fr]": "Un moteur Mobipocket", + "Description[gl]": "Unha infraestrutura para mobipocket", "Description[ia]": "Un retro-administration de Mobipocket", "Description[it]": "Un backend per mobipocket", "Description[ko]": "Mobipocket 백엔드", "Description[nl]": "Een mobipocket-backend", "Description[nn]": "Ein Mobipocket-motor", "Description[pl]": "Obsługa mobipocket", "Description[pt]": "Uma infra-estrutura de Mobipocket", "Description[ru]": "Модуль поддержки формата Mobipocket", "Description[sk]": "Mobipocket backend", "Description[sl]": "Zaledje za Mobipocket", "Description[sr@ijekavian]": "Позадина за Мобипокет", "Description[sr@ijekavianlatin]": "Pozadina za Mobipocket", "Description[sr@latin]": "Pozadina za Mobipocket", "Description[sr]": "Позадина за Мобипокет", "Description[sv]": "Ett Mobipocket-gränssnitt", "Description[tr]": "Bir mobipocket arka ucu", "Description[uk]": "Сервер mobipocket", "Description[x-test]": "xxA mobipocket backendxx", "Description[zh_CN]": "Mobipocket 后端", "Description[zh_TW]": "Mobipocket 後端介面", "Id": "okular_mobi", "License": "GPL", "MimeTypes": [ "application/x-mobipocket-ebook" ], "Name": "Mobipocket Backend", "Name[ca@valencia]": "Dorsal del Mobipocket", "Name[ca]": "Dorsal del Mobipocket", "Name[cs]": " Implementace Mobipocket", "Name[de]": "Anzeigemodul für Mobipocket-Dateien", "Name[el]": "Σύστημα υποστήριξης Mobipocket", "Name[es]": "Motor para Mobipocket", "Name[et]": "Mobipocketi taustaprogramm", "Name[fi]": "Mobipocket-taustaosa", "Name[fr]": "Moteur Mobipocket", + "Name[gl]": "Infraestrutura para Mobipocket", "Name[ia]": "Retro-administration de Mobipocket", "Name[it]": "Backend Mobipocket", "Name[ko]": "Mobipocket 백엔드", "Name[nl]": "Mobipocket-backend", "Name[nn]": "Mobipocket-motor", "Name[pl]": "Obsługa Mobipocket", "Name[pt]": "Infra-Estrutura de Mobipocket", "Name[ru]": "Модуль поддержки формата Mobipocket", "Name[sl]": "Zaledje za Mobipocket", "Name[sr@ijekavian]": "Позадина за Мобипокет", "Name[sr@ijekavianlatin]": "Pozadina za Mobipocket", "Name[sr@latin]": "Pozadina za Mobipocket", "Name[sr]": "Позадина за Мобипокет", "Name[sv]": "Mobipocket-gränssnitt", "Name[tr]": "Mobipocket Arka Ucu", "Name[uk]": "Модуль Mobipocket", "Name[x-test]": "xxMobipocket Backendxx", "Name[zh_CN]": "Mobipocket 后端", "Name[zh_TW]": "Mobipocket 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.1" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/mobipocket/org.kde.okular-mobipocket.metainfo.xml b/generators/mobipocket/org.kde.okular-mobipocket.metainfo.xml index 40a92679d..0d2b41add 100644 --- a/generators/mobipocket/org.kde.okular-mobipocket.metainfo.xml +++ b/generators/mobipocket/org.kde.okular-mobipocket.metainfo.xml @@ -1,94 +1,96 @@ org.kde.okular-mobipocket org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket + Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Mobipocket Мобипокет Mobipocket Мобипокет Mobipocket Mobipocket Mobipocket Mobipocket xxMobipocketxx Mobipocket Mobipocket Adds support for reading Mobipocket E-books Amiesta sofitu pa la llectura de llibros electrónicos Mobipocket Afegeix la implementació per llegir llibres electrònics Mobipocket Afig la implementació per llegir llibres electrònics Mobipocket Přidává podporu pro čtení e-booků Mobipocket Bietet Unterstützung zum Lesen von Mobipocket-E-Books Προσθέτει υποστήριξη για την ανάνγωση Mobipocket E-Books Adds support for reading Mobipocket E-books Permite la lectura de libros electrónicos en formato Mobipocket Mobipocketi e-raamatute lugemise toetus Lisää MobiPocket-e-kirjojen lukutuen Ajouter la gestion en lecture des livres électroniques Mobipocket + Permite ler libros electrónicos Mobipocket. Aggiunge il supporto per la lettura di libri digitali in formato Mobipocket Mobipocket 전자책 읽기 지원 추가 Voegt ondersteuning voor lezen van Mobipocket e-boeken toe Legg til støtte for å lesa e-bøker i Mobipocket-formatet Dodaje obsługę czytania ebooków Mobipocket Adiciona o suporte para ler E-books da Mobipocket Pridá podporu pre čítanie e-kníh Mobipocket Doda podporo za branje e-knjig Mobipocket Подршка за читање мобипокет е‑књига Podrška za čitanje Mobipocket e‑knjiga Подршка за читање мобипокет е‑књига Podrška za čitanje Mobipocket e‑knjiga Lägger till stöd för att läsa Mobipocket e-böcker Mobipocket E-kitaplarını okuma desteği ekler Додає підтримку читання електронних книг у форматі Mobipocket xxAdds support for reading Mobipocket E-booksxx 增加对 Mobipocket 电子书的支持 加入讀取 Mobipocket 電子書的支援 image/bmp image/x-dds image/x-eps image/x-exr image/gif image/x-hdr image/x-ico image/jp2 image/jpeg video/x-mng image/x-portable-bitmap image/x-pcx image/x-portable-graymap image/png image/x-portable-pixmap image/x-psd image/x-rgb image/x-tga image/tiff image/x-xbitmap image/x-xcf image/x-xpixmap image/x-gzeps image/x-bzeps https://okular.kde.org diff --git a/generators/ooo/libokularGenerator_ooo.json b/generators/ooo/libokularGenerator_ooo.json index 0a2a1ef65..6a4382397 100644 --- a/generators/ooo/libokularGenerator_ooo.json +++ b/generators/ooo/libokularGenerator_ooo.json @@ -1,100 +1,102 @@ { "KPlugin": { "Authors": [ { "Email": "tokoe@kde.org", "Name": "Tobias Koenig", "Name[nn]": "Tobias König", "Name[ru]": "Tobias König", "Name[sr@ijekavian]": "Тобијас Кениг", "Name[sr@ijekavianlatin]": "Tobijas Kenig", "Name[sr@latin]": "Tobijas Kenig", "Name[sr]": "Тобијас Кениг", "Name[x-test]": "xxTobias Koenigxx" } ], "Copyright": "© 2006-2008 Tobias Koenig", "Copyright[et]": "© 2006-2008: Tobias Koenig", "Copyright[fi]": "© 2006–2008 Tobias Koenig", "Copyright[nn]": "© 2006–2008 Tobias Koenig", "Copyright[ru]": "© Tobias König, 2006-2008", "Copyright[sr@ijekavian]": "© 2000–2009, Тобијас Кениг", "Copyright[sr@ijekavianlatin]": "© 2000–2009, Tobijas Kenig", "Copyright[sr@latin]": "© 2000–2009, Tobijas Kenig", "Copyright[sr]": "© 2000–2009, Тобијас Кениг", "Copyright[uk]": "© Tobias Koenig, 2006–2008", "Copyright[x-test]": "xx© 2006-2008 Tobias Koenigxx", "Description": "A renderer for OpenDocument Text documents", "Description[ca@valencia]": "Un renderitzador per als documents de text OpenDocument", "Description[ca]": "Un renderitzador per als documents de text OpenDocument", "Description[cs]": "Vykreslovač textových dokumentů OpenDocument", "Description[de]": "Ein Renderer für OpenDocument-Textdokumente", "Description[el]": "Πρόγραμμα αποτύπωσης για έγγραφα κειμένου OpenDocument", "Description[es]": "Un visor para documentos de texto OpenDocument", "Description[et]": "OpenDocument tekstidokumentide renderdaja", "Description[fi]": "OpenDocument-tekstitiedostojen hahmonnin", "Description[fr]": "Un système de rendu pour documents textes OpenDocument", + "Description[gl]": "Un renderizador de documentos de texto de OpenDocument", "Description[ia]": "Un programma de rendition pro documentos de texto de OpenDocument", "Description[it]": "Un visualizzatore per documenti OpenDocument Text", "Description[ko]": "OpenDocument 텍스트 문서 렌더러", "Description[nl]": "Een viewer voor OpenDocument-tekstdocumenten", "Description[nn]": "Ein lesar for OpenDocument-tekstdokument", "Description[pl]": "Program wyświetlający dokumentów tekstowych OpenDocument", "Description[pt]": "Um visualizador de documentos de texto em OpenDocument", "Description[ru]": "Модуль поддержки текстовых документов формата OpenDocument", "Description[sk]": "Vykresľovač pre textové dokumenty OpenDocument", "Description[sl]": "Izrisovalnik besedilnih dokumentov OpenDocument", "Description[sr@ijekavian]": "Рендерер за отворенодокументске текстуалне документе", "Description[sr@ijekavianlatin]": "Renderer za otvorenodokumentske tekstualne dokumente", "Description[sr@latin]": "Renderer za otvorenodokumentske tekstualne dokumente", "Description[sr]": "Рендерер за отворенодокументске текстуалне документе", "Description[sv]": "Ett återgivningsprogram för OpenDocument-textdokument", "Description[tr]": "OpenDocument Metin belgeleri için bir oluşturucu", "Description[uk]": "Модуль для показу текстових документів OpenDocument", "Description[x-test]": "xxA renderer for OpenDocument Text documentsxx", "Description[zh_CN]": "OpenDocument 文本文档渲染器", "Description[zh_TW]": "OpenDocument 文字檔成像器", "Id": "okular_ooo", "License": "GPL", "MimeTypes": [ "application/vnd.oasis.opendocument.text" ], "Name": "OpenDocument Text Backend", "Name[ca@valencia]": "Dorsal per a textos OpenDocument", "Name[ca]": "Dorsal per a textos OpenDocument", "Name[cs]": "Podpůrná vrstva OpenDocument Text", "Name[de]": "Anzeigemodul für OpenDocument-Textformat", "Name[el]": "Σύστημα υποστήριξης κειμένου OpenDocument", "Name[es]": "Motor para texto de OpenDocument", "Name[et]": "OpenDocument teksti taustaprogramm", "Name[fi]": "OpenDocument-tekstitaustaosa", "Name[fr]": "Moteur pour texte OpenDocument", + "Name[gl]": "Infraestrutura para documentos de OpenDocument", "Name[ia]": "Retro-administration de Texto de OpenDocument", "Name[it]": "Backend OpenDocument Text", "Name[ko]": "OpenDocument 텍스트 백엔드", "Name[nl]": "OpenDocument-tekst-backend", "Name[nn]": "OpenDocument-tekstmotor", "Name[pl]": "Silnik dokumentów OpenDocument", "Name[pt]": "Infra-Estrutura de Texto em OpenDocument", "Name[ru]": "Модуль поддержки формата OpenDocument", "Name[sk]": "Backend OpenDocument Text", "Name[sl]": "Zaledje za dokumente OpenDocument", "Name[sr@ijekavian]": "Позадина за отворенодокументски текст", "Name[sr@ijekavianlatin]": "Pozadina za otvorenodokumentski tekst", "Name[sr@latin]": "Pozadina za otvorenodokumentski tekst", "Name[sr]": "Позадина за отворенодокументски текст", "Name[sv]": "OpenDocument-textgränssnitt", "Name[tr]": "OpenDocument Metin Arka ucu", "Name[uk]": "Модуль текстових документів OpenDocument", "Name[x-test]": "xxOpenDocument Text Backendxx", "Name[zh_CN]": "开放文档(ODF)文本后端", "Name[zh_TW]": "OpenDocument 文字檔後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.2.4" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/ooo/org.kde.okular-ooo.metainfo.xml b/generators/ooo/org.kde.okular-ooo.metainfo.xml index 9ce572b7d..45f1e8db2 100644 --- a/generators/ooo/org.kde.okular-ooo.metainfo.xml +++ b/generators/ooo/org.kde.okular-ooo.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-ooo org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument + OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument OpenDocument Отворени документ Otvoreni dokument Отворени документ Otvoreni dokument OpenDocument OpenDocument OpenDocument xxOpenDocumentxx 开放文档格式 OpenDocument Adds support for reading OpenDocument text files Amiesta sofitu pa la llectura de ficheros de testu OpenDocument Afegeix la implementació per llegir fitxers de text OpenDocument Afig la implementació per llegir fitxers de text OpenDocument Přidává podporu pro čtení textových souborů OpenDocument Bietet Unterstützung zum Lesen von OpenDocument-Text-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση αρχείων κειμένου OpenDocument Adds support for reading OpenDocument text files Permite la lectura de archivos de texto en formato OpenDocument OpenDocument tekstifailide lugemise toetus Lisää OpenDocument-tekstitiedostojen lukutuen Permet la lecture des fichiers texte OpenDocument + Permite ler ficheiros de texto OpenDocument. Aggiunge il supporto per la lettura di documenti di testo OpenDocument OpenDocument 텍스트 파일 읽기 지원 추가 Voegt ondersteuning voor lezen van OpenDocument tekstbestanden toe Legg til støtte for å lesa tekstfiler i OpenDocument-formatet Dodaje obsługę czytania plików tekstowych OpenDocument Adiciona o suporte para ler documentos de texto em OpenDocument Pridá podporu pre čítanie textových súborov OpenDocument Doda podporo za branje besedilnih datotek OpenDocument Подршка за читање отворенодокументских текстуалних фајлова Podrška za čitanje otvorenodokumentskih tekstualnih fajlova Подршка за читање отворенодокументских текстуалних фајлова Podrška za čitanje otvorenodokumentskih tekstualnih fajlova Lägger till stöd för att läsa OpenDocument-textfiler OpenDocument metin dosyalarını okumak için destek ekler Додає підтримку читання текстових документів OpenDocument xxAdds support for reading OpenDocument text filesxx 增加对开放文档格式文本文件的阅读支持 加入讀取 OpenDocument 文字檔的支援 application/vnd.oasis.opendocument.text https://okular.kde.org diff --git a/generators/plucker/libokularGenerator_plucker.json b/generators/plucker/libokularGenerator_plucker.json index 5aacdd95f..a7b4dd0a9 100644 --- a/generators/plucker/libokularGenerator_plucker.json +++ b/generators/plucker/libokularGenerator_plucker.json @@ -1,100 +1,102 @@ { "KPlugin": { "Authors": [ { "Email": "tokoe@kde.org", "Name": "Tobias Koenig", "Name[nn]": "Tobias König", "Name[ru]": "Tobias König", "Name[sr@ijekavian]": "Тобијас Кениг", "Name[sr@ijekavianlatin]": "Tobijas Kenig", "Name[sr@latin]": "Tobijas Kenig", "Name[sr]": "Тобијас Кениг", "Name[x-test]": "xxTobias Koenigxx" } ], "Copyright": "© 2007-2008 Tobias Koenig", "Copyright[et]": "© 2007-2008: Tobias Koenig", "Copyright[fi]": "© 2007–2008 Tobias Koenig", "Copyright[nn]": "© 2007–2008 Tobias König", "Copyright[ru]": "© Tobias König, 2007-2008", "Copyright[sr@ijekavian]": "© 2007–2008, Тобијас Кениг", "Copyright[sr@ijekavianlatin]": "© 2007–2008, Tobijas Kenig", "Copyright[sr@latin]": "© 2007–2008, Tobijas Kenig", "Copyright[sr]": "© 2007–2008, Тобијас Кениг", "Copyright[uk]": "© Tobias Koenig, 2007–2008", "Copyright[x-test]": "xx© 2007-2008 Tobias Koenigxx", "Description": "A renderer for Plucker eBooks", "Description[ca@valencia]": "Un renderitzador per a llibres electrònics Plucker", "Description[ca]": "Un renderitzador per a llibres electrònics Plucker", "Description[cs]": "Renderer elektronických knih Plucker", "Description[de]": "Ein Renderer für Plucker eBooks", "Description[el]": "Πρόγραμμα αποτύπωσης για Plucker eBooks", "Description[es]": "Un visor de libros electrónicos Plucker", "Description[et]": "Pluckeri e-raamatute renderdaja", "Description[fi]": "Plucker-e-kirjojen hahmonnin", "Description[fr]": "Système de rendu pour les livres électroniques Plucker", + "Description[gl]": "Un visor de libros electrónicos Plucker", "Description[ia]": "Un rendition pro eBooks (Libros electronic) de Plucker", "Description[it]": "Un visualizzatore per eBook Plucker", "Description[ko]": "Plucker 전자책 렌더러", "Description[nl]": "Een viewer voor Plucker eBooks", "Description[nn]": "Ein gjengjevar for Plucker-e-bøker", "Description[pl]": "Wyświetlanie eksiążek w formacie Plucker", "Description[pt]": "Um visualizador de eBooks do Plucker", "Description[ru]": "Модуль поддержки формата электронных книг Plucker", "Description[sk]": "Vykresľovanie e-kníh Plucker", "Description[sl]": "Izrisovalnik za e-knjige Plucker", "Description[sr@ijekavian]": "Рендерер за Плакерове е‑књиге", "Description[sr@ijekavianlatin]": "Renderer za Pluckerove e‑knjige", "Description[sr@latin]": "Renderer za Pluckerove e‑knjige", "Description[sr]": "Рендерер за Плакерове е‑књиге", "Description[sv]": "Ett återgivningsprogram för Plucker e-böcker", "Description[tr]": "Plucker e-kitapları için bir oluşturucu", "Description[uk]": "Програма для відображення ел. книг Plucker", "Description[x-test]": "xxA renderer for Plucker eBooksxx", "Description[zh_CN]": "Plucker 电子书渲染器", "Description[zh_TW]": "Plucker 電子書成像器", "Id": "okular_plucker", "License": "GPL", "MimeTypes": [ "application/prs.plucker" ], "Name": "Plucker Document Backend", "Name[ca@valencia]": "Dorsal per a documents Plucker", "Name[ca]": "Dorsal per a documents Plucker", "Name[cs]": "Podpůrná vrstva Plucker Dokumentu", "Name[de]": "Anzeigemodul für Plucker-Dokumente", "Name[el]": "Σύστημα υποστήριξης εγγράφων Plucker", "Name[es]": "Motor para documento de Plucker", "Name[et]": "Pluckeri dokumendi taustaprogramm", "Name[fi]": "Plucker-tiedostotaustaosa", "Name[fr]": "Moteur de document Plucker", + "Name[gl]": "Infraestrutura para documentos Plucker", "Name[ia]": "Un retro-administration de documento Plucker", "Name[it]": "Backend per documenti Plucker", "Name[ko]": "Plucker 문서 백엔드", "Name[nl]": "Plucker Document-backend", "Name[nn]": "Plucker-dokumentmotor", "Name[pl]": "Obsługa dokumentów Plucker", "Name[pt]": "Infra-Estrutura de Documentos do Plucker", "Name[ru]": "Модуль поддержки формата Plucker", "Name[sk]": "Backend dokumentov Plucker", "Name[sl]": "Zaledje za dokumente Plucker", "Name[sr@ijekavian]": "Позадина за Плакерови документе", "Name[sr@ijekavianlatin]": "Pozadina za Pluckerovi dokumente", "Name[sr@latin]": "Pozadina za Pluckerovi dokumente", "Name[sr]": "Позадина за Плакерови документе", "Name[sv]": "Plucker-dokumentgränssnitt", "Name[tr]": "Plucker Belgesi Arka Ucu", "Name[uk]": "Модуль документів Plucker", "Name[x-test]": "xxPlucker Document Backendxx", "Name[zh_CN]": "Plucker 文档后端", "Name[zh_TW]": "Pluker 文件後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.1" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/plucker/org.kde.okular-plucker.metainfo.xml b/generators/plucker/org.kde.okular-plucker.metainfo.xml index f4a4be388..28df5dede 100644 --- a/generators/plucker/org.kde.okular-plucker.metainfo.xml +++ b/generators/plucker/org.kde.okular-plucker.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-plucker org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Plucker Plucker Plucker Plucker Plucker Plucker Plucker Plucker Plucker Plucker Plucker Plucker + Plucker Plucker plucker Plucker Plucker Plucker Plucker Plucker Plucker Плакер Plucker Плакер Plucker Plucker Plucker Plucker xxPluckerxx Plucker Plucker Adds support for reading Plucker documents Amiesta sofitu pa la llectura de documentos Plucker Afegeix la implementació per llegir documents Plucker Afig la implementació per llegir documents Plucker Přidává podporu pro čtení dokumentů Plucker Bietet Unterstützung zum Lesen von Plucker-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων Plucker Adds support for reading Plucker documents Permite la lectura de documentos en formato Plucker Pluckeri dokumentide lugemise toetus Lisää Plucker-tiedostojen lukutuen Permet la lecture des documents Plucker + Permite ler documentos de Plucker. Aggiunge il supporto per la lettura di documenti Plucker Plucker 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van Plucker-documenten toe Legg til støtte for å lesa Plucker-dokument Dodaje obsługę czytania dokumnetów Plucker Adiciona o suporte para ler documentos do Plucker Pridá podporu pre čítanie dokumentov Plucker Doda podporo za branje dokumentov Plucker Подршка за читање плакер докумената Podrška za čitanje Plucker dokumenata Подршка за читање плакер докумената Podrška za čitanje Plucker dokumenata Lägger till stöd för att läsa Plucker-dokument Plucker belgelerini okuma desteği ekler Додає підтримку читання документів Plucker xxAdds support for reading Plucker documentsxx 增加对 Plucker 文档的阅读支持 加入讀取 Plucker 文件的支援 application/prs.plucker https://okular.kde.org diff --git a/generators/plucker/unpluck/image.cpp b/generators/plucker/unpluck/image.cpp index 36c4729d3..9179c0e44 100644 --- a/generators/plucker/unpluck/image.cpp +++ b/generators/plucker/unpluck/image.cpp @@ -1,549 +1,549 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * Based on code written by Bill Janssen 2002 * * * * 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. * ***************************************************************************/ #include #include #include #include /* This code requires the Independent JPEG Group libjpeg library, version 6b or later */ extern "C" { #include "jpeglib.h" } #include "unpluck.h" #include "image.h" #define GET_FUNCTION_CODE_TYPE(x) (((x)>>3) & 0x1F) #define GET_FUNCTION_CODE_DATALEN(x) ((x) & 0x7) #define CELLS(row,col) cells[row*cols+col] /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** Code to decode the Palm image format to JPEG *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ #define READ_BIGENDIAN_SHORT(p) (((p)[0] << 8)|((p)[1])) #define READ_BIGENDIAN_LONG(p) (((p)[0] << 24)|((p)[1] << 16)|((p)[2] << 8)|((p)[3])) #define PALM_IS_COMPRESSED_FLAG 0x8000 #define PALM_HAS_COLORMAP_FLAG 0x4000 #define PALM_HAS_TRANSPARENCY_FLAG 0x2000 #define PALM_DIRECT_COLOR_FLAG 0x0400 #define PALM_4_BYTE_FIELD_FLAG 0x0200 #define PALM_COMPRESSION_SCANLINE 0x00 #define PALM_COMPRESSION_RLE 0x01 #define PALM_COMPRESSION_PACKBITS 0x02 #define PALM_COMPRESSION_NONE 0xFF #define PALM_COLORMAP_SIZE 232 typedef struct { unsigned char red; unsigned char green; unsigned char blue; } ColorMapEntry; static ColorMapEntry Palm8BitColormap[] = { {255, 255, 255}, {255, 204, 255}, {255, 153, 255}, {255, 102, 255}, {255, 51, 255}, {255, 0, 255}, {255, 255, 204}, {255, 204, 204}, {255, 153, 204}, {255, 102, 204}, {255, 51, 204}, {255, 0, 204}, {255, 255, 153}, {255, 204, 153}, {255, 153, 153}, {255, 102, 153}, {255, 51, 153}, {255, 0, 153}, {204, 255, 255}, {204, 204, 255}, {204, 153, 255}, {204, 102, 255}, {204, 51, 255}, {204, 0, 255}, {204, 255, 204}, {204, 204, 204}, {204, 153, 204}, {204, 102, 204}, {204, 51, 204}, {204, 0, 204}, {204, 255, 153}, {204, 204, 153}, {204, 153, 153}, {204, 102, 153}, {204, 51, 153}, {204, 0, 153}, {153, 255, 255}, {153, 204, 255}, {153, 153, 255}, {153, 102, 255}, {153, 51, 255}, {153, 0, 255}, {153, 255, 204}, {153, 204, 204}, {153, 153, 204}, {153, 102, 204}, {153, 51, 204}, {153, 0, 204}, {153, 255, 153}, {153, 204, 153}, {153, 153, 153}, {153, 102, 153}, {153, 51, 153}, {153, 0, 153}, {102, 255, 255}, {102, 204, 255}, {102, 153, 255}, {102, 102, 255}, {102, 51, 255}, {102, 0, 255}, {102, 255, 204}, {102, 204, 204}, {102, 153, 204}, {102, 102, 204}, {102, 51, 204}, {102, 0, 204}, {102, 255, 153}, {102, 204, 153}, {102, 153, 153}, {102, 102, 153}, {102, 51, 153}, {102, 0, 153}, { 51, 255, 255}, { 51, 204, 255}, { 51, 153, 255}, { 51, 102, 255}, { 51, 51, 255}, { 51, 0, 255}, { 51, 255, 204}, { 51, 204, 204}, { 51, 153, 204}, { 51, 102, 204}, { 51, 51, 204}, { 51, 0, 204}, { 51, 255, 153}, { 51, 204, 153}, { 51, 153, 153}, { 51, 102, 153}, { 51, 51, 153}, { 51, 0, 153}, { 0, 255, 255}, { 0, 204, 255}, { 0, 153, 255}, { 0, 102, 255}, { 0, 51, 255}, { 0, 0, 255}, { 0, 255, 204}, { 0, 204, 204}, { 0, 153, 204}, { 0, 102, 204}, { 0, 51, 204}, { 0, 0, 204}, { 0, 255, 153}, { 0, 204, 153}, { 0, 153, 153}, { 0, 102, 153}, { 0, 51, 153}, { 0, 0, 153}, {255, 255, 102}, {255, 204, 102}, {255, 153, 102}, {255, 102, 102}, {255, 51, 102}, {255, 0, 102}, {255, 255, 51}, {255, 204, 51}, {255, 153, 51}, {255, 102, 51}, {255, 51, 51}, {255, 0, 51}, {255, 255, 0}, {255, 204, 0}, {255, 153, 0}, {255, 102, 0}, {255, 51, 0}, {255, 0, 0}, {204, 255, 102}, {204, 204, 102}, {204, 153, 102}, {204, 102, 102}, {204, 51, 102}, {204, 0, 102}, {204, 255, 51}, {204, 204, 51}, {204, 153, 51}, {204, 102, 51}, {204, 51, 51}, {204, 0, 51}, {204, 255, 0}, {204, 204, 0}, {204, 153, 0}, {204, 102, 0}, {204, 51, 0}, {204, 0, 0}, {153, 255, 102}, {153, 204, 102}, {153, 153, 102}, {153, 102, 102}, {153, 51, 102}, {153, 0, 102}, {153, 255, 51}, {153, 204, 51}, {153, 153, 51}, {153, 102, 51}, {153, 51, 51}, {153, 0, 51}, {153, 255, 0}, {153, 204, 0}, {153, 153, 0}, {153, 102, 0}, {153, 51, 0}, {153, 0, 0}, {102, 255, 102}, {102, 204, 102}, {102, 153, 102}, {102, 102, 102}, {102, 51, 102}, {102, 0, 102}, {102, 255, 51}, {102, 204, 51}, {102, 153, 51}, {102, 102, 51}, {102, 51, 51}, {102, 0, 51}, {102, 255, 0}, {102, 204, 0}, {102, 153, 0}, {102, 102, 0}, {102, 51, 0}, {102, 0, 0}, { 51, 255, 102}, { 51, 204, 102}, { 51, 153, 102}, { 51, 102, 102}, { 51, 51, 102}, { 51, 0, 102}, { 51, 255, 51}, { 51, 204, 51}, { 51, 153, 51}, { 51, 102, 51}, { 51, 51, 51}, { 51, 0, 51}, { 51, 255, 0}, { 51, 204, 0}, { 51, 153, 0}, { 51, 102, 0}, { 51, 51, 0}, { 51, 0, 0}, { 0, 255, 102}, { 0, 204, 102}, { 0, 153, 102}, { 0, 102, 102}, { 0, 51, 102}, { 0, 0, 102}, { 0, 255, 51}, { 0, 204, 51}, { 0, 153, 51}, { 0, 102, 51}, { 0, 51, 51}, { 0, 0, 51}, { 0, 255, 0}, { 0, 204, 0}, { 0, 153, 0}, { 0, 102, 0}, { 0, 51, 0}, { 17, 17, 17}, { 34, 34, 34}, { 68, 68, 68}, { 85, 85, 85}, {119, 119, 119}, {136, 136, 136}, {170, 170, 170}, {187, 187, 187}, {221, 221, 221}, {238, 238, 238}, {192, 192, 192}, {128, 0, 0}, {128, 0, 128}, { 0, 128, 0}, { 0, 128, 128}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0}, { 0, 0, 0} }; static ColorMapEntry Palm1BitColormap[] = { {255, 255, 255}, { 0, 0, 0} }; static ColorMapEntry Palm2BitColormap[] = { {255, 255, 255}, {192, 192, 192}, {128, 128, 128}, { 0, 0, 0} }; static ColorMapEntry Palm4BitColormap[] = { {255, 255, 255}, {238, 238, 238}, {221, 221, 221}, {204, 204, 204}, {187, 187, 187}, {170, 170, 170}, {153, 153, 153}, {136, 136, 136}, {119, 119, 119}, {102, 102, 102}, { 85, 85, 85}, { 68, 68, 68}, { 51, 51, 51}, { 34, 34, 34}, { 17, 17, 17}, { 0, 0, 0} }; bool TranscribePalmImageToJPEG ( unsigned char *image_bytes_in, QImage &image ) { unsigned int width; unsigned int height; unsigned int bytes_per_row; unsigned int flags; - unsigned int next_depth_offset; +// unsigned int next_depth_offset; unsigned int bits_per_pixel; - unsigned int version; - unsigned int transparent_index; +// unsigned int version; +// unsigned int transparent_index; unsigned int compression_type; unsigned int i; unsigned int j; unsigned int inval; unsigned int inbit; unsigned int mask; unsigned int incount; unsigned int palm_red_bits = 0; unsigned int palm_green_bits = 0; unsigned int palm_blue_bits = 0; unsigned char* palm_ptr; - unsigned char* x_ptr; - unsigned char* imagedata = 0; +// unsigned char* x_ptr; +// unsigned char* imagedata = 0; unsigned char* inbyte; unsigned char* rowbuf; unsigned char* lastrow; unsigned char* imagedatastart; unsigned char* palmimage; ColorMapEntry *colormap; JSAMPLE* jpeg_row; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; JSAMPROW row_pointer[1];/* pointer to JSAMPLE row[s] */ palmimage = image_bytes_in; width = READ_BIGENDIAN_SHORT (palmimage + 0); height = READ_BIGENDIAN_SHORT (palmimage + 2); bytes_per_row = READ_BIGENDIAN_SHORT (palmimage + 4); flags = READ_BIGENDIAN_SHORT (palmimage + 6); bits_per_pixel = palmimage[8]; - version = palmimage[9]; - next_depth_offset = READ_BIGENDIAN_SHORT (palmimage + 10); - transparent_index = palmimage[12]; +// version = palmimage[9]; +// next_depth_offset = READ_BIGENDIAN_SHORT (palmimage + 10); +// transparent_index = palmimage[12]; compression_type = palmimage[13]; /* bytes 14 and 15 are reserved by Palm and always 0 */ if (compression_type == PALM_COMPRESSION_PACKBITS) { return false; } else if ((compression_type != PALM_COMPRESSION_NONE) && (compression_type != PALM_COMPRESSION_RLE) && (compression_type != PALM_COMPRESSION_SCANLINE)) { return false; } /* as of PalmOS 4.0, there are 6 different kinds of Palm pixmaps: 1, 2, or 4 bit grayscale 8-bit StaticColor using the Palm standard colormap 8-bit PseudoColor using a user-specified colormap 16-bit DirectColor using 5 bits for red, 6 for green, and 5 for blue Each of these can be compressed with one of four compression schemes, "RLE", "Scanline", "PackBits", or none. We begin by constructing the colormap. */ if (flags & PALM_HAS_COLORMAP_FLAG) { return false; } else if (bits_per_pixel == 1) { colormap = Palm1BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 2) { colormap = Palm2BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 4) { colormap = Palm4BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 8) { colormap = Palm8BitColormap; imagedatastart = palmimage + 16; } else if (bits_per_pixel == 16 && (flags & PALM_DIRECT_COLOR_FLAG)) { colormap = NULL; palm_red_bits = palmimage[16]; palm_green_bits = palmimage[17]; palm_blue_bits = palmimage[18]; if (palm_blue_bits > 8 || palm_green_bits > 8 || palm_red_bits > 8) { return false; } if (bits_per_pixel > (8 * sizeof (unsigned long))) { return false; } imagedatastart = palmimage + 24; } else { return false; } QTemporaryFile tempFile; tempFile.open(); FILE *outfile = fopen( QFile::encodeName( tempFile.fileName() ).constData(), "w" ); if ( !outfile ) return false; /* now create the JPEG image row buffer */ jpeg_row = (JSAMPLE *) malloc (sizeof (JSAMPLE) * (width * 3)); /* Use standard JPEG error processing */ cinfo.err = jpeg_std_error (&jerr); /* Initialize the JPEG compression object. */ jpeg_create_compress (&cinfo); jpeg_stdio_dest (&cinfo, outfile); cinfo.image_width = width; /* image width and height, in pixels */ cinfo.image_height = height; cinfo.input_components = 3; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ jpeg_set_defaults (&cinfo); jpeg_set_quality (&cinfo, 100, true /* limit to baseline-JPEG values */ ); row_pointer[0] = &jpeg_row[0]; jpeg_start_compress (&cinfo, true); /* row by row, uncompress the Palm image and copy it to the JPEG buffer */ rowbuf = (unsigned char *) malloc (bytes_per_row * width); lastrow = (unsigned char *) malloc (bytes_per_row * width); - for (i = 0, palm_ptr = imagedatastart, x_ptr = imagedata; i < height; + for (i = 0, palm_ptr = imagedatastart/*, x_ptr = imagedata*/; i < height; ++i) { /* first, uncompress the Palm image */ if ((flags & PALM_IS_COMPRESSED_FLAG) && (compression_type == PALM_COMPRESSION_RLE)) { for (j = 0; j < bytes_per_row;) { incount = *palm_ptr++; inval = *palm_ptr++; if (incount + j <= bytes_per_row * width) { memset (rowbuf + j, inval, incount); j += incount; } else { free (rowbuf); free (lastrow); free (jpeg_row); jpeg_destroy_compress (&cinfo); fclose( outfile ); return false; } } } else if ((flags & PALM_IS_COMPRESSED_FLAG) && (compression_type == PALM_COMPRESSION_SCANLINE)) { for (j = 0; j < bytes_per_row; j += 8) { incount = *palm_ptr++; inval = ((bytes_per_row - j) < 8) ? (bytes_per_row - j) : 8; for (inbit = 0; inbit < inval; inbit += 1) { if (incount & (1 << (7 - inbit))) rowbuf[j + inbit] = *palm_ptr++; else rowbuf[j + inbit] = lastrow[j + inbit]; } } memcpy (lastrow, rowbuf, bytes_per_row); } else if (((flags & PALM_IS_COMPRESSED_FLAG) && (compression_type == PALM_COMPRESSION_NONE)) || (flags & PALM_IS_COMPRESSED_FLAG) == 0) { memcpy (rowbuf, palm_ptr, bytes_per_row); palm_ptr += bytes_per_row; } /* next, write it to the GDK bitmap */ if (colormap) { mask = (1 << bits_per_pixel) - 1; for (inbit = 8 - bits_per_pixel, inbyte = rowbuf, j = 0; j < width; ++j) { inval = ((*inbyte) & (mask << inbit)) >> inbit; /* correct for oddity of the 8-bit color Palm pixmap... */ if ((bits_per_pixel == 8) && (inval == 0xFF)) inval = 231; /* now lookup the correct color and set the pixel in the GTK bitmap */ jpeg_row[(j * 3) + 0] = colormap[inval].red; jpeg_row[(j * 3) + 1] = colormap[inval].green; jpeg_row[(j * 3) + 2] = colormap[inval].blue; if (!inbit) { ++inbyte; inbit = 8 - bits_per_pixel; } else { inbit -= bits_per_pixel; } } } else if (!colormap && bits_per_pixel == 16) { for (inbyte = rowbuf, j = 0; j < width; ++j) { inval = (inbyte[0] << 8) | inbyte[1]; jpeg_row[(j * 3) + 0] = (inval >> (bits_per_pixel - palm_red_bits)) & ((1 << palm_red_bits) - 1); jpeg_row[(j * 3) + 1] = (inval >> palm_blue_bits) & ((1 << palm_green_bits) - 1); jpeg_row[(j * 3) + 2] = (inval >> 0) & ((1 << palm_blue_bits) - 1); inbyte += 2; } } (void) jpeg_write_scanlines (&cinfo, row_pointer, 1); } free (rowbuf); free (lastrow); free (jpeg_row); jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); fclose( outfile ); return image.load( tempFile.fileName() ); } typedef struct { unsigned int width; unsigned int height; unsigned int bytes_per_row; unsigned int flags; unsigned int next_depth_offset; unsigned int bits_per_pixel; unsigned int version; unsigned int transparent_index; unsigned int compression_type; unsigned int palm_red_bits; unsigned int palm_green_bits; unsigned int palm_blue_bits; unsigned char* bytes; } PALMPIX; bool TranscribeMultiImageRecord ( plkr_Document* doc, QImage &image, unsigned char* bytes ) { unsigned char* pbytes = 0; unsigned char* outbytes = 0; unsigned char* outptr = 0; unsigned char* ptr = &bytes[12]; plkr_DataRecordType ptype; PALMPIX* cells = 0; PALMPIX* acell = 0; unsigned int record_id = 0; int plen = 0; unsigned int x = 0; unsigned int y = 0; unsigned int cols = 0; unsigned int rows = 0; unsigned int width = 0; unsigned int height = 0; unsigned int bytes_per_row = 0; unsigned int flags = 0; unsigned int bits_per_pixel = 0; unsigned int version = 0; unsigned int transparent_index = 0; unsigned int compression_type = 0; unsigned int palm_red_bits = 0; unsigned int palm_green_bits = 0; unsigned int palm_blue_bits = 0; unsigned int outlen = 0; unsigned int offset = 0; bool status = true; cols = (bytes[8] << 8) + bytes[9]; rows = (bytes[10] << 8) + bytes[11]; cells = (PALMPIX *) calloc (cols * rows, sizeof (PALMPIX)); height = 0; for (y = 0; y < rows; y++) { width = 0; bytes_per_row = 0; for (x = 0; x < cols; x++) { acell = &CELLS (y, x); record_id = (ptr[0] << 8) + ptr[1]; ptr += 2; pbytes = plkr_GetRecordBytes (doc, record_id, &plen, &ptype); if (pbytes == NULL) { free (cells); return false; } pbytes += 8; acell->width = READ_BIGENDIAN_SHORT (&pbytes[0]); width += acell->width; acell->height = READ_BIGENDIAN_SHORT (&pbytes[2]); acell->bytes_per_row = READ_BIGENDIAN_SHORT (&pbytes[4]); bytes_per_row += acell->bytes_per_row; acell->flags = READ_BIGENDIAN_SHORT (&pbytes[6]); flags = acell->flags; acell->bits_per_pixel = pbytes[8]; bits_per_pixel = acell->bits_per_pixel; acell->version = pbytes[9]; version = acell->version; acell->next_depth_offset = READ_BIGENDIAN_SHORT (&pbytes[10]); acell->transparent_index = pbytes[12]; transparent_index = acell->transparent_index; acell->compression_type = pbytes[13]; compression_type = acell->compression_type; if (acell->flags & PALM_HAS_COLORMAP_FLAG) { free (cells); return false; } acell->bytes = pbytes + 16; offset = 16; if (acell->bits_per_pixel == 16 && (acell->flags & PALM_DIRECT_COLOR_FLAG)) { acell->palm_red_bits = pbytes[16]; palm_red_bits = acell->palm_red_bits; acell->palm_green_bits = pbytes[17]; palm_green_bits = acell->palm_green_bits; acell->palm_blue_bits = pbytes[18]; palm_blue_bits = acell->palm_blue_bits; acell->bytes = pbytes + 24; offset = 24; } } height += acell->height; } outlen = bytes_per_row * height + offset; outbytes = (unsigned char *) malloc (outlen); outptr = outbytes; *outptr++ = width >> 8; *outptr++ = width; *outptr++ = height >> 8; *outptr++ = height; *outptr++ = bytes_per_row >> 8; *outptr++ = bytes_per_row; *outptr++ = flags >> 8; *outptr++ = flags; *outptr++ = bits_per_pixel; *outptr++ = version; *outptr++ = 0; /* next_depth_offset */ *outptr++ = 0; *outptr++ = transparent_index; *outptr++ = compression_type; *outptr++ = 0; *outptr++ = 0; if (acell->bits_per_pixel == 16 && (acell->flags & PALM_DIRECT_COLOR_FLAG)) { *outptr++ = palm_red_bits; *outptr++ = palm_green_bits; *outptr++ = palm_blue_bits; *outptr++ = 0; *outptr++ = 0; *outptr++ = 0; *outptr++ = 0; *outptr++ = 0; } for (y = 0; y < rows; y++) { int i, h; acell = &CELLS (y, 0); h = acell->height; for (i = 0; i < h; i++) { for (x = 0; x < cols; x++) { acell = &CELLS (y, x); memcpy (outptr, acell->bytes, acell->bytes_per_row); acell->bytes += acell->bytes_per_row; outptr += acell->bytes_per_row; } } } status = TranscribePalmImageToJPEG (outbytes, image); free (outbytes); free (cells); return status; } diff --git a/generators/plucker/unpluck/qunpluck.cpp b/generators/plucker/unpluck/qunpluck.cpp index 6416a6863..59ad74d73 100644 --- a/generators/plucker/unpluck/qunpluck.cpp +++ b/generators/plucker/unpluck/qunpluck.cpp @@ -1,1208 +1,1208 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * Based on code written by Bill Janssen 2002 * * * * 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. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qunpluck.h" #include "image.h" #define GET_FUNCTION_CODE_TYPE(x) (((x)>>3) & 0x1F) #define GET_FUNCTION_CODE_DATALEN(x) ((x) & 0x7) #define CELLS(row,col) cells[row*cols+col] #define READ_BIGENDIAN_SHORT(p) (((p)[0] << 8)|((p)[1])) #define READ_BIGENDIAN_LONG(p) (((p)[0] << 24)|((p)[1] << 16)|((p)[2] << 8)|((p)[3])) /* static void LinkRecords ( char* dir ) { RecordNode* ptr; char* realfilename; char* linkname; realfilename = (char*)malloc (strlen (dir) + 20); linkname = (char*)malloc (strlen (dir) + 20); for (ptr = records; ptr != NULL; ptr = ptr->next) { if (ptr->page_id != ptr->index) { sprintf (realfilename, "%s/r%d.html", dir, ptr->page_id); sprintf (linkname, "%s/r%d.html", dir, ptr->index); link (realfilename, linkname); } } free (realfilename); free (linkname); } */ class Context { public: int recordId; QTextDocument *document; QTextCursor *cursor; QStack stack; QList images; QString linkUrl; int linkStart; int linkPage; }; class RecordNode { public: int index; int page_id; bool done; }; static Okular::DocumentViewport calculateViewport( QTextDocument *document, const QTextBlock &block ) { if ( !block.isValid() ) return Okular::DocumentViewport(); const QRectF rect = document->documentLayout()->blockBoundingRect( block ); const QSizeF size = document->size(); int page = qRound( rect.y() ) / qRound( size.height() ); Okular::DocumentViewport viewport( page ); viewport.rePos.normalizedX = (double)rect.x() / (double)size.width(); viewport.rePos.normalizedY = (double)rect.y() / (double)size.height(); viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::Center; return viewport; } QUnpluck::QUnpluck() : mDocument( 0 ) { } QUnpluck::~QUnpluck() { mLinks.clear(); mNamedTargets.clear(); mPages.clear(); } bool QUnpluck::open( const QString &fileName ) { mLinks.clear(); mNamedTargets.clear(); mPages.clear(); mDocument = plkr_OpenDBFile( QFile::encodeName( fileName ).data() ); if ( !mDocument ) { mErrorString = QObject::tr( "Unable to open document" ); return false; } - bool status = true; +// bool status = true; mInfo.insert( QStringLiteral("name"), QString::fromLocal8Bit(plkr_GetName( mDocument ) )); mInfo.insert( QStringLiteral("title"), QString::fromLocal8Bit(plkr_GetTitle( mDocument ) )); mInfo.insert( QStringLiteral("author"), QString::fromLocal8Bit(plkr_GetAuthor( mDocument ) )); mInfo.insert( QStringLiteral("time"), QDateTime::fromTime_t( plkr_GetPublicationTime( mDocument ) ).toString() ); AddRecord( plkr_GetHomeRecordID( mDocument ) ); int number = GetNextRecordNumber(); while ( number > 0 ) { - status = TranscribeRecord( number ); + /*status = */TranscribeRecord( number ); number = GetNextRecordNumber (); } // Iterate over all records again to add those which aren't linked directly for ( int i = 1; i < plkr_GetRecordCount( mDocument ); ++i ) AddRecord( plkr_GetUidForIndex( mDocument, i ) ); number = GetNextRecordNumber(); while ( number > 0 ) { - status = TranscribeRecord( number ); + /*status = */TranscribeRecord( number ); number = GetNextRecordNumber (); } for ( int i = 0; i < mRecords.count(); ++i ) delete mRecords[ i ]; mRecords.clear(); plkr_CloseDoc( mDocument ); /** * Calculate hash map */ QHash pageHash; for ( int i = 0; i < mContext.count(); ++i ) pageHash.insert( mContext[ i ]->recordId, i ); /** * Convert ids */ for ( int i = 0; i < mContext.count(); ++i ) { Context *context = mContext[ i ]; for ( int j = 0; j < context->images.count(); ++j ) { int imgNumber = context->images[ j ]; context->document->addResource( QTextDocument::ImageResource, QUrl( QStringLiteral( "%1.jpg" ).arg( imgNumber ) ), mImages[ imgNumber ] ); } mPages.append( context->document ); } qDeleteAll( mContext ); mContext.clear(); // convert record_id into page for ( int i = 0; i < mLinks.count(); ++i ) { mLinks[ i ].page = pageHash[ mLinks[ i ].page ]; if ( mLinks[ i ].url.startsWith( QLatin1String("page:") ) ) { int page = mLinks[ i ].url.midRef( 5 ).toInt(); Okular::DocumentViewport viewport( pageHash[ page ] ); viewport.rePos.normalizedX = 0; viewport.rePos.normalizedY = 0; viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::TopLeft; mLinks[ i ].link = new Okular::GotoAction( QString(), viewport ); } else if ( mLinks[ i ].url.startsWith( QLatin1String("para:") ) ) { QPair data = mNamedTargets[ mLinks[ i ].url ]; QTextDocument *document = mPages[ mLinks[ i ].page ]; Okular::DocumentViewport viewport = calculateViewport( document, data.second ); mLinks[ i ].link = new Okular::GotoAction( QString(), viewport ); } else { mLinks[ i ].link = new Okular::BrowseAction( QUrl(mLinks[ i ].url) ); } } return true; } int QUnpluck::GetNextRecordNumber() { int index = 0; for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( !mRecords[ pos ]->done ) { index = mRecords[ pos ]->index; break; } } return index; } int QUnpluck::GetPageID( int index ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { return mRecords[ pos ]->page_id; } } return 0; } void QUnpluck::AddRecord( int index ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { return; } } RecordNode *node = new RecordNode; node->done = false; node->index = index; node->page_id = index; mRecords.append( node ); } void QUnpluck::MarkRecordDone( int index ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { mRecords[ pos ]->done = true; return; } } AddRecord( index ); MarkRecordDone( index ); } void QUnpluck::SetPageID( int index, int page_id ) { for ( int pos = 0; pos < mRecords.count(); ++pos ) { if ( mRecords[ pos ]->index == index ) { mRecords[ pos ]->page_id = page_id; return; } } AddRecord( index ); SetPageID( index, page_id ); } QString QUnpluck::MailtoURLFromBytes( unsigned char* record_data ) { unsigned char* bytes = record_data + 8; int to_offset = (bytes[0] << 8) + bytes[1]; int cc_offset = (bytes[2] << 8) + bytes[3]; int subject_offset = (bytes[4] << 8) + bytes[5]; int body_offset = (bytes[6] << 8) + bytes[7]; QString url( QStringLiteral("mailto:") ); if ( to_offset != 0 ) url += QString::fromLatin1( (char *)(bytes + to_offset) ); if ( (cc_offset != 0) || (subject_offset != 0) || (body_offset != 0) ) url += QLatin1String( "?" ); if ( cc_offset != 0 ) url += QLatin1String( "cc=" ) + QString::fromLatin1( (char *)(bytes + cc_offset) ); if ( subject_offset != 0 ) url += QLatin1String( "subject=" ) + QString::fromLatin1( (char *)(bytes + subject_offset) ); if ( body_offset != 0 ) url += QLatin1String( "body=" ) + QString::fromLatin1( (char *)(bytes + body_offset) ); return url; } QImage QUnpluck::TranscribeImageRecord( unsigned char* bytes ) { QImage image; TranscribePalmImageToJPEG( bytes + 8, image ); return image; } void QUnpluck::DoStyle( Context* context, int style, bool start ) { if ( start ) { QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); int pointSize = qRound( format.fontPointSize() ); switch (style) { case 1: format.setFontWeight( QFont::Bold ); pointSize += 3; break; case 2: format.setFontWeight( QFont::Bold ); pointSize += 2; break; case 3: format.setFontWeight( QFont::Bold ); pointSize += 1; break; case 4: format.setFontWeight( QFont::Bold ); break; case 5: format.setFontWeight( QFont::Bold ); pointSize += -1; break; case 6: format.setFontWeight( QFont::Bold ); pointSize += -2; break; case 7: format.setFontWeight( QFont::Bold ); break; case 8: format.setFontFamily( QStringLiteral( "Courier New,courier" ) ); break; } format.setFontPointSize( qMax( pointSize, 1 ) ); context->cursor->setCharFormat( format ); } else { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); } } void QUnpluck::ParseText ( plkr_Document* doc, unsigned char* ptr, int text_len, int* font, int* style, Context* context ) { unsigned char* end; int fctype; int fclen; end = ptr + text_len; while (ptr < end) { if (ptr[0]) { context->cursor->insertText( QString::fromLocal8Bit( (char*)ptr ) ); ptr += strlen ((char*)ptr); } else { fctype = GET_FUNCTION_CODE_TYPE (ptr[1]); fclen = 2 + GET_FUNCTION_CODE_DATALEN (ptr[1]); switch (fctype) { case PLKR_TFC_LINK: switch (fclen) { case 4: /* ANCHOR_BEGIN */ { int record_id = (ptr[2] << 8) + ptr[3]; /** TODO: plkr_DataRecordType type = (plkr_DataRecordType)plkr_GetRecordType (doc, record_id); if (type == PLKR_DRTYPE_IMAGE || type == PLKR_DRTYPE_IMAGE_COMPRESSED) output += QString( "" ).arg(record_id); else output += QString( "" ).arg(record_id); */ AddRecord (record_id); } break; case 2: /* ANCHOR_END */ //TODO: output += QString( "" ); break; } ptr += fclen; break; case PLKR_TFC_FONT: DoStyle (context, *style, false); *style = ptr[2]; DoStyle (context, *style, true); ptr += fclen; break; case PLKR_TFC_NEWLINE: { // TODO: remove the setCharFormat when Qt is fixed QTextCharFormat format( context->cursor->charFormat() ); context->cursor->insertText( QStringLiteral("\n") ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_BITALIC: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( true ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_EITALIC: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( false ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_COLOR: if (*font) { (*font)--; if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); } { QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( QColor((ptr[2] << 16), (ptr[3] << 8), ptr[4]) ); context->cursor->setCharFormat( format ); } (*font)++; ptr += fclen; break; case PLKR_TFC_BULINE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( true ); context->cursor->setCharFormat( format ); ptr += fclen; } break; case PLKR_TFC_EULINE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( false ); context->cursor->setCharFormat( format ); ptr += fclen; } break; case PLKR_TFC_BSTRIKE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( true ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_ESTRIKE: { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( false ); context->cursor->setCharFormat( format ); ptr += fclen; break; } case PLKR_TFC_TABLE: if (fclen == 4) { int record_id, datalen; plkr_DataRecordType type = (plkr_DataRecordType)0; unsigned char *bytes = NULL; record_id = (ptr[2] << 8) + ptr[3]; bytes = plkr_GetRecordBytes (doc, record_id, &datalen, &type); TranscribeTableRecord (doc, context, bytes); } ptr += fclen; break; default: ptr += fclen; } } } } bool QUnpluck::TranscribeTableRecord ( plkr_Document* doc, Context* context, unsigned char* bytes ) { unsigned char* ptr = &bytes[24]; unsigned char* end; // char* align_names[] = { "left", "right", "center" }; - bool in_row = false; - int cols; +// bool in_row = false; +// int cols; int size; - int rows; - int border; +// int rows; +// int border; int record_id; - int align; +// int align; int text_len; - int colspan; - int rowspan; +// int colspan; +// int rowspan; int font = 0; int style = 0; int fctype; int fclen; - long border_color; - long link_color; +// long border_color; +// long link_color; size = (bytes[8] << 8) + bytes[9]; - cols = (bytes[10] << 8) + bytes[11]; - rows = (bytes[12] << 8) + bytes[13]; - border = bytes[15]; - border_color = (bytes[17] << 16) + (bytes[18] << 8) + (bytes[19] << 8); - link_color = (bytes[21] << 16) + (bytes[22] << 8) + (bytes[23] << 8); +// cols = (bytes[10] << 8) + bytes[11]; +// rows = (bytes[12] << 8) + bytes[13]; +// border = bytes[15]; +// border_color = (bytes[17] << 16) + (bytes[18] << 8) + (bytes[19] << 8); +// link_color = (bytes[21] << 16) + (bytes[22] << 8) + (bytes[23] << 8); end = ptr + size - 1; /** output += QString( "\n" ).arg(border, border_color, link_color); */ while (ptr < end) { if (ptr[0] == '\0') { fctype = GET_FUNCTION_CODE_TYPE (ptr[1]); fclen = 2 + GET_FUNCTION_CODE_DATALEN (ptr[1]); switch (fctype) { case PLKR_TFC_TABLE: switch (fclen) { case 2: /* NEW_ROW */ /* if (in_row) output += QString( "\n" ); output += QString( "\n" ); - */ in_row = true; + */ ptr += fclen; break; case 9: /* NEW_CELL */ - align = ptr[2]; - colspan = ptr[5]; - rowspan = ptr[6]; +// align = ptr[2]; +// colspan = ptr[5]; +// rowspan = ptr[6]; /** output += QString( "\n" ); break; default: ptr += fclen; } break; default: ptr += fclen; } } else { //output += QString( "
" ).arg( align_names[align], colspan, rowspan ); // border_color); */ if ( (record_id = READ_BIGENDIAN_SHORT (&ptr[3])) ) { QTextCharFormat format = context->cursor->charFormat(); context->cursor->insertImage( QStringLiteral( "%1.jpg" ).arg(record_id) ); context->cursor->setCharFormat( format ); context->images.append( record_id ); AddRecord (record_id); } DoStyle (context, style, true); text_len = READ_BIGENDIAN_SHORT (&ptr[7]); ptr += fclen; ParseText (doc, ptr, text_len, &font, &style, context); ptr += text_len; DoStyle (context, style, false); //output += QString( "
\n" ); return false; } } // output += QString( "\n" ); return true; } typedef struct { int size; int attributes; } ParagraphInfo; static ParagraphInfo *ParseParagraphInfo ( unsigned char* bytes, int* nparas ) { ParagraphInfo* paragraph_info; int j; int n; n = (bytes[2] << 8) + bytes[3]; paragraph_info = (ParagraphInfo *) malloc (sizeof (ParagraphInfo) * n); for (j = 0; j < n; j++) { paragraph_info[j].size = (bytes[8 + (j * 4) + 0] << 8) + bytes[8 + (j * 4) + 1]; paragraph_info[j].attributes = (bytes[8 + (j * 4) + 2] << 8) + bytes[8 + (j * 4) + 3]; } *nparas = n; return paragraph_info; } bool QUnpluck::TranscribeTextRecord ( plkr_Document* doc, int id, Context *context, unsigned char* bytes, plkr_DataRecordType type ) { unsigned char* ptr; unsigned char* run; unsigned char* para_start; unsigned char* data; unsigned char* start; ParagraphInfo* paragraphs; bool first_record_of_page = true; bool current_link; bool current_italic; bool current_struckthrough; bool current_underline; int fctype; int fclen; int para_index; int para_len; int textlen; int data_len; int current_font; int record_index; - int current_alignment; - int current_left_margin; - int current_right_margin; +// int current_alignment; +// int current_left_margin; +// int current_right_margin; int nparagraphs; - long current_color; +// long current_color; record_index = id; paragraphs = ParseParagraphInfo (bytes, &nparagraphs); start = bytes + 8 + ((bytes[2] << 8) + bytes[3]) * 4; for (para_index = 0, ptr = start, run = start; para_index < nparagraphs; para_index++) { para_len = paragraphs[para_index].size; /* If the paragraph is the last in the record, and it consists of a link to the next record in the logical page, we trim off the paragraph and instead insert the whole page */ if (((para_index + 1) == nparagraphs) && (para_len == (sizeof ("Click here for the next part") + 5)) && (*ptr == 0) && (ptr[1] == ((PLKR_TFC_LINK << 3) + 2)) && (strcmp ((char*)(ptr + 4), "Click here for the next part") == 0)) { record_index = (ptr[2] << 8) + ptr[3]; if ((data = plkr_GetRecordBytes (doc, record_index, &data_len, &type)) == NULL) { // ShowWarning ("Can't open record %d!", record_index); free (paragraphs); return false; } else if (!(type == PLKR_DRTYPE_TEXT_COMPRESSED || type == PLKR_DRTYPE_TEXT)) { // ShowWarning ("Bad record type %d in record linked from end of record %d", type, id); free (paragraphs); return false; } first_record_of_page = false; para_index = 0; ptr = data + 8 + ((data[2] << 8) + data[3]) * 4; run = ptr; free (paragraphs); paragraphs = ParseParagraphInfo (data, &nparagraphs); para_len = paragraphs[para_index].size; MarkRecordDone (record_index); SetPageID (record_index, id); } if ((para_index == 0) && !first_record_of_page && (*ptr == 0) && (ptr[1] == ((PLKR_TFC_LINK << 3) + 2)) && (strcmp ((char*)(ptr + 4), "Click here for the previous part") == 0)) { /* throw away this inserted paragraph */ ptr += para_len; run = ptr; continue; } QTextCharFormat format( context->cursor->charFormat() ); QTextBlockFormat blockFormat( context->cursor->blockFormat() ); blockFormat.setAlignment( Qt::AlignLeft ); context->cursor->insertBlock( blockFormat ); context->cursor->setCharFormat( format ); mNamedTargets.insert( QStringLiteral( "para:%1-%2" ).arg( record_index ).arg( para_index ), QPair( GetPageID( record_index ), context->cursor->block() ) ); current_link = false; /* at the beginning of a paragraph, we start with a clean graphics context */ current_font = 0; - current_alignment = 0; - current_color = 0; +// current_alignment = 0; +// current_color = 0; current_italic = false; current_underline = false; current_struckthrough = false; - current_left_margin = 0; - current_right_margin = 0; +// current_left_margin = 0; +// current_right_margin = 0; for (para_start = ptr, textlen = 0; (ptr - para_start) < para_len;) { if (*ptr == 0) { /* function code */ if ((ptr - run) > 0) { /* write out any pending text */ context->cursor->insertText( QString::fromLatin1( (char*)run, ptr - run ) ); textlen += (ptr - run); } ptr++; fctype = GET_FUNCTION_CODE_TYPE (*ptr); fclen = GET_FUNCTION_CODE_DATALEN (*ptr); ptr++; if (fctype == PLKR_TFC_NEWLINE) { // TODO: remove the setCharFormat when Qt is fixed QTextCharFormat format( context->cursor->charFormat() ); context->cursor->insertText( QStringLiteral("\n") ); context->cursor->setCharFormat( format ); } else if (fctype == PLKR_TFC_LINK) { int record_id, real_record_id, datalen; plkr_DataRecordType type = (plkr_DataRecordType)0; unsigned char *bytes = NULL; char *url = NULL; if (fclen == 0) { if (current_link) { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); if ( !context->linkUrl.isEmpty() ) { Link link; link.url = context->linkUrl; link.start = context->linkStart; link.end = context->cursor->position(); link.page = GetPageID( id ); mLinks.append( link ); } } current_link = false; } else { record_id = (ptr[0] << 8) + ptr[1]; bytes = plkr_GetRecordBytes (doc, record_id, &datalen, &type); if (!bytes) { url = plkr_GetRecordURL (doc, record_id); } if (bytes && (type == PLKR_DRTYPE_MAILTO)) { context->linkUrl = MailtoURLFromBytes( bytes ); context->linkStart = context->cursor->position(); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else if (!bytes && url) { context->linkUrl = QString::fromLatin1( url ); context->linkStart = context->cursor->position(); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else if (bytes && (fclen == 2)) { AddRecord (record_id); real_record_id = GetPageID (record_id); if (type == PLKR_DRTYPE_IMAGE || type == PLKR_DRTYPE_IMAGE_COMPRESSED) { context->linkUrl = QStringLiteral( "%1.jpg" ).arg( record_id ); context->linkStart = context->cursor->position(); } else { context->linkUrl = QStringLiteral( "page:%1" ).arg( real_record_id ); context->linkStart = context->cursor->position(); } QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else if (bytes && (fclen == 4)) { AddRecord (record_id); context->linkUrl = QStringLiteral( "para:%1-%2" ).arg( record_id ).arg( (ptr[2] << 8) + ptr[3] ); context->linkStart = context->cursor->position(); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); format.setForeground( Qt::blue ); format.setUnderlineStyle( QTextCharFormat::SingleUnderline ); context->cursor->setCharFormat( format ); current_link = true; } else { // ShowWarning("odd link found: record_id=%d, bytes=0x%p, type=%d, url=%s", record_id, bytes, type, (url ? url : "0x0")); } } } else if (fctype == PLKR_TFC_FONT) { if (current_font != *ptr) { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); QTextCharFormat format( context->cursor->charFormat() ); context->stack.push( format ); int pointSize = qRound( format.fontPointSize() ); if (*ptr == 1) { format.setFontWeight( QFont::Bold ); pointSize += 3; } else if (*ptr == 2) { format.setFontWeight( QFont::Bold ); pointSize += 2; } else if (*ptr == 3) { format.setFontWeight( QFont::Bold ); pointSize += 1; } else if (*ptr == 4) { format.setFontWeight( QFont::Bold ); } else if (*ptr == 5) { format.setFontWeight( QFont::Bold ); pointSize += -1; } else if (*ptr == 6) { format.setFontWeight( QFont::Bold ); pointSize += -2; } else if (*ptr == 7) { format.setFontWeight( QFont::Bold ); } else if (*ptr == 8) { format.setFontFamily( QStringLiteral( "Courier New,courier" ) ); } else if (*ptr == 11) { format.setVerticalAlignment( QTextCharFormat::AlignSuperScript ); } format.setFontPointSize( qMax( pointSize, 1 ) ); context->cursor->setCharFormat( format ); current_font = *ptr; } } else if (fctype == PLKR_TFC_BITALIC) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( true ); context->cursor->setCharFormat( format ); current_italic = true; } else if (fctype == PLKR_TFC_EITALIC) { if (current_italic) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( false ); context->cursor->setCharFormat( format ); current_italic = false; } } else if (fctype == PLKR_TFC_BULINE) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( true ); context->cursor->setCharFormat( format ); current_underline = true; } else if (fctype == PLKR_TFC_EULINE) { if (current_underline) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( false ); context->cursor->setCharFormat( format ); current_underline = false; } } else if (fctype == PLKR_TFC_BSTRIKE) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( true ); context->cursor->setCharFormat( format ); current_struckthrough = true; } else if (fctype == PLKR_TFC_ESTRIKE) { if (current_struckthrough) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( false ); context->cursor->setCharFormat( format ); current_struckthrough = false; } } else if (fctype == PLKR_TFC_HRULE) { QTextCharFormat charFormat = context->cursor->charFormat(); QTextBlockFormat oldBlockFormat = context->cursor->blockFormat(); QTextBlockFormat blockFormat; blockFormat.setProperty( QTextFormat::BlockTrailingHorizontalRulerWidth, QStringLiteral("100%")); context->cursor->insertBlock( blockFormat ); context->cursor->insertBlock( oldBlockFormat ); context->cursor->setCharFormat( charFormat ); } else if (fctype == PLKR_TFC_ALIGN) { - current_alignment = 0; +// current_alignment = 0; if (*ptr < 4) { QTextBlockFormat format( context->cursor->blockFormat() ); if (*ptr == 0) format.setAlignment( Qt::AlignLeft ); else if (*ptr == 1) format.setAlignment( Qt::AlignRight ); else if (*ptr == 2) format.setAlignment( Qt::AlignCenter ); else if (*ptr == 3) format.setAlignment( Qt::AlignJustify ); QTextCharFormat charFormat( context->cursor->charFormat() ); context->cursor->insertBlock( format ); context->cursor->setCharFormat( charFormat ); - current_alignment = (*ptr) + 1; +// current_alignment = (*ptr) + 1; } } else if (fctype == PLKR_TFC_MARGINS) { /* Not easy to set, in HTML */ #if 0 output += QString( "" ).arg(ptr[0], ptr[1]); if (current_left_margin != ptr[0] || current_right_margin != ptr[1]) { if (current_right_margin != 0) fprintf (fp, " ", current_right_margin); fprintf (fp, "\n"); } current_left_margin = ptr[0]; current_right_margin = ptr[1]; if (current_right_margin > 0 || current_left_margin > 0) { fprintf (fp, ""); if (current_left_margin != 0) { fprintf (fp, ""); } fprintf (fp, "
", current_left_margin); if ((ptr - run) > 2) { fwrite (run, 1, ((ptr - 2) - run), fp); textlen += ((ptr - 2) - run); } else { fprintf (fp, " "); } fprintf (fp, ""); if (current_left_margin == 0 && (ptr - run) > 2) { fwrite (run, 1, ((ptr - 2) - run), fp); textlen += ((ptr - 2) - run); } } else { if ((ptr - run) > 2) { fwrite (run, 1, ((ptr - 2) - run), fp); textlen += ((ptr - 2) - run); } } #endif - current_left_margin = ptr[0]; - current_right_margin = ptr[1]; +// current_left_margin = ptr[0]; +// current_right_margin = ptr[1]; } else if (fctype == PLKR_TFC_COLOR) { /* not sure what to do here yet */ /* fprintf (fp, "", ptr[0], ptr[1], ptr[2]);*/ - current_color = - (ptr[0] << 16) + (ptr[1] << 8) + ptr[2]; +// current_color = +// (ptr[0] << 16) + (ptr[1] << 8) + ptr[2]; } else if (fctype == PLKR_TFC_IMAGE || fctype == PLKR_TFC_IMAGE2) { QTextCharFormat format = context->cursor->charFormat(); context->cursor->insertImage( QStringLiteral( "%1.jpg" ).arg( (ptr[0] << 8) + ptr[1] ) ); context->images.append( (ptr[0] << 8) + ptr[1] ); context->cursor->setCharFormat( format ); AddRecord ((ptr[0] << 8) + ptr[1]); } else if (fctype == PLKR_TFC_TABLE) { int record_id, datalen; plkr_DataRecordType type = (plkr_DataRecordType)0; unsigned char *bytes = NULL; record_id = (ptr[0] << 8) + ptr[1]; bytes = plkr_GetRecordBytes (doc, record_id, &datalen, &type); TranscribeTableRecord (doc, context, bytes); } else if (fctype == PLKR_TFC_UCHAR) { if (fclen == 3) context->cursor->insertText( QChar( (ptr[1] << 8) + ptr[2] ) ); else if (fclen == 5) context->cursor->insertText( QChar( (ptr[3] << 8) + ptr[4] ) ); /* skip over alternate text */ ptr += ptr[0]; } else { /* ignore function */ //output += QString( "" ).arg(fctype); } ptr += fclen; run = ptr; } else { ptr++; } } if ((ptr - run) > 0) { /* output any pending text at the end of the paragraph */ context->cursor->insertText( QString::fromLatin1( (char *)run, ptr - run ) ); textlen += (ptr - run); run = ptr; } /* clear the graphics state again */ if (current_font > 0 && current_font < 9 ) { if ( !context->stack.isEmpty() ) context->cursor->setCharFormat( context->stack.pop() ); } if (current_italic) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontItalic( false ); context->cursor->setCharFormat( format ); } if (current_underline) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontUnderline( false ); context->cursor->setCharFormat( format ); } if (current_struckthrough) { QTextCharFormat format( context->cursor->charFormat() ); format.setFontStrikeOut( false ); context->cursor->setCharFormat( format ); } #if 0 if (current_alignment > 0) { context->cursor->insertBlock(); } if (current_right_margin > 0) fprintf (fp, " 
", current_right_margin); else if (current_left_margin > 0) fprintf (fp, ""); /* end the paragraph */ context->cursor->insertBlock(); #endif } free (paragraphs); return true; } bool QUnpluck::TranscribeRecord( int index ) { plkr_DataRecordType type; int data_len; bool status = true; unsigned char *data = plkr_GetRecordBytes( mDocument, index, &data_len, &type); if ( !data ) { MarkRecordDone( index ); return false; } if (type == PLKR_DRTYPE_TEXT_COMPRESSED || type == PLKR_DRTYPE_TEXT) { QTextDocument *document = new QTextDocument; QTextFrameFormat format( document->rootFrame()->frameFormat() ); format.setMargin( 20 ); document->rootFrame()->setFrameFormat( format ); Context *context = new Context; context->recordId = index; context->document = document; context->cursor = new QTextCursor( document ); QTextCharFormat charFormat; charFormat.setFontPointSize( 10 ); charFormat.setFontFamily( QStringLiteral("Helvetica") ); context->cursor->setCharFormat( charFormat ); status = TranscribeTextRecord( mDocument, index, context, data, type ); document->setTextWidth( 600 ); delete context->cursor; mContext.append( context ); } else if (type == PLKR_DRTYPE_IMAGE_COMPRESSED || type == PLKR_DRTYPE_IMAGE) { QImage image = TranscribeImageRecord( data ); mImages.insert( index, image ); } else if (type == PLKR_DRTYPE_MULTIIMAGE) { QImage image; if ( TranscribeMultiImageRecord( mDocument, image, data ) ) mImages.insert( index, image ); } else { status = false; } // plkr_GetHomeRecordID (doc))) MarkRecordDone( index ); return status; } diff --git a/generators/plucker/unpluck/unpluck.cpp b/generators/plucker/unpluck/unpluck.cpp index 143fa3a27..720958699 100644 --- a/generators/plucker/unpluck/unpluck.cpp +++ b/generators/plucker/unpluck/unpluck.cpp @@ -1,1184 +1,1184 @@ /* -*- mode: c; indent-tabs-mode: nil; -*- * $Id: unpluck.c,v 1.12 2003/12/28 20:59:21 chrish Exp $ * * unpluck -- a library to read Plucker data files * Copyright (c) 2002, Bill Janssen * * 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; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #if !defined(WIN32) #include /* for lseek, etc. */ #else #include #endif #include #include #include /* for fstat() */ #include /* for strndup() */ #include /* for errno */ #include /* for O_RDONLY */ #include /* for assert() */ #include #include "unpluck.h" #include "unpluckint.h" /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** Decompression code (taken from the Plucker PalmOS viewer *****/ /***** sources, Copyright (c) 1998-2002, by Mark Ian Lillywhite *****/ /***** and Michael Nordstr�m, also under the GPL) *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ /* uncompress DOC compressed document/image */ static unsigned int UncompressDOC ( unsigned char* src, /* in: compressed document */ unsigned int src_len, /* in: size of compressed document */ unsigned char* dest, /* out: buffer to put uncompressed document in */ unsigned int dest_len /* out: size of buffer to put uncompressed document in */ ) { - unsigned int offset; +// unsigned int offset; unsigned int src_index; unsigned int dest_index; assert (src != NULL && src_len != 0 && dest != NULL && dest_len != 0); - offset = 0; +// offset = 0; src_index = 0; dest_index = 0; memset (dest, 0, dest_len); while (src_index < src_len) { unsigned int token; token = (unsigned int) src[src_index++]; if (0 < token && token < 9) { while (token != 0) { dest[dest_index++] = src[src_index++]; token--; } } else if (token < 0x80) { dest[dest_index++] = token; } else if (0xc0 <= token) { dest[dest_index++] = ' '; dest[dest_index++] = token ^ 0x80; } else { int m; int n; token *= 256; token += src[src_index++]; m = (token & 0x3fff) / 8; n = token & 7; n += 3; while (n != 0) { dest[dest_index] = dest[dest_index - m]; dest_index++; n--; } } } assert (src_index == src_len && dest_index == dest_len); return 1; } /* uncompress ZLib compressed document/image */ static unsigned int UncompressZLib ( unsigned char* src, /* in: compressed document */ unsigned int src_len, /* in: size of compressed document */ unsigned char* dest, /* out: buffer to put uncompressed document in */ unsigned int dest_len, /* out: size of buffer to put uncompressed document in */ unsigned char* owner_id /* in: owner-id key */ ) { z_stream z; unsigned int err; unsigned int keylen; unsigned int i; unsigned char keybuf[OWNER_ID_HASH_LEN]; assert (src != NULL && src_len != 0 && dest != NULL && dest_len != 0); keylen = (owner_id == NULL) ? 0 : MIN (src_len, OWNER_ID_HASH_LEN); memset (&z, 0, sizeof z); if (owner_id != NULL) { for (i = 0; i < keylen; i++) keybuf[i] = src[i] ^ owner_id[i]; z.next_in = keybuf; z.avail_in = keylen; } else { z.next_in = src; z.avail_in = src_len; } z.next_out = dest; z.avail_out = dest_len; err = inflateInit (&z); if (err != Z_OK) { return err; } do { if (z.avail_in == 0 && keylen > 0) { z.next_in = src + keylen; z.avail_in = src_len - keylen; } err = inflate (&z, Z_SYNC_FLUSH); } while (err == Z_OK); if (err != Z_STREAM_END) return err; assert (z.total_out == dest_len); return inflateEnd (&z); } /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** "Open" the DB (read the headers and parse the various *****/ /***** metadata, like URLs, default categories, charsets, etc.) *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ static void FreePluckerDoc ( plkr_Document* doc ) { if (doc->name != NULL) free (doc->name); if (doc->title != NULL) free (doc->title); if (doc->author != NULL) free (doc->author); if (doc->records != NULL) { int i; for (i = 0; i < doc->nrecords; i++) { if (doc->records[i].cache != NULL) free (doc->records[i].cache); } free (doc->records); } if (doc->urls != NULL) free (doc->urls); if (doc->handle != NULL) doc->handle->free (doc->handle); free (doc); } static plkr_DataRecord* FindRecordByIndex ( plkr_Document* doc, int record_index ) { int imin; int imax; int itest; for (imin = 0, imax = doc->nrecords; imin < imax;) { itest = imin + (imax - imin) / 2; /* _plkr_message("imin = %2d, imax = %2d, itest = %2d (%2d), record_index = %2d", imin, imax, itest, doc->records[itest].uid, record_index); */ if (doc->records[itest].uid == record_index) return &doc->records[itest]; else if (record_index > doc->records[itest].uid) imin = itest + 1; else if (record_index < doc->records[itest].uid) imax = itest; } return NULL; } static int GetUncompressedRecord ( plkr_Document* doc, plkr_DBHandle handle, int record_index, unsigned char* buffer, int buffer_size, plkr_DataRecordType expected_type, unsigned char** buffer_out, int* buffer_size_out, plkr_DataRecord** record_out ) { /* read whole data record, including header, into buffer. If some part of the record is compressed, uncompress it. If "buffer" is NULL, allocate enough bytes to fit. Returns TRUE if read is successful, and sets "buffer_out" and "buffer_size_out" and "record_out" on successful return. */ plkr_DataRecord* record; unsigned char* tbuffer = buffer; int size_needed; int blen = buffer_size; record = FindRecordByIndex (doc, record_index); if (record == NULL) { _plkr_message ("No record with index %d", record_index); return FALSE; }; if (expected_type != PLKR_DRTYPE_NONE && record->type != expected_type) { _plkr_message ("Record %d has unexpected type %d; expected %d", record_index, record->type, expected_type); return FALSE; } /* figure size needed */ size_needed = record->uncompressed_size + 8; if ((record->type == PLKR_DRTYPE_TEXT_COMPRESSED) || (record->type == PLKR_DRTYPE_TEXT)) size_needed += 4 * record->nparagraphs; if (!buffer) { if (buffer_out == NULL) { _plkr_message ("No output buffer"); return FALSE; } else if (record->cache) { tbuffer = record->cache; size_needed = record->cached_size; } else { tbuffer = (unsigned char *) malloc (size_needed); blen = size_needed; } } else { tbuffer = buffer; if (buffer_size < size_needed) { _plkr_message ("Buffer too small; needs %d", size_needed); return FALSE; } else if (record->cache) { memcpy (buffer, record->cache, record->cached_size); size_needed = record->cached_size; } } if (!record->cache) { if ((record->type == PLKR_DRTYPE_TEXT_COMPRESSED) || (record->type == PLKR_DRTYPE_IMAGE_COMPRESSED) || (record->type == PLKR_DRTYPE_TABLE_COMPRESSED) || (record->type == PLKR_DRTYPE_GLYPHPAGE) || (record->type == PLKR_DRTYPE_LINKS_COMPRESSED)) { unsigned char *start_of_data, *output_ptr; int len_of_data, buffer_remaining, buf_to_use; unsigned char *buf = (unsigned char*)malloc (record->size); if (!handle->seek (handle, record->offset) || (handle->read (handle, buf, record->size, record->size) != record->size)) { _plkr_message ("Bad read from DBHandle while reading record %d", record->uid); free (buf); if (tbuffer != buffer) free (tbuffer); return FALSE; } #if 0 _plkr_message ("data record %d (%d): uid is %d, # paras = %d, size = %d, type = %d", record_index, record->size, (buf[0] << 8) + buf[1], (buf[2] << 8) + buf[3], (buf[4] << 8) + buf[5], buf[6]); #endif memcpy (tbuffer, buf, 8); output_ptr = tbuffer + 8; buffer_remaining = blen - 8; start_of_data = buf + 8; len_of_data = record->size - 8; if (record->type == PLKR_DRTYPE_TEXT_COMPRESSED) { /* skip over the paragraph headers */ memcpy (output_ptr, start_of_data, 4 * record->nparagraphs); start_of_data += (4 * record->nparagraphs); len_of_data -= (4 * record->nparagraphs); output_ptr += (4 * record->nparagraphs); buffer_remaining -= (4 * record->nparagraphs); } buf_to_use = size_needed - (start_of_data - buf); if (doc->compression == PLKR_COMPRESSION_ZLIB) { if (UncompressZLib (start_of_data, len_of_data, output_ptr, buf_to_use, (doc->owner_id_required ? doc-> owner_id_key : NULL)) != Z_OK) { _plkr_message ("Bad Zlib uncompress of record %d", record_index); free (buf); if (tbuffer != buffer) free (tbuffer); return FALSE; }; } else if (doc->compression == PLKR_COMPRESSION_DOC) { if (UncompressDOC (start_of_data, len_of_data, output_ptr, buf_to_use) != 1) { _plkr_message ("Bad DOC uncompress of record %d", record_index); free (buf); if (tbuffer != buffer) free (tbuffer); return FALSE; }; } free (buf); } else { /* all the record types which don't use compression */ if (!handle->seek (handle, record->offset) || (handle->read (handle, tbuffer, blen, size_needed) != size_needed)) { _plkr_message ("Bad read from DBHandle while reading record %d", record->uid); if (tbuffer != buffer) free (tbuffer); return FALSE; } } } if (record_out) *record_out = record; if (buffer_out) *buffer_out = tbuffer; if (buffer_size_out) *buffer_size_out = size_needed; return TRUE; } static int ParseCategories ( plkr_Document* newdoc, plkr_DBHandle handle ) { struct _plkr_CategoryName* categories; struct _plkr_CategoryName* newc; plkr_DataRecord *record; unsigned char* buf; unsigned char* ptr; int bufsize; if (GetUncompressedRecord (newdoc, handle, newdoc->default_category_record_uid, NULL, 0, PLKR_DRTYPE_CATEGORY, &buf, &bufsize, &record)) { /* keep the record data, since the list of char * ptrs will point into it */ record->cache = buf; record->cached_size = bufsize; categories = NULL; for (ptr = buf + 8; (ptr - buf) < bufsize;) { newc = (struct _plkr_CategoryName *) malloc (sizeof (struct _plkr_CategoryName)); newc->next = categories; categories = newc; newc->name = (char*)ptr; ptr += (strlen ((char*)ptr) + 1); } newdoc->default_categories = categories; return TRUE; } else { return FALSE; } } static int ParseMetadata ( plkr_Document* newdoc, plkr_DBHandle handle ) { unsigned char* buf; unsigned char* ptr; int bufsize; int nsubrecords; int typecode; int subrecord_length; int i; if (!GetUncompressedRecord (newdoc, handle, newdoc->metadata_record_uid, NULL, 0, PLKR_DRTYPE_METADATA, &buf, &bufsize, NULL)) { return FALSE; } else { nsubrecords = (buf[8] << 8) + buf[9]; for (i = 0, ptr = buf + 10; i < nsubrecords; i++) { typecode = (ptr[0] << 8) + ptr[1]; subrecord_length = ((ptr[2] << 8) + ptr[3]) * 2; if (typecode == PLKR_MDTYPE_DEFAULTCHARSET) { newdoc->default_charset_mibenum = (ptr[4] << 8) + ptr[5]; ptr += 6; } else if (typecode == PLKR_MDTYPE_EXCEPTCHARSETS) { int i, n, record_id, mibenum; plkr_DataRecord *record; ptr += 4; for (i = 0, n = subrecord_length / 4; i < n; i++, ptr += 4) { record_id = (ptr[0] << 8) + ptr[1]; mibenum = (ptr[2] << 8) + ptr[3]; record = FindRecordByIndex (newdoc, record_id); if (record == NULL) { _plkr_message ("Can't find record with id %d", record_id); free (buf); return FALSE; } record->charset_mibenum = mibenum; } } else if (typecode == PLKR_MDTYPE_OWNERIDCRC) { newdoc->owner_id_required = TRUE; ptr += 8; } else if (typecode == PLKR_MDTYPE_AUTHOR) { newdoc->author = _plkr_strndup ((char*)( ptr + 4 ), subrecord_length); ptr += (4 + subrecord_length); } else if (typecode == PLKR_MDTYPE_TITLE) { newdoc->title = _plkr_strndup ((char*)( ptr + 4 ), subrecord_length); ptr += (4 + subrecord_length); } else if (typecode == PLKR_MDTYPE_PUBLICATIONTIME) { newdoc->publication_time = READ_BIGENDIAN_LONG (ptr + 4) - PLKR_TIMEADJUST; ptr += 8; } else { _plkr_message ("Bad metadata typecode %d encountered in metadata record", typecode); free (buf); return FALSE; } } free (buf); return TRUE; } } static int ParseURLs ( plkr_Document* newdoc, plkr_DBHandle handle ) { plkr_DataRecord* record; unsigned char* buf; unsigned char* ptr; char** urls; int id; int i; int n; int count; int nurls; int bufsize; struct url_index_record { int last_url_index; int record_id; } *records; buf = NULL; urls = NULL; records = NULL; if (!GetUncompressedRecord (newdoc, handle, newdoc->urls_index_record_uid, NULL, 0, PLKR_DRTYPE_LINKS_INDEX, &buf, &bufsize, NULL)) { return FALSE; } else { n = ((buf[4] << 8) + buf[5]) / 4; records = (struct url_index_record *) malloc (n * sizeof (*records)); for (i = 0, nurls = 0; i < n; i++) { ptr = buf + 8 + (i * 4); records[i].last_url_index = (ptr[0] << 8) + ptr[1]; records[i].record_id = (ptr[2] << 8) + ptr[3]; #ifdef DEBUGURLS _plkr_message ("index %3d: last = %d, record_id = %d", i, records[i].last_url_index, records[i].record_id); #endif /* def DEBUGURLS */ nurls = MAX (nurls, records[i].last_url_index); } free (buf); buf = NULL; } urls = (char **) malloc (nurls * sizeof (char *)); memset (urls, 0, nurls * sizeof (char *)); for (count = 0, i = 0; i < n; i++) { id = records[i].record_id; if (!GetUncompressedRecord (newdoc, handle, id, NULL, 0, PLKR_DRTYPE_NONE, &buf, &bufsize, &record)) { goto errout4; } if (record->type != PLKR_DRTYPE_LINKS && record->type != PLKR_DRTYPE_LINKS_COMPRESSED) { _plkr_message ("Supposed URLs record has bad type %d", record->type); goto errout4; } record->cache = buf; record->cached_size = bufsize; buf = NULL; for (ptr = record->cache + 8; (ptr - record->cache) < record->cached_size; ptr += (strlen ((char*)ptr) + 1)) { #ifdef DEBUGURLS _plkr_message ("%3d: %s", count, ptr); #endif /* def DEBUGURLS */ assert (count < nurls); urls[count++] = (char*)ptr; } } free (records); newdoc->urls = urls; newdoc->nurls = nurls; return TRUE; errout4: if (buf != NULL) free (buf); free (urls); free (records); return FALSE; } plkr_Document* plkr_OpenDoc ( plkr_DBHandle handle ) { ReservedRecordEntry reserved[MAX_RESERVED]; plkr_DataRecord* record; plkr_Document* newdoc; unsigned char utilbuf[128]; static char id_stamp[9] = "DataPlkr"; int i; int nreserved; int records_size; int compression; if (!handle->seek (handle, 0) || (handle->read (handle, utilbuf, sizeof (utilbuf), 78) != 78)) { _plkr_message ("Bad read of DB header"); return NULL; } /* check for type stamp */ if (strncmp ((char *) (utilbuf + 60), id_stamp, 8) != 0) { _plkr_message ("Bad magic number"); return NULL; } /* check for version 1 */ i = (utilbuf[34] << 8) + utilbuf[35]; if (i != 1) { _plkr_message ("Not version 1 of Plucker format; version %d", i); return NULL; } /* get the title, creation time, and last modification time from header */ newdoc = (plkr_Document *) malloc (sizeof (plkr_Document)); memset (newdoc, 0, sizeof (plkr_Document)); newdoc->name = (char*)_plkr_strndup ((char*)utilbuf, MIN (strlen ((char*)utilbuf), 32)); newdoc->creation_time = (time_t) ((utilbuf[36] << 24) + (utilbuf[37] << 16) + (utilbuf[38] << 8) + utilbuf[39] - PLKR_TIMEADJUST); newdoc->modification_time = (time_t) ((utilbuf[40] << 24) + (utilbuf[41] << 16) + (utilbuf[42] << 8) + utilbuf[43] - PLKR_TIMEADJUST); newdoc->nrecords = (utilbuf[76] << 8) + utilbuf[77]; /* Now read the record-list to find out where the records are */ records_size = sizeof (plkr_DataRecord) * newdoc->nrecords; newdoc->records = (plkr_DataRecord *) malloc (records_size); memset (newdoc->records, 0, records_size); for (i = 0; i < newdoc->nrecords; i++) { if (handle->read (handle, utilbuf, sizeof (utilbuf), 8) != 8) { _plkr_message ("Bad read of record list"); FreePluckerDoc (newdoc); return NULL; } newdoc->records[i].offset = (utilbuf[0] << 24) + (utilbuf[1] << 16) + (utilbuf[2] << 8) + utilbuf[3]; } /* process the index record */ if (!handle->seek (handle, newdoc->records[0].offset) || (handle->read (handle, utilbuf, sizeof (utilbuf), 6) != 6)) { _plkr_message ("Bad read of index record"); FreePluckerDoc (newdoc); return NULL; } if ((utilbuf[0] << 8) + utilbuf[1] != 1) { _plkr_message ("index record has bad UID %d", (utilbuf[0] << 8) + utilbuf[1]); FreePluckerDoc (newdoc); return NULL; } newdoc->records[0].uid = 1; compression = (utilbuf[2] << 8) + utilbuf[3]; if (compression == PLKR_COMPRESSION_DOC) newdoc->compression = PLKR_COMPRESSION_DOC; else if (compression == PLKR_COMPRESSION_ZLIB) newdoc->compression = PLKR_COMPRESSION_ZLIB; else { _plkr_message ("Unknown compression type %d", compression); FreePluckerDoc (newdoc); return NULL; } nreserved = (utilbuf[4] << 8) + utilbuf[5]; if (nreserved > MAX_RESERVED) { _plkr_message ("Too many reserved records (%d) for software", nreserved); FreePluckerDoc (newdoc); return NULL; } for (i = 0; i < nreserved; i++) { if (handle->read (handle, utilbuf, sizeof (utilbuf), 4) != 4) { _plkr_message ("Bad read of reserved record list"); FreePluckerDoc (newdoc); return NULL; } reserved[i].name = (ReservedRecordName)( (utilbuf[0] << 8) + utilbuf[1] ); reserved[i].uid = (utilbuf[2] << 8) + utilbuf[3]; } /* OK, now process the data records */ newdoc->max_record_size = 0; for (i = 1; i < newdoc->nrecords; i++) { record = newdoc->records + i; if (!handle->seek (handle, record->offset) || (handle->read (handle, utilbuf, sizeof (utilbuf), 8) != 8)) { _plkr_message ("Can't read header of record %d", i); FreePluckerDoc (newdoc); return NULL; } newdoc->records[i - 1].size = record->offset - newdoc->records[i - 1].offset; record->uid = (utilbuf[0] << 8) + utilbuf[1]; record->nparagraphs = (utilbuf[2] << 8) + utilbuf[3]; record->uncompressed_size = (utilbuf[4] << 8) + utilbuf[5]; record->type = (plkr_DataRecordType)utilbuf[6]; newdoc->max_record_size = MAX (newdoc->max_record_size, record->uncompressed_size); } /* To get the size of the last record we subtract its offset from the total size of the DB. */ if ((i = handle->size (handle)) == 0) { _plkr_message ("Can't obtain size of DB"); FreePluckerDoc (newdoc); return NULL; }; record = newdoc->records + (newdoc->nrecords - 1); record->size = i - record->offset; /* make sure the uncompressed size is set, now that we know the record sizes */ for (i = 0; i < newdoc->nrecords; i++) { record = newdoc->records + i; if (record->uncompressed_size == 0) { if (record->type == PLKR_DRTYPE_LINKS_COMPRESSED || record->type == PLKR_DRTYPE_TEXT_COMPRESSED || record->type == PLKR_DRTYPE_TABLE_COMPRESSED || record->type == PLKR_DRTYPE_IMAGE_COMPRESSED) { _plkr_message ("Bad uncompressed size 0 in record uid %d", record->uid); FreePluckerDoc (newdoc); return NULL; } else { record->uncompressed_size = record->size - 8; } } #ifdef DEBUGOPEN { static char *types[] = { "TEXT", "TEXTC", "IMAGE", "IMAGEC", "MAILTO", "URLINDEX", "URLS", "URLSC", "BOOKMARKS", "CATEGORIES", "METADATA" }; _plkr_message ("%3d: type=%10s, offset=%07x, size=%5d, uncompressed_size=%5d", record->uid, types[MIN (record->type, sizeof (types) / sizeof (char *))], record->offset, record->size, record->uncompressed_size); } #endif } /* find the reserved records */ /* do metadata first, to find out whether we need an owner_id key */ for (i = 0; i < nreserved; i++) { if (reserved[i].name == PLKR_METADATA_NAME) { newdoc->metadata_record_uid = reserved[i].uid; if (!ParseMetadata (newdoc, handle)) { _plkr_message ("Error parsing metadata record"); FreePluckerDoc (newdoc); return NULL; } } } if (newdoc->owner_id_required) { /* we need to set up the owner-id key before uncompressing any records... */ char *owner_id = plkr_GetConfigString (NULL, "owner_id", NULL); if (owner_id != NULL) { unsigned long crc; int owner_id_len = strlen (owner_id); crc = crc32 (0L, NULL, 0); crc = crc32 (crc, (const Bytef*)owner_id, owner_id_len); for (i = 0; i < 10; i++) { crc = crc32 (crc, (const Bytef*)owner_id, owner_id_len); newdoc->owner_id_key[(i * 4) + 0] = (unsigned char)((crc >> 24) & 0xFF); newdoc->owner_id_key[(i * 4) + 1] = (unsigned char)((crc >> 16) & 0xFF); newdoc->owner_id_key[(i * 4) + 2] = (unsigned char)((crc >> 8) & 0xFF); newdoc->owner_id_key[(i * 4) + 3] = (unsigned char)(crc & 0xFF); } } else { _plkr_message ("Document requires owner-id to open"); FreePluckerDoc (newdoc); return NULL; } } /* now do the rest of the reserved records */ for (i = 0; i < nreserved; i++) { if (reserved[i].name == PLKR_HOME_NAME) newdoc->home_record_uid = reserved[i].uid; else if (reserved[i].name == PLKR_DEFAULT_CATEGORY_NAME) { newdoc->default_category_record_uid = reserved[i].uid; if (!ParseCategories (newdoc, handle)) { _plkr_message ("Error parsing default-categories record"); FreePluckerDoc (newdoc); return NULL; } } else if (reserved[i].name == PLKR_URLS_INDEX_NAME) { newdoc->urls_index_record_uid = reserved[i].uid; if (!ParseURLs (newdoc, handle)) { _plkr_message ("Error parsing URLs records"); FreePluckerDoc (newdoc); return NULL; } } } newdoc->handle = handle; #ifdef DEBUGOPEN /* test the record fetch by fetching them! */ for (i = 1; i < newdoc->nrecords; i++) { plkr_DataRecordType type; int n; printf ("==============================================\n" "record %3d (%d bytes)\n", newdoc->records[i].uid, newdoc->records[i].size); (void) plkr_GetRecordBytes (newdoc, newdoc->records[i].uid, &n, &type); } #endif return newdoc; } int plkr_GetUidForIndex ( plkr_Document *doc, int record_index ) { return doc->records[ record_index ].uid; } void plkr_CloseDoc ( plkr_Document * doc ) { if (doc == NULL) { _plkr_message ("Attempt to free NULL doc"); } else { FreePluckerDoc (doc); } } /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** An implementation of a file-based DBHandle *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ static int FpSeek ( plkr_DBHandle handle, long offset ) { long result; result = lseek (handle->dbprivate, offset, SEEK_SET); if (result != offset) { _plkr_message ("Unable to seek fp %d to offset %lu -- %lu instead\n", handle->dbprivate, offset, result); } return (result == offset); } static int FpRead ( plkr_DBHandle handle, unsigned char* buffer, int buffersize, int readsize ) { int result; result = read (handle->dbprivate, buffer, MIN (buffersize, readsize)); if (result != readsize) { _plkr_message ("Unable to read %d bytes from fp %d -- read %d instead\n", MIN (buffersize, readsize), handle->dbprivate, result); } return (result); } static void FpFree ( plkr_DBHandle handle ) { int fp = handle->dbprivate; if (fp > 0) close (fp); } static long FpSize ( plkr_DBHandle handle ) { int fp = handle->dbprivate; struct stat buf; if (fstat (fp, &buf) != 0) { _plkr_message ("Can't stat file; errno %d", errno); return 0; }; return buf.st_size; } plkr_Document* plkr_OpenDBFile ( const char* filename ) { plkr_DBHandle handle; plkr_Document* doc; int fp; #if !defined(WIN32) fp = open (filename, O_RDONLY); #else fp = open (filename, O_RDONLY | O_BINARY); #endif if (fp < 0) { _plkr_message ("Can't open file %s", filename); return NULL; } handle = (plkr_DBHandle) malloc (sizeof (*handle)); handle->dbprivate = fp; handle->seek = FpSeek; handle->read = FpRead; handle->free = FpFree; handle->size = FpSize; doc = plkr_OpenDoc (handle); if (doc == NULL) close (fp); return doc; } /***********************************************************************/ /***********************************************************************/ /***** *****/ /***** Routines to access individual uncompressed records *****/ /***** *****/ /***********************************************************************/ /***********************************************************************/ int plkr_CopyRecordBytes ( plkr_Document* doc, int record_index, unsigned char* output_buffer, int output_buffer_size, plkr_DataRecordType* type ) { plkr_DataRecord* record; int output_size; if (!FindRecordByIndex (doc, record_index)) return 0; if (!GetUncompressedRecord (doc, doc->handle, record_index, output_buffer, output_buffer_size, PLKR_DRTYPE_NONE, NULL, &output_size, &record)) return 0; else { *type = record->type; return output_size; } } unsigned char *plkr_GetRecordBytes ( plkr_Document* doc, int record_index, int* size, plkr_DataRecordType* type ) { plkr_DataRecord* record; unsigned char* buf; if (!FindRecordByIndex (doc, record_index)) return NULL; if (!GetUncompressedRecord (doc, doc->handle, record_index, NULL, 0, PLKR_DRTYPE_NONE, &buf, size, &record)) return NULL; else { if (!record->cache) { record->cache = buf; record->cached_size = *size; } *type = record->type; return buf; } } int plkr_GetHomeRecordID ( plkr_Document* doc ) { return doc->home_record_uid; } char* plkr_GetName ( plkr_Document* doc ) { return doc->name; } char* plkr_GetTitle ( plkr_Document* doc ) { return doc->title; } char* plkr_GetAuthor ( plkr_Document* doc ) { return doc->author; } int plkr_GetDefaultCharset ( plkr_Document* doc ) { return doc->default_charset_mibenum; } unsigned long plkr_GetPublicationTime ( plkr_Document* doc ) { if (doc->publication_time) return (unsigned long) doc->publication_time; else return (unsigned long) doc->creation_time; } plkr_CategoryList plkr_GetDefaultCategories ( plkr_Document* doc ) { return doc->default_categories; } int plkr_GetRecordCount ( plkr_Document* doc ) { return doc->nrecords; } int plkr_GetMaxRecordSize ( plkr_Document* doc ) { return doc->max_record_size; } char* plkr_GetRecordURL ( plkr_Document * doc, int record_index ) { if (record_index < 1 || record_index > doc->nurls) return NULL; else return (doc->urls[record_index - 1]); } int plkr_HasRecordWithID ( plkr_Document* doc, int record_index ) { return (FindRecordByIndex (doc, record_index) != NULL); } int plkr_GetRecordType ( plkr_Document* doc, int record_index ) { plkr_DataRecord* r; r = FindRecordByIndex (doc, record_index); if (r) return r->type; else return PLKR_DRTYPE_NONE; } int plkr_GetRecordCharset ( plkr_Document* doc, int record_index ) { plkr_DataRecord* r; r = FindRecordByIndex (doc, record_index); if (r && ((r->type == PLKR_DRTYPE_TEXT_COMPRESSED) || (r->type == PLKR_DRTYPE_TEXT))) { if (r->charset_mibenum == 0) return doc->default_charset_mibenum; else return r->charset_mibenum; } else return 0; } diff --git a/generators/poppler/annots.cpp b/generators/poppler/annots.cpp index 8edca0c27..c7fd79834 100644 --- a/generators/poppler/annots.cpp +++ b/generators/poppler/annots.cpp @@ -1,427 +1,423 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * Copyright (C) 2012 by Guillermo A. Amaral B. * * * * 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. * ***************************************************************************/ #include // qt/kde includes #include #include #include #include #include "annots.h" #include "debug_pdf.h" #include "generator_pdf.h" #include "popplerembeddedfile.h" #include "config-okular-poppler.h" Q_DECLARE_METATYPE( Poppler::Annotation* ) extern Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound ); extern Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie ); extern Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *popplerScreen ); #ifdef HAVE_POPPLER_0_36 extern QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia ); #endif static void disposeAnnotation( const Okular::Annotation *ann ) { Poppler::Annotation *popplerAnn = qvariant_cast< Poppler::Annotation * >( ann->nativeId() ); delete popplerAnn; } static QPointF normPointToPointF( const Okular::NormalizedPoint& pt ) { return QPointF(pt.x, pt.y); } static QRectF normRectToRectF( const Okular::NormalizedRect& rect ) { return QRectF( QPointF(rect.left, rect.top), QPointF(rect.right, rect.bottom) ); } // Poppler and Okular share the same flag values, but we don't want to export internal flags static int maskExportedFlags(int flags) { return flags & ( Okular::Annotation::Hidden | Okular::Annotation::FixedSize | Okular::Annotation::FixedRotation | Okular::Annotation::DenyPrint | Okular::Annotation::DenyWrite | Okular::Annotation::DenyDelete | Okular::Annotation::ToggleHidingOnMouse ); } //BEGIN PopplerAnnotationProxy implementation PopplerAnnotationProxy::PopplerAnnotationProxy( Poppler::Document *doc, QMutex *userMutex ) : ppl_doc ( doc ), mutex ( userMutex ) { } PopplerAnnotationProxy::~PopplerAnnotationProxy() { } bool PopplerAnnotationProxy::supports( Capability cap ) const { switch ( cap ) { case Addition: case Modification: case Removal: return true; default: return false; } } void PopplerAnnotationProxy::notifyAddition( Okular::Annotation *okl_ann, int page ) { // Export annotation to DOM QDomDocument doc; QDomElement dom_ann = doc.createElement( QStringLiteral("root") ); Okular::AnnotationUtils::storeAnnotation( okl_ann, dom_ann, doc ); QMutexLocker ml(mutex); // Create poppler annotation Poppler::Annotation *ppl_ann = Poppler::AnnotationUtils::createAnnotation( dom_ann ); // Poppler doesn't render StampAnnotations yet if ( ppl_ann->subType() != Poppler::Annotation::AStamp ) okl_ann->setFlags( okl_ann->flags() | Okular::Annotation::ExternallyDrawn ); // Poppler stores highlight points in swapped order if ( ppl_ann->subType() == Poppler::Annotation::AHighlight ) { Poppler::HighlightAnnotation * hlann = static_cast( ppl_ann ); QList quads = hlann->highlightQuads(); QMutableListIterator it( quads ); while ( it.hasNext() ) { Poppler::HighlightAnnotation::Quad &q = it.next(); QPointF t; t = q.points[3]; q.points[3] = q.points[0]; q.points[0] = t; t = q.points[2]; q.points[2] = q.points[1]; q.points[1] = t; } hlann->setHighlightQuads( quads ); } // Bind poppler object to page Poppler::Page *ppl_page = ppl_doc->page( page ); ppl_page->addAnnotation( ppl_ann ); delete ppl_page; // Set pointer to poppler annotation as native Id okl_ann->setNativeId( qVariantFromValue( ppl_ann ) ); okl_ann->setDisposeDataFunction( disposeAnnotation ); qCDebug(OkularPdfDebug) << okl_ann->uniqueName(); } void PopplerAnnotationProxy::notifyModification( const Okular::Annotation *okl_ann, int page, bool appearanceChanged ) { Q_UNUSED( page ); Q_UNUSED( appearanceChanged ); Poppler::Annotation *ppl_ann = qvariant_cast( okl_ann->nativeId() ); if ( !ppl_ann ) // Ignore non-native annotations return; QMutexLocker ml(mutex); if ( okl_ann->flags() & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) ) { // Okular ui already renders the annotation on its own ppl_ann->setFlags( Poppler::Annotation::Hidden ); return; } // Set basic properties // Note: flags and boundary must be set first in order to correctly handle // FixedRotation annotations. ppl_ann->setFlags(maskExportedFlags( okl_ann->flags() )); ppl_ann->setBoundary(normRectToRectF( okl_ann->boundingRectangle() )); ppl_ann->setAuthor( okl_ann->author() ); ppl_ann->setContents( okl_ann->contents() ); // Set style Poppler::Annotation::Style s; s.setColor( okl_ann->style().color() ); s.setWidth( okl_ann->style().width() ); s.setOpacity( okl_ann->style().opacity() ); ppl_ann->setStyle( s ); // Set type-specific properties (if any) switch ( ppl_ann->subType() ) { case Poppler::Annotation::AText: { const Okular::TextAnnotation * okl_txtann = static_cast(okl_ann); Poppler::TextAnnotation * ppl_txtann = static_cast(ppl_ann); ppl_txtann->setTextIcon( okl_txtann->textIcon() ); ppl_txtann->setTextFont( okl_txtann->textFont() ); ppl_txtann->setInplaceAlign( okl_txtann->inplaceAlignment() ); ppl_txtann->setCalloutPoints( QVector() ); ppl_txtann->setInplaceIntent( (Poppler::TextAnnotation::InplaceIntent)okl_txtann->inplaceIntent() ); break; } case Poppler::Annotation::ALine: { const Okular::LineAnnotation * okl_lineann = static_cast(okl_ann); Poppler::LineAnnotation * ppl_lineann = static_cast(ppl_ann); QLinkedList points; foreach ( const Okular::NormalizedPoint &p, okl_lineann->linePoints() ) points.append(normPointToPointF( p )); ppl_lineann->setLinePoints( points ); ppl_lineann->setLineStartStyle( (Poppler::LineAnnotation::TermStyle)okl_lineann->lineStartStyle() ); ppl_lineann->setLineEndStyle( (Poppler::LineAnnotation::TermStyle)okl_lineann->lineEndStyle() ); ppl_lineann->setLineClosed( okl_lineann->lineClosed() ); ppl_lineann->setLineInnerColor( okl_lineann->lineInnerColor() ); ppl_lineann->setLineLeadingForwardPoint( okl_lineann->lineLeadingForwardPoint() ); ppl_lineann->setLineLeadingBackPoint( okl_lineann->lineLeadingBackwardPoint() ); ppl_lineann->setLineShowCaption( okl_lineann->showCaption() ); ppl_lineann->setLineIntent( (Poppler::LineAnnotation::LineIntent)okl_lineann->lineIntent() ); break; } case Poppler::Annotation::AGeom: { const Okular::GeomAnnotation * okl_geomann = static_cast(okl_ann); Poppler::GeomAnnotation * ppl_geomann = static_cast(ppl_ann); ppl_geomann->setGeomType( (Poppler::GeomAnnotation::GeomType)okl_geomann->geometricalType() ); ppl_geomann->setGeomInnerColor( okl_geomann->geometricalInnerColor() ); break; } case Poppler::Annotation::AHighlight: { const Okular::HighlightAnnotation * okl_hlann = static_cast(okl_ann); Poppler::HighlightAnnotation * ppl_hlann = static_cast(ppl_ann); ppl_hlann->setHighlightType( (Poppler::HighlightAnnotation::HighlightType)okl_hlann->highlightType() ); break; } case Poppler::Annotation::AStamp: { const Okular::StampAnnotation * okl_stampann = static_cast(okl_ann); Poppler::StampAnnotation * ppl_stampann = static_cast(ppl_ann); ppl_stampann->setStampIconName( okl_stampann->stampIconName() ); break; } case Poppler::Annotation::AInk: { const Okular::InkAnnotation * okl_inkann = static_cast(okl_ann); Poppler::InkAnnotation * ppl_inkann = static_cast(ppl_ann); QList< QLinkedList > paths; foreach ( const QLinkedList &path, okl_inkann->inkPaths() ) { QLinkedList points; foreach ( const Okular::NormalizedPoint &p, path ) points.append(normPointToPointF( p )); paths.append( points ); } ppl_inkann->setInkPaths( paths ); break; } default: qCDebug(OkularPdfDebug) << "Type-specific property modification is not implemented for this annotation type"; break; } qCDebug(OkularPdfDebug) << okl_ann->uniqueName(); } void PopplerAnnotationProxy::notifyRemoval( Okular::Annotation *okl_ann, int page ) { Poppler::Annotation *ppl_ann = qvariant_cast( okl_ann->nativeId() ); if ( !ppl_ann ) // Ignore non-native annotations return; QMutexLocker ml(mutex); Poppler::Page *ppl_page = ppl_doc->page( page ); ppl_page->removeAnnotation( ppl_ann ); // Also destroys ppl_ann delete ppl_page; okl_ann->setNativeId( qVariantFromValue(0) ); // So that we don't double-free in disposeAnnotation qCDebug(OkularPdfDebug) << okl_ann->uniqueName(); } //END PopplerAnnotationProxy implementation Okular::Annotation* createAnnotationFromPopplerAnnotation( Poppler::Annotation *ann, bool *doDelete ) { Okular::Annotation *annotation = 0; *doDelete = true; bool tieToOkularAnn = false; bool externallyDrawn = false; switch ( ann->subType() ) { case Poppler::Annotation::AFileAttachment: { Poppler::FileAttachmentAnnotation * attachann = static_cast< Poppler::FileAttachmentAnnotation * >( ann ); Okular::FileAttachmentAnnotation * f = new Okular::FileAttachmentAnnotation(); annotation = f; tieToOkularAnn = true; *doDelete = false; f->setFileIconName( attachann->fileIconName() ); f->setEmbeddedFile( new PDFEmbeddedFile( attachann->embeddedFile() ) ); break; } case Poppler::Annotation::ASound: { Poppler::SoundAnnotation * soundann = static_cast< Poppler::SoundAnnotation * >( ann ); Okular::SoundAnnotation * s = new Okular::SoundAnnotation(); annotation = s; s->setSoundIconName( soundann->soundIconName() ); s->setSound( createSoundFromPopplerSound( soundann->sound() ) ); break; } case Poppler::Annotation::AMovie: { Poppler::MovieAnnotation * movieann = static_cast< Poppler::MovieAnnotation * >( ann ); Okular::MovieAnnotation * m = new Okular::MovieAnnotation(); annotation = m; tieToOkularAnn = true; *doDelete = false; m->setMovie( createMovieFromPopplerMovie( movieann->movie() ) ); break; } case Poppler::Annotation::AWidget: { annotation = new Okular::WidgetAnnotation(); break; } case Poppler::Annotation::AScreen: { Okular::ScreenAnnotation * m = new Okular::ScreenAnnotation(); annotation = m; tieToOkularAnn = true; *doDelete = false; break; } #ifdef HAVE_POPPLER_0_36 case Poppler::Annotation::ARichMedia: { Poppler::RichMediaAnnotation * richmediaann = static_cast< Poppler::RichMediaAnnotation * >( ann ); const QPair result = createMovieFromPopplerRichMedia( richmediaann ); if ( result.first ) { Okular::RichMediaAnnotation * r = new Okular::RichMediaAnnotation(); tieToOkularAnn = true; *doDelete = false; annotation = r; r->setMovie( result.first ); r->setEmbeddedFile( result.second ); } break; } #endif case Poppler::Annotation::AText: case Poppler::Annotation::ALine: case Poppler::Annotation::AGeom: case Poppler::Annotation::AHighlight: case Poppler::Annotation::AInk: case Poppler::Annotation::ACaret: - { externallyDrawn = true; - /* fallback */ - } + /* fallthrough */ case Poppler::Annotation::AStamp: - { tieToOkularAnn = true; *doDelete = false; - /* fallback */ - } + /* fallthrough */ default: { // this is uber ugly but i don't know a better way to do it without introducing a poppler::annotation dependency on core QDomDocument doc; QDomElement root = doc.createElement( QStringLiteral("root") ); doc.appendChild( root ); Poppler::AnnotationUtils::storeAnnotation( ann, root, doc ); annotation = Okular::AnnotationUtils::createAnnotation( root ); break; } } if ( annotation ) { // the Contents field might have lines separated by \r QString contents = ann->contents(); contents.replace( QLatin1Char( '\r' ), QLatin1Char( '\n' ) ); annotation->setAuthor( ann->author() ); annotation->setContents( contents ); annotation->setUniqueName( ann->uniqueName() ); annotation->setModificationDate( ann->modificationDate() ); annotation->setCreationDate( ann->creationDate() ); annotation->setFlags( ann->flags() | Okular::Annotation::External ); annotation->setBoundingRectangle( Okular::NormalizedRect::fromQRectF( ann->boundary() ) ); if (externallyDrawn) annotation->setFlags( annotation->flags() | Okular::Annotation::ExternallyDrawn ); // Poppler stores highlight points in swapped order if ( annotation->subType() == Okular::Annotation::AHighlight ) { Okular::HighlightAnnotation * hlann = static_cast( annotation ); QList &quads = hlann->highlightQuads(); for (QList::iterator it = quads.begin(); it != quads.end(); ++it) { Okular::NormalizedPoint t; t = it->point( 3 ); it->setPoint( it->point(0), 3 ); it->setPoint( t, 0 ); t = it->point( 2 ); it->setPoint( it->point(1), 2 ); it->setPoint( t, 1 ); } } if ( annotation->subType() == Okular::Annotation::AText ) { Okular::TextAnnotation * txtann = static_cast( annotation ); if ( txtann->textType() == Okular::TextAnnotation::Linked ) { Poppler::TextAnnotation * ppl_txtann = static_cast( ann ); // Poppler and Okular assume a different default icon name in XML // We re-read it via getter, which always tells the right one txtann->setTextIcon( ppl_txtann->textIcon() ); } } // TODO clone style // TODO clone window // TODO clone revisions if ( tieToOkularAnn ) { annotation->setNativeId( qVariantFromValue( ann ) ); annotation->setDisposeDataFunction( disposeAnnotation ); } } return annotation; } diff --git a/generators/poppler/generator_pdf.cpp b/generators/poppler/generator_pdf.cpp index 42ccb3a26..2a73ca205 100644 --- a/generators/poppler/generator_pdf.cpp +++ b/generators/poppler/generator_pdf.cpp @@ -1,1718 +1,1732 @@ /*************************************************************************** * Copyright (C) 2004-2008 by Albert Astals Cid * * Copyright (C) 2004 by Enrico Ros * * Copyright (C) 2012 by Guillermo A. Amaral B. * * * * 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. * ***************************************************************************/ #include "generator_pdf.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_pdfsettingswidget.h" #include "pdfsettings.h" #include #include #include "debug_pdf.h" #include "annots.h" #include "formfields.h" #include "popplerembeddedfile.h" Q_DECLARE_METATYPE(Poppler::Annotation*) Q_DECLARE_METATYPE(Poppler::FontInfo) Q_DECLARE_METATYPE(const Poppler::LinkMovie*) Q_DECLARE_METATYPE(const Poppler::LinkRendition*) #ifdef HAVE_POPPLER_0_50 Q_DECLARE_METATYPE(const Poppler::LinkOCGState*) #endif static const int defaultPageWidth = 595; static const int defaultPageHeight = 842; class PDFOptionsPage : public QWidget { Q_OBJECT public: PDFOptionsPage() { setWindowTitle( i18n( "PDF Options" ) ); QVBoxLayout *layout = new QVBoxLayout(this); m_printAnnots = new QCheckBox(i18n("Print annotations"), this); m_printAnnots->setToolTip(i18n("Include annotations in the printed document")); m_printAnnots->setWhatsThis(i18n("Includes annotations in the printed document. You can disable this if you want to print the original unannotated document.")); layout->addWidget(m_printAnnots); m_forceRaster = new QCheckBox(i18n("Force rasterization"), this); m_forceRaster->setToolTip(i18n("Rasterize into an image before printing")); m_forceRaster->setWhatsThis(i18n("Forces the rasterization of each page into an image before printing it. This usually gives somewhat worse results, but is useful when printing documents that appear to print incorrectly.")); layout->addWidget(m_forceRaster); layout->addStretch(1); #if defined(Q_OS_WIN) m_printAnnots->setVisible( false ); #endif setPrintAnnots( true ); // Default value } bool printAnnots() { return m_printAnnots->isChecked(); } void setPrintAnnots( bool printAnnots ) { m_printAnnots->setChecked( printAnnots ); } bool printForceRaster() { return m_forceRaster->isChecked(); } void setPrintForceRaster( bool forceRaster ) { m_forceRaster->setChecked( forceRaster ); } private: QCheckBox *m_printAnnots; QCheckBox *m_forceRaster; }; static void fillViewportFromLinkDestination( Okular::DocumentViewport &viewport, const Poppler::LinkDestination &destination ) { viewport.pageNumber = destination.pageNumber() - 1; if (!viewport.isValid()) return; // get destination position // TODO add other attributes to the viewport (taken from link) // switch ( destination->getKind() ) // { // case destXYZ: if (destination.isChangeLeft() || destination.isChangeTop()) { // TODO remember to change this if we implement DPI and/or rotation double left, top; left = destination.left(); top = destination.top(); viewport.rePos.normalizedX = left; viewport.rePos.normalizedY = top; viewport.rePos.enabled = true; viewport.rePos.pos = Okular::DocumentViewport::TopLeft; } /* TODO if ( dest->getChangeZoom() ) make zoom change*/ /* break; default: // implement the others cases break;*/ // } } Okular::Sound* createSoundFromPopplerSound( const Poppler::SoundObject *popplerSound ) { Okular::Sound *sound = popplerSound->soundType() == Poppler::SoundObject::Embedded ? new Okular::Sound( popplerSound->data() ) : new Okular::Sound( popplerSound->url() ); sound->setSamplingRate( popplerSound->samplingRate() ); sound->setChannels( popplerSound->channels() ); sound->setBitsPerSample( popplerSound->bitsPerSample() ); switch ( popplerSound->soundEncoding() ) { case Poppler::SoundObject::Raw: sound->setSoundEncoding( Okular::Sound::Raw ); break; case Poppler::SoundObject::Signed: sound->setSoundEncoding( Okular::Sound::Signed ); break; case Poppler::SoundObject::muLaw: sound->setSoundEncoding( Okular::Sound::muLaw ); break; case Poppler::SoundObject::ALaw: sound->setSoundEncoding( Okular::Sound::ALaw ); break; } return sound; } Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerMovie ) { Okular::Movie *movie = new Okular::Movie( popplerMovie->url() ); movie->setSize( popplerMovie->size() ); movie->setRotation( (Okular::Rotation)( popplerMovie->rotation() / 90 ) ); movie->setShowControls( popplerMovie->showControls() ); movie->setPlayMode( (Okular::Movie::PlayMode)popplerMovie->playMode() ); movie->setAutoPlay( false ); // will be triggered by external MovieAnnotation movie->setShowPosterImage( popplerMovie->showPosterImage() ); movie->setPosterImage( popplerMovie->posterImage() ); return movie; } Okular::Movie* createMovieFromPopplerScreen( const Poppler::LinkRendition *popplerScreen ) { Poppler::MediaRendition *rendition = popplerScreen->rendition(); Okular::Movie *movie = 0; if ( rendition->isEmbedded() ) movie = new Okular::Movie( rendition->fileName(), rendition->data() ); else movie = new Okular::Movie( rendition->fileName() ); movie->setSize( rendition->size() ); movie->setShowControls( rendition->showControls() ); if ( rendition->repeatCount() == 0 ) { movie->setPlayMode( Okular::Movie::PlayRepeat ); } else { movie->setPlayMode( Okular::Movie::PlayLimited ); movie->setPlayRepetitions( rendition->repeatCount() ); } movie->setAutoPlay( rendition->autoPlay() ); return movie; } #ifdef HAVE_POPPLER_0_36 QPair createMovieFromPopplerRichMedia( const Poppler::RichMediaAnnotation *popplerRichMedia ) { const QPair emptyResult(0, 0); /** * To convert a Flash/Video based RichMedia annotation to a movie, we search for the first * Flash/Video richmedia instance and parse the flashVars parameter for the 'source' identifier. * That identifier is then used to find the associated embedded file through the assets * mapping. */ const Poppler::RichMediaAnnotation::Content *content = popplerRichMedia->content(); if ( !content ) return emptyResult; const QList configurations = content->configurations(); if ( configurations.isEmpty() ) return emptyResult; const Poppler::RichMediaAnnotation::Configuration *configuration = configurations[0]; const QList instances = configuration->instances(); if ( instances.isEmpty() ) return emptyResult; const Poppler::RichMediaAnnotation::Instance *instance = instances[0]; if ( ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeFlash ) && ( instance->type() != Poppler::RichMediaAnnotation::Instance::TypeVideo ) ) return emptyResult; const Poppler::RichMediaAnnotation::Params *params = instance->params(); if ( !params ) return emptyResult; QString sourceId; bool playbackLoops = false; const QStringList flashVars = params->flashVars().split( QLatin1Char( '&' ) ); foreach ( const QString & flashVar, flashVars ) { const int pos = flashVar.indexOf( QLatin1Char( '=' ) ); if ( pos == -1 ) continue; const QString key = flashVar.left( pos ); const QString value = flashVar.mid( pos + 1 ); if ( key == QLatin1String( "source" ) ) sourceId = value; else if ( key == QLatin1String( "loop" ) ) playbackLoops = ( value == QLatin1String( "true" ) ? true : false ); } if ( sourceId.isEmpty() ) return emptyResult; const QList assets = content->assets(); if ( assets.isEmpty() ) return emptyResult; Poppler::RichMediaAnnotation::Asset *matchingAsset = 0; foreach ( Poppler::RichMediaAnnotation::Asset *asset, assets ) { if ( asset->name() == sourceId ) { matchingAsset = asset; break; } } if ( !matchingAsset ) return emptyResult; Poppler::EmbeddedFile *embeddedFile = matchingAsset->embeddedFile(); if ( !embeddedFile ) return emptyResult; Okular::EmbeddedFile *pdfEmbeddedFile = new PDFEmbeddedFile( embeddedFile ); Okular::Movie *movie = new Okular::Movie( embeddedFile->name(), embeddedFile->data() ); movie->setPlayMode( playbackLoops ? Okular::Movie::PlayRepeat : Okular::Movie::PlayLimited ); if ( popplerRichMedia && popplerRichMedia->settings() && popplerRichMedia->settings()->activation() ) { if ( popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageOpened || popplerRichMedia->settings()->activation()->condition() == Poppler::RichMediaAnnotation::Activation::PageVisible ) { movie->setAutoPlay( true ); } else { movie->setAutoPlay( false ); } } else { movie->setAutoPlay( false ); } return qMakePair(movie, pdfEmbeddedFile); } #endif /** * Note: the function will take ownership of the popplerLink object. */ Okular::Action* createLinkFromPopplerLink(const Poppler::Link *popplerLink) { if (!popplerLink) return nullptr; Okular::Action *link = 0; const Poppler::LinkGoto *popplerLinkGoto; const Poppler::LinkExecute *popplerLinkExecute; const Poppler::LinkBrowse *popplerLinkBrowse; const Poppler::LinkAction *popplerLinkAction; const Poppler::LinkSound *popplerLinkSound; const Poppler::LinkJavaScript *popplerLinkJS; const Poppler::LinkMovie *popplerLinkMovie; const Poppler::LinkRendition *popplerLinkRendition; Okular::DocumentViewport viewport; bool deletePopplerLink = true; switch(popplerLink->linkType()) { case Poppler::Link::None: break; case Poppler::Link::Goto: { popplerLinkGoto = static_cast(popplerLink); const Poppler::LinkDestination dest = popplerLinkGoto->destination(); const QString destName = dest.destinationName(); if (destName.isEmpty()) { fillViewportFromLinkDestination( viewport, dest ); link = new Okular::GotoAction(popplerLinkGoto->fileName(), viewport); } else { link = new Okular::GotoAction(popplerLinkGoto->fileName(), destName); } } break; case Poppler::Link::Execute: popplerLinkExecute = static_cast(popplerLink); link = new Okular::ExecuteAction( popplerLinkExecute->fileName(), popplerLinkExecute->parameters() ); break; case Poppler::Link::Browse: popplerLinkBrowse = static_cast(popplerLink); link = new Okular::BrowseAction( QUrl(popplerLinkBrowse->url()) ); break; case Poppler::Link::Action: popplerLinkAction = static_cast(popplerLink); link = new Okular::DocumentAction( (Okular::DocumentAction::DocumentActionType)popplerLinkAction->actionType() ); break; case Poppler::Link::Sound: { popplerLinkSound = static_cast(popplerLink); Poppler::SoundObject *popplerSound = popplerLinkSound->sound(); Okular::Sound *sound = createSoundFromPopplerSound( popplerSound ); link = new Okular::SoundAction( popplerLinkSound->volume(), popplerLinkSound->synchronous(), popplerLinkSound->repeat(), popplerLinkSound->mix(), sound ); } break; case Poppler::Link::JavaScript: { popplerLinkJS = static_cast(popplerLink); link = new Okular::ScriptAction( Okular::JavaScript, popplerLinkJS->script() ); } break; case Poppler::Link::Rendition: { deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references popplerLinkRendition = static_cast( popplerLink ); Okular::RenditionAction::OperationType operation = Okular::RenditionAction::None; switch ( popplerLinkRendition->action() ) { case Poppler::LinkRendition::NoRendition: operation = Okular::RenditionAction::None; break; case Poppler::LinkRendition::PlayRendition: operation = Okular::RenditionAction::Play; break; case Poppler::LinkRendition::StopRendition: operation = Okular::RenditionAction::Stop; break; case Poppler::LinkRendition::PauseRendition: operation = Okular::RenditionAction::Pause; break; case Poppler::LinkRendition::ResumeRendition: operation = Okular::RenditionAction::Resume; break; }; Okular::Movie *movie = 0; if ( popplerLinkRendition->rendition() ) movie = createMovieFromPopplerScreen( popplerLinkRendition ); Okular::RenditionAction *renditionAction = new Okular::RenditionAction( operation, movie, Okular::JavaScript, popplerLinkRendition->script() ); renditionAction->setNativeId( QVariant::fromValue( popplerLinkRendition ) ); link = renditionAction; } break; case Poppler::Link::Movie: { deletePopplerLink = false; // we'll delete it inside resolveMediaLinkReferences() after we have resolved all references popplerLinkMovie = static_cast( popplerLink ); Okular::MovieAction::OperationType operation = Okular::MovieAction::Play; switch ( popplerLinkMovie->operation() ) { case Poppler::LinkMovie::Play: operation = Okular::MovieAction::Play; break; case Poppler::LinkMovie::Stop: operation = Okular::MovieAction::Stop; break; case Poppler::LinkMovie::Pause: operation = Okular::MovieAction::Pause; break; case Poppler::LinkMovie::Resume: operation = Okular::MovieAction::Resume; break; }; Okular::MovieAction *movieAction = new Okular::MovieAction( operation ); movieAction->setNativeId( QVariant::fromValue( popplerLinkMovie ) ); link = movieAction; } break; } if ( deletePopplerLink ) delete popplerLink; return link; } /** * Note: the function will take ownership of the popplerLink objects. */ static QLinkedList generateLinks( const QList &popplerLinks ) { QLinkedList links; foreach(const Poppler::Link *popplerLink, popplerLinks) { QRectF linkArea = popplerLink->linkArea(); double nl = linkArea.left(), nt = linkArea.top(), nr = linkArea.right(), nb = linkArea.bottom(); // create the rect using normalized coords and attach the Okular::Link to it Okular::ObjectRect * rect = new Okular::ObjectRect( nl, nt, nr, nb, false, Okular::ObjectRect::Action, createLinkFromPopplerLink(popplerLink) ); // add the ObjectRect to the container links.push_front( rect ); } return links; } /** NOTES on threading: * internal: thread race prevention is done via the 'docLock' mutex. the * mutex is needed only because we have the asynchronous thread; else * the operations are all within the 'gui' thread, scheduled by the * Qt scheduler and no mutex is needed. * external: dangerous operations are all locked via mutex internally, and the * only needed external thing is the 'canGeneratePixmap' method * that tells if the generator is free (since we don't want an * internal queue to store PixmapRequests). A generatedPixmap call * without the 'ready' flag set, results in undefined behavior. * So, as example, printing while generating a pixmap asynchronously is safe, * it might only block the gui thread by 1) waiting for the mutex to unlock * in async thread and 2) doing the 'heavy' print operation. */ OKULAR_EXPORT_PLUGIN(PDFGenerator, "libokularGenerator_poppler.json") static void PDFGeneratorPopplerDebugFunction(const QString &message, const QVariant &closure) { Q_UNUSED(closure); qCDebug(OkularPdfDebug) << "[Poppler]" << message; } PDFGenerator::PDFGenerator( QObject *parent, const QVariantList &args ) : Generator( parent, args ), pdfdoc( 0 ), docSynopsisDirty( true ), docEmbeddedFilesDirty( true ), nextFontPage( 0 ), annotProxy( 0 ) { setFeature( Threaded ); setFeature( TextExtraction ); setFeature( FontInfo ); #ifdef Q_OS_WIN32 setFeature( PrintNative ); #else setFeature( PrintPostscript ); #endif if ( Okular::FilePrinter::ps2pdfAvailable() ) setFeature( PrintToFile ); setFeature( ReadRawData ); setFeature( TiledRendering ); // You only need to do it once not for each of the documents but it is cheap enough // so doing it all the time won't hurt either Poppler::setDebugErrorFunction(PDFGeneratorPopplerDebugFunction, QVariant()); } PDFGenerator::~PDFGenerator() { delete pdfOptionsPage; } //BEGIN Generator inherited functions Okular::Document::OpenResult PDFGenerator::loadDocumentWithPassword( const QString & filePath, QVector & pagesVector, const QString &password ) { #ifndef NDEBUG if ( pdfdoc ) { qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it."; return Okular::Document::OpenError; } #endif // create PDFDoc for the given file pdfdoc = Poppler::Document::load( filePath, 0, 0 ); return init(pagesVector, password); } Okular::Document::OpenResult PDFGenerator::loadDocumentFromDataWithPassword( const QByteArray & fileData, QVector & pagesVector, const QString &password ) { #ifndef NDEBUG if ( pdfdoc ) { qCDebug(OkularPdfDebug) << "PDFGenerator: multiple calls to loadDocument. Check it."; return Okular::Document::OpenError; } #endif // create PDFDoc for the given file pdfdoc = Poppler::Document::loadFromData( fileData, 0, 0 ); return init(pagesVector, password); } Okular::Document::OpenResult PDFGenerator::init(QVector & pagesVector, const QString &password) { if ( !pdfdoc ) return Okular::Document::OpenError; if ( pdfdoc->isLocked() ) { pdfdoc->unlock( password.toLatin1(), password.toLatin1() ); if ( pdfdoc->isLocked() ) { delete pdfdoc; pdfdoc = 0; return Okular::Document::OpenNeedsPassword; } } // build Pages (currentPage was set -1 by deletePages) int pageCount = pdfdoc->numPages(); if (pageCount < 0) { delete pdfdoc; pdfdoc = 0; return Okular::Document::OpenError; } pagesVector.resize(pageCount); rectsGenerated.fill(false, pageCount); annotationsHash.clear(); loadPages(pagesVector, 0, false); // update the configuration reparseConfig(); // create annotation proxy annotProxy = new PopplerAnnotationProxy( pdfdoc, userMutex() ); // the file has been loaded correctly return Okular::Document::OpenSuccess; } bool PDFGenerator::doCloseDocument() { // remove internal objects userMutex()->lock(); delete annotProxy; annotProxy = 0; delete pdfdoc; pdfdoc = 0; userMutex()->unlock(); docSynopsisDirty = true; docSyn.clear(); docEmbeddedFilesDirty = true; qDeleteAll(docEmbeddedFiles); docEmbeddedFiles.clear(); nextFontPage = 0; rectsGenerated.clear(); return true; } void PDFGenerator::loadPages(QVector &pagesVector, int rotation, bool clear) { // TODO XPDF 3.01 check const int count = pagesVector.count(); double w = 0, h = 0; for ( int i = 0; i < count ; i++ ) { // get xpdf page Poppler::Page * p = pdfdoc->page( i ); Okular::Page * page; if (p) { const QSizeF pSize = p->pageSizeF(); w = pSize.width() / 72.0 * dpi().width(); h = pSize.height() / 72.0 * dpi().height(); Okular::Rotation orientation = Okular::Rotation0; switch (p->orientation()) { case Poppler::Page::Landscape: orientation = Okular::Rotation90; break; case Poppler::Page::UpsideDown: orientation = Okular::Rotation180; break; case Poppler::Page::Seascape: orientation = Okular::Rotation270; break; case Poppler::Page::Portrait: orientation = Okular::Rotation0; break; } if (rotation % 2 == 1) qSwap(w,h); // init a Okular::page, add transition and annotation information page = new Okular::Page( i, w, h, orientation ); addTransition( p, page ); if ( true ) //TODO real check addAnnotations( p, page ); Poppler::Link * tmplink = p->action( Poppler::Page::Opening ); if ( tmplink ) { page->setPageAction( Okular::Page::Opening, createLinkFromPopplerLink( tmplink ) ); } tmplink = p->action( Poppler::Page::Closing ); if ( tmplink ) { page->setPageAction( Okular::Page::Closing, createLinkFromPopplerLink( tmplink ) ); } page->setDuration( p->duration() ); page->setLabel( p->label() ); addFormFields( p, page ); // kWarning(PDFDebug).nospace() << page->width() << "x" << page->height(); #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "load page" << i << "with rotation" << rotation << "and orientation" << orientation; #endif delete p; if (clear && pagesVector[i]) delete pagesVector[i]; } else { page = new Okular::Page( i, defaultPageWidth, defaultPageHeight, Okular::Rotation0 ); } // set the Okular::page at the right position in document's pages vector pagesVector[i] = page; } } Okular::DocumentInfo PDFGenerator::generateDocumentInfo( const QSet &keys ) const { Okular::DocumentInfo docInfo; docInfo.set( Okular::DocumentInfo::MimeType, QStringLiteral("application/pdf") ); userMutex()->lock(); if ( pdfdoc ) { // compile internal structure reading properties from PDFDoc if ( keys.contains( Okular::DocumentInfo::Title ) ) docInfo.set( Okular::DocumentInfo::Title, pdfdoc->info(QStringLiteral("Title")) ); if ( keys.contains( Okular::DocumentInfo::Subject ) ) docInfo.set( Okular::DocumentInfo::Subject, pdfdoc->info(QStringLiteral("Subject")) ); if ( keys.contains( Okular::DocumentInfo::Author ) ) docInfo.set( Okular::DocumentInfo::Author, pdfdoc->info(QStringLiteral("Author")) ); if ( keys.contains( Okular::DocumentInfo::Keywords ) ) docInfo.set( Okular::DocumentInfo::Keywords, pdfdoc->info(QStringLiteral("Keywords")) ); if ( keys.contains( Okular::DocumentInfo::Creator ) ) docInfo.set( Okular::DocumentInfo::Creator, pdfdoc->info(QStringLiteral("Creator")) ); if ( keys.contains( Okular::DocumentInfo::Producer ) ) docInfo.set( Okular::DocumentInfo::Producer, pdfdoc->info(QStringLiteral("Producer")) ); if ( keys.contains( Okular::DocumentInfo::CreationDate ) ) docInfo.set( Okular::DocumentInfo::CreationDate, QLocale().toString( pdfdoc->date(QStringLiteral("CreationDate")), QLocale::LongFormat ) ); if ( keys.contains( Okular::DocumentInfo::ModificationDate ) ) docInfo.set( Okular::DocumentInfo::ModificationDate, QLocale().toString( pdfdoc->date(QStringLiteral("ModDate")), QLocale::LongFormat ) ); if ( keys.contains( Okular::DocumentInfo::CustomKeys ) ) { int major, minor; pdfdoc->getPdfVersion(&major, &minor); docInfo.set( QStringLiteral("format"), i18nc( "PDF v. ", "PDF v. %1.%2", major, minor ), i18n( "Format" ) ); docInfo.set( QStringLiteral("encryption"), pdfdoc->isEncrypted() ? i18n( "Encrypted" ) : i18n( "Unencrypted" ), i18n("Security") ); docInfo.set( QStringLiteral("optimization"), pdfdoc->isLinearized() ? i18n( "Yes" ) : i18n( "No" ), i18n("Optimized") ); } docInfo.set( Okular::DocumentInfo::Pages, QString::number( pdfdoc->numPages() ) ); } userMutex()->unlock(); return docInfo; } const Okular::DocumentSynopsis * PDFGenerator::generateDocumentSynopsis() { if ( !docSynopsisDirty ) return &docSyn; if ( !pdfdoc ) return NULL; userMutex()->lock(); QDomDocument *toc = pdfdoc->toc(); userMutex()->unlock(); if ( !toc ) return NULL; addSynopsisChildren(toc, &docSyn); delete toc; docSynopsisDirty = false; return &docSyn; } static Okular::FontInfo::FontType convertPopplerFontInfoTypeToOkularFontInfoType( Poppler::FontInfo::Type type ) { switch ( type ) { case Poppler::FontInfo::Type1: return Okular::FontInfo::Type1; break; case Poppler::FontInfo::Type1C: return Okular::FontInfo::Type1C; break; case Poppler::FontInfo::Type3: return Okular::FontInfo::Type3; break; case Poppler::FontInfo::TrueType: return Okular::FontInfo::TrueType; break; case Poppler::FontInfo::CIDType0: return Okular::FontInfo::CIDType0; break; case Poppler::FontInfo::CIDType0C: return Okular::FontInfo::CIDType0C; break; case Poppler::FontInfo::CIDTrueType: return Okular::FontInfo::CIDTrueType; break; case Poppler::FontInfo::Type1COT: return Okular::FontInfo::Type1COT; break; case Poppler::FontInfo::TrueTypeOT: return Okular::FontInfo::TrueTypeOT; break; case Poppler::FontInfo::CIDType0COT: return Okular::FontInfo::CIDType0COT; break; case Poppler::FontInfo::CIDTrueTypeOT: return Okular::FontInfo::CIDTrueTypeOT; break; case Poppler::FontInfo::unknown: default: ; } return Okular::FontInfo::Unknown; } static Okular::FontInfo::EmbedType embedTypeForPopplerFontInfo( const Poppler::FontInfo &fi ) { Okular::FontInfo::EmbedType ret = Okular::FontInfo::NotEmbedded; if ( fi.isEmbedded() ) { if ( fi.isSubset() ) { ret = Okular::FontInfo::EmbeddedSubset; } else { ret = Okular::FontInfo::FullyEmbedded; } } return ret; } Okular::FontInfo::List PDFGenerator::fontsForPage( int page ) { Okular::FontInfo::List list; if ( page != nextFontPage ) return list; QList fonts; userMutex()->lock(); Poppler::FontIterator* it = pdfdoc->newFontIterator(page); if (it->hasNext()) { fonts = it->next(); } userMutex()->unlock(); foreach (const Poppler::FontInfo &font, fonts) { Okular::FontInfo of; of.setName( font.name() ); of.setType( convertPopplerFontInfoTypeToOkularFontInfoType( font.type() ) ); of.setEmbedType( embedTypeForPopplerFontInfo( font) ); of.setFile( font.file() ); of.setCanBeExtracted( of.embedType() != Okular::FontInfo::NotEmbedded ); QVariant nativeId; nativeId.setValue( font ); of.setNativeId( nativeId ); list.append( of ); } ++nextFontPage; return list; } const QList *PDFGenerator::embeddedFiles() const { if (docEmbeddedFilesDirty) { userMutex()->lock(); const QList &popplerFiles = pdfdoc->embeddedFiles(); foreach(Poppler::EmbeddedFile* pef, popplerFiles) { docEmbeddedFiles.append(new PDFEmbeddedFile(pef)); } userMutex()->unlock(); docEmbeddedFilesDirty = false; } return &docEmbeddedFiles; } QAbstractItemModel* PDFGenerator::layersModel() const { return pdfdoc->hasOptionalContent() ? pdfdoc->optionalContentModel() : NULL; } void PDFGenerator::opaqueAction( const Okular::BackendOpaqueAction *action ) { #ifdef HAVE_POPPLER_0_50 const Poppler::LinkOCGState *popplerLink = action->nativeId().value(); pdfdoc->optionalContentModel()->applyLink( const_cast< Poppler::LinkOCGState* >( popplerLink ) ); #else (void)action; #endif } bool PDFGenerator::isAllowed( Okular::Permission permission ) const { bool b = true; switch ( permission ) { case Okular::AllowModify: b = pdfdoc->okToChange(); break; case Okular::AllowCopy: b = pdfdoc->okToCopy(); break; case Okular::AllowPrint: b = pdfdoc->okToPrint(); break; case Okular::AllowNotes: b = pdfdoc->okToAddNotes(); break; case Okular::AllowFillForms: b = pdfdoc->okToFillForm(); break; default: ; } return b; } QImage PDFGenerator::image( Okular::PixmapRequest * request ) { // debug requests to this (xpdf) generator //qCDebug(OkularPdfDebug) << "id: " << request->id << " is requesting " << (request->async ? "ASYNC" : "sync") << " pixmap for page " << request->page->number() << " [" << request->width << " x " << request->height << "]."; // compute dpi used to get an image with desired width and height Okular::Page * page = request->page(); double pageWidth = page->width(), pageHeight = page->height(); if ( page->rotation() % 2 ) qSwap( pageWidth, pageHeight ); qreal fakeDpiX = request->width() / pageWidth * dpi().width(); qreal fakeDpiY = request->height() / pageHeight * dpi().height(); // generate links rects only the first time bool genObjectRects = !rectsGenerated.at( page->number() ); // 0. LOCK [waits for the thread end] userMutex()->lock(); // 1. Set OutputDev parameters and Generate contents // note: thread safety is set on 'false' for the GUI (this) thread Poppler::Page *p = pdfdoc->page(page->number()); // 2. Take data from outputdev and attach it to the Page QImage img; if (p) { if ( request->isTile() ) { QRect rect = request->normalizedRect().geometry( request->width(), request->height() ); img = p->renderToImage( fakeDpiX, fakeDpiY, rect.x(), rect.y(), rect.width(), rect.height(), Poppler::Page::Rotate0 ); } else { img = p->renderToImage(fakeDpiX, fakeDpiY, -1, -1, -1, -1, Poppler::Page::Rotate0 ); } } else { img = QImage( request->width(), request->height(), QImage::Format_Mono ); img.fill( Qt::white ); } if ( p && genObjectRects ) { // TODO previously we extracted Image type rects too, but that needed porting to poppler // and as we are not doing anything with Image type rects i did not port it, have a look at // dead gp_outputdev.cpp on image extraction page->setObjectRects( generateLinks(p->links()) ); rectsGenerated[ request->page()->number() ] = true; resolveMediaLinkReferences( page ); } // 3. UNLOCK [re-enables shared access] userMutex()->unlock(); delete p; return img; } template void resolveMediaLinks( Okular::Action *action, enum Okular::Annotation::SubType subType, QHash &annotationsHash ) { OkularLinkType *okularAction = static_cast( action ); const PopplerLinkType *popplerLink = action->nativeId().value(); QHashIterator it( annotationsHash ); while ( it.hasNext() ) { it.next(); if ( it.key()->subType() == subType ) { const PopplerAnnotationType *popplerAnnotation = static_cast( it.value() ); if ( popplerLink->isReferencedAnnotation( popplerAnnotation ) ) { okularAction->setAnnotation( static_cast( it.key() ) ); okularAction->setNativeId( QVariant() ); delete popplerLink; // delete the associated Poppler::LinkMovie object, it's not needed anymore break; } } } } void PDFGenerator::resolveMediaLinkReference( Okular::Action *action ) { if ( !action ) return; if ( (action->actionType() != Okular::Action::Movie) && (action->actionType() != Okular::Action::Rendition) ) return; resolveMediaLinks( action, Okular::Annotation::AMovie, annotationsHash ); resolveMediaLinks( action, Okular::Annotation::AScreen, annotationsHash ); } void PDFGenerator::resolveMediaLinkReferences( Okular::Page *page ) { resolveMediaLinkReference( const_cast( page->pageAction( Okular::Page::Opening ) ) ); resolveMediaLinkReference( const_cast( page->pageAction( Okular::Page::Closing ) ) ); foreach ( Okular::Annotation *annotation, page->annotations() ) { if ( annotation->subType() == Okular::Annotation::AScreen ) { Okular::ScreenAnnotation *screenAnnotation = static_cast( annotation ); resolveMediaLinkReference( screenAnnotation->additionalAction( Okular::Annotation::PageOpening ) ); resolveMediaLinkReference( screenAnnotation->additionalAction( Okular::Annotation::PageClosing ) ); } if ( annotation->subType() == Okular::Annotation::AWidget ) { Okular::WidgetAnnotation *widgetAnnotation = static_cast( annotation ); resolveMediaLinkReference( widgetAnnotation->additionalAction( Okular::Annotation::PageOpening ) ); resolveMediaLinkReference( widgetAnnotation->additionalAction( Okular::Annotation::PageClosing ) ); } } foreach ( Okular::FormField *field, page->formFields() ) resolveMediaLinkReference( field->activationAction() ); } Okular::TextPage* PDFGenerator::textPage( Okular::Page *page ) { #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "page" << page->number(); #endif // build a TextList... QList textList; double pageWidth, pageHeight; Poppler::Page *pp = pdfdoc->page( page->number() ); if (pp) { userMutex()->lock(); textList = pp->textList(); userMutex()->unlock(); QSizeF s = pp->pageSizeF(); pageWidth = s.width(); pageHeight = s.height(); delete pp; } else { pageWidth = defaultPageWidth; pageHeight = defaultPageHeight; } Okular::TextPage *tp = abstractTextPage(textList, pageHeight, pageWidth, (Poppler::Page::Rotation)page->orientation()); qDeleteAll(textList); return tp; } void PDFGenerator::requestFontData(const Okular::FontInfo &font, QByteArray *data) { Poppler::FontInfo fi = font.nativeId().value(); *data = pdfdoc->fontData(fi); } #define DUMMY_QPRINTER_COPY bool PDFGenerator::print( QPrinter& printer ) { + bool printAnnots = true; + bool forceRasterize = false; + + if ( pdfOptionsPage ) + { + printAnnots = pdfOptionsPage->printAnnots(); + forceRasterize = pdfOptionsPage->printForceRaster(); + } + #ifdef Q_OS_WIN + // Windows can only print by rasterization and with annotations, because that is + // currently the only way Okular implements printing without using UNIX-specific + // tools like 'lpr'. + forceRasterize = true; + printAnnots = true; +#endif + + if ( forceRasterize && printAnnots) + { QPainter painter; painter.begin(&printer); QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); for ( int i = 0; i < pageList.count(); ++i ) { if ( i != 0 ) printer.newPage(); const int page = pageList.at( i ) - 1; userMutex()->lock(); Poppler::Page *pp = pdfdoc->page( page ); if (pp) { +#ifdef Q_OS_WIN QImage img = pp->renderToImage( printer.physicalDpiX(), printer.physicalDpiY() ); +#else + // UNIX: Same resolution as the postscript rasterizer; see discussion at https://git.reviewboard.kde.org/r/130218/ + QImage img = pp->renderToImage( 300, 300 ); +#endif painter.drawImage( painter.window(), img, QRectF(0, 0, img.width(), img.height()) ); delete pp; } userMutex()->unlock(); } painter.end(); return true; + } -#else #ifdef DUMMY_QPRINTER_COPY // Get the real page size to pass to the ps generator QPrinter dummy( QPrinter::PrinterResolution ); dummy.setFullPage( true ); dummy.setOrientation( printer.orientation() ); dummy.setPageSize( printer.pageSize() ); dummy.setPaperSize( printer.paperSize( QPrinter::Millimeter ), QPrinter::Millimeter ); int width = dummy.width(); int height = dummy.height(); #else int width = printer.width(); int height = printer.height(); #endif if (width <= 0 || height <= 0) { lastPrintError = InvalidPageSizePrintError; return false; } // Create the tempfile to send to FilePrinter, which will manage the deletion QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); if ( !tf.open() ) { lastPrintError = TemporaryFileOpenPrintError; return false; } QString tempfilename = tf.fileName(); // Generate the list of pages to be printed as selected in the print dialog QList pageList = Okular::FilePrinter::pageList( printer, pdfdoc->numPages(), document()->currentPage() + 1, document()->bookmarkedPageList() ); // TODO rotation tf.setAutoRemove(false); QString pstitle = metaData(QStringLiteral("Title"), QVariant()).toString(); if ( pstitle.trimmed().isEmpty() ) { pstitle = document()->currentDocument().fileName(); } - bool printAnnots = true; - bool forceRasterize = false; - if ( pdfOptionsPage ) - { - printAnnots = pdfOptionsPage->printAnnots(); - forceRasterize = pdfOptionsPage->printForceRaster(); - } - Poppler::PSConverter *psConverter = pdfdoc->psConverter(); psConverter->setOutputDevice(&tf); psConverter->setPageList(pageList); psConverter->setPaperWidth(width); psConverter->setPaperHeight(height); psConverter->setRightMargin(0); psConverter->setBottomMargin(0); psConverter->setLeftMargin(0); psConverter->setTopMargin(0); psConverter->setStrictMargins(false); psConverter->setForceRasterize(forceRasterize); psConverter->setTitle(pstitle); if (!printAnnots) psConverter->setPSOptions(psConverter->psOptions() | Poppler::PSConverter::HideAnnotations ); userMutex()->lock(); if (psConverter->convert()) { userMutex()->unlock(); delete psConverter; tf.close(); int ret = Okular::FilePrinter::printFile( printer, tempfilename, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange() ); lastPrintError = Okular::FilePrinter::printError( ret ); return (lastPrintError == NoPrintError); } else { lastPrintError = FileConversionPrintError; delete psConverter; userMutex()->unlock(); } tf.close(); return false; -#endif } QVariant PDFGenerator::metaData( const QString & key, const QVariant & option ) const { if ( key == QLatin1String("StartFullScreen") ) { QMutexLocker ml(userMutex()); // asking for the 'start in fullscreen mode' (pdf property) if ( pdfdoc->pageMode() == Poppler::Document::FullScreen ) return true; } else if ( key == QLatin1String("NamedViewport") && !option.toString().isEmpty() ) { Okular::DocumentViewport viewport; QString optionString = option.toString(); // asking for the page related to a 'named link destination'. the // option is the link name. @see addSynopsisChildren. userMutex()->lock(); Poppler::LinkDestination *ld = pdfdoc->linkDestination( optionString ); userMutex()->unlock(); if ( ld ) { fillViewportFromLinkDestination( viewport, *ld ); } delete ld; if ( viewport.pageNumber >= 0 ) return viewport.toString(); } else if ( key == QLatin1String("DocumentTitle") ) { userMutex()->lock(); QString title = pdfdoc->info( QStringLiteral("Title") ); userMutex()->unlock(); return title; } else if ( key == QLatin1String("OpenTOC") ) { QMutexLocker ml(userMutex()); if ( pdfdoc->pageMode() == Poppler::Document::UseOutlines ) return true; } else if ( key == QLatin1String("DocumentScripts") && option.toString() == QLatin1String("JavaScript") ) { QMutexLocker ml(userMutex()); return pdfdoc->scripts(); } else if ( key == QLatin1String("HasUnsupportedXfaForm") ) { QMutexLocker ml(userMutex()); return pdfdoc->formType() == Poppler::Document::XfaForm; } else if ( key == QLatin1String("FormCalculateOrder") ) { #ifdef HAVE_POPPLER_0_53 QMutexLocker ml(userMutex()); return QVariant::fromValue>(pdfdoc->formCalculateOrder()); #endif } return QVariant(); } bool PDFGenerator::reparseConfig() { if ( !pdfdoc ) return false; bool somethingchanged = false; // load paper color QColor color = documentMetaData( PaperColorMetaData, true ).value< QColor >(); // if paper color is changed we have to rebuild every visible pixmap in addition // to the outputDevice. it's the 'heaviest' case, other effect are just recoloring // over the page rendered on 'standard' white background. if ( color != pdfdoc->paperColor() ) { userMutex()->lock(); pdfdoc->setPaperColor(color); userMutex()->unlock(); somethingchanged = true; } bool aaChanged = setDocumentRenderHints(); somethingchanged = somethingchanged || aaChanged; return somethingchanged; } void PDFGenerator::addPages( KConfigDialog *dlg ) { #ifdef HAVE_POPPLER_0_24 Ui_PDFSettingsWidget pdfsw; QWidget* w = new QWidget(dlg); pdfsw.setupUi(w); dlg->addPage(w, PDFSettings::self(), i18n("PDF"), QStringLiteral("application-pdf"), i18n("PDF Backend Configuration") ); #endif } bool PDFGenerator::setDocumentRenderHints() { bool changed = false; const Poppler::Document::RenderHints oldhints = pdfdoc->renderHints(); #define SET_HINT(hintname, hintdefvalue, hintflag) \ { \ bool newhint = documentMetaData(hintname, hintdefvalue).toBool(); \ if (newhint != oldhints.testFlag(hintflag)) \ { \ pdfdoc->setRenderHint(hintflag, newhint); \ changed = true; \ } \ } SET_HINT(GraphicsAntialiasMetaData, true, Poppler::Document::Antialiasing) SET_HINT(TextAntialiasMetaData, true, Poppler::Document::TextAntialiasing) SET_HINT(TextHintingMetaData, false, Poppler::Document::TextHinting) #undef SET_HINT #ifdef HAVE_POPPLER_0_24 // load thin line mode const int thinLineMode = PDFSettings::enhanceThinLines(); const bool enableThinLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Solid; const bool enableShapeLineSolid = thinLineMode == PDFSettings::EnumEnhanceThinLines::Shape; const bool thinLineSolidWasEnabled = (oldhints & Poppler::Document::ThinLineSolid) == Poppler::Document::ThinLineSolid; const bool thinLineShapeWasEnabled = (oldhints & Poppler::Document::ThinLineShape) == Poppler::Document::ThinLineShape; if (enableThinLineSolid != thinLineSolidWasEnabled) { pdfdoc->setRenderHint(Poppler::Document::ThinLineSolid, enableThinLineSolid); changed = true; } if (enableShapeLineSolid != thinLineShapeWasEnabled) { pdfdoc->setRenderHint(Poppler::Document::ThinLineShape, enableShapeLineSolid); changed = true; } #endif return changed; } Okular::ExportFormat::List PDFGenerator::exportFormats() const { static Okular::ExportFormat::List formats; if ( formats.isEmpty() ) { formats.append( Okular::ExportFormat::standardFormat( Okular::ExportFormat::PlainText ) ); } return formats; } bool PDFGenerator::exportTo( const QString &fileName, const Okular::ExportFormat &format ) { if ( format.mimeType().inherits( QStringLiteral( "text/plain" ) ) ) { QFile f( fileName ); if ( !f.open( QIODevice::WriteOnly ) ) return false; QTextStream ts( &f ); int num = document()->pages(); for ( int i = 0; i < num; ++i ) { QString text; userMutex()->lock(); Poppler::Page *pp = pdfdoc->page(i); if (pp) { text = pp->text(QRect()).normalized(QString::NormalizationForm_KC); } userMutex()->unlock(); ts << text; delete pp; } f.close(); return true; } return false; } //END Generator inherited functions inline void append (Okular::TextPage* ktp, const QString &s, double l, double b, double r, double t) { // kWarning(PDFDebug).nospace() << "text: " << s << " at (" << l << "," << t << ")x(" << r <<","<append(s, new Okular::NormalizedRect(l, t, r, b)); } Okular::TextPage * PDFGenerator::abstractTextPage(const QList &text, double height, double width,int rot) { Q_UNUSED(rot); Okular::TextPage* ktp=new Okular::TextPage; Poppler::TextBox *next; #ifdef PDFGENERATOR_DEBUG qCDebug(OkularPdfDebug) << "getting text page in generator pdf - rotation:" << rot; #endif QString s; bool addChar; foreach (Poppler::TextBox *word, text) { const int qstringCharCount = word->text().length(); next=word->nextWord(); int textBoxChar = 0; for (int j = 0; j < qstringCharCount; j++) { const QChar c = word->text().at(j); if (c.isHighSurrogate()) { s = c; addChar = false; } else if (c.isLowSurrogate()) { s += c; addChar = true; } else { s = c; addChar = true; } if (addChar) { QRectF charBBox = word->charBoundingBox(textBoxChar); append(ktp, (j==qstringCharCount-1 && !next) ? (s + QLatin1Char('\n')) : s, charBBox.left()/width, charBBox.bottom()/height, charBBox.right()/width, charBBox.top()/height); textBoxChar++; } } if ( word->hasSpaceAfter() && next ) { // TODO Check with a document with vertical text // probably won't work and we will need to do comparisons // between wordBBox and nextWordBBox to see if they are // vertically or horizontally aligned QRectF wordBBox = word->boundingBox(); QRectF nextWordBBox = next->boundingBox(); append(ktp, QStringLiteral(" "), wordBBox.right()/width, wordBBox.bottom()/height, nextWordBBox.left()/width, wordBBox.top()/height); } } return ktp; } void PDFGenerator::addSynopsisChildren( QDomNode * parent, QDomNode * parentDestination ) { // keep track of the current listViewItem QDomNode n = parent->firstChild(); while( !n.isNull() ) { // convert the node to an element (sure it is) QDomElement e = n.toElement(); // The name is the same QDomElement item = docSyn.createElement( e.tagName() ); parentDestination->appendChild(item); if (!e.attribute(QStringLiteral("ExternalFileName")).isNull()) item.setAttribute(QStringLiteral("ExternalFileName"), e.attribute(QStringLiteral("ExternalFileName"))); if (!e.attribute(QStringLiteral("DestinationName")).isNull()) item.setAttribute(QStringLiteral("ViewportName"), e.attribute(QStringLiteral("DestinationName"))); if (!e.attribute(QStringLiteral("Destination")).isNull()) { Okular::DocumentViewport vp; fillViewportFromLinkDestination( vp, Poppler::LinkDestination(e.attribute(QStringLiteral("Destination"))) ); item.setAttribute( QStringLiteral("Viewport"), vp.toString() ); } if (!e.attribute(QStringLiteral("Open")).isNull()) item.setAttribute(QStringLiteral("Open"), e.attribute(QStringLiteral("Open"))); if (!e.attribute(QStringLiteral("DestinationURI")).isNull()) item.setAttribute(QStringLiteral("URL"), e.attribute(QStringLiteral("DestinationURI"))); // descend recursively and advance to the next node if ( e.hasChildNodes() ) addSynopsisChildren( &n, & item ); n = n.nextSibling(); } } void PDFGenerator::addAnnotations( Poppler::Page * popplerPage, Okular::Page * page ) { #ifdef HAVE_POPPLER_0_28 QSet subtypes; subtypes << Poppler::Annotation::AFileAttachment << Poppler::Annotation::ASound << Poppler::Annotation::AMovie << Poppler::Annotation::AWidget << Poppler::Annotation::AScreen << Poppler::Annotation::AText << Poppler::Annotation::ALine << Poppler::Annotation::AGeom << Poppler::Annotation::AHighlight << Poppler::Annotation::AInk << Poppler::Annotation::AStamp << Poppler::Annotation::ACaret; QList popplerAnnotations = popplerPage->annotations( subtypes ); #else QList popplerAnnotations = popplerPage->annotations(); #endif foreach(Poppler::Annotation *a, popplerAnnotations) { bool doDelete = true; Okular::Annotation * newann = createAnnotationFromPopplerAnnotation( a, &doDelete ); if (newann) { page->addAnnotation(newann); if ( a->subType() == Poppler::Annotation::AScreen ) { Poppler::ScreenAnnotation *annotScreen = static_cast( a ); Okular::ScreenAnnotation *screenAnnotation = static_cast( newann ); // The activation action const Poppler::Link *actionLink = annotScreen->action(); if ( actionLink ) screenAnnotation->setAction( createLinkFromPopplerLink( actionLink ) ); // The additional actions const Poppler::Link *pageOpeningLink = annotScreen->additionalAction( Poppler::Annotation::PageOpeningAction ); if ( pageOpeningLink ) screenAnnotation->setAdditionalAction( Okular::Annotation::PageOpening, createLinkFromPopplerLink( pageOpeningLink ) ); const Poppler::Link *pageClosingLink = annotScreen->additionalAction( Poppler::Annotation::PageClosingAction ); if ( pageClosingLink ) screenAnnotation->setAdditionalAction( Okular::Annotation::PageClosing, createLinkFromPopplerLink( pageClosingLink ) ); } if ( a->subType() == Poppler::Annotation::AWidget ) { Poppler::WidgetAnnotation *annotWidget = static_cast( a ); Okular::WidgetAnnotation *widgetAnnotation = static_cast( newann ); // The additional actions const Poppler::Link *pageOpeningLink = annotWidget->additionalAction( Poppler::Annotation::PageOpeningAction ); if ( pageOpeningLink ) widgetAnnotation->setAdditionalAction( Okular::Annotation::PageOpening, createLinkFromPopplerLink( pageOpeningLink ) ); const Poppler::Link *pageClosingLink = annotWidget->additionalAction( Poppler::Annotation::PageClosingAction ); if ( pageClosingLink ) widgetAnnotation->setAdditionalAction( Okular::Annotation::PageClosing, createLinkFromPopplerLink( pageClosingLink ) ); } if ( !doDelete ) annotationsHash.insert( newann, a ); } if ( doDelete ) delete a; } } void PDFGenerator::addTransition( Poppler::Page * pdfPage, Okular::Page * page ) // called on opening when MUTEX is not used { Poppler::PageTransition *pdfTransition = pdfPage->transition(); if ( !pdfTransition || pdfTransition->type() == Poppler::PageTransition::Replace ) return; Okular::PageTransition *transition = new Okular::PageTransition(); switch ( pdfTransition->type() ) { case Poppler::PageTransition::Replace: // won't get here, added to avoid warning break; case Poppler::PageTransition::Split: transition->setType( Okular::PageTransition::Split ); break; case Poppler::PageTransition::Blinds: transition->setType( Okular::PageTransition::Blinds ); break; case Poppler::PageTransition::Box: transition->setType( Okular::PageTransition::Box ); break; case Poppler::PageTransition::Wipe: transition->setType( Okular::PageTransition::Wipe ); break; case Poppler::PageTransition::Dissolve: transition->setType( Okular::PageTransition::Dissolve ); break; case Poppler::PageTransition::Glitter: transition->setType( Okular::PageTransition::Glitter ); break; case Poppler::PageTransition::Fly: transition->setType( Okular::PageTransition::Fly ); break; case Poppler::PageTransition::Push: transition->setType( Okular::PageTransition::Push ); break; case Poppler::PageTransition::Cover: transition->setType( Okular::PageTransition::Cover ); break; case Poppler::PageTransition::Uncover: transition->setType( Okular::PageTransition::Uncover ); break; case Poppler::PageTransition::Fade: transition->setType( Okular::PageTransition::Fade ); break; } #ifdef HAVE_POPPLER_0_37 transition->setDuration( pdfTransition->durationReal() ); #else transition->setDuration( pdfTransition->duration() ); #endif switch ( pdfTransition->alignment() ) { case Poppler::PageTransition::Horizontal: transition->setAlignment( Okular::PageTransition::Horizontal ); break; case Poppler::PageTransition::Vertical: transition->setAlignment( Okular::PageTransition::Vertical ); break; } switch ( pdfTransition->direction() ) { case Poppler::PageTransition::Inward: transition->setDirection( Okular::PageTransition::Inward ); break; case Poppler::PageTransition::Outward: transition->setDirection( Okular::PageTransition::Outward ); break; } transition->setAngle( pdfTransition->angle() ); transition->setScale( pdfTransition->scale() ); transition->setIsRectangular( pdfTransition->isRectangular() ); page->setTransition( transition ); } void PDFGenerator::addFormFields( Poppler::Page * popplerPage, Okular::Page * page ) { QList popplerFormFields = popplerPage->formFields(); QLinkedList okularFormFields; foreach( Poppler::FormField *f, popplerFormFields ) { Okular::FormField * of = 0; switch ( f->type() ) { case Poppler::FormField::FormButton: of = new PopplerFormFieldButton( static_cast( f ) ); break; case Poppler::FormField::FormText: of = new PopplerFormFieldText( static_cast( f ) ); break; case Poppler::FormField::FormChoice: of = new PopplerFormFieldChoice( static_cast( f ) ); break; default: ; } if ( of ) // form field created, good - it will take care of the Poppler::FormField okularFormFields.append( of ); else // no form field available - delete the Poppler::FormField delete f; } if ( !okularFormFields.isEmpty() ) page->setFormFields( okularFormFields ); } PDFGenerator::PrintError PDFGenerator::printError() const { return lastPrintError; } QWidget* PDFGenerator::printConfigurationWidget() const { if ( !pdfOptionsPage ) { const_cast(this)->pdfOptionsPage = new PDFOptionsPage(); } return pdfOptionsPage; } bool PDFGenerator::supportsOption( SaveOption option ) const { switch ( option ) { case SaveChanges: { return true; } default: ; } return false; } bool PDFGenerator::save( const QString &fileName, SaveOptions options, QString *errorText ) { Q_UNUSED(errorText); Poppler::PDFConverter *pdfConv = pdfdoc->pdfConverter(); pdfConv->setOutputFileName( fileName ); if ( options & SaveChanges ) pdfConv->setPDFOptions( pdfConv->pdfOptions() | Poppler::PDFConverter::WithChanges ); QMutexLocker locker( userMutex() ); bool success = pdfConv->convert(); if (!success) { switch (pdfConv->lastError()) { case Poppler::BaseConverter::NotSupportedInputFileError: // This can only happen with Poppler before 0.22 which did not have qt5 version break; case Poppler::BaseConverter::NoError: case Poppler::BaseConverter::FileLockedError: // we can't get here break; case Poppler::BaseConverter::OpenOutputError: // the default text message is good for this case break; } } delete pdfConv; return success; } Okular::AnnotationProxy* PDFGenerator::annotationProxy() const { return annotProxy; } #include "generator_pdf.moc" Q_LOGGING_CATEGORY(OkularPdfDebug, "org.kde.okular.generators.pdf", QtWarningMsg) /* kate: replace-tabs on; indent-width 4; */ diff --git a/generators/poppler/libokularGenerator_poppler.json b/generators/poppler/libokularGenerator_poppler.json index bcf56f21f..9444b60af 100644 --- a/generators/poppler/libokularGenerator_poppler.json +++ b/generators/poppler/libokularGenerator_poppler.json @@ -1,101 +1,103 @@ { "KPlugin": { "Authors": [ { "Email": "aacid@kde.org", "Name": "Albert Astals Cid", "Name[ia]": "Albert Astals Cid", "Name[sr@ijekavian]": "Алберт Асталс Сид", "Name[sr@ijekavianlatin]": "Albert Astals Sid", "Name[sr@latin]": "Albert Astals Sid", "Name[sr]": "Алберт Асталс Сид", "Name[x-test]": "xxAlbert Astals Cidxx" } ], "Copyright": "© 2005-2008 Albert Astals Cid", "Copyright[et]": "© 2005-2008: Albert Astals Cid", "Copyright[fi]": "© 2005–2008 Albert Astals Cid", "Copyright[ia]": "Albert Astals Cid", "Copyright[nn]": "© 2005–2008 Albert Astals Cid", "Copyright[ru]": "© Albert Astals Cid, 2005-2008", "Copyright[sr@ijekavian]": "© 2005–2008, Алберт Асталс Сид", "Copyright[sr@ijekavianlatin]": "© 2005–2008, Albert Astals Sid", "Copyright[sr@latin]": "© 2005–2008, Albert Astals Sid", "Copyright[sr]": "© 2005–2008, Алберт Асталс Сид", "Copyright[uk]": "© Albert Astals Cid, 2005–2008", "Copyright[x-test]": "xx© 2005-2008 Albert Astals Cidxx", "Description": "A PDF file renderer", "Description[ca@valencia]": "Un renderitzador de fitxers PDF", "Description[ca]": "Un renderitzador de fitxers PDF", "Description[cs]": "Vykreslovač PDF souborů", "Description[de]": "Ein Renderer für PDF-Dateien", "Description[el]": "Πρόγραμμα αποτύπωσης για PDF αρχεία", "Description[es]": "Un visor de archivos PDF", "Description[et]": "PDF-faili renderdaja", "Description[fi]": "PDF-tiedostohahmonnin", "Description[fr]": "Système de rendu pour les fichiers « PDF »", + "Description[gl]": "Un visor de ficheiros PDF", "Description[ia]": "Un renditor de file de PDF", "Description[it]": "Un visualizzatore di file PDF", "Description[ko]": "PDF 파일 렌더러", "Description[nl]": "Een PDF-bestandsviewer", "Description[nn]": "Ein gjengjevar for PDF-filer", "Description[pl]": "Wyświetlanie pliku PDF", "Description[pt]": "Um visualizador de ficheiros PDF", "Description[ru]": "Модуль поддержки формата PDF", "Description[sk]": "Vykresľovanie PDF súborov", "Description[sl]": "Izrisovalnik datotek PDF", "Description[sr@ijekavian]": "Рендерер ПДФ фајлова", "Description[sr@ijekavianlatin]": "Renderer PDF fajlova", "Description[sr@latin]": "Renderer PDF fajlova", "Description[sr]": "Рендерер ПДФ фајлова", "Description[sv]": "Ett återgivningsprogram för PDF-filer", "Description[tr]": "PDF dosyası oluşturucu", "Description[uk]": "Програма для показу файлів PDF", "Description[x-test]": "xxA PDF file rendererxx", "Description[zh_CN]": "PDF 文件渲染器", "Description[zh_TW]": "PDF 檔成像器", "Id": "okular_poppler", "License": "GPL", "MimeTypes": [ "application/x-pdf", "application/pdf", "application/x-wwf" ], "Name": "PDF Backend", "Name[ca@valencia]": "Dorsal per a PDF", "Name[ca]": "Dorsal per a PDF", "Name[cs]": "Podpůrná vrstva PDF", "Name[de]": "PDF-Anzeigemodul", "Name[el]": "Σύστημα υποστήριξης PDF", "Name[es]": "Motor para PDF", "Name[et]": "PDF-i taustaprogramm", "Name[fi]": "PDF-taustaosa", "Name[fr]": "Moteur PDF", + "Name[gl]": "Infraestrutura de PDF", "Name[ia]": "Retro-administration de PDF", "Name[it]": "Backend PDF", "Name[ko]": "PDF 백엔드", "Name[nl]": "PDF-backend", "Name[nn]": "PDF-motor", "Name[pl]": "Silnik PDF", "Name[pt]": "Infra-Estrutura de PDF", "Name[ru]": "Модуль поддержки формата PDF", "Name[sl]": "Zaledje za PDF", "Name[sr@ijekavian]": "Позадина за ПДФ", "Name[sr@ijekavianlatin]": "Pozadina za PDF", "Name[sr@latin]": "Pozadina za PDF", "Name[sr]": "Позадина за ПДФ", "Name[sv]": "PDF-gränssnitt", "Name[tr]": "PDF Arka Ucu", "Name[uk]": "Модуль PDF", "Name[x-test]": "xxPDF Backendxx", "Name[zh_CN]": "PDF 后端", "Name[zh_TW]": "PDF 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.6.5" }, "X-KDE-Priority": 1, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/poppler/org.kde.okular-poppler.metainfo.xml b/generators/poppler/org.kde.okular-poppler.metainfo.xml index 29ab0f0ef..469d864e7 100644 --- a/generators/poppler/org.kde.okular-poppler.metainfo.xml +++ b/generators/poppler/org.kde.okular-poppler.metainfo.xml @@ -1,74 +1,76 @@ org.kde.okular-poppler org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 PDF PDF PDF PDF PDF PDF PDF PDF PDF PDF PDF PDF + PDF PDF PDF PDF PDF PDF PDF PDF PDF ПДФ PDF ПДФ PDF PDF PDF PDF xxPDFxx PDF PDF Adds support for reading PDF documents Amiesta sofitu pa la llectura de documentos PDF Afegeix la implementació per llegir documents PDF Afig la implementació per llegir documents PDF Přidává podporu pro čtení dokumentů PDF Bietet Unterstützung zum Lesen von PDF-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων PDF Adds support for reading PDF documents Permite la lectura de documentos PDF PDF-dokumentide lugemise toetus Lisää PDF-tiedostojen lukutuen Permet la lecture des documents PDF + Permite ler documentos PDF. Aggiunge il supporto per la lettura di documenti PDF PDF 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van PDF-documenten toe Legg til støtte for å lesa PDF-dokument Dodaje obsługę czytania PDFów Adiciona o suporte para ler documentos em PDF Pridá podporu pre čítanie PDF dokumentov Doda podporo za branje dokumentov PDF Подршка за читање ПДФ докумената Podrška za čitanje PDF dokumenata Подршка за читање ПДФ докумената Podrška za čitanje PDF dokumenata Lägger till stöd för att läsa PDF-dokument PDF belgelerini okuma desteği ekler Додає підтримку читання документів PDF xxAdds support for reading PDF documentsxx 增加对 PDF 文档的阅读支持 加入讀取 PDF 文件的支援 application/pdf application/x-gzpdf application/x-bzpdf application/x-wwf https://okular.kde.org diff --git a/generators/spectre/libokularGenerator_ghostview.json b/generators/spectre/libokularGenerator_ghostview.json index 03971c890..605306fcf 100644 --- a/generators/spectre/libokularGenerator_ghostview.json +++ b/generators/spectre/libokularGenerator_ghostview.json @@ -1,99 +1,101 @@ { "KPlugin": { "Authors": [ { "Email": "aacid@kde.org", "Name": "Albert Astals Cid", "Name[ia]": "Albert Astals Cid", "Name[sr@ijekavian]": "Алберт Асталс Сид", "Name[sr@ijekavianlatin]": "Albert Astals Sid", "Name[sr@latin]": "Albert Astals Sid", "Name[sr]": "Алберт Асталс Сид", "Name[x-test]": "xxAlbert Astals Cidxx" } ], "Copyright": "© 2007-2008 Albert Astals Cid", "Copyright[et]": "© 2007-2008: Albert Astals Cid", "Copyright[fi]": "© 2007–2008 Albert Astals Cid", "Copyright[ia]": "Albert Astals Cid", "Copyright[nn]": "© 2007–2008 Albert Astals Cid", "Copyright[ru]": "© Albert Astals Cid, 2007-2008", "Copyright[sr@ijekavian]": "© 2007–2008, Алберт Асталс Сид", "Copyright[sr@ijekavianlatin]": "© 2007–2008, Albert Astals Sid", "Copyright[sr@latin]": "© 2007–2008, Albert Astals Sid", "Copyright[sr]": "© 2007–2008, Алберт Асталс Сид", "Copyright[uk]": "© Albert Astals Cid, 2007–2008", "Copyright[x-test]": "xx© 2007-2008 Albert Astals Cidxx", "Description": "A PostScript file renderer based on the Spectre library", "Description[ca@valencia]": "Un renderitzador de fitxers PostScript basat en la biblioteca Spectre", "Description[ca]": "Un renderitzador de fitxers PostScript basat en la biblioteca Spectre", "Description[cs]": "Vykreslovač souborů PostScript založený na knihovně Spectre.", "Description[de]": "Ein Renderer für PostScript-Dateien entwickelt aus der Spectre-Bibliothek", "Description[el]": "Πρόγραμμα αποτύπωσης αρχείων PostScript με βάση τη βιβλιοθήκη Spectre", "Description[es]": "Un visor de archivos PostScript basado en la biblioteca Spectre", "Description[et]": "PostScript-faili renderdaja Spectre teegi alusel", "Description[fi]": "Spectre-kirjastoon pohjautuva PostScript-tiedostohahmonnin", "Description[fr]": "Système de rendu pour les fichiers « PostScript » utilisant la librairie Spectre", + "Description[gl]": "Un renderizador de ficheiros PostScript baseado na biblioteca Spectre.", "Description[it]": "Un visualizzatore di file PostScript basato sulla libreria Spectre", "Description[ko]": "Spectre 라이브러리를 사용하는 포스트스크립트 파일 렌더러", "Description[nl]": "Een renderer van PostScript bestanden gebaseerd op de bibliotheek Spectre", "Description[nn]": "Ein gjengjevar for PostScript-filer, basert på Spectre-biblioteket", "Description[pl]": "Wyświetlanie plików PostScript oparte o bibliotekę Spectre", "Description[pt]": "Um visualzador de ficheiros PostScript com base na biblioteca Spectre", "Description[ru]": "Модуль поддержки формата PostScript на основе библиотеки Spectre", "Description[sk]": "Renderovač PostScript súborov založený na knižnici Spectre", "Description[sl]": "Izrisovalnik datotek PostScript, ki temelji na knjižnici Spectre", "Description[sr@ijekavian]": "Рендерер постскрипт фајлова на основу библиотеке Спектер", "Description[sr@ijekavianlatin]": "Renderer PostScript fajlova na osnovu biblioteke Spectre", "Description[sr@latin]": "Renderer PostScript fajlova na osnovu biblioteke Spectre", "Description[sr]": "Рендерер постскрипт фајлова на основу библиотеке Спектер", "Description[sv]": "Ett återgivningsprogram för Postscript-filer baserat på Spectre-biblioteket", "Description[tr]": "Spectre kitaplığına dayalı bir PostScript dosya oluşturucu", "Description[uk]": "Обробник файлів PostScript на основі бібліотеки Spectre", "Description[x-test]": "xxA PostScript file renderer based on the Spectre libraryxx", "Description[zh_CN]": "基于 Spectre 库的 PostScript 文件渲染器", "Description[zh_TW]": "基於 Spectre 函式庫的 PostScript 檔成像器", "Id": "okular_ghostview", "License": "GPL", "MimeTypes": [ "application/postscript", "image/x-eps" ], "Name": "PS Backend", "Name[ca@valencia]": "Dorsal PS", "Name[ca]": "Dorsal PS", "Name[cs]": "Podpůrná vrstva PS", "Name[de]": "PS-Anzeigemodul", "Name[el]": "Σύστημα υποστήριξης PS", "Name[es]": "Motor para PS", "Name[et]": "PS-i taustaprogramm", "Name[fi]": "PS-taustaosa", "Name[fr]": "Moteur PS", + "Name[gl]": "Infraestrutura para PS", "Name[ia]": "Retro-Administration de PS", "Name[it]": "Backend PS", "Name[ko]": "PS 백엔드", "Name[nl]": "PS-backend", "Name[nn]": "PS-motor", "Name[pl]": "Silnik PS", "Name[pt]": "Infra-Estrutura de PS", "Name[ru]": "Модуль поддержки формата PS", "Name[sl]": "Zaledje za PS", "Name[sr@ijekavian]": "Позадина за постскрипт", "Name[sr@ijekavianlatin]": "Pozadina za PostScript", "Name[sr@latin]": "Pozadina za PostScript", "Name[sr]": "Позадина за постскрипт", "Name[sv]": "PS-gränssnitt", "Name[tr]": "PS Arka Ucu", "Name[uk]": "Модуль PS", "Name[x-test]": "xxPS Backendxx", "Name[zh_CN]": "PS 后端", "Name[zh_TW]": "PS 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1.7" }, "X-KDE-Priority": 2, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/spectre/org.kde.okular-spectre.metainfo.xml b/generators/spectre/org.kde.okular-spectre.metainfo.xml index 5cbe4d6fe..3e9d4476b 100644 --- a/generators/spectre/org.kde.okular-spectre.metainfo.xml +++ b/generators/spectre/org.kde.okular-spectre.metainfo.xml @@ -1,76 +1,78 @@ org.kde.okular-spectre org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 PostScript PostScript PostScript PostScript PostScript PostScript PostScript PostScript PostScript PostScript PostScript PostScript + PostScript PostScript 포스트스크립트 PostScript PostScript PostScript PostScript PostScript PostScript Постскрипт PostScript Постскрипт PostScript Postscript PostScript PostScript xxPostScriptxx PostScript PostScript Adds support for reading PostScript documents Amiesta sofitu pa la llectura de documentos PostScript Afegeix la implementació per llegir documents PostScript Afig la implementació per llegir documents PostScript Přidává podporu pro čtení dokumentů PostScript Bietet Unterstützung zum Lesen von PostScript-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων PostScript Adds support for reading PostScript documents Permite la lectura de documentos PostScript PostScript-dokumentide lugemise toetus Lisää PostScript-tiedostojen lukutuen Permet la lecture des documents PostScript + Permite ler documentos PostScript. Aggiunge il supporto per la lettura di documenti PostScript 포스트스크립트 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van PostScript-documenten toe Legg til støtte for å lesa PostScript-dokument Dodaje obsługę czytania dokumentów PostScript Adiciona o suporte para ler documentos em PostScript Pridá podporu pre čítanie dokumentov PostScript Doda podporo za branje dokumentov PostScript Подршка за читање постскрипт докумената Podrška za čitanje PostScript dokumenata Подршка за читање постскрипт докумената Podrška za čitanje PostScript dokumenata Lägger till stöd för att läsa Postscript-dokument PostScript belgelerini okuma desteği ekler Додає підтримку читання документів PostScript xxAdds support for reading PostScript documentsxx 增加对 PostScript 文档的阅读支持 加入讀取 PostScript 文件的支援 application/postscript image/x-eps application/x-gzpostscript application/x-bzpostscript image/x-gzeps image/x-bzepsapplication/pdf https://okular.kde.org diff --git a/generators/tiff/libokularGenerator_tiff.json b/generators/tiff/libokularGenerator_tiff.json index 9d6863d74..19e698da7 100644 --- a/generators/tiff/libokularGenerator_tiff.json +++ b/generators/tiff/libokularGenerator_tiff.json @@ -1,97 +1,99 @@ { "KPlugin": { "Authors": [ { "Email": "pino@kde.org", "Name": "Pino Toscano", "Name[sr@ijekavian]": "Пино Тоскано", "Name[sr@ijekavianlatin]": "Pino Toskano", "Name[sr@latin]": "Pino Toskano", "Name[sr]": "Пино Тоскано", "Name[x-test]": "xxPino Toscanoxx" } ], "Copyright": "© 2006-2008 Pino Toscano", "Copyright[et]": "© 2006-2008: Pino Toscano", "Copyright[fi]": "© 2006–2008 Pino Toscano", "Copyright[nn]": "© 2006–2008 Pino Toscano", "Copyright[ru]": "© Pino Toscano, 2006-2008", "Copyright[sr@ijekavian]": "© 2006–2008, Пино Тоскано", "Copyright[sr@ijekavianlatin]": "© 2006–2008, Pino Toskano", "Copyright[sr@latin]": "© 2006–2008, Pino Toskano", "Copyright[sr]": "© 2006–2008, Пино Тоскано", "Copyright[uk]": "© Pino Toscano, 2006–2008", "Copyright[x-test]": "xx© 2006-2008 Pino Toscanoxx", "Description": "A TIFF backend", "Description[ca@valencia]": "Un dorsal pel TIFF", "Description[ca]": "Un dorsal pel TIFF", "Description[cs]": "Podpůrná vrstva pro TIFF", "Description[de]": "Ein Anzeigemodul für TIFF", "Description[el]": "Σύστημα υποστήριξης TIFF", "Description[es]": "Un motor para TIFF", "Description[et]": "TIFF-i taustaprogramm", "Description[fi]": "TIFF-taustaosa", "Description[fr]": "Un moteur TIFF", + "Description[gl]": "Unha infraestrutura para TIFF", "Description[ia]": "Un retro-administration de TIFF", "Description[it]": "Un backend per TIFF", "Description[ko]": "TIFF 백엔드", "Description[nl]": "Een TIFF-backend", "Description[nn]": "Ein TIFF-motor", "Description[pl]": "Jeden z silników TIFF", "Description[pt]": "Uma infra-estrutura de TIFF", "Description[ru]": "Модуль поддержки формата TIFF", "Description[sk]": "TIFF backend", "Description[sl]": "Zaledje za TIFF", "Description[sr@ijekavian]": "Позадина за ТИФФ", "Description[sr@ijekavianlatin]": "Pozadina za TIFF", "Description[sr@latin]": "Pozadina za TIFF", "Description[sr]": "Позадина за ТИФФ", "Description[sv]": "Ett TIFF-gränssnitt", "Description[tr]": "Bir TIFF arka ucu", "Description[uk]": "Програма для TIFF", "Description[x-test]": "xxA TIFF backendxx", "Description[zh_CN]": "TIFF 后端", "Description[zh_TW]": "TIFF 後端介面", "Id": "okular_tiff", "License": "GPL", "MimeTypes": [ "image/tiff" ], "Name": "TIFF Backend", "Name[ca@valencia]": "Dorsal TIFF", "Name[ca]": "Dorsal TIFF", "Name[cs]": "Podpůrná vrstva pro TIFF", "Name[de]": "Anzeigemodul für TIFF", "Name[el]": "Σύστημα υποστήριξης TIFF", "Name[es]": "Motor para TIFF", "Name[et]": "TIFF-i taustaprogramm", "Name[fi]": "TIFF-taustaosa", "Name[fr]": "Moteur TIFF", + "Name[gl]": "Infraestrutura para TIFF", "Name[ia]": "Retro-Administration de TIFF", "Name[it]": "Backend TIFF", "Name[ko]": "TIFF 백엔드", "Name[nl]": "TIFF-backend", "Name[nn]": "TIFF-motor", "Name[pl]": "Silnik TIFF", "Name[pt]": "Infra-Estrutura de TIFF", "Name[ru]": "Модуль поддержки формата TIFF", "Name[sl]": "Zaledje za TIFF", "Name[sr@ijekavian]": "Позадина за ТИФФ", "Name[sr@ijekavianlatin]": "Pozadina za TIFF", "Name[sr@latin]": "Pozadina za TIFF", "Name[sr]": "Позадина за ТИФФ", "Name[sv]": "TIFF-gränssnitt", "Name[tr]": "TIFF Arka Ucu", "Name[uk]": "Модуль TIFF", "Name[x-test]": "xxTIFF Backendxx", "Name[zh_CN]": "TIFF 后端", "Name[zh_TW]": "TIFF 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.2.4" }, "X-KDE-Priority": 4, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/tiff/org.kde.okular-tiff.metainfo.xml b/generators/tiff/org.kde.okular-tiff.metainfo.xml index 8e2dcf93a..a75d1aab3 100644 --- a/generators/tiff/org.kde.okular-tiff.metainfo.xml +++ b/generators/tiff/org.kde.okular-tiff.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-tiff org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Tiff Tiff TIFF TIFF Tiff Tiff Tiff Tiff Tiff TIFF TIFF Tiff + Tiff Tiff TIFF Tiff TIFF Tiff TIFF Tiff Tiff ТИФФ TIFF ТИФФ TIFF TIFF Tiff Tiff xxTiffxx TIFF Tiff Adds support for reading Tiff documents Amiesta sofitu pa la llectura de documentos Tiff Afegeix la implementació per llegir documents TIFF Afig la implementació per llegir documents TIFF Přidává podporu pro čtení dokumentů Tiff Bietet Unterstützung zum Lesen von TIFF-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων Tiff Adds support for reading Tiff documents Permite la lectura de documentos Tiff TIFF-dokumentide lugemise toetus Lisää TIFF-kuvatiedostojen lukutuen Permet la lecture des documents Tiff + Permite ler documentos Tiff. Aggiunge il supporto per la lettura di documenti Tiff TIFF 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van Tiff-documenten toe Legg til støtte for å lesa TIFF-dokument Dodaje obsługę czytania dokumentów Tiff Adiciona o suporte para ler documentos em TIFF Pridá podporu pre čítanie Tiff dokumentov Doda podporo za branje dokumentov Tiff Подршка за читање ТИФФ докумената Podrška za čitanje TIFF dokumenata Подршка за читање ТИФФ докумената Podrška za čitanje TIFF dokumenata Lägger till stöd för att läsa TIFF-dokument Tiff belgelerini okuma desteği ekler Додає підтримку читання документів TIFF xxAdds support for reading Tiff documentsxx 增加对 TIFF 文档的阅读支持 加入讀取 Tiff 文件的支援 image/tiff https://okular.kde.org diff --git a/generators/txt/libokularGenerator_txt.json b/generators/txt/libokularGenerator_txt.json index 4a414ec38..089f937a5 100644 --- a/generators/txt/libokularGenerator_txt.json +++ b/generators/txt/libokularGenerator_txt.json @@ -1,96 +1,98 @@ { "KPlugin": { "Authors": [ { "Email": "a3at.mail@gmail.com", "Name": "Azat Khuzhin", "Name[ru]": "Азат Хужин", "Name[sr@ijekavian]": "Азат Хужин", "Name[sr@ijekavianlatin]": "Azat Hužin", "Name[sr@latin]": "Azat Hužin", "Name[sr]": "Азат Хужин", "Name[x-test]": "xxAzat Khuzhinxx" } ], "Copyright": "© 2013 Azat Khuzhin", "Copyright[et]": "© 2013: Azat Khuzhin", "Copyright[ru]": "© Азат Хужин, 2013", "Copyright[sr@ijekavian]": "© 2013, Азат Хужин", "Copyright[sr@ijekavianlatin]": "© 2013, Azat Hužin", "Copyright[sr@latin]": "© 2013, Azat Hužin", "Copyright[sr]": "© 2013, Азат Хужин", "Copyright[uk]": "© Azat Khuzhin, 2013", "Copyright[x-test]": "xx© 2013 Azat Khuzhinxx", "Description": "Txt backend", "Description[ca@valencia]": "Dorsal Txt", "Description[ca]": "Dorsal Txt", "Description[cs]": "Podpůrná vrstva txt", "Description[de]": "Anzeigemodul für Textformat", "Description[el]": "Σύστημα υποστήριξης txt", "Description[es]": "Motor Txt", "Description[et]": "Txt taustaprogramm", "Description[fi]": "Txt-taustaosa", "Description[fr]": "Moteur Txt", + "Description[gl]": "Infraestrutura para txt.", "Description[ia]": "Retro-administration per Txt ", "Description[it]": "Backend di testo semplice", "Description[ko]": "TXT 백엔드", "Description[nl]": "Txt-backend", "Description[nn]": "Txt-motor", "Description[pl]": "Silnik tekstowy", "Description[pt]": "Infra-estrutura de TXT", "Description[ru]": "Модуль поддержки формата Txt", "Description[sl]": "Zaledje za txt", "Description[sr@ijekavian]": "Позадина за текст", "Description[sr@ijekavianlatin]": "Pozadina za tekst", "Description[sr@latin]": "Pozadina za tekst", "Description[sr]": "Позадина за текст", "Description[sv]": "Textgränssnitt", "Description[tr]": "Txt arka ucu", "Description[uk]": "Модуль тексту", "Description[x-test]": "xxTxt backendxx", "Description[zh_CN]": "纯文本后端", "Description[zh_TW]": "Txt 後端介面", "Id": "okular_txt", "License": "GPL", "MimeTypes": [ "text/plain" ], "Name": "Txt Backend", "Name[ca@valencia]": "Dorsal Txt", "Name[ca]": "Dorsal Txt", "Name[cs]": "Podpůrná vrstva txt", "Name[de]": "Anzeigemodul für Textformat", "Name[el]": "Σύστημα υποστήριξης txt", "Name[es]": "Motor para Txt", "Name[et]": "Txt taustaprogramm", "Name[fi]": "Txt-taustaosa", "Name[fr]": "Moteur Txt", + "Name[gl]": "Infraestrutura para txt", "Name[ia]": "Retro-administration per Txt ", "Name[it]": "Backend di testo semplice", "Name[ko]": "TXT 백엔드", "Name[nl]": "Txt-backend", "Name[nn]": "Txt-motor", "Name[pl]": "Silnik tekstowy", "Name[pt]": "Infra-Estrutura de TXT", "Name[ru]": "Модуль поддержки формата Txt", "Name[sk]": "Txt backend", "Name[sl]": "Zaledje za txt", "Name[sr@ijekavian]": "Позадина за текст", "Name[sr@ijekavianlatin]": "Pozadina za tekst", "Name[sr@latin]": "Pozadina za tekst", "Name[sr]": "Позадина за текст", "Name[sv]": "Textgränssnitt", "Name[tr]": "Txt Arka Ucu", "Name[uk]": "Модуль тексту", "Name[x-test]": "xxTxt Backendxx", "Name[zh_CN]": "纯文本后端", "Name[zh_TW]": "Txt 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.1" }, "X-KDE-Priority": 2, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": true } diff --git a/generators/txt/org.kde.okular-txt.metainfo.xml b/generators/txt/org.kde.okular-txt.metainfo.xml index 04ec24c77..e8090a68b 100644 --- a/generators/txt/org.kde.okular-txt.metainfo.xml +++ b/generators/txt/org.kde.okular-txt.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-txt org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Text Testu Text Text Text Text Text Text Texto Tekst Teksti Text + Texto Testo 텍스트 Tekst Tekst Tekst Texto Text Besedilo Текст Tekst Текст Tekst Text Metin Текст xxTextxx 文本 文字 Adds support for reading plain text documents Amiesta sofitu pa la llectura de documentos de testu planu Afegeix la implementació per llegir documents de text net Afig la implementació per llegir documents de text net Přidává podporu pro čtení dokumentů v čistém textu Bietet Unterstützung zum Lesen von Text-Dokumenten Προσθέτει υποστήριξη για την ανάνγωση εγγράφων απλού κειμένου Adds support for reading plain text documents Permite la lectura de documentos de texto sin formato Lihttekstidokumentide lugemise toetus Lisää paljaiden tekstitiedostojen lukutuen Permet la lecture des fichiers texte + Permite ler documentos de texto simple. Aggiunge il supporto per la lettura di documenti in testo semplice 일반 텍스트 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van gewone tekstdocumenten toe Legg til støtte for å lesa reintekstdokument Dodaje obsługę czytania zwykłych plików tekstowych Adiciona o suporte para ler documentos em texto simples Pridá podporu pre čítanie čistých textových dokumentov Doda podporo za branje besedilnih dokumentov Подршка за читање докумената у обичном тексту Podrška za čitanje dokumenata u običnom tekstu Подршка за читање докумената у обичном тексту Podrška za čitanje dokumenata u običnom tekstu Lägger till stöd för att läsa enkla textdokument Düz metin belgelerini okuma desteği ekler Додає підтримку читання звичайних текстових документів xxAdds support for reading plain text documentsxx 增加对纯文本文档的支持 加入讀取純文字文件的支援 text/plain https://okular.kde.org diff --git a/generators/xps/libokularGenerator_xps.json b/generators/xps/libokularGenerator_xps.json index 2e48a3e9c..4f411f39e 100644 --- a/generators/xps/libokularGenerator_xps.json +++ b/generators/xps/libokularGenerator_xps.json @@ -1,118 +1,120 @@ { "KPlugin": { "Authors": [ { "Email": "bradh@frogmouth.net", "Name": "Brad Hards", "Name[sr@ijekavian]": "Бред Хардс", "Name[sr@ijekavianlatin]": "Bred Hards", "Name[sr@latin]": "Bred Hards", "Name[sr]": "Бред Хардс", "Name[x-test]": "xxBrad Hardsxx" }, { "Email": "jiri.klement@gmail.com", "Name": "Jiri Klement", "Name[cs]": "Jiří Klement", "Name[sr@ijekavian]": "Јиржи Клемент", "Name[sr@ijekavianlatin]": "Jirži Klement", "Name[sr@latin]": "Jirži Klement", "Name[sr]": "Јиржи Клемент", "Name[x-test]": "xxJiri Klementxx" }, { "Email": "pino@kde.org", "Name": "Pino Toscano", "Name[sr@ijekavian]": "Пино Тоскано", "Name[sr@ijekavianlatin]": "Pino Toskano", "Name[sr@latin]": "Pino Toskano", "Name[sr]": "Пино Тоскано", "Name[x-test]": "xxPino Toscanoxx" } ], "Copyright": "© 2006-2007 Brad Hards\n© 2007 Jiri Klement\n© 2008 Pino Toscano", "Copyright[et]": "© 2006-2007: Brad Hards\n© 2007: Jiri Klement\n© 2008: Pino Toscano", "Copyright[fi]": "© 2006–2007 Brad Hards\n© 2007 Jiri Klement\n© 2008 Pino Toscano", "Copyright[nn]": "© 2006–2007 Brad Hards\n© 2007 Jiri Klement\n© 2008 Pino Toscano", "Copyright[ru]": "© Brad Hards, 2006-2007\n© Jiri Klement, 2007\n© Pino Toscano, 2008", "Copyright[sr@ijekavian]": "© 2006–2007, Бред Хардс\n© 2007, Јиржи Клемент\n© 2008, Пино Тоскано", "Copyright[sr@ijekavianlatin]": "© 2006–2007, Bred Hards\n© 2007, Jirži Klement\n© 2008, Pino Toskano", "Copyright[sr@latin]": "© 2006–2007, Bred Hards\n© 2007, Jirži Klement\n© 2008, Pino Toskano", "Copyright[sr]": "© 2006–2007, Бред Хардс\n© 2007, Јиржи Клемент\n© 2008, Пино Тоскано", "Copyright[uk]": "© Brad Hards, 2006–2007\n© Jiri Klement, 2007\n© Pino Toscano, 2008", "Copyright[x-test]": "xx© 2006-2007 Brad Hards\n© 2007 Jiri Klement\n© 2008 Pino Toscanoxx", "Description": "An XPS backend", "Description[ca@valencia]": "Un dorsal per a l'XPS", "Description[ca]": "Un dorsal per a l'XPS", "Description[cs]": "Podpůrná vrstva XPS", "Description[de]": "Ein Anzeigemodul für XPS", "Description[el]": "Σύστημα υποστήριξης XPS", "Description[es]": "Un motor para XPS", "Description[et]": "XPS-i taustaprogramm", "Description[fi]": "XPS-taustaosa", "Description[fr]": "Un moteur XPS", + "Description[gl]": "Unha infraestrutura para XPS", "Description[ia]": "Un retro-administration de XPS", "Description[it]": "Un backend per XPS", "Description[ko]": "XPS 백엔드", "Description[nl]": "Een XPS-backend", "Description[nn]": "Ein XPS-motor", "Description[pl]": "Jeden z silników XPS", "Description[pt]": "Uma infra-estrutura de XPS", "Description[ru]": "Модуль поддержки формата XPS", "Description[sk]": "XPS backend", "Description[sl]": "Zaledje za XPS", "Description[sr@ijekavian]": "Позадина за ИксПС", "Description[sr@ijekavianlatin]": "Pozadina za XPS", "Description[sr@latin]": "Pozadina za XPS", "Description[sr]": "Позадина за ИксПС", "Description[sv]": "Ett XPS-gränssnitt", "Description[tr]": "Bir XPS arka ucu", "Description[uk]": "Програма для XPS", "Description[x-test]": "xxAn XPS backendxx", "Description[zh_CN]": "XPS 后端", "Description[zh_TW]": "XPS 後端介面", "Id": "okular_xps", "License": "GPL", "MimeTypes": [ "application/oxps", "application/vnd.ms-xpsdocument" ], "Name": "XPS Backend", "Name[ast]": "Backend XPS", "Name[ca@valencia]": "Dorsal XPS", "Name[ca]": "Dorsal XPS", "Name[cs]": "Podpůrná vrstva XPS", "Name[de]": "Anzeigemodul für XPS", "Name[el]": "Σύστημα υποστήριξης XPS", "Name[es]": "Motor para XPS", "Name[et]": "XPS-i taustaprogramm", "Name[fi]": "XPS-taustaosa", "Name[fr]": "Moteur XPS", + "Name[gl]": "Infraestrutura para XPS", "Name[ia]": "Retro-Administration de XPS", "Name[it]": "Backend XPS", "Name[ko]": "XPS 백엔드", "Name[nl]": "XPS-backend", "Name[nn]": "XPS-motor", "Name[pl]": "Silnik XPS", "Name[pt]": "Infra-Estrutura de XPS", "Name[ru]": "Модуль поддержки формата XPS", "Name[sl]": "Zaledje za XPS", "Name[sr@ijekavian]": "Позадина за ИксПС", "Name[sr@ijekavianlatin]": "Pozadina za XPS", "Name[sr@latin]": "Pozadina za XPS", "Name[sr]": "Позадина за ИксПС", "Name[sv]": "XPS-gränssnitt", "Name[tr]": "XPS Arka Ucu", "Name[uk]": "Модуль XPS", "Name[x-test]": "xxXPS Backendxx", "Name[zh_CN]": "XPS 后端", "Name[zh_TW]": "XPS 後端介面", "ServiceTypes": [ "okular/Generator" ], "Version": "0.3.3" }, "X-KDE-Priority": 4, "X-KDE-okularAPIVersion": 1, "X-KDE-okularHasInternalSettings": false } diff --git a/generators/xps/org.kde.okular-xps.metainfo.xml b/generators/xps/org.kde.okular-xps.metainfo.xml index 2488950b5..2cbcd9086 100644 --- a/generators/xps/org.kde.okular-xps.metainfo.xml +++ b/generators/xps/org.kde.okular-xps.metainfo.xml @@ -1,71 +1,73 @@ org.kde.okular-xps org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 XPS XPS XPS XPS XPS XPS XPS XPS XPS XPS XPS XPS + XPS XPS XPS XPS XPS XPS XPS XPS XPS ИксПС XPS ИксПС XPS XPS XPS XPS xxXPSxx XPS XPS Adds support for reading XPS documents Amiesta sofitu pa la llectura de documentos XPS Afegeix la implementació per llegir documents XPS Afig la implementació per llegir documents XPS Přidává podporu pro čtení dokumentů XPS Bietet Unterstützung zum Lesen von XPS-Dokumenten Προσθέτει υποστήριξη για την ανάγνωση εγγράφων XPS Adds support for reading XPS documents Permite la lectura de documentos XPS XPS-dokumentide lugemise toetus Lisää XPS-tiedostojen lukutuen Permet la lecture des documents XPS + Permite ler documentos XPS. Aggiunge il supporto per la lettura di documenti XPS XPS 문서 읽기 지원 추가 Voegt ondersteuning voor lezen van XPS-documenten toe Legg til støtte for å lesa XPS-dokument Dodaje obsługę dokumnetów XPS Adiciona o suporte para ler documentos em XPS Pridá podporu pre čítanie XPS dokumentov Doda podporo za branje dokumentov XPS Подршка за читање ИксПС докумената Podrška za čitanje XPS dokumenata Подршка за читање ИксПС докумената Podrška za čitanje XPS dokumenata Lägger till stöd för att läsa XPS-dokument XPS belgelerini okuma desteği ekler Додає підтримку читання документів XPS xxAdds support for reading XPS documentsxx 增加对 XPS 文档的阅读支持 加入讀取 XPS 文件的支援 text/plain https://okular.kde.org diff --git a/shell/org.kde.okular.appdata.xml b/shell/org.kde.okular.appdata.xml index cdd615e24..1f0a31913 100644 --- a/shell/org.kde.okular.appdata.xml +++ b/shell/org.kde.okular.appdata.xml @@ -1,230 +1,237 @@ org.kde.okular.desktop CC0-1.0 GPL-2.0+ and GFDL-1.3 Okular Okular Okular Okular Okular Okular Okular Okular Okular Okular Okular Okular + Okular Okular Okular Okular Okular Okular Okular Okular Okular Окулар Okular Окулар Okular Okular Okular Okular xxOkularxx Okular Okular Document Viewer Visor de documentos Visualitzador de documents Visualitzador de documents Prohlížeč dokumentů Dokumentenbetrachter Πρόγραμμα προβολής εγγράφων Document Viewer Visor de documentos Dokumendinäitaja Asiakirjakatselin Afficheur de documents + Visor de documentos Visore di documenti 문서 뷰어 Documentenviewer Dokumentvisar Przeglądarka dokumentów Visualizador do Documento Prehliadač dokumentov Pregledovalnik dokumentov Приказивач докумената Prikazivač dokumenata Приказивач докумената Prikazivač dokumenata Dokumentvisare Belge Görüntüleyicisi Програма для перегляду документів xxDocument Viewerxx 文档查看器 文件檢視器

Okular is a universal document viewer developed by KDE. Okular works on multiple platforms, including but not limited to Linux, Windows, Mac OS X, *BSD, etc.

Okular ye un visor universal de documentos desendolcáu por KDE. Okular funciona en múltiples plataformes como Linux, *BSD, MacOS, Windows... etc.

L'Okular és un visualitzador universal de documenta desenvolupat pel KDE. L'Okular funciona en diverses plataformes, incloent però sense limitar en el Linux, Windows, Mac OS X, *BSD, etc.

L'Okular és un visualitzador universal de documenta desenvolupat pel KDE. L'Okular funciona en diverses plataformes, incloent però sense limitar en el Linux, Windows, Mac OS X, *BSD, etc.

Okular ist ein universeller Dokumentenbetrachter, der von KDE entwickelt wird. Okular ist auf verschiedenen Plattformen verfügbar, darunter auch Linux, Windows, Mac OS X, *BSD usw.

Το Okular είναι ένας καθολικός προβολέας εγγράφων που αναπτύχθηκε από το KDE. Το Okular λειτουργεί σε πολλές πλατφόρμες, όπως σε Linux, Windows, Mac OS X, *BSD, κλπ.

Okular is a universal document viewer developed by KDE. Okular works on multiple platforms, including but not limited to Linux, Windows, Mac OS X, *BSD, etc.

Okular es un visor universal de documentos desarrollado por KDE. Okular funciona en diversas plataformas, incluidas Linux, Windows, Mac OS X, *BSD, etc.

Okular on KDE välja töötatud universaalne dokumentide näitaja. Okular võib töötada eri platvormidel, kaasa arvatud Linux, Windows, Mac OS X, *BSD jne.

Okular on KDE:n kehittämä yleiskäyttöinen asiakirjakatselin. Okular toimii useissa ympäristöissä kuten Linuxissa, Windowsissa, Mac OS X:ssä, *BSD:ssä jne.

Okular est un afficheur universel de documents développé par KDE. Okular fonctionne sur de multiples plate-formes, comme Linux, Windows, Mac OS X, etc.

+

Okular é un visor de documentos universal desenvolvido por KDE. Okular funciona en varias plataformas, entre elas Linux, Windows, Mac OS X, *BSD, etc.

Okular è un lettore universale di documenti sviluppato da KDE. Okular funziona su varie piattaforme, incluse (ma non solo) Linux, Windows, Mac OS X, *BSD, ecc.

Okular는 만능 문서 뷰어입니다. Okular는 리눅스, macOS, Windows, *BSD 등 여러 플랫폼에서 동작합니다.

Okular is een universele documentviewer ontwikkeld door KDE. Okular werkt op meerdere platforms, inclusief, maar niet beperkt tot Linux, Windows, Mac OS X, *BSD, etc.

Okular er ein dokumentvisar for mange ulike filformat, og er utvikla av KDE. Okular køyrer på fleire plattformer, blant anna Linux, Windows, Mac OS X og *BSD.

Okular jest wszechstronną przeglądarką dokumentów opracowaną przez KDE. Okular działa na wielu platformach takich jak Linux, Windows, Mac OS X, *BSD, itp.

O Okular é um visualizador universal de documentos desenvolvido pelo KDE. O Okular funciona em diversas plataformas, incluindo mas não se limitando ao Linux, Windows, Mac OS X, *BSD, etc.

Okular je univerzálny prehliadač dokumentov vyvinutý v KDE. Okular beží na mnohých platformách, vrátane Linux, Windows, Mac OS X, *BSD, atď.

Okular je univerzalni pregledovalnik dokumentov, ki so ga razvili pri KDE. Dela v več okoljih vključno s sistemi Linux, Windows, Mac OS X in *BSD.

Окулар је универзални приказивач докумената, који развија КДЕ. Ради на већем броју платформи, укључујући Линукс, Виндоуз, МекОС X, БСД‑ове, итд.

Okular je univerzalni prikazivač dokumenata, koji razvija KDE. Radi na većem broju platformi, uključujući Linux, Windows, MacOS X, BSD‑ove, itd.

Окулар је универзални приказивач докумената, који развија КДЕ. Ради на већем броју платформи, укључујући Линукс, Виндоуз, МекОС X, БСД‑ове, итд.

Okular je univerzalni prikazivač dokumenata, koji razvija KDE. Radi na većem broju platformi, uključujući Linux, Windows, MacOS X, BSD‑ove, itd.

Okular är en universell dokumentvisare utvecklad av KDE. Okular fungerar på flera plattformar, inklusive men inte begränsat till Linux, Windows, Max OS X, *BSD, etc.

Okular, KDE tarafından geliştirilmiş evrensel bir belge görüntüleyicidir. Okular, Linux, Windows, Mac OS X, *BSD, vb. dahil ancak bunlarla sınırlı olmayan birden çok platformda çalışır.

Okular є універсальною програмою для перегляду документів, розробленою KDE. Okular може працювати на багатьох програмних платформах, зокрема Linux, Windows, Mac OS X, *BSD тощо.

xxOkular is a universal document viewer developed by KDE. Okular works on multiple platforms, including but not limited to Linux, Windows, Mac OS X, *BSD, etc.xx

Okular 是由 KDE 开发的通用文档查看器。Okular 可在多个平台上运行,包括但不是限于 Linux,Windows,Mac OS X,* BSD,等。

Okular 是一個由 KDE 開發的通用文件檢視器。Okular 可以在多種平臺上運作,包含但不限於 Linux, Windows, Mac OS X, *BSD 等等。

Features:

Carauterístiques:

Característiques:

Característiques:

Vlastnosti:

Funktionen:

Χαρακτηριστικά:

Features:

Funciones:

Omadused:

Ominaisuuksia:

Fonctionnalités :

+

Funcionalidades:

Funzionalità:

기능:

Kenmerken:

Funksjonar:

Możliwości:

Funcionalidades:

Funkcie:

Zmožnosti:

Могућности:

Mogućnosti:

Могућности:

Mogućnosti:

Funktioner:

Özellikler:

Можливості:

xxFeatures:xx

功能:

功能:

  • Supported Formats: PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Formatos sofitaos: PDF, PS, Tiff, CHM, DjVU, imáxenes, DVI, XPS, ODT, FictionBook, cómics, Pluker, Epub y Fax
  • Formats acceptats: PDF, PS, Tiff, CHM, DjVu, imatges, DVI, XPS, ODT, Fiction Book, llibres de còmic, Plucker, EPub, Fax
  • Formats acceptats: PDF, PS, Tiff, CHM, DjVu, imatges, DVI, XPS, ODT, Fiction Book, llibres de còmic, Plucker, EPub, Fax
  • Podporované formáty: PDF, PS, Tiff, CHM, DjVu, obrázky, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Unterstützte Formate: PDF, PS, Tiff, CHM, DjVu, Bilder, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Υποστηριζόμενοι τύποι αποθήκευσης: PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Supported Formats: PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Formatos permitidos: PDF, PS, Tiff, CHM, DjVu, imágenes, DVI, XPS, ODT, Fiction Book, libros de cómics, Plucker, EPub, Fax
  • Toetatud vormingud: PDF, PS, Tiff, CHM, DjVu, pildid, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, faks
  • Tuetut tiedostomuodot: PDF, PS, TIFF, CHM, DjVu, kuvatiedostot, DVI, XPS, ODT, FictionBook, sarjakuvakirjat, Plucker, EPub, faksit
  • Formats de fichiers gérés : PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • +
  • Formatos compatíbeis: PDF, PS, Tiff, CHM, DjVu, imaxes, DVI, XPS, ODT, FictionBook, banda deseñada, Plucker, EPub e fax.
  • Formati supportati: PDF, PS, Tiff, CHM, DjVu, immagini, DVI, XPS, ODT, Fiction Book, fumetti, Plucker, EPub, Fax
  • 지원하는 형식: PDF, PS, Tiff, CHM, DjVu, 사진, DVI, XPS, ODT, Fiction Book, 만화책, Plucker, EPub, 팩스
  • Ondersteunde formaten: PDF, PS, Tiff, CHM, DjVu, Afbeeldingen, DVI, XPS, ODT, Fictie boek, Stripboek, Plucker, EPub, Fax
  • Støtta format: PDF, PS, Tiff, CHM, DjVu, bilete, DVI, XPS, ODT, e-bøker, teikneseriar, Plucker, EPub, Fax
  • Obsługa formatów: PDF, PS, Tiff, CHM, DjVu, Obrazy, DVI, XPS, ODT, Fikcja, Komiksy, Plucker, EPub, Fax
  • Formatos Suportados: PDF, PS, Tiff, CHM, DjVu, Imagens, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Podporované formáty: PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Podprte vrste: PDF, PS, Tiff, CHM, DjVu, slike, DVI, XPS, ODT, Fiction Book, stripi, Plucker, EPub, faks
  • Подржани формати: ПДФ, постскрипт, ТИФФ, ЦХМ, ДјВу, слике, ДВИ, ИксПС, ОДТ, фикшнбук, стрипови, Плакер, ЕПУБ, факс.
  • Podržani formati: PDF, PostScript, TIFF, CHM, DjVu, slike, DVI, XPS, ODT, FictionBook, stripovi, Plucker, EPUB, faks.
  • Подржани формати: ПДФ, постскрипт, ТИФФ, ЦХМ, ДјВу, слике, ДВИ, ИксПС, ОДТ, фикшнбук, стрипови, Плакер, ЕПУБ, факс.
  • Podržani formati: PDF, PostScript, TIFF, CHM, DjVu, slike, DVI, XPS, ODT, FictionBook, stripovi, Plucker, EPUB, faks.
  • Format som stöds: PDF, PS, Tiff, CHM, DjVu, bilder, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Desteklenen Biçimler: PDF, PS, Tiff, CHM, DjVu, Resimler, DVI, XPS, ODT, Kurgu Kitabı, Çizgi Roman Kitabı, Plucker, EPub, Faks
  • Підтримувані формати: PDF, PS, Tiff, CHM, DjVu, зображення, DVI, XPS, ODT, Fiction Book, комікси, Plucker, EPub, факси.
  • xxSupported Formats: PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Faxxx
  • 支持的格式︰ PDF,PS,Tiff,CHM,DjVu,图片,DVI,XPS,ODT,FictionBook,漫画书,Plucker,EPub,传真
  • 支援的檔案格式:PDF, PS, Tiff, CHM, DjVu, Images, DVI, XPS, ODT, Fiction Book, Comic Book, Plucker, EPub, Fax
  • Sidebar with contents, thumbnails, reviews and bookmarks
  • Barra llateral colos conteníos, miniatures, reseñes y marcadores
  • Barra lateral amb el contingut, miniatures, revisions i punts
  • Barra lateral amb el contingut, miniatures, revisions i punts
  • Seitenleiste mit Inhalten, Vorschauen und Lesezeichen
  • Πλευρική γραμμή με περιεχόμενα, εικόνες προεπισκόπησης, αναλύσεις και σελιδοδείκτες
  • Sidebar with contents, thumbnails, reviews and bookmarks
  • Barra lateral con contenido, miniaturas, revisiones y marcadores
  • Külgriba sisukorra, pisipiltide, annotatsioonide ja järjehoidjatega
  • Sivupaneeli, jossa sisältö, pienoiskuvat, tarkastelut ja kirjanmerkit
  • Barre latérale montrant les contenus, miniatures, informations de relecture et signets
  • +
  • Barra lateral con contido, miniaturas, revisións e marcadores.
  • Barra laterale con contenuti, miniature, revisioni e segnalibri
  • 목차, 책갈피, 미리 보기, 주석이 있는 사이드바
  • Zijbalk met inhoud, miniaturen, reviews en bladwijzers
  • Sidestolpe med innhaldsliste, miniatyrbilete, omtalar og bokmerke
  • Pasek boczny z treścią, miniaturami, recenzją i zakładkami
  • Barra lateral com o conteúdo, miniaturas, revisões e marcação de favoritos
  • Posuvník s obsahom, miniatúrami, revíziami a záložkami
  • Stranska vrstica z vsebino, sličicami, ocenami in zaznamki
  • Бочна трака са садржајем, сличицама, рецензијама и обележивачима.
  • Bočna traka sa sadržajem, sličicama, recenzijama i obeleživačima.
  • Бочна трака са садржајем, сличицама, рецензијама и обележивачима.
  • Bočna traka sa sadržajem, sličicama, recenzijama i obeleživačima.
  • Sidorad med innehåll, miniatyrbilder, granskningar och bokmärken
  • İçindekiler, küçük resimler, incelemeler ve yer imleri bulunan kenar çubuğu
  • Бічні панелі зі списками змісту, мініатюрами сторінок, можливість рецензування та додавання закладок.
  • xxSidebar with contents, thumbnails, reviews and bookmarksxx
  • 目录,缩略图,批注和书签侧边栏
  • 有內容、縮圖、回顧與書籤等選項的側邊欄
  • Annotations support
  • Sofitu d'anotaciones
  • Admet anotacions
  • Admet anotacions
  • Unterstützung für Anmerkungen
  • Υποστήριξη σημειώσεων
  • Annotations support
  • Permite el uso de notas
  • Annotatsioonide toetus
  • Merkintöjen tuki
  • Gestion des annotations
  • +
  • Anotacións.
  • Supporto per le annotazioni
  • 주석 지원
  • Ondersteuning van annotaties
  • Støtte for merknadar
  • Obsługa przypisów
  • Suporte para anotações
  • Podpora anotácií
  • Podpora zabeležkam
  • Подршка за тумачења.
  • Podrška za tumačenja.
  • Подршка за тумачења.
  • Podrška za tumačenja.
  • Stöd för kommentarer
  • Ek açıklamalar desteği
  • Підтримка анотування.
  • xxAnnotations supportxx
  • 支持批注
  • 支援註記
https://www.kde.org/images/screenshots/okular.png https://okular.kde.org/ https://bugs.kde.org/enter_bug.cgi?format=guided&product=okular KDE okular
diff --git a/ui/annotationproxymodels.cpp b/ui/annotationproxymodels.cpp index 285307d2f..f870cd4ca 100644 --- a/ui/annotationproxymodels.cpp +++ b/ui/annotationproxymodels.cpp @@ -1,604 +1,604 @@ /*************************************************************************** * Copyright (C) 2007 by Tobias Koenig * * * * 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. * ***************************************************************************/ #include "annotationproxymodels.h" #include #include #include #include "annotationmodel.h" #include "debug_ui.h" static quint32 mixIndex( int row, int column ) { return ( row << 4 ) | column; } PageFilterProxyModel::PageFilterProxyModel( QObject *parent ) : QSortFilterProxyModel( parent ), mGroupByCurrentPage( false ), mCurrentPage( -1 ) { setDynamicSortFilter( true ); } void PageFilterProxyModel::groupByCurrentPage( bool value ) { if ( mGroupByCurrentPage == value ) return; mGroupByCurrentPage = value; invalidateFilter(); } void PageFilterProxyModel::setCurrentPage( int page ) { if ( mCurrentPage == page ) return; mCurrentPage = page; // no need to invalidate when we're not showing the current page only if ( !mGroupByCurrentPage ) return; invalidateFilter(); } bool PageFilterProxyModel::filterAcceptsRow( int row, const QModelIndex &sourceParent ) const { if ( !mGroupByCurrentPage ) return true; const QModelIndex pageIndex = sourceModel()->index( row, 0, sourceParent ); int page = sourceModel()->data( pageIndex, AnnotationModel::PageRole ).toInt(); return (page == mCurrentPage); } PageGroupProxyModel::PageGroupProxyModel( QObject *parent ) : QAbstractProxyModel( parent ), mGroupByPage( false ) { } int PageGroupProxyModel::columnCount( const QModelIndex &parentIndex ) const { // For top-level and second level we have always only one column if ( mGroupByPage ) { if ( parentIndex.isValid() ) { if ( parentIndex.parent().isValid() ) return 0; else { return 1; // second-level } } else { return 1; // top-level } } else { if ( !parentIndex.isValid() ) // top-level return 1; else return 0; } return 1; } int PageGroupProxyModel::rowCount( const QModelIndex &parentIndex ) const { if ( mGroupByPage ) { if ( parentIndex.isValid() ) { if ( parentIndex.parent().isValid() ) return 0; else { return mTreeIndexes[ parentIndex.row() ].second.count(); // second-level } } else { return mTreeIndexes.count(); // top-level } } else { if ( !parentIndex.isValid() ) // top-level return mIndexes.count(); else return 0; } } QModelIndex PageGroupProxyModel::index( int row, int column, const QModelIndex &parentIndex ) const { if ( row < 0 || column != 0 ) return QModelIndex(); if ( mGroupByPage ) { if ( parentIndex.isValid() ) { if ( parentIndex.row() >= 0 && parentIndex.row() < mTreeIndexes.count() && row < mTreeIndexes[ parentIndex.row() ].second.count() ) return createIndex( row, column, qint32( parentIndex.row() + 1 ) ); else return QModelIndex(); } else { if ( row < mTreeIndexes.count() ) return createIndex( row, column ); else return QModelIndex(); } } else { if ( row < mIndexes.count() ) return createIndex( row, column, mixIndex( parentIndex.row(), parentIndex.column() ) ); else return QModelIndex(); } } QModelIndex PageGroupProxyModel::parent( const QModelIndex &idx ) const { if ( mGroupByPage ) { if ( idx.internalId() == 0 ) // top-level return QModelIndex(); else return index( idx.internalId() - 1, idx.column() ); } else { // We have only top-level items return QModelIndex(); } } QModelIndex PageGroupProxyModel::mapFromSource( const QModelIndex &sourceIndex ) const { if ( mGroupByPage ) { if ( sourceIndex.parent().isValid() ) { return index( sourceIndex.row(), sourceIndex.column(), sourceIndex.parent() ); } else { return index( sourceIndex.row(), sourceIndex.column() ); } } else { for ( int i = 0; i < mIndexes.count(); ++i ) { if ( mIndexes[ i ] == sourceIndex ) return index( i, 0 ); } return QModelIndex(); } } QModelIndex PageGroupProxyModel::mapToSource( const QModelIndex &proxyIndex ) const { if ( !proxyIndex.isValid() ) return QModelIndex(); if ( mGroupByPage ) { if ( proxyIndex.internalId() == 0 ) { if ( proxyIndex.row() >= mTreeIndexes.count() || proxyIndex.row() < 0 ) return QModelIndex(); return mTreeIndexes[ proxyIndex.row() ].first; } else { - if ( proxyIndex.internalId() - 1 >= mTreeIndexes.count() || + if ( qint32(proxyIndex.internalId()) - 1 >= mTreeIndexes.count() || proxyIndex.row() >= mTreeIndexes[ proxyIndex.internalId() - 1 ].second.count() ) return QModelIndex(); return mTreeIndexes[ proxyIndex.internalId() - 1 ].second[ proxyIndex.row() ]; } } else { if ( proxyIndex.column() > 0 || proxyIndex.row() >= mIndexes.count() ) return QModelIndex(); else { return mIndexes[ proxyIndex.row() ]; } } } void PageGroupProxyModel::setSourceModel( QAbstractItemModel *model ) { if ( sourceModel() ) { disconnect( sourceModel(), &QAbstractItemModel::layoutChanged, this, &PageGroupProxyModel::rebuildIndexes ); disconnect( sourceModel(), &QAbstractItemModel::modelReset, this, &PageGroupProxyModel::rebuildIndexes ); disconnect( sourceModel(), &QAbstractItemModel::rowsInserted, this, &PageGroupProxyModel::rebuildIndexes ); disconnect( sourceModel(), &QAbstractItemModel::rowsRemoved, this, &PageGroupProxyModel::rebuildIndexes ); } QAbstractProxyModel::setSourceModel( model ); connect( sourceModel(), &QAbstractItemModel::layoutChanged, this, &PageGroupProxyModel::rebuildIndexes ); connect( sourceModel(), &QAbstractItemModel::modelReset, this, &PageGroupProxyModel::rebuildIndexes ); connect( sourceModel(), &QAbstractItemModel::rowsInserted, this, &PageGroupProxyModel::rebuildIndexes ); connect( sourceModel(), &QAbstractItemModel::rowsRemoved, this, &PageGroupProxyModel::rebuildIndexes ); rebuildIndexes(); } void PageGroupProxyModel::rebuildIndexes() { beginResetModel(); if ( mGroupByPage ) { mTreeIndexes.clear(); for ( int row = 0; row < sourceModel()->rowCount(); ++row ) { const QModelIndex pageIndex = sourceModel()->index( row, 0 ); QList itemIndexes; for ( int subRow = 0; subRow < sourceModel()->rowCount( pageIndex ); ++subRow ) { itemIndexes.append( sourceModel()->index( subRow, 0, pageIndex ) ); } mTreeIndexes.append( QPair >( pageIndex, itemIndexes ) ); } } else { mIndexes.clear(); for ( int row = 0; row < sourceModel()->rowCount(); ++row ) { const QModelIndex pageIndex = sourceModel()->index( row, 0 ); for ( int subRow = 0; subRow < sourceModel()->rowCount( pageIndex ); ++subRow ) { mIndexes.append( sourceModel()->index( subRow, 0, pageIndex ) ); } } } endResetModel(); } void PageGroupProxyModel::groupByPage( bool value ) { if ( mGroupByPage == value ) return; mGroupByPage = value; rebuildIndexes(); } class AuthorGroupItem { public: enum Type { Page, Author, Annotation }; AuthorGroupItem( AuthorGroupItem *parent, Type type = Page, const QModelIndex &index = QModelIndex() ) : mParent( parent ), mType( type ), mIndex( index ) { } ~AuthorGroupItem() { qDeleteAll( mChilds ); } void appendChild( AuthorGroupItem *child ) { mChilds.append( child ); } AuthorGroupItem* parent() const { return mParent; } AuthorGroupItem* child( int row ) const { return mChilds.value( row ); } int childCount() const { return mChilds.count(); } void dump( int level = 0 ) { QString prefix; for ( int i = 0; i < level; ++i ) prefix += QLatin1Char(' '); qCDebug(OkularUiDebug, "%s%s", qPrintable( prefix ), ( mType == Page ? "Page" : (mType == Author ? "Author" : "Annotation") ) ); for ( int i = 0; i < mChilds.count(); ++i ) mChilds[ i ]->dump( level + 2 ); } const AuthorGroupItem* findIndex( const QModelIndex &index ) const { if ( index == mIndex ) return this; for ( int i = 0; i < mChilds.count(); ++i ) { const AuthorGroupItem *item = mChilds[ i ]->findIndex( index ); if ( item ) return item; } return 0; } int row() const { return ( mParent ? mParent->mChilds.indexOf( const_cast( this ) ) : 0 ); } Type type() const { return mType; } QModelIndex index() const { return mIndex; } void setAuthor( const QString &author ) { mAuthor = author; } QString author() const { return mAuthor; } private: AuthorGroupItem *mParent; Type mType; QModelIndex mIndex; QList mChilds; QString mAuthor; }; class AuthorGroupProxyModel::Private { public: Private( AuthorGroupProxyModel *parent ) : mParent( parent ), mRoot( 0 ), mGroupByAuthor( false ) { } ~Private() { delete mRoot; } AuthorGroupProxyModel *mParent; AuthorGroupItem *mRoot; bool mGroupByAuthor; }; AuthorGroupProxyModel::AuthorGroupProxyModel( QObject *parent ) : QAbstractProxyModel( parent ), d( new Private( this ) ) { } AuthorGroupProxyModel::~AuthorGroupProxyModel() { delete d; } int AuthorGroupProxyModel::columnCount( const QModelIndex& ) const { return 1; } int AuthorGroupProxyModel::rowCount( const QModelIndex &parentIndex ) const { AuthorGroupItem *item = 0; if ( !parentIndex.isValid() ) item = d->mRoot; else item = static_cast( parentIndex.internalPointer() ); return item ? item->childCount() : 0; } QModelIndex AuthorGroupProxyModel::index( int row, int column, const QModelIndex &parentIndex ) const { if ( !hasIndex( row, column, parentIndex ) ) return QModelIndex(); AuthorGroupItem *parentItem = 0; if ( !parentIndex.isValid() ) parentItem = d->mRoot; else parentItem = static_cast( parentIndex.internalPointer() ); AuthorGroupItem *child = parentItem->child( row ); if ( child ) return createIndex( row, column, child ); else return QModelIndex(); } QModelIndex AuthorGroupProxyModel::parent( const QModelIndex &index ) const { if ( !index.isValid() ) return QModelIndex(); AuthorGroupItem *childItem = static_cast( index.internalPointer() ); AuthorGroupItem *parentItem = childItem->parent(); if ( parentItem == d->mRoot ) return QModelIndex(); else return createIndex( parentItem->row(), 0, parentItem ); } QModelIndex AuthorGroupProxyModel::mapFromSource( const QModelIndex &sourceIndex ) const { if ( !sourceIndex.isValid() ) return QModelIndex(); const AuthorGroupItem *item = d->mRoot->findIndex( sourceIndex ); if ( !item ) return QModelIndex(); return createIndex( item->row(), 0, const_cast( item ) ); } QModelIndex AuthorGroupProxyModel::mapToSource( const QModelIndex &proxyIndex ) const { if ( !proxyIndex.isValid() ) return QModelIndex(); AuthorGroupItem *item = static_cast( proxyIndex.internalPointer() ); return item->index(); } void AuthorGroupProxyModel::setSourceModel( QAbstractItemModel *model ) { if ( sourceModel() ) { disconnect( sourceModel(), &QAbstractItemModel::layoutChanged, this, &AuthorGroupProxyModel::rebuildIndexes ); disconnect( sourceModel(), &QAbstractItemModel::modelReset, this, &AuthorGroupProxyModel::rebuildIndexes ); disconnect( sourceModel(), &QAbstractItemModel::rowsInserted, this, &AuthorGroupProxyModel::rebuildIndexes ); disconnect( sourceModel(), &QAbstractItemModel::rowsRemoved, this, &AuthorGroupProxyModel::rebuildIndexes ); } QAbstractProxyModel::setSourceModel( model ); connect( sourceModel(), &QAbstractItemModel::layoutChanged, this, &AuthorGroupProxyModel::rebuildIndexes ); connect( sourceModel(), &QAbstractItemModel::modelReset, this, &AuthorGroupProxyModel::rebuildIndexes ); connect( sourceModel(), &QAbstractItemModel::rowsInserted, this, &AuthorGroupProxyModel::rebuildIndexes ); connect( sourceModel(), &QAbstractItemModel::rowsRemoved, this, &AuthorGroupProxyModel::rebuildIndexes ); rebuildIndexes(); } static bool isAuthorItem( const QModelIndex &index ) { if ( !index.isValid() ) { return false; } AuthorGroupItem *item = static_cast( index.internalPointer() ); return (item->type() == AuthorGroupItem::Author); } QItemSelection AuthorGroupProxyModel::mapSelectionToSource( const QItemSelection &selection ) const { QModelIndexList proxyIndexes = selection.indexes(); QItemSelection sourceSelection; for ( int i = 0; i < proxyIndexes.size(); ++i ) { if ( !isAuthorItem( proxyIndexes.at( i ) ) ) sourceSelection << QItemSelectionRange( mapToSource( proxyIndexes.at( i ) ) ); } return sourceSelection; } QItemSelection AuthorGroupProxyModel::mapSelectionFromSource( const QItemSelection &selection ) const { return QAbstractProxyModel::mapSelectionFromSource( selection ); } QVariant AuthorGroupProxyModel::data( const QModelIndex &proxyIndex, int role ) const { if ( isAuthorItem( proxyIndex ) ) { AuthorGroupItem *item = static_cast( proxyIndex.internalPointer() ); if ( role == Qt::DisplayRole ) return item->author(); else if ( role == Qt::DecorationRole ) return QIcon::fromTheme( item->author().isEmpty() ? QStringLiteral("user-away") : QStringLiteral("user-identity") ); else return QVariant(); } else { return QAbstractProxyModel::data( proxyIndex, role ); } } QMap AuthorGroupProxyModel::itemData( const QModelIndex &index ) const { if ( isAuthorItem( index ) ) { return QMap(); } else { return QAbstractProxyModel::itemData( index ); } } Qt::ItemFlags AuthorGroupProxyModel::flags( const QModelIndex &index ) const { if ( isAuthorItem( index ) ) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return QAbstractProxyModel::flags( index ); } } void AuthorGroupProxyModel::groupByAuthor( bool value ) { if ( d->mGroupByAuthor == value ) return; d->mGroupByAuthor = value; rebuildIndexes(); } void AuthorGroupProxyModel::rebuildIndexes() { beginResetModel(); delete d->mRoot; d->mRoot = new AuthorGroupItem( 0 ); if ( d->mGroupByAuthor ) { QMap authorMap; for ( int row = 0; row < sourceModel()->rowCount(); ++row ) { const QModelIndex idx = sourceModel()->index( row, 0 ); const QString author = sourceModel()->data( idx, AnnotationModel::AuthorRole ).toString(); if ( !author.isEmpty() ) { // We have the annotations as top-level, so introduce authors as new // top-levels and append the annotations AuthorGroupItem *authorItem = authorMap.value( author, 0 ); if ( !authorItem ) { authorItem = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Author ); authorItem->setAuthor( author ); // Add item to tree d->mRoot->appendChild( authorItem ); // Insert to lookup list authorMap.insert( author, authorItem ); } AuthorGroupItem *item = new AuthorGroupItem( authorItem, AuthorGroupItem::Annotation, idx ); authorItem->appendChild( item ); } else { // We have the pages as top-level, so we use them as top-level, append the // authors for all annotations of the page, and then the annotations themself AuthorGroupItem *pageItem = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Page, idx ); d->mRoot->appendChild( pageItem ); // First collect all authors... QMap pageAuthorMap; for ( int subRow = 0; subRow < sourceModel()->rowCount( idx ); ++subRow ) { const QModelIndex annIdx = sourceModel()->index( subRow, 0, idx ); const QString author = sourceModel()->data( annIdx, AnnotationModel::AuthorRole ).toString(); AuthorGroupItem *authorItem = pageAuthorMap.value( author, 0 ); if ( !authorItem ) { authorItem = new AuthorGroupItem( pageItem, AuthorGroupItem::Author ); authorItem->setAuthor( author ); // Add item to tree pageItem->appendChild( authorItem ); // Insert to lookup list pageAuthorMap.insert( author, authorItem ); } AuthorGroupItem *item = new AuthorGroupItem( authorItem, AuthorGroupItem::Annotation, annIdx ); authorItem->appendChild( item ); } } } } else { for ( int row = 0; row < sourceModel()->rowCount(); ++row ) { const QModelIndex idx = sourceModel()->index( row, 0 ); const QString author = sourceModel()->data( idx, AnnotationModel::AuthorRole ).toString(); if ( !author.isEmpty() ) { // We have the annotations as top-level items AuthorGroupItem *item = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Annotation, idx ); d->mRoot->appendChild( item ); } else { // We have the pages as top-level items AuthorGroupItem *pageItem = new AuthorGroupItem( d->mRoot, AuthorGroupItem::Page, idx ); d->mRoot->appendChild( pageItem ); // Append all annotations as second-level for ( int subRow = 0; subRow < sourceModel()->rowCount( idx ); ++subRow ) { const QModelIndex subIdx = sourceModel()->index( subRow, 0, idx ); AuthorGroupItem *item = new AuthorGroupItem( pageItem, AuthorGroupItem::Annotation, subIdx ); pageItem->appendChild( item ); } } } } endResetModel(); } #include "moc_annotationproxymodels.cpp" diff --git a/ui/pagepainter.cpp b/ui/pagepainter.cpp index 33b81bf32..a87bcc68c 100644 --- a/ui/pagepainter.cpp +++ b/ui/pagepainter.cpp @@ -1,1066 +1,1014 @@ /*************************************************************************** * Copyright (C) 2005 by Enrico Ros * * * * 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. * ***************************************************************************/ #include "pagepainter.h" // qt / kde includes #include #include #include #include #include #include #include #include #include // system includes #include // local includes #include "core/area.h" #include "core/page.h" #include "core/page_p.h" #include "core/annotations.h" #include "core/utils.h" #include "guiutils.h" #include "settings.h" #include "core/observer.h" #include "core/tile.h" #include "settings_core.h" #include "ui/debug_ui.h" Q_GLOBAL_STATIC_WITH_ARGS( QPixmap, busyPixmap, ( KIconLoader::global()->loadIcon(QLatin1String("okular"), KIconLoader::NoGroup, IconSize(KIconLoader::Desktop), KIconLoader::DefaultState, QStringList(), 0, true) ) ) #define TEXTANNOTATION_ICONSIZE 24 inline QPen buildPen( const Okular::Annotation *ann, double width, const QColor &color ) { QPen p( QBrush( color ), width, ann->style().lineStyle() == Okular::Annotation::Dashed ? Qt::DashLine : Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin ); return p; } void PagePainter::paintPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits ) { paintCroppedPageOnPainter( destPainter, page, observer, flags, scaledWidth, scaledHeight, limits, Okular::NormalizedRect( 0, 0, 1, 1 ), 0 ); } void PagePainter::paintCroppedPageOnPainter( QPainter * destPainter, const Okular::Page * page, Okular::DocumentObserver *observer, int flags, int scaledWidth, int scaledHeight, const QRect &limits, const Okular::NormalizedRect &crop, Okular::NormalizedPoint *viewPortPoint ) { qreal dpr = destPainter->device()->devicePixelRatioF(); /* Calculate the cropped geometry of the page */ QRect scaledCrop = crop.geometry( scaledWidth, scaledHeight ); const QRect dScaledCrop(QRectF(scaledCrop.x() * dpr, scaledCrop.y() * dpr, scaledCrop.width() * dpr, scaledCrop.height() * dpr).toAlignedRect()); int croppedWidth = scaledCrop.width(); int croppedHeight = scaledCrop.height(); int dScaledWidth = ceil(scaledWidth * dpr); int dScaledHeight = ceil(scaledHeight * dpr); const QRect dLimits(QRectF(limits.x() * dpr, limits.y() * dpr, limits.width() * dpr, limits.height() * dpr).toAlignedRect()); QColor paperColor = Qt::white; QColor backgroundColor = paperColor; if ( Okular::SettingsCore::changeColors() ) { switch ( Okular::SettingsCore::renderMode() ) { case Okular::SettingsCore::EnumRenderMode::Inverted: backgroundColor = Qt::black; break; case Okular::SettingsCore::EnumRenderMode::Paper: paperColor = Okular::SettingsCore::paperColor(); backgroundColor = paperColor; break; case Okular::SettingsCore::EnumRenderMode::Recolor: backgroundColor = Okular::Settings::recolorBackground(); break; default: ; } } destPainter->fillRect( limits, backgroundColor ); const bool hasTilesManager = page->hasTilesManager( observer ); QPixmap pixmap; if ( !hasTilesManager ) { /** 1 - RETRIEVE THE 'PAGE+ID' PIXMAP OR A SIMILAR 'PAGE' ONE **/ const QPixmap *p = 0; p = page->_o_nearestPixmap( observer, dScaledWidth, dScaledHeight ); if ( p != NULL ) { pixmap = p->copy(); pixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); } /** 1B - IF NO PIXMAP, DRAW EMPTY PAGE **/ double pixmapRescaleRatio = !pixmap.isNull() ? dScaledWidth / (double)pixmap.width() : -1; long pixmapPixels = !pixmap.isNull() ? (long)pixmap.width() * (long)pixmap.height() : 0; if ( pixmap.isNull() || pixmapRescaleRatio > 20.0 || pixmapRescaleRatio < 0.25 || (dScaledWidth > pixmap.width() && pixmapPixels > 60000000L) ) { // draw something on the blank page: the okular icon or a cross (as a fallback) if ( !busyPixmap()->isNull() ) { busyPixmap->setDevicePixelRatio(dpr); destPainter->drawPixmap( QPoint( 10, 10 ), *busyPixmap() ); } else { destPainter->setPen( Qt::gray ); destPainter->drawLine( 0, 0, croppedWidth-1, croppedHeight-1 ); destPainter->drawLine( 0, croppedHeight-1, croppedWidth-1, 0 ); } return; } } /** 2 - FIND OUT WHAT TO PAINT (Flags + Configuration + Presence) **/ bool canDrawHighlights = (flags & Highlights) && !page->m_highlights.isEmpty(); bool canDrawTextSelection = (flags & TextSelection) && page->textSelection(); bool canDrawAnnotations = (flags & Annotations) && !page->m_annotations.isEmpty(); bool enhanceLinks = (flags & EnhanceLinks) && Okular::Settings::highlightLinks(); bool enhanceImages = (flags & EnhanceImages) && Okular::Settings::highlightImages(); // vectors containing objects to draw // make this a qcolor, rect map, since we don't need // to know s_id here! we are only drawing this right? QList< QPair > * bufferedHighlights = 0; QList< Okular::Annotation * > * bufferedAnnotations = 0; QList< Okular::Annotation * > * unbufferedAnnotations = 0; Okular::Annotation *boundingRectOnlyAnn = 0; // Paint the bounding rect of this annotation // fill up lists with visible annotation/highlight objects/text selections if ( canDrawHighlights || canDrawTextSelection || canDrawAnnotations ) { // precalc normalized 'limits rect' for intersection double nXMin = ( (double)limits.left() / dScaledWidth ) + crop.left, nXMax = ( (double)limits.right() / dScaledWidth ) + crop.left, nYMin = ( (double)limits.top() / dScaledHeight ) + crop.top, nYMax = ( (double)limits.bottom() / dScaledHeight ) + crop.top; // append all highlights inside limits to their list if ( canDrawHighlights ) { if ( !bufferedHighlights ) bufferedHighlights = new QList< QPair >(); /* else {*/ Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax ); QLinkedList< Okular::HighlightAreaRect * >::const_iterator h2It = page->m_highlights.constBegin(), hEnd = page->m_highlights.constEnd(); Okular::HighlightAreaRect::const_iterator hIt; for ( ; h2It != hEnd; ++h2It ) for (hIt=(*h2It)->constBegin(); hIt!=(*h2It)->constEnd(); ++hIt) { if ((*hIt).intersects(limitRect)) bufferedHighlights->append( qMakePair((*h2It)->color,*hIt) ); } delete limitRect; //} } if ( canDrawTextSelection ) { if ( !bufferedHighlights ) bufferedHighlights = new QList< QPair >(); /* else {*/ Okular::NormalizedRect* limitRect = new Okular::NormalizedRect(nXMin, nYMin, nXMax, nYMax ); const Okular::RegularAreaRect *textSelection = page->textSelection(); Okular::HighlightAreaRect::const_iterator hIt = textSelection->constBegin(), hEnd = textSelection->constEnd(); for ( ; hIt != hEnd; ++hIt ) { if ( (*hIt).intersects( limitRect ) ) bufferedHighlights->append( qMakePair( page->textSelectionColor(), *hIt ) ); } delete limitRect; //} } // append annotations inside limits to the un/buffered list if ( canDrawAnnotations ) { QLinkedList< Okular::Annotation * >::const_iterator aIt = page->m_annotations.constBegin(), aEnd = page->m_annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * ann = *aIt; int flags = ann->flags(); if ( flags & Okular::Annotation::Hidden ) continue; if ( flags & Okular::Annotation::ExternallyDrawn ) { // ExternallyDrawn annots are never rendered by PagePainter. // Just paint the boundingRect if the annot is moved or resized. if ( flags & (Okular::Annotation::BeingMoved | Okular::Annotation::BeingResized) ) { boundingRectOnlyAnn = ann; } continue; } bool intersects = ann->transformedBoundingRectangle().intersects( nXMin, nYMin, nXMax, nYMax ); if ( ann->subType() == Okular::Annotation::AText ) { Okular::TextAnnotation * ta = static_cast< Okular::TextAnnotation * >( ann ); if ( ta->textType() == Okular::TextAnnotation::Linked ) { Okular::NormalizedRect iconrect( ann->transformedBoundingRectangle().left, ann->transformedBoundingRectangle().top, ann->transformedBoundingRectangle().left + TEXTANNOTATION_ICONSIZE / page->width(), ann->transformedBoundingRectangle().top + TEXTANNOTATION_ICONSIZE / page->height() ); intersects = iconrect.intersects( nXMin, nYMin, nXMax, nYMax ); } } if ( intersects ) { Okular::Annotation::SubType type = ann->subType(); if ( type == Okular::Annotation::ALine || type == Okular::Annotation::AHighlight || type == Okular::Annotation::AInk /*|| (type == Annotation::AGeom && ann->style().opacity() < 0.99)*/ ) { if ( !bufferedAnnotations ) bufferedAnnotations = new QList< Okular::Annotation * >(); bufferedAnnotations->append( ann ); } else { if ( !unbufferedAnnotations ) unbufferedAnnotations = new QList< Okular::Annotation * >(); unbufferedAnnotations->append( ann ); } } } } // end of intersections checking } /** 3 - ENABLE BACKBUFFERING IF DIRECT IMAGE MANIPULATION IS NEEDED **/ bool bufferAccessibility = (flags & Accessibility) && Okular::SettingsCore::changeColors() && (Okular::SettingsCore::renderMode() != Okular::SettingsCore::EnumRenderMode::Paper); bool useBackBuffer = bufferAccessibility || bufferedHighlights || bufferedAnnotations || viewPortPoint; QPixmap * backPixmap = 0; QPainter * mixedPainter = 0; QRect limitsInPixmap = limits.translated( scaledCrop.topLeft() ); QRect dLimitsInPixmap = dLimits.translated( dScaledCrop.topLeft() ); // limits within full (scaled but uncropped) pixmap /** 4A -- REGULAR FLOW. PAINT PIXMAP NORMAL OR RESCALED USING GIVEN QPAINTER **/ /* when zoomed in */ if ( !useBackBuffer ) { if ( hasTilesManager ) { const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight ); const QList tiles = page->tilesAt( observer, normalizedLimits ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Okular::Tile &tile = *tIt; QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect dTileRect = QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect(); QRect limitsInTile = limits & tileRect; QRectF dLimitsInTile = dLimits & dTileRect; if ( !limitsInTile.isEmpty() ) { QPixmap tilePixmap = tile.pixmap()->copy(); tilePixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); if ( tilePixmap.width() == dTileRect.width() && tilePixmap.height() == dTileRect.height() ) { destPainter->drawPixmap( limitsInTile.topLeft(), tilePixmap, dLimitsInTile.translated( -dTileRect.topLeft() ) ); qCWarning(OkularUiDebug) << "PagePainter: 4AT1"; } else { destPainter->drawPixmap( tileRect, tilePixmap); qCWarning(OkularUiDebug) << "PagePainter: 4AT2"; } } tIt++; } } else { QString r = QString::number(qrand()); qCWarning(OkularUiDebug) << "PagePainter: 4AN" << pixmap.width() << dScaledWidth << ";" << pixmap.height() << dScaledHeight << ";" << dLimitsInPixmap; pixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4an_before.png"); QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap); scaledCroppedPixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4an.png"); destPainter->drawPixmap( limits.topLeft(), scaledCroppedPixmap, QRectF(0, 0, dLimits.width(),dLimits.height())); qCWarning(OkularUiDebug) << "PagePainter: 4AN" << scaledCroppedPixmap.devicePixelRatioF(); } // 4A.2. active painter is the one passed to this method mixedPainter = destPainter; } /** 4B -- BUFFERED FLOW. IMAGE PAINTING + OPERATIONS. QPAINTER OVER PIXMAP **/ else { // the image over which we are going to draw QImage backImage = QImage( dLimits.width(), dLimits.height(), QImage::Format_ARGB32_Premultiplied ); backImage.setDevicePixelRatio(dpr); backImage.fill( paperColor ); QPainter p( &backImage ); - bool has_alpha; - if ( !pixmap.isNull() ) - has_alpha = pixmap.hasAlpha(); - else - has_alpha = true; - if ( hasTilesManager ) { const Okular::NormalizedRect normalizedLimits( limitsInPixmap, scaledWidth, scaledHeight ); const QList tiles = page->tilesAt( observer, normalizedLimits ); QList::const_iterator tIt = tiles.constBegin(), tEnd = tiles.constEnd(); while ( tIt != tEnd ) { const Okular::Tile &tile = *tIt; QRect tileRect = tile.rect().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect dTileRect(QRectF(tileRect.x() * dpr, tileRect.y() * dpr, tileRect.width() * dpr, tileRect.height() * dpr).toAlignedRect()); QRect limitsInTile = limits & tileRect; QRect dLimitsInTile = dLimits & dTileRect; if ( !limitsInTile.isEmpty() ) { QPixmap tilePixmap = tile.pixmap()->copy(); tilePixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); - if ( !tilePixmap.hasAlpha() ) - has_alpha = false; - if ( tilePixmap.width() == dTileRect.width() && tilePixmap.height() == dTileRect.height() ) { qCWarning(OkularUiDebug) << "PagePainter: 4BT1"; p.drawPixmap( limitsInTile.translated( -limits.topLeft() ).topLeft(), tilePixmap, dLimitsInTile.translated( -dTileRect.topLeft() ) ); } else { qCWarning(OkularUiDebug) << "PagePainter: 4BT2"; double xScale = tilePixmap.width() / (double)dTileRect.width(); double yScale = tilePixmap.height() / (double)dTileRect.height(); QTransform transform( xScale, 0, 0, yScale, 0, 0 ); p.drawPixmap( limitsInTile.translated( -limits.topLeft() ), tilePixmap, transform.mapRect( dLimitsInTile ).translated( -transform.mapRect( dTileRect ).topLeft() ) ); //tilePixmap.save("/tmp/pix.png"); qCWarning(OkularUiDebug) << "PagePainter: 4BT2 DONE"; } } ++tIt; } } else { // 4B.1. draw the page pixmap: normal or scaled QString r = QString::number(qrand()); qDebug() << "PagePainter: 4BN: " << r; QPixmap scaledCroppedPixmap = pixmap.scaled(dScaledWidth, dScaledHeight).copy(dLimitsInPixmap); scaledCroppedPixmap.setDevicePixelRatio(dpr); scaledCroppedPixmap.save("/tmp/paint/p_" + QString::number(page->number()) + "_" + r + "_4bn.png"); p.drawPixmap( 0, 0, scaledCroppedPixmap ); } p.end(); // 4B.2. modify pixmap following accessibility settings if ( bufferAccessibility ) { switch ( Okular::SettingsCore::renderMode() ) { case Okular::SettingsCore::EnumRenderMode::Inverted: // Invert image pixels using QImage internal function backImage.invertPixels(QImage::InvertRgb); break; case Okular::SettingsCore::EnumRenderMode::Recolor: recolor(&backImage, Okular::Settings::recolorForeground(), Okular::Settings::recolorBackground()); break; case Okular::SettingsCore::EnumRenderMode::BlackWhite: // Manual Gray and Contrast unsigned int * data = (unsigned int *)backImage.bits(); int val, pixels = backImage.width() * backImage.height(), con = Okular::Settings::bWContrast(), thr = 255 - Okular::Settings::bWThreshold(); for( int i = 0; i < pixels; ++i ) { val = qGray( data[i] ); if ( val > thr ) val = 128 + (127 * (val - thr)) / (255 - thr); else if ( val < thr ) val = (128 * val) / thr; if ( con > 2 ) { val = con * ( val - thr ) / 2 + thr; if ( val > 255 ) val = 255; else if ( val < 0 ) val = 0; } data[i] = qRgba( val, val, val, 255 ); } break; } } // 4B.3. highlight rects in page if ( bufferedHighlights ) { // draw highlights that are inside the 'limits' paint region - QList< QPair >::const_iterator hIt = bufferedHighlights->constBegin(), hEnd = bufferedHighlights->constEnd(); - for ( ; hIt != hEnd; ++hIt ) + for (const auto& highlight : *bufferedHighlights) { - const Okular::NormalizedRect & r = (*hIt).second; + const Okular::NormalizedRect & r = highlight.second; // find out the rect to highlight on pixmap QRect highlightRect = r.geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ).intersected( limits ); highlightRect.translate( -limits.left(), -limits.top() ); + const QColor highlightColor = highlight.first; QPainter painter(&backImage); - painter.fillRect(highlightRect, QColor((*hIt).first.red(), (*hIt).first.green(), (*hIt).first.blue(), 150)); - - // the code below works too, but the mordern method would be to just use a QPainter? - - // highlight composition (product: highlight color * destcolor) - /* - QRect highlightRect = r.geometry( dScaledWidth, dScaledHeight ).translated( -dScaledCrop.topLeft() ).intersected( dLimits ); - highlightRect.translate( -dLimits.left(), -dLimits.top() ); - - unsigned int * data = (unsigned int *)backImage.bits(); - int val, newR, newG, newB, - rh = (*hIt).first.red(), - gh = (*hIt).first.green(), - bh = (*hIt).first.blue(), - offset = highlightRect.top() * backImage.width(); - for( int y = highlightRect.top(); y <= highlightRect.bottom(); ++y ) - { - for( int x = highlightRect.left(); x <= highlightRect.right(); ++x ) - { - val = data[ x + offset ]; - //for odt or epub - if(has_alpha) - { - newR = qRed(val); - newG = qGreen(val); - newB = qBlue(val); - - if(newR == newG && newG == newB && newR == 0) - newR = newG = newB = 255; - - newR = (newR * rh) / 255; - newG = (newG * gh) / 255; - newB = (newB * bh) / 255; - } - else - { - newR = (qRed(val) * rh) / 255; - newG = (qGreen(val) * gh) / 255; - newB = (qBlue(val) * bh) / 255; - } - data[ x + offset ] = qRgba( newR, newG, newB, 255 ); - } - offset += backImage.width(); - } - }*/ + painter.setCompositionMode(QPainter::CompositionMode_Multiply); + painter.fillRect(highlightRect, highlightColor); } } // 4B.4. paint annotations [COMPOSITED ONES] if ( bufferedAnnotations ) { // Albert: This is quite "heavy" but all the backImage that reach here are QImage::Format_ARGB32_Premultiplied // and have to be so that the QPainter::CompositionMode_Multiply works // we could also put a // backImage = backImage.convertToFormat(QImage::Format_ARGB32_Premultiplied) // that would be almost a noop, but we'll leave the assert for now Q_ASSERT(backImage.format() == QImage::Format_ARGB32_Premultiplied); // precalc constants for normalizing [0,1] page coordinates into normalized [0,1] limit rect coordinates double pageScale = (double)croppedWidth / page->width(); double xOffset = (double)limits.left() / (double)scaledWidth + crop.left, xScale = (double)scaledWidth / (double)limits.width(), yOffset = (double)limits.top() / (double)scaledHeight + crop.top, yScale = (double)scaledHeight / (double)limits.height(); // paint all buffered annotations in the page QList< Okular::Annotation * >::const_iterator aIt = bufferedAnnotations->constBegin(), aEnd = bufferedAnnotations->constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; Okular::Annotation::SubType type = a->subType(); QColor acolor = a->style().color(); if ( !acolor.isValid() ) acolor = Qt::yellow; acolor.setAlphaF( a->style().opacity() ); // draw LineAnnotation MISSING: all if ( type == Okular::Annotation::ALine ) { // get the annotation Okular::LineAnnotation * la = (Okular::LineAnnotation *) a; NormalizedPath path; // normalize page point to image const QLinkedList points = la->transformedLinePoints(); QLinkedList::const_iterator it = points.constBegin(); QLinkedList::const_iterator itEnd = points.constEnd(); for ( ; it != itEnd; ++it ) { Okular::NormalizedPoint point; point.x = ( (*it).x - xOffset) * xScale; point.y = ( (*it).y - yOffset) * yScale; path.append( point ); } const QPen linePen = buildPen( a, a->style().width(), a->style().color() ); QBrush fillBrush; if ( la->lineClosed() && la->lineInnerColor().isValid() ) fillBrush = QBrush( la->lineInnerColor() ); // draw the line as normalized path into image drawShapeOnImage( backImage, path, la->lineClosed(), linePen, fillBrush, pageScale ,Multiply); if ( path.count() == 2 && fabs( la->lineLeadingForwardPoint() ) > 0.1 ) { Okular::NormalizedPoint delta( la->transformedLinePoints().last().x - la->transformedLinePoints().first().x, la->transformedLinePoints().first().y - la->transformedLinePoints().last().y ); double angle = atan2( delta.y, delta.x ); if ( delta.y < 0 ) angle += 2 * M_PI; int sign = la->lineLeadingForwardPoint() > 0.0 ? 1 : -1; double LLx = fabs( la->lineLeadingForwardPoint() ) * cos( angle + sign * M_PI_2 + 2 * M_PI ) / page->width(); double LLy = fabs( la->lineLeadingForwardPoint() ) * sin( angle + sign * M_PI_2 + 2 * M_PI ) / page->height(); NormalizedPath path2; NormalizedPath path3; Okular::NormalizedPoint point; point.x = ( la->transformedLinePoints().first().x + LLx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().first().y - LLy - yOffset ) * yScale; path2.append( point ); point.x = ( la->transformedLinePoints().last().x + LLx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().last().y - LLy - yOffset ) * yScale; path3.append( point ); // do we have the extension on the "back"? if ( fabs( la->lineLeadingBackwardPoint() ) > 0.1 ) { double LLEx = la->lineLeadingBackwardPoint() * cos( angle - sign * M_PI_2 + 2 * M_PI ) / page->width(); double LLEy = la->lineLeadingBackwardPoint() * sin( angle - sign * M_PI_2 + 2 * M_PI ) / page->height(); point.x = ( la->transformedLinePoints().first().x + LLEx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().first().y - LLEy - yOffset ) * yScale; path2.append( point ); point.x = ( la->transformedLinePoints().last().x + LLEx - xOffset ) * xScale; point.y = ( la->transformedLinePoints().last().y - LLEy - yOffset ) * yScale; path3.append( point ); } else { path2.append( path[0] ); path3.append( path[1] ); } drawShapeOnImage( backImage, path2, false, linePen, QBrush(), pageScale, Multiply ); drawShapeOnImage( backImage, path3, false, linePen, QBrush(), pageScale, Multiply ); } } // draw HighlightAnnotation MISSING: under/strike width, feather, capping else if ( type == Okular::Annotation::AHighlight ) { // get the annotation Okular::HighlightAnnotation * ha = (Okular::HighlightAnnotation *) a; Okular::HighlightAnnotation::HighlightType type = ha->highlightType(); // draw each quad of the annotation int quads = ha->highlightQuads().size(); for ( int q = 0; q < quads; q++ ) { NormalizedPath path; const Okular::HighlightAnnotation::Quad & quad = ha->highlightQuads()[ q ]; // normalize page point to image for ( int i = 0; i < 4; i++ ) { Okular::NormalizedPoint point; point.x = (quad.transformedPoint( i ).x - xOffset) * xScale; point.y = (quad.transformedPoint( i ).y - yOffset) * yScale; path.append( point ); } // draw the normalized path into image switch ( type ) { // highlight the whole rect case Okular::HighlightAnnotation::Highlight: drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply ); break; // highlight the bottom part of the rect case Okular::HighlightAnnotation::Squiggly: path[ 3 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0; path[ 3 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0; path[ 2 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0; path[ 2 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0; drawShapeOnImage( backImage, path, true, Qt::NoPen, acolor, pageScale, Multiply ); break; // make a line at 3/4 of the height case Okular::HighlightAnnotation::Underline: path[ 0 ].x = ( 3 * path[ 0 ].x + path[ 3 ].x ) / 4.0; path[ 0 ].y = ( 3 * path[ 0 ].y + path[ 3 ].y ) / 4.0; path[ 1 ].x = ( 3 * path[ 1 ].x + path[ 2 ].x ) / 4.0; path[ 1 ].y = ( 3 * path[ 1 ].y + path[ 2 ].y ) / 4.0; path.pop_back(); path.pop_back(); drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale ); break; // make a line at 1/2 of the height case Okular::HighlightAnnotation::StrikeOut: path[ 0 ].x = ( path[ 0 ].x + path[ 3 ].x ) / 2.0; path[ 0 ].y = ( path[ 0 ].y + path[ 3 ].y ) / 2.0; path[ 1 ].x = ( path[ 1 ].x + path[ 2 ].x ) / 2.0; path[ 1 ].y = ( path[ 1 ].y + path[ 2 ].y ) / 2.0; path.pop_back(); path.pop_back(); drawShapeOnImage( backImage, path, false, QPen( acolor, 2 ), QBrush(), pageScale ); break; } } } // draw InkAnnotation MISSING:invar width, PENTRACER else if ( type == Okular::Annotation::AInk ) { // get the annotation Okular::InkAnnotation * ia = (Okular::InkAnnotation *) a; // draw each ink path const QList< QLinkedList > transformedInkPaths = ia->transformedInkPaths(); const QPen inkPen = buildPen( a, a->style().width(), acolor ); int paths = transformedInkPaths.size(); for ( int p = 0; p < paths; p++ ) { NormalizedPath path; const QLinkedList & inkPath = transformedInkPaths[ p ]; // normalize page point to image QLinkedList::const_iterator pIt = inkPath.constBegin(), pEnd = inkPath.constEnd(); for ( ; pIt != pEnd; ++pIt ) { const Okular::NormalizedPoint & inkPoint = *pIt; Okular::NormalizedPoint point; point.x = (inkPoint.x - xOffset) * xScale; point.y = (inkPoint.y - yOffset) * yScale; path.append( point ); } // draw the normalized path into image drawShapeOnImage( backImage, path, false, inkPen, QBrush(), pageScale ); } } } // end current annotation drawing } if(viewPortPoint) { QPainter painter(&backImage); painter.translate( -limits.left(), -limits.top() ); painter.setPen( QApplication::palette().color( QPalette::Active, QPalette::Highlight ) ); painter.drawLine( 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1, viewPortPoint->y * scaledHeight + 1 ); // ROTATION CURRENTLY NOT IMPLEMENTED /* if( page->rotation() == Okular::Rotation0) { } else if(page->rotation() == Okular::Rotation270) { painter.drawLine( viewPortPoint->y * scaledHeight + 1, 0, viewPortPoint->y * scaledHeight + 1, scaledWidth - 1); } else if(page->rotation() == Okular::Rotation180) { painter.drawLine( 0, (1.0 - viewPortPoint->y) * scaledHeight - 1, scaledWidth - 1, (1.0 - viewPortPoint->y) * scaledHeight - 1 ); } else if(page->rotation() == Okular::Rotation90) // not right, rotation clock-wise { painter.drawLine( scaledWidth - (viewPortPoint->y * scaledHeight + 1), 0, scaledWidth - (viewPortPoint->y * scaledHeight + 1), scaledWidth - 1); } */ } // 4B.5. create the back pixmap converting from the local image backPixmap = new QPixmap( QPixmap::fromImage( backImage ) ); backPixmap->setDevicePixelRatio(dpr); // 4B.6. create a painter over the pixmap and set it as the active one mixedPainter = new QPainter( backPixmap ); mixedPainter->translate( -limits.left(), -limits.top() ); } /** 5 -- MIXED FLOW. Draw ANNOTATIONS [OPAQUE ONES] on ACTIVE PAINTER **/ if ( unbufferedAnnotations ) { // iterate over annotations and paint AText, AGeom, AStamp QList< Okular::Annotation * >::const_iterator aIt = unbufferedAnnotations->constBegin(), aEnd = unbufferedAnnotations->constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; // honor opacity settings on supported types unsigned int opacity = (unsigned int)( 255.0 * a->style().opacity() ); // skip the annotation drawing if all the annotation is fully // transparent, but not with text annotations if ( opacity <= 0 && a->subType() != Okular::Annotation::AText ) continue; QColor acolor = a->style().color(); if ( !acolor.isValid() ) acolor = Qt::yellow; acolor.setAlpha( opacity ); // get annotation boundary and drawn rect QRect annotBoundary = a->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); QRect annotRect = annotBoundary.intersected( limits ); QRect innerRect( annotRect.left() - annotBoundary.left(), annotRect.top() - annotBoundary.top(), annotRect.width(), annotRect.height() ); QRectF dInnerRect(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr); Okular::Annotation::SubType type = a->subType(); // draw TextAnnotation if ( type == Okular::Annotation::AText ) { Okular::TextAnnotation * text = (Okular::TextAnnotation *)a; if ( text->textType() == Okular::TextAnnotation::InPlace ) { QImage image( annotBoundary.size(), QImage::Format_ARGB32 ); image.fill( acolor.rgba() ); QPainter painter( &image ); painter.setFont( text->textFont() ); Qt::AlignmentFlag halign = ( text->inplaceAlignment() == 1 ? Qt::AlignHCenter : ( text->inplaceAlignment() == 2 ? Qt::AlignRight : Qt::AlignLeft ) ); const double invXScale = (double)page->width() / scaledWidth; const double invYScale = (double)page->height() / scaledHeight; const double borderWidth = text->style().width(); painter.scale( 1 / invXScale, 1 / invYScale ); painter.drawText( borderWidth * invXScale, borderWidth * invYScale, (image.width() - 2 * borderWidth) * invXScale, (image.height() - 2 * borderWidth) * invYScale, Qt::AlignTop | halign | Qt::TextWrapAnywhere, text->contents() ); painter.resetTransform(); //Required as asking for a zero width pen results //in a default width pen (1.0) being created if ( borderWidth != 0 ) { QPen pen( Qt::black, borderWidth ); painter.setPen( pen ); painter.drawRect( 0, 0, image.width() - 1, image.height() - 1 ); } painter.end(); mixedPainter->drawImage( annotBoundary.topLeft(), image ); } else if ( text->textType() == Okular::TextAnnotation::Linked ) { // get pixmap, colorize and alpha-blend it QString path; QPixmap pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::User, 32, KIconLoader::DefaultState, QStringList(), &path, true ); if ( path.isEmpty() ) pixmap = GuiUtils::iconLoader()->loadIcon( text->textIcon().toLower(), KIconLoader::NoGroup, 32 ); QRect annotBoundary2 = QRect( annotBoundary.topLeft(), QSize( TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr ) ); QRect annotRect2 = annotBoundary2.intersected( limits ); QRect innerRect2( annotRect2.left() - annotBoundary2.left(), annotRect2.top() - annotBoundary2.top(), annotRect2.width(), annotRect2.height() ); QPixmap scaledCroppedPixmap = pixmap.scaled(TEXTANNOTATION_ICONSIZE * dpr, TEXTANNOTATION_ICONSIZE * dpr).copy(dInnerRect.toAlignedRect()); scaledCroppedPixmap.setDevicePixelRatio(dpr); QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); // if the annotation color is valid (ie it was set), then // use it to colorize the icon, otherwise the icon will be // "gray" if ( a->style().color().isValid() ) GuiUtils::colorizeImage( scaledCroppedImage, a->style().color(), opacity ); pixmap = QPixmap::fromImage( scaledCroppedImage ); // draw the mangled image to painter mixedPainter->drawPixmap( annotRect.topLeft(), pixmap); } } // draw StampAnnotation else if ( type == Okular::Annotation::AStamp ) { Okular::StampAnnotation * stamp = (Okular::StampAnnotation *)a; // get pixmap and alpha blend it if needed QPixmap pixmap = GuiUtils::loadStamp( stamp->stampIconName(), annotBoundary.size() ); if ( !pixmap.isNull() ) // should never happen but can happen on huge sizes { const QRect dInnerRect(QRectF(innerRect.x() * dpr, innerRect.y() * dpr, innerRect.width() * dpr, innerRect.height() * dpr).toAlignedRect()); QPixmap scaledCroppedPixmap = pixmap.scaled(annotBoundary.width() * dpr, annotBoundary.height() * dpr).copy(dInnerRect); scaledCroppedPixmap.setDevicePixelRatio(dpr); QImage scaledCroppedImage = scaledCroppedPixmap.toImage(); if ( opacity < 255 ) changeImageAlpha( scaledCroppedImage, opacity ); pixmap = QPixmap::fromImage( scaledCroppedImage ); // draw the scaled and al mixedPainter->drawPixmap( annotRect.topLeft(), pixmap ); } } // draw GeomAnnotation else if ( type == Okular::Annotation::AGeom ) { Okular::GeomAnnotation * geom = (Okular::GeomAnnotation *)a; // check whether there's anything to draw if ( geom->style().width() || geom->geometricalInnerColor().isValid() ) { mixedPainter->save(); const double width = geom->style().width() * Okular::Utils::realDpi(nullptr).width() / ( 72.0 * 2.0 ) * scaledWidth / page->width(); QRectF r( .0, .0, annotBoundary.width(), annotBoundary.height() ); r.adjust( width, width, -width, -width ); r.translate( annotBoundary.topLeft() ); if ( geom->geometricalInnerColor().isValid() ) { r.adjust( width, width, -width, -width ); const QColor color = geom->geometricalInnerColor(); mixedPainter->setPen( Qt::NoPen ); mixedPainter->setBrush( QColor( color.red(), color.green(), color.blue(), opacity ) ); if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare ) mixedPainter->drawRect( r ); else mixedPainter->drawEllipse( r ); r.adjust( -width, -width, width, width ); } if ( geom->style().width() ) // need to check the original size here.. { mixedPainter->setPen( buildPen( a, width * 2, acolor ) ); mixedPainter->setBrush( Qt::NoBrush ); if ( geom->geometricalType() == Okular::GeomAnnotation::InscribedSquare ) mixedPainter->drawRect( r ); else mixedPainter->drawEllipse( r ); } mixedPainter->restore(); } } // draw extents rectangle if ( Okular::Settings::debugDrawAnnotationRect() ) { mixedPainter->setPen( a->style().color() ); mixedPainter->drawRect( annotBoundary ); } } } if ( boundingRectOnlyAnn ) { QRect annotBoundary = boundingRectOnlyAnn->transformedBoundingRectangle().geometry( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ); mixedPainter->setPen( Qt::DashLine ); mixedPainter->drawRect( annotBoundary ); } /** 6 -- MIXED FLOW. Draw LINKS+IMAGES BORDER on ACTIVE PAINTER **/ if ( enhanceLinks || enhanceImages ) { mixedPainter->save(); mixedPainter->scale( scaledWidth, scaledHeight ); mixedPainter->translate( -crop.left, -crop.top ); QColor normalColor = QApplication::palette().color( QPalette::Active, QPalette::Highlight ); // enlarging limits for intersection is like growing the 'rectGeometry' below QRect limitsEnlarged = limits; limitsEnlarged.adjust( -2, -2, 2, 2 ); // draw rects that are inside the 'limits' paint region as opaque rects QLinkedList< Okular::ObjectRect * >::const_iterator lIt = page->m_rects.constBegin(), lEnd = page->m_rects.constEnd(); for ( ; lIt != lEnd; ++lIt ) { Okular::ObjectRect * rect = *lIt; if ( (enhanceLinks && rect->objectType() == Okular::ObjectRect::Action) || (enhanceImages && rect->objectType() == Okular::ObjectRect::Image) ) { if ( limitsEnlarged.intersects( rect->boundingRect( scaledWidth, scaledHeight ).translated( -scaledCrop.topLeft() ) ) ) { mixedPainter->strokePath( rect->region(), QPen( normalColor, 0 ) ); } } } mixedPainter->restore(); } /** 7 -- BUFFERED FLOW. Copy BACKPIXMAP on DESTINATION PAINTER **/ if ( useBackBuffer ) { delete mixedPainter; destPainter->drawPixmap( limits.left(), limits.top(), *backPixmap ); delete backPixmap; } // delete object containers delete bufferedHighlights; delete bufferedAnnotations; delete unbufferedAnnotations; qDebug() << "PagePainter END"; } /** Private Helpers :: Pixmap conversion **/ void PagePainter::cropPixmapOnImage( QImage & dest, const QPixmap * src, const QRect & r ) { qreal dpr = src->devicePixelRatioF(); // handle quickly the case in which the whole pixmap has to be converted if ( r == QRect( 0, 0, src->width() / dpr, src->height() / dpr ) ) { dest = src->toImage(); dest = dest.convertToFormat(QImage::Format_ARGB32_Premultiplied); } // else copy a portion of the src to an internal pixmap (smaller) and convert it else { QImage croppedImage( r.width() * dpr, r.height() * dpr, QImage::Format_ARGB32_Premultiplied ); croppedImage.setDevicePixelRatio(dpr); QPainter p( &croppedImage ); p.drawPixmap( 0, 0, *src, r.left(), r.top(), r.width(), r.height() ); p.end(); dest = croppedImage; } } void PagePainter::recolor(QImage *image, const QColor &foreground, const QColor &background) { if (image->format() != QImage::Format_ARGB32_Premultiplied) { qCWarning(OkularUiDebug) << "Wrong image format! Converting..."; *image = image->convertToFormat(QImage::Format_ARGB32_Premultiplied); } Q_ASSERT(image->format() == QImage::Format_ARGB32_Premultiplied); const float scaleRed = background.redF() - foreground.redF(); const float scaleGreen = background.greenF() - foreground.greenF(); const float scaleBlue = background.blueF() - foreground.blueF(); for (int y=0; yheight(); y++) { QRgb *pixels = reinterpret_cast(image->scanLine(y)); for (int x=0; xwidth(); x++) { const int lightness = qGray(pixels[x]); pixels[x] = qRgba(scaleRed * lightness + foreground.red(), scaleGreen * lightness + foreground.green(), scaleBlue * lightness + foreground.blue(), qAlpha(pixels[x])); } } } /** Private Helpers :: Image Drawing **/ // from Arthur - qt4 static inline int qt_div_255(int x) { return (x + (x>>8) + 0x80) >> 8; } void PagePainter::changeImageAlpha( QImage & image, unsigned int destAlpha ) { // iterate over all pixels changing the alpha component value unsigned int * data = (unsigned int *)image.bits(); unsigned int pixels = image.width() * image.height(); int source, sourceAlpha; for( unsigned int i = 0; i < pixels; ++i ) { // optimize this loop keeping byte order into account source = data[i]; if ( (sourceAlpha = qAlpha( source )) == 255 ) { // use destAlpha data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), destAlpha ); } else { // use destAlpha * sourceAlpha product sourceAlpha = qt_div_255( destAlpha * sourceAlpha ); data[i] = qRgba( qRed(source), qGreen(source), qBlue(source), sourceAlpha ); } } } void PagePainter::drawShapeOnImage( QImage & image, const NormalizedPath & normPath, bool closeShape, const QPen & pen, const QBrush & brush, double penWidthMultiplier, RasterOperation op //float antiAliasRadius ) { // safety checks int pointsNumber = normPath.size(); if ( pointsNumber < 2 ) return; int imageWidth = image.width(); int imageHeight = image.height(); double fImageWidth = (double)imageWidth; double fImageHeight = (double)imageHeight; // stroke outline double penWidth = (double)pen.width() * penWidthMultiplier; QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); QPen pen2 = pen; pen2.setWidthF(penWidth); painter.setPen(pen2); painter.setBrush(brush); if (op == Multiply) { painter.setCompositionMode(QPainter::CompositionMode_Multiply); } if ( brush.style() == Qt::NoBrush ) { // create a polygon QPolygonF poly( closeShape ? pointsNumber + 1 : pointsNumber ); for ( int i = 0; i < pointsNumber; ++i ) { poly[ i ] = QPointF( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight ); } if ( closeShape ) poly[ pointsNumber ] = poly[ 0 ]; painter.drawPolyline( poly ); } else { // create a 'path' QPainterPath path; path.setFillRule( Qt::WindingFill ); path.moveTo( normPath[ 0 ].x * fImageWidth, normPath[ 0 ].y * fImageHeight ); for ( int i = 1; i < pointsNumber; i++ ) { path.lineTo( normPath[ i ].x * fImageWidth, normPath[ i ].y * fImageHeight ); } if ( closeShape ) path.closeSubpath(); painter.drawPath( path ); } } /* kate: replace-tabs on; indent-width 4; */ diff --git a/ui/pageview.cpp b/ui/pageview.cpp index 5a98d912b..05ade0274 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -1,5348 +1,5348 @@ /*************************************************************************** * Copyright (C) 2004-2005 by Enrico Ros * * Copyright (C) 2004-2006 by Albert Astals Cid * * * * With portions of code from kpdf/kpdf_pagewidget.cc by: * * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003 by Dirk Mueller * * Copyright (C) 2004 by James Ots * * Copyright (C) 2011 by Jiri Baum - NICTA * * * * 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. * ***************************************************************************/ #include "pageview.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // system includes #include #include // local includes #include "debug_ui.h" #include "formwidgets.h" #include "pageviewutils.h" #include "pagepainter.h" #include "core/annotations.h" #include "annotwindow.h" #include "guiutils.h" #include "annotationpopup.h" #include "pageviewannotator.h" #include "pageviewmouseannotation.h" #include "priorities.h" #include "toolaction.h" #include "okmenutitle.h" #ifdef HAVE_SPEECH #include "tts.h" #endif #include "videowidget.h" #include "core/action.h" #include "core/area.h" #include "core/document_p.h" #include "core/form.h" #include "core/page.h" #include "core/misc.h" #include "core/generator.h" #include "core/movie.h" #include "core/audioplayer.h" #include "core/sourcereference.h" #include "core/tile.h" #include "settings.h" #include "settings_core.h" #include "url_utils.h" #include "magnifierview.h" static const int pageflags = PagePainter::Accessibility | PagePainter::EnhanceLinks | PagePainter::EnhanceImages | PagePainter::Highlights | PagePainter::TextSelection | PagePainter::Annotations; static const float kZoomValues[] = { 0.12, 0.25, 0.33, 0.50, 0.66, 0.75, 1.00, 1.25, 1.50, 2.00, 4.00, 8.00, 16.00 }; static inline double normClamp( double value, double def ) { return ( value < 0.0 || value > 1.0 ) ? def : value; } struct TableSelectionPart { PageViewItem * item; Okular::NormalizedRect rectInItem; Okular::NormalizedRect rectInSelection; TableSelectionPart(PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p); }; TableSelectionPart::TableSelectionPart( PageViewItem * item_p, const Okular::NormalizedRect &rectInItem_p, const Okular::NormalizedRect &rectInSelection_p) : item ( item_p ), rectInItem (rectInItem_p), rectInSelection (rectInSelection_p) { } // structure used internally by PageView for data storage class PageViewPrivate { public: PageViewPrivate( PageView *qq ); FormWidgetsController* formWidgetsController(); #ifdef HAVE_SPEECH OkularTTS* tts(); #endif QString selectedText() const; // the document, pageviewItems and the 'visible cache' PageView *q; Okular::Document * document; QVector< PageViewItem * > items; QLinkedList< PageViewItem * > visibleItems; MagnifierView *magnifierView; // view layout (columns and continuous in Settings), zoom and mouse PageView::ZoomMode zoomMode; float zoomFactor; QPoint mouseGrabPos; QPoint mousePressPos; QPoint mouseSelectPos; int mouseMidLastY; bool mouseSelecting; QRect mouseSelectionRect; QColor mouseSelectionColor; bool mouseTextSelecting; QSet< int > pagesWithTextSelection; bool mouseOnRect; int mouseMode; MouseAnnotation * mouseAnnotation; // table selection QList tableSelectionCols; QList tableSelectionRows; QList tableSelectionParts; bool tableDividersGuessed; // viewport move bool viewportMoveActive; QTime viewportMoveTime; QPoint viewportMoveDest; int lastSourceLocationViewportPageNumber; double lastSourceLocationViewportNormalizedX; double lastSourceLocationViewportNormalizedY; QTimer * viewportMoveTimer; int controlWheelAccumulatedDelta; // auto scroll int scrollIncrement; QTimer * autoScrollTimer; // annotations PageViewAnnotator * annotator; //text annotation dialogs list QHash< Okular::Annotation *, AnnotWindow * > m_annowindows; // other stuff QTimer * delayResizeEventTimer; bool dirtyLayout; bool blockViewport; // prevents changes to viewport bool blockPixmapsRequest; // prevent pixmap requests PageViewMessage * messageWindow; // in pageviewutils.h bool m_formsVisible; FormWidgetsController *formsWidgetController; #ifdef HAVE_SPEECH OkularTTS * m_tts; #endif QTimer * refreshTimer; QSet refreshPages; // bbox state for Trim to Selection mode Okular::NormalizedRect trimBoundingBox; // infinite resizing loop prevention bool verticalScrollBarVisible; bool horizontalScrollBarVisible; // drag scroll QPoint dragScrollVector; QTimer dragScrollTimer; // left click depress QTimer leftClickTimer; // actions QAction * aRotateClockwise; QAction * aRotateCounterClockwise; QAction * aRotateOriginal; KSelectAction * aPageSizes; KActionMenu * aTrimMode; KToggleAction * aTrimMargins; QAction * aMouseNormal; QAction * aMouseSelect; QAction * aMouseTextSelect; QAction * aMouseTableSelect; QAction * aMouseMagnifier; KToggleAction * aTrimToSelection; KToggleAction * aToggleAnnotator; KSelectAction * aZoom; QAction * aZoomIn; QAction * aZoomOut; KToggleAction * aZoomFitWidth; KToggleAction * aZoomFitPage; KToggleAction * aZoomAutoFit; KActionMenu * aViewMode; KToggleAction * aViewContinuous; QAction * aPrevAction; QAction * aToggleForms; QAction * aSpeakDoc; QAction * aSpeakPage; QAction * aSpeakStop; KActionCollection * actionCollection; QActionGroup * mouseModeActionGroup; QAction * aFitWindowToPage; int setting_viewCols; bool rtl_Mode; // Keep track of whether tablet pen is currently pressed down bool penDown; }; PageViewPrivate::PageViewPrivate( PageView *qq ) : q( qq ) #ifdef HAVE_SPEECH , m_tts( 0 ) #endif { } FormWidgetsController* PageViewPrivate::formWidgetsController() { if ( !formsWidgetController ) { formsWidgetController = new FormWidgetsController( document ); QObject::connect( formsWidgetController, SIGNAL( changed( int ) ), q, SLOT( slotFormChanged( int ) ) ); QObject::connect( formsWidgetController, SIGNAL( action( Okular::Action* ) ), q, SLOT( slotAction( Okular::Action* ) ) ); } return formsWidgetController; } #ifdef HAVE_SPEECH OkularTTS* PageViewPrivate::tts() { if ( !m_tts ) { m_tts = new OkularTTS( q ); if ( aSpeakStop ) { QObject::connect( m_tts, &OkularTTS::isSpeaking, aSpeakStop, &QAction::setEnabled ); } } return m_tts; } #endif /* PageView. What's in this file? -> quick overview. * Code weight (in rows) and meaning: * 160 - constructor and creating actions plus their connected slots (empty stuff) * 70 - DocumentObserver inherited methodes (important) * 550 - events: mouse, keyboard, drag * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc.. * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable, * and many insignificant stuff like this comment :-) */ PageView::PageView( QWidget *parent, Okular::Document *document ) : QAbstractScrollArea( parent ) , Okular::View( QLatin1String( "PageView" ) ) { // create and initialize private storage structure d = new PageViewPrivate( this ); d->document = document; d->aRotateClockwise = 0; d->aRotateCounterClockwise = 0; d->aRotateOriginal = 0; d->aViewMode = 0; d->zoomMode = PageView::ZoomFitWidth; d->zoomFactor = 1.0; d->mouseSelecting = false; d->mouseTextSelecting = false; d->mouseOnRect = false; d->mouseMode = Okular::Settings::mouseMode(); d->mouseAnnotation = new MouseAnnotation( this, document ); d->tableDividersGuessed = false; d->viewportMoveActive = false; d->lastSourceLocationViewportPageNumber = -1; d->lastSourceLocationViewportNormalizedX = 0.0; d->lastSourceLocationViewportNormalizedY = 0.0; d->viewportMoveTimer = 0; d->controlWheelAccumulatedDelta = 0; d->scrollIncrement = 0; d->autoScrollTimer = 0; d->annotator = 0; d->dirtyLayout = false; d->blockViewport = false; d->blockPixmapsRequest = false; d->messageWindow = new PageViewMessage(this); d->m_formsVisible = false; d->formsWidgetController = 0; #ifdef HAVE_SPEECH d->m_tts = 0; #endif d->refreshTimer = 0; d->aRotateClockwise = 0; d->aRotateCounterClockwise = 0; d->aRotateOriginal = 0; d->aPageSizes = 0; d->aTrimMode = 0; d->aTrimMargins = 0; d->aTrimToSelection = 0; d->aMouseNormal = 0; d->aMouseSelect = 0; d->aMouseTextSelect = 0; d->aToggleAnnotator = 0; d->aZoomFitWidth = 0; d->aZoomFitPage = 0; d->aZoomAutoFit = 0; d->aViewMode = 0; d->aViewContinuous = 0; d->aPrevAction = 0; d->aToggleForms = 0; d->aSpeakDoc = 0; d->aSpeakPage = 0; d->aSpeakStop = 0; d->actionCollection = 0; d->aPageSizes=0; d->setting_viewCols = Okular::Settings::viewColumns(); d->rtl_Mode = Okular::Settings::rtlReadingDirection(); d->mouseModeActionGroup = 0; d->penDown = false; d->aMouseMagnifier = 0; d->aFitWindowToPage = 0; d->trimBoundingBox = Okular::NormalizedRect(); // Null box switch( Okular::Settings::zoomMode() ) { case 0: { d->zoomFactor = 1; d->zoomMode = PageView::ZoomFixed; break; } case 1: { d->zoomMode = PageView::ZoomFitWidth; break; } case 2: { d->zoomMode = PageView::ZoomFitPage; break; } case 3: { d->zoomMode = PageView::ZoomFitAuto; break; } } d->delayResizeEventTimer = new QTimer( this ); d->delayResizeEventTimer->setSingleShot( true ); connect( d->delayResizeEventTimer, &QTimer::timeout, this, &PageView::delayedResizeEvent ); setFrameStyle(QFrame::NoFrame); setAttribute( Qt::WA_StaticContents ); setObjectName( QStringLiteral( "okular::pageView" ) ); // viewport setup: setup focus, and track mouse viewport()->setFocusProxy( this ); viewport()->setFocusPolicy( Qt::StrongFocus ); viewport()->setAttribute( Qt::WA_OpaquePaintEvent ); viewport()->setAttribute( Qt::WA_NoSystemBackground ); viewport()->setMouseTracking( true ); viewport()->setAutoFillBackground( false ); // the apparently "magic" value of 20 is the same used internally in QScrollArea verticalScrollBar()->setCursor( Qt::ArrowCursor ); verticalScrollBar()->setSingleStep( 20 ); horizontalScrollBar()->setCursor( Qt::ArrowCursor ); horizontalScrollBar()->setSingleStep( 20 ); // conntect the padding of the viewport to pixmaps requests connect(horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &PageView::slotRequestVisiblePixmaps); connect(verticalScrollBar(), &QAbstractSlider::valueChanged, this, &PageView::slotRequestVisiblePixmaps); connect( &d->dragScrollTimer, &QTimer::timeout, this, &PageView::slotDragScroll ); d->leftClickTimer.setSingleShot( true ); connect( &d->leftClickTimer, &QTimer::timeout, this, &PageView::slotShowSizeAllCursor ); // set a corner button to resize the view to the page size // QPushButton * resizeButton = new QPushButton( viewport() ); // resizeButton->setPixmap( SmallIcon("crop") ); // setCornerWidget( resizeButton ); // resizeButton->setEnabled( false ); // connect(...); setAttribute( Qt::WA_InputMethodEnabled, true ); // Grab pinch gestures to zoom and rotate the view grabGesture(Qt::PinchGesture); d->magnifierView = new MagnifierView(document, this); d->magnifierView->hide(); d->magnifierView->setGeometry(0, 0, 351, 201); // TODO: more dynamic? connect(document, &Okular::Document::processMovieAction, this, &PageView::slotProcessMovieAction); connect(document, &Okular::Document::processRenditionAction, this, &PageView::slotProcessRenditionAction); // schedule the welcome message QMetaObject::invokeMethod(this, "slotShowWelcome", Qt::QueuedConnection); } PageView::~PageView() { #ifdef HAVE_SPEECH if ( d->m_tts ) d->m_tts->stopAllSpeechs(); #endif delete d->mouseAnnotation; // delete the local storage structure // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // will bite us and clear d->m_annowindows QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows; d->m_annowindows.clear(); qDeleteAll( annowindows ); // delete all widgets QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; delete d->formsWidgetController; d->document->removeObserver( this ); delete d; } void PageView::setupBaseActions( KActionCollection * ac ) { d->actionCollection = ac; // Zoom actions ( higher scales takes lots of memory! ) d->aZoom = new KSelectAction(QIcon::fromTheme( QStringLiteral("page-zoom") ), i18n("Zoom"), this); ac->addAction(QStringLiteral("zoom_to"), d->aZoom ); d->aZoom->setEditable( true ); d->aZoom->setMaxComboViewCount( 14 ); connect( d->aZoom, SIGNAL(triggered(QAction*)), this, SLOT(slotZoom()) ); updateZoomText(); d->aZoomIn = KStandardAction::zoomIn( this, SLOT(slotZoomIn()), ac ); d->aZoomOut = KStandardAction::zoomOut( this, SLOT(slotZoomOut()), ac ); } void PageView::setupViewerActions( KActionCollection * ac ) { d->actionCollection = ac; ac->setDefaultShortcut(d->aZoomIn, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Plus)); ac->setDefaultShortcut(d->aZoomOut, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Minus)); // orientation menu actions d->aRotateClockwise = new QAction( QIcon::fromTheme( QStringLiteral("object-rotate-right") ), i18n( "Rotate &Right" ), this ); d->aRotateClockwise->setIconText( i18nc( "Rotate right", "Right" ) ); ac->addAction( QStringLiteral("view_orientation_rotate_cw"), d->aRotateClockwise ); d->aRotateClockwise->setEnabled( false ); connect( d->aRotateClockwise, &QAction::triggered, this, &PageView::slotRotateClockwise ); d->aRotateCounterClockwise = new QAction( QIcon::fromTheme( QStringLiteral("object-rotate-left") ), i18n( "Rotate &Left" ), this ); d->aRotateCounterClockwise->setIconText( i18nc( "Rotate left", "Left" ) ); ac->addAction( QStringLiteral("view_orientation_rotate_ccw"), d->aRotateCounterClockwise ); d->aRotateCounterClockwise->setEnabled( false ); connect( d->aRotateCounterClockwise, &QAction::triggered, this, &PageView::slotRotateCounterClockwise ); d->aRotateOriginal = new QAction( i18n( "Original Orientation" ), this ); ac->addAction( QStringLiteral("view_orientation_original"), d->aRotateOriginal ); d->aRotateOriginal->setEnabled( false ); connect( d->aRotateOriginal, &QAction::triggered, this, &PageView::slotRotateOriginal ); d->aPageSizes = new KSelectAction(i18n("&Page Size"), this); ac->addAction(QStringLiteral("view_pagesizes"), d->aPageSizes); d->aPageSizes->setEnabled( false ); connect( d->aPageSizes , SIGNAL(triggered(int)), this, SLOT(slotPageSizes(int)) ); // Trim View actions d->aTrimMode = new KActionMenu(i18n( "&Trim View" ), this ); d->aTrimMode->setDelayed( false ); ac->addAction(QStringLiteral("view_trim_mode"), d->aTrimMode ); d->aTrimMargins = new KToggleAction( i18n( "&Trim Margins" ), d->aTrimMode->menu() ); d->aTrimMode->addAction( d->aTrimMargins ); ac->addAction( QStringLiteral("view_trim_margins"), d->aTrimMargins ); d->aTrimMargins->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Margins ) ); connect( d->aTrimMargins, &QAction::toggled, this, &PageView::slotTrimMarginsToggled ); d->aTrimMargins->setChecked( Okular::Settings::trimMargins() ); d->aTrimToSelection = new KToggleAction( i18n( "Trim To &Selection" ), d->aTrimMode->menu() ); d->aTrimMode->addAction( d->aTrimToSelection); ac->addAction( QStringLiteral("view_trim_selection"), d->aTrimToSelection); d->aTrimToSelection->setData( qVariantFromValue( (int)Okular::Settings::EnumTrimMode::Selection ) ); connect( d->aTrimToSelection, &QAction::toggled, this, &PageView::slotTrimToSelectionToggled ); d->aZoomFitWidth = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-width") ), i18n("Fit &Width"), this); ac->addAction(QStringLiteral("view_fit_to_width"), d->aZoomFitWidth ); connect( d->aZoomFitWidth, &QAction::toggled, this, &PageView::slotFitToWidthToggled ); d->aZoomFitPage = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-best") ), i18n("Fit &Page"), this); ac->addAction(QStringLiteral("view_fit_to_page"), d->aZoomFitPage ); connect( d->aZoomFitPage, &QAction::toggled, this, &PageView::slotFitToPageToggled ); d->aZoomAutoFit = new KToggleAction(QIcon::fromTheme( QStringLiteral("zoom-fit-best") ), i18n("&Auto Fit"), this); ac->addAction(QStringLiteral("view_auto_fit"), d->aZoomAutoFit ); connect( d->aZoomAutoFit, &QAction::toggled, this, &PageView::slotAutoFitToggled ); d->aFitWindowToPage = new QAction(QIcon::fromTheme( QStringLiteral("zoom-fit-width") ), i18n("Fit Wi&ndow to Page"), this); d->aFitWindowToPage->setEnabled( Okular::Settings::viewMode() == (int)Okular::Settings::EnumViewMode::Single ); ac->setDefaultShortcut(d->aFitWindowToPage, QKeySequence(Qt::CTRL + Qt::Key_J) ); ac->addAction( QStringLiteral("fit_window_to_page"), d->aFitWindowToPage ); connect( d->aFitWindowToPage, &QAction::triggered, this, &PageView::slotFitWindowToPage ); // View-Layout actions d->aViewMode = new KActionMenu( QIcon::fromTheme( QStringLiteral("view-split-left-right") ), i18n( "&View Mode" ), this ); d->aViewMode->setDelayed( false ); #define ADD_VIEWMODE_ACTION( text, name, id ) \ do { \ QAction *vm = new QAction( text, this ); \ vm->setCheckable( true ); \ vm->setData( qVariantFromValue( id ) ); \ d->aViewMode->addAction( vm ); \ ac->addAction( QStringLiteral(name), vm ); \ vmGroup->addAction( vm ); \ } while( 0 ) ac->addAction(QStringLiteral("view_render_mode"), d->aViewMode ); QActionGroup *vmGroup = new QActionGroup( this ); //d->aViewMode->menu() ); ADD_VIEWMODE_ACTION( i18n( "Single Page" ), "view_render_mode_single", (int)Okular::Settings::EnumViewMode::Single ); ADD_VIEWMODE_ACTION( i18n( "Facing Pages" ), "view_render_mode_facing", (int)Okular::Settings::EnumViewMode::Facing ); ADD_VIEWMODE_ACTION( i18n( "Facing Pages (Center First Page)" ), "view_render_mode_facing_center_first", (int)Okular::Settings::EnumViewMode::FacingFirstCentered ); ADD_VIEWMODE_ACTION( i18n( "Overview" ), "view_render_mode_overview", (int)Okular::Settings::EnumViewMode::Summary ); const QList viewModeActions = d->aViewMode->menu()->actions(); foreach(QAction *viewModeAction, viewModeActions) { if (viewModeAction->data().toInt() == Okular::Settings::viewMode()) { viewModeAction->setChecked( true ); } } connect( vmGroup, &QActionGroup::triggered, this, &PageView::slotViewMode ); #undef ADD_VIEWMODE_ACTION d->aViewContinuous = new KToggleAction(QIcon::fromTheme( QStringLiteral("view-list-text") ), i18n("&Continuous"), this); ac->addAction(QStringLiteral("view_continuous"), d->aViewContinuous ); connect( d->aViewContinuous, &QAction::toggled, this, &PageView::slotContinuousToggled ); d->aViewContinuous->setChecked( Okular::Settings::viewContinuous() ); // Mouse mode actions for viewer mode d->mouseModeActionGroup = new QActionGroup( this ); d->mouseModeActionGroup->setExclusive( true ); d->aMouseNormal = new QAction( QIcon::fromTheme( QStringLiteral("input-mouse") ), i18n( "&Browse Tool" ), this ); ac->addAction(QStringLiteral("mouse_drag"), d->aMouseNormal ); connect( d->aMouseNormal, &QAction::triggered, this, &PageView::slotSetMouseNormal ); d->aMouseNormal->setIconText( i18nc( "Browse Tool", "Browse" ) ); d->aMouseNormal->setCheckable( true ); ac->setDefaultShortcut(d->aMouseNormal, QKeySequence(Qt::CTRL + Qt::Key_1)); d->aMouseNormal->setActionGroup( d->mouseModeActionGroup ); d->aMouseNormal->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Browse ); QAction * mz = new QAction(QIcon::fromTheme( QStringLiteral("page-zoom") ), i18n("&Zoom Tool"), this); ac->addAction(QStringLiteral("mouse_zoom"), mz ); connect( mz, &QAction::triggered, this, &PageView::slotSetMouseZoom ); mz->setIconText( i18nc( "Zoom Tool", "Zoom" ) ); mz->setCheckable( true ); ac->setDefaultShortcut(mz, QKeySequence(Qt::CTRL + Qt::Key_2)); mz->setActionGroup( d->mouseModeActionGroup ); mz->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Zoom ); QAction * aToggleChangeColors = new QAction(i18n("&Toggle Change Colors"), this); ac->addAction(QStringLiteral("toggle_change_colors"), aToggleChangeColors ); connect( aToggleChangeColors, &QAction::triggered, this, &PageView::slotToggleChangeColors ); } // WARNING: 'setupViewerActions' must have been called before this method void PageView::setupActions( KActionCollection * ac ) { d->actionCollection = ac; ac->setDefaultShortcuts(d->aZoomIn, KStandardShortcut::zoomIn()); ac->setDefaultShortcuts(d->aZoomOut, KStandardShortcut::zoomOut()); // Mouse-Mode actions d->aMouseSelect = new QAction(QIcon::fromTheme( QStringLiteral("select-rectangular") ), i18n("&Selection Tool"), this); ac->addAction(QStringLiteral("mouse_select"), d->aMouseSelect ); connect( d->aMouseSelect, &QAction::triggered, this, &PageView::slotSetMouseSelect ); d->aMouseSelect->setIconText( i18nc( "Select Tool", "Selection" ) ); d->aMouseSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseSelect, Qt::CTRL + Qt::Key_3); d->aMouseSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::RectSelect ); d->aMouseTextSelect = new QAction(QIcon::fromTheme( QStringLiteral("draw-text") ), i18n("&Text Selection Tool"), this); ac->addAction(QStringLiteral("mouse_textselect"), d->aMouseTextSelect ); connect( d->aMouseTextSelect, &QAction::triggered, this, &PageView::slotSetMouseTextSelect ); d->aMouseTextSelect->setIconText( i18nc( "Text Selection Tool", "Text Selection" ) ); d->aMouseTextSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseTextSelect, Qt::CTRL + Qt::Key_4); d->aMouseTextSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseTextSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TextSelect ); d->aMouseTableSelect = new QAction(QIcon::fromTheme( QStringLiteral("table") ), i18n("T&able Selection Tool"), this); ac->addAction(QStringLiteral("mouse_tableselect"), d->aMouseTableSelect ); connect( d->aMouseTableSelect, &QAction::triggered, this, &PageView::slotSetMouseTableSelect ); d->aMouseTableSelect->setIconText( i18nc( "Table Selection Tool", "Table Selection" ) ); d->aMouseTableSelect->setCheckable( true ); ac->setDefaultShortcut(d->aMouseTableSelect, Qt::CTRL + Qt::Key_5); d->aMouseTableSelect->setActionGroup( d->mouseModeActionGroup ); d->aMouseTableSelect->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::TableSelect ); d->aMouseMagnifier = new QAction(QIcon::fromTheme( QStringLiteral("document-preview") ), i18n("&Magnifier"), this); ac->addAction(QStringLiteral("mouse_magnifier"), d->aMouseMagnifier ); connect( d->aMouseMagnifier, &QAction::triggered, this, &PageView::slotSetMouseMagnifier ); d->aMouseMagnifier->setIconText( i18nc( "Magnifier Tool", "Magnifier" ) ); d->aMouseMagnifier->setCheckable( true ); ac->setDefaultShortcut(d->aMouseMagnifier, Qt::CTRL + Qt::Key_6); d->aMouseMagnifier->setActionGroup( d->mouseModeActionGroup ); d->aMouseMagnifier->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier ); d->aToggleAnnotator = new KToggleAction(QIcon::fromTheme( QStringLiteral("draw-freehand") ), i18n("&Review"), this); ac->addAction(QStringLiteral("mouse_toggle_annotate"), d->aToggleAnnotator ); d->aToggleAnnotator->setCheckable( true ); connect( d->aToggleAnnotator, &QAction::toggled, this, &PageView::slotToggleAnnotator ); ac->setDefaultShortcut(d->aToggleAnnotator, Qt::Key_F6); ToolAction *ta = new ToolAction( this ); ac->addAction( QStringLiteral("mouse_selecttools"), ta ); ta->addAction( d->aMouseSelect ); ta->addAction( d->aMouseTextSelect ); ta->addAction( d->aMouseTableSelect ); // speak actions #ifdef HAVE_SPEECH d->aSpeakDoc = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Whole Document" ), this ); ac->addAction( QStringLiteral("speak_document"), d->aSpeakDoc ); d->aSpeakDoc->setEnabled( false ); connect( d->aSpeakDoc, &QAction::triggered, this, &PageView::slotSpeakDocument ); d->aSpeakPage = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Current Page" ), this ); ac->addAction( QStringLiteral("speak_current_page"), d->aSpeakPage ); d->aSpeakPage->setEnabled( false ); connect( d->aSpeakPage, &QAction::triggered, this, &PageView::slotSpeakCurrentPage ); d->aSpeakStop = new QAction( QIcon::fromTheme( QStringLiteral("media-playback-stop") ), i18n( "Stop Speaking" ), this ); ac->addAction( QStringLiteral("speak_stop_all"), d->aSpeakStop ); d->aSpeakStop->setEnabled( false ); connect( d->aSpeakStop, &QAction::triggered, this, &PageView::slotStopSpeaks ); #else d->aSpeakDoc = 0; d->aSpeakPage = 0; d->aSpeakStop = 0; #endif // Other actions QAction * su = new QAction(i18n("Scroll Up"), this); ac->addAction(QStringLiteral("view_scroll_up"), su ); connect( su, &QAction::triggered, this, &PageView::slotAutoScrollUp ); ac->setDefaultShortcut(su, QKeySequence(Qt::SHIFT + Qt::Key_Up)); addAction(su); QAction * sd = new QAction(i18n("Scroll Down"), this); ac->addAction(QStringLiteral("view_scroll_down"), sd ); connect( sd, &QAction::triggered, this, &PageView::slotAutoScrollDown ); ac->setDefaultShortcut(sd, QKeySequence(Qt::SHIFT + Qt::Key_Down)); addAction(sd); QAction * spu = new QAction(i18n("Scroll Page Up"), this); ac->addAction( QStringLiteral("view_scroll_page_up"), spu ); connect( spu, &QAction::triggered, this, &PageView::slotScrollUp ); ac->setDefaultShortcut(spu, QKeySequence(Qt::SHIFT + Qt::Key_Space)); addAction( spu ); QAction * spd = new QAction(i18n("Scroll Page Down"), this); ac->addAction( QStringLiteral("view_scroll_page_down"), spd ); connect( spd, &QAction::triggered, this, &PageView::slotScrollDown ); ac->setDefaultShortcut(spd, QKeySequence(Qt::Key_Space)); addAction( spd ); d->aToggleForms = new QAction( this ); ac->addAction( QStringLiteral("view_toggle_forms"), d->aToggleForms ); connect( d->aToggleForms, &QAction::triggered, this, &PageView::slotToggleForms ); d->aToggleForms->setEnabled( false ); toggleFormWidgets( false ); // Setup undo and redo actions QAction *kundo = KStandardAction::create( KStandardAction::Undo, d->document, SLOT(undo()), ac ); QAction *kredo = KStandardAction::create( KStandardAction::Redo, d->document, SLOT(redo()), ac ); connect(d->document, &Okular::Document::canUndoChanged, kundo, &QAction::setEnabled); connect(d->document, &Okular::Document::canRedoChanged, kredo, &QAction::setEnabled); kundo->setEnabled(false); kredo->setEnabled(false); } bool PageView::canFitPageWidth() const { return Okular::Settings::viewMode() != Okular::Settings::EnumViewMode::Single || d->zoomMode != ZoomFitWidth; } void PageView::fitPageWidth( int page ) { // zoom: Fit Width, columns: 1. setActions + relayout + setPage + update d->zoomMode = ZoomFitWidth; Okular::Settings::setViewMode( 0 ); d->aZoomFitWidth->setChecked( true ); d->aZoomFitPage->setChecked( false ); d->aZoomAutoFit->setChecked( false ); d->aViewMode->menu()->actions().at( 0 )->setChecked( true ); viewport()->setUpdatesEnabled( false ); slotRelayoutPages(); viewport()->setUpdatesEnabled( true ); d->document->setViewportPage( page ); updateZoomText(); setFocus(); } void PageView::openAnnotationWindow( Okular::Annotation * annotation, int pageNumber ) { if ( !annotation ) return; // find the annot window AnnotWindow* existWindow = 0; QHash< Okular::Annotation *, AnnotWindow * >::ConstIterator it = d->m_annowindows.constFind( annotation ); if ( it != d->m_annowindows.constEnd() ) { existWindow = *it; } if ( existWindow == 0 ) { existWindow = new AnnotWindow( this, annotation, d->document, pageNumber ); connect(existWindow, &QObject::destroyed, this, &PageView::slotAnnotationWindowDestroyed); d->m_annowindows.insert( annotation, existWindow ); } existWindow->show(); } void PageView::slotAnnotationWindowDestroyed( QObject * window ) { QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin(); QHash< Okular::Annotation*, AnnotWindow * >::Iterator itEnd = d->m_annowindows.end(); while ( it != itEnd ) { if ( it.value() == window ) { it = d->m_annowindows.erase( it ); } else { ++it; } } } void PageView::displayMessage( const QString & message, const QString & details, PageViewMessage::Icon icon, int duration ) { if ( !Okular::Settings::showOSD() ) { if (icon == PageViewMessage::Error) { if ( !details.isEmpty() ) KMessageBox::detailedError( this, message, details ); else KMessageBox::error( this, message ); } return; } // hide messageWindow if string is empty if ( message.isEmpty() ) return d->messageWindow->hide(); // display message (duration is length dependant) if (duration==-1) { duration = 500 + 100 * message.length(); if ( !details.isEmpty() ) duration += 500 + 100 * details.length(); } d->messageWindow->display( message, details, icon, duration ); } void PageView::reparseConfig() { // set the scroll bars policies Qt::ScrollBarPolicy scrollBarMode = Okular::Settings::showScrollBars() ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff; if ( horizontalScrollBarPolicy() != scrollBarMode ) { setHorizontalScrollBarPolicy( scrollBarMode ); setVerticalScrollBarPolicy( scrollBarMode ); } if ( Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Summary && ( (int)Okular::Settings::viewColumns() != d->setting_viewCols ) ) { d->setting_viewCols = Okular::Settings::viewColumns(); slotRelayoutPages(); } if (Okular::Settings::rtlReadingDirection() != d->rtl_Mode ) { d->rtl_Mode = Okular::Settings::rtlReadingDirection(); slotRelayoutPages(); } updatePageStep(); if ( d->annotator ) { d->annotator->setEnabled( false ); d->annotator->reparseConfig(); if ( d->aToggleAnnotator->isChecked() ) slotToggleAnnotator( true ); } // Something like invert colors may have changed // As we don't have a way to find out the old value // We just update the viewport, this shouldn't be that bad // since it's just a repaint of pixmaps we already have viewport()->update(); } KActionCollection *PageView::actionCollection() const { return d->actionCollection; } QAction *PageView::toggleFormsAction() const { return d->aToggleForms; } int PageView::contentAreaWidth() const { return horizontalScrollBar()->maximum() + viewport()->width(); } int PageView::contentAreaHeight() const { return verticalScrollBar()->maximum() + viewport()->height(); } QPoint PageView::contentAreaPosition() const { return QPoint( horizontalScrollBar()->value(), verticalScrollBar()->value() ); } QPoint PageView::contentAreaPoint( const QPoint & pos ) const { return pos + contentAreaPosition(); } QPointF PageView::contentAreaPoint( const QPointF & pos ) const { return pos + contentAreaPosition(); } QString PageViewPrivate::selectedText() const { if ( pagesWithTextSelection.isEmpty() ) return QString(); QString text; QList< int > selpages = pagesWithTextSelection.toList(); qSort( selpages ); const Okular::Page * pg = 0; if ( selpages.count() == 1 ) { pg = document->page( selpages.first() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } else { pg = document->page( selpages.first() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); int end = selpages.count() - 1; for( int i = 1; i < end; ++i ) { pg = document->page( selpages.at( i ) ); text.append( pg->text( 0, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } pg = document->page( selpages.last() ); text.append( pg->text( pg->textSelection(), Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ) ); } return text; } void PageView::copyTextSelection() const { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); cb->setText( text, QClipboard::Clipboard ); } } void PageView::selectAll() { QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); for ( ; it < itEnd; ++it ) { Okular::RegularAreaRect * area = textSelectionForItem( *it ); d->pagesWithTextSelection.insert( (*it)->pageNumber() ); d->document->setPageTextSelection( (*it)->pageNumber(), area, palette().color( QPalette::Active, QPalette::Highlight ) ); } } //BEGIN DocumentObserver inherited methods void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) { bool documentChanged = setupFlags & Okular::DocumentObserver::DocumentChanged; // reuse current pages if nothing new if ( ( pageSet.count() == d->items.count() ) && !documentChanged && !( setupFlags & Okular::DocumentObserver::NewLayoutForPages ) ) { int count = pageSet.count(); for ( int i = 0; (i < count) && !documentChanged; i++ ) if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) documentChanged = true; if ( !documentChanged ) return; } // mouseAnnotation must not access our PageViewItem widgets any longer d->mouseAnnotation->reset(); // delete all widgets (one for each page in pageSet) QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; d->items.clear(); d->visibleItems.clear(); d->pagesWithTextSelection.clear(); toggleFormWidgets( false ); if ( d->formsWidgetController ) d->formsWidgetController->dropRadioButtons(); bool haspages = !pageSet.isEmpty(); bool hasformwidgets = false; // create children widgets QVector< Okular::Page * >::const_iterator setIt = pageSet.constBegin(), setEnd = pageSet.constEnd(); for ( ; setIt != setEnd; ++setIt ) { PageViewItem * item = new PageViewItem( *setIt ); d->items.push_back( item ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug).nospace() << "cropped geom for " << d->items.last()->pageNumber() << " is " << d->items.last()->croppedGeometry(); #endif const QLinkedList< Okular::FormField * > pageFields = (*setIt)->formFields(); QLinkedList< Okular::FormField * >::const_iterator ffIt = pageFields.constBegin(), ffEnd = pageFields.constEnd(); for ( ; ffIt != ffEnd; ++ffIt ) { Okular::FormField * ff = *ffIt; FormWidgetIface * w = FormWidgetFactory::createWidget( ff, viewport() ); if ( w ) { w->setPageItem( item ); w->setFormWidgetsController( d->formWidgetsController() ); w->setVisibility( false ); w->setCanBeFilled( d->document->isAllowed( Okular::AllowFillForms ) ); item->formWidgets().insert( ff->id(), w ); hasformwidgets = true; } } const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations(); QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.constBegin(), aEnd = annotations.constEnd(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; if ( a->subType() == Okular::Annotation::AMovie ) { Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), d->document, viewport() ); item->videoWidgets().insert( movieAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::ARichMedia ) { Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), d->document, viewport() ); item->videoWidgets().insert( richMediaAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn ); if ( movie ) { VideoWidget * vw = new VideoWidget( screenAnn, movie, d->document, viewport() ); item->videoWidgets().insert( movie, vw ); vw->pageInitialized(); } } } } // invalidate layout so relayout/repaint will happen on next viewport change if ( haspages ) { // We do a delayed call to slotRelayoutPages but also set the dirtyLayout // because we might end up in notifyViewportChanged while slotRelayoutPages // has not been done and we don't want that to happen d->dirtyLayout = true; QMetaObject::invokeMethod(this, "slotRelayoutPages", Qt::QueuedConnection); } else { // update the mouse cursor when closing because we may have close through a link and // want the cursor to come back to the normal cursor updateCursor(); // then, make the message window and scrollbars disappear, and trigger a repaint d->messageWindow->hide(); resizeContentArea( QSize( 0,0 ) ); viewport()->update(); // when there is no change to the scrollbars, no repaint would // be done and the old document would still be shown } // OSD to display pages if ( documentChanged && pageSet.count() > 0 && Okular::Settings::showOSD() ) d->messageWindow->display( i18np(" Loaded a one-page document.", " Loaded a %1-page document.", pageSet.count() ), QString(), PageViewMessage::Info, 4000 ); updateActionState( haspages, documentChanged, hasformwidgets ); // We need to assign it to a different list otherwise slotAnnotationWindowDestroyed // will bite us and clear d->m_annowindows QHash< Okular::Annotation *, AnnotWindow * > annowindows = d->m_annowindows; d->m_annowindows.clear(); qDeleteAll( annowindows ); selectionClear(); } void PageView::updateActionState( bool haspages, bool documentChanged, bool hasformwidgets ) { if ( d->aPageSizes ) { // may be null if dummy mode is on bool pageSizes = d->document->supportsPageSizes(); d->aPageSizes->setEnabled( pageSizes ); // set the new page sizes: // - if the generator supports them // - if the document changed if ( pageSizes && documentChanged ) { QStringList items; foreach ( const Okular::PageSize &p, d->document->pageSizes() ) items.append( p.name() ); d->aPageSizes->setItems( items ); } } if ( d->aTrimMargins ) d->aTrimMargins->setEnabled( haspages ); if ( d->aTrimToSelection ) d->aTrimToSelection->setEnabled( haspages ); if ( d->aViewMode ) d->aViewMode->setEnabled( haspages ); if ( d->aViewContinuous ) d->aViewContinuous->setEnabled( haspages ); if ( d->aZoomFitWidth ) d->aZoomFitWidth->setEnabled( haspages ); if ( d->aZoomFitPage ) d->aZoomFitPage->setEnabled( haspages ); if ( d->aZoomAutoFit ) d->aZoomAutoFit->setEnabled( haspages ); if ( d->aZoom ) { d->aZoom->selectableActionGroup()->setEnabled( haspages ); d->aZoom->setEnabled( haspages ); } if ( d->aZoomIn ) d->aZoomIn->setEnabled( haspages ); if ( d->aZoomOut ) d->aZoomOut->setEnabled( haspages ); if ( d->mouseModeActionGroup ) d->mouseModeActionGroup->setEnabled( haspages ); if ( d->aRotateClockwise ) d->aRotateClockwise->setEnabled( haspages ); if ( d->aRotateCounterClockwise ) d->aRotateCounterClockwise->setEnabled( haspages ); if ( d->aRotateOriginal ) d->aRotateOriginal->setEnabled( haspages ); if ( d->aToggleForms ) { // may be null if dummy mode is on d->aToggleForms->setEnabled( haspages && hasformwidgets ); } bool allowAnnotations = d->document->isAllowed( Okular::AllowNotes ); if ( d->annotator ) { bool allowTools = haspages && allowAnnotations; d->annotator->setToolsEnabled( allowTools ); d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); } if ( d->aToggleAnnotator ) { if ( !allowAnnotations && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); } d->aToggleAnnotator->setEnabled( allowAnnotations ); } #ifdef HAVE_SPEECH if ( d->aSpeakDoc ) { const bool enablettsactions = haspages ? Okular::Settings::useTTS() : false; d->aSpeakDoc->setEnabled( enablettsactions ); d->aSpeakPage->setEnabled( enablettsactions ); } #endif if (d->aMouseMagnifier) d->aMouseMagnifier->setEnabled(d->document->supportsTiles()); if ( d->aFitWindowToPage ) d->aFitWindowToPage->setEnabled( haspages && !Okular::Settings::viewContinuous() ); } bool PageView::areSourceLocationsShownGraphically() const { return Okular::Settings::showSourceLocationsGraphically(); } void PageView::setShowSourceLocationsGraphically(bool show) { if( show == Okular::Settings::showSourceLocationsGraphically() ) { return; } Okular::Settings::setShowSourceLocationsGraphically( show ); viewport()->update(); } void PageView::setLastSourceLocationViewport( const Okular::DocumentViewport& vp ) { if( vp.rePos.enabled ) { d->lastSourceLocationViewportNormalizedX = normClamp( vp.rePos.normalizedX, 0.5 ); d->lastSourceLocationViewportNormalizedY = normClamp( vp.rePos.normalizedY, 0.0 ); } else { d->lastSourceLocationViewportNormalizedX = 0.5; d->lastSourceLocationViewportNormalizedY = 0.0; } d->lastSourceLocationViewportPageNumber = vp.pageNumber; viewport()->update(); } void PageView::clearLastSourceLocationViewport() { d->lastSourceLocationViewportPageNumber = -1; d->lastSourceLocationViewportNormalizedX = 0.0; d->lastSourceLocationViewportNormalizedY = 0.0; viewport()->update(); } void PageView::notifyViewportChanged( bool smoothMove ) { QMetaObject::invokeMethod(this, "slotRealNotifyViewportChanged", Qt::QueuedConnection, Q_ARG( bool, smoothMove )); } void PageView::slotRealNotifyViewportChanged( bool smoothMove ) { // if we are the one changing viewport, skip this nofity if ( d->blockViewport ) return; // block setViewport outgoing calls d->blockViewport = true; // find PageViewItem matching the viewport description const Okular::DocumentViewport & vp = d->document->viewport(); PageViewItem * item = 0; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == vp.pageNumber ) { item = *iIt; break; } if ( !item ) { qCWarning(OkularUiDebug) << "viewport for page" << vp.pageNumber << "has no matching item!"; d->blockViewport = false; return; } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "document viewport changed"; #endif // relayout in "Single Pages" mode or if a relayout is pending d->blockPixmapsRequest = true; if ( !Okular::Settings::viewContinuous() || d->dirtyLayout ) slotRelayoutPages(); // restore viewport center or use default {x-center,v-top} alignment const QRect & r = item->croppedGeometry(); int newCenterX = r.left(), newCenterY = r.top(); if ( vp.rePos.enabled ) { if ( vp.rePos.pos == Okular::DocumentViewport::Center ) { newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.5 ) * (double)r.width() ); newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() ); } else { // TopLeft newCenterX += (int)( normClamp( vp.rePos.normalizedX, 0.0 ) * (double)r.width() + viewport()->width() / 2 ); newCenterY += (int)( normClamp( vp.rePos.normalizedY, 0.0 ) * (double)r.height() + viewport()->height() / 2 ); } } else { newCenterX += r.width() / 2; newCenterY += viewport()->height() / 2 - 10; } // if smooth movement requested, setup parameters and start it if ( smoothMove ) { d->viewportMoveActive = true; d->viewportMoveTime.start(); d->viewportMoveDest.setX( newCenterX ); d->viewportMoveDest.setY( newCenterY ); if ( !d->viewportMoveTimer ) { d->viewportMoveTimer = new QTimer( this ); connect( d->viewportMoveTimer, &QTimer::timeout, this, &PageView::slotMoveViewport ); } d->viewportMoveTimer->start( 25 ); verticalScrollBar()->setEnabled( false ); horizontalScrollBar()->setEnabled( false ); } else center( newCenterX, newCenterY ); d->blockPixmapsRequest = false; // request visible pixmaps in the current viewport and recompute it slotRequestVisiblePixmaps(); // enable setViewport calls d->blockViewport = false; if( viewport() ) { viewport()->update(); } // since the page has moved below cursor, update it updateCursor(); } void PageView::notifyPageChanged( int pageNumber, int changedFlags ) { // only handle pixmap / highlight changes notifies if ( changedFlags & DocumentObserver::Bookmark ) return; if ( changedFlags & DocumentObserver::Annotations ) { const QLinkedList< Okular::Annotation * > annots = d->document->page( pageNumber )->annotations(); const QLinkedList< Okular::Annotation * >::ConstIterator annItEnd = annots.end(); QHash< Okular::Annotation*, AnnotWindow * >::Iterator it = d->m_annowindows.begin(); for ( ; it != d->m_annowindows.end(); ) { QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, it.key() ); if ( annIt != annItEnd ) { (*it)->reloadInfo(); ++it; } else { AnnotWindow *w = *it; it = d->m_annowindows.erase( it ); // Need to delete after removing from the list // otherwise deleting will call slotAnnotationWindowDestroyed which will mess // the list and the iterators delete w; } } QLinkedList< Okular::Annotation * >::ConstIterator annIt = qFind( annots, d->mouseAnnotation->annotation() ); if ( annIt == annItEnd ) { d->mouseAnnotation->cancel(); } } if ( changedFlags & DocumentObserver::BoundingBox ) { #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "BoundingBox change on page" << pageNumber; #endif slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! // Repaint the whole widget since layout may have changed viewport()->update(); return; } // iterate over visible items: if page(pageNumber) is one of them, repaint it QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == pageNumber && (*iIt)->isVisible() ) { // update item's rectangle plus the little outline QRect expandedRect = (*iIt)->croppedGeometry(); // a PageViewItem is placed in the global page layout, // while we need to map its position in the viewport coordinates // (to get the correct area to repaint) expandedRect.translate( -contentAreaPosition() ); expandedRect.adjust( -1, -1, 3, 3 ); viewport()->update( expandedRect ); // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor if ( cursor().shape() != Qt::SizeVerCursor ) { // since the page has been regenerated below cursor, update it updateCursor(); } break; } } void PageView::notifyContentsCleared( int changedFlags ) { // if pixmaps were cleared, re-ask them if ( changedFlags & DocumentObserver::Pixmap ) QMetaObject::invokeMethod(this, "slotRequestVisiblePixmaps", Qt::QueuedConnection); } void PageView::notifyZoom( int factor ) { if ( factor > 0 ) updateZoom( ZoomIn ); else updateZoom( ZoomOut ); } bool PageView::canUnloadPixmap( int pageNumber ) const { if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) { // if the item is visible, forbid unloading QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( (*vIt)->pageNumber() == pageNumber ) return false; } else { // forbid unloading of the visible items, and of the previous and next QLinkedList< PageViewItem * >::const_iterator vIt = d->visibleItems.constBegin(), vEnd = d->visibleItems.constEnd(); for ( ; vIt != vEnd; ++vIt ) if ( abs( (*vIt)->pageNumber() - pageNumber ) <= 1 ) return false; } // if hidden premit unloading return true; } void PageView::notifyCurrentPageChanged( int previous, int current ) { if ( previous != -1 ) { PageViewItem * item = d->items.at( previous ); if ( item ) { Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) videoWidget->pageLeft(); } } if ( current != -1 ) { PageViewItem * item = d->items.at( current ); if ( item ) { Q_FOREACH ( VideoWidget *videoWidget, item->videoWidgets() ) videoWidget->pageEntered(); } // update zoom text and factor if in a ZoomFit/* zoom mode if ( d->zoomMode != ZoomFixed ) updateZoomText(); } } //END DocumentObserver inherited methods //BEGIN View inherited methods bool PageView::supportsCapability( ViewCapability capability ) const { switch ( capability ) { case Zoom: case ZoomModality: return true; } return false; } Okular::View::CapabilityFlags PageView::capabilityFlags( ViewCapability capability ) const { switch ( capability ) { case Zoom: case ZoomModality: return CapabilityRead | CapabilityWrite | CapabilitySerializable; } return 0; } QVariant PageView::capability( ViewCapability capability ) const { switch ( capability ) { case Zoom: return d->zoomFactor; case ZoomModality: return d->zoomMode; } return QVariant(); } void PageView::setCapability( ViewCapability capability, const QVariant &option ) { switch ( capability ) { case Zoom: { bool ok = true; double factor = option.toDouble( &ok ); if ( ok && factor > 0.0 ) { d->zoomFactor = static_cast< float >( factor ); updateZoom( ZoomRefreshCurrent ); } break; } case ZoomModality: { bool ok = true; int mode = option.toInt( &ok ); if ( ok ) { if ( mode >= 0 && mode < 3 ) updateZoom( (ZoomMode)mode ); } break; } } } //END View inherited methods //BEGIN widget events bool PageView::event( QEvent * event ) { if ( event->type() == QEvent::Gesture ) { return gestureEvent(static_cast( event )); } // do not stop the event return QAbstractScrollArea::event( event ); } bool PageView::gestureEvent( QGestureEvent * event ) { QPinchGesture *pinch = static_cast(event->gesture(Qt::PinchGesture)); if (pinch) { // Viewport zoom level at the moment where the pinch gesture starts. // The viewport zoom level _during_ the gesture will be this value // times the relative zoom reported by QGestureEvent. static qreal vanillaZoom = d->zoomFactor; if (pinch->state() == Qt::GestureStarted) { vanillaZoom = d->zoomFactor; } const QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); // Zoom if (pinch->changeFlags() & QPinchGesture::ScaleFactorChanged) { d->zoomFactor = vanillaZoom * pinch->totalScaleFactor(); d->blockPixmapsRequest = true; updateZoom( ZoomRefreshCurrent ); d->blockPixmapsRequest = false; viewport()->repaint(); } // Count the number of 90-degree rotations we did since the start of the pinch gesture. // Otherwise a pinch turned to 90 degrees and held there will rotate the page again and again. static int rotations = 0; if (changeFlags & QPinchGesture::RotationAngleChanged) { // Rotation angle relative to the accumulated page rotations triggered by the current pinch // We actually turn at 80 degrees rather than at 90 degrees. That's less strain on the hands. const qreal relativeAngle = pinch->rotationAngle() - rotations*90; if (relativeAngle > 80) { slotRotateClockwise(); rotations++; } if (relativeAngle < -80) { slotRotateCounterClockwise(); rotations--; } } if (pinch->state() == Qt::GestureFinished) { rotations = 0; } return true; } return false; } void PageView::paintEvent(QPaintEvent *pe) { const QPoint areaPos = contentAreaPosition(); // create the rect into contents from the clipped screen rect QRect viewportRect = viewport()->rect(); viewportRect.translate( areaPos ); QRect contentsRect = pe->rect().translated( areaPos ).intersected( viewportRect ); if ( !contentsRect.isValid() ) return; #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "paintevent" << contentsRect; #endif // create the screen painter. a pixel painted at contentsX,contentsY // appears to the top-left corner of the scrollview. QPainter screenPainter( viewport() ); // translate to simulate the scrolled content widget screenPainter.translate( -areaPos ); // selectionRect is the normalized mouse selection rect QRect selectionRect = d->mouseSelectionRect; if ( !selectionRect.isNull() ) selectionRect = selectionRect.normalized(); // selectionRectInternal without the border QRect selectionRectInternal = selectionRect; selectionRectInternal.adjust( 1, 1, -1, -1 ); // color for blending QColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ? d->mouseSelectionColor : Qt::red; // subdivide region into rects const QVector &allRects = pe->region().rects(); uint numRects = allRects.count(); // preprocess rects area to see if it worths or not using subdivision uint summedArea = 0; for ( uint i = 0; i < numRects; i++ ) { const QRect & r = allRects[i]; summedArea += r.width() * r.height(); } // very elementary check: SUMj(Region[j].area) is less than boundingRect.area bool useSubdivision = summedArea < (0.6 * contentsRect.width() * contentsRect.height()); if ( !useSubdivision ) numRects = 1; // iterate over the rects (only one loop if not using subdivision) for ( uint i = 0; i < numRects; i++ ) { if ( useSubdivision ) { // set 'contentsRect' to a part of the sub-divided region contentsRect = allRects[i].translated( areaPos ).intersected( viewportRect ); if ( !contentsRect.isValid() ) continue; } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << contentsRect; #endif // note: this check will take care of all things requiring alpha blending (not only selection) bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect ); // also alpha-blend when there is a table selection... wantCompositing |= !d->tableSelectionParts.isEmpty(); if ( wantCompositing && Okular::Settings::enableCompositing() ) { // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0}) QPixmap doubleBuffer( contentsRect.size() * devicePixelRatioF() ); doubleBuffer.setDevicePixelRatio(devicePixelRatioF()); QPainter pixmapPainter( &doubleBuffer ); pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() ); // 1) Layer 0: paint items and clear bg on unpainted rects drawDocumentOnPainter( contentsRect, &pixmapPainter ); // 2a) Layer 1a: paint (blend) transparent selection (rectangle) if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { QRect blendRect = selectionRectInternal.intersected( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents QPixmap blendedPixmap( blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); blendedPixmap.setDevicePixelRatio(devicePixelRatioF()); QPainter p( &blendedPixmap ); p.drawPixmap( 0, 0, doubleBuffer, blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(), blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); QColor blCol = selBlendColor.dark( 140 ); blCol.setAlphaF( 0.2 ); p.fillRect( blendedPixmap.rect(), blCol ); p.end(); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); } // draw border (red if the selection is too small) pixmapPainter.setPen( selBlendColor ); pixmapPainter.drawRect( selectionRect.adjusted( 0, 0, -1, -1 ) ); } // 2b) Layer 1b: paint (blend) transparent selection (table) foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && !selectionPartRectInternal.contains( contentsRect ) ) { QRect blendRect = selectionPartRectInternal.intersected( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents QPixmap blendedPixmap( blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); blendedPixmap.setDevicePixelRatio(devicePixelRatioF()); QPainter p( &blendedPixmap ); p.drawPixmap( 0, 0, doubleBuffer, blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(), blendRect.width() * devicePixelRatioF(), blendRect.height() * devicePixelRatioF() ); QColor blCol = d->mouseSelectionColor.dark( 140 ); blCol.setAlphaF( 0.2 ); p.fillRect( blendedPixmap.rect(), blCol ); p.end(); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedPixmap ); } // draw border (red if the selection is too small) pixmapPainter.setPen( d->mouseSelectionColor ); pixmapPainter.drawRect( selectionPartRect.adjusted( 0, 0, -1, -1 ) ); } } drawTableDividers( &pixmapPainter ); // 3a) Layer 1: give annotator painting control if ( d->annotator && d->annotator->routePaints( contentsRect ) ) d->annotator->routePaint( &pixmapPainter, contentsRect ); // 3b) Layer 1: give mouseAnnotation painting control d->mouseAnnotation->routePaint( &pixmapPainter, contentsRect ); // 4) Layer 2: overlays if ( Okular::Settings::debugDrawBoundaries() ) { pixmapPainter.setPen( Qt::blue ); pixmapPainter.drawRect( contentsRect ); } // finish painting and draw contents pixmapPainter.end(); screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer ); } else { // 1) Layer 0: paint items and clear bg on unpainted rects drawDocumentOnPainter( contentsRect, &screenPainter ); // 2a) Layer 1a: paint opaque selection (rectangle) if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); screenPainter.drawRect( selectionRect ); } // 2b) Layer 1b: paint opaque selection (table) foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); if ( !selectionPartRect.isNull() && selectionPartRect.intersects( contentsRect ) && !selectionPartRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().color( QPalette::Active, QPalette::Highlight ).dark(110) ); screenPainter.drawRect( selectionPartRect ); } } drawTableDividers( &screenPainter ); // 3a) Layer 1: give annotator painting control if ( d->annotator && d->annotator->routePaints( contentsRect ) ) d->annotator->routePaint( &screenPainter, contentsRect ); // 3b) Layer 1: give mouseAnnotation painting control d->mouseAnnotation->routePaint( &screenPainter, contentsRect ); // 4) Layer 2: overlays if ( Okular::Settings::debugDrawBoundaries() ) { screenPainter.setPen( Qt::red ); screenPainter.drawRect( contentsRect ); } } } } void PageView::drawTableDividers(QPainter * screenPainter) { if (!d->tableSelectionParts.isEmpty()) { screenPainter->setPen( d->mouseSelectionColor.dark() ); if (d->tableDividersGuessed) { QPen p = screenPainter->pen(); p.setStyle( Qt::DashLine ); screenPainter->setPen( p ); } foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); QRect selectionPartRectInternal = selectionPartRect; selectionPartRectInternal.adjust( 1, 1, -1, -1 ); foreach(double col, d->tableSelectionCols) { if (col >= tsp.rectInSelection.left && col <= tsp.rectInSelection.right) { col = (col - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); const int x = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; screenPainter->drawLine( x, selectionPartRectInternal.top(), x, selectionPartRectInternal.top() + selectionPartRectInternal.height() ); } } foreach(double row, d->tableSelectionRows) { if (row >= tsp.rectInSelection.top && row <= tsp.rectInSelection.bottom) { row = (row - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); const int y = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; screenPainter->drawLine( selectionPartRectInternal.left(), y, selectionPartRectInternal.left() + selectionPartRectInternal.width(), y ); } } } } } void PageView::resizeEvent( QResizeEvent *e ) { if ( d->items.isEmpty() ) { resizeContentArea( e->size() ); return; } if ( ( d->zoomMode == ZoomFitWidth || d->zoomMode == ZoomFitAuto ) && !verticalScrollBar()->isVisible() && qAbs(e->oldSize().height() - e->size().height()) < verticalScrollBar()->width() && d->verticalScrollBarVisible ) { // this saves us from infinite resizing loop because of scrollbars appearing and disappearing // see bug 160628 for more info // TODO looks are still a bit ugly because things are left uncentered // but better a bit ugly than unusable d->verticalScrollBarVisible = false; resizeContentArea( e->size() ); return; } else if ( d->zoomMode == ZoomFitAuto && !horizontalScrollBar()->isVisible() && qAbs(e->oldSize().width() - e->size().width()) < horizontalScrollBar()->height() && d->horizontalScrollBarVisible ) { // this saves us from infinite resizing loop because of scrollbars appearing and disappearing // TODO looks are still a bit ugly because things are left uncentered // but better a bit ugly than unusable d->horizontalScrollBarVisible = false; resizeContentArea( e->size() ); return; } // start a timer that will refresh the pixmap after 0.2s d->delayResizeEventTimer->start( 200 ); d->verticalScrollBarVisible = verticalScrollBar()->isVisible(); d->horizontalScrollBarVisible = horizontalScrollBar()->isVisible(); } void PageView::keyPressEvent( QKeyEvent * e ) { e->accept(); // if performing a selection or dyn zooming, disable keys handling if ( ( d->mouseSelecting && e->key() != Qt::Key_Escape ) || ( QApplication::mouseButtons () & Qt::MidButton ) ) return; // if viewport is moving, disable keys handling if ( d->viewportMoveActive ) return; // move/scroll page by using keys switch ( e->key() ) { case Qt::Key_J: case Qt::Key_K: case Qt::Key_Down: case Qt::Key_PageDown: case Qt::Key_Up: case Qt::Key_PageUp: case Qt::Key_Backspace: if ( e->key() == Qt::Key_Down || e->key() == Qt::Key_PageDown || e->key() == Qt::Key_J ) { bool singleStep = e->key() == Qt::Key_Down || e->key() == Qt::Key_J; slotScrollDown( singleStep ); } else { bool singleStep = e->key() == Qt::Key_Up || e->key() == Qt::Key_K; slotScrollUp( singleStep ); } break; case Qt::Key_Left: case Qt::Key_H: if ( horizontalScrollBar()->maximum() == 0 ) { //if we cannot scroll we go to the previous page vertically int next_page = d->document->currentPage() - viewColumns(); d->document->setViewportPage(next_page); } else horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); break; case Qt::Key_Right: case Qt::Key_L: if ( horizontalScrollBar()->maximum() == 0 ) { //if we cannot scroll we advance the page vertically int next_page = d->document->currentPage() + viewColumns(); d->document->setViewportPage(next_page); } else horizontalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); break; case Qt::Key_Escape: emit escPressed(); selectionClear( d->tableDividersGuessed ? ClearOnlyDividers : ClearAllSelection ); d->mousePressPos = QPoint(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = 0; } d->mouseAnnotation->routeKeyPressEvent( e ); break; case Qt::Key_Delete: d->mouseAnnotation->routeKeyPressEvent( e ); break; case Qt::Key_Shift: case Qt::Key_Control: if ( d->autoScrollTimer ) { if ( d->autoScrollTimer->isActive() ) d->autoScrollTimer->stop(); else slotAutoScroll(); return; } - // else fall trhough + // fallthrough default: e->ignore(); return; } // if a known key has been pressed, stop scrolling the page if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::keyReleaseEvent( QKeyEvent * e ) { e->accept(); if ( d->annotator && d->annotator->active() ) { if ( d->annotator->routeKeyEvent( e ) ) return; } if ( e->key() == Qt::Key_Escape && d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::inputMethodEvent( QInputMethodEvent * e ) { Q_UNUSED(e) } void PageView::tabletEvent( QTabletEvent * e ) { // Ignore tablet events that we don't care about if ( !( e->type() == QEvent::TabletPress || e->type() == QEvent::TabletRelease || e->type() == QEvent::TabletMove ) ) { e->ignore(); return; } // Determine pen state bool penReleased = false; if ( e->type() == QEvent::TabletPress ) { d->penDown = true; } if ( e->type() == QEvent::TabletRelease ) { d->penDown = false; penReleased = true; } // If we're editing an annotation and the tablet pen is either down or just released // then dispatch event to annotator if ( d->annotator && d->annotator->active() && ( d->penDown || penReleased ) ) { const QPoint eventPos = contentAreaPoint( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const QPoint localOriginInGlobal = mapToGlobal( QPoint(0,0) ); // routeTabletEvent will accept or ignore event as appropriate d->annotator->routeTabletEvent( e, pageItem, localOriginInGlobal ); } else { e->ignore(); } } void PageView::mouseMoveEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; // if holding mouse mid button, perform zoom if ( e->buttons() & Qt::MidButton ) { int mouseY = e->globalPos().y(); int deltaY = d->mouseMidLastY - mouseY; // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); const int absDeltaY = abs(deltaY); if ( absDeltaY > mouseContainer.height() / 2 ) { deltaY = mouseContainer.height() - absDeltaY; } const float upperZoomLimit = d->document->supportsTiles() ? 15.99 : 3.99; if ( mouseY <= mouseContainer.top() + 4 && d->zoomFactor < upperZoomLimit ) { mouseY = mouseContainer.bottom() - 5; QCursor::setPos( e->globalPos().x(), mouseY ); } // wrap mouse from bottom to top else if ( mouseY >= mouseContainer.bottom() - 4 && d->zoomFactor > 0.101 ) { mouseY = mouseContainer.top() + 5; QCursor::setPos( e->globalPos().x(), mouseY ); } // remember last position d->mouseMidLastY = mouseY; // update zoom level, perform zoom and redraw if ( deltaY ) { d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); d->blockPixmapsRequest = true; updateZoom( ZoomRefreshCurrent ); d->blockPixmapsRequest = false; viewport()->repaint(); } return; } const QPoint eventPos = contentAreaPoint( e->pos() ); // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); updateCursor( eventPos ); d->annotator->routeMouseEvent( e, pageItem ); return; } bool leftButton = (e->buttons() == Qt::LeftButton); bool rightButton = (e->buttons() == Qt::RightButton); switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse: { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( leftButton ) { d->leftClickTimer.stop(); if ( pageItem && d->mouseAnnotation->isActive() ) { // if left button pressed and annotation is focused, forward move event d->mouseAnnotation->routeMouseMoveEvent( pageItem, eventPos, leftButton ); } // drag page else if ( !d->mouseGrabPos.isNull() ) { setCursor( Qt::ClosedHandCursor ); QPoint mousePos = e->globalPos(); QPoint delta = d->mouseGrabPos - mousePos; // wrap mouse from top to bottom const QRect mouseContainer = QApplication::desktop()->screenGeometry( this ); // If the delta is huge it probably means we just wrapped in that direction const QPoint absDelta(abs(delta.x()), abs(delta.y())); if ( absDelta.y() > mouseContainer.height() / 2 ) { delta.setY(mouseContainer.height() - absDelta.y()); } if ( absDelta.x() > mouseContainer.width() / 2 ) { delta.setX(mouseContainer.width() - absDelta.x()); } if ( mousePos.y() <= mouseContainer.top() + 4 && verticalScrollBar()->value() < verticalScrollBar()->maximum() - 10 ) { mousePos.setY( mouseContainer.bottom() - 5 ); QCursor::setPos( mousePos ); } // wrap mouse from bottom to top else if ( mousePos.y() >= mouseContainer.bottom() - 4 && verticalScrollBar()->value() > 10 ) { mousePos.setY( mouseContainer.top() + 5 ); QCursor::setPos( mousePos ); } // remember last position d->mouseGrabPos = mousePos; // scroll page by position increment scrollTo( horizontalScrollBar()->value() + delta.x(), verticalScrollBar()->value() + delta.y() ); } } else if ( rightButton && !d->mousePressPos.isNull() && d->aMouseSelect ) { // if mouse moves 5 px away from the press point, switch to 'selection' int deltaX = d->mousePressPos.x() - e->globalPos().x(), deltaY = d->mousePressPos.y() - e->globalPos().y(); if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 ) { d->aPrevAction = d->aMouseNormal; d->aMouseSelect->trigger(); QPoint newPos = eventPos + QPoint( deltaX, deltaY ); selectionStart( newPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); updateSelection( eventPos ); break; } } else { /* Forward move events which are still not yet consumed by "mouse grab" or aMouseSelect */ d->mouseAnnotation->routeMouseMoveEvent( pageItem, eventPos, leftButton ); updateCursor(); } } break; case Okular::Settings::EnumMouseMode::Zoom: case Okular::Settings::EnumMouseMode::RectSelect: case Okular::Settings::EnumMouseMode::TableSelect: case Okular::Settings::EnumMouseMode::TrimSelect: // set second corner of selection if ( d->mouseSelecting ) updateSelection( eventPos ); break; case Okular::Settings::EnumMouseMode::Magnifier: if ( e->buttons() ) // if any button is pressed at all { moveMagnifier( e->pos() ); updateMagnifier( eventPos ); } break; case Okular::Settings::EnumMouseMode::TextSelect: // if mouse moves 5 px away from the press point and the document soupports text extraction, do 'textselection' if ( !d->mouseTextSelecting && !d->mousePressPos.isNull() && d->document->supportsSearching() && ( ( eventPos - d->mouseSelectPos ).manhattanLength() > 5 ) ) { d->mouseTextSelecting = true; } updateSelection( eventPos ); updateCursor(); break; } } void PageView::mousePressEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // if performing a selection or dyn zooming, disable mouse press if ( d->mouseSelecting || ( e->button() != Qt::MidButton && ( e->buttons() & Qt::MidButton) ) || d->viewportMoveActive ) return; // if the page is scrolling, stop it if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } // if pressing mid mouse button while not doing other things, begin 'continuous zoom' mode if ( e->button() == Qt::MidButton ) { d->mouseMidLastY = e->globalPos().y(); setCursor( Qt::SizeVerCursor ); return; } const QPoint eventPos = contentAreaPoint( e->pos() ); // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); d->annotator->routeMouseEvent( e, pageItem ); return; } // trigger history navigation for additional mouse buttons if ( e->button() == Qt::XButton1 ) { emit mouseBackButtonClick(); return; } if ( e->button() == Qt::XButton2 ) { emit mouseForwardButtonClick(); return; } // update press / 'start drag' mouse position d->mousePressPos = e->globalPos(); // handle mode dependant mouse press actions bool leftButton = e->button() == Qt::LeftButton, rightButton = e->button() == Qt::RightButton; // Not sure we should erase the selection when clicking with left. if ( d->mouseMode != Okular::Settings::EnumMouseMode::TextSelect ) textSelectionClear(); switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse: // drag start / click / link following { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( leftButton ) { if ( pageItem ) { d->mouseAnnotation->routeMousePressEvent( pageItem, eventPos ); } d->mouseGrabPos = d->mouseOnRect ? QPoint() : d->mousePressPos; if ( !d->mouseOnRect ) d->leftClickTimer.start( QApplication::doubleClickInterval() + 10 ); } else if ( rightButton ) { if ( pageItem ) { // find out normalized mouse coords inside current item const QRect & itemRect = pageItem->uncroppedGeometry(); double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); const QLinkedList< const Okular::ObjectRect *> orects = pageItem->page()->objectRects( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); if ( !orects.isEmpty() ) { AnnotationPopup popup( d->document, AnnotationPopup::MultiAnnotationMode, this ); foreach ( const Okular::ObjectRect * orect, orects ) { Okular::Annotation * ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); if ( ann && (ann->subType() != Okular::Annotation::AWidget) ) popup.addAnnotation( ann, pageItem->pageNumber() ); } connect( &popup, &AnnotationPopup::openAnnotationWindow, this, &PageView::openAnnotationWindow ); popup.exec( e->globalPos() ); // Since ↑ spins its own event loop we won't get the mouse release event // so reset mousePressPos here d->mousePressPos = QPoint(); } } } } break; case Okular::Settings::EnumMouseMode::Zoom: // set first corner of the zoom rect if ( leftButton ) selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ), false ); else if ( rightButton ) updateZoom( ZoomOut ); break; case Okular::Settings::EnumMouseMode::Magnifier: moveMagnifier( e->pos() ); d->magnifierView->show(); updateMagnifier( eventPos ); break; case Okular::Settings::EnumMouseMode::RectSelect: // set first corner of the selection rect case Okular::Settings::EnumMouseMode::TrimSelect: if ( leftButton ) { selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); } break; case Okular::Settings::EnumMouseMode::TableSelect: if ( leftButton ) { if (d->tableSelectionParts.isEmpty()) { selectionStart( eventPos, palette().color( QPalette::Active, QPalette::Highlight ).light( 120 ), false ); } else { QRect updatedRect; foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); // This will update the whole table rather than just the added/removed divider // (which can span more than one part). updatedRect = updatedRect.united(selectionPartRect); if (!selectionPartRect.contains(eventPos)) continue; // At this point it's clear we're either adding or removing a divider manually, so obviously the user is happy with the guess (if any). d->tableDividersGuessed = false; // There's probably a neat trick to finding which edge it's closest to, // but this way has the advantage of simplicity. const int fromLeft = abs(selectionPartRect.left() - eventPos.x()); const int fromRight = abs(selectionPartRect.left() + selectionPartRect.width() - eventPos.x()); const int fromTop = abs(selectionPartRect.top() - eventPos.y()); const int fromBottom = abs(selectionPartRect.top() + selectionPartRect.height() - eventPos.y()); const int colScore = fromToptableSelectionCols.length(); i++) { const double col = (d->tableSelectionCols[i] - tsp.rectInSelection.left) / (tsp.rectInSelection.right - tsp.rectInSelection.left); const int colX = selectionPartRect.left() + col * selectionPartRect.width() + 0.5; if (abs(colX - eventPos.x())<=3) { d->tableSelectionCols.removeAt(i); deleted=true; break; } } if (!deleted) { double col = eventPos.x() - selectionPartRect.left(); col /= selectionPartRect.width(); // at this point, it's normalised within the part col *= (tsp.rectInSelection.right - tsp.rectInSelection.left); col += tsp.rectInSelection.left; // at this point, it's normalised within the whole table d->tableSelectionCols.append(col); qSort(d->tableSelectionCols); } } else { bool deleted=false; for(int i=0; itableSelectionRows.length(); i++) { const double row = (d->tableSelectionRows[i] - tsp.rectInSelection.top) / (tsp.rectInSelection.bottom - tsp.rectInSelection.top); const int rowY = selectionPartRect.top() + row * selectionPartRect.height() + 0.5; if (abs(rowY - eventPos.y())<=3) { d->tableSelectionRows.removeAt(i); deleted=true; break; } } if (!deleted) { double row = eventPos.y() - selectionPartRect.top(); row /= selectionPartRect.height(); // at this point, it's normalised within the part row *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); row += tsp.rectInSelection.top; // at this point, it's normalised within the whole table d->tableSelectionRows.append(row); qSort(d->tableSelectionRows); } } } updatedRect.translate( -contentAreaPosition() ); viewport()->update( updatedRect ); } } break; case Okular::Settings::EnumMouseMode::TextSelect: d->mouseSelectPos = eventPos; if ( !rightButton ) { textSelectionClear(); } break; } } void PageView::mouseReleaseEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; // stop the drag scrolling d->dragScrollTimer.stop(); d->leftClickTimer.stop(); const bool leftButton = e->button() == Qt::LeftButton; const bool rightButton = e->button() == Qt::RightButton; if ( d->mouseAnnotation->isActive() && leftButton ) { // Just finished to move the annotation d->mouseAnnotation->routeMouseReleaseEvent(); } // don't perform any mouse action when no document is shown.. if ( d->items.isEmpty() ) { // ..except for right Clicks (emitted even it viewport is empty) if ( e->button() == Qt::RightButton ) emit rightClick( 0, e->globalPos() ); return; } // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; const QPoint eventPos = contentAreaPoint( e->pos() ); // handle mode indepent mid buttom zoom if ( e->button() == Qt::MidButton ) { // request pixmaps since it was disabled during drag slotRequestVisiblePixmaps(); // the cursor may now be over a link.. update it updateCursor( eventPos ); return; } // if we're editing an annotation, dispatch event to it if ( d->annotator && d->annotator->active() ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); d->annotator->routeMouseEvent( e, pageItem ); return; } switch ( d->mouseMode ) { case Okular::Settings::EnumMouseMode::Browse:{ // return the cursor to its normal state after dragging if ( cursor().shape() == Qt::ClosedHandCursor ) updateCursor( eventPos ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const QPoint pressPos = contentAreaPoint( mapFromGlobal( d->mousePressPos ) ); const PageViewItem * pageItemPressPos = pickItemOnPoint( pressPos.x(), pressPos.y() ); // if the mouse has not moved since the press, that's a -click- if ( leftButton && pageItem && pageItem == pageItemPressPos && ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) { double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); const Okular::ObjectRect * rect; rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) { // handle click over a link const Okular::Action * action = static_cast< const Okular::Action * >( rect->object() ); d->document->processAction( action ); } else if ( e->modifiers() == Qt::ShiftModifier ) { // TODO: find a better way to activate the source reference "links" // for the moment they are activated with Shift + left click // Search the nearest source reference. rect = pageItem->page()->objectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( !rect ) { static const double s_minDistance = 0.025; // FIXME?: empirical value? double distance = 0.0; rect = pageItem->page()->nearestObjectRect( Okular::ObjectRect::SourceRef, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight(), &distance ); // distance is distanceSqr, adapt it to a normalized value distance = distance / (pow( pageItem->uncroppedWidth(), 2 ) + pow( pageItem->uncroppedHeight(), 2 )); if ( rect && ( distance > s_minDistance ) ) rect = 0; } if ( rect ) { const Okular::SourceReference * ref = static_cast< const Okular::SourceReference * >( rect->object() ); d->document->processSourceReference( ref ); } else { const Okular::SourceReference * ref = d->document->dynamicSourceReference( pageItem-> pageNumber(), nX * pageItem->page()->width(), nY * pageItem->page()->height() ); if ( ref ) { d->document->processSourceReference( ref ); delete ref; } } } #if 0 else { // a link can move us to another page or even to another document, there's no point in trying to // process the click on the image once we have processes the click on the link rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->width(), pageItem->height() ); if ( rect ) { // handle click over a image } /* Enrico and me have decided this is not worth the trouble it generates else { // if not on a rect, the click selects the page // if ( pageItem->pageNumber() != (int)d->document->currentPage() ) d->document->setViewportPage( pageItem->pageNumber(), this ); }*/ } #endif } else if ( rightButton && !d->mouseAnnotation->isModified() ) { if ( pageItem && pageItem == pageItemPressPos && ( (d->mousePressPos - e->globalPos()).manhattanLength() < QApplication::startDragDistance() ) ) { double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); const Okular::ObjectRect * rect; rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) { // handle right click over a link const Okular::Action * link = static_cast< const Okular::Action * >( rect->object() ); // creating the menu and its actions QMenu menu( this ); QAction * actProcessLink = menu.addAction( i18n( "Follow This Link" ) ); QAction * actStopSound = 0; if ( link->actionType() == Okular::Action::Sound ) { actProcessLink->setText( i18n( "Play this Sound" ) ); if ( Okular::AudioPlayer::instance()->state() == Okular::AudioPlayer::PlayingState ) { actStopSound = menu.addAction( i18n( "Stop Sound" ) ); } } QAction * actCopyLinkLocation = 0; if ( dynamic_cast< const Okular::BrowseAction * >( link ) ) actCopyLinkLocation = menu.addAction( QIcon::fromTheme( QStringLiteral("edit-copy") ), i18n( "Copy Link Address" ) ); QAction * res = menu.exec( e->globalPos() ); if ( res ) { if ( res == actProcessLink ) { d->document->processAction( link ); } else if ( res == actCopyLinkLocation ) { const Okular::BrowseAction * browseLink = static_cast< const Okular::BrowseAction * >( link ); QClipboard *cb = QApplication::clipboard(); cb->setText( browseLink->url().toDisplayString(), QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( browseLink->url().toDisplayString(), QClipboard::Selection ); } else if ( res == actStopSound ) { Okular::AudioPlayer::instance()->stopPlaybacks(); } } } else { // a link can move us to another page or even to another document, there's no point in trying to // process the click on the image once we have processes the click on the link rect = pageItem->page()->objectRect( Okular::ObjectRect::Image, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) { // handle right click over a image } else { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem->page(), e->globalPos() ); } } } else { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); } } }break; case Okular::Settings::EnumMouseMode::Zoom: // if a selection rect has been defined, zoom into it if ( leftButton && d->mouseSelecting ) { QRect selRect = d->mouseSelectionRect.normalized(); if ( selRect.width() <= 8 && selRect.height() <= 8 ) { selectionClear(); break; } // find out new zoom ratio and normalized view center (relative to the contentsRect) double zoom = qMin( (double)viewport()->width() / (double)selRect.width(), (double)viewport()->height() / (double)selRect.height() ); double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentAreaWidth()); double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentAreaHeight()); const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; if ( d->zoomFactor <= upperZoomLimit || zoom <= 1.0 ) { d->zoomFactor *= zoom; viewport()->setUpdatesEnabled( false ); updateZoom( ZoomRefreshCurrent ); viewport()->setUpdatesEnabled( true ); } // recenter view and update the viewport center( (int)(nX * contentAreaWidth()), (int)(nY * contentAreaHeight()) ); viewport()->update(); // hide message box and delete overlay window selectionClear(); } break; case Okular::Settings::EnumMouseMode::Magnifier: d->magnifierView->hide(); break; case Okular::Settings::EnumMouseMode::TrimSelect: { // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { break; } PageViewItem * pageItem = pickItemOnPoint(eventPos.x(), eventPos.y()); // ensure end point rests within a page, or ignore if (!pageItem) { break; } QRect selectionRect = d->mouseSelectionRect.normalized(); double nLeft = pageItem->absToPageX(selectionRect.left()); double nRight = pageItem->absToPageX(selectionRect.right()); double nTop = pageItem->absToPageY(selectionRect.top()); double nBottom = pageItem->absToPageY(selectionRect.bottom()); if ( nLeft < 0 ) nLeft = 0; if ( nTop < 0 ) nTop = 0; if ( nRight > 1 ) nRight = 1; if ( nBottom > 1 ) nBottom = 1; d->trimBoundingBox = Okular::NormalizedRect(nLeft, nTop, nRight, nBottom); // Trim Selection successfully done, hide prompt d->messageWindow->hide(); // clear widget selection and invalidate rect selectionClear(); // When Trim selection bbox interaction is over, we should switch to another mousemode. if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = 0; } else { d->aMouseNormal->trigger(); } // with d->trimBoundingBox defined, redraw for trim to take visual effect if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } break; } case Okular::Settings::EnumMouseMode::RectSelect: { // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); break; } // if a selection is defined, display a popup if ( (!leftButton && !d->aPrevAction) || (!rightButton && d->aPrevAction) || !d->mouseSelecting ) break; QRect selectionRect = d->mouseSelectionRect.normalized(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = 0; } break; } // if we support text generation QString selectedText; if (d->document->supportsSearching()) { // grab text in selection by extracting it from all intersected pages const Okular::Page * okularPage=0; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( !item->isVisible() ) continue; const QRect & itemRect = item->croppedGeometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one okularPage= item->page(); qCDebug(OkularUiDebug) << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); // grab text in the rect that intersects itemRect QRect relativeRect = selectionRect.intersected( itemRect ); relativeRect.translate( -item->uncroppedGeometry().topLeft() ); Okular::RegularAreaRect rects; rects.append( Okular::NormalizedRect( relativeRect, item->uncroppedWidth(), item->uncroppedHeight() ) ); selectedText += okularPage->text( &rects ); } } } // popup that ask to copy:text and copy/save:image QMenu menu( this ); QAction *textToClipboard = 0; #ifdef HAVE_SPEECH QAction *speakText = 0; #endif QAction *imageToClipboard = 0; QAction *imageToFile = 0; if ( d->document->supportsSearching() && !selectedText.isEmpty() ) { menu.addAction( new OKMenuTitle( &menu, i18np( "Text (1 character)", "Text (%1 characters)", selectedText.length() ) ) ); textToClipboard = menu.addAction( QIcon::fromTheme(QStringLiteral("edit-copy")), i18n( "Copy to Clipboard" ) ); bool copyAllowed = d->document->isAllowed( Okular::AllowCopy ); if ( !copyAllowed ) { textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } #ifdef HAVE_SPEECH if ( Okular::Settings::useTTS() ) speakText = menu.addAction( QIcon::fromTheme(QStringLiteral("text-speak")), i18n( "Speak Text" ) ); #endif if ( copyAllowed ) { addWebShortcutsMenu( &menu, selectedText ); } } menu.addAction( new OKMenuTitle( &menu, i18n( "Image (%1 by %2 pixels)", selectionRect.width(), selectionRect.height() ) ) ); imageToClipboard = menu.addAction( QIcon::fromTheme(QStringLiteral("image-x-generic")), i18n( "Copy to Clipboard" ) ); imageToFile = menu.addAction( QIcon::fromTheme(QStringLiteral("document-save")), i18n( "Save to File..." ) ); QAction *choice = menu.exec( e->globalPos() ); // check if the user really selected an action if ( choice ) { // IMAGE operation chosen if ( choice == imageToClipboard || choice == imageToFile ) { // renders page into a pixmap QPixmap copyPix( selectionRect.width(), selectionRect.height() ); QPainter copyPainter( ©Pix ); copyPainter.translate( -selectionRect.left(), -selectionRect.top() ); drawDocumentOnPainter( selectionRect, ©Painter ); copyPainter.end(); if ( choice == imageToClipboard ) { // [2] copy pixmap to clipboard QClipboard *cb = QApplication::clipboard(); cb->setPixmap( copyPix, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setPixmap( copyPix, QClipboard::Selection ); d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard.", copyPix.width(), copyPix.height() ) ); } else if ( choice == imageToFile ) { // [3] save pixmap to file QString fileName = QFileDialog::getSaveFileName(this, i18n("Save file"), QString(), i18n("Images (*.png *.jpeg)")); if ( fileName.isEmpty() ) d->messageWindow->display( i18n( "File not saved." ), QString(), PageViewMessage::Warning ); else { QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl( QUrl::fromLocalFile(fileName) ); QString type; if ( !mime.isDefault() ) type = QStringLiteral("PNG"); else type = mime.name().section( QLatin1Char('/'), -1 ).toUpper(); copyPix.save( fileName, qPrintable( type ) ); d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file.", copyPix.width(), copyPix.height(), type ) ); } } } // TEXT operation chosen else { if ( choice == textToClipboard ) { // [1] copy text to clipboard QClipboard *cb = QApplication::clipboard(); cb->setText( selectedText, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( selectedText, QClipboard::Selection ); } #ifdef HAVE_SPEECH else if ( choice == speakText ) { // [2] speech selection using TTS d->tts()->say( selectedText ); } #endif } } // clear widget selection and invalidate rect selectionClear(); // restore previous action if came from it using right button if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = 0; } }break; case Okular::Settings::EnumMouseMode::TableSelect: { // if mouse is released and selection is null this is a rightClick if ( rightButton && !d->mouseSelecting ) { PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); break; } QRect selectionRect = d->mouseSelectionRect.normalized(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 && d->tableSelectionParts.isEmpty() ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = 0; } break; } if (d->mouseSelecting) { // break up the selection into page-relative pieces d->tableSelectionParts.clear(); const Okular::Page * okularPage=0; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( !item->isVisible() ) continue; const QRect & itemRect = item->croppedGeometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one okularPage= item->page(); qCDebug(OkularUiDebug) << "checking if page" << item->pageNumber() << "has text:" << okularPage->hasTextPage(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); // grab text in the rect that intersects itemRect QRect rectInItem = selectionRect.intersected( itemRect ); rectInItem.translate( -item->uncroppedGeometry().topLeft() ); QRect rectInSelection = selectionRect.intersected( itemRect ); rectInSelection.translate( -selectionRect.topLeft() ); d->tableSelectionParts.append( TableSelectionPart( item, Okular::NormalizedRect( rectInItem, item->uncroppedWidth(), item->uncroppedHeight() ), Okular::NormalizedRect( rectInSelection, selectionRect.width(), selectionRect.height() ) ) ); } } QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( 0, 0, 1, 1 ); updatedRect.translate( -contentAreaPosition() ); d->mouseSelecting = false; d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); d->tableSelectionCols.clear(); d->tableSelectionRows.clear(); guessTableDividers(); viewport()->update( updatedRect ); } if ( !d->document->isAllowed( Okular::AllowCopy ) ) { d->messageWindow->display( i18n("Copy forbidden by DRM"), QString(), PageViewMessage::Info, -1 ); break; } QString selText; QString selHtml; QList xs = d->tableSelectionCols; QList ys = d->tableSelectionRows; xs.prepend(0.0); xs.append(1.0); ys.prepend(0.0); ys.append(1.0); selHtml = QString::fromLatin1("" "" ""); for (int r=0; r+1"); for (int c=0; c+1tableSelectionParts) { // first, crop the cell to this part if (!tsp.rectInSelection.intersects(cell)) continue; Okular::NormalizedRect cellPart = tsp.rectInSelection & cell; // intersection // second, convert it from table coordinates to part coordinates cellPart.left -= tsp.rectInSelection.left; cellPart.left /= (tsp.rectInSelection.right - tsp.rectInSelection.left); cellPart.right -= tsp.rectInSelection.left; cellPart.right /= (tsp.rectInSelection.right - tsp.rectInSelection.left); cellPart.top -= tsp.rectInSelection.top; cellPart.top /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); cellPart.bottom -= tsp.rectInSelection.top; cellPart.bottom /= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); // third, convert from part coordinates to item coordinates cellPart.left *= (tsp.rectInItem.right - tsp.rectInItem.left); cellPart.left += tsp.rectInItem.left; cellPart.right *= (tsp.rectInItem.right - tsp.rectInItem.left); cellPart.right += tsp.rectInItem.left; cellPart.top *= (tsp.rectInItem.bottom - tsp.rectInItem.top); cellPart.top += tsp.rectInItem.top; cellPart.bottom *= (tsp.rectInItem.bottom - tsp.rectInItem.top); cellPart.bottom += tsp.rectInItem.top; // now get the text Okular::RegularAreaRect rects; rects.append( cellPart ); txt += tsp.item->page()->text( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); } QString html = txt; selText += txt.replace(QLatin1Char('\n'), QLatin1Char(' ')); html.replace(QLatin1Char('&'), QLatin1String("&")).replace(QLatin1Char('<'), QLatin1String("<")).replace(QLatin1Char('>'), QLatin1String(">")); // Remove newlines, do not turn them into
, because // Excel interprets
within cell as new cell... html.replace(QLatin1Char('\n'), QLatin1String(" ")); selHtml += QStringLiteral("
"); } selText += QLatin1Char('\n'); selHtml += QLatin1String("\n"); } selHtml += QLatin1String("
") + html + QStringLiteral("
\n"); QClipboard *cb = QApplication::clipboard(); QMimeData *md = new QMimeData(); md->setText(selText); md->setHtml(selHtml); cb->setMimeData( md, QClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setMimeData( md, QClipboard::Selection ); }break; case Okular::Settings::EnumMouseMode::TextSelect: if ( d->mouseTextSelecting ) { d->mouseTextSelecting = false; // textSelectionClear(); if ( d->document->isAllowed( Okular::AllowCopy ) ) { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); if ( cb->supportsSelection() ) cb->setText( text, QClipboard::Selection ); } } } else if ( !d->mousePressPos.isNull() && rightButton ) { PageViewItem* item = pickItemOnPoint(eventPos.x(),eventPos.y()); const Okular::Page *page; //if there is text selected in the page if (item && (page = item->page())->textSelection()) { QMenu menu( this ); QAction *textToClipboard = menu.addAction( QIcon::fromTheme( QStringLiteral("edit-copy") ), i18n( "Copy Text" ) ); QAction *httpLink = 0; #ifdef HAVE_SPEECH QAction *speakText = 0; if ( Okular::Settings::useTTS() ) speakText = menu.addAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Text" ) ); #endif if ( !d->document->isAllowed( Okular::AllowCopy ) ) { textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } else { addWebShortcutsMenu( &menu, d->selectedText() ); } const QString url = UrlUtils::getUrl( d->selectedText() ); if ( !url.isEmpty() ) { const QString squeezedText = KStringHandler::rsqueeze( url, 30 ); httpLink = menu.addAction( i18n( "Go to '%1'", squeezedText ) ); } QAction *choice = menu.exec( e->globalPos() ); // check if the user really selected an action if ( choice ) { if ( choice == textToClipboard ) copyTextSelection(); #ifdef HAVE_SPEECH else if ( choice == speakText ) { const QString text = d->selectedText(); d->tts()->say( text ); } #endif else if ( choice == httpLink ) new KRun( QUrl( url ), this ); } } } break; } // reset mouse press / 'drag start' position d->mousePressPos = QPoint(); } void PageView::guessTableDividers() { QList< QPair > colTicks, rowTicks, colSelectionTicks, rowSelectionTicks; foreach ( const TableSelectionPart& tsp, d->tableSelectionParts ) { // add ticks for the edges of this area... colSelectionTicks.append( qMakePair( tsp.rectInSelection.left, +1 ) ); colSelectionTicks.append( qMakePair( tsp.rectInSelection.right, -1 ) ); rowSelectionTicks.append( qMakePair( tsp.rectInSelection.top, +1 ) ); rowSelectionTicks.append( qMakePair( tsp.rectInSelection.bottom, -1 ) ); // get the words in this part Okular::RegularAreaRect rects; rects.append( tsp.rectInItem ); const Okular::TextEntity::List words = tsp.item->page()->words( &rects, Okular::TextPage::CentralPixelTextAreaInclusionBehaviour ); foreach (Okular::TextEntity *te, words) { if (te->text().isEmpty()) { delete te; continue; } Okular::NormalizedRect wordArea = *te->area(); // convert it from item coordinates to part coordinates wordArea.left -= tsp.rectInItem.left; wordArea.left /= (tsp.rectInItem.right - tsp.rectInItem.left); wordArea.right -= tsp.rectInItem.left; wordArea.right /= (tsp.rectInItem.right - tsp.rectInItem.left); wordArea.top -= tsp.rectInItem.top; wordArea.top /= (tsp.rectInItem.bottom - tsp.rectInItem.top); wordArea.bottom -= tsp.rectInItem.top; wordArea.bottom /= (tsp.rectInItem.bottom - tsp.rectInItem.top); // convert from part coordinates to table coordinates wordArea.left *= (tsp.rectInSelection.right - tsp.rectInSelection.left); wordArea.left += tsp.rectInSelection.left; wordArea.right *= (tsp.rectInSelection.right - tsp.rectInSelection.left); wordArea.right += tsp.rectInSelection.left; wordArea.top *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); wordArea.top += tsp.rectInSelection.top; wordArea.bottom *= (tsp.rectInSelection.bottom - tsp.rectInSelection.top); wordArea.bottom += tsp.rectInSelection.top; // add to the ticks arrays... colTicks.append( qMakePair( wordArea.left, +1) ); colTicks.append( qMakePair( wordArea.right, -1) ); rowTicks.append( qMakePair( wordArea.top, +1) ); rowTicks.append( qMakePair( wordArea.bottom, -1) ); delete te; } } int tally = 0; qSort( colSelectionTicks ); qSort( rowSelectionTicks ); for (int i = 0; i < colSelectionTicks.length(); ++i) { tally += colSelectionTicks[i].second; if ( tally == 0 && i + 1 < colSelectionTicks.length() && colSelectionTicks[i+1].first != colSelectionTicks[i].first) { colTicks.append( qMakePair( colSelectionTicks[i].first, +1 ) ); colTicks.append( qMakePair( colSelectionTicks[i+1].first, -1 ) ); } } Q_ASSERT( tally == 0 ); for (int i = 0; i < rowSelectionTicks.length(); ++i) { tally += rowSelectionTicks[i].second; if ( tally == 0 && i + 1 < rowSelectionTicks.length() && rowSelectionTicks[i+1].first != rowSelectionTicks[i].first) { rowTicks.append( qMakePair( rowSelectionTicks[i].first, +1 ) ); rowTicks.append( qMakePair( rowSelectionTicks[i+1].first, -1 ) ); } } Q_ASSERT( tally == 0 ); qSort( colTicks ); qSort( rowTicks ); for (int i = 0; i < colTicks.length(); ++i) { tally += colTicks[i].second; if ( tally == 0 && i + 1 < colTicks.length() && colTicks[i+1].first != colTicks[i].first) { d->tableSelectionCols.append( (colTicks[i].first+colTicks[i+1].first) / 2 ); d->tableDividersGuessed = true; } } Q_ASSERT( tally == 0 ); for (int i = 0; i < rowTicks.length(); ++i) { tally += rowTicks[i].second; if ( tally == 0 && i + 1 < rowTicks.length() && rowTicks[i+1].first != rowTicks[i].first) { d->tableSelectionRows.append( (rowTicks[i].first+rowTicks[i+1].first) / 2 ); d->tableDividersGuessed = true; } } Q_ASSERT( tally == 0 ); } void PageView::mouseDoubleClickEvent( QMouseEvent * e ) { d->controlWheelAccumulatedDelta = 0; if ( e->button() == Qt::LeftButton ) { const QPoint eventPos = contentAreaPoint( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); if ( pageItem ) { // find out normalized mouse coords inside current item double nX = pageItem->absToPageX(eventPos.x()); double nY = pageItem->absToPageY(eventPos.y()); if ( d->mouseMode == Okular::Settings::EnumMouseMode::TextSelect ) { textSelectionClear(); Okular::RegularAreaRect *wordRect = pageItem->page()->wordAt( Okular::NormalizedPoint( nX, nY ) ); if ( wordRect ) { // TODO words with hyphens across pages d->document->setPageTextSelection( pageItem->pageNumber(), wordRect, palette().color( QPalette::Active, QPalette::Highlight ) ); d->pagesWithTextSelection << pageItem->pageNumber(); if ( d->document->isAllowed( Okular::AllowCopy ) ) { const QString text = d->selectedText(); if ( !text.isEmpty() ) { QClipboard *cb = QApplication::clipboard(); if ( cb->supportsSelection() ) cb->setText( text, QClipboard::Selection ); } } return; } } const QRect & itemRect = pageItem->uncroppedGeometry(); Okular::Annotation * ann = 0; const Okular::ObjectRect * orect = pageItem->page()->objectRect( Okular::ObjectRect::OAnnotation, nX, nY, itemRect.width(), itemRect.height() ); if ( orect ) ann = ( (Okular::AnnotationObjectRect *)orect )->annotation(); if ( ann && ann->subType() != Okular::Annotation::AWidget ) { openAnnotationWindow( ann, pageItem->pageNumber() ); } } } } void PageView::wheelEvent( QWheelEvent *e ) { // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; if ( !d->document->isOpened() ) { QAbstractScrollArea::wheelEvent( e ); return; } int delta = e->delta(), vScroll = verticalScrollBar()->value(); e->accept(); if ( (e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) { d->controlWheelAccumulatedDelta += delta; if ( d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep ) { slotZoomOut(); d->controlWheelAccumulatedDelta = 0; } else if ( d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep ) { slotZoomIn(); d->controlWheelAccumulatedDelta = 0; } } else { d->controlWheelAccumulatedDelta = 0; if ( delta <= -QWheelEvent::DefaultDeltasPerStep && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->maximum() ) { // go to next page if ( (int)d->document->currentPage() < d->items.count() - 1 ) { // more optimized than document->setNextPage and then move view to top Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += viewColumns(); if ( newViewport.pageNumber >= (int)d->items.count() ) newViewport.pageNumber = d->items.count() - 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } else if ( delta >= QWheelEvent::DefaultDeltasPerStep && !Okular::Settings::viewContinuous() && vScroll == verticalScrollBar()->minimum() ) { // go to prev page if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= viewColumns(); if ( newViewport.pageNumber < 0 ) newViewport.pageNumber = 0; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } else QAbstractScrollArea::wheelEvent( e ); } updateCursor(); } bool PageView::viewportEvent( QEvent * e ) { if ( e->type() == QEvent::ToolTip && d->mouseMode == Okular::Settings::EnumMouseMode::Browse ) { QHelpEvent * he = static_cast< QHelpEvent* >( e ); if ( d->mouseAnnotation->isMouseOver() ) { d->mouseAnnotation->routeTooltipEvent( he ); } else { const QPoint eventPos = contentAreaPoint( he->pos() ); PageViewItem * pageItem = pickItemOnPoint( eventPos.x(), eventPos.y() ); const Okular::ObjectRect * rect = 0; const Okular::Action * link = 0; if ( pageItem ) { double nX = pageItem->absToPageX( eventPos.x() ); double nY = pageItem->absToPageY( eventPos.y() ); rect = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( rect ) link = static_cast< const Okular::Action * >( rect->object() ); } if ( link ) { QRect r = rect->boundingRect( pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); r.translate( pageItem->uncroppedGeometry().topLeft() ); r.translate( -contentAreaPosition() ); QString tip = link->actionTip(); if ( !tip.isEmpty() ) QToolTip::showText( he->globalPos(), tip, viewport(), r ); } } e->accept(); return true; } else // do not stop the event return QAbstractScrollArea::viewportEvent( e ); } void PageView::scrollContentsBy( int dx, int dy ) { const QRect r = viewport()->rect(); viewport()->scroll( dx, dy, r ); // HACK manually repaint the damaged regions, as it seems some updates are missed // thus leaving artifacts around QRegion rgn( r ); rgn -= rgn & r.translated( dx, dy ); foreach ( const QRect &rect, rgn.rects() ) viewport()->repaint( rect ); } //END widget events QList< Okular::RegularAreaRect * > PageView::textSelections( const QPoint& start, const QPoint& end, int& firstpage ) { firstpage = -1; QList< Okular::RegularAreaRect * > ret; QSet< int > affectedItemsSet; QRect selectionRect = QRect( start, end ).normalized(); foreach( PageViewItem * item, d->items ) { if ( item->isVisible() && selectionRect.intersects( item->croppedGeometry() ) ) affectedItemsSet.insert( item->pageNumber() ); } #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << ">>>> item selected by mouse:" << affectedItemsSet.count(); #endif if ( !affectedItemsSet.isEmpty() ) { // is the mouse drag line the ne-sw diagonal of the selection rect? bool direction_ne_sw = start == selectionRect.topRight() || start == selectionRect.bottomLeft(); int tmpmin = d->document->pages(); int tmpmax = 0; foreach( int p, affectedItemsSet ) { if ( p < tmpmin ) tmpmin = p; if ( p > tmpmax ) tmpmax = p; } PageViewItem * a = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.right() : selectionRect.left() ), (int)selectionRect.top() ); int min = a && ( a->pageNumber() != tmpmax ) ? a->pageNumber() : tmpmin; PageViewItem * b = pickItemOnPoint( (int)( direction_ne_sw ? selectionRect.left() : selectionRect.right() ), (int)selectionRect.bottom() ); int max = b && ( b->pageNumber() != tmpmin ) ? b->pageNumber() : tmpmax; QList< int > affectedItemsIds; for ( int i = min; i <= max; ++i ) affectedItemsIds.append( i ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << ">>>> pages:" << affectedItemsIds; #endif firstpage = affectedItemsIds.first(); if ( affectedItemsIds.count() == 1 ) { PageViewItem * item = d->items[ affectedItemsIds.first() ]; selectionRect.translate( -item->uncroppedGeometry().topLeft() ); ret.append( textSelectionForItem( item, direction_ne_sw ? selectionRect.topRight() : selectionRect.topLeft(), direction_ne_sw ? selectionRect.bottomLeft() : selectionRect.bottomRight() ) ); } else if ( affectedItemsIds.count() > 1 ) { // first item PageViewItem * first = d->items[ affectedItemsIds.first() ]; QRect geom = first->croppedGeometry().intersected( selectionRect ).translated( -first->uncroppedGeometry().topLeft() ); ret.append( textSelectionForItem( first, selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.topRight() : geom.topLeft() ) : ( direction_ne_sw ? geom.bottomRight() : geom.bottomLeft() ), QPoint() ) ); // last item PageViewItem * last = d->items[ affectedItemsIds.last() ]; geom = last->croppedGeometry().intersected( selectionRect ).translated( -last->uncroppedGeometry().topLeft() ); // the last item needs to appended at last... Okular::RegularAreaRect * lastArea = textSelectionForItem( last, QPoint(), selectionRect.bottom() > geom.height() ? ( direction_ne_sw ? geom.bottomLeft() : geom.bottomRight() ) : ( direction_ne_sw ? geom.topLeft() : geom.topRight() ) ); affectedItemsIds.removeFirst(); affectedItemsIds.removeLast(); // item between the two above foreach( int page, affectedItemsIds ) { ret.append( textSelectionForItem( d->items[ page ] ) ); } ret.append( lastArea ); } } return ret; } void PageView::drawDocumentOnPainter( const QRect & contentsRect, QPainter * p ) { QColor backColor = viewport()->palette().color( QPalette::Dark ); // when checking if an Item is contained in contentsRect, instead of // growing PageViewItems rects (for keeping outline into account), we // grow the contentsRect QRect checkRect = contentsRect; checkRect.adjust( -3, -3, 1, 1 ); // create a region from which we'll subtract painted rects QRegion remainingArea( contentsRect ); // iterate over all items painting the ones intersecting contentsRect QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { // check if a piece of the page intersects the contents rect if ( !(*iIt)->isVisible() || !(*iIt)->croppedGeometry().intersects( checkRect ) ) continue; // get item and item's outline geometries PageViewItem * item = *iIt; QRect itemGeometry = item->croppedGeometry(), outlineGeometry = itemGeometry; outlineGeometry.adjust( -1, -1, 3, 3 ); // move the painter to the top-left corner of the real page p->save(); p->translate( itemGeometry.left(), itemGeometry.top() ); // draw the page outline (black border and 2px bottom-right shadow) if ( !itemGeometry.contains( contentsRect ) ) { int itemWidth = itemGeometry.width(), itemHeight = itemGeometry.height(); // draw simple outline p->setPen( Qt::black ); p->drawRect( -1, -1, itemWidth + 1, itemHeight + 1 ); // draw bottom/right gradient static const int levels = 2; int r = backColor.red() / (levels + 2) + 6, g = backColor.green() / (levels + 2) + 6, b = backColor.blue() / (levels + 2) + 6; for ( int i = 0; i < levels; i++ ) { p->setPen( QColor( r * (i+2), g * (i+2), b * (i+2) ) ); p->drawLine( i, i + itemHeight + 1, i + itemWidth + 1, i + itemHeight + 1 ); p->drawLine( i + itemWidth + 1, i, i + itemWidth + 1, i + itemHeight ); p->setPen( backColor ); p->drawLine( -1, i + itemHeight + 1, i - 1, i + itemHeight + 1 ); p->drawLine( i + itemWidth + 1, -1, i + itemWidth + 1, i - 1 ); } } // draw the page using the PagePainter with all flags active if ( contentsRect.intersects( itemGeometry ) ) { Okular::NormalizedPoint *viewPortPoint = 0; Okular::NormalizedPoint point( d->lastSourceLocationViewportNormalizedX, d->lastSourceLocationViewportNormalizedY ); if( Okular::Settings::showSourceLocationsGraphically() && item->pageNumber() == d->lastSourceLocationViewportPageNumber ) { viewPortPoint = &point; } QRect pixmapRect = contentsRect.intersected( itemGeometry ); pixmapRect.translate( -item->croppedGeometry().topLeft() ); PagePainter::paintCroppedPageOnPainter( p, item->page(), this, pageflags, item->uncroppedWidth(), item->uncroppedHeight(), pixmapRect, item->crop(), viewPortPoint ); } // remove painted area from 'remainingArea' and restore painter remainingArea -= outlineGeometry.intersected( contentsRect ); p->restore(); } // fill with background color the unpainted area const QVector &backRects = remainingArea.rects(); int backRectsNumber = backRects.count(); for ( int jr = 0; jr < backRectsNumber; jr++ ) p->fillRect( backRects[ jr ], backColor ); } void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight ) { const Okular::Page * okularPage = item->page(); double width = okularPage->width(), height = okularPage->height(), zoom = d->zoomFactor; Okular::NormalizedRect crop( 0., 0., 1., 1. ); // Handle cropping, due to either "Trim Margin" or "Trim to Selection" cases if (( Okular::Settings::trimMargins() && okularPage->isBoundingBoxKnown() && !okularPage->boundingBox().isNull() ) || ( d->aTrimToSelection && d->aTrimToSelection->isChecked() && !d->trimBoundingBox.isNull())) { crop = Okular::Settings::trimMargins() ? okularPage->boundingBox() : d->trimBoundingBox; // Rotate the bounding box for ( int i = okularPage->rotation(); i > 0; --i ) { Okular::NormalizedRect rot = crop; crop.left = 1 - rot.bottom; crop.top = rot.left; crop.right = 1 - rot.top; crop.bottom = rot.right; } // Expand the crop slightly beyond the bounding box (for Trim Margins only) if (Okular::Settings::trimMargins()) { static const double cropExpandRatio = 0.04; const double cropExpand = cropExpandRatio * ( (crop.right-crop.left) + (crop.bottom-crop.top) ) / 2; crop = Okular::NormalizedRect( crop.left - cropExpand, crop.top - cropExpand, crop.right + cropExpand, crop.bottom + cropExpand ) & Okular::NormalizedRect( 0, 0, 1, 1 ); } // We currently generate a larger image and then crop it, so if the // crop rect is very small the generated image is huge. Hence, we shouldn't // let the crop rect become too small. static double minCropRatio; if (Okular::Settings::trimMargins()) { // Make sure we crop by at most 50% in either dimension: minCropRatio = 0.5; } else { // Looser Constraint for "Trim Selection" minCropRatio = 0.20; } if ( ( crop.right - crop.left ) < minCropRatio ) { const double newLeft = ( crop.left + crop.right ) / 2 - minCropRatio/2; crop.left = qMax( 0.0, qMin( 1.0 - minCropRatio, newLeft ) ); crop.right = crop.left + minCropRatio; } if ( ( crop.bottom - crop.top ) < minCropRatio ) { const double newTop = ( crop.top + crop.bottom ) / 2 - minCropRatio/2; crop.top = qMax( 0.0, qMin( 1.0 - minCropRatio, newTop ) ); crop.bottom = crop.top + minCropRatio; } width *= ( crop.right - crop.left ); height *= ( crop.bottom - crop.top ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug) << "Cropped page" << okularPage->number() << "to" << crop << "width" << width << "height" << height << "by bbox" << okularPage->boundingBox(); #endif } if ( d->zoomMode == ZoomFixed ) { width *= zoom; height *= zoom; item->setWHZC( (int)width, (int)height, d->zoomFactor, crop ); } else if ( d->zoomMode == ZoomFitWidth ) { height = ( height / width ) * colWidth; zoom = (double)colWidth / width; item->setWHZC( colWidth, (int)height, zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } else if ( d->zoomMode == ZoomFitPage ) { const double scaleW = (double)colWidth / (double)width; const double scaleH = (double)rowHeight / (double)height; zoom = qMin( scaleW, scaleH ); item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } else if ( d->zoomMode == ZoomFitAuto ) { const double aspectRatioRelation = 1.25; // relation between aspect ratios for "auto fit" const double uiAspect = (double)rowHeight / (double)colWidth; const double pageAspect = (double)height / (double)width; const double rel = uiAspect / pageAspect; const bool isContinuous = Okular::Settings::viewContinuous(); if ( !isContinuous && rel > aspectRatioRelation ) { // UI space is relatively much higher than the page zoom = (double)rowHeight / (double)height; } else if ( rel < 1.0 / aspectRatioRelation ) { // UI space is relatively much wider than the page in relation zoom = (double)colWidth / (double)width; } else { // aspect ratios of page and UI space are very similar const double scaleW = (double)colWidth / (double)width; const double scaleH = (double)rowHeight / (double)height; zoom = qMin( scaleW, scaleH ); } item->setWHZC( (int)(zoom * width), (int)(zoom * height), zoom, crop ); if ((uint)item->pageNumber() == d->document->currentPage()) d->zoomFactor = zoom; } #ifndef NDEBUG else qCDebug(OkularUiDebug) << "calling updateItemSize with unrecognized d->zoomMode!"; #endif } PageViewItem * PageView::pickItemOnPoint( int x, int y ) { PageViewItem * item = 0; QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; const QRect & r = i->croppedGeometry(); if ( x < r.right() && x > r.left() && y < r.bottom() ) { if ( y > r.top() ) item = i; break; } } return item; } void PageView::textSelectionClear() { // something to clear if ( !d->pagesWithTextSelection.isEmpty() ) { QSet< int >::ConstIterator it = d->pagesWithTextSelection.constBegin(), itEnd = d->pagesWithTextSelection.constEnd(); for ( ; it != itEnd; ++it ) d->document->setPageTextSelection( *it, 0, QColor() ); d->pagesWithTextSelection.clear(); } } void PageView::selectionStart( const QPoint & pos, const QColor & color, bool /*aboveAll*/ ) { selectionClear(); d->mouseSelecting = true; d->mouseSelectionRect.setRect( pos.x(), pos.y(), 1, 1 ); d->mouseSelectionColor = color; // ensures page doesn't scroll if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::scrollPosIntoView( const QPoint & pos ) { if (pos.x() < horizontalScrollBar()->value()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value()); else if (horizontalScrollBar()->value() + viewport()->width() < pos.x()) d->dragScrollVector.setX(pos.x() - horizontalScrollBar()->value() - viewport()->width()); else d->dragScrollVector.setX(0); if (pos.y() < verticalScrollBar()->value()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value()); else if (verticalScrollBar()->value() + viewport()->height() < pos.y()) d->dragScrollVector.setY(pos.y() - verticalScrollBar()->value() - viewport()->height()); else d->dragScrollVector.setY(0); if (d->dragScrollVector != QPoint(0, 0)) { if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100); } else d->dragScrollTimer.stop(); } void PageView::updateSelection( const QPoint & pos ) { if ( d->mouseSelecting ) { scrollPosIntoView( pos ); // update the selection rect QRect updateRect = d->mouseSelectionRect; d->mouseSelectionRect.setBottomLeft( pos ); updateRect |= d->mouseSelectionRect; updateRect.translate( -contentAreaPosition() ); viewport()->update( updateRect.adjusted( -1, -2, 2, 1 ) ); } else if ( d->mouseTextSelecting) { scrollPosIntoView( pos ); int first = -1; const QList< Okular::RegularAreaRect * > selections = textSelections( pos, d->mouseSelectPos, first ); QSet< int > pagesWithSelectionSet; for ( int i = 0; i < selections.count(); ++i ) pagesWithSelectionSet.insert( i + first ); const QSet< int > noMoreSelectedPages = d->pagesWithTextSelection - pagesWithSelectionSet; // clear the selection from pages not selected anymore foreach( int p, noMoreSelectedPages ) { d->document->setPageTextSelection( p, 0, QColor() ); } // set the new selection for the selected pages foreach( int p, pagesWithSelectionSet ) { d->document->setPageTextSelection( p, selections[ p - first ], palette().color( QPalette::Active, QPalette::Highlight ) ); } d->pagesWithTextSelection = pagesWithSelectionSet; } } static Okular::NormalizedPoint rotateInNormRect( const QPoint &rotated, const QRect &rect, Okular::Rotation rotation ) { Okular::NormalizedPoint ret; switch ( rotation ) { case Okular::Rotation0: ret = Okular::NormalizedPoint( rotated.x(), rotated.y(), rect.width(), rect.height() ); break; case Okular::Rotation90: ret = Okular::NormalizedPoint( rotated.y(), rect.width() - rotated.x(), rect.height(), rect.width() ); break; case Okular::Rotation180: ret = Okular::NormalizedPoint( rect.width() - rotated.x(), rect.height() - rotated.y(), rect.width(), rect.height() ); break; case Okular::Rotation270: ret = Okular::NormalizedPoint( rect.height() - rotated.y(), rotated.x(), rect.height(), rect.width() ); break; } return ret; } Okular::RegularAreaRect * PageView::textSelectionForItem( PageViewItem * item, const QPoint & startPoint, const QPoint & endPoint ) { const QRect & geometry = item->uncroppedGeometry(); Okular::NormalizedPoint startCursor( 0.0, 0.0 ); if ( !startPoint.isNull() ) { startCursor = rotateInNormRect( startPoint, geometry, item->page()->rotation() ); } Okular::NormalizedPoint endCursor( 1.0, 1.0 ); if ( !endPoint.isNull() ) { endCursor = rotateInNormRect( endPoint, geometry, item->page()->rotation() ); } Okular::TextSelection mouseTextSelectionInfo( startCursor, endCursor ); const Okular::Page * okularPage = item->page(); if ( !okularPage->hasTextPage() ) d->document->requestTextPage( okularPage->number() ); Okular::RegularAreaRect * selectionArea = okularPage->textArea( &mouseTextSelectionInfo ); #ifdef PAGEVIEW_DEBUG qCDebug(OkularUiDebug).nospace() << "text areas (" << okularPage->number() << "): " << ( selectionArea ? QString::number( selectionArea->count() ) : "(none)" ); #endif return selectionArea; } void PageView::selectionClear(const ClearMode mode) { QRect updatedRect = d->mouseSelectionRect.normalized().adjusted( -2, -2, 2, 2 ); d->mouseSelecting = false; d->mouseSelectionRect.setCoords( 0, 0, 0, 0 ); d->tableSelectionCols.clear(); d->tableSelectionRows.clear(); d->tableDividersGuessed = false; foreach (const TableSelectionPart &tsp, d->tableSelectionParts) { QRect selectionPartRect = tsp.rectInItem.geometry(tsp.item->uncroppedWidth(), tsp.item->uncroppedHeight()); selectionPartRect.translate( tsp.item->uncroppedGeometry().topLeft () ); // should check whether this is on-screen here? updatedRect = updatedRect.united(selectionPartRect); } if ( mode != ClearOnlyDividers ) { d->tableSelectionParts.clear(); } d->tableSelectionParts.clear(); updatedRect.translate( -contentAreaPosition() ); viewport()->update( updatedRect ); } // const to be used for both zoomFactorFitMode function and slotRelayoutPages. static const int kcolWidthMargin = 6; static const int krowHeightMargin = 12; double PageView::zoomFactorFitMode( ZoomMode mode ) { const int pageCount = d->items.count(); if ( pageCount == 0 ) return 0; const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); const bool overrideCentering = facingCentered && pageCount < 3; const int nCols = overrideCentering ? 1 : viewColumns(); const double colWidth = viewport()->width() / nCols - kcolWidthMargin; const double rowHeight = viewport()->height() - krowHeightMargin; const PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage()) ]; // prevent segmentation fault when openning a new document; if ( !currentItem ) return 0; const Okular::Page * okularPage = currentItem->page(); const double width = okularPage->width(), height = okularPage->height(); if ( mode == ZoomFitWidth ) return (double) colWidth / width; if ( mode == ZoomFitPage ) { const double scaleW = (double) colWidth / (double)width; const double scaleH = (double) rowHeight / (double)height; return qMin(scaleW, scaleH); } return 0; } void PageView::updateZoom( ZoomMode newZoomMode ) { if ( newZoomMode == ZoomFixed ) { if ( d->aZoom->currentItem() == 0 ) newZoomMode = ZoomFitWidth; else if ( d->aZoom->currentItem() == 1 ) newZoomMode = ZoomFitPage; else if ( d->aZoom->currentItem() == 2 ) newZoomMode = ZoomFitAuto; } float newFactor = d->zoomFactor; QAction * checkedZoomAction = 0; switch ( newZoomMode ) { case ZoomFixed:{ //ZoomFixed case QString z = d->aZoom->currentText(); // kdelibs4 sometimes adds accelerators to actions' text directly :( z.remove (QLatin1Char('&')); z.remove (QLatin1Char('%')); newFactor = QLocale().toDouble( z ) / 100.0; }break; case ZoomIn: case ZoomOut:{ const float zoomFactorFitWidth = zoomFactorFitMode(ZoomFitWidth); const float zoomFactorFitPage = zoomFactorFitMode(ZoomFitPage); QVector zoomValue(15); qCopy(kZoomValues, kZoomValues + 13, zoomValue.begin()); zoomValue[13] = zoomFactorFitWidth; zoomValue[14] = zoomFactorFitPage; qSort(zoomValue.begin(), zoomValue.end()); QVector::iterator i; if ( newZoomMode == ZoomOut ) { if (newFactor <= zoomValue.first()) return; i = qLowerBound(zoomValue.begin(), zoomValue.end(), newFactor) - 1; } else { if (newFactor >= zoomValue.last()) return; i = qUpperBound(zoomValue.begin(), zoomValue.end(), newFactor); } const float tmpFactor = *i; if ( tmpFactor == zoomFactorFitWidth ) { newZoomMode = ZoomFitWidth; checkedZoomAction = d->aZoomFitWidth; } else if ( tmpFactor == zoomFactorFitPage ) { newZoomMode = ZoomFitPage; checkedZoomAction = d->aZoomFitPage; } else { newFactor = tmpFactor; newZoomMode = ZoomFixed; } } break; case ZoomFitWidth: checkedZoomAction = d->aZoomFitWidth; break; case ZoomFitPage: checkedZoomAction = d->aZoomFitPage; break; case ZoomFitAuto: checkedZoomAction = d->aZoomAutoFit; break; case ZoomRefreshCurrent: newZoomMode = ZoomFixed; d->zoomFactor = -1; break; } const float upperZoomLimit = d->document->supportsTiles() ? 16.0 : 4.0; if ( newFactor > upperZoomLimit ) newFactor = upperZoomLimit; if ( newFactor < 0.1 ) newFactor = 0.1; if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) ) { // rebuild layout and update the whole viewport d->zoomMode = newZoomMode; d->zoomFactor = newFactor; // be sure to block updates to document's viewport bool prevState = d->blockViewport; d->blockViewport = true; slotRelayoutPages(); d->blockViewport = prevState; // request pixmaps slotRequestVisiblePixmaps(); // update zoom text updateZoomText(); // update actions checked state if ( d->aZoomFitWidth ) { d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth ); d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage ); d->aZoomAutoFit->setChecked( checkedZoomAction == d->aZoomAutoFit ); } } else if ( newZoomMode == ZoomFixed && newFactor == d->zoomFactor ) updateZoomText(); d->aZoomIn->setEnabled( d->zoomFactor < upperZoomLimit-0.001 ); d->aZoomOut->setEnabled( d->zoomFactor > 0.101 ); } void PageView::updateZoomText() { // use current page zoom as zoomFactor if in ZoomFit/* mode if ( d->zoomMode != ZoomFixed && d->items.count() > 0 ) d->zoomFactor = d->items[ qMax( 0, (int)d->document->currentPage() ) ]->zoomFactor(); float newFactor = d->zoomFactor; d->aZoom->removeAllActions(); // add items that describe fit actions QStringList translated; translated << i18n("Fit Width") << i18n("Fit Page") << i18n("Auto Fit"); // add percent items int idx = 0, selIdx = 3; bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio int zoomValueCount = 11; if ( d->document->supportsTiles() ) zoomValueCount = 13; while ( idx < zoomValueCount || !inserted ) { float value = idx < zoomValueCount ? kZoomValues[ idx ] : newFactor; if ( !inserted && newFactor < (value - 0.0001) ) value = newFactor; else idx ++; if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) ) inserted = true; if ( !inserted ) selIdx++; // we do not need to display 2-digit precision QString localValue( QLocale().toString( value * 100.0, 'f', 1 ) ); localValue.remove( QLocale().decimalPoint() + QLatin1Char('0') ); // remove a trailing zero in numbers like 66.70 if ( localValue.right( 1 ) == QLatin1String( "0" ) && localValue.indexOf( QLocale().decimalPoint() ) > -1 ) localValue.chop( 1 ); translated << QStringLiteral( "%1%" ).arg( localValue ); } d->aZoom->setItems( translated ); // select current item in list if ( d->zoomMode == ZoomFitWidth ) selIdx = 0; else if ( d->zoomMode == ZoomFitPage ) selIdx = 1; else if ( d->zoomMode == ZoomFitAuto ) selIdx = 2; // we have to temporarily enable the actions as otherwise we can't set a new current item d->aZoom->setEnabled( true ); d->aZoom->selectableActionGroup()->setEnabled( true ); d->aZoom->setCurrentItem( selIdx ); d->aZoom->setEnabled( d->items.size() > 0 ); d->aZoom->selectableActionGroup()->setEnabled( d->items.size() > 0 ); } void PageView::updateCursor() { const QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); updateCursor( p ); } void PageView::updateCursor( const QPoint &p ) { // detect the underlaying page (if present) PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() ); if ( d->annotator && d->annotator->active() ) { if ( pageItem || d->annotator->annotating() ) setCursor( d->annotator->cursor() ); else setCursor( Qt::ForbiddenCursor ); } else if ( pageItem ) { double nX = pageItem->absToPageX(p.x()); double nY = pageItem->absToPageY(p.y()); // if over a ObjectRect (of type Link) change cursor to hand if ( d->mouseMode == Okular::Settings::EnumMouseMode::TextSelect ) setCursor( Qt::IBeamCursor ); else if ( d->mouseMode == Okular::Settings::EnumMouseMode::Magnifier ) setCursor( Qt::CrossCursor ); else if ( d->mouseMode == Okular::Settings::EnumMouseMode::RectSelect ) setCursor( Qt::CrossCursor ); else if ( d->mouseMode == Okular::Settings::EnumMouseMode::TrimSelect ) setCursor( Qt::CrossCursor ); else if ( d->mouseMode == Okular::Settings::EnumMouseMode::Browse ) { d->mouseOnRect = false; if ( d->mouseAnnotation->isMouseOver() ) { d->mouseOnRect = true; setCursor( d->mouseAnnotation->cursor() ); } else { const Okular::ObjectRect * linkobj = pageItem->page()->objectRect( Okular::ObjectRect::Action, nX, nY, pageItem->uncroppedWidth(), pageItem->uncroppedHeight() ); if ( linkobj ) { d->mouseOnRect = true; setCursor( Qt::PointingHandCursor ); } else { setCursor( Qt::OpenHandCursor ); } } } else { setCursor( Qt::ArrowCursor ); } } else { // if there's no page over the cursor and we were showing the pointingHandCursor // go back to the normal one d->mouseOnRect = false; setCursor( Qt::ArrowCursor ); } } void PageView::reloadForms() { QLinkedList< PageViewItem * >::const_iterator iIt = d->visibleItems.constBegin(), iEnd = d->visibleItems.constEnd(); if( d->m_formsVisible ) { for ( ; iIt != iEnd; ++iIt ) { (*iIt)->reloadFormWidgetsState(); } } } void PageView::moveMagnifier( const QPoint& p ) // non scaled point { const int w = d->magnifierView->width() * 0.5; const int h = d->magnifierView->height() * 0.5; int x = p.x() - w; int y = p.y() - h; const int max_x = viewport()->width(); const int max_y = viewport()->height(); QPoint scroll(0,0); if (x < 0) { if (horizontalScrollBar()->value() > 0) scroll.setX(x - w); x = 0; } if (y < 0) { if (verticalScrollBar()->value() > 0) scroll.setY(y - h); y = 0; } if (p.x() + w > max_x) { if (horizontalScrollBar()->value() < horizontalScrollBar()->maximum()) scroll.setX(p.x() + 2 * w - max_x); x = max_x - d->magnifierView->width() - 1; } if (p.y() + h > max_y) { if (verticalScrollBar()->value() < verticalScrollBar()->maximum()) scroll.setY(p.y() + 2 * h - max_y); y = max_y - d->magnifierView->height() - 1; } if (!scroll.isNull()) scrollPosIntoView(contentAreaPoint(p + scroll)); d->magnifierView->move(x, y); } void PageView::updateMagnifier( const QPoint& p ) // scaled point { /* translate mouse coordinates to page coordinates and inform the magnifier of the situation */ PageViewItem *item = pickItemOnPoint(p.x(), p.y()); if (item) { Okular::NormalizedPoint np(item->absToPageX(p.x()), item->absToPageY(p.y())); d->magnifierView->updateView( np, item->page() ); } } int PageView::viewColumns() const { int vm = Okular::Settings::viewMode(); if (vm == Okular::Settings::EnumViewMode::Single) return 1; else if (vm == Okular::Settings::EnumViewMode::Facing || vm == Okular::Settings::EnumViewMode::FacingFirstCentered) return 2; else return Okular::Settings::viewColumns(); } void PageView::center(int cx, int cy) { scrollTo( cx - viewport()->width() / 2, cy - viewport()->height() / 2 ); } void PageView::scrollTo( int x, int y ) { bool prevState = d->blockPixmapsRequest; int newValue = -1; if ( x != horizontalScrollBar()->value() || y != verticalScrollBar()->value() ) newValue = 1; // Pretend this call is the result of a scrollbar event d->blockPixmapsRequest = true; horizontalScrollBar()->setValue( x ); verticalScrollBar()->setValue( y ); d->blockPixmapsRequest = prevState; slotRequestVisiblePixmaps( newValue ); } void PageView::toggleFormWidgets( bool on ) { bool somehadfocus = false; QVector< PageViewItem * >::const_iterator dIt = d->items.constBegin(), dEnd = d->items.constEnd(); for ( ; dIt != dEnd; ++dIt ) { bool hadfocus = (*dIt)->setFormWidgetsVisible( on ); somehadfocus = somehadfocus || hadfocus; } if ( somehadfocus ) setFocus(); d->m_formsVisible = on; if ( d->aToggleForms ) // it may not exist if we are on dummy mode { if ( d->m_formsVisible ) { d->aToggleForms->setText( i18n( "Hide Forms" ) ); } else { d->aToggleForms->setText( i18n( "Show Forms" ) ); } } } void PageView::resizeContentArea( const QSize & newSize ) { const QSize vs = viewport()->size(); int hRange = newSize.width() - vs.width(); int vRange = newSize.height() - vs.height(); if ( horizontalScrollBar()->isVisible() && hRange == verticalScrollBar()->width() && verticalScrollBar()->isVisible() && vRange == horizontalScrollBar()->height() && Okular::Settings::showScrollBars() ) { hRange = 0; vRange = 0; } horizontalScrollBar()->setRange( 0, hRange ); verticalScrollBar()->setRange( 0, vRange ); updatePageStep(); } void PageView::updatePageStep() { const QSize vs = viewport()->size(); horizontalScrollBar()->setPageStep( vs.width() ); verticalScrollBar()->setPageStep( vs.height() * (100 - Okular::Settings::scrollOverlap()) / 100 ); } void PageView::addWebShortcutsMenu( QMenu * menu, const QString & text ) { if ( text.isEmpty() ) { return; } QString searchText = text; searchText = searchText.replace( QLatin1Char('\n'), QLatin1Char(' ') ).replace(QLatin1Char( '\r'), QLatin1Char(' ') ).simplified(); if ( searchText.isEmpty() ) { return; } KUriFilterData filterData( searchText ); filterData.setSearchFilteringOptions( KUriFilterData::RetrievePreferredSearchProvidersOnly ); if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::NormalTextFilter ) ) { const QStringList searchProviders = filterData.preferredSearchProviders(); if ( !searchProviders.isEmpty() ) { QMenu *webShortcutsMenu = new QMenu( menu ); webShortcutsMenu->setIcon( QIcon::fromTheme( QStringLiteral("preferences-web-browser-shortcuts") ) ); const QString squeezedText = KStringHandler::rsqueeze( searchText, 21 ); webShortcutsMenu->setTitle( i18n( "Search for '%1' with", squeezedText ) ); QAction *action = 0; foreach( const QString &searchProvider, searchProviders ) { action = new QAction( searchProvider, webShortcutsMenu ); action->setIcon( QIcon::fromTheme( filterData.iconNameForPreferredSearchProvider( searchProvider ) ) ); action->setData( filterData.queryForPreferredSearchProvider( searchProvider ) ); connect( action, &QAction::triggered, this, &PageView::slotHandleWebShortcutAction ); webShortcutsMenu->addAction( action ); } webShortcutsMenu->addSeparator(); action = new QAction( i18n( "Configure Web Shortcuts..." ), webShortcutsMenu ); action->setIcon( QIcon::fromTheme( QStringLiteral("configure") ) ); connect( action, &QAction::triggered, this, &PageView::slotConfigureWebShortcuts ); webShortcutsMenu->addAction( action ); menu->addMenu(webShortcutsMenu); } } } //BEGIN private SLOTS void PageView::slotRelayoutPages() // called by: notifySetup, viewportResizeEvent, slotViewMode, slotContinuousToggled, updateZoom { // set an empty container if we have no pages const int pageCount = d->items.count(); if ( pageCount < 1 ) { return; } // if viewport was auto-moving, stop it if ( d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveActive = false; d->viewportMoveTimer->stop(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); } // common iterator used in this method and viewport parameters QVector< PageViewItem * >::const_iterator iIt, iEnd = d->items.constEnd(); int viewportWidth = viewport()->width(), viewportHeight = viewport()->height(), fullWidth = 0, fullHeight = 0; QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewportWidth, viewportHeight ); // handle the 'center first page in row' stuff const bool facing = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount > 1; const bool facingCentered = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::FacingFirstCentered || (Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Facing && pageCount == 1); const bool overrideCentering = facingCentered && pageCount < 3; const bool centerFirstPage = facingCentered && !overrideCentering; const bool facingPages = facing || centerFirstPage; const bool centerLastPage = centerFirstPage && pageCount % 2 == 0; const bool continuousView = Okular::Settings::viewContinuous(); const int nCols = overrideCentering ? 1 : viewColumns(); const bool singlePageViewMode = Okular::Settings::viewMode() == Okular::Settings::EnumViewMode::Single; if ( d->aFitWindowToPage ) d->aFitWindowToPage->setEnabled( !continuousView && singlePageViewMode ); // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately PageViewItem * currentItem = d->items[ qMax( 0, (int)d->document->currentPage() ) ]; // Here we find out column's width and row's height to compute a table // so we can place widgets 'centered in virtual cells'. const int nRows = (int)ceil( (float)(centerFirstPage ? (pageCount + nCols - 1) : pageCount) / (float)nCols ); int * colWidth = new int[ nCols ], * rowHeight = new int[ nRows ], cIdx = 0, rIdx = 0; for ( int i = 0; i < nCols; i++ ) colWidth[ i ] = viewportWidth / nCols; for ( int i = 0; i < nRows; i++ ) rowHeight[ i ] = 0; // handle the 'centering on first row' stuff if ( centerFirstPage ) cIdx += nCols - 1; // 1) find the maximum columns width and rows height for a grid in // which each page must well-fit inside a cell for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; // update internal page size (leaving a little margin in case of Fit* modes) updateItemSize( item, colWidth[ cIdx ] - kcolWidthMargin, viewportHeight - krowHeightMargin ); // find row's maximum height and column's max width if ( item->croppedWidth() + kcolWidthMargin > colWidth[ cIdx ] ) colWidth[ cIdx ] = item->croppedWidth() + kcolWidthMargin; if ( item->croppedHeight() + krowHeightMargin > rowHeight[ rIdx ] ) rowHeight[ rIdx ] = item->croppedHeight() + krowHeightMargin; // handle the 'centering on first row' stuff // update col/row indices if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; } } const int pageRowIdx = ( ( centerFirstPage ? nCols - 1 : 0 ) + currentItem->pageNumber() ) / nCols; // 2) compute full size for ( int i = 0; i < nCols; i++ ) fullWidth += colWidth[ i ]; if ( continuousView ) { for ( int i = 0; i < nRows; i++ ) fullHeight += rowHeight[ i ]; } else fullHeight = rowHeight[ pageRowIdx ]; // 3) arrange widgets inside cells (and refine fullHeight if needed) int insertX = 0, insertY = fullHeight < viewportHeight ? ( viewportHeight - fullHeight ) / 2 : 0; const int origInsertY = insertY; cIdx = 0; rIdx = 0; if ( centerFirstPage ) { cIdx += nCols - 1; for ( int i = 0; i < cIdx; ++i ) insertX += colWidth[ i ]; } for ( iIt = d->items.constBegin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; int cWidth = colWidth[ cIdx ], rHeight = rowHeight[ rIdx ]; if ( continuousView || rIdx == pageRowIdx ) { const bool reallyDoCenterFirst = item->pageNumber() == 0 && centerFirstPage; const bool reallyDoCenterLast = item->pageNumber() == pageCount - 1 && centerLastPage; int actualX = 0; if ( reallyDoCenterFirst || reallyDoCenterLast ) { // page is centered across entire viewport actualX = (fullWidth - item->croppedWidth()) / 2; } else if ( facingPages ) { if (Okular::Settings::rtlReadingDirection()){ // RTL reading mode actualX = ( (centerFirstPage && item->pageNumber() % 2 == 0) || (!centerFirstPage && item->pageNumber() % 2 == 1) ) ? (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; } else { // page edges 'touch' the center of the viewport actualX = ( (centerFirstPage && item->pageNumber() % 2 == 1) || (!centerFirstPage && item->pageNumber() % 2 == 0) ) ? (fullWidth / 2) - item->croppedWidth() - 1 : (fullWidth / 2) + 1; } } else { // page is centered within its virtual column //actualX = insertX + (cWidth - item->croppedWidth()) / 2; if (Okular::Settings::rtlReadingDirection()){ actualX = fullWidth - insertX - cWidth +( (cWidth - item->croppedWidth()) / 2); } else { actualX = insertX + (cWidth - item->croppedWidth()) / 2; } } item->moveTo( actualX, (continuousView ? insertY : origInsertY) + (rHeight - item->croppedHeight()) / 2 ); item->setVisible( true ); } else { item->moveTo( 0, 0 ); item->setVisible( false ); } item->setFormWidgetsVisible( d->m_formsVisible ); // advance col/row index insertX += cWidth; if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; insertX = 0; insertY += rHeight; } #ifdef PAGEVIEW_DEBUG kWarning() << "updating size for pageno" << item->pageNumber() << "cropped" << item->croppedGeometry() << "uncropped" << item->uncroppedGeometry(); #endif } delete [] colWidth; delete [] rowHeight; // 3) reset dirty state d->dirtyLayout = false; // 4) update scrollview's contents size and recenter view bool wasUpdatesEnabled = viewport()->updatesEnabled(); if ( fullWidth != contentAreaWidth() || fullHeight != contentAreaHeight() ) { const Okular::DocumentViewport vp = d->document->viewport(); // disable updates and resize the viewportContents if ( wasUpdatesEnabled ) viewport()->setUpdatesEnabled( false ); resizeContentArea( QSize( fullWidth, fullHeight ) ); // restore previous viewport if defined and updates enabled if ( wasUpdatesEnabled ) { if ( vp.pageNumber >= 0 ) { int prevX = horizontalScrollBar()->value(), prevY = verticalScrollBar()->value(); const QRect & geometry = d->items[ vp.pageNumber ]->croppedGeometry(); double nX = vp.rePos.enabled ? normClamp( vp.rePos.normalizedX, 0.5 ) : 0.5, nY = vp.rePos.enabled ? normClamp( vp.rePos.normalizedY, 0.0 ) : 0.0; center( geometry.left() + qRound( nX * (double)geometry.width() ), geometry.top() + qRound( nY * (double)geometry.height() ) ); // center() usually moves the viewport, that requests pixmaps too. // if that doesn't happen we have to request them by hand if ( prevX == horizontalScrollBar()->value() && prevY == verticalScrollBar()->value() ) slotRequestVisiblePixmaps(); } // or else go to center page else center( fullWidth / 2, 0 ); viewport()->setUpdatesEnabled( true ); } } // 5) update the whole viewport if updated enabled if ( wasUpdatesEnabled ) viewport()->update(); } void PageView::delayedResizeEvent() { // If we already got here we don't need to execute the timer slot again d->delayResizeEventTimer->stop(); slotRelayoutPages(); slotRequestVisiblePixmaps(); } static void slotRequestPreloadPixmap( Okular::DocumentObserver * observer, const PageViewItem * i, const QRect &expandedViewportRect, QLinkedList< Okular::PixmapRequest * > *requestedPixmaps ) { Okular::NormalizedRect preRenderRegion; const QRect intersectionRect = expandedViewportRect.intersected( i->croppedGeometry() ); if ( !intersectionRect.isEmpty() ) preRenderRegion = Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ); // request the pixmap if not already present if ( !i->page()->hasPixmap( observer, i->uncroppedWidth(), i->uncroppedHeight(), preRenderRegion ) && i->uncroppedWidth() > 0 ) { Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; requestFeatures |= Okular::PixmapRequest::Asynchronous; const bool pageHasTilesManager = i->page()->hasTilesManager( observer ); if ( pageHasTilesManager && !preRenderRegion.isNull() ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); requestedPixmaps->push_back( p ); p->setNormalizedRect( preRenderRegion ); p->setTile( true ); } else if ( !pageHasTilesManager ) { Okular::PixmapRequest * p = new Okular::PixmapRequest( observer, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRELOAD_PRIO, requestFeatures ); requestedPixmaps->push_back( p ); p->setNormalizedRect( preRenderRegion ); } } } void PageView::slotRequestVisiblePixmaps( int newValue ) { // if requests are blocked (because raised by an unwanted event), exit if ( d->blockPixmapsRequest || d->viewportMoveActive ) return; // precalc view limits for intersecting with page coords inside the loop const bool isEvent = newValue != -1 && !d->blockViewport; const QRect viewportRect( horizontalScrollBar()->value(), verticalScrollBar()->value(), viewport()->width(), viewport()->height() ); const QRect viewportRectAtZeroZero( 0, 0, viewport()->width(), viewport()->height() ); // some variables used to determine the viewport int nearPageNumber = -1; const double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0; const double viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0; double focusedX = 0.5, focusedY = 0.0, minDistance = -1.0; // Margin (in pixels) around the viewport to preload const int pixelsToExpand = 512; // iterate over all items d->visibleItems.clear(); QLinkedList< Okular::PixmapRequest * > requestedPixmaps; QVector< Okular::VisiblePageRect * > visibleRects; QVector< PageViewItem * >::const_iterator iIt = d->items.constBegin(), iEnd = d->items.constEnd(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; foreach( FormWidgetIface *fwi, i->formWidgets() ) { Okular::NormalizedRect r = fwi->rect(); fwi->moveTo( qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); } Q_FOREACH ( VideoWidget *vw, i->videoWidgets() ) { const Okular::NormalizedRect r = vw->normGeometry(); vw->move( qRound( i->uncroppedGeometry().left() + i->uncroppedWidth() * r.left ) + 1 - viewportRect.left(), qRound( i->uncroppedGeometry().top() + i->uncroppedHeight() * r.top ) + 1 - viewportRect.top() ); if ( vw->isPlaying() && viewportRectAtZeroZero.intersected( vw->geometry() ).isEmpty() ) { vw->stop(); vw->pageLeft(); } } if ( !i->isVisible() ) continue; #ifdef PAGEVIEW_DEBUG kWarning() << "checking page" << i->pageNumber(); kWarning().nospace() << "viewportRect is " << viewportRect << ", page item is " << i->croppedGeometry() << " intersect : " << viewportRect.intersects( i->croppedGeometry() ); #endif // if the item doesn't intersect the viewport, skip it QRect intersectionRect = viewportRect.intersected( i->croppedGeometry() ); if ( intersectionRect.isEmpty() ) { continue; } // add the item to the 'visible list' d->visibleItems.push_back( i ); Okular::VisiblePageRect * vItem = new Okular::VisiblePageRect( i->pageNumber(), Okular::NormalizedRect( intersectionRect.translated( -i->uncroppedGeometry().topLeft() ), i->uncroppedWidth(), i->uncroppedHeight() ) ); visibleRects.push_back( vItem ); #ifdef PAGEVIEW_DEBUG kWarning() << "checking for pixmap for page" << i->pageNumber() << "=" << i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight() ); kWarning() << "checking for text for page" << i->pageNumber() << "=" << i->page()->hasTextPage(); #endif Okular::NormalizedRect expandedVisibleRect = vItem->rect; if ( i->page()->hasTilesManager( this ) && Okular::Settings::memoryLevel() != Okular::Settings::EnumMemoryLevel::Low ) { double rectMargin = pixelsToExpand/(double)i->uncroppedHeight(); expandedVisibleRect.left = qMax( 0.0, vItem->rect.left - rectMargin ); expandedVisibleRect.top = qMax( 0.0, vItem->rect.top - rectMargin ); expandedVisibleRect.right = qMin( 1.0, vItem->rect.right + rectMargin ); expandedVisibleRect.bottom = qMin( 1.0, vItem->rect.bottom + rectMargin ); } // if the item has not the right pixmap, add a request for it if ( !i->page()->hasPixmap( this, i->uncroppedWidth(), i->uncroppedHeight(), expandedVisibleRect ) ) { #ifdef PAGEVIEW_DEBUG kWarning() << "rerequesting visible pixmaps for page" << i->pageNumber() << "!"; #endif Okular::PixmapRequest * p = new Okular::PixmapRequest( this, i->pageNumber(), i->uncroppedWidth(), i->uncroppedHeight(), PAGEVIEW_PRIO, Okular::PixmapRequest::Asynchronous ); requestedPixmaps.push_back( p ); if ( i->page()->hasTilesManager( this ) ) { p->setNormalizedRect( expandedVisibleRect ); p->setTile( true ); } else p->setNormalizedRect( vItem->rect ); } // look for the item closest to viewport center and the relative // position between the item and the viewport center if ( isEvent ) { const QRect & geometry = i->croppedGeometry(); // compute distance between item center and viewport center (slightly moved left) double distance = hypot( (geometry.left() + geometry.right()) / 2 - (viewportCenterX - 4), (geometry.top() + geometry.bottom()) / 2 - viewportCenterY ); if ( distance >= minDistance && nearPageNumber != -1 ) continue; nearPageNumber = i->pageNumber(); minDistance = distance; if ( geometry.height() > 0 && geometry.width() > 0 ) { focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width(); focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height(); } } } // if preloading is enabled, add the pages before and after in preloading if ( !d->visibleItems.isEmpty() && Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) { // as the requests are done in the order as they appear in the list, // request first the next page and then the previous int pagesToPreload = viewColumns(); // if the greedy option is set, preload all pages if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) pagesToPreload = d->items.count(); const QRect expandedViewportRect = viewportRect.adjusted( 0, -pixelsToExpand, 0, pixelsToExpand ); for( int j = 1; j <= pagesToPreload; j++ ) { // add the page after the 'visible series' in preload const int tailRequest = d->visibleItems.last()->pageNumber() + j; if ( tailRequest < (int)d->items.count() ) { slotRequestPreloadPixmap( this, d->items[ tailRequest ], expandedViewportRect, &requestedPixmaps ); } // add the page before the 'visible series' in preload const int headRequest = d->visibleItems.first()->pageNumber() - j; if ( headRequest >= 0 ) { slotRequestPreloadPixmap( this, d->items[ headRequest ], expandedViewportRect, &requestedPixmaps ); } // stop if we've already reached both ends of the document if ( headRequest < 0 && tailRequest >= (int)d->items.count() ) break; } } // send requests to the document if ( !requestedPixmaps.isEmpty() ) { d->document->requestPixmaps( requestedPixmaps ); } // if this functions was invoked by viewport events, send update to document if ( isEvent && nearPageNumber != -1 ) { // determine the document viewport Okular::DocumentViewport newViewport( nearPageNumber ); newViewport.rePos.enabled = true; newViewport.rePos.normalizedX = focusedX; newViewport.rePos.normalizedY = focusedY; // set the viewport to other observers d->document->setViewport( newViewport , this ); } d->document->setVisiblePageRects( visibleRects, this ); } void PageView::slotMoveViewport() { // converge to viewportMoveDest in 1 second int diffTime = d->viewportMoveTime.elapsed(); if ( diffTime >= 667 || !d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveTimer->stop(); d->viewportMoveActive = false; slotRequestVisiblePixmaps(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); return; } // move the viewport smoothly (kmplot: p(x)=1+0.47*(x-1)^3-0.25*(x-1)^4) float convergeSpeed = (float)diffTime / 667.0, x = ((float)viewport()->width() / 2.0) + horizontalScrollBar()->value(), y = ((float)viewport()->height() / 2.0) + verticalScrollBar()->value(), diffX = (float)d->viewportMoveDest.x() - x, diffY = (float)d->viewportMoveDest.y() - y; convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed); center( (int)(x + diffX * convergeSpeed), (int)(y + diffY * convergeSpeed ) ); } void PageView::slotAutoScroll() { // the first time create the timer if ( !d->autoScrollTimer ) { d->autoScrollTimer = new QTimer( this ); d->autoScrollTimer->setSingleShot( true ); connect( d->autoScrollTimer, &QTimer::timeout, this, &PageView::slotAutoScroll ); } // if scrollIncrement is zero, stop the timer if ( !d->scrollIncrement ) { d->autoScrollTimer->stop(); return; } // compute delay between timer ticks and scroll amount per tick int index = abs( d->scrollIncrement ) - 1; // 0..9 const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 }; const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 }; d->autoScrollTimer->start( scrollDelay[ index ] ); int delta = d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ]; verticalScrollBar()->setValue(verticalScrollBar()->value() + delta); } void PageView::slotDragScroll() { scrollTo( horizontalScrollBar()->value() + d->dragScrollVector.x(), verticalScrollBar()->value() + d->dragScrollVector.y() ); QPoint p = contentAreaPosition() + viewport()->mapFromGlobal( QCursor::pos() ); updateSelection( p ); } void PageView::slotShowWelcome() { // show initial welcome text d->messageWindow->display( i18n( "Welcome" ), QString(), PageViewMessage::Info, 2000 ); } void PageView::slotShowSizeAllCursor() { setCursor( Qt::SizeAllCursor ); } void PageView::slotHandleWebShortcutAction() { QAction *action = qobject_cast( sender() ); if (action) { KUriFilterData filterData( action->data().toString() ); if ( KUriFilter::self()->filterSearchUri( filterData, KUriFilter::WebShortcutFilter ) ) { QDesktopServices::openUrl( filterData.uri() ); } } } void PageView::slotConfigureWebShortcuts() { KToolInvocation::kdeinitExec( QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts") ); } void PageView::slotZoom() { if ( !d->aZoom->selectableActionGroup()->isEnabled() ) return; setFocus(); updateZoom( ZoomFixed ); } void PageView::slotZoomIn() { updateZoom( ZoomIn ); } void PageView::slotZoomOut() { updateZoom( ZoomOut ); } void PageView::slotFitToWidthToggled( bool on ) { if ( on ) updateZoom( ZoomFitWidth ); } void PageView::slotFitToPageToggled( bool on ) { if ( on ) updateZoom( ZoomFitPage ); } void PageView::slotAutoFitToggled( bool on ) { if ( on ) updateZoom( ZoomFitAuto ); } void PageView::slotViewMode( QAction *action ) { const int nr = action->data().toInt(); if ( (int)Okular::Settings::viewMode() != nr ) { Okular::Settings::setViewMode( nr ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotContinuousToggled( bool on ) { if ( Okular::Settings::viewContinuous() != on ) { Okular::Settings::setViewContinuous( on ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotSetMouseNormal() { d->mouseMode = Okular::Settings::EnumMouseMode::Browse; Okular::Settings::setMouseMode( d->mouseMode ); // hide the messageWindow d->messageWindow->hide(); // reshow the annotator toolbar if hiding was forced (and if it is not already visible) if ( d->annotator && d->annotator->hidingWasForced() && d->aToggleAnnotator && !d->aToggleAnnotator->isChecked() ) d->aToggleAnnotator->trigger(); // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseZoom() { d->mouseMode = Okular::Settings::EnumMouseMode::Zoom; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseMagnifier() { d->mouseMode = Okular::Settings::EnumMouseMode::Magnifier; Okular::Settings::setMouseMode( d->mouseMode ); d->messageWindow->display( i18n( "Click to see the magnified view." ), QString() ); // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::RectSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseTextSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::TextSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Select text" ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotSetMouseTableSelect() { d->mouseMode = Okular::Settings::EnumMouseMode::TableSelect; Okular::Settings::setMouseMode( d->mouseMode ); // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the table, then click near edges to divide up; press Esc to clear." ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); Okular::Settings::self()->save(); } void PageView::slotToggleAnnotator( bool on ) { // the 'inHere' trick is needed as the slotSetMouseZoom() calls this static bool inHere = false; if ( inHere ) return; inHere = true; // the annotator can be used in normal mouse mode only, so if asked for it, // switch to normal mode if ( on && d->mouseMode != Okular::Settings::EnumMouseMode::Browse ) d->aMouseNormal->trigger(); // ask for Author's name if not already set if ( Okular::Settings::identityAuthor().isEmpty() ) { // get default username from the kdelibs/kdecore/KUser KUser currentUser; QString userName = currentUser.property( KUser::FullName ).toString(); // ask the user for confirmation/change if ( userName.isEmpty() ) { bool ok = false; userName = QInputDialog::getText(0, i18n( "Annotations author" ), i18n( "Please insert your name or initials:" ), QLineEdit::Normal, QString(), &ok ); if ( !ok ) { d->aToggleAnnotator->trigger(); inHere = false; return; } } // save the name Okular::Settings::setIdentityAuthor( userName ); Okular::Settings::self()->save(); } // create the annotator object if not present if ( !d->annotator ) { d->annotator = new PageViewAnnotator( this, d->document ); bool allowTools = d->document->pages() > 0 && d->document->isAllowed( Okular::AllowNotes ); d->annotator->setToolsEnabled( allowTools ); d->annotator->setTextToolsEnabled( allowTools && d->document->supportsSearching() ); } // initialize/reset annotator (and show/hide toolbar) d->annotator->setEnabled( on ); d->annotator->setHidingForced( false ); inHere = false; } void PageView::slotAutoScrollUp() { if ( d->scrollIncrement < -9 ) return; d->scrollIncrement--; slotAutoScroll(); setFocus(); } void PageView::slotAutoScrollDown() { if ( d->scrollIncrement > 9 ) return; d->scrollIncrement++; slotAutoScroll(); setFocus(); } void PageView::slotScrollUp( bool singleStep ) { // if in single page mode and at the top of the screen, go to \ page if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minimum() ) { if ( singleStep ) verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepSub ); else verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepSub ); } else if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= viewColumns(); if ( newViewport.pageNumber < 0 ) newViewport.pageNumber = 0; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } void PageView::slotScrollDown( bool singleStep ) { // if in single page mode and at the bottom of the screen, go to next page if ( Okular::Settings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maximum() ) { if ( singleStep ) verticalScrollBar()->triggerAction( QScrollBar::SliderSingleStepAdd ); else verticalScrollBar()->triggerAction( QScrollBar::SliderPageStepAdd ); } else if ( (int)d->document->currentPage() < d->items.count() - 1 ) { // more optimized than document->setNextPage and then move view to top Okular::DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += viewColumns(); if ( newViewport.pageNumber >= (int)d->items.count() ) newViewport.pageNumber = d->items.count() - 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } void PageView::slotRotateClockwise() { int id = ( (int)d->document->rotation() + 1 ) % 4; d->document->setRotation( id ); } void PageView::slotRotateCounterClockwise() { int id = ( (int)d->document->rotation() + 3 ) % 4; d->document->setRotation( id ); } void PageView::slotRotateOriginal() { d->document->setRotation( 0 ); } void PageView::slotPageSizes( int newsize ) { if ( newsize < 0 || newsize >= d->document->pageSizes().count() ) return; d->document->setPageSize( d->document->pageSizes().at( newsize ) ); } // Enforce mutual-exclusion between trim modes // Each mode is uniquely identified by a single value // From Okular::Settings::EnumTrimMode void PageView::updateTrimMode( int except_id ) { const QList trimModeActions = d->aTrimMode->menu()->actions(); foreach(QAction *trimModeAction, trimModeActions) { if (trimModeAction->data().toInt() != except_id) trimModeAction->setChecked( false ); } } void PageView::slotTrimMarginsToggled( bool on ) { if (on) { // Turn off any other Trim modes updateTrimMode(d->aTrimMargins->data().toInt()); } if ( Okular::Settings::trimMargins() != on ) { Okular::Settings::setTrimMargins( on ); Okular::Settings::self()->save(); if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } } } void PageView::slotTrimToSelectionToggled( bool on ) { if ( on ) { // Turn off any other Trim modes updateTrimMode(d->aTrimToSelection->data().toInt()); d->mouseMode = Okular::Settings::EnumMouseMode::TrimSelect; // change the text in messageWindow (and show it if hidden) d->messageWindow->display( i18n( "Draw a rectangle around the page area you wish to keep visible" ), QString(), PageViewMessage::Info, -1 ); // force hiding of annotator toolbar if ( d->aToggleAnnotator && d->aToggleAnnotator->isChecked() ) { d->aToggleAnnotator->trigger(); d->annotator->setHidingForced( true ); } // force an update of the cursor updateCursor(); } else { // toggled off while making selection if ( Okular::Settings::EnumMouseMode::TrimSelect == d->mouseMode ) { // clear widget selection and invalidate rect selectionClear(); // When Trim selection bbox interaction is over, we should switch to another mousemode. if ( d->aPrevAction ) { d->aPrevAction->trigger(); d->aPrevAction = 0; } else { d->aMouseNormal->trigger(); } } d->trimBoundingBox = Okular::NormalizedRect(); // invalidate box if ( d->document->pages() > 0 ) { slotRelayoutPages(); slotRequestVisiblePixmaps(); // TODO: slotRelayoutPages() may have done this already! } } } void PageView::slotToggleForms() { toggleFormWidgets( !d->m_formsVisible ); } void PageView::slotFormChanged( int pageNumber ) { if ( !d->refreshTimer ) { d->refreshTimer = new QTimer( this ); d->refreshTimer->setSingleShot( true ); connect( d->refreshTimer, &QTimer::timeout, this, &PageView::slotRefreshPage ); } d->refreshPages << pageNumber; int delay = 0; if ( d->m_formsVisible ) { delay = 1000; } d->refreshTimer->start( delay ); } void PageView::slotRefreshPage() { foreach(int req, d->refreshPages) { QMetaObject::invokeMethod( d->document, "refreshPixmaps", Qt::QueuedConnection, Q_ARG( int, req ) ); } d->refreshPages.clear(); } #ifdef HAVE_SPEECH void PageView::slotSpeakDocument() { QString text; QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); for ( ; it < itEnd; ++it ) { Okular::RegularAreaRect * area = textSelectionForItem( *it ); text.append( (*it)->page()->text( area ) ); text.append( '\n' ); delete area; } d->tts()->say( text ); } void PageView::slotSpeakCurrentPage() { const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); Okular::RegularAreaRect * area = textSelectionForItem( item ); const QString text = item->page()->text( area ); delete area; d->tts()->say( text ); } void PageView::slotStopSpeaks() { if ( !d->m_tts ) return; d->m_tts->stopAllSpeechs(); } #endif void PageView::slotAction( Okular::Action *action ) { d->document->processAction( action ); } void PageView::externalKeyPressEvent( QKeyEvent *e ) { keyPressEvent( e ); } void PageView::slotProcessMovieAction( const Okular::MovieAction *action ) { const Okular::MovieAnnotation *movieAnnotation = action->annotation(); if ( !movieAnnotation ) return; Okular::Movie *movie = movieAnnotation->movie(); if ( !movie ) return; const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); if ( !item ) return; VideoWidget *vw = item->videoWidgets().value( movie ); if ( !vw ) return; vw->show(); switch ( action->operation() ) { case Okular::MovieAction::Play: vw->stop(); vw->play(); break; case Okular::MovieAction::Stop: vw->stop(); break; case Okular::MovieAction::Pause: vw->pause(); break; case Okular::MovieAction::Resume: vw->play(); break; }; } void PageView::slotProcessRenditionAction( const Okular::RenditionAction *action ) { Okular::Movie *movie = action->movie(); if ( !movie ) return; const int currentPage = d->document->viewport().pageNumber; PageViewItem *item = d->items.at( currentPage ); if ( !item ) return; VideoWidget *vw = item->videoWidgets().value( movie ); if ( !vw ) return; if ( action->operation() == Okular::RenditionAction::None ) return; vw->show(); switch ( action->operation() ) { case Okular::RenditionAction::Play: vw->stop(); vw->play(); break; case Okular::RenditionAction::Stop: vw->stop(); break; case Okular::RenditionAction::Pause: vw->pause(); break; case Okular::RenditionAction::Resume: vw->play(); break; default: return; }; } void PageView::slotToggleChangeColors() { Okular::SettingsCore::setChangeColors( !Okular::SettingsCore::changeColors() ); Okular::Settings::self()->save(); viewport()->update(); } void PageView::slotFitWindowToPage() { PageViewItem currentPageItem = NULL; QSize viewportSize = viewport()->size(); foreach ( const PageViewItem * pageItem, d->items ) { if ( pageItem->isVisible() ) { currentPageItem = *pageItem; break; } } const QSize pageSize = QSize( currentPageItem.uncroppedWidth() + kcolWidthMargin, currentPageItem.uncroppedHeight() + krowHeightMargin ); if ( verticalScrollBar()->isVisible() ) viewportSize.setWidth( viewportSize.width() + verticalScrollBar()->width() ); if ( horizontalScrollBar()->isVisible() ) viewportSize.setHeight( viewportSize.height() + horizontalScrollBar()->height() ); emit fitWindowToPage( viewportSize, pageSize ); } //END private SLOTS #include "moc_pageview.cpp" /* kate: replace-tabs on; indent-width 4; */ diff --git a/ui/videowidget.cpp b/ui/videowidget.cpp index 659172a9c..f29ce904c 100644 --- a/ui/videowidget.cpp +++ b/ui/videowidget.cpp @@ -1,449 +1,450 @@ /*************************************************************************** * Copyright (C) 2008 by Pino Toscano * * * * 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. * ***************************************************************************/ #include "videowidget.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/annotations.h" #include "core/area.h" #include "core/document.h" #include "core/movie.h" #include "snapshottaker.h" static QAction* createToolBarButtonWithWidgetPopup( QToolBar* toolBar, QWidget *widget, const QIcon &icon ) { QToolButton *button = new QToolButton( toolBar ); QAction *action = toolBar->addWidget( button ); button->setAutoRaise( true ); button->setIcon( icon ); button->setPopupMode( QToolButton::InstantPopup ); QMenu *menu = new QMenu( button ); button->setMenu( menu ); QWidgetAction *widgetAction = new QWidgetAction( menu ); QWidget *dummy = new QWidget( menu ); widgetAction->setDefaultWidget( dummy ); QVBoxLayout *dummyLayout = new QVBoxLayout( dummy ); dummyLayout->setMargin( 5 ); dummyLayout->addWidget( widget ); menu->addAction( widgetAction ); return action; } /* Private storage. */ class VideoWidget::Private { public: Private( Okular::Movie *m, Okular::Document *doc, VideoWidget *qq ) : q( qq ), movie( m ), document( doc ), player( 0 ), loaded( false ) { } ~Private() { if ( player ) player->stop(); } enum PlayPauseMode { PlayMode, PauseMode }; void load(); void setupPlayPauseAction( PlayPauseMode mode ); void setPosterImage( const QImage& ); void takeSnapshot(); void videoStopped(); void stateChanged(Phonon::State, Phonon::State); // slots void finished(); void playOrPause(); VideoWidget *q; Okular::Movie *movie; Okular::Document *document; Okular::NormalizedRect geom; Phonon::VideoPlayer *player; Phonon::SeekSlider *seekSlider; QToolBar *controlBar; QAction *playPauseAction; QAction *stopAction; QAction *seekSliderAction; QAction *seekSliderMenuAction; QStackedLayout *pageLayout; QLabel *posterImagePage; bool loaded : 1; double repetitionsLeft; }; static QUrl urlFromUrlString(const QString &url, Okular::Document *document) { QUrl newurl; if ( url.startsWith( QLatin1Char( '/' ) ) ) { newurl = QUrl::fromLocalFile( url ); } else { newurl = QUrl( url ); if ( newurl.isRelative() ) { newurl = document->currentDocument().adjusted(QUrl::RemoveFilename); newurl.setPath( newurl.path() + url ); } } return newurl; } void VideoWidget::Private::load() { repetitionsLeft = movie->playRepetitions(); if ( loaded ) return; loaded = true; player->load( urlFromUrlString( movie->url(), document ) ); connect( player->mediaObject(), SIGNAL( stateChanged( Phonon::State, Phonon::State ) ), q, SLOT( stateChanged( Phonon::State, Phonon::State ) ) ); seekSlider->setEnabled( true ); } void VideoWidget::Private::setupPlayPauseAction( PlayPauseMode mode ) { if ( mode == PlayMode ) { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-start") ) ); playPauseAction->setText( i18nc( "start the movie playback", "Play" ) ); } else if ( mode == PauseMode ) { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-pause") ) ); playPauseAction->setText( i18nc( "pause the movie playback", "Pause" ) ); } } void VideoWidget::Private::takeSnapshot() { const QUrl url = urlFromUrlString( movie->url(), document ); SnapshotTaker *taker = new SnapshotTaker( url, q ); q->connect( taker, SIGNAL( finished( const QImage& ) ), q, SLOT( setPosterImage( const QImage& ) ) ); } void VideoWidget::Private::videoStopped() { if ( movie->showPosterImage() ) pageLayout->setCurrentIndex( 1 ); else q->hide(); } void VideoWidget::Private::finished() { switch ( movie->playMode() ) { case Okular::Movie::PlayLimited: case Okular::Movie::PlayOpen: repetitionsLeft -= 1.0; if ( repetitionsLeft < 1e-5 ) { // allow for some calculation error // playback has ended stopAction->setEnabled( false ); setupPlayPauseAction( PlayMode ); if ( movie->playMode() == Okular::Movie::PlayLimited ) controlBar->setVisible( false ); videoStopped(); } else // not done yet, repeat // if repetitionsLeft is less than 1, we are supposed to stop midway, but not even Adobe reader does this player->play(); break; case Okular::Movie::PlayRepeat: // repeat the playback player->play(); break; case Okular::Movie::PlayPalindrome: // FIXME we should play backward, but we cannot player->play(); break; } } void VideoWidget::Private::playOrPause() { if ( player->isPlaying() ) { player->pause(); setupPlayPauseAction( PlayMode ); } else { q->play(); } } void VideoWidget::Private::setPosterImage( const QImage &image ) { if ( !image.isNull() ) { // cache the snapshot image movie->setPosterImage( image ); } posterImagePage->setPixmap( QPixmap::fromImage( image ) ); } void VideoWidget::Private::stateChanged( Phonon::State newState, Phonon::State ) { if ( newState == Phonon::PlayingState ) pageLayout->setCurrentIndex( 0 ); } VideoWidget::VideoWidget( const Okular::Annotation *annotation, Okular::Movie *movie, Okular::Document *document, QWidget *parent ) : QWidget( parent ), d( new Private( movie, document, this ) ) { // do not propagate the mouse events to the parent widget; // they should be tied to this widget, not spread around... setAttribute( Qt::WA_NoMousePropagation ); // Setup player page QWidget *playerPage = new QWidget; QVBoxLayout *mainlay = new QVBoxLayout( playerPage ); mainlay->setMargin( 0 ); mainlay->setSpacing( 0 ); d->player = new Phonon::VideoPlayer( Phonon::NoCategory, playerPage ); d->player->installEventFilter( playerPage ); mainlay->addWidget( d->player ); d->controlBar = new QToolBar( playerPage ); d->controlBar->setIconSize( QSize( 16, 16 ) ); d->controlBar->setAutoFillBackground( true ); mainlay->addWidget( d->controlBar ); d->playPauseAction = new QAction( d->controlBar ); d->controlBar->addAction( d->playPauseAction ); d->setupPlayPauseAction( Private::PlayMode ); d->stopAction = d->controlBar->addAction( QIcon::fromTheme( QStringLiteral("media-playback-stop") ), i18nc( "stop the movie playback", "Stop" ), this, SLOT(stop()) ); d->stopAction->setEnabled( false ); d->controlBar->addSeparator(); d->seekSlider = new Phonon::SeekSlider( d->player->mediaObject(), d->controlBar ); d->seekSliderAction = d->controlBar->addWidget( d->seekSlider ); d->seekSlider->setEnabled( false ); Phonon::SeekSlider *verticalSeekSlider = new Phonon::SeekSlider( d->player->mediaObject(), 0 ); verticalSeekSlider->setMaximumHeight( 100 ); d->seekSliderMenuAction = createToolBarButtonWithWidgetPopup( d->controlBar, verticalSeekSlider, QIcon::fromTheme( QStringLiteral("player-time") ) ); d->seekSliderMenuAction->setVisible( false ); d->controlBar->setVisible( movie->showControls() ); connect( d->player, SIGNAL(finished()), this, SLOT(finished()) ); connect( d->playPauseAction, SIGNAL(triggered()), this, SLOT(playOrPause()) ); d->geom = annotation->transformedBoundingRectangle(); // Setup poster image page d->posterImagePage = new QLabel; d->posterImagePage->setScaledContents( true ); d->posterImagePage->installEventFilter( this ); d->posterImagePage->setCursor( Qt::PointingHandCursor ); d->pageLayout = new QStackedLayout( this ); d->pageLayout->setMargin( 0 ); d->pageLayout->setSpacing( 0 ); d->pageLayout->addWidget( playerPage ); d->pageLayout->addWidget( d->posterImagePage ); if ( movie->showPosterImage() ) { d->pageLayout->setCurrentIndex( 1 ); const QImage posterImage = movie->posterImage(); if ( posterImage.isNull() ) { d->takeSnapshot(); } else { d->setPosterImage( posterImage ); } } else { d->pageLayout->setCurrentIndex( 0 ); } } VideoWidget::~VideoWidget() { delete d; } void VideoWidget::setNormGeometry( const Okular::NormalizedRect &rect ) { d->geom = rect; } Okular::NormalizedRect VideoWidget::normGeometry() const { return d->geom; } bool VideoWidget::isPlaying() const { return d->player->isPlaying(); } void VideoWidget::pageInitialized() { hide(); } void VideoWidget::pageEntered() { if ( d->movie->showPosterImage() ) { d->pageLayout->setCurrentIndex( 1 ); show(); } if ( d->movie->autoPlay() ) { show(); QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection); } } void VideoWidget::pageLeft() { d->player->stop(); d->videoStopped(); hide(); } void VideoWidget::play() { d->controlBar->setVisible( d->movie->showControls() ); d->load(); // if d->repetitionsLeft is less than 1, we are supposed to stop midway, but not even Adobe reader does this d->player->play(); d->stopAction->setEnabled( true ); d->setupPlayPauseAction( Private::PauseMode ); } void VideoWidget::stop() { d->player->stop(); d->stopAction->setEnabled( false ); d->setupPlayPauseAction( Private::PlayMode ); } void VideoWidget::pause() { d->player->pause(); d->setupPlayPauseAction( Private::PlayMode ); } bool VideoWidget::eventFilter( QObject * object, QEvent * event ) { if ( object == d->player || object == d->posterImagePage ) { switch ( event->type() ) { case QEvent::MouseButtonPress: { QMouseEvent * me = static_cast< QMouseEvent * >( event ); if ( me->button() == Qt::LeftButton ) { if ( !d->player->isPlaying() ) { play(); } event->accept(); } + break; } case QEvent::Wheel: { if ( object == d->posterImagePage ) { QWheelEvent * we = static_cast< QWheelEvent * >( event ); // forward wheel events to parent widget QWheelEvent *copy = new QWheelEvent( we->pos(), we->globalPos(), we->delta(), we->buttons(), we->modifiers(), we->orientation() ); QCoreApplication::postEvent( parentWidget(), copy ); } break; } default: ; } } return false; } bool VideoWidget::event( QEvent * event ) { switch ( event->type() ) { case QEvent::ToolTip: // "eat" the help events (= tooltips), avoid parent widgets receiving them event->accept(); return true; break; default: ; } return QWidget::event( event ); } void VideoWidget::resizeEvent( QResizeEvent * event ) { const QSize &s = event->size(); int usedSpace = d->seekSlider->geometry().left() + d->seekSlider->iconSize().width(); // try to give the slider at least 30px of space if ( s.width() < ( usedSpace + 30 ) ) { d->seekSliderAction->setVisible( false ); d->seekSliderMenuAction->setVisible( true ); } else { d->seekSliderAction->setVisible( true ); d->seekSliderMenuAction->setVisible( false ); } } #include "moc_videowidget.cpp"