diff --git a/3rdparty_licenses/libjpeg_README.txt b/3rdparty_licenses/libjpeg_README.txt index 80b455a..13b245c 100644 --- a/3rdparty_licenses/libjpeg_README.txt +++ b/3rdparty_licenses/libjpeg_README.txt @@ -1,371 +1,371 @@ -The Independent JPEG Group's JPEG software -========================================== - -README for release 9d of 12-Jan-2020 -==================================== - -This distribution contains the ninth public release of the Independent JPEG -Group's free JPEG software. You are welcome to redistribute this software and -to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. - -This software is the work of Tom Lane, Guido Vollbeding, Philip Gladstone, -Bill Allombert, Jim Boucher, Lee Crocker, Bob Friesenhahn, Ben Jackson, -John Korejwa, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, -Ge' Weijers, and other members of the Independent JPEG Group. - -IJG is not affiliated with the ISO/IEC JTC1/SC29/WG1 standards committee -(previously known as JPEG, together with ITU-T SG16). - - -DOCUMENTATION ROADMAP -===================== - -This file contains the following sections: - -OVERVIEW General description of JPEG and the IJG software. -LEGAL ISSUES Copyright, lack of warranty, terms of distribution. -REFERENCES Where to learn more about JPEG. -ARCHIVE LOCATIONS Where to find newer versions of this software. -ACKNOWLEDGMENTS Special thanks. -FILE FORMAT WARS Software *not* to get. -TO DO Plans for future IJG releases. - -Other documentation files in the distribution are: - -User documentation: - install.txt How to configure and install the IJG software. - usage.txt Usage instructions for cjpeg, djpeg, jpegtran, - rdjpgcom, and wrjpgcom. - *.1 Unix-style man pages for programs (same info as usage.txt). - wizard.txt Advanced usage instructions for JPEG wizards only. - change.log Version-to-version change highlights. -Programmer and internal documentation: - libjpeg.txt How to use the JPEG library in your own programs. - example.c Sample code for calling the JPEG library. - structure.txt Overview of the JPEG library's internal structure. - filelist.txt Road map of IJG files. - coderules.txt Coding style rules --- please read if you contribute code. - -Please read at least the files install.txt and usage.txt. Some information -can also be found in the JPEG FAQ (Frequently Asked Questions) article. See -ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. - -If you want to understand how the JPEG code works, we suggest reading one or -more of the REFERENCES, then looking at the documentation files (in roughly -the order listed) before diving into the code. - - -OVERVIEW -======== - -This package contains C software to implement JPEG image encoding, decoding, -and transcoding. JPEG (pronounced "jay-peg") is a standardized compression -method for full-color and grayscale images. - -This software implements JPEG baseline, extended-sequential, and progressive -compression processes. Provision is made for supporting all variants of these -processes, although some uncommon parameter settings aren't implemented yet. -We have made no provision for supporting the hierarchical or lossless -processes defined in the standard. - -We provide a set of library routines for reading and writing JPEG image files, -plus two sample applications "cjpeg" and "djpeg", which use the library to -perform conversion between JPEG and some other popular image file formats. -The library is intended to be reused in other applications. - -In order to support file conversion and viewing software, we have included -considerable functionality beyond the bare JPEG coding/decoding capability; -for example, the color quantization modules are not strictly part of JPEG -decoding, but they are essential for output to colormapped file formats or -colormapped displays. These extra functions can be compiled out of the -library if not required for a particular application. - -We have also included "jpegtran", a utility for lossless transcoding between -different JPEG processes, and "rdjpgcom" and "wrjpgcom", two simple -applications for inserting and extracting textual comments in JFIF files. - -The emphasis in designing this software has been on achieving portability and -flexibility, while also making it fast enough to be useful. In particular, -the software is not intended to be read as a tutorial on JPEG. (See the -REFERENCES section for introductory material.) Rather, it is intended to -be reliable, portable, industrial-strength code. We do not claim to have -achieved that goal in every aspect of the software, but we strive for it. - -We welcome the use of this software as a component of commercial products. -No royalty is required, but we do ask for an acknowledgement in product -documentation, as described under LEGAL ISSUES. - - -LEGAL ISSUES -============ - -In plain English: - -1. We don't promise that this software works. (But if you find any bugs, - please let us know!) -2. You can use this software for whatever you want. You don't have to pay us. -3. You may not pretend that you wrote this software. If you use it in a - program, you must acknowledge somewhere in your documentation that - you've used the IJG code. - -In legalese: - -The authors make NO WARRANTY or representation, either express or implied, -with respect to this software, its quality, accuracy, merchantability, or -fitness for a particular purpose. This software is provided "AS IS", and you, -its user, assume the entire risk as to its quality and accuracy. - -This software is copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding. -All Rights Reserved except as specified below. - -Permission is hereby granted to use, copy, modify, and distribute this -software (or portions thereof) for any purpose, without fee, subject to these -conditions: -(1) If any part of the source code for this software is distributed, then this -README file must be included, with this copyright and no-warranty notice -unaltered; and any additions, deletions, or changes to the original files -must be clearly indicated in accompanying documentation. -(2) If only executable code is distributed, then the accompanying -documentation must state that "this software is based in part on the work of -the Independent JPEG Group". -(3) Permission for use of this software is granted only if the user accepts -full responsibility for any undesirable consequences; the authors accept -NO LIABILITY for damages of any kind. - -These conditions apply to any software derived from or based on the IJG code, -not just to the unmodified library. If you use our work, you ought to -acknowledge us. - -Permission is NOT granted for the use of any IJG author's name or company name -in advertising or publicity relating to this software or products derived from -it. This software may be referred to only as "the Independent JPEG Group's -software". - -We specifically permit and encourage the use of this software as the basis of -commercial products, provided that all warranty or liability claims are -assumed by the product vendor. - - -The Unix configuration script "configure" was produced with GNU Autoconf. -It is copyright by the Free Software Foundation but is freely distributable. -The same holds for its supporting scripts (config.guess, config.sub, -ltmain.sh). Another support script, install-sh, is copyright by X Consortium -but is also freely distributable. - - -REFERENCES -========== - -We recommend reading one or more of these references before trying to -understand the innards of the JPEG software. - -The best short technical introduction to the JPEG compression algorithm is - Wallace, Gregory K. "The JPEG Still Picture Compression Standard", - Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. -(Adjacent articles in that issue discuss MPEG motion picture compression, -applications of JPEG, and related topics.) If you don't have the CACM issue -handy, a PDF file containing a revised version of Wallace's article is -available at http://www.ijg.org/files/Wallace.JPEG.pdf. The file (actually -a preprint for an article that appeared in IEEE Trans. Consumer Electronics) -omits the sample images that appeared in CACM, but it includes corrections -and some added material. Note: the Wallace article is copyright ACM and IEEE, -and it may not be used for commercial purposes. - -A somewhat less technical, more leisurely introduction to JPEG can be found in -"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by -M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides -good explanations and example C code for a multitude of compression methods -including JPEG. It is an excellent source if you are comfortable reading C -code but don't know much about data compression in general. The book's JPEG -sample code is far from industrial-strength, but when you are ready to look -at a full implementation, you've got one here... - -The best currently available description of JPEG is the textbook "JPEG Still -Image Data Compression Standard" by William B. Pennebaker and Joan L. -Mitchell, published by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. -Price US$59.95, 638 pp. The book includes the complete text of the ISO JPEG -standards (DIS 10918-1 and draft DIS 10918-2). -Although this is by far the most detailed and comprehensive exposition of -JPEG publicly available, we point out that it is still missing an explanation -of the most essential properties and algorithms of the underlying DCT -technology. -If you think that you know about DCT-based JPEG after reading this book, -then you are in delusion. The real fundamentals and corresponding potential -of DCT-based JPEG are not publicly known so far, and that is the reason for -all the mistaken developments taking place in the image coding domain. - -The original JPEG standard is divided into two parts, Part 1 being the actual -specification, while Part 2 covers compliance testing methods. Part 1 is -titled "Digital Compression and Coding of Continuous-tone Still Images, -Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS -10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of -Continuous-tone Still Images, Part 2: Compliance testing" and has document -numbers ISO/IEC IS 10918-2, ITU-T T.83. -IJG JPEG 8 introduced an implementation of the JPEG SmartScale extension -which is specified in two documents: A contributed document at ITU and ISO -with title "ITU-T JPEG-Plus Proposal for Extending ITU-T T.81 for Advanced -Image Coding", April 2006, Geneva, Switzerland. The latest version of this -document is Revision 3. And a contributed document ISO/IEC JTC1/SC29/WG1 N -5799 with title "Evolution of JPEG", June/July 2011, Berlin, Germany. -IJG JPEG 9 introduces a reversible color transform for improved lossless -compression which is described in a contributed document ISO/IEC JTC1/SC29/ -WG1 N 6080 with title "JPEG 9 Lossless Coding", June/July 2012, Paris, -France. - -The JPEG standard does not specify all details of an interchangeable file -format. For the omitted details we follow the "JFIF" conventions, version 2. -JFIF version 1 has been adopted as Recommendation ITU-T T.871 (05/2011) : -Information technology - Digital compression and coding of continuous-tone -still images: JPEG File Interchange Format (JFIF). It is available as a -free download in PDF file format from http://www.itu.int/rec/T-REC-T.871. -A PDF file of the older JFIF document is available at -http://www.w3.org/Graphics/JPEG/jfif3.pdf. - -The TIFF 6.0 file format specification can be obtained by FTP from -ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme -found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. -IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). -Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 -(Compression tag 7). Copies of this Note can be obtained from -http://www.ijg.org/files/. It is expected that the next revision -of the TIFF spec will replace the 6.0 JPEG design with the Note's design. -Although IJG's own code does not support TIFF/JPEG, the free libtiff library -uses our library to implement TIFF/JPEG per the Note. - - -ARCHIVE LOCATIONS -================= - -The "official" archive site for this software is www.ijg.org. -The most recent released version can always be found there in -directory "files". This particular version will be archived as -http://www.ijg.org/files/jpegsrc.v9d.tar.gz, and in Windows-compatible -"zip" archive format as http://www.ijg.org/files/jpegsr9d.zip. - -The JPEG FAQ (Frequently Asked Questions) article is a source of some -general information about JPEG. -It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ -and other news.answers archive sites, including the official news.answers -archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. -If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu -with body - send usenet/news.answers/jpeg-faq/part1 - send usenet/news.answers/jpeg-faq/part2 - - -ACKNOWLEDGMENTS -=============== - -Thank to Juergen Bruder for providing me with a copy of the common DCT -algorithm article, only to find out that I had come to the same result -in a more direct and comprehensible way with a more generative approach. - -Thank to Istvan Sebestyen and Joan L. Mitchell for inviting me to the -ITU JPEG (Study Group 16) meeting in Geneva, Switzerland. - -Thank to Thomas Wiegand and Gary Sullivan for inviting me to the -Joint Video Team (MPEG & ITU) meeting in Geneva, Switzerland. - -Thank to Thomas Richter and Daniel Lee for inviting me to the -ISO/IEC JTC1/SC29/WG1 (previously known as JPEG, together with ITU-T SG16) -meeting in Berlin, Germany. - -Thank to John Korejwa and Massimo Ballerini for inviting me to -fruitful consultations in Boston, MA and Milan, Italy. - -Thank to Hendrik Elstner, Roland Fassauer, Simone Zuck, Guenther -Maier-Gerber, Walter Stoeber, Fred Schmitz, and Norbert Braunagel -for corresponding business development. - -Thank to Nico Zschach and Dirk Stelling of the technical support team -at the Digital Images company in Halle for providing me with extra -equipment for configuration tests. - -Thank to Richard F. Lyon (then of Foveon Inc.) for fruitful -communication about JPEG configuration in Sigma Photo Pro software. - -Thank to Andrew Finkenstadt for hosting the ijg.org site. - -Thank to Thomas G. Lane for the original design and development of -this singular software package. - -Thank to Lars Goehler, Andreas Heinecke, Sebastian Fuss, Yvonne Roebert, -Andrej Werner, and Ulf-Dietrich Braumann for support and public relations. - - -FILE FORMAT WARS -================ - -The ISO/IEC JTC1/SC29/WG1 standards committee (previously known as JPEG, -together with ITU-T SG16) currently promotes different formats containing -the name "JPEG" which is misleading because these formats are incompatible -with original DCT-based JPEG and are based on faulty technologies. -IJG therefore does not and will not support such momentary mistakes -(see REFERENCES). -There exist also distributions under the name "OpenJPEG" promoting such -kind of formats which is misleading because they don't support original -JPEG images. -We have no sympathy for the promotion of inferior formats. Indeed, one of -the original reasons for developing this free software was to help force -convergence on common, interoperable format standards for JPEG files. -Don't use an incompatible file format! -(In any case, our decoder will remain capable of reading existing JPEG -image files indefinitely.) - -The ISO committee pretends to be "responsible for the popular JPEG" in their -public reports which is not true because they don't respond to actual -requirements for the maintenance of the original JPEG specification. -Furthermore, the ISO committee pretends to "ensure interoperability" with -their standards which is not true because their "standards" support only -application-specific and proprietary use cases and contain mathematically -incorrect code. - -There are currently different distributions in circulation containing the -name "libjpeg" which is misleading because they don't have the features and -are incompatible with formats supported by actual IJG libjpeg distributions. -One of those fakes is released by members of the ISO committee and just uses -the name of libjpeg for misdirection of people, similar to the abuse of the -name JPEG as described above, while having nothing in common with actual IJG -libjpeg distributions and containing mathematically incorrect code. -The other one claims to be a "derivative" or "fork" of the original libjpeg, -but violates the license conditions as described under LEGAL ISSUES above -and violates basic C programming properties. -We have no sympathy for the release of misleading, incorrect and illegal -distributions derived from obsolete code bases. -Don't use an obsolete code base! - -According to the UCC (Uniform Commercial Code) law, IJG has the lawful and -legal right to foreclose on certain standardization bodies and other -institutions or corporations that knowingly perform substantial and -systematic deceptive acts and practices, fraud, theft, and damaging of the -value of the people of this planet without their knowing, willing and -intentional consent. -The titles, ownership, and rights of these institutions and all their assets -are now duly secured and held in trust for the free people of this planet. -People of the planet, on every country, may have a financial interest in -the assets of these former principals, agents, and beneficiaries of the -foreclosed institutions and corporations. -IJG asserts what is: that each man, woman, and child has unalienable value -and rights granted and deposited in them by the Creator and not any one of -the people is subordinate to any artificial principality, corporate fiction -or the special interest of another without their appropriate knowing, -willing and intentional consent made by contract or accommodation agreement. -IJG expresses that which already was. -The people have already determined and demanded that public administration -entities, national governments, and their supporting judicial systems must -be fully transparent, accountable, and liable. -IJG has secured the value for all concerned free people of the planet. - -A partial list of foreclosed institutions and corporations ("Hall of Shame") -is currently prepared and will be published later. - - -TO DO -===== - -Version 9 is the second release of a new generation JPEG standard -to overcome the limitations of the original JPEG specification, -and is the first true source reference JPEG codec. -More features are being prepared for coming releases... - -Please send bug reports, offers of help, etc. to jpeg-info@jpegclub.org. +The Independent JPEG Group's JPEG software +========================================== + +README for release 9d of 12-Jan-2020 +==================================== + +This distribution contains the ninth public release of the Independent JPEG +Group's free JPEG software. You are welcome to redistribute this software and +to use it for any purpose, subject to the conditions under LEGAL ISSUES, below. + +This software is the work of Tom Lane, Guido Vollbeding, Philip Gladstone, +Bill Allombert, Jim Boucher, Lee Crocker, Bob Friesenhahn, Ben Jackson, +John Korejwa, Julian Minguillon, Luis Ortiz, George Phillips, Davide Rossi, +Ge' Weijers, and other members of the Independent JPEG Group. + +IJG is not affiliated with the ISO/IEC JTC1/SC29/WG1 standards committee +(previously known as JPEG, together with ITU-T SG16). + + +DOCUMENTATION ROADMAP +===================== + +This file contains the following sections: + +OVERVIEW General description of JPEG and the IJG software. +LEGAL ISSUES Copyright, lack of warranty, terms of distribution. +REFERENCES Where to learn more about JPEG. +ARCHIVE LOCATIONS Where to find newer versions of this software. +ACKNOWLEDGMENTS Special thanks. +FILE FORMAT WARS Software *not* to get. +TO DO Plans for future IJG releases. + +Other documentation files in the distribution are: + +User documentation: + install.txt How to configure and install the IJG software. + usage.txt Usage instructions for cjpeg, djpeg, jpegtran, + rdjpgcom, and wrjpgcom. + *.1 Unix-style man pages for programs (same info as usage.txt). + wizard.txt Advanced usage instructions for JPEG wizards only. + change.log Version-to-version change highlights. +Programmer and internal documentation: + libjpeg.txt How to use the JPEG library in your own programs. + example.c Sample code for calling the JPEG library. + structure.txt Overview of the JPEG library's internal structure. + filelist.txt Road map of IJG files. + coderules.txt Coding style rules --- please read if you contribute code. + +Please read at least the files install.txt and usage.txt. Some information +can also be found in the JPEG FAQ (Frequently Asked Questions) article. See +ARCHIVE LOCATIONS below to find out where to obtain the FAQ article. + +If you want to understand how the JPEG code works, we suggest reading one or +more of the REFERENCES, then looking at the documentation files (in roughly +the order listed) before diving into the code. + + +OVERVIEW +======== + +This package contains C software to implement JPEG image encoding, decoding, +and transcoding. JPEG (pronounced "jay-peg") is a standardized compression +method for full-color and grayscale images. + +This software implements JPEG baseline, extended-sequential, and progressive +compression processes. Provision is made for supporting all variants of these +processes, although some uncommon parameter settings aren't implemented yet. +We have made no provision for supporting the hierarchical or lossless +processes defined in the standard. + +We provide a set of library routines for reading and writing JPEG image files, +plus two sample applications "cjpeg" and "djpeg", which use the library to +perform conversion between JPEG and some other popular image file formats. +The library is intended to be reused in other applications. + +In order to support file conversion and viewing software, we have included +considerable functionality beyond the bare JPEG coding/decoding capability; +for example, the color quantization modules are not strictly part of JPEG +decoding, but they are essential for output to colormapped file formats or +colormapped displays. These extra functions can be compiled out of the +library if not required for a particular application. + +We have also included "jpegtran", a utility for lossless transcoding between +different JPEG processes, and "rdjpgcom" and "wrjpgcom", two simple +applications for inserting and extracting textual comments in JFIF files. + +The emphasis in designing this software has been on achieving portability and +flexibility, while also making it fast enough to be useful. In particular, +the software is not intended to be read as a tutorial on JPEG. (See the +REFERENCES section for introductory material.) Rather, it is intended to +be reliable, portable, industrial-strength code. We do not claim to have +achieved that goal in every aspect of the software, but we strive for it. + +We welcome the use of this software as a component of commercial products. +No royalty is required, but we do ask for an acknowledgement in product +documentation, as described under LEGAL ISSUES. + + +LEGAL ISSUES +============ + +In plain English: + +1. We don't promise that this software works. (But if you find any bugs, + please let us know!) +2. You can use this software for whatever you want. You don't have to pay us. +3. You may not pretend that you wrote this software. If you use it in a + program, you must acknowledge somewhere in your documentation that + you've used the IJG code. + +In legalese: + +The authors make NO WARRANTY or representation, either express or implied, +with respect to this software, its quality, accuracy, merchantability, or +fitness for a particular purpose. This software is provided "AS IS", and you, +its user, assume the entire risk as to its quality and accuracy. + +This software is copyright (C) 1991-2020, Thomas G. Lane, Guido Vollbeding. +All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this +software (or portions thereof) for any purpose, without fee, subject to these +conditions: +(1) If any part of the source code for this software is distributed, then this +README file must be included, with this copyright and no-warranty notice +unaltered; and any additions, deletions, or changes to the original files +must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying +documentation must state that "this software is based in part on the work of +the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts +full responsibility for any undesirable consequences; the authors accept +NO LIABILITY for damages of any kind. + +These conditions apply to any software derived from or based on the IJG code, +not just to the unmodified library. If you use our work, you ought to +acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name +in advertising or publicity relating to this software or products derived from +it. This software may be referred to only as "the Independent JPEG Group's +software". + +We specifically permit and encourage the use of this software as the basis of +commercial products, provided that all warranty or liability claims are +assumed by the product vendor. + + +The Unix configuration script "configure" was produced with GNU Autoconf. +It is copyright by the Free Software Foundation but is freely distributable. +The same holds for its supporting scripts (config.guess, config.sub, +ltmain.sh). Another support script, install-sh, is copyright by X Consortium +but is also freely distributable. + + +REFERENCES +========== + +We recommend reading one or more of these references before trying to +understand the innards of the JPEG software. + +The best short technical introduction to the JPEG compression algorithm is + Wallace, Gregory K. "The JPEG Still Picture Compression Standard", + Communications of the ACM, April 1991 (vol. 34 no. 4), pp. 30-44. +(Adjacent articles in that issue discuss MPEG motion picture compression, +applications of JPEG, and related topics.) If you don't have the CACM issue +handy, a PDF file containing a revised version of Wallace's article is +available at http://www.ijg.org/files/Wallace.JPEG.pdf. The file (actually +a preprint for an article that appeared in IEEE Trans. Consumer Electronics) +omits the sample images that appeared in CACM, but it includes corrections +and some added material. Note: the Wallace article is copyright ACM and IEEE, +and it may not be used for commercial purposes. + +A somewhat less technical, more leisurely introduction to JPEG can be found in +"The Data Compression Book" by Mark Nelson and Jean-loup Gailly, published by +M&T Books (New York), 2nd ed. 1996, ISBN 1-55851-434-1. This book provides +good explanations and example C code for a multitude of compression methods +including JPEG. It is an excellent source if you are comfortable reading C +code but don't know much about data compression in general. The book's JPEG +sample code is far from industrial-strength, but when you are ready to look +at a full implementation, you've got one here... + +The best currently available description of JPEG is the textbook "JPEG Still +Image Data Compression Standard" by William B. Pennebaker and Joan L. +Mitchell, published by Van Nostrand Reinhold, 1993, ISBN 0-442-01272-1. +Price US$59.95, 638 pp. The book includes the complete text of the ISO JPEG +standards (DIS 10918-1 and draft DIS 10918-2). +Although this is by far the most detailed and comprehensive exposition of +JPEG publicly available, we point out that it is still missing an explanation +of the most essential properties and algorithms of the underlying DCT +technology. +If you think that you know about DCT-based JPEG after reading this book, +then you are in delusion. The real fundamentals and corresponding potential +of DCT-based JPEG are not publicly known so far, and that is the reason for +all the mistaken developments taking place in the image coding domain. + +The original JPEG standard is divided into two parts, Part 1 being the actual +specification, while Part 2 covers compliance testing methods. Part 1 is +titled "Digital Compression and Coding of Continuous-tone Still Images, +Part 1: Requirements and guidelines" and has document numbers ISO/IEC IS +10918-1, ITU-T T.81. Part 2 is titled "Digital Compression and Coding of +Continuous-tone Still Images, Part 2: Compliance testing" and has document +numbers ISO/IEC IS 10918-2, ITU-T T.83. +IJG JPEG 8 introduced an implementation of the JPEG SmartScale extension +which is specified in two documents: A contributed document at ITU and ISO +with title "ITU-T JPEG-Plus Proposal for Extending ITU-T T.81 for Advanced +Image Coding", April 2006, Geneva, Switzerland. The latest version of this +document is Revision 3. And a contributed document ISO/IEC JTC1/SC29/WG1 N +5799 with title "Evolution of JPEG", June/July 2011, Berlin, Germany. +IJG JPEG 9 introduces a reversible color transform for improved lossless +compression which is described in a contributed document ISO/IEC JTC1/SC29/ +WG1 N 6080 with title "JPEG 9 Lossless Coding", June/July 2012, Paris, +France. + +The JPEG standard does not specify all details of an interchangeable file +format. For the omitted details we follow the "JFIF" conventions, version 2. +JFIF version 1 has been adopted as Recommendation ITU-T T.871 (05/2011) : +Information technology - Digital compression and coding of continuous-tone +still images: JPEG File Interchange Format (JFIF). It is available as a +free download in PDF file format from http://www.itu.int/rec/T-REC-T.871. +A PDF file of the older JFIF document is available at +http://www.w3.org/Graphics/JPEG/jfif3.pdf. + +The TIFF 6.0 file format specification can be obtained by FTP from +ftp://ftp.sgi.com/graphics/tiff/TIFF6.ps.gz. The JPEG incorporation scheme +found in the TIFF 6.0 spec of 3-June-92 has a number of serious problems. +IJG does not recommend use of the TIFF 6.0 design (TIFF Compression tag 6). +Instead, we recommend the JPEG design proposed by TIFF Technical Note #2 +(Compression tag 7). Copies of this Note can be obtained from +http://www.ijg.org/files/. It is expected that the next revision +of the TIFF spec will replace the 6.0 JPEG design with the Note's design. +Although IJG's own code does not support TIFF/JPEG, the free libtiff library +uses our library to implement TIFF/JPEG per the Note. + + +ARCHIVE LOCATIONS +================= + +The "official" archive site for this software is www.ijg.org. +The most recent released version can always be found there in +directory "files". This particular version will be archived as +http://www.ijg.org/files/jpegsrc.v9d.tar.gz, and in Windows-compatible +"zip" archive format as http://www.ijg.org/files/jpegsr9d.zip. + +The JPEG FAQ (Frequently Asked Questions) article is a source of some +general information about JPEG. +It is available on the World Wide Web at http://www.faqs.org/faqs/jpeg-faq/ +and other news.answers archive sites, including the official news.answers +archive at rtfm.mit.edu: ftp://rtfm.mit.edu/pub/usenet/news.answers/jpeg-faq/. +If you don't have Web or FTP access, send e-mail to mail-server@rtfm.mit.edu +with body + send usenet/news.answers/jpeg-faq/part1 + send usenet/news.answers/jpeg-faq/part2 + + +ACKNOWLEDGMENTS +=============== + +Thank to Juergen Bruder for providing me with a copy of the common DCT +algorithm article, only to find out that I had come to the same result +in a more direct and comprehensible way with a more generative approach. + +Thank to Istvan Sebestyen and Joan L. Mitchell for inviting me to the +ITU JPEG (Study Group 16) meeting in Geneva, Switzerland. + +Thank to Thomas Wiegand and Gary Sullivan for inviting me to the +Joint Video Team (MPEG & ITU) meeting in Geneva, Switzerland. + +Thank to Thomas Richter and Daniel Lee for inviting me to the +ISO/IEC JTC1/SC29/WG1 (previously known as JPEG, together with ITU-T SG16) +meeting in Berlin, Germany. + +Thank to John Korejwa and Massimo Ballerini for inviting me to +fruitful consultations in Boston, MA and Milan, Italy. + +Thank to Hendrik Elstner, Roland Fassauer, Simone Zuck, Guenther +Maier-Gerber, Walter Stoeber, Fred Schmitz, and Norbert Braunagel +for corresponding business development. + +Thank to Nico Zschach and Dirk Stelling of the technical support team +at the Digital Images company in Halle for providing me with extra +equipment for configuration tests. + +Thank to Richard F. Lyon (then of Foveon Inc.) for fruitful +communication about JPEG configuration in Sigma Photo Pro software. + +Thank to Andrew Finkenstadt for hosting the ijg.org site. + +Thank to Thomas G. Lane for the original design and development of +this singular software package. + +Thank to Lars Goehler, Andreas Heinecke, Sebastian Fuss, Yvonne Roebert, +Andrej Werner, and Ulf-Dietrich Braumann for support and public relations. + + +FILE FORMAT WARS +================ + +The ISO/IEC JTC1/SC29/WG1 standards committee (previously known as JPEG, +together with ITU-T SG16) currently promotes different formats containing +the name "JPEG" which is misleading because these formats are incompatible +with original DCT-based JPEG and are based on faulty technologies. +IJG therefore does not and will not support such momentary mistakes +(see REFERENCES). +There exist also distributions under the name "OpenJPEG" promoting such +kind of formats which is misleading because they don't support original +JPEG images. +We have no sympathy for the promotion of inferior formats. Indeed, one of +the original reasons for developing this free software was to help force +convergence on common, interoperable format standards for JPEG files. +Don't use an incompatible file format! +(In any case, our decoder will remain capable of reading existing JPEG +image files indefinitely.) + +The ISO committee pretends to be "responsible for the popular JPEG" in their +public reports which is not true because they don't respond to actual +requirements for the maintenance of the original JPEG specification. +Furthermore, the ISO committee pretends to "ensure interoperability" with +their standards which is not true because their "standards" support only +application-specific and proprietary use cases and contain mathematically +incorrect code. + +There are currently different distributions in circulation containing the +name "libjpeg" which is misleading because they don't have the features and +are incompatible with formats supported by actual IJG libjpeg distributions. +One of those fakes is released by members of the ISO committee and just uses +the name of libjpeg for misdirection of people, similar to the abuse of the +name JPEG as described above, while having nothing in common with actual IJG +libjpeg distributions and containing mathematically incorrect code. +The other one claims to be a "derivative" or "fork" of the original libjpeg, +but violates the license conditions as described under LEGAL ISSUES above +and violates basic C programming properties. +We have no sympathy for the release of misleading, incorrect and illegal +distributions derived from obsolete code bases. +Don't use an obsolete code base! + +According to the UCC (Uniform Commercial Code) law, IJG has the lawful and +legal right to foreclose on certain standardization bodies and other +institutions or corporations that knowingly perform substantial and +systematic deceptive acts and practices, fraud, theft, and damaging of the +value of the people of this planet without their knowing, willing and +intentional consent. +The titles, ownership, and rights of these institutions and all their assets +are now duly secured and held in trust for the free people of this planet. +People of the planet, on every country, may have a financial interest in +the assets of these former principals, agents, and beneficiaries of the +foreclosed institutions and corporations. +IJG asserts what is: that each man, woman, and child has unalienable value +and rights granted and deposited in them by the Creator and not any one of +the people is subordinate to any artificial principality, corporate fiction +or the special interest of another without their appropriate knowing, +willing and intentional consent made by contract or accommodation agreement. +IJG expresses that which already was. +The people have already determined and demanded that public administration +entities, national governments, and their supporting judicial systems must +be fully transparent, accountable, and liable. +IJG has secured the value for all concerned free people of the planet. + +A partial list of foreclosed institutions and corporations ("Hall of Shame") +is currently prepared and will be published later. + + +TO DO +===== + +Version 9 is the second release of a new generation JPEG standard +to overcome the limitations of the original JPEG specification, +and is the first true source reference JPEG codec. +More features are being prepared for coming releases... + +Please send bug reports, offers of help, etc. to jpeg-info@jpegclub.org. diff --git a/CodeGenerator/CodeGenerator.pro b/CodeGenerator/CodeGenerator.pro index ab4875a..c7da918 100644 --- a/CodeGenerator/CodeGenerator.pro +++ b/CodeGenerator/CodeGenerator.pro @@ -1,53 +1,53 @@ -# Copyright (C) 2018-2021 Jakub Melka -# -# This file is part of PDF4QT. -# -# PDF4QT is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# with the written consent of the copyright owner, any later version. -# -# PDF4QT 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PDF4QT. If not, see . - -QT += core gui xml - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -CONFIG += c++11 - -# The following define makes your compiler emit warnings if you use -# any Qt feature that has been marked deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if it uses deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -QMAKE_CXXFLAGS += /std:c++latest /utf-8 - -DESTDIR = $$OUT_PWD/.. - -LIBS += -L$$OUT_PWD/.. - - -SOURCES += \ - codegenerator.cpp \ - main.cpp \ - generatormainwindow.cpp - -HEADERS += \ - codegenerator.h \ - generatormainwindow.h - -FORMS += \ - generatormainwindow.ui - +# Copyright (C) 2018-2021 Jakub Melka +# +# This file is part of PDF4QT. +# +# PDF4QT is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# with the written consent of the copyright owner, any later version. +# +# PDF4QT 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PDF4QT. If not, see . + +QT += core gui xml + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +QMAKE_CXXFLAGS += /std:c++latest /utf-8 + +DESTDIR = $$OUT_PWD/.. + +LIBS += -L$$OUT_PWD/.. + + +SOURCES += \ + codegenerator.cpp \ + main.cpp \ + generatormainwindow.cpp + +HEADERS += \ + codegenerator.h \ + generatormainwindow.h + +FORMS += \ + generatormainwindow.ui + diff --git a/CodeGenerator/codegenerator.cpp b/CodeGenerator/codegenerator.cpp index ec3e905..5e2ebe9 100644 --- a/CodeGenerator/codegenerator.cpp +++ b/CodeGenerator/codegenerator.cpp @@ -1,1402 +1,1402 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "codegenerator.h" - -#include -#include -#include -#include -#include - -namespace codegen -{ - -GeneratedCodeStorage::GeneratedCodeStorage(QObject* parent) : - BaseClass(parent) -{ - -} - -QObjectList GeneratedCodeStorage::getFunctions() const -{ - return m_functions; -} - -void GeneratedCodeStorage::setFunctions(const QObjectList& functions) -{ - m_functions = functions; - - auto comparator = [](const QObject* left, const QObject* right) - { - const GeneratedFunction* leftFunction = qobject_cast(left); - const GeneratedFunction* rightFunction = qobject_cast(right); - return leftFunction->getFunctionName() < rightFunction->getFunctionName(); - }; - std::sort(m_functions.begin(), m_functions.end(), comparator); -} - -GeneratedFunction* GeneratedCodeStorage::addFunction(const QString& name) -{ - GeneratedFunction* function = new GeneratedFunction(this); - function->setFunctionName(name); - m_functions.append(function); - return function; -} - -GeneratedFunction* GeneratedCodeStorage::addFunction(GeneratedFunction* function) -{ - function->setParent(this); - m_functions.append(function); - return function; -} - -void GeneratedCodeStorage::removeFunction(GeneratedFunction* function) -{ - function->deleteLater(); - m_functions.removeOne(function); -} - -void GeneratedCodeStorage::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const -{ - stream << Qt::endl << Qt::endl; - - for (const QObject* object : m_functions) - { - const GeneratedFunction* generatedFunction = qobject_cast(object); - generatedFunction->generateCode(stream, parameters); - stream << Qt::endl << Qt::endl; - } -} - -QObject* Serializer::load(const QDomElement& element, QObject* parent) -{ - QString className = element.attribute("class"); - const int metaTypeId = QMetaType::type(className.toLatin1()); - const QMetaObject* metaObject = QMetaType::metaObjectForType(metaTypeId); - - if (metaObject) - { - QObject* deserializedObject = metaObject->newInstance(Q_ARG(QObject*, parent)); - - const int propertyCount = metaObject->propertyCount(); - for (int i = 0; i < propertyCount; ++i) - { - QMetaProperty property = metaObject->property(i); - if (property.isWritable()) - { - // Find, if property was serialized - QDomElement propertyElement; - QDomNodeList children = element.childNodes(); - for (int i = 0; i < children.count(); ++i) - { - QDomNode child = children.item(i); - if (child.isElement()) - { - QDomElement childElement = child.toElement(); - QString attributeName = childElement.attribute("name"); - if (attributeName == property.name()) - { - propertyElement = child.toElement(); - break; - } - } - } - - if (!propertyElement.isNull()) - { - // Deserialize the element - if (property.isEnumType()) - { - QMetaEnum metaEnum = property.enumerator(); - Q_ASSERT(metaEnum.isValid()); - property.write(deserializedObject, metaEnum.keyToValue(propertyElement.text().toLatin1().data())); - } - else if (property.userType() == qMetaTypeId()) - { - QObjectList objectList; - QDomNodeList children = propertyElement.childNodes(); - for (int i = 0; i < children.count(); ++i) - { - QDomNode node = children.item(i); - if (node.isElement()) - { - QDomElement element = node.toElement(); - if (QObject* object = Serializer::load(element, deserializedObject)) - { - objectList.append(object); - } - } - } - - property.write(deserializedObject, QVariant::fromValue(qMove(objectList))); - } - else - { - QVariant value = propertyElement.text(); - property.write(deserializedObject, value); - } - } - } - } - - return deserializedObject; - } - - return nullptr; -} - -void Serializer::store(QObject* object, QDomElement& element) -{ - Q_ASSERT(object); - - const QMetaObject* metaObject = object->metaObject(); - element.setAttribute("class", QString(metaObject->className())); - - const int propertyCount = metaObject->propertyCount(); - if (propertyCount > 0) - { - for (int i = 0; i < propertyCount; ++i) - { - QMetaProperty property = metaObject->property(i); - if (property.isReadable()) - { - QDomElement propertyElement = element.ownerDocument().createElement("property"); - element.appendChild(propertyElement); - propertyElement.setAttribute("name", property.name()); - - QVariant value = property.read(object); - if (property.isEnumType()) - { - QMetaEnum metaEnum = property.enumerator(); - Q_ASSERT(metaEnum.isValid()); - propertyElement.appendChild(propertyElement.ownerDocument().createTextNode(metaEnum.valueToKey(value.toInt()))); - } - else if (value.canConvert()) - { - QObjectList objectList = value.value(); - for (QObject* currentObject : objectList) - { - QDomElement objectElement = element.ownerDocument().createElement("QObject"); - propertyElement.appendChild(objectElement); - Serializer::store(currentObject, objectElement); - } - } - else - { - propertyElement.appendChild(propertyElement.ownerDocument().createTextNode(value.toString())); - } - } - } - } -} - -QObject* Serializer::clone(QObject* object, QObject* parent) -{ - QDomDocument document; - QDomElement rootElement = document.createElement("root"); - document.appendChild(rootElement); - Serializer::store(object, rootElement); - return Serializer::load(rootElement, parent); -} - -CodeGenerator::CodeGenerator(QObject* parent) : - BaseClass(parent) -{ - qRegisterMetaType("codegen::GeneratedCodeStorage"); - qRegisterMetaType("codegen::GeneratedFunction"); - qRegisterMetaType("codegen::GeneratedBase"); - qRegisterMetaType("codegen::GeneratedParameter"); - qRegisterMetaType("codegen::GeneratedPDFObject"); - qRegisterMetaType("codegen::GeneratedAction"); - qRegisterMetaType("QObjectList"); -} - -void CodeGenerator::initialize() -{ - m_storage = new GeneratedCodeStorage(this); -} - -void CodeGenerator::load(const QDomDocument& document) -{ - delete m_storage; - m_storage = nullptr; - - m_storage = qobject_cast(Serializer::load(document.firstChildElement("root"), this)); -} - -void CodeGenerator::store(QDomDocument& document) -{ - if (m_storage) - { - QDomElement rootElement = document.createElement("root"); - document.appendChild(rootElement); - Serializer::store(m_storage, rootElement); - } -} - -void CodeGenerator::generateCode(QString headerName, QString sourceName) const -{ - QString startMark = "/* START GENERATED CODE */"; - QString endMark = "/* END GENERATED CODE */"; - QString className = "PDFDocumentBuilder"; - const int indent = 4; - - QFile headerFile(headerName); - if (headerFile.exists()) - { - if (headerFile.open(QFile::ReadOnly | QFile::Text)) - { - QString utfCode = QString::fromUtf8(headerFile.readAll()); - headerFile.close(); - - int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); - int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); - - QString frontPart = utfCode.left(startIndex); - QString backPart = utfCode.mid(endIndex); - QString headerGeneratedCode = generateHeader(indent); - QString allCode = frontPart + headerGeneratedCode + backPart; - - headerFile.open(QFile::WriteOnly | QFile::Truncate); - headerFile.write(allCode.toUtf8()); - headerFile.close(); - } - } - - QFile sourceFile(sourceName); - if (sourceFile.exists()) - { - if (sourceFile.open(QFile::ReadOnly | QFile::Text)) - { - QString utfCode = QString::fromUtf8(sourceFile.readAll()); - sourceFile.close(); - - int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); - int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); - - QString frontPart = utfCode.left(startIndex); - QString backPart = utfCode.mid(endIndex); - QString sourceGeneratedCode = generateSource(className, indent); - QString allCode = frontPart + sourceGeneratedCode + backPart; - - sourceFile.open(QFile::WriteOnly | QFile::Truncate); - sourceFile.write(allCode.toUtf8()); - sourceFile.close(); - } - } -} - -QString CodeGenerator::generateHeader(int indent) const -{ - QByteArray ba; - { - QTextStream stream(&ba, QIODevice::WriteOnly); - stream.setCodec("UTF-8"); - stream.setRealNumberPrecision(3); - stream.setRealNumberNotation(QTextStream::FixedNotation); - - CodeGeneratorParameters parameters; - parameters.header = true; - parameters.indent = indent; - m_storage->generateCode(stream, parameters); - } - - return QString::fromUtf8(ba); -} - -QString CodeGenerator::generateSource(QString className, int indent) const -{ - QByteArray ba; - { - QTextStream stream(&ba, QIODevice::WriteOnly); - stream.setCodec("UTF-8"); - stream.setRealNumberPrecision(3); - stream.setRealNumberNotation(QTextStream::FixedNotation); - - CodeGeneratorParameters parameters; - parameters.header = false; - parameters.indent = indent; - parameters.className = className; - m_storage->generateCode(stream, parameters); - } - - return QString::fromUtf8(ba); -} - -GeneratedFunction::GeneratedFunction(QObject* parent) : - BaseClass(parent) -{ - -} - -QString GeneratedFunction::getFunctionTypeString() const -{ - return Serializer::convertEnumToString(m_functionType); -} - -void GeneratedFunction::setFunctionTypeString(const QString& string) -{ - Serializer::convertStringToEnum(string, m_functionType); -} - -GeneratedFunction::FunctionType GeneratedFunction::getFunctionType() const -{ - return m_functionType; -} - -void GeneratedFunction::setFunctionType(const FunctionType& functionType) -{ - m_functionType = functionType; -} - -QString GeneratedFunction::getFunctionName() const -{ - return m_functionName; -} - -void GeneratedFunction::setFunctionName(const QString& functionName) -{ - m_functionName = functionName; -} - -QString GeneratedFunction::getFunctionDescription() const -{ - return m_functionDescription; -} - -void GeneratedFunction::setFunctionDescription(const QString& functionDescription) -{ - m_functionDescription = functionDescription; -} - -GeneratedFunction* GeneratedFunction::clone(QObject* parent) -{ - return qobject_cast(Serializer::clone(this, parent)); -} - -void GeneratedFunction::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const -{ - QStringList parameterCaptions; - QStringList parameterTexts; - std::function gatherParameters = [&](const GeneratedBase* object, Pass pass) - { - if (pass != Pass::Enter) - { - return; - } - - if (const GeneratedParameter* generatedParameter = qobject_cast(object)) - { - parameterCaptions << QString("%1 %2").arg(generatedParameter->getParameterName(), generatedParameter->getParameterDescription()); - parameterTexts << QString("%1 %2").arg(getCppType(generatedParameter->getParameterDataType()), generatedParameter->getParameterName()); - } - }; - applyFunctor(gatherParameters); - - if (parameterTexts.isEmpty()) - { - parameterTexts << ""; - } - - if (parameters.header) - { - // Generate header source code - - // Function comments - for (const QString& string : getFormattedTextWithLayout("/// ", "/// ", getFunctionDescription(), parameters.indent)) - { - stream << string << Qt::endl; - } - - // Function parameter comments - for (const QString& parameterCaption : parameterCaptions) - { - for (const QString& string : getFormattedTextWithLayout("/// \\param ", "/// ", parameterCaption, parameters.indent)) - { - stream << string << Qt::endl; - } - } - - // Function declaration - QString functionHeader = QString("%1 %2(").arg(getCppType(getReturnType()), getFunctionName()); - QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); - QString functionFooter = QString(");"); - - for (QString& str : parameterTexts) - { - str += ","; - } - parameterTexts.back().replace(",", functionFooter); - - QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, parameters.indent); - for (const QString& functionDeclarationItem : functionDeclaration) - { - stream << functionDeclarationItem << Qt::endl; - } - } - else - { - // Generate c++ source code - QString functionHeader = QString("%1 %2::%3(").arg(getCppType(getReturnType()), parameters.className, getFunctionName()); - QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); - QString functionFooter = QString(")"); - - for (QString& str : parameterTexts) - { - str += ","; - } - parameterTexts.back().replace(",", functionFooter); - - QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, 0); - for (const QString& functionDeclarationItem : functionDeclaration) - { - stream << functionDeclarationItem << Qt::endl; - } - - QString indent(parameters.indent, QChar(QChar::Space)); - - stream << "{" << Qt::endl; - stream << indent << "PDFObjectFactory objectBuilder;" << Qt::endl << Qt::endl; - - generateSourceCode(stream, parameters); - - stream << "}" << Qt::endl; - } -} - -bool GeneratedFunction::hasField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - case FieldType::ItemType: - case FieldType::DataType: - case FieldType::Description: - return true; - - case FieldType::Value: - return false; - - default: - break; - } - - return false; -} - -QVariant GeneratedFunction::readField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - return m_functionName; - case FieldType::ItemType: - return int(m_functionType); - case FieldType::DataType: - return int(m_returnType); - case FieldType::Description: - return m_functionDescription; - - case FieldType::Value: - default: - break; - } - - return QVariant(); -} - -void GeneratedFunction::writeField(GeneratedBase::FieldType fieldType, QVariant value) -{ - switch (fieldType) - { - case FieldType::Name: - m_functionName = value.toString(); - break; - case FieldType::ItemType: - m_functionType = static_cast(value.toInt()); - break; - case FieldType::DataType: - m_returnType = static_cast(value.toInt()); - break; - case FieldType::Description: - m_functionDescription = value.toString(); - break; - - case FieldType::Value: - default: - break; - } -} - -void GeneratedFunction::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) -{ - switch (fieldType) - { - case FieldType::ItemType: - Serializer::fillComboBox(comboBox, m_functionType); - break; - case FieldType::DataType: - Serializer::fillComboBox(comboBox, m_returnType); - break; - - default: - break; - } -} - -bool GeneratedFunction::canHaveSubitems() const -{ - return true; -} - -QStringList GeneratedFunction::getCaptions() const -{ - return QStringList() << QString("Function") << QString("%1 %2(...)").arg(Serializer::convertEnumToString(m_returnType), m_functionName); -} - -GeneratedBase* GeneratedFunction::appendItem() -{ - Q_ASSERT(canHaveSubitems()); - - GeneratedBase* newItem = new GeneratedAction(this); - addItem(newItem); - return newItem; -} - -GeneratedFunction::DataType GeneratedFunction::getReturnType() const -{ - return m_returnType; -} - -void GeneratedFunction::setReturnType(DataType returnType) -{ - m_returnType = returnType; -} - -GeneratedAction::GeneratedAction(QObject* parent) : - BaseClass(parent), - m_actionType(CreateObject) -{ - -} - -bool GeneratedAction::hasField(FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - return m_actionType == CreateObject; - case FieldType::ItemType: - return true; - case FieldType::DataType: - return hasField(FieldType::Name) && !m_variableName.isEmpty(); - case FieldType::Description: - return m_actionType == Code; - - default: - break; - } - - return false; -} - -QVariant GeneratedAction::readField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - return m_variableName; - case FieldType::ItemType: - return int(m_actionType); - case FieldType::DataType: - return int(m_variableType); - case FieldType::Description: - return m_code; - - default: - break; - } - - return QVariant(); -} - -void GeneratedAction::writeField(GeneratedBase::FieldType fieldType, QVariant value) -{ - switch (fieldType) - { - case FieldType::Name: - m_variableName = value.toString(); - break; - case FieldType::ItemType: - m_actionType = static_cast(value.toInt()); - break; - case FieldType::DataType: - m_variableType = static_cast(value.toInt()); - break; - case FieldType::Description: - m_code = value.toString(); - break; - - default: - break; - } -} - -void GeneratedAction::fillComboBox(QComboBox* comboBox, FieldType fieldType) -{ - switch (fieldType) - { - case FieldType::ItemType: - Serializer::fillComboBox(comboBox, m_actionType); - break; - case FieldType::DataType: - Serializer::fillComboBox(comboBox, m_variableType); - break; - - default: - break; - } -} - -bool GeneratedAction::canHaveSubitems() const -{ - switch (m_actionType) - { - case Parameters: - case CreateObject: - return true; - - default: - break; - } - - return false; -} - -QStringList GeneratedAction::getCaptions() const -{ - return QStringList() << QString("Action %1").arg(Serializer::convertEnumToString(m_actionType)) << (!m_variableName.isEmpty() ? QString("%1 %2").arg(Serializer::convertEnumToString(m_variableType), m_variableName) : QString()); -} - -GeneratedBase* GeneratedAction::appendItem() -{ - Q_ASSERT(canHaveSubitems()); - GeneratedBase* newItem = nullptr; - switch (m_actionType) - { - case Parameters: - newItem = new GeneratedParameter(this); - break; - - default: - newItem = new GeneratedPDFObject(this); - break; - } - - addItem(newItem); - return newItem; -} - -GeneratedAction::ActionType GeneratedAction::getActionType() const -{ - return m_actionType; -} - -void GeneratedAction::setActionType(ActionType actionType) -{ - m_actionType = actionType; - - if (!canHaveSubitems()) - { - clearItems(); - } -} - -QString GeneratedAction::getVariableName() const -{ - return m_variableName; -} - -void GeneratedAction::setVariableName(const QString& variableName) -{ - m_variableName = variableName; -} - -GeneratedAction::DataType GeneratedAction::getVariableType() const -{ - return m_variableType; -} - -void GeneratedAction::setVariableType(DataType variableType) -{ - m_variableType = variableType; -} - -QString GeneratedAction::getCode() const -{ - return m_code; -} - -void GeneratedAction::setCode(const QString& code) -{ - m_code = code; -} - -void GeneratedAction::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const -{ - if (pass == Pass::Enter && getActionType() == Code) - { - QString indent(parameters.indent, QChar(QChar::Space)); - QStringList lines = getCode().split(QChar('\n'), Qt::KeepEmptyParts, Qt::CaseInsensitive); - - for (QString string : lines) - { - string = string.trimmed(); - if (!string.isEmpty()) - { - stream << indent << string << Qt::endl; - } - else - { - stream << Qt::endl; - } - } - } - - if (pass == Pass::Leave && getActionType() == CreateObject) - { - QString indent(parameters.indent, QChar(QChar::Space)); - - switch (getVariableType()) - { - case _PDFObject: - { - stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = objectBuilder.takeObject();"; - break; - } - - case _PDFObjectReference: - { - stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = addObject(objectBuilder.takeObject());"; - break; - } - - default: - Q_ASSERT(false); - break; - } - } - - if (pass == Pass::Leave && getActionType() != Parameters && !parameters.isLastItem) - { - stream << Qt::endl; - } -} - -void GeneratedBase::generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const -{ - generateSourceCodeImpl(stream, parameters, Pass::Enter); - - for (const QObject* object : m_items) - { - const GeneratedBase* generatedBase = qobject_cast(object); - CodeGeneratorParameters parametersTemporary = parameters; - parametersTemporary.isFirstItem = object == m_items.front(); - parametersTemporary.isLastItem = object == m_items.back(); - generatedBase->generateSourceCode(stream, parametersTemporary); - } - - generateSourceCodeImpl(stream, parameters, Pass::Leave); -} - -void GeneratedBase::applyFunctor(std::function& functor) const -{ - functor(this, Pass::Enter); - - for (const QObject* object : m_items) - { - const GeneratedBase* generatedBase = qobject_cast(object); - generatedBase->applyFunctor(functor); - } - - functor(this, Pass::Leave); -} - -bool GeneratedBase::canPerformOperation(Operation operation) const -{ - const bool isFunction = qobject_cast(this) != nullptr; - switch (operation) - { - case Operation::Delete: - return !isFunction; - - case Operation::MoveUp: - { - if (const GeneratedBase* parentBase = getParent()) - { - QObjectList items = parentBase->getItems(); - return items.indexOf(const_cast(this)) > 0; - } - - break; - } - - case Operation::MoveDown: - { - if (const GeneratedBase* parentBase = getParent()) - { - QObjectList items = parentBase->getItems(); - return items.indexOf(const_cast(this)) < items.size() - 1; - } - - break; - } - - case Operation::NewSibling: - return !isFunction; - - case Operation::NewChild: - return canHaveSubitems(); - - default: - break; - } - - return false; -} - -void GeneratedBase::performOperation(GeneratedBase::Operation operation) -{ - switch (operation) - { - case Operation::Delete: - { - getParent()->removeItem(this); - break; - } - - case Operation::MoveUp: - { - GeneratedBase* parentItem = getParent(); - QObjectList items = parentItem->getItems(); - const int index = items.indexOf(this); - items.removeAll(const_cast(this)); - items.insert(index - 1, this); - parentItem->setItems(qMove(items)); - break; - } - - case Operation::MoveDown: - { - GeneratedBase* parentItem = getParent(); - QObjectList items = parentItem->getItems(); - const int index = items.indexOf(this); - items.removeAll(const_cast(this)); - items.insert(index + 1, this); - parentItem->setItems(qMove(items)); - break; - } - - case Operation::NewSibling: - { - GeneratedBase* parentItem = getParent(); - if (GeneratedBase* createdItem = parentItem->appendItem()) - { - QObjectList items = parentItem->getItems(); - items.removeAll(createdItem); - items.insert(items.indexOf(const_cast(this)) + 1, createdItem); - parentItem->setItems(qMove(items)); - } - break; - } - - case Operation::NewChild: - { - appendItem(); - break; - } - - default: - break; - } -} - -QObjectList GeneratedBase::getItems() const -{ - return m_items; -} - -void GeneratedBase::setItems(const QObjectList& items) -{ - m_items = items; -} - -void GeneratedBase::addItem(QObject* object) -{ - object->setParent(this); - m_items.append(object); -} - -void GeneratedBase::removeItem(QObject* object) -{ - object->deleteLater(); - m_items.removeAll(object); -} - -void GeneratedBase::clearItems() -{ - qDeleteAll(m_items); - m_items.clear(); -} - -void GeneratedBase::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const -{ - Q_UNUSED(stream); - Q_UNUSED(parameters); - Q_UNUSED(pass); -} - -QString GeneratedBase::getCppType(DataType type) const -{ - return Serializer::convertEnumToString(type).mid(1); -} - -QStringList GeneratedBase::getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const -{ - QFont font = QApplication::font(); - QFontMetrics fontMetrics(font); - - int usedLength = indent + qMax(firstPrefix.length(), prefix.length()); - int length = 80 - usedLength; - QString testText(length, QChar('A')); - int width = fontMetrics.width(testText); - - QTextLayout layout(text, font); - layout.setCacheEnabled(false); - QTextOption textOption = layout.textOption(); - textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - layout.setTextOption(textOption); - - layout.beginLayout(); - while (true) - { - QTextLine textLine = layout.createLine(); - if (!textLine.isValid()) - { - break; - } - - textLine.setLineWidth(width); - } - layout.endLayout(); - - QStringList texts; - const int lineCount = layout.lineCount(); - for (int i = 0; i < lineCount; ++i) - { - QTextLine line = layout.lineAt(i); - texts << text.mid(line.textStart(), line.textLength()); - } - return getFormattedTextBlock(firstPrefix, prefix, texts, indent); -} - -QStringList GeneratedBase::getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const -{ - QString indentText(indent, QChar(QChar::Space)); - firstPrefix.prepend(indentText); - prefix.prepend(indentText); - - QStringList result; - QString currentPrefix = firstPrefix; - for (const QString& text : texts) - { - result << QString("%1%2").arg(currentPrefix, text); - currentPrefix = prefix; - } - return result; -} - -GeneratedPDFObject::GeneratedPDFObject(QObject* parent) : - BaseClass(parent) -{ - -} - -bool GeneratedPDFObject::hasField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - return m_objectType == DictionaryItemSimple || m_objectType == DictionaryItemComplex; - case FieldType::ItemType: - return true; - case FieldType::Value: - return m_objectType == Object || m_objectType == ArraySimple || m_objectType == DictionaryItemSimple; - - default: - break; - } - - return false; -} - -QVariant GeneratedPDFObject::readField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - return m_dictionaryItemName; - case FieldType::ItemType: - return int(m_objectType); - case FieldType::Value: - return m_value; - - default: - break; - } - - return QVariant(); -} - -void GeneratedPDFObject::writeField(GeneratedBase::FieldType fieldType, QVariant value) -{ - switch (fieldType) - { - case FieldType::Name: - m_dictionaryItemName = value.toString(); - break; - case FieldType::ItemType: - m_objectType = static_cast(value.toInt()); - break; - case FieldType::Value: - m_value = value.toString(); - break; - - default: - break; - } -} - -void GeneratedPDFObject::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) -{ - switch (fieldType) - { - case FieldType::ItemType: - Serializer::fillComboBox(comboBox, m_objectType); - break; - - default: - break; - } -} - -bool GeneratedPDFObject::canHaveSubitems() const -{ - switch (m_objectType) - { - case ArrayComplex: - case Dictionary: - case DictionaryItemComplex: - return true; - - default: - break; - } - - return false; -} - -QStringList GeneratedPDFObject::getCaptions() const -{ - QString name; - switch (m_objectType) - { - case Object: - name = tr("Object"); - break; - case ArraySimple: - name = tr("Array (simple)"); - break; - case ArrayComplex: - name = tr("Array (complex)"); - break; - case Dictionary: - name = tr("Dictionary"); - break; - case DictionaryItemSimple: - name = tr("Item (simple), name = '%1'").arg(m_dictionaryItemName); - break; - case DictionaryItemComplex: - name = tr("Item (complex), name = '%1'").arg(m_dictionaryItemName); - break; - - default: - Q_ASSERT(false); - break; - } - - return QStringList() << name << m_value; -} - -GeneratedBase* GeneratedPDFObject::appendItem() -{ - Q_ASSERT(canHaveSubitems()); - GeneratedBase* newItem = new GeneratedPDFObject(this); - addItem(newItem); - return newItem; -} - -QString GeneratedPDFObject::getValue() const -{ - return m_value; -} - -void GeneratedPDFObject::setValue(const QString& value) -{ - m_value = value; -} - -GeneratedPDFObject::ObjectType GeneratedPDFObject::getObjectType() const -{ - return m_objectType; -} - -void GeneratedPDFObject::setObjectType(ObjectType objectType) -{ - m_objectType = objectType; - - if (!canHaveSubitems()) - { - clearItems(); - } -} - -QString GeneratedPDFObject::getDictionaryItemName() const -{ - return m_dictionaryItemName; -} - -void GeneratedPDFObject::setDictionaryItemName(const QString& dictionaryItemName) -{ - m_dictionaryItemName = dictionaryItemName; -} - -void GeneratedPDFObject::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const -{ - QString indent(parameters.indent, QChar(QChar::Space)); - QString writeTo("objectBuilder << "); - - switch (getObjectType()) - { - case codegen::GeneratedPDFObject::Object: - { - if (pass == Pass::Enter) - { - stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; - } - break; - } - - case codegen::GeneratedPDFObject::ArraySimple: - { - if (pass == Pass::Enter) - { - stream << indent << "objectBuilder.beginArray();" << Qt::endl; - for (const QString& arrayPart : getValue().trimmed().split(";")) - { - stream << indent << writeTo << arrayPart << ";" << Qt::endl; - } - stream << indent << "objectBuilder.endArray();" << Qt::endl; - } - break; - } - - case codegen::GeneratedPDFObject::ArrayComplex: - { - if (pass == Pass::Enter) - { - stream << indent << "objectBuilder.beginArray();" << Qt::endl; - } - if (pass == Pass::Leave) - { - stream << indent << "objectBuilder.endArray();" << Qt::endl; - } - break; - } - - case codegen::GeneratedPDFObject::Dictionary: - { - if (pass == Pass::Enter) - { - stream << indent << "objectBuilder.beginDictionary();" << Qt::endl; - } - if (pass == Pass::Leave) - { - stream << indent << "objectBuilder.endDictionary();" << Qt::endl; - } - break; - } - - case codegen::GeneratedPDFObject::DictionaryItemSimple: - { - if (pass == Pass::Enter) - { - stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; - stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; - stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; - } - break; - } - - case codegen::GeneratedPDFObject::DictionaryItemComplex: - { - if (pass == Pass::Enter) - { - stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; - } - if (pass == Pass::Leave) - { - stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; - } - break; - } - - default: - Q_ASSERT(false); - break; - } -} - -GeneratedParameter::GeneratedParameter(QObject* parent) : - BaseClass(parent) -{ - -} - -bool GeneratedParameter::hasField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - case FieldType::DataType: - case FieldType::Description: - return true; - - default: - break; - } - - return false; -} - -QVariant GeneratedParameter::readField(GeneratedBase::FieldType fieldType) const -{ - switch (fieldType) - { - case FieldType::Name: - return m_parameterName; - case FieldType::DataType: - return m_parameterDataType; - case FieldType::Description: - return m_parameterDescription; - - default: - break; - } - - return QVariant(); -} - -void GeneratedParameter::writeField(GeneratedBase::FieldType fieldType, QVariant value) -{ - switch (fieldType) - { - case FieldType::Name: - m_parameterName = value.toString(); - break; - case FieldType::DataType: - m_parameterDataType = static_cast(value.toInt()); - break; - case FieldType::Description: - m_parameterDescription = value.toString(); - break; - - default: - break; - } -} - -void GeneratedParameter::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) -{ - switch (fieldType) - { - case FieldType::DataType: - Serializer::fillComboBox(comboBox, m_parameterDataType); - break; - - default: - break; - } -} - -bool GeneratedParameter::canHaveSubitems() const -{ - return false; -} - -QStringList GeneratedParameter::getCaptions() const -{ - return QStringList() << QString("%1 %2").arg(Serializer::convertEnumToString(m_parameterDataType)).arg(m_parameterName) << m_parameterDescription; -} - -GeneratedBase* GeneratedParameter::appendItem() -{ - return nullptr; -} - -QString GeneratedParameter::getParameterName() const -{ - return m_parameterName; -} - -void GeneratedParameter::setParameterName(const QString& parameterName) -{ - m_parameterName = parameterName; -} - -GeneratedParameter::DataType GeneratedParameter::getParameterDataType() const -{ - return m_parameterDataType; -} - -void GeneratedParameter::setParameterDataType(const DataType& parameterDataType) -{ - m_parameterDataType = parameterDataType; -} - -QString GeneratedParameter::getParameterDescription() const -{ - return m_parameterDescription; -} - -void GeneratedParameter::setParameterDescription(const QString& parameterDescription) -{ - m_parameterDescription = parameterDescription; -} - -} +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "codegenerator.h" + +#include +#include +#include +#include +#include + +namespace codegen +{ + +GeneratedCodeStorage::GeneratedCodeStorage(QObject* parent) : + BaseClass(parent) +{ + +} + +QObjectList GeneratedCodeStorage::getFunctions() const +{ + return m_functions; +} + +void GeneratedCodeStorage::setFunctions(const QObjectList& functions) +{ + m_functions = functions; + + auto comparator = [](const QObject* left, const QObject* right) + { + const GeneratedFunction* leftFunction = qobject_cast(left); + const GeneratedFunction* rightFunction = qobject_cast(right); + return leftFunction->getFunctionName() < rightFunction->getFunctionName(); + }; + std::sort(m_functions.begin(), m_functions.end(), comparator); +} + +GeneratedFunction* GeneratedCodeStorage::addFunction(const QString& name) +{ + GeneratedFunction* function = new GeneratedFunction(this); + function->setFunctionName(name); + m_functions.append(function); + return function; +} + +GeneratedFunction* GeneratedCodeStorage::addFunction(GeneratedFunction* function) +{ + function->setParent(this); + m_functions.append(function); + return function; +} + +void GeneratedCodeStorage::removeFunction(GeneratedFunction* function) +{ + function->deleteLater(); + m_functions.removeOne(function); +} + +void GeneratedCodeStorage::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const +{ + stream << Qt::endl << Qt::endl; + + for (const QObject* object : m_functions) + { + const GeneratedFunction* generatedFunction = qobject_cast(object); + generatedFunction->generateCode(stream, parameters); + stream << Qt::endl << Qt::endl; + } +} + +QObject* Serializer::load(const QDomElement& element, QObject* parent) +{ + QString className = element.attribute("class"); + const int metaTypeId = QMetaType::type(className.toLatin1()); + const QMetaObject* metaObject = QMetaType::metaObjectForType(metaTypeId); + + if (metaObject) + { + QObject* deserializedObject = metaObject->newInstance(Q_ARG(QObject*, parent)); + + const int propertyCount = metaObject->propertyCount(); + for (int i = 0; i < propertyCount; ++i) + { + QMetaProperty property = metaObject->property(i); + if (property.isWritable()) + { + // Find, if property was serialized + QDomElement propertyElement; + QDomNodeList children = element.childNodes(); + for (int i = 0; i < children.count(); ++i) + { + QDomNode child = children.item(i); + if (child.isElement()) + { + QDomElement childElement = child.toElement(); + QString attributeName = childElement.attribute("name"); + if (attributeName == property.name()) + { + propertyElement = child.toElement(); + break; + } + } + } + + if (!propertyElement.isNull()) + { + // Deserialize the element + if (property.isEnumType()) + { + QMetaEnum metaEnum = property.enumerator(); + Q_ASSERT(metaEnum.isValid()); + property.write(deserializedObject, metaEnum.keyToValue(propertyElement.text().toLatin1().data())); + } + else if (property.userType() == qMetaTypeId()) + { + QObjectList objectList; + QDomNodeList children = propertyElement.childNodes(); + for (int i = 0; i < children.count(); ++i) + { + QDomNode node = children.item(i); + if (node.isElement()) + { + QDomElement element = node.toElement(); + if (QObject* object = Serializer::load(element, deserializedObject)) + { + objectList.append(object); + } + } + } + + property.write(deserializedObject, QVariant::fromValue(qMove(objectList))); + } + else + { + QVariant value = propertyElement.text(); + property.write(deserializedObject, value); + } + } + } + } + + return deserializedObject; + } + + return nullptr; +} + +void Serializer::store(QObject* object, QDomElement& element) +{ + Q_ASSERT(object); + + const QMetaObject* metaObject = object->metaObject(); + element.setAttribute("class", QString(metaObject->className())); + + const int propertyCount = metaObject->propertyCount(); + if (propertyCount > 0) + { + for (int i = 0; i < propertyCount; ++i) + { + QMetaProperty property = metaObject->property(i); + if (property.isReadable()) + { + QDomElement propertyElement = element.ownerDocument().createElement("property"); + element.appendChild(propertyElement); + propertyElement.setAttribute("name", property.name()); + + QVariant value = property.read(object); + if (property.isEnumType()) + { + QMetaEnum metaEnum = property.enumerator(); + Q_ASSERT(metaEnum.isValid()); + propertyElement.appendChild(propertyElement.ownerDocument().createTextNode(metaEnum.valueToKey(value.toInt()))); + } + else if (value.canConvert()) + { + QObjectList objectList = value.value(); + for (QObject* currentObject : objectList) + { + QDomElement objectElement = element.ownerDocument().createElement("QObject"); + propertyElement.appendChild(objectElement); + Serializer::store(currentObject, objectElement); + } + } + else + { + propertyElement.appendChild(propertyElement.ownerDocument().createTextNode(value.toString())); + } + } + } + } +} + +QObject* Serializer::clone(QObject* object, QObject* parent) +{ + QDomDocument document; + QDomElement rootElement = document.createElement("root"); + document.appendChild(rootElement); + Serializer::store(object, rootElement); + return Serializer::load(rootElement, parent); +} + +CodeGenerator::CodeGenerator(QObject* parent) : + BaseClass(parent) +{ + qRegisterMetaType("codegen::GeneratedCodeStorage"); + qRegisterMetaType("codegen::GeneratedFunction"); + qRegisterMetaType("codegen::GeneratedBase"); + qRegisterMetaType("codegen::GeneratedParameter"); + qRegisterMetaType("codegen::GeneratedPDFObject"); + qRegisterMetaType("codegen::GeneratedAction"); + qRegisterMetaType("QObjectList"); +} + +void CodeGenerator::initialize() +{ + m_storage = new GeneratedCodeStorage(this); +} + +void CodeGenerator::load(const QDomDocument& document) +{ + delete m_storage; + m_storage = nullptr; + + m_storage = qobject_cast(Serializer::load(document.firstChildElement("root"), this)); +} + +void CodeGenerator::store(QDomDocument& document) +{ + if (m_storage) + { + QDomElement rootElement = document.createElement("root"); + document.appendChild(rootElement); + Serializer::store(m_storage, rootElement); + } +} + +void CodeGenerator::generateCode(QString headerName, QString sourceName) const +{ + QString startMark = "/* START GENERATED CODE */"; + QString endMark = "/* END GENERATED CODE */"; + QString className = "PDFDocumentBuilder"; + const int indent = 4; + + QFile headerFile(headerName); + if (headerFile.exists()) + { + if (headerFile.open(QFile::ReadOnly | QFile::Text)) + { + QString utfCode = QString::fromUtf8(headerFile.readAll()); + headerFile.close(); + + int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); + int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); + + QString frontPart = utfCode.left(startIndex); + QString backPart = utfCode.mid(endIndex); + QString headerGeneratedCode = generateHeader(indent); + QString allCode = frontPart + headerGeneratedCode + backPart; + + headerFile.open(QFile::WriteOnly | QFile::Truncate); + headerFile.write(allCode.toUtf8()); + headerFile.close(); + } + } + + QFile sourceFile(sourceName); + if (sourceFile.exists()) + { + if (sourceFile.open(QFile::ReadOnly | QFile::Text)) + { + QString utfCode = QString::fromUtf8(sourceFile.readAll()); + sourceFile.close(); + + int startIndex = utfCode.indexOf(startMark, Qt::CaseSensitive) + startMark.length(); + int endIndex = utfCode.indexOf(endMark, Qt::CaseSensitive); + + QString frontPart = utfCode.left(startIndex); + QString backPart = utfCode.mid(endIndex); + QString sourceGeneratedCode = generateSource(className, indent); + QString allCode = frontPart + sourceGeneratedCode + backPart; + + sourceFile.open(QFile::WriteOnly | QFile::Truncate); + sourceFile.write(allCode.toUtf8()); + sourceFile.close(); + } + } +} + +QString CodeGenerator::generateHeader(int indent) const +{ + QByteArray ba; + { + QTextStream stream(&ba, QIODevice::WriteOnly); + stream.setCodec("UTF-8"); + stream.setRealNumberPrecision(3); + stream.setRealNumberNotation(QTextStream::FixedNotation); + + CodeGeneratorParameters parameters; + parameters.header = true; + parameters.indent = indent; + m_storage->generateCode(stream, parameters); + } + + return QString::fromUtf8(ba); +} + +QString CodeGenerator::generateSource(QString className, int indent) const +{ + QByteArray ba; + { + QTextStream stream(&ba, QIODevice::WriteOnly); + stream.setCodec("UTF-8"); + stream.setRealNumberPrecision(3); + stream.setRealNumberNotation(QTextStream::FixedNotation); + + CodeGeneratorParameters parameters; + parameters.header = false; + parameters.indent = indent; + parameters.className = className; + m_storage->generateCode(stream, parameters); + } + + return QString::fromUtf8(ba); +} + +GeneratedFunction::GeneratedFunction(QObject* parent) : + BaseClass(parent) +{ + +} + +QString GeneratedFunction::getFunctionTypeString() const +{ + return Serializer::convertEnumToString(m_functionType); +} + +void GeneratedFunction::setFunctionTypeString(const QString& string) +{ + Serializer::convertStringToEnum(string, m_functionType); +} + +GeneratedFunction::FunctionType GeneratedFunction::getFunctionType() const +{ + return m_functionType; +} + +void GeneratedFunction::setFunctionType(const FunctionType& functionType) +{ + m_functionType = functionType; +} + +QString GeneratedFunction::getFunctionName() const +{ + return m_functionName; +} + +void GeneratedFunction::setFunctionName(const QString& functionName) +{ + m_functionName = functionName; +} + +QString GeneratedFunction::getFunctionDescription() const +{ + return m_functionDescription; +} + +void GeneratedFunction::setFunctionDescription(const QString& functionDescription) +{ + m_functionDescription = functionDescription; +} + +GeneratedFunction* GeneratedFunction::clone(QObject* parent) +{ + return qobject_cast(Serializer::clone(this, parent)); +} + +void GeneratedFunction::generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const +{ + QStringList parameterCaptions; + QStringList parameterTexts; + std::function gatherParameters = [&](const GeneratedBase* object, Pass pass) + { + if (pass != Pass::Enter) + { + return; + } + + if (const GeneratedParameter* generatedParameter = qobject_cast(object)) + { + parameterCaptions << QString("%1 %2").arg(generatedParameter->getParameterName(), generatedParameter->getParameterDescription()); + parameterTexts << QString("%1 %2").arg(getCppType(generatedParameter->getParameterDataType()), generatedParameter->getParameterName()); + } + }; + applyFunctor(gatherParameters); + + if (parameterTexts.isEmpty()) + { + parameterTexts << ""; + } + + if (parameters.header) + { + // Generate header source code + + // Function comments + for (const QString& string : getFormattedTextWithLayout("/// ", "/// ", getFunctionDescription(), parameters.indent)) + { + stream << string << Qt::endl; + } + + // Function parameter comments + for (const QString& parameterCaption : parameterCaptions) + { + for (const QString& string : getFormattedTextWithLayout("/// \\param ", "/// ", parameterCaption, parameters.indent)) + { + stream << string << Qt::endl; + } + } + + // Function declaration + QString functionHeader = QString("%1 %2(").arg(getCppType(getReturnType()), getFunctionName()); + QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); + QString functionFooter = QString(");"); + + for (QString& str : parameterTexts) + { + str += ","; + } + parameterTexts.back().replace(",", functionFooter); + + QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, parameters.indent); + for (const QString& functionDeclarationItem : functionDeclaration) + { + stream << functionDeclarationItem << Qt::endl; + } + } + else + { + // Generate c++ source code + QString functionHeader = QString("%1 %2::%3(").arg(getCppType(getReturnType()), parameters.className, getFunctionName()); + QString functionHeaderNext(functionHeader.length(), QChar(QChar::Space)); + QString functionFooter = QString(")"); + + for (QString& str : parameterTexts) + { + str += ","; + } + parameterTexts.back().replace(",", functionFooter); + + QStringList functionDeclaration = getFormattedTextBlock(functionHeader, functionHeaderNext, parameterTexts, 0); + for (const QString& functionDeclarationItem : functionDeclaration) + { + stream << functionDeclarationItem << Qt::endl; + } + + QString indent(parameters.indent, QChar(QChar::Space)); + + stream << "{" << Qt::endl; + stream << indent << "PDFObjectFactory objectBuilder;" << Qt::endl << Qt::endl; + + generateSourceCode(stream, parameters); + + stream << "}" << Qt::endl; + } +} + +bool GeneratedFunction::hasField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + case FieldType::ItemType: + case FieldType::DataType: + case FieldType::Description: + return true; + + case FieldType::Value: + return false; + + default: + break; + } + + return false; +} + +QVariant GeneratedFunction::readField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + return m_functionName; + case FieldType::ItemType: + return int(m_functionType); + case FieldType::DataType: + return int(m_returnType); + case FieldType::Description: + return m_functionDescription; + + case FieldType::Value: + default: + break; + } + + return QVariant(); +} + +void GeneratedFunction::writeField(GeneratedBase::FieldType fieldType, QVariant value) +{ + switch (fieldType) + { + case FieldType::Name: + m_functionName = value.toString(); + break; + case FieldType::ItemType: + m_functionType = static_cast(value.toInt()); + break; + case FieldType::DataType: + m_returnType = static_cast(value.toInt()); + break; + case FieldType::Description: + m_functionDescription = value.toString(); + break; + + case FieldType::Value: + default: + break; + } +} + +void GeneratedFunction::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) +{ + switch (fieldType) + { + case FieldType::ItemType: + Serializer::fillComboBox(comboBox, m_functionType); + break; + case FieldType::DataType: + Serializer::fillComboBox(comboBox, m_returnType); + break; + + default: + break; + } +} + +bool GeneratedFunction::canHaveSubitems() const +{ + return true; +} + +QStringList GeneratedFunction::getCaptions() const +{ + return QStringList() << QString("Function") << QString("%1 %2(...)").arg(Serializer::convertEnumToString(m_returnType), m_functionName); +} + +GeneratedBase* GeneratedFunction::appendItem() +{ + Q_ASSERT(canHaveSubitems()); + + GeneratedBase* newItem = new GeneratedAction(this); + addItem(newItem); + return newItem; +} + +GeneratedFunction::DataType GeneratedFunction::getReturnType() const +{ + return m_returnType; +} + +void GeneratedFunction::setReturnType(DataType returnType) +{ + m_returnType = returnType; +} + +GeneratedAction::GeneratedAction(QObject* parent) : + BaseClass(parent), + m_actionType(CreateObject) +{ + +} + +bool GeneratedAction::hasField(FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + return m_actionType == CreateObject; + case FieldType::ItemType: + return true; + case FieldType::DataType: + return hasField(FieldType::Name) && !m_variableName.isEmpty(); + case FieldType::Description: + return m_actionType == Code; + + default: + break; + } + + return false; +} + +QVariant GeneratedAction::readField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + return m_variableName; + case FieldType::ItemType: + return int(m_actionType); + case FieldType::DataType: + return int(m_variableType); + case FieldType::Description: + return m_code; + + default: + break; + } + + return QVariant(); +} + +void GeneratedAction::writeField(GeneratedBase::FieldType fieldType, QVariant value) +{ + switch (fieldType) + { + case FieldType::Name: + m_variableName = value.toString(); + break; + case FieldType::ItemType: + m_actionType = static_cast(value.toInt()); + break; + case FieldType::DataType: + m_variableType = static_cast(value.toInt()); + break; + case FieldType::Description: + m_code = value.toString(); + break; + + default: + break; + } +} + +void GeneratedAction::fillComboBox(QComboBox* comboBox, FieldType fieldType) +{ + switch (fieldType) + { + case FieldType::ItemType: + Serializer::fillComboBox(comboBox, m_actionType); + break; + case FieldType::DataType: + Serializer::fillComboBox(comboBox, m_variableType); + break; + + default: + break; + } +} + +bool GeneratedAction::canHaveSubitems() const +{ + switch (m_actionType) + { + case Parameters: + case CreateObject: + return true; + + default: + break; + } + + return false; +} + +QStringList GeneratedAction::getCaptions() const +{ + return QStringList() << QString("Action %1").arg(Serializer::convertEnumToString(m_actionType)) << (!m_variableName.isEmpty() ? QString("%1 %2").arg(Serializer::convertEnumToString(m_variableType), m_variableName) : QString()); +} + +GeneratedBase* GeneratedAction::appendItem() +{ + Q_ASSERT(canHaveSubitems()); + GeneratedBase* newItem = nullptr; + switch (m_actionType) + { + case Parameters: + newItem = new GeneratedParameter(this); + break; + + default: + newItem = new GeneratedPDFObject(this); + break; + } + + addItem(newItem); + return newItem; +} + +GeneratedAction::ActionType GeneratedAction::getActionType() const +{ + return m_actionType; +} + +void GeneratedAction::setActionType(ActionType actionType) +{ + m_actionType = actionType; + + if (!canHaveSubitems()) + { + clearItems(); + } +} + +QString GeneratedAction::getVariableName() const +{ + return m_variableName; +} + +void GeneratedAction::setVariableName(const QString& variableName) +{ + m_variableName = variableName; +} + +GeneratedAction::DataType GeneratedAction::getVariableType() const +{ + return m_variableType; +} + +void GeneratedAction::setVariableType(DataType variableType) +{ + m_variableType = variableType; +} + +QString GeneratedAction::getCode() const +{ + return m_code; +} + +void GeneratedAction::setCode(const QString& code) +{ + m_code = code; +} + +void GeneratedAction::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const +{ + if (pass == Pass::Enter && getActionType() == Code) + { + QString indent(parameters.indent, QChar(QChar::Space)); + QStringList lines = getCode().split(QChar('\n'), Qt::KeepEmptyParts, Qt::CaseInsensitive); + + for (QString string : lines) + { + string = string.trimmed(); + if (!string.isEmpty()) + { + stream << indent << string << Qt::endl; + } + else + { + stream << Qt::endl; + } + } + } + + if (pass == Pass::Leave && getActionType() == CreateObject) + { + QString indent(parameters.indent, QChar(QChar::Space)); + + switch (getVariableType()) + { + case _PDFObject: + { + stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = objectBuilder.takeObject();"; + break; + } + + case _PDFObjectReference: + { + stream << indent << getCppType(getVariableType()) << " " << getVariableName() << " = addObject(objectBuilder.takeObject());"; + break; + } + + default: + Q_ASSERT(false); + break; + } + } + + if (pass == Pass::Leave && getActionType() != Parameters && !parameters.isLastItem) + { + stream << Qt::endl; + } +} + +void GeneratedBase::generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const +{ + generateSourceCodeImpl(stream, parameters, Pass::Enter); + + for (const QObject* object : m_items) + { + const GeneratedBase* generatedBase = qobject_cast(object); + CodeGeneratorParameters parametersTemporary = parameters; + parametersTemporary.isFirstItem = object == m_items.front(); + parametersTemporary.isLastItem = object == m_items.back(); + generatedBase->generateSourceCode(stream, parametersTemporary); + } + + generateSourceCodeImpl(stream, parameters, Pass::Leave); +} + +void GeneratedBase::applyFunctor(std::function& functor) const +{ + functor(this, Pass::Enter); + + for (const QObject* object : m_items) + { + const GeneratedBase* generatedBase = qobject_cast(object); + generatedBase->applyFunctor(functor); + } + + functor(this, Pass::Leave); +} + +bool GeneratedBase::canPerformOperation(Operation operation) const +{ + const bool isFunction = qobject_cast(this) != nullptr; + switch (operation) + { + case Operation::Delete: + return !isFunction; + + case Operation::MoveUp: + { + if (const GeneratedBase* parentBase = getParent()) + { + QObjectList items = parentBase->getItems(); + return items.indexOf(const_cast(this)) > 0; + } + + break; + } + + case Operation::MoveDown: + { + if (const GeneratedBase* parentBase = getParent()) + { + QObjectList items = parentBase->getItems(); + return items.indexOf(const_cast(this)) < items.size() - 1; + } + + break; + } + + case Operation::NewSibling: + return !isFunction; + + case Operation::NewChild: + return canHaveSubitems(); + + default: + break; + } + + return false; +} + +void GeneratedBase::performOperation(GeneratedBase::Operation operation) +{ + switch (operation) + { + case Operation::Delete: + { + getParent()->removeItem(this); + break; + } + + case Operation::MoveUp: + { + GeneratedBase* parentItem = getParent(); + QObjectList items = parentItem->getItems(); + const int index = items.indexOf(this); + items.removeAll(const_cast(this)); + items.insert(index - 1, this); + parentItem->setItems(qMove(items)); + break; + } + + case Operation::MoveDown: + { + GeneratedBase* parentItem = getParent(); + QObjectList items = parentItem->getItems(); + const int index = items.indexOf(this); + items.removeAll(const_cast(this)); + items.insert(index + 1, this); + parentItem->setItems(qMove(items)); + break; + } + + case Operation::NewSibling: + { + GeneratedBase* parentItem = getParent(); + if (GeneratedBase* createdItem = parentItem->appendItem()) + { + QObjectList items = parentItem->getItems(); + items.removeAll(createdItem); + items.insert(items.indexOf(const_cast(this)) + 1, createdItem); + parentItem->setItems(qMove(items)); + } + break; + } + + case Operation::NewChild: + { + appendItem(); + break; + } + + default: + break; + } +} + +QObjectList GeneratedBase::getItems() const +{ + return m_items; +} + +void GeneratedBase::setItems(const QObjectList& items) +{ + m_items = items; +} + +void GeneratedBase::addItem(QObject* object) +{ + object->setParent(this); + m_items.append(object); +} + +void GeneratedBase::removeItem(QObject* object) +{ + object->deleteLater(); + m_items.removeAll(object); +} + +void GeneratedBase::clearItems() +{ + qDeleteAll(m_items); + m_items.clear(); +} + +void GeneratedBase::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const +{ + Q_UNUSED(stream); + Q_UNUSED(parameters); + Q_UNUSED(pass); +} + +QString GeneratedBase::getCppType(DataType type) const +{ + return Serializer::convertEnumToString(type).mid(1); +} + +QStringList GeneratedBase::getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const +{ + QFont font = QApplication::font(); + QFontMetrics fontMetrics(font); + + int usedLength = indent + qMax(firstPrefix.length(), prefix.length()); + int length = 80 - usedLength; + QString testText(length, QChar('A')); + int width = fontMetrics.width(testText); + + QTextLayout layout(text, font); + layout.setCacheEnabled(false); + QTextOption textOption = layout.textOption(); + textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + layout.setTextOption(textOption); + + layout.beginLayout(); + while (true) + { + QTextLine textLine = layout.createLine(); + if (!textLine.isValid()) + { + break; + } + + textLine.setLineWidth(width); + } + layout.endLayout(); + + QStringList texts; + const int lineCount = layout.lineCount(); + for (int i = 0; i < lineCount; ++i) + { + QTextLine line = layout.lineAt(i); + texts << text.mid(line.textStart(), line.textLength()); + } + return getFormattedTextBlock(firstPrefix, prefix, texts, indent); +} + +QStringList GeneratedBase::getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const +{ + QString indentText(indent, QChar(QChar::Space)); + firstPrefix.prepend(indentText); + prefix.prepend(indentText); + + QStringList result; + QString currentPrefix = firstPrefix; + for (const QString& text : texts) + { + result << QString("%1%2").arg(currentPrefix, text); + currentPrefix = prefix; + } + return result; +} + +GeneratedPDFObject::GeneratedPDFObject(QObject* parent) : + BaseClass(parent) +{ + +} + +bool GeneratedPDFObject::hasField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + return m_objectType == DictionaryItemSimple || m_objectType == DictionaryItemComplex; + case FieldType::ItemType: + return true; + case FieldType::Value: + return m_objectType == Object || m_objectType == ArraySimple || m_objectType == DictionaryItemSimple; + + default: + break; + } + + return false; +} + +QVariant GeneratedPDFObject::readField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + return m_dictionaryItemName; + case FieldType::ItemType: + return int(m_objectType); + case FieldType::Value: + return m_value; + + default: + break; + } + + return QVariant(); +} + +void GeneratedPDFObject::writeField(GeneratedBase::FieldType fieldType, QVariant value) +{ + switch (fieldType) + { + case FieldType::Name: + m_dictionaryItemName = value.toString(); + break; + case FieldType::ItemType: + m_objectType = static_cast(value.toInt()); + break; + case FieldType::Value: + m_value = value.toString(); + break; + + default: + break; + } +} + +void GeneratedPDFObject::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) +{ + switch (fieldType) + { + case FieldType::ItemType: + Serializer::fillComboBox(comboBox, m_objectType); + break; + + default: + break; + } +} + +bool GeneratedPDFObject::canHaveSubitems() const +{ + switch (m_objectType) + { + case ArrayComplex: + case Dictionary: + case DictionaryItemComplex: + return true; + + default: + break; + } + + return false; +} + +QStringList GeneratedPDFObject::getCaptions() const +{ + QString name; + switch (m_objectType) + { + case Object: + name = tr("Object"); + break; + case ArraySimple: + name = tr("Array (simple)"); + break; + case ArrayComplex: + name = tr("Array (complex)"); + break; + case Dictionary: + name = tr("Dictionary"); + break; + case DictionaryItemSimple: + name = tr("Item (simple), name = '%1'").arg(m_dictionaryItemName); + break; + case DictionaryItemComplex: + name = tr("Item (complex), name = '%1'").arg(m_dictionaryItemName); + break; + + default: + Q_ASSERT(false); + break; + } + + return QStringList() << name << m_value; +} + +GeneratedBase* GeneratedPDFObject::appendItem() +{ + Q_ASSERT(canHaveSubitems()); + GeneratedBase* newItem = new GeneratedPDFObject(this); + addItem(newItem); + return newItem; +} + +QString GeneratedPDFObject::getValue() const +{ + return m_value; +} + +void GeneratedPDFObject::setValue(const QString& value) +{ + m_value = value; +} + +GeneratedPDFObject::ObjectType GeneratedPDFObject::getObjectType() const +{ + return m_objectType; +} + +void GeneratedPDFObject::setObjectType(ObjectType objectType) +{ + m_objectType = objectType; + + if (!canHaveSubitems()) + { + clearItems(); + } +} + +QString GeneratedPDFObject::getDictionaryItemName() const +{ + return m_dictionaryItemName; +} + +void GeneratedPDFObject::setDictionaryItemName(const QString& dictionaryItemName) +{ + m_dictionaryItemName = dictionaryItemName; +} + +void GeneratedPDFObject::generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, GeneratedBase::Pass pass) const +{ + QString indent(parameters.indent, QChar(QChar::Space)); + QString writeTo("objectBuilder << "); + + switch (getObjectType()) + { + case codegen::GeneratedPDFObject::Object: + { + if (pass == Pass::Enter) + { + stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::ArraySimple: + { + if (pass == Pass::Enter) + { + stream << indent << "objectBuilder.beginArray();" << Qt::endl; + for (const QString& arrayPart : getValue().trimmed().split(";")) + { + stream << indent << writeTo << arrayPart << ";" << Qt::endl; + } + stream << indent << "objectBuilder.endArray();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::ArrayComplex: + { + if (pass == Pass::Enter) + { + stream << indent << "objectBuilder.beginArray();" << Qt::endl; + } + if (pass == Pass::Leave) + { + stream << indent << "objectBuilder.endArray();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::Dictionary: + { + if (pass == Pass::Enter) + { + stream << indent << "objectBuilder.beginDictionary();" << Qt::endl; + } + if (pass == Pass::Leave) + { + stream << indent << "objectBuilder.endDictionary();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::DictionaryItemSimple: + { + if (pass == Pass::Enter) + { + stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; + stream << indent << writeTo << getValue().trimmed() << ";" << Qt::endl; + stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; + } + break; + } + + case codegen::GeneratedPDFObject::DictionaryItemComplex: + { + if (pass == Pass::Enter) + { + stream << indent << QString("objectBuilder.beginDictionaryItem(\"%1\");").arg(getDictionaryItemName()) << Qt::endl; + } + if (pass == Pass::Leave) + { + stream << indent << "objectBuilder.endDictionaryItem();" << Qt::endl; + } + break; + } + + default: + Q_ASSERT(false); + break; + } +} + +GeneratedParameter::GeneratedParameter(QObject* parent) : + BaseClass(parent) +{ + +} + +bool GeneratedParameter::hasField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + case FieldType::DataType: + case FieldType::Description: + return true; + + default: + break; + } + + return false; +} + +QVariant GeneratedParameter::readField(GeneratedBase::FieldType fieldType) const +{ + switch (fieldType) + { + case FieldType::Name: + return m_parameterName; + case FieldType::DataType: + return m_parameterDataType; + case FieldType::Description: + return m_parameterDescription; + + default: + break; + } + + return QVariant(); +} + +void GeneratedParameter::writeField(GeneratedBase::FieldType fieldType, QVariant value) +{ + switch (fieldType) + { + case FieldType::Name: + m_parameterName = value.toString(); + break; + case FieldType::DataType: + m_parameterDataType = static_cast(value.toInt()); + break; + case FieldType::Description: + m_parameterDescription = value.toString(); + break; + + default: + break; + } +} + +void GeneratedParameter::fillComboBox(QComboBox* comboBox, GeneratedBase::FieldType fieldType) +{ + switch (fieldType) + { + case FieldType::DataType: + Serializer::fillComboBox(comboBox, m_parameterDataType); + break; + + default: + break; + } +} + +bool GeneratedParameter::canHaveSubitems() const +{ + return false; +} + +QStringList GeneratedParameter::getCaptions() const +{ + return QStringList() << QString("%1 %2").arg(Serializer::convertEnumToString(m_parameterDataType)).arg(m_parameterName) << m_parameterDescription; +} + +GeneratedBase* GeneratedParameter::appendItem() +{ + return nullptr; +} + +QString GeneratedParameter::getParameterName() const +{ + return m_parameterName; +} + +void GeneratedParameter::setParameterName(const QString& parameterName) +{ + m_parameterName = parameterName; +} + +GeneratedParameter::DataType GeneratedParameter::getParameterDataType() const +{ + return m_parameterDataType; +} + +void GeneratedParameter::setParameterDataType(const DataType& parameterDataType) +{ + m_parameterDataType = parameterDataType; +} + +QString GeneratedParameter::getParameterDescription() const +{ + return m_parameterDescription; +} + +void GeneratedParameter::setParameterDescription(const QString& parameterDescription) +{ + m_parameterDescription = parameterDescription; +} + +} diff --git a/CodeGenerator/codegenerator.h b/CodeGenerator/codegenerator.h index ecfba40..611bfbc 100644 --- a/CodeGenerator/codegenerator.h +++ b/CodeGenerator/codegenerator.h @@ -1,454 +1,454 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef CODEGENERATOR_H -#define CODEGENERATOR_H - -#include -#include -#include -#include -#include -#include -#include - -namespace codegen -{ - -struct CodeGeneratorParameters -{ - bool header = false; - int indent = 0; - QString className; - bool isFirstItem = false; - bool isLastItem = false; -}; - -class Serializer -{ -public: - static QObject* load(const QDomElement& element, QObject* parent); - static void store(QObject* object, QDomElement& element); - static QObject* clone(QObject* object, QObject* parent); - - template - static inline QString convertEnumToString(T enumValue) - { - QMetaEnum metaEnum = QMetaEnum::fromType(); - Q_ASSERT(metaEnum.isValid()); - return metaEnum.valueToKey(enumValue); - } - - template - static inline void convertStringToEnum(const QString enumString, T& value) - { - QMetaEnum metaEnum = QMetaEnum::fromType(); - Q_ASSERT(metaEnum.isValid()); - bool ok = false; - value = static_cast(metaEnum.keyToValue(enumString.toLatin1().data(), &ok)); - if (!ok) - { - // Set default value, if something fails. - value = T(); - } - } - - template - static inline void fillComboBox(QComboBox* comboBox, T value) - { - QMetaEnum metaEnum = QMetaEnum::fromType(); - Q_ASSERT(metaEnum.isValid()); - - const int keyCount = metaEnum.keyCount(); - comboBox->setUpdatesEnabled(false); - comboBox->clear(); - for (int i = 0; i < keyCount; ++i) - { - comboBox->addItem(metaEnum.key(i), metaEnum.value(i)); - } - comboBox->setCurrentIndex(comboBox->findData(int(value))); - comboBox->setUpdatesEnabled(true); - } -}; - -class GeneratedFunction; - -class GeneratedCodeStorage : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - Q_INVOKABLE GeneratedCodeStorage(QObject* parent); - - Q_PROPERTY(QObjectList functions READ getFunctions WRITE setFunctions) - - QObjectList getFunctions() const; - void setFunctions(const QObjectList& functions); - - GeneratedFunction* addFunction(const QString& name); - GeneratedFunction* addFunction(GeneratedFunction* function); - void removeFunction(GeneratedFunction* function); - - void generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; - -private: - QObjectList m_functions; -}; - -class GeneratedBase : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - using BaseClass::BaseClass; - - enum class FieldType - { - Name, - ItemType, - DataType, - Value, - Description - }; - - enum DataType - { - _void, - _PDFNull, - _bool, - _PDFInteger, - _PDFReal, - _PDFObjectReference, - _PDFObject, - _PDFIntegerVector, - _PDFObjectReferenceVector, - _PDFFormSubmitFlags, - _QString, - _QPointF, - _QRectF, - _QColor, - _QVariant, - _QPolygonF, - _QDateTime, - _QLocale, - _QByteArray, - _Polygons, - _TextAnnotationIcon, - _LinkHighlightMode, - _TextAlignment, - _AnnotationLineEnding, - _AnnotationBorderStyle, - _FileAttachmentIcon, - _Stamp, - _PDFDestination, - _PageRotation - }; - Q_ENUM(DataType) - - Q_PROPERTY(QObjectList items READ getItems WRITE setItems) - - virtual bool hasField(FieldType fieldType) const = 0; - virtual QVariant readField(FieldType fieldType) const = 0; - virtual void writeField(FieldType fieldType, QVariant value) = 0; - virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) = 0; - virtual bool canHaveSubitems() const = 0; - virtual QStringList getCaptions() const = 0; - virtual GeneratedBase* appendItem() = 0; - - enum class Pass - { - Enter, - Leave - }; - - void generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; - void applyFunctor(std::function& functor) const; - - enum class Operation - { - Delete, - MoveUp, - MoveDown, - NewSibling, - NewChild - }; - - GeneratedBase* getParent() { return qobject_cast(parent()); } - const GeneratedBase* getParent() const { return qobject_cast(parent()); } - bool canPerformOperation(Operation operation) const; - void performOperation(Operation operation); - - QObjectList getItems() const; - void setItems(const QObjectList& items); - void addItem(QObject* object); - void removeItem(QObject* object); - void clearItems(); - -protected: - virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const; - - QString getCppType(DataType type) const; - QStringList getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const; - QStringList getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const; - -private: - QObjectList m_items; -}; - -class GeneratedParameter : public GeneratedBase -{ - Q_OBJECT - -private: - using BaseClass = GeneratedBase; - -public: - Q_INVOKABLE GeneratedParameter(QObject* parent); - Q_PROPERTY(QString parameterName READ getParameterName WRITE setParameterName) - Q_PROPERTY(DataType parameterType READ getParameterDataType WRITE setParameterDataType) - Q_PROPERTY(QString parameterDescription READ getParameterDescription WRITE setParameterDescription) - - virtual bool hasField(FieldType fieldType) const override; - virtual QVariant readField(FieldType fieldType) const override; - virtual void writeField(FieldType fieldType, QVariant value) override; - virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; - virtual bool canHaveSubitems() const override; - virtual QStringList getCaptions() const override; - virtual GeneratedBase* appendItem() override; - - QString getParameterName() const; - void setParameterName(const QString& parameterName); - - DataType getParameterDataType() const; - void setParameterDataType(const DataType& parameterDataType); - - QString getParameterDescription() const; - void setParameterDescription(const QString& parameterDescription); - -private: - QString m_parameterName; - DataType m_parameterDataType = _void; - QString m_parameterDescription; -}; - -class GeneratedPDFObject : public GeneratedBase -{ - Q_OBJECT - -private: - using BaseClass = GeneratedBase; - -public: - - enum ObjectType - { - Object, - ArraySimple, - ArrayComplex, - Dictionary, - DictionaryItemSimple, - DictionaryItemComplex - }; - Q_ENUM(ObjectType) - - Q_INVOKABLE GeneratedPDFObject(QObject* parent); - Q_PROPERTY(QString dictionaryItemName READ getDictionaryItemName WRITE setDictionaryItemName) - Q_PROPERTY(ObjectType objectType READ getObjectType WRITE setObjectType) - Q_PROPERTY(QString value READ getValue WRITE setValue) - - virtual bool hasField(FieldType fieldType) const override; - virtual QVariant readField(FieldType fieldType) const override; - virtual void writeField(FieldType fieldType, QVariant value) override; - virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; - virtual bool canHaveSubitems() const override; - virtual QStringList getCaptions() const override; - virtual GeneratedBase* appendItem() override; - - QString getValue() const; - void setValue(const QString& value); - - ObjectType getObjectType() const; - void setObjectType(ObjectType objectType); - - QString getDictionaryItemName() const; - void setDictionaryItemName(const QString& dictionaryItemName); - -protected: - virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const override; - -private: - QString m_dictionaryItemName; - ObjectType m_objectType = Object; - QString m_value; -}; - -class GeneratedAction : public GeneratedBase -{ - Q_OBJECT - -private: - using BaseClass = GeneratedBase; - -public: - - enum ActionType - { - Parameters, - CreateObject, - Code - }; - Q_ENUM(ActionType) - - Q_INVOKABLE GeneratedAction(QObject* parent); - Q_PROPERTY(ActionType actionType READ getActionType WRITE setActionType) - Q_PROPERTY(QString variableName READ getVariableName WRITE setVariableName) - Q_PROPERTY(DataType variableType READ getVariableType WRITE setVariableType) - Q_PROPERTY(QString code READ getCode WRITE setCode) - - virtual bool hasField(FieldType fieldType) const override; - virtual QVariant readField(FieldType fieldType) const override; - virtual void writeField(FieldType fieldType, QVariant value) override; - virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; - virtual bool canHaveSubitems() const override; - virtual QStringList getCaptions() const override; - virtual GeneratedBase* appendItem() override; - - ActionType getActionType() const; - void setActionType(ActionType actionType); - - QString getVariableName() const; - void setVariableName(const QString& variableName); - - DataType getVariableType() const; - void setVariableType(DataType variableType); - - QString getCode() const; - void setCode(const QString& code); - -protected: - virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const override; - -private: - ActionType m_actionType; - QString m_variableName; - DataType m_variableType = _void; - QString m_code; -}; - -class GeneratedFunction : public GeneratedBase -{ - Q_OBJECT - -private: - using BaseClass = GeneratedBase; - -public: - - enum FunctionType - { - Structure, - Annotations, - ColorSpace, - Actions, - Forms - }; - Q_ENUM(FunctionType) - - - Q_INVOKABLE GeneratedFunction(QObject* parent); - - Q_PROPERTY(FunctionType functionType READ getFunctionType WRITE setFunctionType) - Q_PROPERTY(QString functionName READ getFunctionName WRITE setFunctionName) - Q_PROPERTY(QString functionDescription READ getFunctionDescription WRITE setFunctionDescription) - Q_PROPERTY(DataType returnType READ getReturnType WRITE setReturnType) - - virtual bool hasField(FieldType fieldType) const override; - virtual QVariant readField(FieldType fieldType) const override; - virtual void writeField(FieldType fieldType, QVariant value) override; - virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; - virtual bool canHaveSubitems() const override; - virtual QStringList getCaptions() const override; - virtual GeneratedBase* appendItem() override; - - QString getFunctionTypeString() const; - void setFunctionTypeString(const QString& string); - - FunctionType getFunctionType() const; - void setFunctionType(const FunctionType& functionType); - - QString getFunctionName() const; - void setFunctionName(const QString& functionName); - - QString getFunctionDescription() const; - void setFunctionDescription(const QString& functionDescription); - - DataType getReturnType() const; - void setReturnType(DataType returnType); - - /// Create a clone of this function - GeneratedFunction* clone(QObject* parent); - - void generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; - -private: - FunctionType m_functionType = FunctionType::Annotations; - QString m_functionName; - QString m_functionDescription; - DataType m_returnType = _void; -}; - -class CodeGenerator : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - CodeGenerator(QObject* parent = nullptr); - - void initialize(); - - QObjectList getFunctions() const { return m_storage->getFunctions(); } - GeneratedFunction* addFunction(const QString& name) { return m_storage->addFunction(name); } - GeneratedFunction* addFunction(GeneratedFunction* function) { return m_storage->addFunction(function); } - void removeFunction(GeneratedFunction* function) { m_storage->removeFunction(function); } - - void load(const QDomDocument& document); - void store(QDomDocument& document); - - void generateCode(QString headerName, QString sourceName) const; - -private: - QString generateHeader(int indent) const; - QString generateSource(QString className, int indent) const; - - GeneratedCodeStorage* m_storage = nullptr; -}; - -} // namespace codegen - -Q_DECLARE_METATYPE(codegen::GeneratedCodeStorage*) -Q_DECLARE_METATYPE(codegen::GeneratedFunction*) - -#endif // CODEGENERATOR_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef CODEGENERATOR_H +#define CODEGENERATOR_H + +#include +#include +#include +#include +#include +#include +#include + +namespace codegen +{ + +struct CodeGeneratorParameters +{ + bool header = false; + int indent = 0; + QString className; + bool isFirstItem = false; + bool isLastItem = false; +}; + +class Serializer +{ +public: + static QObject* load(const QDomElement& element, QObject* parent); + static void store(QObject* object, QDomElement& element); + static QObject* clone(QObject* object, QObject* parent); + + template + static inline QString convertEnumToString(T enumValue) + { + QMetaEnum metaEnum = QMetaEnum::fromType(); + Q_ASSERT(metaEnum.isValid()); + return metaEnum.valueToKey(enumValue); + } + + template + static inline void convertStringToEnum(const QString enumString, T& value) + { + QMetaEnum metaEnum = QMetaEnum::fromType(); + Q_ASSERT(metaEnum.isValid()); + bool ok = false; + value = static_cast(metaEnum.keyToValue(enumString.toLatin1().data(), &ok)); + if (!ok) + { + // Set default value, if something fails. + value = T(); + } + } + + template + static inline void fillComboBox(QComboBox* comboBox, T value) + { + QMetaEnum metaEnum = QMetaEnum::fromType(); + Q_ASSERT(metaEnum.isValid()); + + const int keyCount = metaEnum.keyCount(); + comboBox->setUpdatesEnabled(false); + comboBox->clear(); + for (int i = 0; i < keyCount; ++i) + { + comboBox->addItem(metaEnum.key(i), metaEnum.value(i)); + } + comboBox->setCurrentIndex(comboBox->findData(int(value))); + comboBox->setUpdatesEnabled(true); + } +}; + +class GeneratedFunction; + +class GeneratedCodeStorage : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + Q_INVOKABLE GeneratedCodeStorage(QObject* parent); + + Q_PROPERTY(QObjectList functions READ getFunctions WRITE setFunctions) + + QObjectList getFunctions() const; + void setFunctions(const QObjectList& functions); + + GeneratedFunction* addFunction(const QString& name); + GeneratedFunction* addFunction(GeneratedFunction* function); + void removeFunction(GeneratedFunction* function); + + void generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; + +private: + QObjectList m_functions; +}; + +class GeneratedBase : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + using BaseClass::BaseClass; + + enum class FieldType + { + Name, + ItemType, + DataType, + Value, + Description + }; + + enum DataType + { + _void, + _PDFNull, + _bool, + _PDFInteger, + _PDFReal, + _PDFObjectReference, + _PDFObject, + _PDFIntegerVector, + _PDFObjectReferenceVector, + _PDFFormSubmitFlags, + _QString, + _QPointF, + _QRectF, + _QColor, + _QVariant, + _QPolygonF, + _QDateTime, + _QLocale, + _QByteArray, + _Polygons, + _TextAnnotationIcon, + _LinkHighlightMode, + _TextAlignment, + _AnnotationLineEnding, + _AnnotationBorderStyle, + _FileAttachmentIcon, + _Stamp, + _PDFDestination, + _PageRotation + }; + Q_ENUM(DataType) + + Q_PROPERTY(QObjectList items READ getItems WRITE setItems) + + virtual bool hasField(FieldType fieldType) const = 0; + virtual QVariant readField(FieldType fieldType) const = 0; + virtual void writeField(FieldType fieldType, QVariant value) = 0; + virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) = 0; + virtual bool canHaveSubitems() const = 0; + virtual QStringList getCaptions() const = 0; + virtual GeneratedBase* appendItem() = 0; + + enum class Pass + { + Enter, + Leave + }; + + void generateSourceCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; + void applyFunctor(std::function& functor) const; + + enum class Operation + { + Delete, + MoveUp, + MoveDown, + NewSibling, + NewChild + }; + + GeneratedBase* getParent() { return qobject_cast(parent()); } + const GeneratedBase* getParent() const { return qobject_cast(parent()); } + bool canPerformOperation(Operation operation) const; + void performOperation(Operation operation); + + QObjectList getItems() const; + void setItems(const QObjectList& items); + void addItem(QObject* object); + void removeItem(QObject* object); + void clearItems(); + +protected: + virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const; + + QString getCppType(DataType type) const; + QStringList getFormattedTextWithLayout(QString firstPrefix, QString prefix, QString text, int indent) const; + QStringList getFormattedTextBlock(QString firstPrefix, QString prefix, QStringList texts, int indent) const; + +private: + QObjectList m_items; +}; + +class GeneratedParameter : public GeneratedBase +{ + Q_OBJECT + +private: + using BaseClass = GeneratedBase; + +public: + Q_INVOKABLE GeneratedParameter(QObject* parent); + Q_PROPERTY(QString parameterName READ getParameterName WRITE setParameterName) + Q_PROPERTY(DataType parameterType READ getParameterDataType WRITE setParameterDataType) + Q_PROPERTY(QString parameterDescription READ getParameterDescription WRITE setParameterDescription) + + virtual bool hasField(FieldType fieldType) const override; + virtual QVariant readField(FieldType fieldType) const override; + virtual void writeField(FieldType fieldType, QVariant value) override; + virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; + virtual bool canHaveSubitems() const override; + virtual QStringList getCaptions() const override; + virtual GeneratedBase* appendItem() override; + + QString getParameterName() const; + void setParameterName(const QString& parameterName); + + DataType getParameterDataType() const; + void setParameterDataType(const DataType& parameterDataType); + + QString getParameterDescription() const; + void setParameterDescription(const QString& parameterDescription); + +private: + QString m_parameterName; + DataType m_parameterDataType = _void; + QString m_parameterDescription; +}; + +class GeneratedPDFObject : public GeneratedBase +{ + Q_OBJECT + +private: + using BaseClass = GeneratedBase; + +public: + + enum ObjectType + { + Object, + ArraySimple, + ArrayComplex, + Dictionary, + DictionaryItemSimple, + DictionaryItemComplex + }; + Q_ENUM(ObjectType) + + Q_INVOKABLE GeneratedPDFObject(QObject* parent); + Q_PROPERTY(QString dictionaryItemName READ getDictionaryItemName WRITE setDictionaryItemName) + Q_PROPERTY(ObjectType objectType READ getObjectType WRITE setObjectType) + Q_PROPERTY(QString value READ getValue WRITE setValue) + + virtual bool hasField(FieldType fieldType) const override; + virtual QVariant readField(FieldType fieldType) const override; + virtual void writeField(FieldType fieldType, QVariant value) override; + virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; + virtual bool canHaveSubitems() const override; + virtual QStringList getCaptions() const override; + virtual GeneratedBase* appendItem() override; + + QString getValue() const; + void setValue(const QString& value); + + ObjectType getObjectType() const; + void setObjectType(ObjectType objectType); + + QString getDictionaryItemName() const; + void setDictionaryItemName(const QString& dictionaryItemName); + +protected: + virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const override; + +private: + QString m_dictionaryItemName; + ObjectType m_objectType = Object; + QString m_value; +}; + +class GeneratedAction : public GeneratedBase +{ + Q_OBJECT + +private: + using BaseClass = GeneratedBase; + +public: + + enum ActionType + { + Parameters, + CreateObject, + Code + }; + Q_ENUM(ActionType) + + Q_INVOKABLE GeneratedAction(QObject* parent); + Q_PROPERTY(ActionType actionType READ getActionType WRITE setActionType) + Q_PROPERTY(QString variableName READ getVariableName WRITE setVariableName) + Q_PROPERTY(DataType variableType READ getVariableType WRITE setVariableType) + Q_PROPERTY(QString code READ getCode WRITE setCode) + + virtual bool hasField(FieldType fieldType) const override; + virtual QVariant readField(FieldType fieldType) const override; + virtual void writeField(FieldType fieldType, QVariant value) override; + virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; + virtual bool canHaveSubitems() const override; + virtual QStringList getCaptions() const override; + virtual GeneratedBase* appendItem() override; + + ActionType getActionType() const; + void setActionType(ActionType actionType); + + QString getVariableName() const; + void setVariableName(const QString& variableName); + + DataType getVariableType() const; + void setVariableType(DataType variableType); + + QString getCode() const; + void setCode(const QString& code); + +protected: + virtual void generateSourceCodeImpl(QTextStream& stream, CodeGeneratorParameters& parameters, Pass pass) const override; + +private: + ActionType m_actionType; + QString m_variableName; + DataType m_variableType = _void; + QString m_code; +}; + +class GeneratedFunction : public GeneratedBase +{ + Q_OBJECT + +private: + using BaseClass = GeneratedBase; + +public: + + enum FunctionType + { + Structure, + Annotations, + ColorSpace, + Actions, + Forms + }; + Q_ENUM(FunctionType) + + + Q_INVOKABLE GeneratedFunction(QObject* parent); + + Q_PROPERTY(FunctionType functionType READ getFunctionType WRITE setFunctionType) + Q_PROPERTY(QString functionName READ getFunctionName WRITE setFunctionName) + Q_PROPERTY(QString functionDescription READ getFunctionDescription WRITE setFunctionDescription) + Q_PROPERTY(DataType returnType READ getReturnType WRITE setReturnType) + + virtual bool hasField(FieldType fieldType) const override; + virtual QVariant readField(FieldType fieldType) const override; + virtual void writeField(FieldType fieldType, QVariant value) override; + virtual void fillComboBox(QComboBox* comboBox, FieldType fieldType) override; + virtual bool canHaveSubitems() const override; + virtual QStringList getCaptions() const override; + virtual GeneratedBase* appendItem() override; + + QString getFunctionTypeString() const; + void setFunctionTypeString(const QString& string); + + FunctionType getFunctionType() const; + void setFunctionType(const FunctionType& functionType); + + QString getFunctionName() const; + void setFunctionName(const QString& functionName); + + QString getFunctionDescription() const; + void setFunctionDescription(const QString& functionDescription); + + DataType getReturnType() const; + void setReturnType(DataType returnType); + + /// Create a clone of this function + GeneratedFunction* clone(QObject* parent); + + void generateCode(QTextStream& stream, CodeGeneratorParameters& parameters) const; + +private: + FunctionType m_functionType = FunctionType::Annotations; + QString m_functionName; + QString m_functionDescription; + DataType m_returnType = _void; +}; + +class CodeGenerator : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + CodeGenerator(QObject* parent = nullptr); + + void initialize(); + + QObjectList getFunctions() const { return m_storage->getFunctions(); } + GeneratedFunction* addFunction(const QString& name) { return m_storage->addFunction(name); } + GeneratedFunction* addFunction(GeneratedFunction* function) { return m_storage->addFunction(function); } + void removeFunction(GeneratedFunction* function) { m_storage->removeFunction(function); } + + void load(const QDomDocument& document); + void store(QDomDocument& document); + + void generateCode(QString headerName, QString sourceName) const; + +private: + QString generateHeader(int indent) const; + QString generateSource(QString className, int indent) const; + + GeneratedCodeStorage* m_storage = nullptr; +}; + +} // namespace codegen + +Q_DECLARE_METATYPE(codegen::GeneratedCodeStorage*) +Q_DECLARE_METATYPE(codegen::GeneratedFunction*) + +#endif // CODEGENERATOR_H diff --git a/CodeGenerator/generatormainwindow.cpp b/CodeGenerator/generatormainwindow.cpp index 194f34e..c37dc43 100644 --- a/CodeGenerator/generatormainwindow.cpp +++ b/CodeGenerator/generatormainwindow.cpp @@ -1,523 +1,523 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "generatormainwindow.h" -#include "ui_generatormainwindow.h" - -#include "codegenerator.h" - -#include -#include -#include -#include -#include -#include - -GeneratorMainWindow::GeneratorMainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::GeneratorMainWindow), - m_generator(new codegen::CodeGenerator(this)), - m_currentFunction(nullptr), - m_currentSettings(nullptr), - m_isLoadingData(false) -{ - ui->setupUi(this); - - ui->parameterTreeWidget->header()->setMinimumSectionSize(200); - - m_generator->initialize(); - - loadSettings(); - - if (!m_defaultFileName.isEmpty()) - { - load(m_defaultFileName); - } - connect(ui->generatedFunctionsTreeWidget, &QTreeWidget::currentItemChanged, this, &GeneratorMainWindow::onCurrentItemChanged); - connect(ui->parameterTreeWidget, &QTreeWidget::currentItemChanged, this, &GeneratorMainWindow::onParameterTreeCurrentItemChanged); - connect(ui->parameterNameEdit, &QLineEdit::editingFinished, this, &GeneratorMainWindow::saveGeneratedSettings); - connect(ui->parameterItemTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &GeneratorMainWindow::saveGeneratedSettings); - connect(ui->parameterDataTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &GeneratorMainWindow::saveGeneratedSettings); - connect(ui->parameterValueEdit, &QLineEdit::editingFinished, this, &GeneratorMainWindow::saveGeneratedSettings); - connect(ui->parameterDescriptionEdit, &QTextBrowser::textChanged, this, &GeneratorMainWindow::saveGeneratedSettings); - - ui->parameterItemTypeCombo->setMaxVisibleItems(30); - ui->parameterDataTypeCombo->setMaxVisibleItems(30); - - setWindowState(Qt::WindowMaximized); - updateFunctionListUI(); -} - -GeneratorMainWindow::~GeneratorMainWindow() -{ - delete ui; -} - -void GeneratorMainWindow::load(const QString& fileName) -{ - QFile file(fileName); - if (file.open(QFile::ReadOnly | QFile::Truncate)) - { - QTextStream stream(&file); - stream.setCodec("UTF-8"); - - QDomDocument document; - document.setContent(stream.readAll()); - m_generator->load(document); - file.close(); - } -} - -void GeneratorMainWindow::saveSettings() -{ - QSettings settings("MelkaJ"); - settings.setValue("fileName", m_defaultFileName); - settings.setValue("headerFile", m_headerFileName); - settings.setValue("sourceFile", m_sourceFileName); -} - -void GeneratorMainWindow::loadGeneratedSettings() -{ - BoolGuard guard(m_isLoadingData); - - const bool hasName = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Name); - const bool hasItemType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::ItemType); - const bool hasDataType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::DataType); - const bool hasValue = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Value); - const bool hasDescription = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Description); - - ui->parameterNameEdit->setEnabled(hasName); - ui->parameterItemTypeCombo->setEnabled(hasItemType); - ui->parameterDataTypeCombo->setEnabled(hasDataType); - ui->parameterValueEdit->setEnabled(hasValue); - ui->parameterDescriptionEdit->setEnabled(hasDescription); - - if (hasName) - { - ui->parameterNameEdit->setText(m_currentSettings->readField(codegen::GeneratedBase::FieldType::Name).toString()); - } - else - { - ui->parameterNameEdit->clear(); - } - - if (hasItemType) - { - m_currentSettings->fillComboBox(ui->parameterItemTypeCombo, codegen::GeneratedBase::FieldType::ItemType); - } - else - { - ui->parameterItemTypeCombo->clear(); - } - - if (hasDataType) - { - m_currentSettings->fillComboBox(ui->parameterDataTypeCombo, codegen::GeneratedBase::FieldType::DataType); - } - else - { - ui->parameterDataTypeCombo->clear(); - } - - if (hasValue) - { - ui->parameterValueEdit->setText(m_currentSettings->readField(codegen::GeneratedBase::FieldType::Value).toString()); - } - else - { - ui->parameterValueEdit->clear(); - } - - if (hasDescription) - { - ui->parameterDescriptionEdit->setText(m_currentSettings->readField(codegen::GeneratedBase::FieldType::Description).toString()); - } - else - { - ui->parameterDescriptionEdit->clear(); - } - - ui->itemDeleteButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::Delete)); - ui->itemUpButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::MoveUp)); - ui->itemDownButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::MoveDown)); - ui->itemNewSiblingButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::NewSibling)); - ui->itemNewChildButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::NewChild)); -} - -void GeneratorMainWindow::saveGeneratedSettings() -{ - if (m_isLoadingData || !m_currentSettings) - { - return; - } - - const bool hasName = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Name); - const bool hasItemType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::ItemType); - const bool hasDataType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::DataType); - const bool hasValue = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Value); - const bool hasDescription = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Description); - - m_currentSettings->writeField(codegen::GeneratedBase::FieldType::Name, hasName ? ui->parameterNameEdit->text() : QVariant()); - m_currentSettings->writeField(codegen::GeneratedBase::FieldType::ItemType, hasItemType ? ui->parameterItemTypeCombo->currentData() : QVariant()); - m_currentSettings->writeField(codegen::GeneratedBase::FieldType::DataType, hasDataType ? ui->parameterDataTypeCombo->currentData() : QVariant()); - m_currentSettings->writeField(codegen::GeneratedBase::FieldType::Value, hasValue ? ui->parameterValueEdit->text() : QVariant()); - m_currentSettings->writeField(codegen::GeneratedBase::FieldType::Description, hasDescription ? ui->parameterDescriptionEdit->toPlainText() : QVariant()); - - if (sender() != ui->parameterDescriptionEdit) - { - loadGeneratedSettings(); - - // Update the captions - std::function updateFunction = [&](QTreeWidgetItem* item) - { - const codegen::GeneratedBase* generatedBase = item->data(0, Qt::UserRole).value(); - QStringList captions = generatedBase->getCaptions(); - - for (int i = 0; i < captions.size(); ++i) - { - item->setText(i, captions[i]); - } - - for (int i = 0; i < item->childCount(); ++i) - { - updateFunction(item->child(i)); - } - }; - updateFunction(ui->parameterTreeWidget->topLevelItem(0)); - } -} - -void GeneratorMainWindow::updateFunctionListUI() -{ - BoolGuard guard(m_isLoadingData); - - ui->generatedFunctionsTreeWidget->setUpdatesEnabled(false); - ui->generatedFunctionsTreeWidget->clear(); - m_mapFunctionToWidgetItem.clear(); - - // First, create roots - std::map mapFunctionTypeToRoot; - for (QObject* functionObject : m_generator->getFunctions()) - { - codegen::GeneratedFunction* function = qobject_cast(functionObject); - Q_ASSERT(function); - - if (!mapFunctionTypeToRoot.count(function->getFunctionType())) - { - QTreeWidgetItem* subroot = new QTreeWidgetItem(ui->generatedFunctionsTreeWidget, QStringList(function->getFunctionTypeString())); - subroot->setFlags(Qt::ItemIsEnabled); - mapFunctionTypeToRoot[function->getFunctionType()] = subroot; - } - } - ui->generatedFunctionsTreeWidget->sortByColumn(0, Qt::AscendingOrder); - - for (QObject* functionObject : m_generator->getFunctions()) - { - codegen::GeneratedFunction* function = qobject_cast(functionObject); - Q_ASSERT(function); - - QTreeWidgetItem* functionItem = new QTreeWidgetItem(mapFunctionTypeToRoot[function->getFunctionType()], QStringList(function->getFunctionName())); - functionItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - functionItem->setData(0, Qt::UserRole, QVariant::fromValue(function)); - m_mapFunctionToWidgetItem[function] = functionItem; - } - - ui->generatedFunctionsTreeWidget->expandAll(); - ui->generatedFunctionsTreeWidget->setUpdatesEnabled(true); - - // Select current function - auto it = m_mapFunctionToWidgetItem.find(m_currentFunction); - if (it != m_mapFunctionToWidgetItem.cend()) - { - ui->generatedFunctionsTreeWidget->setCurrentItem(it->second, 0, QItemSelectionModel::SelectCurrent); - } -} - -void GeneratorMainWindow::updateGeneratedSettingsTree() -{ - BoolGuard guard(m_isLoadingData); - - ui->parameterTreeWidget->setUpdatesEnabled(false); - ui->parameterTreeWidget->clear(); - - QTreeWidgetItem* selected = nullptr; - std::function createTree = [this, &selected, &createTree](codegen::GeneratedBase* generatedItem, QTreeWidgetItem* parent) - { - QTreeWidgetItem* item = nullptr; - if (parent) - { - item = new QTreeWidgetItem(parent, generatedItem->getCaptions()); - } - else - { - item = new QTreeWidgetItem(ui->parameterTreeWidget, generatedItem->getCaptions()); - } - - item->setData(0, Qt::UserRole, QVariant::fromValue(generatedItem)); - - if (generatedItem == m_currentSettings) - { - selected = item; - } - - for (QObject* object : generatedItem->getItems()) - { - createTree(qobject_cast(object), item); - } - }; - - if (m_currentFunction) - { - createTree(m_currentFunction, nullptr); - } - - ui->parameterTreeWidget->expandAll(); - ui->parameterTreeWidget->resizeColumnToContents(0); - ui->parameterTreeWidget->setUpdatesEnabled(true); - - if (selected) - { - ui->parameterTreeWidget->setCurrentItem(selected, 0, QItemSelectionModel::SelectCurrent); - } -} - -void GeneratorMainWindow::setCurrentFunction(codegen::GeneratedFunction* currentFunction) -{ - if (m_currentFunction != currentFunction) - { - BoolGuard guard(m_isLoadingData); - m_currentFunction = currentFunction; - - // Select current function - auto it = m_mapFunctionToWidgetItem.find(m_currentFunction); - if (it != m_mapFunctionToWidgetItem.cend()) - { - ui->generatedFunctionsTreeWidget->setCurrentItem(it->second, 0, QItemSelectionModel::SelectCurrent); - ui->generatedFunctionsTreeWidget->setFocus(); - setCurrentSettings(m_currentFunction); - updateGeneratedSettingsTree(); - } - } -} - -void GeneratorMainWindow::setCurrentSettings(codegen::GeneratedBase* currentSettings) -{ - if (m_currentSettings != currentSettings) - { - BoolGuard guard(m_isLoadingData); - m_currentSettings = currentSettings; - loadGeneratedSettings(); - } -} - -void GeneratorMainWindow::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) -{ - Q_UNUSED(previous); - - if (m_isLoadingData) - { - return; - } - - codegen::GeneratedFunction* function = current->data(0, Qt::UserRole).value(); - setCurrentFunction(function); -} - -void GeneratorMainWindow::onParameterTreeCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) -{ - Q_UNUSED(previous); - - if (m_isLoadingData) - { - return; - } - - codegen::GeneratedBase* baseObject = current->data(0, Qt::UserRole).value(); - setCurrentSettings(baseObject); -} - -void GeneratorMainWindow::loadSettings() -{ - QSettings settings("MelkaJ"); - m_defaultFileName = settings.value("fileName").toString(); - m_headerFileName = settings.value("headerFile", QVariant()).toString(); - m_sourceFileName = settings.value("sourceFile", QVariant()).toString(); -} - -void GeneratorMainWindow::save(const QString& fileName) -{ - QFile file(fileName); - if (file.open(QFile::WriteOnly | QFile::Truncate)) - { - QTextStream stream(&file); - stream.setCodec("UTF-8"); - - QDomDocument document; - m_generator->store(document); - document.save(stream, 2, QDomDocument::EncodingFromTextStream); - file.close(); - - // Update default file name - m_defaultFileName = fileName; - saveSettings(); - } -} - -void GeneratorMainWindow::on_actionLoad_triggered() -{ - QFileInfo info(m_defaultFileName); - QString fileName = QFileDialog::getOpenFileName(this, tr("Select XML definition file"), info.dir().absolutePath(), "XML files (*.xml)"); - if (!fileName.isEmpty()) - { - m_defaultFileName = fileName; - saveSettings(); - - load(m_defaultFileName); - } -} - -void GeneratorMainWindow::on_actionSaveAs_triggered() -{ - QFileInfo info(m_defaultFileName); - QString fileName = QFileDialog::getSaveFileName(this, tr("Select XML definition file"), info.dir().absolutePath(), "XML files (*.xml)"); - if (!fileName.isEmpty()) - { - save(fileName); - } -} - -void GeneratorMainWindow::on_actionSave_triggered() -{ - if (!m_defaultFileName.isEmpty()) - { - save(m_defaultFileName); - } - else - { - on_actionSaveAs_triggered(); - } -} - -void GeneratorMainWindow::on_newFunctionButton_clicked() -{ - QString functionName = QInputDialog::getText(this, tr("Create function"), tr("Enter function name")); - if (!functionName.isEmpty()) - { - codegen::GeneratedFunction* generatedFunction = m_generator->addFunction(functionName); - updateFunctionListUI(); - setCurrentFunction(generatedFunction); - } -} - -void GeneratorMainWindow::on_cloneFunctionButton_clicked() -{ - if (m_currentFunction) - { - codegen::GeneratedFunction* generatedFunction = m_generator->addFunction(m_currentFunction->clone(nullptr)); - updateFunctionListUI(); - setCurrentFunction(generatedFunction); - } -} - -void GeneratorMainWindow::on_removeFunctionButton_clicked() -{ - if (m_currentFunction) - { - codegen::GeneratedFunction* functionToDelete = m_currentFunction; - setCurrentFunction(nullptr); - m_generator->removeFunction(functionToDelete); - updateFunctionListUI(); - } -} - -void GeneratorMainWindow::on_itemDeleteButton_clicked() -{ - if (m_currentSettings) - { - m_currentSettings->performOperation(codegen::GeneratedBase::Operation::Delete); - setCurrentSettings(m_currentFunction); - updateGeneratedSettingsTree(); - } -} - -void GeneratorMainWindow::on_itemUpButton_clicked() -{ - if (m_currentSettings) - { - m_currentSettings->performOperation(codegen::GeneratedBase::Operation::MoveUp); - updateGeneratedSettingsTree(); - loadGeneratedSettings(); - } -} - -void GeneratorMainWindow::on_itemDownButton_clicked() -{ - if (m_currentSettings) - { - m_currentSettings->performOperation(codegen::GeneratedBase::Operation::MoveDown); - updateGeneratedSettingsTree(); - loadGeneratedSettings(); - } -} - -void GeneratorMainWindow::on_itemNewChildButton_clicked() -{ - if (m_currentSettings) - { - m_currentSettings->performOperation(codegen::GeneratedBase::Operation::NewChild); - updateGeneratedSettingsTree(); - loadGeneratedSettings(); - } -} - -void GeneratorMainWindow::on_itemNewSiblingButton_clicked() -{ - if (m_currentSettings) - { - m_currentSettings->performOperation(codegen::GeneratedBase::Operation::NewSibling); - updateGeneratedSettingsTree(); - loadGeneratedSettings(); - } -} - -void GeneratorMainWindow::on_actionSet_code_header_h_triggered() -{ - QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp header"), QString(), "cpp header (*.h)"); - if (!fileName.isEmpty()) - { - m_headerFileName = fileName; - saveSettings(); - } -} - -void GeneratorMainWindow::on_actionSet_code_source_cpp_triggered() -{ - QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp source"), QString(), "cpp source (*.cpp)"); - if (!fileName.isEmpty()) - { - m_sourceFileName = fileName; - saveSettings(); - } -} - -void GeneratorMainWindow::on_actionGenerate_code_triggered() -{ - if (m_generator) - { - m_generator->generateCode(m_headerFileName, m_sourceFileName); - } -} +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "generatormainwindow.h" +#include "ui_generatormainwindow.h" + +#include "codegenerator.h" + +#include +#include +#include +#include +#include +#include + +GeneratorMainWindow::GeneratorMainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::GeneratorMainWindow), + m_generator(new codegen::CodeGenerator(this)), + m_currentFunction(nullptr), + m_currentSettings(nullptr), + m_isLoadingData(false) +{ + ui->setupUi(this); + + ui->parameterTreeWidget->header()->setMinimumSectionSize(200); + + m_generator->initialize(); + + loadSettings(); + + if (!m_defaultFileName.isEmpty()) + { + load(m_defaultFileName); + } + connect(ui->generatedFunctionsTreeWidget, &QTreeWidget::currentItemChanged, this, &GeneratorMainWindow::onCurrentItemChanged); + connect(ui->parameterTreeWidget, &QTreeWidget::currentItemChanged, this, &GeneratorMainWindow::onParameterTreeCurrentItemChanged); + connect(ui->parameterNameEdit, &QLineEdit::editingFinished, this, &GeneratorMainWindow::saveGeneratedSettings); + connect(ui->parameterItemTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &GeneratorMainWindow::saveGeneratedSettings); + connect(ui->parameterDataTypeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &GeneratorMainWindow::saveGeneratedSettings); + connect(ui->parameterValueEdit, &QLineEdit::editingFinished, this, &GeneratorMainWindow::saveGeneratedSettings); + connect(ui->parameterDescriptionEdit, &QTextBrowser::textChanged, this, &GeneratorMainWindow::saveGeneratedSettings); + + ui->parameterItemTypeCombo->setMaxVisibleItems(30); + ui->parameterDataTypeCombo->setMaxVisibleItems(30); + + setWindowState(Qt::WindowMaximized); + updateFunctionListUI(); +} + +GeneratorMainWindow::~GeneratorMainWindow() +{ + delete ui; +} + +void GeneratorMainWindow::load(const QString& fileName) +{ + QFile file(fileName); + if (file.open(QFile::ReadOnly | QFile::Truncate)) + { + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + QDomDocument document; + document.setContent(stream.readAll()); + m_generator->load(document); + file.close(); + } +} + +void GeneratorMainWindow::saveSettings() +{ + QSettings settings("MelkaJ"); + settings.setValue("fileName", m_defaultFileName); + settings.setValue("headerFile", m_headerFileName); + settings.setValue("sourceFile", m_sourceFileName); +} + +void GeneratorMainWindow::loadGeneratedSettings() +{ + BoolGuard guard(m_isLoadingData); + + const bool hasName = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Name); + const bool hasItemType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::ItemType); + const bool hasDataType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::DataType); + const bool hasValue = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Value); + const bool hasDescription = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Description); + + ui->parameterNameEdit->setEnabled(hasName); + ui->parameterItemTypeCombo->setEnabled(hasItemType); + ui->parameterDataTypeCombo->setEnabled(hasDataType); + ui->parameterValueEdit->setEnabled(hasValue); + ui->parameterDescriptionEdit->setEnabled(hasDescription); + + if (hasName) + { + ui->parameterNameEdit->setText(m_currentSettings->readField(codegen::GeneratedBase::FieldType::Name).toString()); + } + else + { + ui->parameterNameEdit->clear(); + } + + if (hasItemType) + { + m_currentSettings->fillComboBox(ui->parameterItemTypeCombo, codegen::GeneratedBase::FieldType::ItemType); + } + else + { + ui->parameterItemTypeCombo->clear(); + } + + if (hasDataType) + { + m_currentSettings->fillComboBox(ui->parameterDataTypeCombo, codegen::GeneratedBase::FieldType::DataType); + } + else + { + ui->parameterDataTypeCombo->clear(); + } + + if (hasValue) + { + ui->parameterValueEdit->setText(m_currentSettings->readField(codegen::GeneratedBase::FieldType::Value).toString()); + } + else + { + ui->parameterValueEdit->clear(); + } + + if (hasDescription) + { + ui->parameterDescriptionEdit->setText(m_currentSettings->readField(codegen::GeneratedBase::FieldType::Description).toString()); + } + else + { + ui->parameterDescriptionEdit->clear(); + } + + ui->itemDeleteButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::Delete)); + ui->itemUpButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::MoveUp)); + ui->itemDownButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::MoveDown)); + ui->itemNewSiblingButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::NewSibling)); + ui->itemNewChildButton->setEnabled(m_currentSettings && m_currentSettings->canPerformOperation(codegen::GeneratedBase::Operation::NewChild)); +} + +void GeneratorMainWindow::saveGeneratedSettings() +{ + if (m_isLoadingData || !m_currentSettings) + { + return; + } + + const bool hasName = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Name); + const bool hasItemType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::ItemType); + const bool hasDataType = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::DataType); + const bool hasValue = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Value); + const bool hasDescription = m_currentSettings && m_currentSettings->hasField(codegen::GeneratedBase::FieldType::Description); + + m_currentSettings->writeField(codegen::GeneratedBase::FieldType::Name, hasName ? ui->parameterNameEdit->text() : QVariant()); + m_currentSettings->writeField(codegen::GeneratedBase::FieldType::ItemType, hasItemType ? ui->parameterItemTypeCombo->currentData() : QVariant()); + m_currentSettings->writeField(codegen::GeneratedBase::FieldType::DataType, hasDataType ? ui->parameterDataTypeCombo->currentData() : QVariant()); + m_currentSettings->writeField(codegen::GeneratedBase::FieldType::Value, hasValue ? ui->parameterValueEdit->text() : QVariant()); + m_currentSettings->writeField(codegen::GeneratedBase::FieldType::Description, hasDescription ? ui->parameterDescriptionEdit->toPlainText() : QVariant()); + + if (sender() != ui->parameterDescriptionEdit) + { + loadGeneratedSettings(); + + // Update the captions + std::function updateFunction = [&](QTreeWidgetItem* item) + { + const codegen::GeneratedBase* generatedBase = item->data(0, Qt::UserRole).value(); + QStringList captions = generatedBase->getCaptions(); + + for (int i = 0; i < captions.size(); ++i) + { + item->setText(i, captions[i]); + } + + for (int i = 0; i < item->childCount(); ++i) + { + updateFunction(item->child(i)); + } + }; + updateFunction(ui->parameterTreeWidget->topLevelItem(0)); + } +} + +void GeneratorMainWindow::updateFunctionListUI() +{ + BoolGuard guard(m_isLoadingData); + + ui->generatedFunctionsTreeWidget->setUpdatesEnabled(false); + ui->generatedFunctionsTreeWidget->clear(); + m_mapFunctionToWidgetItem.clear(); + + // First, create roots + std::map mapFunctionTypeToRoot; + for (QObject* functionObject : m_generator->getFunctions()) + { + codegen::GeneratedFunction* function = qobject_cast(functionObject); + Q_ASSERT(function); + + if (!mapFunctionTypeToRoot.count(function->getFunctionType())) + { + QTreeWidgetItem* subroot = new QTreeWidgetItem(ui->generatedFunctionsTreeWidget, QStringList(function->getFunctionTypeString())); + subroot->setFlags(Qt::ItemIsEnabled); + mapFunctionTypeToRoot[function->getFunctionType()] = subroot; + } + } + ui->generatedFunctionsTreeWidget->sortByColumn(0, Qt::AscendingOrder); + + for (QObject* functionObject : m_generator->getFunctions()) + { + codegen::GeneratedFunction* function = qobject_cast(functionObject); + Q_ASSERT(function); + + QTreeWidgetItem* functionItem = new QTreeWidgetItem(mapFunctionTypeToRoot[function->getFunctionType()], QStringList(function->getFunctionName())); + functionItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + functionItem->setData(0, Qt::UserRole, QVariant::fromValue(function)); + m_mapFunctionToWidgetItem[function] = functionItem; + } + + ui->generatedFunctionsTreeWidget->expandAll(); + ui->generatedFunctionsTreeWidget->setUpdatesEnabled(true); + + // Select current function + auto it = m_mapFunctionToWidgetItem.find(m_currentFunction); + if (it != m_mapFunctionToWidgetItem.cend()) + { + ui->generatedFunctionsTreeWidget->setCurrentItem(it->second, 0, QItemSelectionModel::SelectCurrent); + } +} + +void GeneratorMainWindow::updateGeneratedSettingsTree() +{ + BoolGuard guard(m_isLoadingData); + + ui->parameterTreeWidget->setUpdatesEnabled(false); + ui->parameterTreeWidget->clear(); + + QTreeWidgetItem* selected = nullptr; + std::function createTree = [this, &selected, &createTree](codegen::GeneratedBase* generatedItem, QTreeWidgetItem* parent) + { + QTreeWidgetItem* item = nullptr; + if (parent) + { + item = new QTreeWidgetItem(parent, generatedItem->getCaptions()); + } + else + { + item = new QTreeWidgetItem(ui->parameterTreeWidget, generatedItem->getCaptions()); + } + + item->setData(0, Qt::UserRole, QVariant::fromValue(generatedItem)); + + if (generatedItem == m_currentSettings) + { + selected = item; + } + + for (QObject* object : generatedItem->getItems()) + { + createTree(qobject_cast(object), item); + } + }; + + if (m_currentFunction) + { + createTree(m_currentFunction, nullptr); + } + + ui->parameterTreeWidget->expandAll(); + ui->parameterTreeWidget->resizeColumnToContents(0); + ui->parameterTreeWidget->setUpdatesEnabled(true); + + if (selected) + { + ui->parameterTreeWidget->setCurrentItem(selected, 0, QItemSelectionModel::SelectCurrent); + } +} + +void GeneratorMainWindow::setCurrentFunction(codegen::GeneratedFunction* currentFunction) +{ + if (m_currentFunction != currentFunction) + { + BoolGuard guard(m_isLoadingData); + m_currentFunction = currentFunction; + + // Select current function + auto it = m_mapFunctionToWidgetItem.find(m_currentFunction); + if (it != m_mapFunctionToWidgetItem.cend()) + { + ui->generatedFunctionsTreeWidget->setCurrentItem(it->second, 0, QItemSelectionModel::SelectCurrent); + ui->generatedFunctionsTreeWidget->setFocus(); + setCurrentSettings(m_currentFunction); + updateGeneratedSettingsTree(); + } + } +} + +void GeneratorMainWindow::setCurrentSettings(codegen::GeneratedBase* currentSettings) +{ + if (m_currentSettings != currentSettings) + { + BoolGuard guard(m_isLoadingData); + m_currentSettings = currentSettings; + loadGeneratedSettings(); + } +} + +void GeneratorMainWindow::onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) +{ + Q_UNUSED(previous); + + if (m_isLoadingData) + { + return; + } + + codegen::GeneratedFunction* function = current->data(0, Qt::UserRole).value(); + setCurrentFunction(function); +} + +void GeneratorMainWindow::onParameterTreeCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous) +{ + Q_UNUSED(previous); + + if (m_isLoadingData) + { + return; + } + + codegen::GeneratedBase* baseObject = current->data(0, Qt::UserRole).value(); + setCurrentSettings(baseObject); +} + +void GeneratorMainWindow::loadSettings() +{ + QSettings settings("MelkaJ"); + m_defaultFileName = settings.value("fileName").toString(); + m_headerFileName = settings.value("headerFile", QVariant()).toString(); + m_sourceFileName = settings.value("sourceFile", QVariant()).toString(); +} + +void GeneratorMainWindow::save(const QString& fileName) +{ + QFile file(fileName); + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + QDomDocument document; + m_generator->store(document); + document.save(stream, 2, QDomDocument::EncodingFromTextStream); + file.close(); + + // Update default file name + m_defaultFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionLoad_triggered() +{ + QFileInfo info(m_defaultFileName); + QString fileName = QFileDialog::getOpenFileName(this, tr("Select XML definition file"), info.dir().absolutePath(), "XML files (*.xml)"); + if (!fileName.isEmpty()) + { + m_defaultFileName = fileName; + saveSettings(); + + load(m_defaultFileName); + } +} + +void GeneratorMainWindow::on_actionSaveAs_triggered() +{ + QFileInfo info(m_defaultFileName); + QString fileName = QFileDialog::getSaveFileName(this, tr("Select XML definition file"), info.dir().absolutePath(), "XML files (*.xml)"); + if (!fileName.isEmpty()) + { + save(fileName); + } +} + +void GeneratorMainWindow::on_actionSave_triggered() +{ + if (!m_defaultFileName.isEmpty()) + { + save(m_defaultFileName); + } + else + { + on_actionSaveAs_triggered(); + } +} + +void GeneratorMainWindow::on_newFunctionButton_clicked() +{ + QString functionName = QInputDialog::getText(this, tr("Create function"), tr("Enter function name")); + if (!functionName.isEmpty()) + { + codegen::GeneratedFunction* generatedFunction = m_generator->addFunction(functionName); + updateFunctionListUI(); + setCurrentFunction(generatedFunction); + } +} + +void GeneratorMainWindow::on_cloneFunctionButton_clicked() +{ + if (m_currentFunction) + { + codegen::GeneratedFunction* generatedFunction = m_generator->addFunction(m_currentFunction->clone(nullptr)); + updateFunctionListUI(); + setCurrentFunction(generatedFunction); + } +} + +void GeneratorMainWindow::on_removeFunctionButton_clicked() +{ + if (m_currentFunction) + { + codegen::GeneratedFunction* functionToDelete = m_currentFunction; + setCurrentFunction(nullptr); + m_generator->removeFunction(functionToDelete); + updateFunctionListUI(); + } +} + +void GeneratorMainWindow::on_itemDeleteButton_clicked() +{ + if (m_currentSettings) + { + m_currentSettings->performOperation(codegen::GeneratedBase::Operation::Delete); + setCurrentSettings(m_currentFunction); + updateGeneratedSettingsTree(); + } +} + +void GeneratorMainWindow::on_itemUpButton_clicked() +{ + if (m_currentSettings) + { + m_currentSettings->performOperation(codegen::GeneratedBase::Operation::MoveUp); + updateGeneratedSettingsTree(); + loadGeneratedSettings(); + } +} + +void GeneratorMainWindow::on_itemDownButton_clicked() +{ + if (m_currentSettings) + { + m_currentSettings->performOperation(codegen::GeneratedBase::Operation::MoveDown); + updateGeneratedSettingsTree(); + loadGeneratedSettings(); + } +} + +void GeneratorMainWindow::on_itemNewChildButton_clicked() +{ + if (m_currentSettings) + { + m_currentSettings->performOperation(codegen::GeneratedBase::Operation::NewChild); + updateGeneratedSettingsTree(); + loadGeneratedSettings(); + } +} + +void GeneratorMainWindow::on_itemNewSiblingButton_clicked() +{ + if (m_currentSettings) + { + m_currentSettings->performOperation(codegen::GeneratedBase::Operation::NewSibling); + updateGeneratedSettingsTree(); + loadGeneratedSettings(); + } +} + +void GeneratorMainWindow::on_actionSet_code_header_h_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp header"), QString(), "cpp header (*.h)"); + if (!fileName.isEmpty()) + { + m_headerFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionSet_code_source_cpp_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select cpp source"), QString(), "cpp source (*.cpp)"); + if (!fileName.isEmpty()) + { + m_sourceFileName = fileName; + saveSettings(); + } +} + +void GeneratorMainWindow::on_actionGenerate_code_triggered() +{ + if (m_generator) + { + m_generator->generateCode(m_headerFileName, m_sourceFileName); + } +} diff --git a/CodeGenerator/generatormainwindow.h b/CodeGenerator/generatormainwindow.h index b41cba3..dc510d2 100644 --- a/CodeGenerator/generatormainwindow.h +++ b/CodeGenerator/generatormainwindow.h @@ -1,109 +1,109 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef GENERATORMAINWINDOW_H -#define GENERATORMAINWINDOW_H - -#include - -class QTreeWidgetItem; - -namespace codegen -{ -class CodeGenerator; -class GeneratedFunction; -class GeneratedBase; -} - -namespace Ui -{ -class GeneratorMainWindow; -} - -class BoolGuard -{ -public: - explicit inline BoolGuard(bool& value) : - m_oldValue(value), - m_value(&value) - { - *m_value = true; - } - - inline ~BoolGuard() - { - *m_value = m_oldValue; - } - -private: - bool m_oldValue; - bool* m_value; -}; - -class GeneratorMainWindow : public QMainWindow -{ - Q_OBJECT - -public: - GeneratorMainWindow(QWidget* parent = nullptr); - virtual ~GeneratorMainWindow() override; - - void load(const QString& fileName); - void save(const QString& fileName); - -private slots: - void on_actionLoad_triggered(); - void on_actionSaveAs_triggered(); - void on_actionSave_triggered(); - void on_newFunctionButton_clicked(); - void on_cloneFunctionButton_clicked(); - void on_removeFunctionButton_clicked(); - void on_itemDeleteButton_clicked(); - void on_itemUpButton_clicked(); - void on_itemDownButton_clicked(); - void on_itemNewChildButton_clicked(); - void on_itemNewSiblingButton_clicked(); - void on_actionSet_code_header_h_triggered(); - void on_actionSet_code_source_cpp_triggered(); - void on_actionGenerate_code_triggered(); - -private: - void loadSettings(); - void saveSettings(); - - void loadGeneratedSettings(); - void saveGeneratedSettings(); - - void updateFunctionListUI(); - void updateGeneratedSettingsTree(); - void setCurrentFunction(codegen::GeneratedFunction* currentFunction); - void setCurrentSettings(codegen::GeneratedBase* currentSettings); - void onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); - void onParameterTreeCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); - - Ui::GeneratorMainWindow* ui; - codegen::CodeGenerator* m_generator; - codegen::GeneratedFunction* m_currentFunction; - codegen::GeneratedBase* m_currentSettings; - QString m_defaultFileName; - QString m_headerFileName; - QString m_sourceFileName; - std::map m_mapFunctionToWidgetItem; - bool m_isLoadingData; -}; - -#endif // GENERATORMAINWINDOW_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef GENERATORMAINWINDOW_H +#define GENERATORMAINWINDOW_H + +#include + +class QTreeWidgetItem; + +namespace codegen +{ +class CodeGenerator; +class GeneratedFunction; +class GeneratedBase; +} + +namespace Ui +{ +class GeneratorMainWindow; +} + +class BoolGuard +{ +public: + explicit inline BoolGuard(bool& value) : + m_oldValue(value), + m_value(&value) + { + *m_value = true; + } + + inline ~BoolGuard() + { + *m_value = m_oldValue; + } + +private: + bool m_oldValue; + bool* m_value; +}; + +class GeneratorMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + GeneratorMainWindow(QWidget* parent = nullptr); + virtual ~GeneratorMainWindow() override; + + void load(const QString& fileName); + void save(const QString& fileName); + +private slots: + void on_actionLoad_triggered(); + void on_actionSaveAs_triggered(); + void on_actionSave_triggered(); + void on_newFunctionButton_clicked(); + void on_cloneFunctionButton_clicked(); + void on_removeFunctionButton_clicked(); + void on_itemDeleteButton_clicked(); + void on_itemUpButton_clicked(); + void on_itemDownButton_clicked(); + void on_itemNewChildButton_clicked(); + void on_itemNewSiblingButton_clicked(); + void on_actionSet_code_header_h_triggered(); + void on_actionSet_code_source_cpp_triggered(); + void on_actionGenerate_code_triggered(); + +private: + void loadSettings(); + void saveSettings(); + + void loadGeneratedSettings(); + void saveGeneratedSettings(); + + void updateFunctionListUI(); + void updateGeneratedSettingsTree(); + void setCurrentFunction(codegen::GeneratedFunction* currentFunction); + void setCurrentSettings(codegen::GeneratedBase* currentSettings); + void onCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); + void onParameterTreeCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); + + Ui::GeneratorMainWindow* ui; + codegen::CodeGenerator* m_generator; + codegen::GeneratedFunction* m_currentFunction; + codegen::GeneratedBase* m_currentSettings; + QString m_defaultFileName; + QString m_headerFileName; + QString m_sourceFileName; + std::map m_mapFunctionToWidgetItem; + bool m_isLoadingData; +}; + +#endif // GENERATORMAINWINDOW_H diff --git a/CodeGenerator/generatormainwindow.ui b/CodeGenerator/generatormainwindow.ui index 35d5ebd..10fac04 100644 --- a/CodeGenerator/generatormainwindow.ui +++ b/CodeGenerator/generatormainwindow.ui @@ -1,290 +1,290 @@ - - - GeneratorMainWindow - - - - 0 - 0 - 1121 - 662 - - - - Code Generator - - - - - - - - - true - - - false - - - - 1 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Remove - - - - - - - Clone - - - - - - - New - - - - - - - - - - - - - Parameters - - - - - - Data type - - - - - - - - - - - - - - - - false - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Value - - - - - - - Item type - - - - - - - Name - - - - - - - Text description / C++ code - - - - - - - - - - - - - 2 - - - false - - - - 1 - - - - - 2 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Delete - - - - - - - Up - - - - - - - Down - - - - - - - New Child - - - - - - - New Sibling - - - - - - - - - - - - - 0 - 0 - 1121 - 21 - - - - - File - - - - - - - - Code - - - - - - - - - - - - Load - - - Ctrl+O - - - - - Save - - - Ctrl+S - - - - - Save As... - - - - - Set code header (*.h) - - - - - Set code source (*.cpp) - - - - - Generate code - - - Ctrl+G - - - - - - + + + GeneratorMainWindow + + + + 0 + 0 + 1121 + 662 + + + + Code Generator + + + + + + + + + true + + + false + + + + 1 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Remove + + + + + + + Clone + + + + + + + New + + + + + + + + + + + + + Parameters + + + + + + Data type + + + + + + + + + + + + + + + + false + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Value + + + + + + + Item type + + + + + + + Name + + + + + + + Text description / C++ code + + + + + + + + + + + + + 2 + + + false + + + + 1 + + + + + 2 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete + + + + + + + Up + + + + + + + Down + + + + + + + New Child + + + + + + + New Sibling + + + + + + + + + + + + + 0 + 0 + 1121 + 21 + + + + + File + + + + + + + + Code + + + + + + + + + + + + Load + + + Ctrl+O + + + + + Save + + + Ctrl+S + + + + + Save As... + + + + + Set code header (*.h) + + + + + Set code source (*.cpp) + + + + + Generate code + + + Ctrl+G + + + + + + diff --git a/CodeGenerator/main.cpp b/CodeGenerator/main.cpp index 28ea8da..8bde156 100644 --- a/CodeGenerator/main.cpp +++ b/CodeGenerator/main.cpp @@ -1,30 +1,30 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "generatormainwindow.h" - -#include -#include - -int main(int argc, char *argv[]) -{ - qSetGlobalQHashSeed(0); - QApplication a(argc, argv); - GeneratorMainWindow w; - w.show(); - return a.exec(); -} +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "generatormainwindow.h" + +#include +#include + +int main(int argc, char *argv[]) +{ + qSetGlobalQHashSeed(0); + QApplication a(argc, argv); + GeneratorMainWindow w; + w.show(); + return a.exec(); +} diff --git a/JBIG2_Viewer/JBIG2_Viewer.pro b/JBIG2_Viewer/JBIG2_Viewer.pro index f7580d3..fcaa889 100644 --- a/JBIG2_Viewer/JBIG2_Viewer.pro +++ b/JBIG2_Viewer/JBIG2_Viewer.pro @@ -1,36 +1,36 @@ -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -CONFIG += c++11 - -# The following define makes your compiler emit warnings if you use -# any Qt feature that has been marked deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if it uses deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -QMAKE_CXXFLAGS += /std:c++latest /utf-8 - -INCLUDEPATH += $$PWD/../PDF4QtLib/Sources - -DESTDIR = $$OUT_PWD/.. - -LIBS += -L$$OUT_PWD/.. - -LIBS += -lPDF4QtLib - -SOURCES += \ - main.cpp \ - mainwindow.cpp - -HEADERS += \ - mainwindow.h - -FORMS += \ - mainwindow.ui +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +QMAKE_CXXFLAGS += /std:c++latest /utf-8 + +INCLUDEPATH += $$PWD/../PDF4QtLib/Sources + +DESTDIR = $$OUT_PWD/.. + +LIBS += -L$$OUT_PWD/.. + +LIBS += -lPDF4QtLib + +SOURCES += \ + main.cpp \ + mainwindow.cpp + +HEADERS += \ + mainwindow.h + +FORMS += \ + mainwindow.ui diff --git a/JBIG2_Viewer/main.cpp b/JBIG2_Viewer/main.cpp index 9cfa17b..97190a8 100644 --- a/JBIG2_Viewer/main.cpp +++ b/JBIG2_Viewer/main.cpp @@ -1,33 +1,33 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "mainwindow.h" - -#include - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - - QCoreApplication::setOrganizationName("MelkaJ"); - QCoreApplication::setApplicationName("JBIG2_image_viewer"); - QCoreApplication::setApplicationVersion("1.0"); - - MainWindow w; - w.show(); - return a.exec(); -} +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QCoreApplication::setOrganizationName("MelkaJ"); + QCoreApplication::setApplicationName("JBIG2_image_viewer"); + QCoreApplication::setApplicationVersion("1.0"); + + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/JBIG2_Viewer/mainwindow.cpp b/JBIG2_Viewer/mainwindow.cpp index 592012b..a3e5f57 100644 --- a/JBIG2_Viewer/mainwindow.cpp +++ b/JBIG2_Viewer/mainwindow.cpp @@ -1,121 +1,121 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include "pdfexception.h" -#include "pdfjbig2decoder.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MainWindow::MainWindow(QWidget* parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - setWindowState(Qt::WindowMaximized); - - QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); - m_directory = settings.value("Directory").toString(); -} - -MainWindow::~MainWindow() -{ - QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); - settings.setValue("Directory", m_directory); - - delete ui; -} - -void MainWindow::reportRenderError(pdf::RenderErrorType type, QString message) -{ - Q_UNUSED(type); - QMessageBox::critical(this, tr("Error"), message); -} - -void MainWindow::on_actionAddImage_triggered() -{ - QString fileName = QFileDialog::getOpenFileName(this, tr("Open image"), m_directory, QString("Bitmap image (*.bmp)")); - if (QFile::exists(fileName)) - { - QImageReader reader(fileName); - QImage image = reader.read(); - - if (!image.isNull()) - { - QFileInfo file(fileName); - m_directory = file.filePath(); - addImage(file.fileName(), qMove(image)); - } - } -} - -void MainWindow::addImage(QString title, QImage image) -{ - QGroupBox* groupBox = new QGroupBox(this); - QHBoxLayout* layout = new QHBoxLayout(groupBox); - groupBox->setTitle(title); - QLabel* label = new QLabel(groupBox); - layout->addWidget(label); - label->setPixmap(QPixmap::fromImage(image)); - label->setFixedSize(image.size()); - ui->imagesMainLayout->addWidget(groupBox); -} - -void MainWindow::on_actionClear_triggered() -{ - for (int i = 0; i < ui->imagesMainLayout->count(); ++i) - { - ui->imagesMainLayout->itemAt(i)->widget()->deleteLater(); - } -} - -void MainWindow::on_actionAdd_JBIG2_image_triggered() -{ - QString fileName = QFileDialog::getOpenFileName(this, tr("Open image"), m_directory, QString("JBIG2 image (*.jb2)")); - if (QFile::exists(fileName)) - { - QFile file(fileName); - if (file.open(QFile::ReadOnly)) - { - m_directory = QFileInfo(file).filePath(); - QByteArray data = file.readAll(); - file.close(); - - try - { - pdf::PDFJBIG2Decoder decoder(data, QByteArray(), this); - - QElapsedTimer timer; - timer.start(); - pdf::PDFImageData imageData = decoder.decodeFileStream(); - qint64 time = timer.elapsed(); - - if (imageData.isValid()) - { - QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_Mono); - const uchar* sourceData = reinterpret_cast(imageData.getData().constData()); - Q_ASSERT(imageData.getData().size() == image.byteCount()); - std::transform(sourceData, sourceData + imageData.getData().size(), image.bits(), [](const uchar value) { return value; }); - addImage(file.fileName() + QString(", Decoded in %1 [msec]").arg(time), qMove(image)); - } - } - catch (pdf::PDFException exception) - { - QMessageBox::critical(this, tr("Error"), exception.getMessage()); - } - } - } -} - -void MainWindow::reportRenderErrorOnce(pdf::RenderErrorType type, QString message) -{ - Q_UNUSED(type); - QMessageBox::critical(this, tr("Error"), message); -} +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "pdfexception.h" +#include "pdfjbig2decoder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget* parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + setWindowState(Qt::WindowMaximized); + + QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); + m_directory = settings.value("Directory").toString(); +} + +MainWindow::~MainWindow() +{ + QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); + settings.setValue("Directory", m_directory); + + delete ui; +} + +void MainWindow::reportRenderError(pdf::RenderErrorType type, QString message) +{ + Q_UNUSED(type); + QMessageBox::critical(this, tr("Error"), message); +} + +void MainWindow::on_actionAddImage_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open image"), m_directory, QString("Bitmap image (*.bmp)")); + if (QFile::exists(fileName)) + { + QImageReader reader(fileName); + QImage image = reader.read(); + + if (!image.isNull()) + { + QFileInfo file(fileName); + m_directory = file.filePath(); + addImage(file.fileName(), qMove(image)); + } + } +} + +void MainWindow::addImage(QString title, QImage image) +{ + QGroupBox* groupBox = new QGroupBox(this); + QHBoxLayout* layout = new QHBoxLayout(groupBox); + groupBox->setTitle(title); + QLabel* label = new QLabel(groupBox); + layout->addWidget(label); + label->setPixmap(QPixmap::fromImage(image)); + label->setFixedSize(image.size()); + ui->imagesMainLayout->addWidget(groupBox); +} + +void MainWindow::on_actionClear_triggered() +{ + for (int i = 0; i < ui->imagesMainLayout->count(); ++i) + { + ui->imagesMainLayout->itemAt(i)->widget()->deleteLater(); + } +} + +void MainWindow::on_actionAdd_JBIG2_image_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open image"), m_directory, QString("JBIG2 image (*.jb2)")); + if (QFile::exists(fileName)) + { + QFile file(fileName); + if (file.open(QFile::ReadOnly)) + { + m_directory = QFileInfo(file).filePath(); + QByteArray data = file.readAll(); + file.close(); + + try + { + pdf::PDFJBIG2Decoder decoder(data, QByteArray(), this); + + QElapsedTimer timer; + timer.start(); + pdf::PDFImageData imageData = decoder.decodeFileStream(); + qint64 time = timer.elapsed(); + + if (imageData.isValid()) + { + QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_Mono); + const uchar* sourceData = reinterpret_cast(imageData.getData().constData()); + Q_ASSERT(imageData.getData().size() == image.byteCount()); + std::transform(sourceData, sourceData + imageData.getData().size(), image.bits(), [](const uchar value) { return value; }); + addImage(file.fileName() + QString(", Decoded in %1 [msec]").arg(time), qMove(image)); + } + } + catch (pdf::PDFException exception) + { + QMessageBox::critical(this, tr("Error"), exception.getMessage()); + } + } + } +} + +void MainWindow::reportRenderErrorOnce(pdf::RenderErrorType type, QString message) +{ + Q_UNUSED(type); + QMessageBox::critical(this, tr("Error"), message); +} diff --git a/JBIG2_Viewer/mainwindow.h b/JBIG2_Viewer/mainwindow.h index 308a3ca..1e44e23 100644 --- a/JBIG2_Viewer/mainwindow.h +++ b/JBIG2_Viewer/mainwindow.h @@ -1,34 +1,34 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include - -#include "pdfexception.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class MainWindow; } -QT_END_NAMESPACE - -class MainWindow : public QMainWindow, public pdf::PDFRenderErrorReporter -{ - Q_OBJECT - -public: - MainWindow(QWidget* parent = nullptr); - virtual ~MainWindow() override; - - virtual void reportRenderError(pdf::RenderErrorType type, QString message) override; - virtual void reportRenderErrorOnce(pdf::RenderErrorType type, QString message) override; - -private slots: - void on_actionAddImage_triggered(); - void on_actionClear_triggered(); - void on_actionAdd_JBIG2_image_triggered(); - -private: - void addImage(QString title, QImage image); - - Ui::MainWindow* ui; - QString m_directory; -}; -#endif // MAINWINDOW_H +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "pdfexception.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow, public pdf::PDFRenderErrorReporter +{ + Q_OBJECT + +public: + MainWindow(QWidget* parent = nullptr); + virtual ~MainWindow() override; + + virtual void reportRenderError(pdf::RenderErrorType type, QString message) override; + virtual void reportRenderErrorOnce(pdf::RenderErrorType type, QString message) override; + +private slots: + void on_actionAddImage_triggered(); + void on_actionClear_triggered(); + void on_actionAdd_JBIG2_image_triggered(); + +private: + void addImage(QString title, QImage image); + + Ui::MainWindow* ui; + QString m_directory; +}; +#endif // MAINWINDOW_H diff --git a/JBIG2_Viewer/mainwindow.ui b/JBIG2_Viewer/mainwindow.ui index a75e54c..83148ba 100644 --- a/JBIG2_Viewer/mainwindow.ui +++ b/JBIG2_Viewer/mainwindow.ui @@ -1,110 +1,110 @@ - - - MainWindow - - - - 0 - 0 - 800 - 600 - - - - JBIG2 Image Viewer - - - - - - - true - - - - - 0 - 0 - 780 - 537 - - - - - - - Images - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - 0 - 0 - 800 - 21 - - - - - File - - - - - - - - - - - Add image - - - Ctrl+O - - - - - Clear - - - Ctrl+W - - - - - Add JBIG2 image - - - Ctrl+J - - - - - - + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + JBIG2 Image Viewer + + + + + + + true + + + + + 0 + 0 + 780 + 537 + + + + + + + Images + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + 0 + 0 + 800 + 21 + + + + + File + + + + + + + + + + + Add image + + + Ctrl+O + + + + + Clear + + + Ctrl+W + + + + + Add JBIG2 image + + + Ctrl+J + + + + + + diff --git a/LICENSE.txt b/LICENSE.txt index 11e8067..0a04128 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,165 +1,165 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/NOTES.txt b/NOTES.txt index b9a66b1..da7bdda 100644 --- a/NOTES.txt +++ b/NOTES.txt @@ -1,105 +1,105 @@ - -Conformance to PDF 2.0 specification notes and tasks: - -Section 1: OK -Section 2: OK -Section 3: OK -Section 4: OK -Section 5: OK -Section 6: OK - -Section 7: Issues - - 7.4.7 JBIG2 decoder ammendments 1 and 2 not implemented - - 7.6.5 Public-key security handlers not implemented - - 7.6.7 Unencrypted wrapper document not implemented - - 7.10.2 Only linear interpolation for sampled function is performed - -Section 8: Issues - - 8.3.2.3 In user space, 1 / 72 inch is used, no UserUnit entry is used - - 8.6.3 transfer functions and halftones are not used at all, color - management system is used to convert colors - - 8.6.5.6 No device to default color space is performed. Device color - spaces can be set in color management system. - - 8.6.5.8 default rendering intent is perceptual instead of relative colorimetric - - 8.6.5.9 Black point compensation is user setting, graphic state setting - is not respected (it is ignored) - - 8.6.7 Overprint not implemented - - 8.10.2 Some form dictionary entries, in table 93, are not all being processed - - 8.10.4 Reference XObject is not supported. Instead, proxy is displayed. - -Section 9: Issues - - 9.3.2 Negative font size is not supported - - 9.3.8 Text knockout not implemented - - 9.5 Some type of fonts not supported yet (CIDFont, some others) - - 9.6.2.1 Missing width is not used for missing characters - - 9.6.4 d1 operator incorrectly doesn't ignore color/alpha operators - - 9.7.4 CIDSystemInfo ignored in Table 115 - -Section 10: Issues - - Color management system is used for color conversions. This can - be turned of, and in that case, methods from 10.4.2 are used. - Undercolor removal and black generation are not used. - - 10.6 Halftoning is not implemented - - 10.7.2 Flatness is ignored - - 10.7.3 Smoothness is ignored - - 10.7.5 Automatic stroke adjustment is ignored - - 10.8 Separations are not produced - - 10.8.3 Separation simulation is not performed - -Section 11: Implemented partially - - Transparency is not implemented in regular Qt's painter, - but separate transparency renderer exist, which can render - also spot colors - -Section 12: Issues - - 12.3.2.2 Destinations are used only to leap on specified page, - position on page or fit hints are ignored - - 12.3.5.2 File collection folders are ignored, files are - displayed in a single list - - 12.3.6 Navigator for file collections is ignored - - 12.4.2 Page indices are used instead of labels - - 12.4.4 Presentations are not implemented - - 12.5.5 Annotation's transparency group is ignored - - 12.5.6.3 Annotation's states are not implemented in viewer - - 12.5.6.22 Watermark annotation - matrix and fixed print entry is not used - - 12.6.3 Some trigger events are not executed or not implemented - - 12.6.4 Some action are not executed in viewer - - 12.6.4.8 URI actions doesn't use IsMap - - 12.7.4.3 Variable text is implemented by Qt's QTextLayout - and drawing, pdf's specific default appearance strings, - and rich text is not implemented. This doesn't conform - to PDF specification, but all unicode characters can be used. - - 12.7.5.5 Signature field locking not implemented and signature - seed dictionary is ignored - - 12.7.6.2 Sumbit form action not implemented in viewer - - 12.7.6.4 Import data action not implemented in viewer - - 12.7.8 Form data format not implemented - - 12.8 Modification detection using UR3/DocMDP method is not - used, if signature is not valid, then it is not checked for - modifications - - 12.8 OCSP (Online Certificate Status Protocol) is not performed - - 12.8.4 Long term validation of signatures not performed - - 12.8.4.5 Validation data in custom VRI are not used, instead of them, - validation data in DSS or embedded in the signature are used - - 12.8.5 Document timestamp dictionary not implemented - - 12.8.6 Permissions not implemented - - 12.9 Measure feature not implemented - - 12.10 Geospatial features not implemented - -Section 13: Not implemented - - Multimedia features not implemented (except some classes, which - can read the data for multimedia - movies, sounds...) - -Section 14: Issues - - 14.3 Document metadata stream is not processed - - 14.4 Modifiable file identifier is not updated - - 14.5 Page-piece dictionaries not implemented - - 14.9 Accessibility support not implemented - - 14.10 Web capture not implemented - - 14.11 Prepress support not implemented at all, only - basic functionality works (media box, crop box, or, - for example, reading output intents) - - 14.12 Document parts not implemented - -Annex F: Not implemented - - Linearized PDF not implemented + +Conformance to PDF 2.0 specification notes and tasks: + +Section 1: OK +Section 2: OK +Section 3: OK +Section 4: OK +Section 5: OK +Section 6: OK + +Section 7: Issues + - 7.4.7 JBIG2 decoder ammendments 1 and 2 not implemented + - 7.6.5 Public-key security handlers not implemented + - 7.6.7 Unencrypted wrapper document not implemented + - 7.10.2 Only linear interpolation for sampled function is performed + +Section 8: Issues + - 8.3.2.3 In user space, 1 / 72 inch is used, no UserUnit entry is used + - 8.6.3 transfer functions and halftones are not used at all, color + management system is used to convert colors + - 8.6.5.6 No device to default color space is performed. Device color + spaces can be set in color management system. + - 8.6.5.8 default rendering intent is perceptual instead of relative colorimetric + - 8.6.5.9 Black point compensation is user setting, graphic state setting + is not respected (it is ignored) + - 8.6.7 Overprint not implemented + - 8.10.2 Some form dictionary entries, in table 93, are not all being processed + - 8.10.4 Reference XObject is not supported. Instead, proxy is displayed. + +Section 9: Issues + - 9.3.2 Negative font size is not supported + - 9.3.8 Text knockout not implemented + - 9.5 Some type of fonts not supported yet (CIDFont, some others) + - 9.6.2.1 Missing width is not used for missing characters + - 9.6.4 d1 operator incorrectly doesn't ignore color/alpha operators + - 9.7.4 CIDSystemInfo ignored in Table 115 + +Section 10: Issues + - Color management system is used for color conversions. This can + be turned of, and in that case, methods from 10.4.2 are used. + Undercolor removal and black generation are not used. + - 10.6 Halftoning is not implemented + - 10.7.2 Flatness is ignored + - 10.7.3 Smoothness is ignored + - 10.7.5 Automatic stroke adjustment is ignored + - 10.8 Separations are not produced + - 10.8.3 Separation simulation is not performed + +Section 11: Implemented partially + - Transparency is not implemented in regular Qt's painter, + but separate transparency renderer exist, which can render + also spot colors + +Section 12: Issues + - 12.3.2.2 Destinations are used only to leap on specified page, + position on page or fit hints are ignored + - 12.3.5.2 File collection folders are ignored, files are + displayed in a single list + - 12.3.6 Navigator for file collections is ignored + - 12.4.2 Page indices are used instead of labels + - 12.4.4 Presentations are not implemented + - 12.5.5 Annotation's transparency group is ignored + - 12.5.6.3 Annotation's states are not implemented in viewer + - 12.5.6.22 Watermark annotation - matrix and fixed print entry is not used + - 12.6.3 Some trigger events are not executed or not implemented + - 12.6.4 Some action are not executed in viewer + - 12.6.4.8 URI actions doesn't use IsMap + - 12.7.4.3 Variable text is implemented by Qt's QTextLayout + and drawing, pdf's specific default appearance strings, + and rich text is not implemented. This doesn't conform + to PDF specification, but all unicode characters can be used. + - 12.7.5.5 Signature field locking not implemented and signature + seed dictionary is ignored + - 12.7.6.2 Sumbit form action not implemented in viewer + - 12.7.6.4 Import data action not implemented in viewer + - 12.7.8 Form data format not implemented + - 12.8 Modification detection using UR3/DocMDP method is not + used, if signature is not valid, then it is not checked for + modifications + - 12.8 OCSP (Online Certificate Status Protocol) is not performed + - 12.8.4 Long term validation of signatures not performed + - 12.8.4.5 Validation data in custom VRI are not used, instead of them, + validation data in DSS or embedded in the signature are used + - 12.8.5 Document timestamp dictionary not implemented + - 12.8.6 Permissions not implemented + - 12.9 Measure feature not implemented + - 12.10 Geospatial features not implemented + +Section 13: Not implemented + - Multimedia features not implemented (except some classes, which + can read the data for multimedia - movies, sounds...) + +Section 14: Issues + - 14.3 Document metadata stream is not processed + - 14.4 Modifiable file identifier is not updated + - 14.5 Page-piece dictionaries not implemented + - 14.9 Accessibility support not implemented + - 14.10 Web capture not implemented + - 14.11 Prepress support not implemented at all, only + basic functionality works (media box, crop box, or, + for example, reading output intents) + - 14.12 Document parts not implemented + +Annex F: Not implemented + - Linearized PDF not implemented diff --git a/Pdf4Qt.pro b/Pdf4Qt.pro index 3c26213..82a13a3 100644 --- a/Pdf4Qt.pro +++ b/Pdf4Qt.pro @@ -1,42 +1,42 @@ -# Copyright (C) 2018-2021 Jakub Melka -# -# This file is part of PDF4QT. -# -# PDF4QT is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# with the written consent of the copyright owner, any later version. -# -# PDF4QT 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PDF4QT. If not, see . - -TEMPLATE = subdirs - -SUBDIRS += \ - Pdf4QtLib \ - CodeGenerator \ - JBIG2_Viewer \ - PdfExampleGenerator \ - PdfTool \ - UnitTests \ - Pdf4QtViewer \ - Pdf4QtViewerPlugins \ - Pdf4QtViewerProfi \ - Pdf4QtViewerLite \ - Pdf4QtDocPageOrganizer - -CodeGenerator.depends = Pdf4QtLib -JBIG2_Viewer.depends = Pdf4QtLib -PdfExampleGenerator.depends = Pdf4QtLib -PdfTool.depends = Pdf4QtLib -UnitTests.depends = Pdf4QtLib -Pdf4QtViewer.depends = Pdf4QtLib -Pdf4QtViewerPlugins.depends = Pdf4QtLib -Pdf4QtViewerProfi.depends = Pdf4QtViewer -Pdf4QtViewerLite.depends = Pdf4QtViewer -Pdf4QtDocPageOrganizer.depends = Pdf4QtLib +# Copyright (C) 2018-2021 Jakub Melka +# +# This file is part of PDF4QT. +# +# PDF4QT is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# with the written consent of the copyright owner, any later version. +# +# PDF4QT 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PDF4QT. If not, see . + +TEMPLATE = subdirs + +SUBDIRS += \ + Pdf4QtLib \ + CodeGenerator \ + JBIG2_Viewer \ + PdfExampleGenerator \ + PdfTool \ + UnitTests \ + Pdf4QtViewer \ + Pdf4QtViewerPlugins \ + Pdf4QtViewerProfi \ + Pdf4QtViewerLite \ + Pdf4QtDocPageOrganizer + +CodeGenerator.depends = Pdf4QtLib +JBIG2_Viewer.depends = Pdf4QtLib +PdfExampleGenerator.depends = Pdf4QtLib +PdfTool.depends = Pdf4QtLib +UnitTests.depends = Pdf4QtLib +Pdf4QtViewer.depends = Pdf4QtLib +Pdf4QtViewerPlugins.depends = Pdf4QtLib +Pdf4QtViewerProfi.depends = Pdf4QtViewer +Pdf4QtViewerLite.depends = Pdf4QtViewer +Pdf4QtDocPageOrganizer.depends = Pdf4QtLib diff --git a/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro b/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro index d9803c4..a0dcc94 100644 --- a/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro +++ b/Pdf4QtDocPageOrganizer/Pdf4QtDocPageOrganizer.pro @@ -1,68 +1,68 @@ -# Copyright (C) 2021 Jakub Melka -# -# This file is part of PDF4QT. -# -# PDF4QT is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# with the written consent of the copyright owner, any later version. -# -# PDF4QT 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PDF4QT. If not, see . - -QT += core gui widgets winextras - -TARGET = Pdf4QtDocPageOrganizer -TEMPLATE = app - -VERSION = 1.0.0 - -RC_ICONS = $$PWD/app-icon.ico - -QMAKE_TARGET_DESCRIPTION = "PDF Document Page Organizer" -QMAKE_TARGET_COPYRIGHT = "(c) Jakub Melka 2018-2021" - -DEFINES += QT_DEPRECATED_WARNINGS -QMAKE_CXXFLAGS += /std:c++latest /utf-8 - -INCLUDEPATH += $$PWD/../PDF4QtLib/Sources -DESTDIR = $$OUT_PWD/.. -LIBS += -L$$OUT_PWD/.. -LIBS += -lPDF4QtLib -CONFIG += force_debug_info no_check_exist - -application.files = $$DESTDIR/Pdf4QtDocPageOrganizer.exe -application.path = $$DESTDIR/install -application.CONFIG += no_check_exist -INSTALLS += application - -SOURCES += \ - aboutdialog.cpp \ - assembleoutputsettingsdialog.cpp \ - main.cpp \ - mainwindow.cpp \ - pageitemdelegate.cpp \ - pageitemmodel.cpp \ - selectbookmarkstoregroupdialog.cpp - -FORMS += \ - aboutdialog.ui \ - assembleoutputsettingsdialog.ui \ - mainwindow.ui \ - selectbookmarkstoregroupdialog.ui - -HEADERS += \ - aboutdialog.h \ - assembleoutputsettingsdialog.h \ - mainwindow.h \ - pageitemdelegate.h \ - pageitemmodel.h \ - selectbookmarkstoregroupdialog.h - -RESOURCES += \ - resources.qrc +# Copyright (C) 2021 Jakub Melka +# +# This file is part of PDF4QT. +# +# PDF4QT is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# with the written consent of the copyright owner, any later version. +# +# PDF4QT 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PDF4QT. If not, see . + +QT += core gui widgets winextras + +TARGET = Pdf4QtDocPageOrganizer +TEMPLATE = app + +VERSION = 1.0.0 + +RC_ICONS = $$PWD/app-icon.ico + +QMAKE_TARGET_DESCRIPTION = "PDF Document Page Organizer" +QMAKE_TARGET_COPYRIGHT = "(c) Jakub Melka 2018-2021" + +DEFINES += QT_DEPRECATED_WARNINGS +QMAKE_CXXFLAGS += /std:c++latest /utf-8 + +INCLUDEPATH += $$PWD/../PDF4QtLib/Sources +DESTDIR = $$OUT_PWD/.. +LIBS += -L$$OUT_PWD/.. +LIBS += -lPDF4QtLib +CONFIG += force_debug_info no_check_exist + +application.files = $$DESTDIR/Pdf4QtDocPageOrganizer.exe +application.path = $$DESTDIR/install +application.CONFIG += no_check_exist +INSTALLS += application + +SOURCES += \ + aboutdialog.cpp \ + assembleoutputsettingsdialog.cpp \ + main.cpp \ + mainwindow.cpp \ + pageitemdelegate.cpp \ + pageitemmodel.cpp \ + selectbookmarkstoregroupdialog.cpp + +FORMS += \ + aboutdialog.ui \ + assembleoutputsettingsdialog.ui \ + mainwindow.ui \ + selectbookmarkstoregroupdialog.ui + +HEADERS += \ + aboutdialog.h \ + assembleoutputsettingsdialog.h \ + mainwindow.h \ + pageitemdelegate.h \ + pageitemmodel.h \ + selectbookmarkstoregroupdialog.h + +RESOURCES += \ + resources.qrc diff --git a/Pdf4QtDocPageOrganizer/aboutdialog.cpp b/Pdf4QtDocPageOrganizer/aboutdialog.cpp index 56a4f64..db4b411 100644 --- a/Pdf4QtDocPageOrganizer/aboutdialog.cpp +++ b/Pdf4QtDocPageOrganizer/aboutdialog.cpp @@ -1,64 +1,64 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "aboutdialog.h" -#include "ui_aboutdialog.h" - -#include "pdfutils.h" -#include "pdfwidgetutils.h" - -namespace pdfdocpage -{ - -PDFAboutDialog::PDFAboutDialog(QWidget* parent) : - QDialog(parent), - ui(new Ui::PDFAboutDialog) -{ - ui->setupUi(this); - - QString html = ui->copyrightLabel->text(); - html.replace("PdfForQtViewer", QApplication::applicationDisplayName()); - ui->copyrightLabel->setText(html); - - std::vector infos = pdf::PDFDependentLibraryInfo::getLibraryInfo(); - - ui->tableWidget->setColumnCount(4); - ui->tableWidget->setRowCount(static_cast(infos.size())); - ui->tableWidget->setHorizontalHeaderLabels(QStringList() << tr("Library") << tr("Version") << tr("License") << tr("URL")); - ui->tableWidget->setEditTriggers(QTableWidget::NoEditTriggers); - ui->tableWidget->setSelectionMode(QTableView::SingleSelection); - ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - - for (int i = 0; i < infos.size(); ++i) - { - const pdf::PDFDependentLibraryInfo& info = infos[i]; - ui->tableWidget->setItem(i, 0, new QTableWidgetItem(info.library)); - ui->tableWidget->setItem(i, 1, new QTableWidgetItem(info.version)); - ui->tableWidget->setItem(i, 2, new QTableWidgetItem(info.license)); - ui->tableWidget->setItem(i, 3, new QTableWidgetItem(info.url)); - } - - pdf::PDFWidgetUtils::scaleWidget(this, QSize(750, 600)); - pdf::PDFWidgetUtils::style(this); -} - -PDFAboutDialog::~PDFAboutDialog() -{ - delete ui; -} - -} // namespace pdfdocpage +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "aboutdialog.h" +#include "ui_aboutdialog.h" + +#include "pdfutils.h" +#include "pdfwidgetutils.h" + +namespace pdfdocpage +{ + +PDFAboutDialog::PDFAboutDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::PDFAboutDialog) +{ + ui->setupUi(this); + + QString html = ui->copyrightLabel->text(); + html.replace("PdfForQtViewer", QApplication::applicationDisplayName()); + ui->copyrightLabel->setText(html); + + std::vector infos = pdf::PDFDependentLibraryInfo::getLibraryInfo(); + + ui->tableWidget->setColumnCount(4); + ui->tableWidget->setRowCount(static_cast(infos.size())); + ui->tableWidget->setHorizontalHeaderLabels(QStringList() << tr("Library") << tr("Version") << tr("License") << tr("URL")); + ui->tableWidget->setEditTriggers(QTableWidget::NoEditTriggers); + ui->tableWidget->setSelectionMode(QTableView::SingleSelection); + ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + for (int i = 0; i < infos.size(); ++i) + { + const pdf::PDFDependentLibraryInfo& info = infos[i]; + ui->tableWidget->setItem(i, 0, new QTableWidgetItem(info.library)); + ui->tableWidget->setItem(i, 1, new QTableWidgetItem(info.version)); + ui->tableWidget->setItem(i, 2, new QTableWidgetItem(info.license)); + ui->tableWidget->setItem(i, 3, new QTableWidgetItem(info.url)); + } + + pdf::PDFWidgetUtils::scaleWidget(this, QSize(750, 600)); + pdf::PDFWidgetUtils::style(this); +} + +PDFAboutDialog::~PDFAboutDialog() +{ + delete ui; +} + +} // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/aboutdialog.h b/Pdf4QtDocPageOrganizer/aboutdialog.h index b59fd91..4776270 100644 --- a/Pdf4QtDocPageOrganizer/aboutdialog.h +++ b/Pdf4QtDocPageOrganizer/aboutdialog.h @@ -1,45 +1,45 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H -#define PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H - -#include - -namespace Ui -{ -class PDFAboutDialog; -} - -namespace pdfdocpage -{ - -class PDFAboutDialog : public QDialog -{ - Q_OBJECT - -public: - explicit PDFAboutDialog(QWidget* parent); - virtual ~PDFAboutDialog() override; - -private: - Ui::PDFAboutDialog* ui; -}; - -} // namespace pdfdocpage - -#endif // PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H +#define PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H + +#include + +namespace Ui +{ +class PDFAboutDialog; +} + +namespace pdfdocpage +{ + +class PDFAboutDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PDFAboutDialog(QWidget* parent); + virtual ~PDFAboutDialog() override; + +private: + Ui::PDFAboutDialog* ui; +}; + +} // namespace pdfdocpage + +#endif // PDFDOCPAGEORGANIZER_PDFABOUTDIALOG_H diff --git a/Pdf4QtDocPageOrganizer/aboutdialog.ui b/Pdf4QtDocPageOrganizer/aboutdialog.ui index 7c9489e..8cef7a9 100644 --- a/Pdf4QtDocPageOrganizer/aboutdialog.ui +++ b/Pdf4QtDocPageOrganizer/aboutdialog.ui @@ -1,269 +1,269 @@ - - - PDFAboutDialog - - - - 0 - 0 - 900 - 600 - - - - - 900 - 600 - - - - About - - - - - - <html><head/><body><p><span style=" font-weight:600;">PdfForQtViewer</span></p><p>Copyright 2018-2021 Jakub Melka. All rights reserved.</p><p>THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p></body></html> - - - true - - - - - - - true - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.875pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> GNU LESSER GENERAL PUBLIC LICENSE</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Version 3, 29 June 2007</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Copyright (C) 2007 Free Software Foundation, Inc. &lt;https://fsf.org/&gt;</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Everyone is permitted to copy and distribute verbatim copies</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> of this license document, but changing it is not allowed.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> This version of the GNU Lesser General Public License incorporates</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">the terms and conditions of version 3 of the GNU General Public</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">License, supplemented by the additional permissions listed below.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 0. Additional Definitions.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> As used herein, &quot;this License&quot; refers to version 3 of the GNU Lesser</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License, and the &quot;GNU GPL&quot; refers to version 3 of the GNU</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> &quot;The Library&quot; refers to a covered work governed by this License,</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">other than an Application or a Combined Work as defined below.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> An &quot;Application&quot; is any work that makes use of an interface provided</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">by the Library, but which is not otherwise based on the Library.</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Defining a subclass of a class defined by the Library is deemed a mode</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">of using an interface provided by the Library.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> A &quot;Combined Work&quot; is a work produced by combining or linking an</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Application with the Library. The particular version of the Library</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">with which the Combined Work was made is also called the &quot;Linked</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Version&quot;.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The &quot;Minimal Corresponding Source&quot; for a Combined Work means the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Corresponding Source for the Combined Work, excluding any source code</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">for portions of the Combined Work that, considered in isolation, are</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">based on the Application, and not on the Linked Version.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The &quot;Corresponding Application Code&quot; for a Combined Work means the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">object code and/or source code for the Application, including any data</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">and utility programs needed for reproducing the Combined Work from the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Application, but excluding the System Libraries of the Combined Work.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 1. Exception to Section 3 of the GNU GPL.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> You may convey a covered work under sections 3 and 4 of this License</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">without being bound by section 3 of the GNU GPL.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 2. Conveying Modified Versions.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> If you modify a copy of the Library, and, in your modifications, a</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">facility refers to a function or data to be supplied by an Application</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">that uses the facility (other than as an argument passed when the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">facility is invoked), then you may convey a copy of the modified</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">version:</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) under this License, provided that you make a good faith effort to</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> ensure that, in the event an Application does not supply the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> function or data, the facility still operates, and performs</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> whatever part of its purpose remains meaningful, or</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) under the GNU GPL, with none of the additional permissions of</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> this License applicable to that copy.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 3. Object Code Incorporating Material from Library Header Files.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The object code form of an Application may incorporate material from</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">a header file that is part of the Library. You may convey such object</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">code under terms of your choice, provided that, if the incorporated</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">material is not limited to numerical parameters, data structure</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">layouts and accessors, or small macros, inline functions and templates</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">(ten or fewer lines in length), you do both of the following:</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) Give prominent notice with each copy of the object code that the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Library is used in it and that the Library and its use are</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> covered by this License.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) Accompany the object code with a copy of the GNU GPL and this license</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> document.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 4. Combined Works.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> You may convey a Combined Work under terms of your choice that,</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">taken together, effectively do not restrict modification of the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">portions of the Library contained in the Combined Work and reverse</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">engineering for debugging such modifications, if you also do each of</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">the following:</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) Give prominent notice with each copy of the Combined Work that</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> the Library is used in it and that the Library and its use are</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> covered by this License.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) Accompany the Combined Work with a copy of the GNU GPL and this license</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> document.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> c) For a Combined Work that displays copyright notices during</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> execution, include the copyright notice for the Library among</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> these notices, as well as a reference directing the user to the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> copies of the GNU GPL and this license document.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> d) Do one of the following:</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 0) Convey the Minimal Corresponding Source under the terms of this</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> License, and the Corresponding Application Code in a form</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> suitable for, and under terms that permit, the user to</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> recombine or relink the Application with a modified version of</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> the Linked Version to produce a modified Combined Work, in the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> manner specified by section 6 of the GNU GPL for conveying</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Corresponding Source.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 1) Use a suitable shared library mechanism for linking with the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Library. A suitable mechanism is one that (a) uses at run time</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a copy of the Library already present on the user's computer</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> system, and (b) will operate properly with a modified version</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> of the Library that is interface-compatible with the Linked</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Version.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> e) Provide Installation Information, but only if you would otherwise</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> be required to provide such information under section 6 of the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> GNU GPL, and only to the extent that such information is</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> necessary to install and execute a modified version of the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Combined Work produced by recombining or relinking the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Application with a modified version of the Linked Version. (If</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> you use option 4d0, the Installation Information must accompany</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> the Minimal Corresponding Source and Corresponding Application</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Code. If you use option 4d1, you must provide the Installation</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Information in the manner specified by section 6 of the GNU GPL</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> for conveying Corresponding Source.)</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 5. Combined Libraries.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> You may place library facilities that are a work based on the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Library side by side in a single library together with other library</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">facilities that are not Applications and are not covered by this</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">License, and convey such a combined library under terms of your</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">choice, if you do both of the following:</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) Accompany the combined library with a copy of the same work based</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> on the Library, uncombined with any other library facilities,</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> conveyed under the terms of this License.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) Give prominent notice with the combined library that part of it</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> is a work based on the Library, and explaining where to find the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> accompanying uncombined form of the same work.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 6. Revised Versions of the GNU Lesser General Public License.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The Free Software Foundation may publish revised and/or new versions</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">of the GNU Lesser General Public License from time to time. Such new</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">versions will be similar in spirit to the present version, but may</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">differ in detail to address new problems or concerns.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Each version is given a distinguishing version number. If the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Library as you received it specifies that a certain numbered version</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">of the GNU Lesser General Public License &quot;or any later version&quot;</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">applies to it, you have the option of following the terms and</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">conditions either of that published version or of any later version</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">published by the Free Software Foundation. If the Library as you</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">received it does not specify a version number of the GNU Lesser</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License, you may choose any version of the GNU Lesser</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License ever published by the Free Software Foundation.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> If the Library as you received it specifies that a proxy can decide</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">whether future versions of the GNU Lesser General Public License shall</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">apply, that proxy's public statement of acceptance of any version is</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">permanent authorization for you to choose that version for the</p> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Library.</p> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - - - - - - - Used libraries - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - PDFAboutDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - PDFAboutDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - + + + PDFAboutDialog + + + + 0 + 0 + 900 + 600 + + + + + 900 + 600 + + + + About + + + + + + <html><head/><body><p><span style=" font-weight:600;">PdfForQtViewer</span></p><p>Copyright 2018-2021 Jakub Melka. All rights reserved.</p><p>THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p></body></html> + + + true + + + + + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.875pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> GNU LESSER GENERAL PUBLIC LICENSE</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Version 3, 29 June 2007</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Copyright (C) 2007 Free Software Foundation, Inc. &lt;https://fsf.org/&gt;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Everyone is permitted to copy and distribute verbatim copies</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> of this license document, but changing it is not allowed.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> This version of the GNU Lesser General Public License incorporates</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">the terms and conditions of version 3 of the GNU General Public</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">License, supplemented by the additional permissions listed below.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 0. Additional Definitions.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> As used herein, &quot;this License&quot; refers to version 3 of the GNU Lesser</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License, and the &quot;GNU GPL&quot; refers to version 3 of the GNU</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> &quot;The Library&quot; refers to a covered work governed by this License,</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">other than an Application or a Combined Work as defined below.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> An &quot;Application&quot; is any work that makes use of an interface provided</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">by the Library, but which is not otherwise based on the Library.</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Defining a subclass of a class defined by the Library is deemed a mode</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">of using an interface provided by the Library.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> A &quot;Combined Work&quot; is a work produced by combining or linking an</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Application with the Library. The particular version of the Library</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">with which the Combined Work was made is also called the &quot;Linked</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Version&quot;.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The &quot;Minimal Corresponding Source&quot; for a Combined Work means the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Corresponding Source for the Combined Work, excluding any source code</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">for portions of the Combined Work that, considered in isolation, are</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">based on the Application, and not on the Linked Version.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The &quot;Corresponding Application Code&quot; for a Combined Work means the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">object code and/or source code for the Application, including any data</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">and utility programs needed for reproducing the Combined Work from the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Application, but excluding the System Libraries of the Combined Work.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 1. Exception to Section 3 of the GNU GPL.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> You may convey a covered work under sections 3 and 4 of this License</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">without being bound by section 3 of the GNU GPL.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 2. Conveying Modified Versions.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> If you modify a copy of the Library, and, in your modifications, a</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">facility refers to a function or data to be supplied by an Application</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">that uses the facility (other than as an argument passed when the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">facility is invoked), then you may convey a copy of the modified</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">version:</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) under this License, provided that you make a good faith effort to</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> ensure that, in the event an Application does not supply the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> function or data, the facility still operates, and performs</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> whatever part of its purpose remains meaningful, or</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) under the GNU GPL, with none of the additional permissions of</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> this License applicable to that copy.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 3. Object Code Incorporating Material from Library Header Files.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The object code form of an Application may incorporate material from</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">a header file that is part of the Library. You may convey such object</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">code under terms of your choice, provided that, if the incorporated</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">material is not limited to numerical parameters, data structure</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">layouts and accessors, or small macros, inline functions and templates</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">(ten or fewer lines in length), you do both of the following:</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) Give prominent notice with each copy of the object code that the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Library is used in it and that the Library and its use are</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> covered by this License.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) Accompany the object code with a copy of the GNU GPL and this license</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> document.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 4. Combined Works.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> You may convey a Combined Work under terms of your choice that,</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">taken together, effectively do not restrict modification of the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">portions of the Library contained in the Combined Work and reverse</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">engineering for debugging such modifications, if you also do each of</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">the following:</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) Give prominent notice with each copy of the Combined Work that</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> the Library is used in it and that the Library and its use are</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> covered by this License.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) Accompany the Combined Work with a copy of the GNU GPL and this license</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> document.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> c) For a Combined Work that displays copyright notices during</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> execution, include the copyright notice for the Library among</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> these notices, as well as a reference directing the user to the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> copies of the GNU GPL and this license document.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> d) Do one of the following:</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 0) Convey the Minimal Corresponding Source under the terms of this</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> License, and the Corresponding Application Code in a form</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> suitable for, and under terms that permit, the user to</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> recombine or relink the Application with a modified version of</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> the Linked Version to produce a modified Combined Work, in the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> manner specified by section 6 of the GNU GPL for conveying</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Corresponding Source.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 1) Use a suitable shared library mechanism for linking with the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Library. A suitable mechanism is one that (a) uses at run time</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a copy of the Library already present on the user's computer</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> system, and (b) will operate properly with a modified version</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> of the Library that is interface-compatible with the Linked</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Version.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> e) Provide Installation Information, but only if you would otherwise</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> be required to provide such information under section 6 of the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> GNU GPL, and only to the extent that such information is</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> necessary to install and execute a modified version of the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Combined Work produced by recombining or relinking the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Application with a modified version of the Linked Version. (If</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> you use option 4d0, the Installation Information must accompany</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> the Minimal Corresponding Source and Corresponding Application</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Code. If you use option 4d1, you must provide the Installation</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Information in the manner specified by section 6 of the GNU GPL</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> for conveying Corresponding Source.)</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 5. Combined Libraries.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> You may place library facilities that are a work based on the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Library side by side in a single library together with other library</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">facilities that are not Applications and are not covered by this</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">License, and convey such a combined library under terms of your</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">choice, if you do both of the following:</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> a) Accompany the combined library with a copy of the same work based</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> on the Library, uncombined with any other library facilities,</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> conveyed under the terms of this License.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> b) Give prominent notice with the combined library that part of it</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> is a work based on the Library, and explaining where to find the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> accompanying uncombined form of the same work.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> 6. Revised Versions of the GNU Lesser General Public License.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> The Free Software Foundation may publish revised and/or new versions</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">of the GNU Lesser General Public License from time to time. Such new</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">versions will be similar in spirit to the present version, but may</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">differ in detail to address new problems or concerns.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> Each version is given a distinguishing version number. If the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Library as you received it specifies that a certain numbered version</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">of the GNU Lesser General Public License &quot;or any later version&quot;</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">applies to it, you have the option of following the terms and</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">conditions either of that published version or of any later version</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">published by the Free Software Foundation. If the Library as you</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">received it does not specify a version number of the GNU Lesser</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License, you may choose any version of the GNU Lesser</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">General Public License ever published by the Free Software Foundation.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"> If the Library as you received it specifies that a proxy can decide</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">whether future versions of the GNU Lesser General Public License shall</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">apply, that proxy's public statement of acceptance of any version is</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">permanent authorization for you to choose that version for the</p> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Library.</p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + Used libraries + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PDFAboutDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PDFAboutDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp index fc3f63b..5a82e63 100644 --- a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp +++ b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.cpp @@ -1,78 +1,78 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "assembleoutputsettingsdialog.h" -#include "ui_assembleoutputsettingsdialog.h" - -#include "pdfwidgetutils.h" - -#include - -namespace pdfdocpage -{ - -AssembleOutputSettingsDialog::AssembleOutputSettingsDialog(QString directory, QWidget* parent) : - QDialog(parent), - ui(new Ui::AssembleOutputSettingsDialog) -{ - ui->setupUi(this); - ui->directoryEdit->setText(directory); - - ui->outlineModeComboBox->addItem(tr("No Outline"), int(pdf::PDFDocumentManipulator::OutlineMode::NoOutline)); - ui->outlineModeComboBox->addItem(tr("Join Outlines"), int(pdf::PDFDocumentManipulator::OutlineMode::Join)); - ui->outlineModeComboBox->addItem(tr("Document Parts"), int(pdf::PDFDocumentManipulator::OutlineMode::DocumentParts)); - ui->outlineModeComboBox->setCurrentIndex(ui->outlineModeComboBox->findData(int(pdf::PDFDocumentManipulator::OutlineMode::DocumentParts))); - - pdf::PDFWidgetUtils::scaleWidget(this, QSize(450, 150)); - pdf::PDFWidgetUtils::style(this); -} - -AssembleOutputSettingsDialog::~AssembleOutputSettingsDialog() -{ - delete ui; -} - -QString AssembleOutputSettingsDialog::getDirectory() const -{ - return ui->directoryEdit->text(); -} - -QString AssembleOutputSettingsDialog::getFileName() const -{ - return ui->fileTemplateEdit->text(); -} - -bool AssembleOutputSettingsDialog::isOverwriteFiles() const -{ - return ui->overwriteFilesCheckBox->isChecked(); -} - -pdf::PDFDocumentManipulator::OutlineMode AssembleOutputSettingsDialog::getOutlineMode() const -{ - return pdf::PDFDocumentManipulator::OutlineMode(ui->outlineModeComboBox->currentData().toInt()); -} - -void AssembleOutputSettingsDialog::on_selectDirectoryButton_clicked() -{ - QString directory = QFileDialog::getExistingDirectory(this, tr("Select output directory"), ui->directoryEdit->text()); - if (!directory.isEmpty()) - { - ui->directoryEdit->setText(directory); - } -} - -} // namespace pdfdocpage +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "assembleoutputsettingsdialog.h" +#include "ui_assembleoutputsettingsdialog.h" + +#include "pdfwidgetutils.h" + +#include + +namespace pdfdocpage +{ + +AssembleOutputSettingsDialog::AssembleOutputSettingsDialog(QString directory, QWidget* parent) : + QDialog(parent), + ui(new Ui::AssembleOutputSettingsDialog) +{ + ui->setupUi(this); + ui->directoryEdit->setText(directory); + + ui->outlineModeComboBox->addItem(tr("No Outline"), int(pdf::PDFDocumentManipulator::OutlineMode::NoOutline)); + ui->outlineModeComboBox->addItem(tr("Join Outlines"), int(pdf::PDFDocumentManipulator::OutlineMode::Join)); + ui->outlineModeComboBox->addItem(tr("Document Parts"), int(pdf::PDFDocumentManipulator::OutlineMode::DocumentParts)); + ui->outlineModeComboBox->setCurrentIndex(ui->outlineModeComboBox->findData(int(pdf::PDFDocumentManipulator::OutlineMode::DocumentParts))); + + pdf::PDFWidgetUtils::scaleWidget(this, QSize(450, 150)); + pdf::PDFWidgetUtils::style(this); +} + +AssembleOutputSettingsDialog::~AssembleOutputSettingsDialog() +{ + delete ui; +} + +QString AssembleOutputSettingsDialog::getDirectory() const +{ + return ui->directoryEdit->text(); +} + +QString AssembleOutputSettingsDialog::getFileName() const +{ + return ui->fileTemplateEdit->text(); +} + +bool AssembleOutputSettingsDialog::isOverwriteFiles() const +{ + return ui->overwriteFilesCheckBox->isChecked(); +} + +pdf::PDFDocumentManipulator::OutlineMode AssembleOutputSettingsDialog::getOutlineMode() const +{ + return pdf::PDFDocumentManipulator::OutlineMode(ui->outlineModeComboBox->currentData().toInt()); +} + +void AssembleOutputSettingsDialog::on_selectDirectoryButton_clicked() +{ + QString directory = QFileDialog::getExistingDirectory(this, tr("Select output directory"), ui->directoryEdit->text()); + if (!directory.isEmpty()) + { + ui->directoryEdit->setText(directory); + } +} + +} // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h index 18140c7..0594b3a 100644 --- a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h +++ b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.h @@ -1,55 +1,55 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H -#define PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H - -#include "pdfdocumentmanipulator.h" - -#include - -namespace Ui -{ -class AssembleOutputSettingsDialog; -} - -namespace pdfdocpage -{ - -class AssembleOutputSettingsDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AssembleOutputSettingsDialog(QString directory, QWidget* parent); - virtual ~AssembleOutputSettingsDialog() override; - - QString getDirectory() const; - QString getFileName() const; - bool isOverwriteFiles() const; - pdf::PDFDocumentManipulator::OutlineMode getOutlineMode() const; - -private slots: - void on_selectDirectoryButton_clicked(); - -private: - Ui::AssembleOutputSettingsDialog* ui; -}; - -} // namespace pdfdocpage - -#endif // PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H +#define PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H + +#include "pdfdocumentmanipulator.h" + +#include + +namespace Ui +{ +class AssembleOutputSettingsDialog; +} + +namespace pdfdocpage +{ + +class AssembleOutputSettingsDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AssembleOutputSettingsDialog(QString directory, QWidget* parent); + virtual ~AssembleOutputSettingsDialog() override; + + QString getDirectory() const; + QString getFileName() const; + bool isOverwriteFiles() const; + pdf::PDFDocumentManipulator::OutlineMode getOutlineMode() const; + +private slots: + void on_selectDirectoryButton_clicked(); + +private: + Ui::AssembleOutputSettingsDialog* ui; +}; + +} // namespace pdfdocpage + +#endif // PDFDOCPAGEORGANIZER_ASSEMBLEOUTPUTSETTINGSDIALOG_H diff --git a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui index 72f5fcf..72ffcf7 100644 --- a/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui +++ b/Pdf4QtDocPageOrganizer/assembleoutputsettingsdialog.ui @@ -1,131 +1,131 @@ - - - AssembleOutputSettingsDialog - - - - 0 - 0 - 614 - 250 - - - - Assemble Documents - - - - - - Assemble Documents - - - - - - ... - - - - - - - - - - File template - - - - - - - <html><head/><body><p>In a template file name, you can use symbols '#' for output document number (means output document index, not input document) or '@' for page number of input document (if document contains more pages, it is a page number of a original document), or '%' for index of input document. Use more '#' or '@' or '%' for setting minimal number of digits (if number has less digits, the they are padded with zero).</p></body></html> - - - true - - - - - - - Overwrite existing files - - - - - - - doc-#.pdf - - - - - - - Generate into directory - - - - - - - Outline Mode - - - - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - AssembleOutputSettingsDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - AssembleOutputSettingsDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - + + + AssembleOutputSettingsDialog + + + + 0 + 0 + 614 + 250 + + + + Assemble Documents + + + + + + Assemble Documents + + + + + + ... + + + + + + + + + + File template + + + + + + + <html><head/><body><p>In a template file name, you can use symbols '#' for output document number (means output document index, not input document) or '@' for page number of input document (if document contains more pages, it is a page number of a original document), or '%' for index of input document. Use more '#' or '@' or '%' for setting minimal number of digits (if number has less digits, the they are padded with zero).</p></body></html> + + + true + + + + + + + Overwrite existing files + + + + + + + doc-#.pdf + + + + + + + Generate into directory + + + + + + + Outline Mode + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AssembleOutputSettingsDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AssembleOutputSettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtDocPageOrganizer/main.cpp b/Pdf4QtDocPageOrganizer/main.cpp index 996a0d9..1c740f0 100644 --- a/Pdf4QtDocPageOrganizer/main.cpp +++ b/Pdf4QtDocPageOrganizer/main.cpp @@ -1,46 +1,46 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfconstants.h" -#include "mainwindow.h" - -#include -#include - -int main(int argc, char *argv[]) -{ - QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true); - QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); - QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true); - QApplication application(argc, argv); - - QCoreApplication::setOrganizationName("MelkaJ"); - QCoreApplication::setApplicationName("PDF4QT DocPage Organizer"); - QCoreApplication::setApplicationVersion(pdf::PDF_LIBRARY_VERSION); - QApplication::setApplicationDisplayName(QApplication::translate("Application", "PDF4QT DocPage Organizer")); - QCommandLineParser parser; - parser.setApplicationDescription(QCoreApplication::applicationName()); - parser.addHelpOption(); - parser.addVersionOption(); - parser.addPositionalArgument("file", "The PDF file to open."); - parser.process(application); - - pdfdocpage::MainWindow mainWindow(nullptr); - mainWindow.show(); - - return application.exec(); -} +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfconstants.h" +#include "mainwindow.h" + +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, true); + QApplication::setAttribute(Qt::AA_DisableHighDpiScaling, true); + QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity, true); + QApplication application(argc, argv); + + QCoreApplication::setOrganizationName("MelkaJ"); + QCoreApplication::setApplicationName("PDF4QT DocPage Organizer"); + QCoreApplication::setApplicationVersion(pdf::PDF_LIBRARY_VERSION); + QApplication::setApplicationDisplayName(QApplication::translate("Application", "PDF4QT DocPage Organizer")); + QCommandLineParser parser; + parser.setApplicationDescription(QCoreApplication::applicationName()); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("file", "The PDF file to open."); + parser.process(application); + + pdfdocpage::MainWindow mainWindow(nullptr); + mainWindow.show(); + + return application.exec(); +} diff --git a/Pdf4QtDocPageOrganizer/mainwindow.cpp b/Pdf4QtDocPageOrganizer/mainwindow.cpp index c2c5cb2..d18f830 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.cpp +++ b/Pdf4QtDocPageOrganizer/mainwindow.cpp @@ -1,872 +1,872 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include "aboutdialog.h" -#include "assembleoutputsettingsdialog.h" -#include "selectbookmarkstoregroupdialog.h" - -#include "pdfaction.h" -#include "pdfwidgetutils.h" -#include "pdfdocumentreader.h" -#include "pdfdocumentwriter.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace pdfdocpage -{ - -MainWindow::MainWindow(QWidget* parent) : - QMainWindow(parent), - ui(new Ui::MainWindow), - m_model(new PageItemModel(this)), - m_delegate(new PageItemDelegate(m_model, this)), - m_dropAction(Qt::IgnoreAction) -{ - ui->setupUi(this); - - m_delegate->setPageImageSize(getDefaultPageImageSize()); - - ui->documentItemsView->setModel(m_model); - ui->documentItemsView->setItemDelegate(m_delegate); - setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(800, 600))); - - ui->actionClear->setData(int(Operation::Clear)); - ui->actionCloneSelection->setData(int(Operation::CloneSelection)); - ui->actionRemoveSelection->setData(int(Operation::RemoveSelection)); - ui->actionReplaceSelection->setData(int(Operation::ReplaceSelection)); - ui->actionRestoreRemovedItems->setData(int(Operation::RestoreRemovedItems)); - ui->actionUndo->setData(int(Operation::Undo)); - ui->actionRedo->setData(int(Operation::Redo)); - ui->actionCut->setData(int(Operation::Cut)); - ui->actionCopy->setData(int(Operation::Copy)); - ui->actionPaste->setData(int(Operation::Paste)); - ui->actionRotate_Left->setData(int(Operation::RotateLeft)); - ui->actionRotate_Right->setData(int(Operation::RotateRight)); - ui->actionGroup->setData(int(Operation::Group)); - ui->actionUngroup->setData(int(Operation::Ungroup)); - ui->actionSelect_None->setData(int(Operation::SelectNone)); - ui->actionSelect_All->setData(int(Operation::SelectAll)); - ui->actionSelect_Even->setData(int(Operation::SelectEven)); - ui->actionSelect_Odd->setData(int(Operation::SelectOdd)); - ui->actionSelect_Portrait->setData(int(Operation::SelectPortrait)); - ui->actionSelect_Landscape->setData(int(Operation::SelectLandscape)); - ui->actionZoom_In->setData(int(Operation::ZoomIn)); - ui->actionZoom_Out->setData(int(Operation::ZoomOut)); - ui->actionUnited_Document->setData(int(Operation::Unite)); - ui->actionSeparate_to_Multiple_Documents->setData(int(Operation::Separate)); - ui->actionSeparate_to_Multiple_Documents_Grouped->setData(int(Operation::SeparateGrouped)); - ui->actionInsert_Image->setData(int(Operation::InsertImage)); - ui->actionInsert_Empty_Page->setData(int(Operation::InsertEmptyPage)); - ui->actionInsert_PDF->setData(int(Operation::InsertPDF)); - ui->actionGet_Source->setData(int(Operation::GetSource)); - ui->actionAbout->setData(int(Operation::About)); - ui->actionInvert_Selection->setData(int(Operation::InvertSelection)); - ui->actionRegroup_Even_Odd->setData(int(Operation::RegroupEvenOdd)); - ui->actionRegroup_by_Page_Pairs->setData(int(Operation::RegroupPaired)); - ui->actionRegroup_by_Bookmarks->setData(int(Operation::RegroupBookmarks)); - ui->actionRegroup_by_Alternating_Pages->setData(int(Operation::RegroupAlternatingPages)); - ui->actionRegroup_by_Alternating_Pages_Reversed_Order->setData(int(Operation::RegroupAlternatingPagesReversed)); - - QToolBar* mainToolbar = addToolBar(tr("Main")); - mainToolbar->setObjectName("main_toolbar"); - mainToolbar->addAction(ui->actionAddDocument); - mainToolbar->addSeparator(); - mainToolbar->addActions({ ui->actionCloneSelection, ui->actionRemoveSelection }); - mainToolbar->addSeparator(); - mainToolbar->addActions({ ui->actionUndo, ui->actionRedo }); - mainToolbar->addSeparator(); - mainToolbar->addActions({ ui->actionCut, ui->actionCopy, ui->actionPaste }); - mainToolbar->addSeparator(); - mainToolbar->addActions({ ui->actionGroup, ui->actionUngroup }); - QToolBar* insertToolbar = addToolBar(tr("Insert")); - insertToolbar->setObjectName("insert_toolbar"); - insertToolbar->addActions({ ui->actionInsert_PDF, ui->actionInsert_Image, ui->actionInsert_Empty_Page }); - QToolBar* selectToolbar = addToolBar(tr("Select")); - selectToolbar->setObjectName("select_toolbar"); - selectToolbar->addActions({ ui->actionSelect_None, ui->actionSelect_All, ui->actionSelect_Even, ui->actionSelect_Odd, ui->actionSelect_Portrait, ui->actionSelect_Landscape, ui->actionInvert_Selection }); - QToolBar* regroupToolbar = addToolBar(tr("Regroup")); - regroupToolbar->setObjectName("regroup_toolbar"); - regroupToolbar->addActions({ ui->actionRegroup_Even_Odd, ui->actionRegroup_by_Page_Pairs, ui->actionRegroup_by_Bookmarks, ui->actionRegroup_by_Alternating_Pages, ui->actionRegroup_by_Alternating_Pages_Reversed_Order }); - QToolBar* zoomToolbar = addToolBar(tr("Zoom")); - zoomToolbar->setObjectName("zoom_toolbar"); - zoomToolbar->addActions({ ui->actionZoom_In, ui->actionZoom_Out }); - QToolBar* makeToolbar = addToolBar(tr("Make")); - makeToolbar->setObjectName("make_toolbar"); - makeToolbar->addActions({ ui->actionUnited_Document, ui->actionSeparate_to_Multiple_Documents, ui->actionSeparate_to_Multiple_Documents_Grouped }); - - QSize iconSize = pdf::PDFWidgetUtils::scaleDPI(this, QSize(24, 24)); - auto toolbars = findChildren(); - for (QToolBar* toolbar : toolbars) - { - toolbar->setIconSize(iconSize); - ui->menuToolbars->addAction(toolbar->toggleViewAction()); - } - - connect(&m_mapper, QOverload::of(&QSignalMapper::mapped), this, &MainWindow::onMappedActionTriggered); - connect(ui->documentItemsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions); - - QList actions = findChildren(); - for (QAction* action : actions) - { - QVariant actionData = action->data(); - if (actionData.isValid()) - { - connect(action, &QAction::triggered, &m_mapper, QOverload<>::of(&QSignalMapper::map)); - m_mapper.setMapping(action, actionData.toInt()); - } - } - - // Initialize pixmap cache size - const int depth = 4; // 4 bytes (ARGB) - const int reserveSize = 2; // Caching of two screens - QSize size = QGuiApplication::primaryScreen()->availableVirtualSize(); - int bytes = size.width() * size.height() * depth * reserveSize; - int kBytes = bytes / 1024; - QPixmapCache::setCacheLimit(kBytes); - - loadSettings(); - updateActions(); -} - -MainWindow::~MainWindow() -{ - saveSettings(); - delete ui; -} - -QSize MainWindow::getMinPageImageSize() const -{ - return pdf::PDFWidgetUtils::scaleDPI(this, QSize(40, 40)); -} - -QSize MainWindow::getDefaultPageImageSize() const -{ - return pdf::PDFWidgetUtils::scaleDPI(this, QSize(100, 100)); -} - -QSize MainWindow::getMaxPageImageSize() const -{ - return pdf::PDFWidgetUtils::scaleDPI(this, QSize(250, 250)); -} - -void MainWindow::on_actionClose_triggered() -{ - close(); -} - -void MainWindow::on_actionAddDocument_triggered() -{ - QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)")); - if (!fileName.isEmpty()) - { - insertDocument(fileName, QModelIndex()); - } -} - -void MainWindow::onMappedActionTriggered(int actionId) -{ - performOperation(static_cast(actionId)); -} - -void MainWindow::updateActions() -{ - QList actions = findChildren(); - for (QAction* action : actions) - { - QVariant actionData = action->data(); - if (actionData.isValid()) - { - action->setEnabled(canPerformOperation(static_cast(actionData.toInt()))); - } - } -} - -void MainWindow::loadSettings() -{ - QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); - settings.beginGroup("MainWindow"); - QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray(); - if (geometry.isEmpty()) - { - QRect availableGeometry = QApplication::desktop()->availableGeometry(this); - QRect windowRect(0, 0, availableGeometry.width() / 2, availableGeometry.height() / 2); - windowRect = windowRect.translated(availableGeometry.center() - windowRect.center()); - setGeometry(windowRect); - } - else - { - restoreGeometry(geometry); - } - - QByteArray state = settings.value("windowState", QByteArray()).toByteArray(); - if (!state.isEmpty()) - { - restoreState(state); - } - settings.endGroup(); - - settings.beginGroup("Settings"); - m_settings.directory = settings.value("directory").toString(); - settings.endGroup(); -} - -void MainWindow::saveSettings() -{ - QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); - settings.beginGroup("MainWindow"); - settings.setValue("geometry", saveGeometry()); - settings.setValue("windowState", saveState()); - settings.endGroup(); - - settings.beginGroup("Settings"); - settings.setValue("directory", m_settings.directory); - settings.endGroup(); -} - -void MainWindow::insertDocument(const QString& fileName, const QModelIndex& insertIndex) -{ - auto queryPassword = [this](bool* ok) - { - *ok = false; - return QInputDialog::getText(this, tr("Encrypted document"), tr("Enter password to access document content"), QLineEdit::Password, QString(), ok); - }; - - // Mark current directory as this - QFileInfo fileInfo(fileName); - m_settings.directory = fileInfo.dir().absolutePath(); - - // Try to open a new document - pdf::PDFDocumentReader reader(nullptr, qMove(queryPassword), true, false); - pdf::PDFDocument document = reader.readFromFile(fileName); - - QString errorMessage = reader.getErrorMessage(); - pdf::PDFDocumentReader::Result result = reader.getReadingResult(); - if (result == pdf::PDFDocumentReader::Result::OK) - { - const pdf::PDFSecurityHandler* securityHandler = document.getStorage().getSecurityHandler(); - if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Assemble) || - securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Modify)) - { - m_model->insertDocument(fileName, qMove(document), insertIndex); - } - else - { - QMessageBox::critical(this, tr("Error"), tr("Document security doesn't permit to organize pages.")); - } - } - else if (result == pdf::PDFDocumentReader::Result::Failed) - { - QMessageBox::critical(this, tr("Error"), errorMessage); - } - - updateActions(); -} - -bool MainWindow::canPerformOperation(Operation operation) const -{ - QModelIndexList selection = ui->documentItemsView->selectionModel()->selection().indexes(); - const bool isSelected = !selection.isEmpty(); - const bool isMultiSelected = selection.size() > 1; - const bool isModelEmpty = m_model->rowCount(QModelIndex()) == 0; - - switch (operation) - { - case Operation::Clear: - return true; - - case Operation::CloneSelection: - case Operation::RemoveSelection: - case Operation::ReplaceSelection: - return isSelected; - - case Operation::RestoreRemovedItems: - return !m_model->isTrashBinEmpty(); - - case Operation::Undo: - return m_model->canUndo(); - - case Operation::Redo: - return m_model->canRedo(); - - case Operation::Cut: - case Operation::Copy: - return isSelected; - - case Operation::Paste: - { - const QMimeData* mimeData = QApplication::clipboard()->mimeData(); - return mimeData && mimeData->hasFormat(PageItemModel::getMimeDataType()); - } - - case Operation::RotateLeft: - case Operation::RotateRight: - return isSelected; - - case Operation::Group: - return isMultiSelected; - case Operation::Ungroup: - return m_model->isGrouped(selection); - - case Operation::SelectNone: - return isSelected; - - case Operation::SelectAll: - case Operation::SelectEven: - case Operation::SelectOdd: - case Operation::SelectPortrait: - case Operation::SelectLandscape: - return !isModelEmpty; - - case Operation::ZoomIn: - return m_delegate->getPageImageSize() != getMaxPageImageSize(); - - case Operation::ZoomOut: - return m_delegate->getPageImageSize() != getMinPageImageSize(); - - case Operation::Unite: - case Operation::Separate: - case Operation::SeparateGrouped: - return !isModelEmpty; - - case Operation::InsertImage: - case Operation::InsertEmptyPage: - case Operation::InsertPDF: - case Operation::GetSource: - case Operation::About: - return true; - - case Operation::InvertSelection: - return !isModelEmpty; - - case Operation::RegroupEvenOdd: - { - PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); - return info.isDocumentOnly(); - } - - case Operation::RegroupPaired: - return !isModelEmpty && !selection.isEmpty(); - - case Operation::RegroupBookmarks: - { - PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); - return info.isSingleDocument(); - } - - case Operation::RegroupAlternatingPages: - case Operation::RegroupAlternatingPagesReversed: - { - PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); - return info.isTwoDocuments(); - } - - default: - Q_ASSERT(false); - break; - } - - return false; -} - -void MainWindow::performOperation(Operation operation) -{ - switch (operation) - { - case Operation::Clear: - { - m_model->clear(); - QPixmapCache::clear(); - break; - } - case Operation::CloneSelection: - { - m_model->cloneSelection(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - } - - case Operation::RemoveSelection: - { - m_model->removeSelection(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - } - - case Operation::RestoreRemovedItems: - { - QModelIndexList restoredItemIndices = m_model->restoreRemovedItems(); - QItemSelection itemSelection; - for (const QModelIndex& index : restoredItemIndices) - { - itemSelection.select(index, index); - } - ui->documentItemsView->selectionModel()->select(itemSelection, QItemSelectionModel::ClearAndSelect); - break; - } - - case Operation::Undo: - { - m_model->undo(); - break; - } - - case Operation::Redo: - { - m_model->redo(); - break; - } - - case Operation::Cut: - case Operation::Copy: - { - QModelIndexList indices = ui->documentItemsView->selectionModel()->selection().indexes(); - - if (indices.isEmpty()) - { - return; - } - - if (QMimeData* mimeData = m_model->mimeData(indices)) - { - QApplication::clipboard()->setMimeData(mimeData); - } - - ui->documentItemsView->clearSelection(); - m_dropAction = (operation == Operation::Cut) ? Qt::MoveAction : Qt::CopyAction; - break; - } - - case Operation::Paste: - { - QModelIndexList indices = ui->documentItemsView->selectionModel()->selection().indexes(); - - int insertRow = m_model->rowCount(QModelIndex()) - 1; - if (!indices.isEmpty()) - { - insertRow = indices.back().row(); - } - - QModelIndex insertIndex = m_model->index(insertRow, 0, QModelIndex()); - const QMimeData* mimeData = QApplication::clipboard()->mimeData(); - if (m_model->canDropMimeData(mimeData, m_dropAction, -1, -1, insertIndex)) - { - m_model->dropMimeData(mimeData, m_dropAction, -1, -1, insertIndex); - } - break; - } - - case Operation::Group: - m_model->group(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - - case Operation::Ungroup: - m_model->ungroup(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - - case Operation::SelectNone: - ui->documentItemsView->clearSelection(); - break; - - case Operation::SelectAll: - ui->documentItemsView->selectAll(); - break; - - case Operation::SelectEven: - ui->documentItemsView->selectionModel()->select(m_model->getSelectionEven(), QItemSelectionModel::ClearAndSelect); - break; - - case Operation::SelectOdd: - ui->documentItemsView->selectionModel()->select(m_model->getSelectionOdd(), QItemSelectionModel::ClearAndSelect); - break; - - case Operation::SelectPortrait: - ui->documentItemsView->selectionModel()->select(m_model->getSelectionPortrait(), QItemSelectionModel::ClearAndSelect); - break; - - case Operation::SelectLandscape: - ui->documentItemsView->selectionModel()->select(m_model->getSelectionLandscape(), QItemSelectionModel::ClearAndSelect); - break; - - case Operation::ZoomIn: - { - QSize pageImageSize = m_delegate->getPageImageSize(); - pageImageSize *= 1.2; - pageImageSize = pageImageSize.boundedTo(getMaxPageImageSize()); - - if (pageImageSize != m_delegate->getPageImageSize()) - { - m_delegate->setPageImageSize(pageImageSize); - } - break; - } - - case Operation::ZoomOut: - { - QSize pageImageSize = m_delegate->getPageImageSize(); - pageImageSize /= 1.2; - pageImageSize = pageImageSize.expandedTo(getMinPageImageSize()); - - if (pageImageSize != m_delegate->getPageImageSize()) - { - m_delegate->setPageImageSize(pageImageSize); - } - break; - } - - case Operation::RotateLeft: - m_model->rotateLeft(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - - case Operation::RotateRight: - m_model->rotateRight(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - - case Operation::GetSource: - QDesktopServices::openUrl(QUrl("https://github.com/JakubMelka/PDF4QT")); - break; - - case Operation::InsertEmptyPage: - m_model->insertEmptyPage(ui->documentItemsView->selectionModel()->selection().indexes()); - break; - - case Operation::About: - { - PDFAboutDialog aboutDialog(this); - aboutDialog.exec(); - break; - } - - case Operation::Unite: - case Operation::Separate: - case Operation::SeparateGrouped: - { - PageItemModel::AssembleMode assembleMode = PageItemModel::AssembleMode::Unite; - - switch (operation) - { - case Operation::Unite: - assembleMode = PageItemModel::AssembleMode::Unite; - break; - - case Operation::Separate: - assembleMode = PageItemModel::AssembleMode::Separate; - break; - - case Operation::SeparateGrouped: - assembleMode = PageItemModel::AssembleMode::SeparateGrouped; - break; - - default: - Q_ASSERT(false); - } - - std::vector> assembledDocuments = m_model->getAssembledPages(assembleMode); - - // Check we have something to process - if (assembledDocuments.empty()) - { - QMessageBox::critical(this, tr("Error"), tr("No documents to assemble.")); - break; - } - - AssembleOutputSettingsDialog dialog(m_settings.directory, this); - if (dialog.exec() == QDialog::Accepted) - { - pdf::PDFDocumentManipulator manipulator; - - // Add documents and images - for (const auto& documentItem : m_model->getDocuments()) - { - manipulator.addDocument(documentItem.first, &documentItem.second.document); - } - for (const auto& imageItem : m_model->getImages()) - { - manipulator.addImage(imageItem.first, imageItem.second.image); - } - - // Jakub Melka: create assembled documents - pdf::PDFOperationResult result(true); - std::vector> assembledDocumentStorage; - - int sourceDocumentIndex = 1; - int assembledDocumentIndex = 1; - int sourcePageIndex = 1; - int documentCount = int(m_model->getDocuments().size()); - - QString directory = dialog.getDirectory(); - QString fileNameTemplate = dialog.getFileName(); - const bool isOverwriteEnabled = dialog.isOverwriteFiles(); - pdf::PDFDocumentManipulator::OutlineMode outlineMode = dialog.getOutlineMode(); - manipulator.setOutlineMode(outlineMode); - - if (!directory.endsWith('/')) - { - directory += "/"; - } - - auto replaceInString = [](QString& templateString, QChar character, int number) - { - int index = templateString.indexOf(character, 0, Qt::CaseSensitive); - if (index != -1) - { - int lastIndex = templateString.lastIndexOf(character, -1, Qt::CaseSensitive); - int count = lastIndex - index + 1; - - QString textNumber = QString::number(number); - textNumber = textNumber.rightJustified(count, '0', false); - - templateString.remove(index, count); - templateString.insert(index, textNumber); - } - }; - - for (const std::vector& assembledPages : assembledDocuments) - { - pdf::PDFOperationResult currentResult = manipulator.assemble(assembledPages); - if (!currentResult && result) - { - result = currentResult; - break; - } - - pdf::PDFDocumentManipulator::AssembledPage samplePage = assembledPages.front(); - sourceDocumentIndex = samplePage.documentIndex == -1 ? documentCount + samplePage.imageIndex : samplePage.documentIndex; - sourcePageIndex = qMax(int(samplePage.pageIndex + 1), 1); - - QString fileName = fileNameTemplate; - - replaceInString(fileName, '#', assembledDocumentIndex); - replaceInString(fileName, '@', sourcePageIndex); - replaceInString(fileName, '%', sourceDocumentIndex); - - if (!fileName.endsWith(".pdf")) - { - fileName += ".pdf"; - } - fileName.prepend(directory); - - assembledDocumentStorage.emplace_back(std::make_pair(std::move(fileName), manipulator.takeAssembledDocument())); - ++assembledDocumentIndex; - } - - if (!result) - { - QMessageBox::critical(this, tr("Error"), result.getErrorMessage()); - return; - } - - // Now, try to save files - for (const auto& assembledDocumentItem : assembledDocumentStorage) - { - QString filename = assembledDocumentItem.first; - const pdf::PDFDocument* document = &assembledDocumentItem.second; - - const bool isDocumentFileAlreadyExisting = QFile::exists(filename); - if (!isOverwriteEnabled && isDocumentFileAlreadyExisting) - { - QMessageBox::critical(this, tr("Error"), tr("Document with given filename already exists.")); - return; - } - - pdf::PDFDocumentWriter writer(nullptr); - pdf::PDFOperationResult result = writer.write(filename, document, isDocumentFileAlreadyExisting); - - if (!result) - { - QMessageBox::critical(this, tr("Error"), result.getErrorMessage()); - return; - } - } - } - - break; - } - - case Operation::InsertImage: - { - QStringList filters; - for (const QByteArray& imageFormat : QImageReader::supportedImageFormats()) - { - filters << QString::fromLatin1(imageFormat).toLower(); - } - QString filter = tr("Images (*.%1)").arg(filters.join(" *.")); - QString fileName = QFileDialog::getOpenFileName(this, tr("Select Image"), m_settings.directory, filter, nullptr); - - if (!fileName.isEmpty()) - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - m_model->insertImage(fileName, !indexes.isEmpty() ? indexes.front() : QModelIndex()); - } - break; - } - - case Operation::InsertPDF: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)")); - if (!fileName.isEmpty()) - { - insertDocument(fileName, !indexes.isEmpty() ? indexes.back() : QModelIndex()); - } - break; - } - - case Operation::ReplaceSelection: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - - if (indexes.isEmpty()) - { - // Jakub Melka: we have nothing to do, selection is empty - return; - } - - QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)")); - if (!fileName.isEmpty()) - { - insertDocument(fileName, indexes.back()); - m_model->removeSelection(indexes); - } - break; - } - - case Operation::InvertSelection: - { - QModelIndex rootIndex = ui->documentItemsView->rootIndex(); - - QModelIndex firstIndex = rootIndex.child(0, 0); - QModelIndex lastIndex = rootIndex.child(rootIndex.model()->rowCount() - 1, 0); - QItemSelection selection(firstIndex, lastIndex); - - ui->documentItemsView->selectionModel()->select(selection, QItemSelectionModel::Toggle); - break; - } - - case Operation::RegroupEvenOdd: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - m_model->regroupEvenOdd(indexes); - break; - } - - case Operation::RegroupPaired: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - m_model->regroupPaired(indexes); - break; - } - - case Operation::RegroupBookmarks: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - - if (!indexes.isEmpty()) - { - PageItemModel::SelectionInfo selectionInfo = m_model->getSelectionInfo(indexes); - const std::map& documents = m_model->getDocuments(); - - auto it = documents.find(selectionInfo.firstDocumentIndex); - if (it != documents.end()) - { - const pdf::PDFDocument* document = &it->second.document; - SelectBookmarksToRegroupDialog dialog(document, this); - - if (dialog.exec() == SelectBookmarksToRegroupDialog::Accepted) - { - std::vector breakPageIndices; - std::vector outlineItems = dialog.getSelectedOutlineItems(); - - // Jakub Melka: Resolve outline items. Try to find an index - // of page of each outline item. - for (const pdf::PDFOutlineItem* item : outlineItems) - { - const pdf::PDFAction* action = item->getAction(); - const pdf::PDFActionGoTo* actionGoto = dynamic_cast(action); - if (actionGoto) - { - pdf::PDFDestination destination = actionGoto->getDestination(); - - if (destination.getDestinationType() == pdf::DestinationType::Named) - { - if (const pdf::PDFDestination* targetDestination = document->getCatalog()->getNamedDestination(destination.getName())) - { - destination = *targetDestination; - } - else - { - destination = pdf::PDFDestination(); - } - } - - if (destination.isValid()) - { - const size_t pageIndex = document->getCatalog()->getPageIndexFromPageReference(destination.getPageReference()); - if (pageIndex != pdf::PDFCatalog::INVALID_PAGE_INDEX) - { - breakPageIndices.push_back(pageIndex + 1); - } - } - } - } - - std::sort(breakPageIndices.begin(), breakPageIndices.end()); - breakPageIndices.erase(std::unique(breakPageIndices.begin(), breakPageIndices.end()), breakPageIndices.end()); - - m_model->regroupBookmarks(indexes, breakPageIndices); - } - } - } - - break; - } - - case Operation::RegroupAlternatingPages: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - m_model->regroupAlternatingPages(indexes, false); - break; - } - - case Operation::RegroupAlternatingPagesReversed: - { - QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); - m_model->regroupAlternatingPages(indexes, true); - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - updateActions(); -} - -} // namespace pdfdocpage +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "aboutdialog.h" +#include "assembleoutputsettingsdialog.h" +#include "selectbookmarkstoregroupdialog.h" + +#include "pdfaction.h" +#include "pdfwidgetutils.h" +#include "pdfdocumentreader.h" +#include "pdfdocumentwriter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pdfdocpage +{ + +MainWindow::MainWindow(QWidget* parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + m_model(new PageItemModel(this)), + m_delegate(new PageItemDelegate(m_model, this)), + m_dropAction(Qt::IgnoreAction) +{ + ui->setupUi(this); + + m_delegate->setPageImageSize(getDefaultPageImageSize()); + + ui->documentItemsView->setModel(m_model); + ui->documentItemsView->setItemDelegate(m_delegate); + setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(800, 600))); + + ui->actionClear->setData(int(Operation::Clear)); + ui->actionCloneSelection->setData(int(Operation::CloneSelection)); + ui->actionRemoveSelection->setData(int(Operation::RemoveSelection)); + ui->actionReplaceSelection->setData(int(Operation::ReplaceSelection)); + ui->actionRestoreRemovedItems->setData(int(Operation::RestoreRemovedItems)); + ui->actionUndo->setData(int(Operation::Undo)); + ui->actionRedo->setData(int(Operation::Redo)); + ui->actionCut->setData(int(Operation::Cut)); + ui->actionCopy->setData(int(Operation::Copy)); + ui->actionPaste->setData(int(Operation::Paste)); + ui->actionRotate_Left->setData(int(Operation::RotateLeft)); + ui->actionRotate_Right->setData(int(Operation::RotateRight)); + ui->actionGroup->setData(int(Operation::Group)); + ui->actionUngroup->setData(int(Operation::Ungroup)); + ui->actionSelect_None->setData(int(Operation::SelectNone)); + ui->actionSelect_All->setData(int(Operation::SelectAll)); + ui->actionSelect_Even->setData(int(Operation::SelectEven)); + ui->actionSelect_Odd->setData(int(Operation::SelectOdd)); + ui->actionSelect_Portrait->setData(int(Operation::SelectPortrait)); + ui->actionSelect_Landscape->setData(int(Operation::SelectLandscape)); + ui->actionZoom_In->setData(int(Operation::ZoomIn)); + ui->actionZoom_Out->setData(int(Operation::ZoomOut)); + ui->actionUnited_Document->setData(int(Operation::Unite)); + ui->actionSeparate_to_Multiple_Documents->setData(int(Operation::Separate)); + ui->actionSeparate_to_Multiple_Documents_Grouped->setData(int(Operation::SeparateGrouped)); + ui->actionInsert_Image->setData(int(Operation::InsertImage)); + ui->actionInsert_Empty_Page->setData(int(Operation::InsertEmptyPage)); + ui->actionInsert_PDF->setData(int(Operation::InsertPDF)); + ui->actionGet_Source->setData(int(Operation::GetSource)); + ui->actionAbout->setData(int(Operation::About)); + ui->actionInvert_Selection->setData(int(Operation::InvertSelection)); + ui->actionRegroup_Even_Odd->setData(int(Operation::RegroupEvenOdd)); + ui->actionRegroup_by_Page_Pairs->setData(int(Operation::RegroupPaired)); + ui->actionRegroup_by_Bookmarks->setData(int(Operation::RegroupBookmarks)); + ui->actionRegroup_by_Alternating_Pages->setData(int(Operation::RegroupAlternatingPages)); + ui->actionRegroup_by_Alternating_Pages_Reversed_Order->setData(int(Operation::RegroupAlternatingPagesReversed)); + + QToolBar* mainToolbar = addToolBar(tr("Main")); + mainToolbar->setObjectName("main_toolbar"); + mainToolbar->addAction(ui->actionAddDocument); + mainToolbar->addSeparator(); + mainToolbar->addActions({ ui->actionCloneSelection, ui->actionRemoveSelection }); + mainToolbar->addSeparator(); + mainToolbar->addActions({ ui->actionUndo, ui->actionRedo }); + mainToolbar->addSeparator(); + mainToolbar->addActions({ ui->actionCut, ui->actionCopy, ui->actionPaste }); + mainToolbar->addSeparator(); + mainToolbar->addActions({ ui->actionGroup, ui->actionUngroup }); + QToolBar* insertToolbar = addToolBar(tr("Insert")); + insertToolbar->setObjectName("insert_toolbar"); + insertToolbar->addActions({ ui->actionInsert_PDF, ui->actionInsert_Image, ui->actionInsert_Empty_Page }); + QToolBar* selectToolbar = addToolBar(tr("Select")); + selectToolbar->setObjectName("select_toolbar"); + selectToolbar->addActions({ ui->actionSelect_None, ui->actionSelect_All, ui->actionSelect_Even, ui->actionSelect_Odd, ui->actionSelect_Portrait, ui->actionSelect_Landscape, ui->actionInvert_Selection }); + QToolBar* regroupToolbar = addToolBar(tr("Regroup")); + regroupToolbar->setObjectName("regroup_toolbar"); + regroupToolbar->addActions({ ui->actionRegroup_Even_Odd, ui->actionRegroup_by_Page_Pairs, ui->actionRegroup_by_Bookmarks, ui->actionRegroup_by_Alternating_Pages, ui->actionRegroup_by_Alternating_Pages_Reversed_Order }); + QToolBar* zoomToolbar = addToolBar(tr("Zoom")); + zoomToolbar->setObjectName("zoom_toolbar"); + zoomToolbar->addActions({ ui->actionZoom_In, ui->actionZoom_Out }); + QToolBar* makeToolbar = addToolBar(tr("Make")); + makeToolbar->setObjectName("make_toolbar"); + makeToolbar->addActions({ ui->actionUnited_Document, ui->actionSeparate_to_Multiple_Documents, ui->actionSeparate_to_Multiple_Documents_Grouped }); + + QSize iconSize = pdf::PDFWidgetUtils::scaleDPI(this, QSize(24, 24)); + auto toolbars = findChildren(); + for (QToolBar* toolbar : toolbars) + { + toolbar->setIconSize(iconSize); + ui->menuToolbars->addAction(toolbar->toggleViewAction()); + } + + connect(&m_mapper, QOverload::of(&QSignalMapper::mapped), this, &MainWindow::onMappedActionTriggered); + connect(ui->documentItemsView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::updateActions); + + QList actions = findChildren(); + for (QAction* action : actions) + { + QVariant actionData = action->data(); + if (actionData.isValid()) + { + connect(action, &QAction::triggered, &m_mapper, QOverload<>::of(&QSignalMapper::map)); + m_mapper.setMapping(action, actionData.toInt()); + } + } + + // Initialize pixmap cache size + const int depth = 4; // 4 bytes (ARGB) + const int reserveSize = 2; // Caching of two screens + QSize size = QGuiApplication::primaryScreen()->availableVirtualSize(); + int bytes = size.width() * size.height() * depth * reserveSize; + int kBytes = bytes / 1024; + QPixmapCache::setCacheLimit(kBytes); + + loadSettings(); + updateActions(); +} + +MainWindow::~MainWindow() +{ + saveSettings(); + delete ui; +} + +QSize MainWindow::getMinPageImageSize() const +{ + return pdf::PDFWidgetUtils::scaleDPI(this, QSize(40, 40)); +} + +QSize MainWindow::getDefaultPageImageSize() const +{ + return pdf::PDFWidgetUtils::scaleDPI(this, QSize(100, 100)); +} + +QSize MainWindow::getMaxPageImageSize() const +{ + return pdf::PDFWidgetUtils::scaleDPI(this, QSize(250, 250)); +} + +void MainWindow::on_actionClose_triggered() +{ + close(); +} + +void MainWindow::on_actionAddDocument_triggered() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)")); + if (!fileName.isEmpty()) + { + insertDocument(fileName, QModelIndex()); + } +} + +void MainWindow::onMappedActionTriggered(int actionId) +{ + performOperation(static_cast(actionId)); +} + +void MainWindow::updateActions() +{ + QList actions = findChildren(); + for (QAction* action : actions) + { + QVariant actionData = action->data(); + if (actionData.isValid()) + { + action->setEnabled(canPerformOperation(static_cast(actionData.toInt()))); + } + } +} + +void MainWindow::loadSettings() +{ + QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); + settings.beginGroup("MainWindow"); + QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray(); + if (geometry.isEmpty()) + { + QRect availableGeometry = QApplication::desktop()->availableGeometry(this); + QRect windowRect(0, 0, availableGeometry.width() / 2, availableGeometry.height() / 2); + windowRect = windowRect.translated(availableGeometry.center() - windowRect.center()); + setGeometry(windowRect); + } + else + { + restoreGeometry(geometry); + } + + QByteArray state = settings.value("windowState", QByteArray()).toByteArray(); + if (!state.isEmpty()) + { + restoreState(state); + } + settings.endGroup(); + + settings.beginGroup("Settings"); + m_settings.directory = settings.value("directory").toString(); + settings.endGroup(); +} + +void MainWindow::saveSettings() +{ + QSettings settings(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); + settings.beginGroup("MainWindow"); + settings.setValue("geometry", saveGeometry()); + settings.setValue("windowState", saveState()); + settings.endGroup(); + + settings.beginGroup("Settings"); + settings.setValue("directory", m_settings.directory); + settings.endGroup(); +} + +void MainWindow::insertDocument(const QString& fileName, const QModelIndex& insertIndex) +{ + auto queryPassword = [this](bool* ok) + { + *ok = false; + return QInputDialog::getText(this, tr("Encrypted document"), tr("Enter password to access document content"), QLineEdit::Password, QString(), ok); + }; + + // Mark current directory as this + QFileInfo fileInfo(fileName); + m_settings.directory = fileInfo.dir().absolutePath(); + + // Try to open a new document + pdf::PDFDocumentReader reader(nullptr, qMove(queryPassword), true, false); + pdf::PDFDocument document = reader.readFromFile(fileName); + + QString errorMessage = reader.getErrorMessage(); + pdf::PDFDocumentReader::Result result = reader.getReadingResult(); + if (result == pdf::PDFDocumentReader::Result::OK) + { + const pdf::PDFSecurityHandler* securityHandler = document.getStorage().getSecurityHandler(); + if (securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Assemble) || + securityHandler->isAllowed(pdf::PDFSecurityHandler::Permission::Modify)) + { + m_model->insertDocument(fileName, qMove(document), insertIndex); + } + else + { + QMessageBox::critical(this, tr("Error"), tr("Document security doesn't permit to organize pages.")); + } + } + else if (result == pdf::PDFDocumentReader::Result::Failed) + { + QMessageBox::critical(this, tr("Error"), errorMessage); + } + + updateActions(); +} + +bool MainWindow::canPerformOperation(Operation operation) const +{ + QModelIndexList selection = ui->documentItemsView->selectionModel()->selection().indexes(); + const bool isSelected = !selection.isEmpty(); + const bool isMultiSelected = selection.size() > 1; + const bool isModelEmpty = m_model->rowCount(QModelIndex()) == 0; + + switch (operation) + { + case Operation::Clear: + return true; + + case Operation::CloneSelection: + case Operation::RemoveSelection: + case Operation::ReplaceSelection: + return isSelected; + + case Operation::RestoreRemovedItems: + return !m_model->isTrashBinEmpty(); + + case Operation::Undo: + return m_model->canUndo(); + + case Operation::Redo: + return m_model->canRedo(); + + case Operation::Cut: + case Operation::Copy: + return isSelected; + + case Operation::Paste: + { + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + return mimeData && mimeData->hasFormat(PageItemModel::getMimeDataType()); + } + + case Operation::RotateLeft: + case Operation::RotateRight: + return isSelected; + + case Operation::Group: + return isMultiSelected; + case Operation::Ungroup: + return m_model->isGrouped(selection); + + case Operation::SelectNone: + return isSelected; + + case Operation::SelectAll: + case Operation::SelectEven: + case Operation::SelectOdd: + case Operation::SelectPortrait: + case Operation::SelectLandscape: + return !isModelEmpty; + + case Operation::ZoomIn: + return m_delegate->getPageImageSize() != getMaxPageImageSize(); + + case Operation::ZoomOut: + return m_delegate->getPageImageSize() != getMinPageImageSize(); + + case Operation::Unite: + case Operation::Separate: + case Operation::SeparateGrouped: + return !isModelEmpty; + + case Operation::InsertImage: + case Operation::InsertEmptyPage: + case Operation::InsertPDF: + case Operation::GetSource: + case Operation::About: + return true; + + case Operation::InvertSelection: + return !isModelEmpty; + + case Operation::RegroupEvenOdd: + { + PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); + return info.isDocumentOnly(); + } + + case Operation::RegroupPaired: + return !isModelEmpty && !selection.isEmpty(); + + case Operation::RegroupBookmarks: + { + PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); + return info.isSingleDocument(); + } + + case Operation::RegroupAlternatingPages: + case Operation::RegroupAlternatingPagesReversed: + { + PageItemModel::SelectionInfo info = m_model->getSelectionInfo(selection); + return info.isTwoDocuments(); + } + + default: + Q_ASSERT(false); + break; + } + + return false; +} + +void MainWindow::performOperation(Operation operation) +{ + switch (operation) + { + case Operation::Clear: + { + m_model->clear(); + QPixmapCache::clear(); + break; + } + case Operation::CloneSelection: + { + m_model->cloneSelection(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + } + + case Operation::RemoveSelection: + { + m_model->removeSelection(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + } + + case Operation::RestoreRemovedItems: + { + QModelIndexList restoredItemIndices = m_model->restoreRemovedItems(); + QItemSelection itemSelection; + for (const QModelIndex& index : restoredItemIndices) + { + itemSelection.select(index, index); + } + ui->documentItemsView->selectionModel()->select(itemSelection, QItemSelectionModel::ClearAndSelect); + break; + } + + case Operation::Undo: + { + m_model->undo(); + break; + } + + case Operation::Redo: + { + m_model->redo(); + break; + } + + case Operation::Cut: + case Operation::Copy: + { + QModelIndexList indices = ui->documentItemsView->selectionModel()->selection().indexes(); + + if (indices.isEmpty()) + { + return; + } + + if (QMimeData* mimeData = m_model->mimeData(indices)) + { + QApplication::clipboard()->setMimeData(mimeData); + } + + ui->documentItemsView->clearSelection(); + m_dropAction = (operation == Operation::Cut) ? Qt::MoveAction : Qt::CopyAction; + break; + } + + case Operation::Paste: + { + QModelIndexList indices = ui->documentItemsView->selectionModel()->selection().indexes(); + + int insertRow = m_model->rowCount(QModelIndex()) - 1; + if (!indices.isEmpty()) + { + insertRow = indices.back().row(); + } + + QModelIndex insertIndex = m_model->index(insertRow, 0, QModelIndex()); + const QMimeData* mimeData = QApplication::clipboard()->mimeData(); + if (m_model->canDropMimeData(mimeData, m_dropAction, -1, -1, insertIndex)) + { + m_model->dropMimeData(mimeData, m_dropAction, -1, -1, insertIndex); + } + break; + } + + case Operation::Group: + m_model->group(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + + case Operation::Ungroup: + m_model->ungroup(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + + case Operation::SelectNone: + ui->documentItemsView->clearSelection(); + break; + + case Operation::SelectAll: + ui->documentItemsView->selectAll(); + break; + + case Operation::SelectEven: + ui->documentItemsView->selectionModel()->select(m_model->getSelectionEven(), QItemSelectionModel::ClearAndSelect); + break; + + case Operation::SelectOdd: + ui->documentItemsView->selectionModel()->select(m_model->getSelectionOdd(), QItemSelectionModel::ClearAndSelect); + break; + + case Operation::SelectPortrait: + ui->documentItemsView->selectionModel()->select(m_model->getSelectionPortrait(), QItemSelectionModel::ClearAndSelect); + break; + + case Operation::SelectLandscape: + ui->documentItemsView->selectionModel()->select(m_model->getSelectionLandscape(), QItemSelectionModel::ClearAndSelect); + break; + + case Operation::ZoomIn: + { + QSize pageImageSize = m_delegate->getPageImageSize(); + pageImageSize *= 1.2; + pageImageSize = pageImageSize.boundedTo(getMaxPageImageSize()); + + if (pageImageSize != m_delegate->getPageImageSize()) + { + m_delegate->setPageImageSize(pageImageSize); + } + break; + } + + case Operation::ZoomOut: + { + QSize pageImageSize = m_delegate->getPageImageSize(); + pageImageSize /= 1.2; + pageImageSize = pageImageSize.expandedTo(getMinPageImageSize()); + + if (pageImageSize != m_delegate->getPageImageSize()) + { + m_delegate->setPageImageSize(pageImageSize); + } + break; + } + + case Operation::RotateLeft: + m_model->rotateLeft(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + + case Operation::RotateRight: + m_model->rotateRight(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + + case Operation::GetSource: + QDesktopServices::openUrl(QUrl("https://github.com/JakubMelka/PDF4QT")); + break; + + case Operation::InsertEmptyPage: + m_model->insertEmptyPage(ui->documentItemsView->selectionModel()->selection().indexes()); + break; + + case Operation::About: + { + PDFAboutDialog aboutDialog(this); + aboutDialog.exec(); + break; + } + + case Operation::Unite: + case Operation::Separate: + case Operation::SeparateGrouped: + { + PageItemModel::AssembleMode assembleMode = PageItemModel::AssembleMode::Unite; + + switch (operation) + { + case Operation::Unite: + assembleMode = PageItemModel::AssembleMode::Unite; + break; + + case Operation::Separate: + assembleMode = PageItemModel::AssembleMode::Separate; + break; + + case Operation::SeparateGrouped: + assembleMode = PageItemModel::AssembleMode::SeparateGrouped; + break; + + default: + Q_ASSERT(false); + } + + std::vector> assembledDocuments = m_model->getAssembledPages(assembleMode); + + // Check we have something to process + if (assembledDocuments.empty()) + { + QMessageBox::critical(this, tr("Error"), tr("No documents to assemble.")); + break; + } + + AssembleOutputSettingsDialog dialog(m_settings.directory, this); + if (dialog.exec() == QDialog::Accepted) + { + pdf::PDFDocumentManipulator manipulator; + + // Add documents and images + for (const auto& documentItem : m_model->getDocuments()) + { + manipulator.addDocument(documentItem.first, &documentItem.second.document); + } + for (const auto& imageItem : m_model->getImages()) + { + manipulator.addImage(imageItem.first, imageItem.second.image); + } + + // Jakub Melka: create assembled documents + pdf::PDFOperationResult result(true); + std::vector> assembledDocumentStorage; + + int sourceDocumentIndex = 1; + int assembledDocumentIndex = 1; + int sourcePageIndex = 1; + int documentCount = int(m_model->getDocuments().size()); + + QString directory = dialog.getDirectory(); + QString fileNameTemplate = dialog.getFileName(); + const bool isOverwriteEnabled = dialog.isOverwriteFiles(); + pdf::PDFDocumentManipulator::OutlineMode outlineMode = dialog.getOutlineMode(); + manipulator.setOutlineMode(outlineMode); + + if (!directory.endsWith('/')) + { + directory += "/"; + } + + auto replaceInString = [](QString& templateString, QChar character, int number) + { + int index = templateString.indexOf(character, 0, Qt::CaseSensitive); + if (index != -1) + { + int lastIndex = templateString.lastIndexOf(character, -1, Qt::CaseSensitive); + int count = lastIndex - index + 1; + + QString textNumber = QString::number(number); + textNumber = textNumber.rightJustified(count, '0', false); + + templateString.remove(index, count); + templateString.insert(index, textNumber); + } + }; + + for (const std::vector& assembledPages : assembledDocuments) + { + pdf::PDFOperationResult currentResult = manipulator.assemble(assembledPages); + if (!currentResult && result) + { + result = currentResult; + break; + } + + pdf::PDFDocumentManipulator::AssembledPage samplePage = assembledPages.front(); + sourceDocumentIndex = samplePage.documentIndex == -1 ? documentCount + samplePage.imageIndex : samplePage.documentIndex; + sourcePageIndex = qMax(int(samplePage.pageIndex + 1), 1); + + QString fileName = fileNameTemplate; + + replaceInString(fileName, '#', assembledDocumentIndex); + replaceInString(fileName, '@', sourcePageIndex); + replaceInString(fileName, '%', sourceDocumentIndex); + + if (!fileName.endsWith(".pdf")) + { + fileName += ".pdf"; + } + fileName.prepend(directory); + + assembledDocumentStorage.emplace_back(std::make_pair(std::move(fileName), manipulator.takeAssembledDocument())); + ++assembledDocumentIndex; + } + + if (!result) + { + QMessageBox::critical(this, tr("Error"), result.getErrorMessage()); + return; + } + + // Now, try to save files + for (const auto& assembledDocumentItem : assembledDocumentStorage) + { + QString filename = assembledDocumentItem.first; + const pdf::PDFDocument* document = &assembledDocumentItem.second; + + const bool isDocumentFileAlreadyExisting = QFile::exists(filename); + if (!isOverwriteEnabled && isDocumentFileAlreadyExisting) + { + QMessageBox::critical(this, tr("Error"), tr("Document with given filename already exists.")); + return; + } + + pdf::PDFDocumentWriter writer(nullptr); + pdf::PDFOperationResult result = writer.write(filename, document, isDocumentFileAlreadyExisting); + + if (!result) + { + QMessageBox::critical(this, tr("Error"), result.getErrorMessage()); + return; + } + } + } + + break; + } + + case Operation::InsertImage: + { + QStringList filters; + for (const QByteArray& imageFormat : QImageReader::supportedImageFormats()) + { + filters << QString::fromLatin1(imageFormat).toLower(); + } + QString filter = tr("Images (*.%1)").arg(filters.join(" *.")); + QString fileName = QFileDialog::getOpenFileName(this, tr("Select Image"), m_settings.directory, filter, nullptr); + + if (!fileName.isEmpty()) + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->insertImage(fileName, !indexes.isEmpty() ? indexes.front() : QModelIndex()); + } + break; + } + + case Operation::InsertPDF: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)")); + if (!fileName.isEmpty()) + { + insertDocument(fileName, !indexes.isEmpty() ? indexes.back() : QModelIndex()); + } + break; + } + + case Operation::ReplaceSelection: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + + if (indexes.isEmpty()) + { + // Jakub Melka: we have nothing to do, selection is empty + return; + } + + QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_settings.directory, tr("PDF document (*.pdf)")); + if (!fileName.isEmpty()) + { + insertDocument(fileName, indexes.back()); + m_model->removeSelection(indexes); + } + break; + } + + case Operation::InvertSelection: + { + QModelIndex rootIndex = ui->documentItemsView->rootIndex(); + + QModelIndex firstIndex = rootIndex.child(0, 0); + QModelIndex lastIndex = rootIndex.child(rootIndex.model()->rowCount() - 1, 0); + QItemSelection selection(firstIndex, lastIndex); + + ui->documentItemsView->selectionModel()->select(selection, QItemSelectionModel::Toggle); + break; + } + + case Operation::RegroupEvenOdd: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupEvenOdd(indexes); + break; + } + + case Operation::RegroupPaired: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupPaired(indexes); + break; + } + + case Operation::RegroupBookmarks: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + + if (!indexes.isEmpty()) + { + PageItemModel::SelectionInfo selectionInfo = m_model->getSelectionInfo(indexes); + const std::map& documents = m_model->getDocuments(); + + auto it = documents.find(selectionInfo.firstDocumentIndex); + if (it != documents.end()) + { + const pdf::PDFDocument* document = &it->second.document; + SelectBookmarksToRegroupDialog dialog(document, this); + + if (dialog.exec() == SelectBookmarksToRegroupDialog::Accepted) + { + std::vector breakPageIndices; + std::vector outlineItems = dialog.getSelectedOutlineItems(); + + // Jakub Melka: Resolve outline items. Try to find an index + // of page of each outline item. + for (const pdf::PDFOutlineItem* item : outlineItems) + { + const pdf::PDFAction* action = item->getAction(); + const pdf::PDFActionGoTo* actionGoto = dynamic_cast(action); + if (actionGoto) + { + pdf::PDFDestination destination = actionGoto->getDestination(); + + if (destination.getDestinationType() == pdf::DestinationType::Named) + { + if (const pdf::PDFDestination* targetDestination = document->getCatalog()->getNamedDestination(destination.getName())) + { + destination = *targetDestination; + } + else + { + destination = pdf::PDFDestination(); + } + } + + if (destination.isValid()) + { + const size_t pageIndex = document->getCatalog()->getPageIndexFromPageReference(destination.getPageReference()); + if (pageIndex != pdf::PDFCatalog::INVALID_PAGE_INDEX) + { + breakPageIndices.push_back(pageIndex + 1); + } + } + } + } + + std::sort(breakPageIndices.begin(), breakPageIndices.end()); + breakPageIndices.erase(std::unique(breakPageIndices.begin(), breakPageIndices.end()), breakPageIndices.end()); + + m_model->regroupBookmarks(indexes, breakPageIndices); + } + } + } + + break; + } + + case Operation::RegroupAlternatingPages: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupAlternatingPages(indexes, false); + break; + } + + case Operation::RegroupAlternatingPagesReversed: + { + QModelIndexList indexes = ui->documentItemsView->selectionModel()->selection().indexes(); + m_model->regroupAlternatingPages(indexes, true); + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + updateActions(); +} + +} // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/mainwindow.h b/Pdf4QtDocPageOrganizer/mainwindow.h index 9130fef..40cea6f 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.h +++ b/Pdf4QtDocPageOrganizer/mainwindow.h @@ -1,128 +1,128 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCPAGEORGANIZER_MAINWINDOW_H -#define PDFDOCPAGEORGANIZER_MAINWINDOW_H - -#include - -#include "pageitemmodel.h" -#include "pageitemdelegate.h" - -#include - -namespace Ui -{ -class MainWindow; -} - -namespace pdfdocpage -{ - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget* parent); - virtual ~MainWindow() override; - - QSize getMinPageImageSize() const; - QSize getDefaultPageImageSize() const; - QSize getMaxPageImageSize() const; - - enum class Operation - { - Clear, - CloneSelection, - RemoveSelection, - ReplaceSelection, - RestoreRemovedItems, - - Undo, - Redo, - - Cut, - Copy, - Paste, - - RotateLeft, - RotateRight, - - Group, - Ungroup, - - SelectNone, - SelectAll, - SelectEven, - SelectOdd, - SelectPortrait, - SelectLandscape, - InvertSelection, - - ZoomIn, - ZoomOut, - - Unite, - Separate, - SeparateGrouped, - - InsertImage, - InsertEmptyPage, - InsertPDF, - - RegroupEvenOdd, - RegroupPaired, - RegroupBookmarks, - RegroupAlternatingPages, - RegroupAlternatingPagesReversed, - - GetSource, - About - }; - -private slots: - void on_actionClose_triggered(); - void on_actionAddDocument_triggered(); - void onMappedActionTriggered(int actionId); - void updateActions(); - -private: - void loadSettings(); - void saveSettings(); - void insertDocument(const QString& fileName, const QModelIndex& insertIndex); - - bool canPerformOperation(Operation operation) const; - void performOperation(Operation operation); - - struct Settings - { - QString directory; - }; - - Ui::MainWindow* ui; - - PageItemModel* m_model; - PageItemDelegate* m_delegate; - Settings m_settings; - QSignalMapper m_mapper; - Qt::DropAction m_dropAction; -}; - -} // namespace pdfdocpage - -#endif // PDFDOCPAGEORGANIZER_MAINWINDOW_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCPAGEORGANIZER_MAINWINDOW_H +#define PDFDOCPAGEORGANIZER_MAINWINDOW_H + +#include + +#include "pageitemmodel.h" +#include "pageitemdelegate.h" + +#include + +namespace Ui +{ +class MainWindow; +} + +namespace pdfdocpage +{ + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget* parent); + virtual ~MainWindow() override; + + QSize getMinPageImageSize() const; + QSize getDefaultPageImageSize() const; + QSize getMaxPageImageSize() const; + + enum class Operation + { + Clear, + CloneSelection, + RemoveSelection, + ReplaceSelection, + RestoreRemovedItems, + + Undo, + Redo, + + Cut, + Copy, + Paste, + + RotateLeft, + RotateRight, + + Group, + Ungroup, + + SelectNone, + SelectAll, + SelectEven, + SelectOdd, + SelectPortrait, + SelectLandscape, + InvertSelection, + + ZoomIn, + ZoomOut, + + Unite, + Separate, + SeparateGrouped, + + InsertImage, + InsertEmptyPage, + InsertPDF, + + RegroupEvenOdd, + RegroupPaired, + RegroupBookmarks, + RegroupAlternatingPages, + RegroupAlternatingPagesReversed, + + GetSource, + About + }; + +private slots: + void on_actionClose_triggered(); + void on_actionAddDocument_triggered(); + void onMappedActionTriggered(int actionId); + void updateActions(); + +private: + void loadSettings(); + void saveSettings(); + void insertDocument(const QString& fileName, const QModelIndex& insertIndex); + + bool canPerformOperation(Operation operation) const; + void performOperation(Operation operation); + + struct Settings + { + QString directory; + }; + + Ui::MainWindow* ui; + + PageItemModel* m_model; + PageItemDelegate* m_delegate; + Settings m_settings; + QSignalMapper m_mapper; + Qt::DropAction m_dropAction; +}; + +} // namespace pdfdocpage + +#endif // PDFDOCPAGEORGANIZER_MAINWINDOW_H diff --git a/Pdf4QtDocPageOrganizer/mainwindow.ui b/Pdf4QtDocPageOrganizer/mainwindow.ui index 506c2eb..aed788c 100644 --- a/Pdf4QtDocPageOrganizer/mainwindow.ui +++ b/Pdf4QtDocPageOrganizer/mainwindow.ui @@ -1,590 +1,590 @@ - - - MainWindow - - - - 0 - 0 - 800 - 600 - - - - Workspace - - - - - - - true - - - QAbstractItemView::DragDrop - - - Qt::MoveAction - - - QAbstractItemView::ExtendedSelection - - - QListView::LeftToRight - - - QListView::IconMode - - - Qt::AlignCenter - - - - - - - - - 0 - 0 - 800 - 21 - - - - - File - - - - - - - - Edit - - - - - - - - - - - - - - - - - - - - - - Insert - - - - - - - - View - - - - - - - - - - - - - - - Make - - - - - - - - Help - - - - - - - Toolbars - - - - - Regroup - - - - - - - - - - - - - - - - - - - - - :/pdfdocpage/resources/open.svg:/pdfdocpage/resources/open.svg - - - Add Document - - - Ctrl+O - - - - - - :/pdfdocpage/resources/close.svg:/pdfdocpage/resources/close.svg - - - Close - - - Alt+F4 - - - - - - :/pdfdocpage/resources/clone-selection.svg:/pdfdocpage/resources/clone-selection.svg - - - Clone Selection - - - Ctrl+L - - - - - - :/pdfdocpage/resources/remove-selection.svg:/pdfdocpage/resources/remove-selection.svg - - - Remove Selection - - - Del - - - - - - :/pdfdocpage/resources/restore-removed-items.svg:/pdfdocpage/resources/restore-removed-items.svg - - - Restore Removed Items - - - Ctrl+Shift+R - - - - - - :/pdfdocpage/resources/insert-page-from-pdf.svg:/pdfdocpage/resources/insert-page-from-pdf.svg - - - Insert PDF - - - Insert PDF - - - Ctrl+I - - - - - - :/pdfdocpage/resources/insert-image.svg:/pdfdocpage/resources/insert-image.svg - - - Insert Image - - - Ctrl+Alt+I - - - - - - :/pdfdocpage/resources/insert-empty-page.svg:/pdfdocpage/resources/insert-empty-page.svg - - - Insert Empty Page - - - Ctrl+Shift+I - - - - - - :/pdfdocpage/resources/cut.svg:/pdfdocpage/resources/cut.svg - - - Cut - - - Ctrl+X - - - - - - :/pdfdocpage/resources/copy.svg:/pdfdocpage/resources/copy.svg - - - Copy - - - Ctrl+C - - - - - - :/pdfdocpage/resources/paste.svg:/pdfdocpage/resources/paste.svg - - - Paste - - - Ctrl+V - - - - - - :/pdfdocpage/resources/replace-selection.svg:/pdfdocpage/resources/replace-selection.svg - - - Replace Selection - - - Ctrl+Alt+R - - - - - - :/pdfdocpage/resources/select-none.svg:/pdfdocpage/resources/select-none.svg - - - Select None - - - Ctrl+N - - - - - - :/pdfdocpage/resources/select-all.svg:/pdfdocpage/resources/select-all.svg - - - Select All - - - Ctrl+A - - - - - - :/pdfdocpage/resources/select-even.svg:/pdfdocpage/resources/select-even.svg - - - Select Even - - - F9 - - - - - - :/pdfdocpage/resources/select-odd.svg:/pdfdocpage/resources/select-odd.svg - - - Select Odd - - - F10 - - - - - - :/pdfdocpage/resources/select-portrait.svg:/pdfdocpage/resources/select-portrait.svg - - - Select Portrait - - - F11 - - - - - - :/pdfdocpage/resources/select-landscape.svg:/pdfdocpage/resources/select-landscape.svg - - - Select Landscape - - - F12 - - - - - - :/pdfdocpage/resources/rotate-right.svg:/pdfdocpage/resources/rotate-right.svg - - - Rotate Right - - - F4 - - - - - - :/pdfdocpage/resources/rotate-left.svg:/pdfdocpage/resources/rotate-left.svg - - - Rotate Left - - - Shift+F4 - - - - - - :/pdfdocpage/resources/zoom-in.svg:/pdfdocpage/resources/zoom-in.svg - - - Zoom In - - - Ctrl++ - - - - - - :/pdfdocpage/resources/zoom-out.svg:/pdfdocpage/resources/zoom-out.svg - - - Zoom Out - - - Ctrl+- - - - - - - :/pdfdocpage/resources/get-source.svg:/pdfdocpage/resources/get-source.svg - - - Get Source - - - - - - :/pdfdocpage/resources/about.svg:/pdfdocpage/resources/about.svg - - - About - - - F1 - - - - - - :/pdfdocpage/resources/make-united-document.svg:/pdfdocpage/resources/make-united-document.svg - - - United Document - - - F5 - - - - - - :/pdfdocpage/resources/make-separated-document.svg:/pdfdocpage/resources/make-separated-document.svg - - - Separate to Multiple Documents - - - F6 - - - - - - :/pdfdocpage/resources/make-separated-document-from-groups.svg:/pdfdocpage/resources/make-separated-document-from-groups.svg - - - Separate to Multiple Documents (Grouped) - - - F7 - - - - - - :/pdfdocpage/resources/group.svg:/pdfdocpage/resources/group.svg - - - Group - - - Ctrl+G - - - - - - :/pdfdocpage/resources/ungroup.svg:/pdfdocpage/resources/ungroup.svg - - - Ungroup - - - Ctrl+Shift+G - - - - - - :/pdfdocpage/resources/clear.svg:/pdfdocpage/resources/clear.svg - - - Clear - - - Ctrl+W - - - - - - :/pdfdocpage/resources/regroup-even-odd.svg:/pdfdocpage/resources/regroup-even-odd.svg - - - Regroup by Even/Odd Pages - - - - - - :/pdfdocpage/resources/regroup-pairs.svg:/pdfdocpage/resources/regroup-pairs.svg - - - Regroup by Page Pairs - - - - - - :/pdfdocpage/resources/regroup-bookmarks.svg:/pdfdocpage/resources/regroup-bookmarks.svg - - - Regroup by Bookmarks - - - - - - :/pdfdocpage/resources/regroup-alternating.svg:/pdfdocpage/resources/regroup-alternating.svg - - - Regroup by Alternating Pages - - - - - - :/pdfdocpage/resources/regroup-alternating-reversed.svg:/pdfdocpage/resources/regroup-alternating-reversed.svg - - - Regroup by Alternating Pages (Reversed Order) - - - - - - :/pdfdocpage/resources/invert-selection.svg:/pdfdocpage/resources/invert-selection.svg - - - Invert Selection - - - - - - :/pdfdocpage/resources/undo.svg:/pdfdocpage/resources/undo.svg - - - Undo - - - Ctrl+Z - - - - - - :/pdfdocpage/resources/redo.svg:/pdfdocpage/resources/redo.svg - - - Redo - - - Ctrl+Y - - - - - - - - + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + Workspace + + + + + + + true + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + QAbstractItemView::ExtendedSelection + + + QListView::LeftToRight + + + QListView::IconMode + + + Qt::AlignCenter + + + + + + + + + 0 + 0 + 800 + 21 + + + + + File + + + + + + + + Edit + + + + + + + + + + + + + + + + + + + + + + Insert + + + + + + + + View + + + + + + + + + + + + + + + Make + + + + + + + + Help + + + + + + + Toolbars + + + + + Regroup + + + + + + + + + + + + + + + + + + + + + :/pdfdocpage/resources/open.svg:/pdfdocpage/resources/open.svg + + + Add Document + + + Ctrl+O + + + + + + :/pdfdocpage/resources/close.svg:/pdfdocpage/resources/close.svg + + + Close + + + Alt+F4 + + + + + + :/pdfdocpage/resources/clone-selection.svg:/pdfdocpage/resources/clone-selection.svg + + + Clone Selection + + + Ctrl+L + + + + + + :/pdfdocpage/resources/remove-selection.svg:/pdfdocpage/resources/remove-selection.svg + + + Remove Selection + + + Del + + + + + + :/pdfdocpage/resources/restore-removed-items.svg:/pdfdocpage/resources/restore-removed-items.svg + + + Restore Removed Items + + + Ctrl+Shift+R + + + + + + :/pdfdocpage/resources/insert-page-from-pdf.svg:/pdfdocpage/resources/insert-page-from-pdf.svg + + + Insert PDF + + + Insert PDF + + + Ctrl+I + + + + + + :/pdfdocpage/resources/insert-image.svg:/pdfdocpage/resources/insert-image.svg + + + Insert Image + + + Ctrl+Alt+I + + + + + + :/pdfdocpage/resources/insert-empty-page.svg:/pdfdocpage/resources/insert-empty-page.svg + + + Insert Empty Page + + + Ctrl+Shift+I + + + + + + :/pdfdocpage/resources/cut.svg:/pdfdocpage/resources/cut.svg + + + Cut + + + Ctrl+X + + + + + + :/pdfdocpage/resources/copy.svg:/pdfdocpage/resources/copy.svg + + + Copy + + + Ctrl+C + + + + + + :/pdfdocpage/resources/paste.svg:/pdfdocpage/resources/paste.svg + + + Paste + + + Ctrl+V + + + + + + :/pdfdocpage/resources/replace-selection.svg:/pdfdocpage/resources/replace-selection.svg + + + Replace Selection + + + Ctrl+Alt+R + + + + + + :/pdfdocpage/resources/select-none.svg:/pdfdocpage/resources/select-none.svg + + + Select None + + + Ctrl+N + + + + + + :/pdfdocpage/resources/select-all.svg:/pdfdocpage/resources/select-all.svg + + + Select All + + + Ctrl+A + + + + + + :/pdfdocpage/resources/select-even.svg:/pdfdocpage/resources/select-even.svg + + + Select Even + + + F9 + + + + + + :/pdfdocpage/resources/select-odd.svg:/pdfdocpage/resources/select-odd.svg + + + Select Odd + + + F10 + + + + + + :/pdfdocpage/resources/select-portrait.svg:/pdfdocpage/resources/select-portrait.svg + + + Select Portrait + + + F11 + + + + + + :/pdfdocpage/resources/select-landscape.svg:/pdfdocpage/resources/select-landscape.svg + + + Select Landscape + + + F12 + + + + + + :/pdfdocpage/resources/rotate-right.svg:/pdfdocpage/resources/rotate-right.svg + + + Rotate Right + + + F4 + + + + + + :/pdfdocpage/resources/rotate-left.svg:/pdfdocpage/resources/rotate-left.svg + + + Rotate Left + + + Shift+F4 + + + + + + :/pdfdocpage/resources/zoom-in.svg:/pdfdocpage/resources/zoom-in.svg + + + Zoom In + + + Ctrl++ + + + + + + :/pdfdocpage/resources/zoom-out.svg:/pdfdocpage/resources/zoom-out.svg + + + Zoom Out + + + Ctrl+- + + + + + + :/pdfdocpage/resources/get-source.svg:/pdfdocpage/resources/get-source.svg + + + Get Source + + + + + + :/pdfdocpage/resources/about.svg:/pdfdocpage/resources/about.svg + + + About + + + F1 + + + + + + :/pdfdocpage/resources/make-united-document.svg:/pdfdocpage/resources/make-united-document.svg + + + United Document + + + F5 + + + + + + :/pdfdocpage/resources/make-separated-document.svg:/pdfdocpage/resources/make-separated-document.svg + + + Separate to Multiple Documents + + + F6 + + + + + + :/pdfdocpage/resources/make-separated-document-from-groups.svg:/pdfdocpage/resources/make-separated-document-from-groups.svg + + + Separate to Multiple Documents (Grouped) + + + F7 + + + + + + :/pdfdocpage/resources/group.svg:/pdfdocpage/resources/group.svg + + + Group + + + Ctrl+G + + + + + + :/pdfdocpage/resources/ungroup.svg:/pdfdocpage/resources/ungroup.svg + + + Ungroup + + + Ctrl+Shift+G + + + + + + :/pdfdocpage/resources/clear.svg:/pdfdocpage/resources/clear.svg + + + Clear + + + Ctrl+W + + + + + + :/pdfdocpage/resources/regroup-even-odd.svg:/pdfdocpage/resources/regroup-even-odd.svg + + + Regroup by Even/Odd Pages + + + + + + :/pdfdocpage/resources/regroup-pairs.svg:/pdfdocpage/resources/regroup-pairs.svg + + + Regroup by Page Pairs + + + + + + :/pdfdocpage/resources/regroup-bookmarks.svg:/pdfdocpage/resources/regroup-bookmarks.svg + + + Regroup by Bookmarks + + + + + + :/pdfdocpage/resources/regroup-alternating.svg:/pdfdocpage/resources/regroup-alternating.svg + + + Regroup by Alternating Pages + + + + + + :/pdfdocpage/resources/regroup-alternating-reversed.svg:/pdfdocpage/resources/regroup-alternating-reversed.svg + + + Regroup by Alternating Pages (Reversed Order) + + + + + + :/pdfdocpage/resources/invert-selection.svg:/pdfdocpage/resources/invert-selection.svg + + + Invert Selection + + + + + + :/pdfdocpage/resources/undo.svg:/pdfdocpage/resources/undo.svg + + + Undo + + + Ctrl+Z + + + + + + :/pdfdocpage/resources/redo.svg:/pdfdocpage/resources/redo.svg + + + Redo + + + Ctrl+Y + + + + + + + + diff --git a/Pdf4QtDocPageOrganizer/pageitemdelegate.cpp b/Pdf4QtDocPageOrganizer/pageitemdelegate.cpp index 60c75bd..e068620 100644 --- a/Pdf4QtDocPageOrganizer/pageitemdelegate.cpp +++ b/Pdf4QtDocPageOrganizer/pageitemdelegate.cpp @@ -1,242 +1,242 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pageitemdelegate.h" -#include "pageitemmodel.h" -#include "pdfwidgetutils.h" -#include "pdfpainterutils.h" -#include "pdfrenderer.h" -#include "pdfcompiler.h" -#include "pdfconstants.h" - -#include -#include - -namespace pdfdocpage -{ - -PageItemDelegate::PageItemDelegate(PageItemModel* model, QObject* parent) : - BaseClass(parent), - m_model(model), - m_rasterizer(nullptr) -{ - m_rasterizer = new pdf::PDFRasterizer(this); - QSurfaceFormat format; - format.setSamples(16); - m_rasterizer->reset(true, format); -} - -PageItemDelegate::~PageItemDelegate() -{ - -} - -void PageItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - const PageGroupItem* item = m_model->getItem(index); - - if (!item) - { - return; - } - - QRect rect = option.rect; - - QSize scaledSize = pdf::PDFWidgetUtils::scaleDPI(option.widget, m_pageImageSize); - int verticalSpacing = pdf::PDFWidgetUtils::scaleDPI_y(option.widget, getVerticalSpacing()); - int horizontalSpacing = pdf::PDFWidgetUtils::scaleDPI_x(option.widget, getHorizontalSpacing()); - - QRect pageBoundingRect = QRect(QPoint(rect.left() + (rect.width() - scaledSize.width()) / 2, rect.top() + verticalSpacing), scaledSize); - - // Draw page preview - if (!item->groups.empty()) - { - const PageGroupItem::GroupItem& groupItem = item->groups.front(); - QSizeF rotatedPageSize = pdf::PDFPage::getRotatedBox(QRectF(QPointF(0, 0), groupItem.rotatedPageDimensionsMM), groupItem.pageAdditionalRotation).size(); - QSize pageImageSize = rotatedPageSize.scaled(pageBoundingRect.size(), Qt::KeepAspectRatio).toSize(); - QRect pageImageRect(pageBoundingRect.topLeft() + QPoint((pageBoundingRect.width() - pageImageSize.width()) / 2, (pageBoundingRect.height() - pageImageSize.height()) / 2), pageImageSize); - - painter->setBrush(Qt::white); - painter->drawRect(pageImageRect); - - QPixmap pageImagePixmap = getPageImagePixmap(item, pageImageRect); - if (!pageImagePixmap.isNull()) - { - painter->drawPixmap(pageImageRect, pageImagePixmap); - } - - painter->setPen(QPen(Qt::black)); - painter->setBrush(Qt::NoBrush); - painter->drawRect(pageImageRect); - } - - int textOffset = pageBoundingRect.bottom() + verticalSpacing; - QRect textRect = option.rect; - textRect.setTop(textOffset); - textRect.setHeight(option.fontMetrics.lineSpacing()); - painter->drawText(textRect, Qt::AlignCenter | Qt::TextSingleLine, item->groupName); - textRect.translate(0, textRect.height()); - painter->drawText(textRect, Qt::AlignCenter | Qt::TextSingleLine, item->pagesCaption); - - if (option.state.testFlag(QStyle::State_Selected)) - { - QColor selectedColor = option.palette.color(QPalette::Active, QPalette::Highlight); - selectedColor.setAlphaF(0.3); - painter->fillRect(rect, selectedColor); - } - - QPoint tagPoint = rect.topRight() + QPoint(-horizontalSpacing, verticalSpacing); - for (const QString& tag : item->tags) - { - QStringList splitted = tag.split('@', Qt::KeepEmptyParts); - if (splitted.size() != 2 || splitted.back().isEmpty()) - { - continue; - } - - QColor color; - color.setNamedColor(splitted.front()); - QRect bubbleRect = pdf::PDFPainterHelper::drawBubble(painter, tagPoint, color, splitted.back(), Qt::AlignLeft | Qt::AlignBottom); - tagPoint.ry() += bubbleRect.height() + verticalSpacing; - } -} - -QSize PageItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - Q_UNUSED(index); - - QSize scaledSize = pdf::PDFWidgetUtils::scaleDPI(option.widget, m_pageImageSize); - int height = scaledSize.height() + option.fontMetrics.lineSpacing() * 2 + 2 * pdf::PDFWidgetUtils::scaleDPI_y(option.widget, getVerticalSpacing()); - int width = qMax(pdf::PDFWidgetUtils::scaleDPI_x(option.widget, 40), scaledSize.width() + 2 * pdf::PDFWidgetUtils::scaleDPI_x(option.widget, getHorizontalSpacing())); - return QSize(width, height); -} - -QSize PageItemDelegate::getPageImageSize() const -{ - return m_pageImageSize; -} - -void PageItemDelegate::setPageImageSize(QSize pageImageSize) -{ - if (m_pageImageSize != pageImageSize) - { - m_pageImageSize = pageImageSize; - emit sizeHintChanged(QModelIndex()); - } -} - -QPixmap PageItemDelegate::getPageImagePixmap(const PageGroupItem* item, QRect rect) const -{ - QPixmap pixmap; - - Q_ASSERT(item); - if (item->groups.empty()) - { - return pixmap; - } - - const PageGroupItem::GroupItem& groupItem = item->groups.front(); - if (groupItem.pageType == PT_Empty) - { - return pixmap; - } - - // Jakub Melka: generate key and see, if pixmap is not cached - QString key = QString("%1#%2#%3#%4#%5@%6x%7").arg(groupItem.documentIndex).arg(groupItem.imageIndex).arg(int(groupItem.pageAdditionalRotation)).arg(groupItem.pageIndex).arg(groupItem.pageType).arg(rect.width()).arg(rect.height()); - - if (!QPixmapCache::find(key, &pixmap)) - { - // We must draw the pixmap - pixmap = QPixmap(rect.width(), rect.height()); - pixmap.fill(Qt::transparent); - - switch (groupItem.pageType) - { - case pdfdocpage::PT_DocumentPage: - { - const auto& documents = m_model->getDocuments(); - auto it = documents.find(groupItem.documentIndex); - if (it != documents.cend()) - { - const pdf::PDFDocument& document = it->second.document; - const pdf::PDFInteger pageIndex = groupItem.pageIndex - 1; - if (pageIndex >= 0 && pageIndex < pdf::PDFInteger(document.getCatalog()->getPageCount())) - { - const pdf::PDFPage* page = document.getCatalog()->getPage(pageIndex); - Q_ASSERT(page); - - pdf::PDFPrecompiledPage compiledPage; - pdf::PDFFontCache fontCache(pdf::DEFAULT_FONT_CACHE_LIMIT, pdf::DEFAULT_REALIZED_FONT_CACHE_LIMIT); - pdf::PDFCMSManager cmsManager(nullptr); - pdf::PDFOptionalContentActivity optionalContentActivity(&document, pdf::OCUsage::View, nullptr); - - fontCache.setDocument(pdf::PDFModifiedDocument(const_cast(&document), &optionalContentActivity)); - cmsManager.setDocument(&document); - - pdf::PDFCMSPointer cms = cmsManager.getCurrentCMS(); - pdf::PDFRenderer renderer(&document, &fontCache, cms.data(), &optionalContentActivity, pdf::PDFRenderer::getDefaultFeatures(), pdf::PDFMeshQualitySettings()); - renderer.compile(&compiledPage, pageIndex); - - QSize imageSize = rect.size(); - QImage pageImage = m_rasterizer->render(pageIndex, page, &compiledPage, imageSize, pdf::PDFRenderer::getDefaultFeatures(), nullptr, groupItem.pageAdditionalRotation); - pixmap = QPixmap::fromImage(qMove(pageImage)); - } - } - break; - } - - case pdfdocpage::PT_Image: - { - const auto& images = m_model->getImages(); - auto it = images.find(groupItem.imageIndex); - if (it != images.cend()) - { - const QImage& image = it->second.image; - if (!image.isNull()) - { - QRect drawRect(QPoint(0, 0), rect.size()); - QRect mediaBox(QPoint(0, 0), image.size()); - QRectF rotatedMediaBox = pdf::PDFPage::getRotatedBox(mediaBox, groupItem.pageAdditionalRotation); - QMatrix matrix = pdf::PDFRenderer::createMediaBoxToDevicePointMatrix(rotatedMediaBox, drawRect, groupItem.pageAdditionalRotation); - - QPainter painter(&pixmap); - painter.setWorldMatrixEnabled(true); - painter.setWorldMatrix(matrix); - painter.translate(0, image.height()); - painter.scale(1.0, -1.0); - painter.drawImage(0, 0, image); - } - } - break; - } - - case pdfdocpage::PT_Empty: - Q_ASSERT(false); - break; - - default: - Q_ASSERT(false); - break; - } - - QPixmapCache::insert(key, pixmap); - } - - return pixmap; -} - -} // namespace pdfdocpage +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pageitemdelegate.h" +#include "pageitemmodel.h" +#include "pdfwidgetutils.h" +#include "pdfpainterutils.h" +#include "pdfrenderer.h" +#include "pdfcompiler.h" +#include "pdfconstants.h" + +#include +#include + +namespace pdfdocpage +{ + +PageItemDelegate::PageItemDelegate(PageItemModel* model, QObject* parent) : + BaseClass(parent), + m_model(model), + m_rasterizer(nullptr) +{ + m_rasterizer = new pdf::PDFRasterizer(this); + QSurfaceFormat format; + format.setSamples(16); + m_rasterizer->reset(true, format); +} + +PageItemDelegate::~PageItemDelegate() +{ + +} + +void PageItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + const PageGroupItem* item = m_model->getItem(index); + + if (!item) + { + return; + } + + QRect rect = option.rect; + + QSize scaledSize = pdf::PDFWidgetUtils::scaleDPI(option.widget, m_pageImageSize); + int verticalSpacing = pdf::PDFWidgetUtils::scaleDPI_y(option.widget, getVerticalSpacing()); + int horizontalSpacing = pdf::PDFWidgetUtils::scaleDPI_x(option.widget, getHorizontalSpacing()); + + QRect pageBoundingRect = QRect(QPoint(rect.left() + (rect.width() - scaledSize.width()) / 2, rect.top() + verticalSpacing), scaledSize); + + // Draw page preview + if (!item->groups.empty()) + { + const PageGroupItem::GroupItem& groupItem = item->groups.front(); + QSizeF rotatedPageSize = pdf::PDFPage::getRotatedBox(QRectF(QPointF(0, 0), groupItem.rotatedPageDimensionsMM), groupItem.pageAdditionalRotation).size(); + QSize pageImageSize = rotatedPageSize.scaled(pageBoundingRect.size(), Qt::KeepAspectRatio).toSize(); + QRect pageImageRect(pageBoundingRect.topLeft() + QPoint((pageBoundingRect.width() - pageImageSize.width()) / 2, (pageBoundingRect.height() - pageImageSize.height()) / 2), pageImageSize); + + painter->setBrush(Qt::white); + painter->drawRect(pageImageRect); + + QPixmap pageImagePixmap = getPageImagePixmap(item, pageImageRect); + if (!pageImagePixmap.isNull()) + { + painter->drawPixmap(pageImageRect, pageImagePixmap); + } + + painter->setPen(QPen(Qt::black)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(pageImageRect); + } + + int textOffset = pageBoundingRect.bottom() + verticalSpacing; + QRect textRect = option.rect; + textRect.setTop(textOffset); + textRect.setHeight(option.fontMetrics.lineSpacing()); + painter->drawText(textRect, Qt::AlignCenter | Qt::TextSingleLine, item->groupName); + textRect.translate(0, textRect.height()); + painter->drawText(textRect, Qt::AlignCenter | Qt::TextSingleLine, item->pagesCaption); + + if (option.state.testFlag(QStyle::State_Selected)) + { + QColor selectedColor = option.palette.color(QPalette::Active, QPalette::Highlight); + selectedColor.setAlphaF(0.3); + painter->fillRect(rect, selectedColor); + } + + QPoint tagPoint = rect.topRight() + QPoint(-horizontalSpacing, verticalSpacing); + for (const QString& tag : item->tags) + { + QStringList splitted = tag.split('@', Qt::KeepEmptyParts); + if (splitted.size() != 2 || splitted.back().isEmpty()) + { + continue; + } + + QColor color; + color.setNamedColor(splitted.front()); + QRect bubbleRect = pdf::PDFPainterHelper::drawBubble(painter, tagPoint, color, splitted.back(), Qt::AlignLeft | Qt::AlignBottom); + tagPoint.ry() += bubbleRect.height() + verticalSpacing; + } +} + +QSize PageItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(index); + + QSize scaledSize = pdf::PDFWidgetUtils::scaleDPI(option.widget, m_pageImageSize); + int height = scaledSize.height() + option.fontMetrics.lineSpacing() * 2 + 2 * pdf::PDFWidgetUtils::scaleDPI_y(option.widget, getVerticalSpacing()); + int width = qMax(pdf::PDFWidgetUtils::scaleDPI_x(option.widget, 40), scaledSize.width() + 2 * pdf::PDFWidgetUtils::scaleDPI_x(option.widget, getHorizontalSpacing())); + return QSize(width, height); +} + +QSize PageItemDelegate::getPageImageSize() const +{ + return m_pageImageSize; +} + +void PageItemDelegate::setPageImageSize(QSize pageImageSize) +{ + if (m_pageImageSize != pageImageSize) + { + m_pageImageSize = pageImageSize; + emit sizeHintChanged(QModelIndex()); + } +} + +QPixmap PageItemDelegate::getPageImagePixmap(const PageGroupItem* item, QRect rect) const +{ + QPixmap pixmap; + + Q_ASSERT(item); + if (item->groups.empty()) + { + return pixmap; + } + + const PageGroupItem::GroupItem& groupItem = item->groups.front(); + if (groupItem.pageType == PT_Empty) + { + return pixmap; + } + + // Jakub Melka: generate key and see, if pixmap is not cached + QString key = QString("%1#%2#%3#%4#%5@%6x%7").arg(groupItem.documentIndex).arg(groupItem.imageIndex).arg(int(groupItem.pageAdditionalRotation)).arg(groupItem.pageIndex).arg(groupItem.pageType).arg(rect.width()).arg(rect.height()); + + if (!QPixmapCache::find(key, &pixmap)) + { + // We must draw the pixmap + pixmap = QPixmap(rect.width(), rect.height()); + pixmap.fill(Qt::transparent); + + switch (groupItem.pageType) + { + case pdfdocpage::PT_DocumentPage: + { + const auto& documents = m_model->getDocuments(); + auto it = documents.find(groupItem.documentIndex); + if (it != documents.cend()) + { + const pdf::PDFDocument& document = it->second.document; + const pdf::PDFInteger pageIndex = groupItem.pageIndex - 1; + if (pageIndex >= 0 && pageIndex < pdf::PDFInteger(document.getCatalog()->getPageCount())) + { + const pdf::PDFPage* page = document.getCatalog()->getPage(pageIndex); + Q_ASSERT(page); + + pdf::PDFPrecompiledPage compiledPage; + pdf::PDFFontCache fontCache(pdf::DEFAULT_FONT_CACHE_LIMIT, pdf::DEFAULT_REALIZED_FONT_CACHE_LIMIT); + pdf::PDFCMSManager cmsManager(nullptr); + pdf::PDFOptionalContentActivity optionalContentActivity(&document, pdf::OCUsage::View, nullptr); + + fontCache.setDocument(pdf::PDFModifiedDocument(const_cast(&document), &optionalContentActivity)); + cmsManager.setDocument(&document); + + pdf::PDFCMSPointer cms = cmsManager.getCurrentCMS(); + pdf::PDFRenderer renderer(&document, &fontCache, cms.data(), &optionalContentActivity, pdf::PDFRenderer::getDefaultFeatures(), pdf::PDFMeshQualitySettings()); + renderer.compile(&compiledPage, pageIndex); + + QSize imageSize = rect.size(); + QImage pageImage = m_rasterizer->render(pageIndex, page, &compiledPage, imageSize, pdf::PDFRenderer::getDefaultFeatures(), nullptr, groupItem.pageAdditionalRotation); + pixmap = QPixmap::fromImage(qMove(pageImage)); + } + } + break; + } + + case pdfdocpage::PT_Image: + { + const auto& images = m_model->getImages(); + auto it = images.find(groupItem.imageIndex); + if (it != images.cend()) + { + const QImage& image = it->second.image; + if (!image.isNull()) + { + QRect drawRect(QPoint(0, 0), rect.size()); + QRect mediaBox(QPoint(0, 0), image.size()); + QRectF rotatedMediaBox = pdf::PDFPage::getRotatedBox(mediaBox, groupItem.pageAdditionalRotation); + QMatrix matrix = pdf::PDFRenderer::createMediaBoxToDevicePointMatrix(rotatedMediaBox, drawRect, groupItem.pageAdditionalRotation); + + QPainter painter(&pixmap); + painter.setWorldMatrixEnabled(true); + painter.setWorldMatrix(matrix); + painter.translate(0, image.height()); + painter.scale(1.0, -1.0); + painter.drawImage(0, 0, image); + } + } + break; + } + + case pdfdocpage::PT_Empty: + Q_ASSERT(false); + break; + + default: + Q_ASSERT(false); + break; + } + + QPixmapCache::insert(key, pixmap); + } + + return pixmap; +} + +} // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/pageitemdelegate.h b/Pdf4QtDocPageOrganizer/pageitemdelegate.h index 65775b6..24d12bc 100644 --- a/Pdf4QtDocPageOrganizer/pageitemdelegate.h +++ b/Pdf4QtDocPageOrganizer/pageitemdelegate.h @@ -1,62 +1,62 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCPAGEORGANIZER_PAGEITEMDELEGATE_H -#define PDFDOCPAGEORGANIZER_PAGEITEMDELEGATE_H - -#include "pdfrenderer.h" -#include "pdfcms.h" - -#include - -namespace pdfdocpage -{ - -class PageItemModel; -struct PageGroupItem; - -class PageItemDelegate : public QAbstractItemDelegate -{ - Q_OBJECT - -private: - using BaseClass = QAbstractItemDelegate; - -public: - explicit PageItemDelegate(PageItemModel* model, QObject* parent); - virtual ~PageItemDelegate() override; - - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; - virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; - - QSize getPageImageSize() const; - void setPageImageSize(QSize pageImageSize); - -private: - static constexpr int getVerticalSpacing() { return 5; } - static constexpr int getHorizontalSpacing() { return 5; } - - QPixmap getPageImagePixmap(const PageGroupItem* item, QRect rect) const; - - PageItemModel* m_model; - QSize m_pageImageSize; - pdf::PDFRasterizer* m_rasterizer; -}; - -} // namespace pdfdocpage - -#endif // PDFDOCPAGEORGANIZER_PAGEITEMDELEGATE_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCPAGEORGANIZER_PAGEITEMDELEGATE_H +#define PDFDOCPAGEORGANIZER_PAGEITEMDELEGATE_H + +#include "pdfrenderer.h" +#include "pdfcms.h" + +#include + +namespace pdfdocpage +{ + +class PageItemModel; +struct PageGroupItem; + +class PageItemDelegate : public QAbstractItemDelegate +{ + Q_OBJECT + +private: + using BaseClass = QAbstractItemDelegate; + +public: + explicit PageItemDelegate(PageItemModel* model, QObject* parent); + virtual ~PageItemDelegate() override; + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + QSize getPageImageSize() const; + void setPageImageSize(QSize pageImageSize); + +private: + static constexpr int getVerticalSpacing() { return 5; } + static constexpr int getHorizontalSpacing() { return 5; } + + QPixmap getPageImagePixmap(const PageGroupItem* item, QRect rect) const; + + PageItemModel* m_model; + QSize m_pageImageSize; + pdf::PDFRasterizer* m_rasterizer; +}; + +} // namespace pdfdocpage + +#endif // PDFDOCPAGEORGANIZER_PAGEITEMDELEGATE_H diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp index ac29929..c666e63 100644 --- a/Pdf4QtDocPageOrganizer/pageitemmodel.cpp +++ b/Pdf4QtDocPageOrganizer/pageitemmodel.cpp @@ -1,1282 +1,1282 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pageitemmodel.h" - -#include -#include - -#include - -namespace pdfdocpage -{ - -PageItemModel::PageItemModel(QObject* parent) : - QAbstractItemModel(parent) -{ -} - -QVariant PageItemModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - Q_UNUSED(section); - Q_UNUSED(orientation); - Q_UNUSED(role); - - return QVariant(); -} - -QModelIndex PageItemModel::index(int row, int column, const QModelIndex& parent) const -{ - if (hasIndex(row, column, parent)) - { - return createIndex(row, column, nullptr); - } - - return QModelIndex(); -} - -QModelIndex PageItemModel::parent(const QModelIndex& index) const -{ - Q_UNUSED(index); - - return QModelIndex(); -} - -int PageItemModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - { - return 0; - } - - return int(m_pageGroupItems.size()); -} - -int PageItemModel::columnCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - { - return 0; - } - - return 1; -} - -QVariant PageItemModel::data(const QModelIndex& index, int role) const -{ - if (!index.isValid()) - { - return QVariant(); - } - - switch (role) - { - case Qt::DisplayRole: - return m_pageGroupItems.at(index.row()).groupName; - - default: - break; - } - - return QVariant(); -} - -int PageItemModel::insertDocument(QString fileName, pdf::PDFDocument document, const QModelIndex& index) -{ - Modifier modifier(this); - auto it = std::find_if(m_documents.cbegin(), m_documents.cend(), [&](const auto& item) { return item.second.fileName == fileName; }); - if (it != m_documents.cend()) - { - return -1; - } - - int newIndex = 1; - - if (!m_documents.empty()) - { - newIndex = (m_documents.rbegin()->first) + 1; - } - - m_documents[newIndex] = { qMove(fileName), qMove(document) }; - createDocumentGroup(newIndex, index); - return newIndex; -} - -int PageItemModel::insertImage(QString fileName, const QModelIndex& index) -{ - Modifier modifier(this); - QFile file(fileName); - - if (file.open(QFile::ReadOnly)) - { - ImageItem item; - item.imageData = file.readAll(); - - QImageReader reader(fileName); - item.image = reader.read(); - - file.close(); - - if (!item.image.isNull()) - { - int newIndex = 1; - - if (!m_images.empty()) - { - newIndex = (m_images.rbegin()->first) + 1; - } - - m_images[newIndex] = qMove(item); - - // Insert image item - PageGroupItem newItem; - - newItem.groups.reserve(1); - - PageGroupItem::GroupItem groupItem; - groupItem.imageIndex = newIndex; - groupItem.rotatedPageDimensionsMM = m_images[newIndex].image.size() * 0.1; - groupItem.pageType = PT_Image; - newItem.groups.push_back(qMove(groupItem)); - - updateItemCaptionAndTags(newItem); - int insertRow = index.isValid() ? index.row() + 1 : int(m_pageGroupItems.size()); - - beginInsertRows(QModelIndex(), insertRow, insertRow); - m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem)); - endInsertRows(); - - return newIndex; - } - } - - return -1; -} - -const PageGroupItem* PageItemModel::getItem(const QModelIndex& index) const -{ - if (index.isValid()) - { - if (index.row() < m_pageGroupItems.size()) - { - return &m_pageGroupItems.at(index.row()); - } - } - - return nullptr; -} - -PageGroupItem* PageItemModel::getItem(const QModelIndex& index) -{ - if (index.isValid()) - { - if (index.row() < m_pageGroupItems.size()) - { - return &m_pageGroupItems.at(index.row()); - } - } - - return nullptr; -} - -bool PageItemModel::isGrouped(const QModelIndexList& indices) const -{ - for (const QModelIndex& index : indices) - { - if (const PageGroupItem* item = getItem(index)) - { - if (item->isGrouped()) - { - return true; - } - } - } - - return false; -} - -QItemSelection PageItemModel::getSelectionEven() const -{ - return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.pageIndex % 2 == 1; }); -} - -QItemSelection PageItemModel::getSelectionOdd() const -{ - return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.pageIndex % 2 == 0; }); -} - -QItemSelection PageItemModel::getSelectionPortrait() const -{ - return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.rotatedPageDimensionsMM.width() <= groupItem.rotatedPageDimensionsMM.height(); }); -} - -QItemSelection PageItemModel::getSelectionLandscape() const -{ - return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.rotatedPageDimensionsMM.width() >= groupItem.rotatedPageDimensionsMM.height(); }); -} - -void PageItemModel::group(const QModelIndexList& list) -{ - if (list.isEmpty()) - { - return; - } - - Modifier modifier(this); - - std::vector groupedIndices; - groupedIndices.reserve(list.size()); - std::transform(list.cbegin(), list.cend(), std::back_inserter(groupedIndices), [](const auto& index) { return index.row(); }); - std::sort(groupedIndices.begin(), groupedIndices.end()); - - std::vector newPageGroupItems; - std::vector newGroups; - newPageGroupItems.reserve(m_pageGroupItems.size()); - for (size_t i = 0; i < m_pageGroupItems.size(); ++i) - { - const PageGroupItem& item = m_pageGroupItems[i]; - if (std::binary_search(groupedIndices.cbegin(), groupedIndices.cend(), i)) - { - newGroups.insert(newGroups.end(), item.groups.begin(), item.groups.end()); - } - else - { - newPageGroupItems.push_back(item); - } - } - - PageGroupItem newItem; - newItem.groups = qMove(newGroups); - updateItemCaptionAndTags(newItem); - newPageGroupItems.insert(std::next(newPageGroupItems.begin(), groupedIndices.front()), qMove(newItem)); - - if (newPageGroupItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(newPageGroupItems); - endResetModel(); - } -} - -void PageItemModel::ungroup(const QModelIndexList& list) -{ - if (list.isEmpty()) - { - return; - } - - Modifier modifier(this); - - std::vector ungroupedIndices; - ungroupedIndices.reserve(list.size()); - std::transform(list.cbegin(), list.cend(), std::back_inserter(ungroupedIndices), [](const auto& index) { return index.row(); }); - std::sort(ungroupedIndices.begin(), ungroupedIndices.end()); - - std::vector newPageGroupItems; - newPageGroupItems.reserve(m_pageGroupItems.size()); - for (size_t i = 0; i < m_pageGroupItems.size(); ++i) - { - const PageGroupItem& item = m_pageGroupItems[i]; - if (item.isGrouped() && std::binary_search(ungroupedIndices.cbegin(), ungroupedIndices.cend(), i)) - { - for (const PageGroupItem::GroupItem& groupItem : item.groups) - { - PageGroupItem newItem; - newItem.groups = { groupItem }; - updateItemCaptionAndTags(newItem); - newPageGroupItems.push_back(qMove(newItem)); - } - } - else - { - newPageGroupItems.push_back(item); - } - } - - if (newPageGroupItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(newPageGroupItems); - endResetModel(); - } -} - -QModelIndexList PageItemModel::restoreRemovedItems() -{ - QModelIndexList result; - - if (m_trashBin.empty()) - { - // Jakub Melka: nothing to do - return result; - } - - Modifier modifier(this); - - const int trashBinSize = int(m_trashBin.size()); - const int rowCount = this->rowCount(QModelIndex()); - beginInsertRows(QModelIndex(), rowCount, rowCount + trashBinSize - 1); - m_pageGroupItems.insert(m_pageGroupItems.end(), std::make_move_iterator(m_trashBin.begin()), std::make_move_iterator(m_trashBin.end())); - m_trashBin.clear(); - endInsertRows(); - - result.reserve(trashBinSize); - for (int i = rowCount; i < rowCount + trashBinSize; ++i) - { - result << index(i, 0, QModelIndex()); - } - - return result; -} - -QModelIndexList PageItemModel::cloneSelection(const QModelIndexList& list) -{ - QModelIndexList result; - - if (list.empty()) - { - // Jakub Melka: nothing to do - return result; - } - - Modifier modifier(this); - - std::vector rows; - rows.reserve(list.size()); - std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); - - std::vector clonedGroups; - clonedGroups.reserve(rows.size()); - - for (int row : rows) - { - clonedGroups.push_back(m_pageGroupItems[row]); - } - - const int insertRow = rows.back() + 1; - const int lastRow = insertRow + int(rows.size()); - - beginResetModel(); - m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), clonedGroups.begin(), clonedGroups.end()); - endResetModel(); - - result.reserve(int(rows.size())); - for (int i = insertRow; i < lastRow; ++i) - { - result << index(i, 0, QModelIndex()); - } - - return result; -} - -void PageItemModel::removeSelection(const QModelIndexList& list) -{ - if (list.empty()) - { - // Jakub Melka: nothing to do - return; - } - - Modifier modifier(this); - - std::vector rows; - rows.reserve(list.size()); - std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); - std::sort(rows.begin(), rows.end(), std::greater()); - - beginResetModel(); - for (int row : rows) - { - m_trashBin.emplace_back(qMove(m_pageGroupItems[row])); - m_pageGroupItems.erase(std::next(m_pageGroupItems.begin(), row)); - } - endResetModel(); -} - -void PageItemModel::insertEmptyPage(const QModelIndexList& list) -{ - Modifier modifier(this); - - if (list.isEmpty()) - { - insertEmptyPage(QModelIndex()); - } - else - { - QModelIndexList listCopy = list; - std::sort(listCopy.begin(), listCopy.end()); - std::reverse(listCopy.begin(), listCopy.end()); - - for (const QModelIndex& index: listCopy) - { - insertEmptyPage(index); - } - } -} - -void PageItemModel::insertEmptyPage(const QModelIndex& index) -{ - int insertRow = index.isValid()? index.row() + 1 : int(m_pageGroupItems.size()); - - const int templateRow = index.isValid() ? index.row() : int(m_pageGroupItems.size()) - 1; - const bool isTemplateRowValid = templateRow > -1; - - PageGroupItem::GroupItem groupItem; - groupItem.pageAdditionalRotation = isTemplateRowValid ? m_pageGroupItems[templateRow].groups.back().pageAdditionalRotation : pdf::PageRotation::None; - groupItem.pageType = PT_Empty; - groupItem.rotatedPageDimensionsMM = isTemplateRowValid ? m_pageGroupItems[templateRow].groups.back().rotatedPageDimensionsMM : QSizeF(210, 297); - - PageGroupItem blankPageItem; - blankPageItem.groups.push_back(groupItem); - updateItemCaptionAndTags(blankPageItem); - - beginInsertRows(QModelIndex(), insertRow, insertRow); - m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), std::move(blankPageItem)); - endInsertRows(); -} - -std::vector PageItemModel::extractItems(std::vector& items, - const QModelIndexList& selection) const -{ - std::vector extractedItems; - - std::vector rows; - rows.reserve(selection.size()); - std::transform(selection.cbegin(), selection.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); - std::sort(rows.begin(), rows.end(), std::greater()); - - for (int row : rows) - { - extractedItems.insert(extractedItems.begin(), items[row].groups.cbegin(), items[row].groups.cend()); - items.erase(std::next(items.begin(), row)); - } - - return extractedItems; -} - -void PageItemModel::rotateLeft(const QModelIndexList& list) -{ - if (list.isEmpty()) - { - return; - } - - Modifier modifier(this); - - int rowMin = list.front().row(); - int rowMax = list.front().row(); - - for (const QModelIndex& index : list) - { - if (PageGroupItem* item = getItem(index)) - { - item->rotateLeft(); - } - - rowMin = qMin(rowMin, index.row()); - rowMax = qMax(rowMax, index.row()); - } - - rowMin = qMax(rowMin, 0); - rowMax = qMin(rowMax, rowCount(QModelIndex()) - 1); - - emit dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex())); -} - -void PageItemModel::rotateRight(const QModelIndexList& list) -{ - if (list.isEmpty()) - { - return; - } - - Modifier modifier(this); - - int rowMin = list.front().row(); - int rowMax = list.front().row(); - - for (const QModelIndex& index : list) - { - if (PageGroupItem* item = getItem(index)) - { - item->rotateRight(); - } - - rowMin = qMin(rowMin, index.row()); - rowMax = qMax(rowMax, index.row()); - } - - rowMin = qMax(rowMin, 0); - rowMax = qMin(rowMax, rowCount(QModelIndex()) - 1); - - emit dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex())); -} - -PageItemModel::SelectionInfo PageItemModel::getSelectionInfo(const QModelIndexList& list) const -{ - SelectionInfo info; - - std::set documents; - std::set images; - - for (const QModelIndex& index : list) - { - const PageGroupItem* item = getItem(index); - - if (!item) - { - continue; - } - - for (const PageGroupItem::GroupItem& groupItem : item->groups) - { - switch (groupItem.pageType) - { - case pdfdocpage::PT_DocumentPage: - documents.insert(groupItem.documentIndex); - break; - - case pdfdocpage::PT_Image: - images.insert(groupItem.imageIndex); - break; - - case pdfdocpage::PT_Empty: - ++info.blankPageCount; - break; - - default: - Q_ASSERT(false); - break; - } - } - } - - info.documentCount = int(documents.size()); - info.imageCount = int(images.size()); - info.firstDocumentIndex = !documents.empty() ? *documents.begin() : 0; - - return info; -} - -void PageItemModel::regroupEvenOdd(const QModelIndexList& list) -{ - if (list.empty()) - { - return; - } - - Modifier modifier(this); - - std::vector pageGroupItems = m_pageGroupItems; - std::vector extractedItems = extractItems(pageGroupItems, list); - - auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [](const auto& item) { return item.pageIndex % 2 == 1; }); - std::vector oddItems(extractedItems.begin(), it); - std::vector evenItems(it, extractedItems.end()); - - if (!oddItems.empty()) - { - PageGroupItem item; - item.groups = std::move(oddItems); - updateItemCaptionAndTags(item); - pageGroupItems.emplace_back(std::move(item)); - } - - if (!evenItems.empty()) - { - PageGroupItem item; - item.groups = std::move(evenItems); - updateItemCaptionAndTags(item); - pageGroupItems.emplace_back(std::move(item)); - } - - if (pageGroupItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(pageGroupItems); - endResetModel(); - } -} - -void PageItemModel::regroupPaired(const QModelIndexList& list) -{ - if (list.empty()) - { - return; - } - - Modifier modifier(this); - - std::vector pageGroupItems = m_pageGroupItems; - std::vector extractedItems = extractItems(pageGroupItems, list); - - auto it = extractedItems.begin(); - while (it != extractedItems.cend()) - { - PageGroupItem item; - item.groups = { *it++ }; - - if (it != extractedItems.cend()) - { - item.groups.emplace_back(std::move(*it++)); - } - - updateItemCaptionAndTags(item); - pageGroupItems.emplace_back(std::move(item)); - } - - if (pageGroupItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(pageGroupItems); - endResetModel(); - } -} - -void PageItemModel::regroupBookmarks(const QModelIndexList& list, const std::vector& indices) -{ - if (list.empty()) - { - return; - } - - Modifier modifier(this); - - std::vector pageGroupItems = m_pageGroupItems; - std::vector extractedItems = extractItems(pageGroupItems, list); - std::sort(extractedItems.begin(), extractedItems.end(), [](const auto& l, const auto& r) { return l.pageIndex < r.pageIndex; }); - - PageGroupItem item; - - for (auto it = extractedItems.begin(); it != extractedItems.cend(); ++it) - { - PageGroupItem::GroupItem groupItem = *it; - - if (std::binary_search(indices.cbegin(), indices.cend(), groupItem.pageIndex) && - !item.groups.empty()) - { - updateItemCaptionAndTags(item); - pageGroupItems.emplace_back(std::move(item)); - item = PageGroupItem(); - } - - item.groups.push_back(groupItem); - } - - if (!item.groups.empty()) - { - updateItemCaptionAndTags(item); - pageGroupItems.emplace_back(std::move(item)); - item = PageGroupItem(); - } - - if (pageGroupItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(pageGroupItems); - endResetModel(); - } -} - -void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool reversed) -{ - if (list.empty()) - { - return; - } - - Modifier modifier(this); - - std::vector pageGroupItems = m_pageGroupItems; - std::vector extractedItems = extractItems(pageGroupItems, list); - const int documentIndex = extractedItems.front().documentIndex; - - auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [documentIndex](const auto& item) { return item.documentIndex == documentIndex; }); - std::vector firstDocItems(extractedItems.begin(), it); - std::vector secondDocItems(it, extractedItems.end()); - - if (reversed) - { - std::reverse(secondDocItems.begin(), secondDocItems.end()); - } - - auto itF = firstDocItems.begin(); - auto itS = secondDocItems.begin(); - - PageGroupItem item; - - while (itF != firstDocItems.cend() || itS != secondDocItems.cend()) - { - if (itF != firstDocItems.cend()) - { - item.groups.emplace_back(std::move(*itF++)); - } - - if (itS != secondDocItems.cend()) - { - item.groups.emplace_back(std::move(*itS++)); - } - } - - updateItemCaptionAndTags(item); - pageGroupItems.emplace_back(std::move(item)); - - if (pageGroupItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(pageGroupItems); - endResetModel(); - } -} - -void PageItemModel::undo() -{ - performUndoRedo(m_undoSteps, m_redoSteps); -} - -void PageItemModel::performUndoRedo(std::vector& load, - std::vector& save) -{ - if (load.empty()) - { - return; - } - - save.emplace_back(getCurrentStep()); - UndoRedoStep step = std::move(load.back()); - load.pop_back(); - updateUndoRedoSteps(); - - beginResetModel(); - m_pageGroupItems = std::move(step.pageGroupItems); - m_trashBin = std::move(step.trashBin); - endResetModel(); -} - -void PageItemModel::redo() -{ - performUndoRedo(m_redoSteps, m_undoSteps); -} - -QItemSelection PageItemModel::getSelectionImpl(std::function filter) const -{ - QItemSelection result; - - for (int i = 0; i < rowCount(QModelIndex()); ++i) - { - QModelIndex rowIndex = index(i, 0, QModelIndex()); - if (const PageGroupItem* item = getItem(rowIndex)) - { - bool isApplied = false; - for (const auto& groupItem : item->groups) - { - if (filter(groupItem)) - { - isApplied = true; - break; - } - } - - if (isApplied) - { - result.select(rowIndex, rowIndex); - } - } - } - - return result; -} - -void PageItemModel::updateUndoRedoSteps() -{ - while (m_undoSteps.size() > MAX_UNDO_REDO_STEPS) - { - m_undoSteps.erase(m_undoSteps.begin()); - } - - while (m_redoSteps.size() > MAX_UNDO_REDO_STEPS) - { - m_redoSteps.erase(m_redoSteps.begin()); - } -} - -void PageItemModel::clearUndoRedo() -{ - m_undoSteps.clear(); - m_redoSteps.clear(); -} - -void PageItemModel::createDocumentGroup(int index, const QModelIndex& insertIndex) -{ - const DocumentItem& item = m_documents.at(index); - const pdf::PDFInteger pageCount = item.document.getCatalog()->getPageCount(); - pdf::PDFClosedIntervalSet pageSet; - pageSet.addInterval(1, pageCount); - - PageGroupItem newItem; - newItem.groupName = getGroupNameFromDocument(index); - newItem.pagesCaption = pageSet.toText(true); - - if (pageCount > 1) - { - newItem.tags = QStringList() << QString("#00CC00@+%1").arg(pageCount - 1); - } - - newItem.groups.reserve(pageCount); - for (pdf::PDFInteger i = 1; i <= pageCount; ++i) - { - PageGroupItem::GroupItem groupItem; - groupItem.documentIndex = index; - groupItem.pageIndex = i; - groupItem.rotatedPageDimensionsMM = item.document.getCatalog()->getPage(i - 1)->getRotatedMediaBoxMM().size(); - newItem.groups.push_back(qMove(groupItem)); - } - - int insertRow = rowCount(QModelIndex()); - if (insertIndex.isValid()) - { - insertRow = insertIndex.row() + 1; - } - - beginInsertRows(QModelIndex(), insertRow, insertRow); - m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem)); - endInsertRows(); -} - -QString PageItemModel::getGroupNameFromDocument(int index) const -{ - if (index == -1) - { - return tr("Page Group"); - } - - const DocumentItem& item = m_documents.at(index); - - QString title = item.document.getInfo()->title; - if (!title.isEmpty()) - { - return title; - } - - QFileInfo fileInfo(item.fileName); - return fileInfo.fileName(); -} - -void PageItemModel::updateItemCaptionAndTags(PageGroupItem& item) const -{ - std::set documentIndices = item.getDocumentIndices(); - const size_t pageCount = item.groups.size(); - - if (documentIndices.size() == 1) - { - pdf::PDFClosedIntervalSet pageSet; - for (const auto& groupItem : item.groups) - { - if (groupItem.pageIndex != -1) - { - pageSet.addInterval(groupItem.pageIndex, groupItem.pageIndex); - } - } - - item.groupName = getGroupNameFromDocument(*documentIndices.begin()); - item.pagesCaption = pageSet.toText(true); - } - else - { - item.groupName = tr("Document collection"); - item.pagesCaption = tr("Page Count: %1").arg(item.groups.size()); - } - - bool hasImages = false; - bool hasEmptyPage = false; - - size_t imageCount = 0; - size_t emptyPageCount = 0; - - for (const PageGroupItem::GroupItem& group : item.groups) - { - switch (group.pageType) - { - case pdfdocpage::PT_DocumentPage: - break; - - case pdfdocpage::PT_Image: - hasImages = true; - ++imageCount; - break; - - case pdfdocpage::PT_Empty: - hasEmptyPage = true; - ++emptyPageCount; - break; - } - } - - if (imageCount == pageCount) - { - item.groupName = imageCount == 1 ? tr("Image") : tr("Images"); - } - - if (emptyPageCount == pageCount) - { - item.groupName = emptyPageCount == 1 ? tr("Blank Page") : tr("Blank Pages"); - } - - item.tags.clear(); - if (pageCount > 1) - { - item.tags << QString("#00CC00@+%1").arg(pageCount - 1); - } - if (documentIndices.size() > 1) - { - item.tags << tr("#BBBB00@Collection"); - } - if (hasEmptyPage) - { - item.tags << tr("#D98335@Blank"); - } - if (hasImages) - { - item.tags << tr("#24A5EA@Image"); - } -} - -std::set PageGroupItem::getDocumentIndices() const -{ - std::set result; - - for (const auto& groupItem : groups) - { - result.insert(groupItem.documentIndex); - } - - return result; -} - -void PageGroupItem::rotateLeft() -{ - for (auto& groupItem : groups) - { - groupItem.pageAdditionalRotation = pdf::getPageRotationRotatedLeft(groupItem.pageAdditionalRotation); - } -} - -void PageGroupItem::rotateRight() -{ - for (auto& groupItem : groups) - { - groupItem.pageAdditionalRotation = pdf::getPageRotationRotatedRight(groupItem.pageAdditionalRotation); - } -} - -QStringList PageItemModel::mimeTypes() const -{ - return { getMimeDataType() }; -} - -QMimeData* PageItemModel::mimeData(const QModelIndexList& indexes) const -{ - if (indexes.isEmpty()) - { - return nullptr; - } - - QMimeData* mimeData = new QMimeData; - - QByteArray serializedData; - { - QDataStream stream(&serializedData, QIODevice::WriteOnly); - for (const QModelIndex& index : indexes) - { - stream << index.row(); - } - } - - mimeData->setData(getMimeDataType(), serializedData); - return mimeData; -} - -bool PageItemModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const -{ - Q_UNUSED(row); - Q_UNUSED(column); - Q_UNUSED(parent); - - switch (action) - { - case Qt::CopyAction: - case Qt::MoveAction: - case Qt::IgnoreAction: - break; - - case Qt::LinkAction: - case Qt::TargetMoveAction: - return false; - - default: - Q_ASSERT(false); - break; - } - - return data && data->hasFormat(getMimeDataType()); -} - -bool PageItemModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) -{ - if (action == Qt::IgnoreAction) - { - return true; - } - - if (!data->hasFormat(getMimeDataType())) - { - return false; - } - - if (column > 0) - { - return false; - } - - Modifier modifier(this); - - int insertRow = rowCount(QModelIndex()); - if (row > -1) - { - insertRow = row; - } - else if (parent.isValid()) - { - insertRow = parent.row(); - } - - std::vector rows; - - QByteArray serializedData = data->data(getMimeDataType()); - QDataStream stream(&serializedData, QIODevice::ReadOnly); - while (!stream.atEnd()) - { - int row = -1; - stream >> row; - rows.push_back(row); - } - - qSort(rows); - - // Sanity checks on rows - if (rows.empty()) - { - return false; - } - if (rows.front() < 0 || rows.back() >= rowCount(QModelIndex())) - { - return false; - } - - std::vector newItems = m_pageGroupItems; - std::vector workItems; - - // When moving, update insert row - if (action == Qt::MoveAction) - { - const int originalInsertRow = insertRow; - if (rows.front() < originalInsertRow) - { - ++insertRow; - } - - for (int currentRow : rows) - { - if (currentRow < originalInsertRow) - { - --insertRow; - } - } - } - - workItems.reserve(rows.size()); - for (int currentRow : rows) - { - workItems.push_back(m_pageGroupItems[currentRow]); - } - - // When move, delete old page items - if (action == Qt::MoveAction) - { - for (auto it = rows.rbegin(); it != rows.rend(); ++it) - { - newItems.erase(std::next(newItems.begin(), *it)); - } - } - - // Insert work items at a given position - newItems.insert(std::next(newItems.begin(), insertRow), workItems.begin(), workItems.end()); - - if (newItems != m_pageGroupItems) - { - beginResetModel(); - m_pageGroupItems = std::move(newItems); - endResetModel(); - } - - return true; -} - -Qt::DropActions PageItemModel::supportedDropActions() const -{ - return Qt::CopyAction | Qt::MoveAction; -} - -Qt::DropActions PageItemModel::supportedDragActions() const -{ - return Qt::CopyAction | Qt::MoveAction; -} - -Qt::ItemFlags PageItemModel::flags(const QModelIndex& index) const -{ - Qt::ItemFlags flags = BaseClass::flags(index); - - if (index.isValid()) - { - flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - } - - return flags; -} - -std::vector> PageItemModel::getAssembledPages(AssembleMode mode) const -{ - std::vector> result; - - auto createAssembledPage = [this](const PageGroupItem::GroupItem& item) - { - pdf::PDFDocumentManipulator::AssembledPage assembledPage; - - assembledPage.documentIndex = item.documentIndex; - assembledPage.imageIndex = item.imageIndex; - assembledPage.pageIndex = item.pageIndex; - - if (assembledPage.pageIndex > 0) - { - --assembledPage.pageIndex; - } - - pdf::PageRotation originalPageRotation = pdf::PageRotation::None; - if (item.pageType == PT_DocumentPage) - { - auto it = m_documents.find(item.documentIndex); - if (it != m_documents.cend()) - { - const pdf::PDFPage* page = it->second.document.getCatalog()->getPage(item.pageIndex - 1); - originalPageRotation = page->getPageRotation(); - } - } - - assembledPage.pageRotation = pdf::getPageRotationCombined(originalPageRotation, item.pageAdditionalRotation); - assembledPage.pageSize = item.rotatedPageDimensionsMM; - - return assembledPage; - }; - - switch (mode) - { - case AssembleMode::Unite: - { - std::vector unitedDocument; - - for (const auto& pageGroupItem : m_pageGroupItems) - { - for (const auto& groupItem : pageGroupItem.groups) - { - unitedDocument.emplace_back(createAssembledPage(groupItem)); - } - } - - result.emplace_back(std::move(unitedDocument)); - break; - } - - case AssembleMode::Separate: - { - for (const auto& pageGroupItem : m_pageGroupItems) - { - for (const auto& groupItem : pageGroupItem.groups) - { - result.emplace_back().emplace_back(createAssembledPage(groupItem)); - } - } - break; - } - - case AssembleMode::SeparateGrouped: - { - for (const auto& pageGroupItem : m_pageGroupItems) - { - std::vector groupDocument; - for (const auto& groupItem : pageGroupItem.groups) - { - groupDocument.emplace_back(createAssembledPage(groupItem)); - } - result.emplace_back(std::move(groupDocument)); - } - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - // Remove empty documents - result.erase(std::remove_if(result.begin(), result.end(), [](const auto& pages) { return pages.empty(); }), result.end()); - - return result; -} - -void PageItemModel::clear() -{ - beginResetModel(); - m_pageGroupItems.clear(); - m_documents.clear(); - m_trashBin.clear(); - clearUndoRedo(); - endResetModel(); -} - -PageItemModel::Modifier::Modifier(PageItemModel* model) : - m_model(model) -{ - Q_ASSERT(model); - - m_stateBeforeModification = m_model->getCurrentStep(); -} - -PageItemModel::Modifier::~Modifier() -{ - UndoRedoStep stateAfterModification = m_model->getCurrentStep(); - - if (m_stateBeforeModification != stateAfterModification) - { - m_model->m_undoSteps.emplace_back(std::move(m_stateBeforeModification)); - m_model->m_redoSteps.clear(); - m_model->updateUndoRedoSteps(); - } -} - -} // namespace pdfdocpage +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pageitemmodel.h" + +#include +#include + +#include + +namespace pdfdocpage +{ + +PageItemModel::PageItemModel(QObject* parent) : + QAbstractItemModel(parent) +{ +} + +QVariant PageItemModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section); + Q_UNUSED(orientation); + Q_UNUSED(role); + + return QVariant(); +} + +QModelIndex PageItemModel::index(int row, int column, const QModelIndex& parent) const +{ + if (hasIndex(row, column, parent)) + { + return createIndex(row, column, nullptr); + } + + return QModelIndex(); +} + +QModelIndex PageItemModel::parent(const QModelIndex& index) const +{ + Q_UNUSED(index); + + return QModelIndex(); +} + +int PageItemModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + { + return 0; + } + + return int(m_pageGroupItems.size()); +} + +int PageItemModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + { + return 0; + } + + return 1; +} + +QVariant PageItemModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + { + return QVariant(); + } + + switch (role) + { + case Qt::DisplayRole: + return m_pageGroupItems.at(index.row()).groupName; + + default: + break; + } + + return QVariant(); +} + +int PageItemModel::insertDocument(QString fileName, pdf::PDFDocument document, const QModelIndex& index) +{ + Modifier modifier(this); + auto it = std::find_if(m_documents.cbegin(), m_documents.cend(), [&](const auto& item) { return item.second.fileName == fileName; }); + if (it != m_documents.cend()) + { + return -1; + } + + int newIndex = 1; + + if (!m_documents.empty()) + { + newIndex = (m_documents.rbegin()->first) + 1; + } + + m_documents[newIndex] = { qMove(fileName), qMove(document) }; + createDocumentGroup(newIndex, index); + return newIndex; +} + +int PageItemModel::insertImage(QString fileName, const QModelIndex& index) +{ + Modifier modifier(this); + QFile file(fileName); + + if (file.open(QFile::ReadOnly)) + { + ImageItem item; + item.imageData = file.readAll(); + + QImageReader reader(fileName); + item.image = reader.read(); + + file.close(); + + if (!item.image.isNull()) + { + int newIndex = 1; + + if (!m_images.empty()) + { + newIndex = (m_images.rbegin()->first) + 1; + } + + m_images[newIndex] = qMove(item); + + // Insert image item + PageGroupItem newItem; + + newItem.groups.reserve(1); + + PageGroupItem::GroupItem groupItem; + groupItem.imageIndex = newIndex; + groupItem.rotatedPageDimensionsMM = m_images[newIndex].image.size() * 0.1; + groupItem.pageType = PT_Image; + newItem.groups.push_back(qMove(groupItem)); + + updateItemCaptionAndTags(newItem); + int insertRow = index.isValid() ? index.row() + 1 : int(m_pageGroupItems.size()); + + beginInsertRows(QModelIndex(), insertRow, insertRow); + m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem)); + endInsertRows(); + + return newIndex; + } + } + + return -1; +} + +const PageGroupItem* PageItemModel::getItem(const QModelIndex& index) const +{ + if (index.isValid()) + { + if (index.row() < m_pageGroupItems.size()) + { + return &m_pageGroupItems.at(index.row()); + } + } + + return nullptr; +} + +PageGroupItem* PageItemModel::getItem(const QModelIndex& index) +{ + if (index.isValid()) + { + if (index.row() < m_pageGroupItems.size()) + { + return &m_pageGroupItems.at(index.row()); + } + } + + return nullptr; +} + +bool PageItemModel::isGrouped(const QModelIndexList& indices) const +{ + for (const QModelIndex& index : indices) + { + if (const PageGroupItem* item = getItem(index)) + { + if (item->isGrouped()) + { + return true; + } + } + } + + return false; +} + +QItemSelection PageItemModel::getSelectionEven() const +{ + return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.pageIndex % 2 == 1; }); +} + +QItemSelection PageItemModel::getSelectionOdd() const +{ + return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.pageIndex % 2 == 0; }); +} + +QItemSelection PageItemModel::getSelectionPortrait() const +{ + return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.rotatedPageDimensionsMM.width() <= groupItem.rotatedPageDimensionsMM.height(); }); +} + +QItemSelection PageItemModel::getSelectionLandscape() const +{ + return getSelectionImpl([](const PageGroupItem::GroupItem& groupItem) { return groupItem.rotatedPageDimensionsMM.width() >= groupItem.rotatedPageDimensionsMM.height(); }); +} + +void PageItemModel::group(const QModelIndexList& list) +{ + if (list.isEmpty()) + { + return; + } + + Modifier modifier(this); + + std::vector groupedIndices; + groupedIndices.reserve(list.size()); + std::transform(list.cbegin(), list.cend(), std::back_inserter(groupedIndices), [](const auto& index) { return index.row(); }); + std::sort(groupedIndices.begin(), groupedIndices.end()); + + std::vector newPageGroupItems; + std::vector newGroups; + newPageGroupItems.reserve(m_pageGroupItems.size()); + for (size_t i = 0; i < m_pageGroupItems.size(); ++i) + { + const PageGroupItem& item = m_pageGroupItems[i]; + if (std::binary_search(groupedIndices.cbegin(), groupedIndices.cend(), i)) + { + newGroups.insert(newGroups.end(), item.groups.begin(), item.groups.end()); + } + else + { + newPageGroupItems.push_back(item); + } + } + + PageGroupItem newItem; + newItem.groups = qMove(newGroups); + updateItemCaptionAndTags(newItem); + newPageGroupItems.insert(std::next(newPageGroupItems.begin(), groupedIndices.front()), qMove(newItem)); + + if (newPageGroupItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(newPageGroupItems); + endResetModel(); + } +} + +void PageItemModel::ungroup(const QModelIndexList& list) +{ + if (list.isEmpty()) + { + return; + } + + Modifier modifier(this); + + std::vector ungroupedIndices; + ungroupedIndices.reserve(list.size()); + std::transform(list.cbegin(), list.cend(), std::back_inserter(ungroupedIndices), [](const auto& index) { return index.row(); }); + std::sort(ungroupedIndices.begin(), ungroupedIndices.end()); + + std::vector newPageGroupItems; + newPageGroupItems.reserve(m_pageGroupItems.size()); + for (size_t i = 0; i < m_pageGroupItems.size(); ++i) + { + const PageGroupItem& item = m_pageGroupItems[i]; + if (item.isGrouped() && std::binary_search(ungroupedIndices.cbegin(), ungroupedIndices.cend(), i)) + { + for (const PageGroupItem::GroupItem& groupItem : item.groups) + { + PageGroupItem newItem; + newItem.groups = { groupItem }; + updateItemCaptionAndTags(newItem); + newPageGroupItems.push_back(qMove(newItem)); + } + } + else + { + newPageGroupItems.push_back(item); + } + } + + if (newPageGroupItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(newPageGroupItems); + endResetModel(); + } +} + +QModelIndexList PageItemModel::restoreRemovedItems() +{ + QModelIndexList result; + + if (m_trashBin.empty()) + { + // Jakub Melka: nothing to do + return result; + } + + Modifier modifier(this); + + const int trashBinSize = int(m_trashBin.size()); + const int rowCount = this->rowCount(QModelIndex()); + beginInsertRows(QModelIndex(), rowCount, rowCount + trashBinSize - 1); + m_pageGroupItems.insert(m_pageGroupItems.end(), std::make_move_iterator(m_trashBin.begin()), std::make_move_iterator(m_trashBin.end())); + m_trashBin.clear(); + endInsertRows(); + + result.reserve(trashBinSize); + for (int i = rowCount; i < rowCount + trashBinSize; ++i) + { + result << index(i, 0, QModelIndex()); + } + + return result; +} + +QModelIndexList PageItemModel::cloneSelection(const QModelIndexList& list) +{ + QModelIndexList result; + + if (list.empty()) + { + // Jakub Melka: nothing to do + return result; + } + + Modifier modifier(this); + + std::vector rows; + rows.reserve(list.size()); + std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); + + std::vector clonedGroups; + clonedGroups.reserve(rows.size()); + + for (int row : rows) + { + clonedGroups.push_back(m_pageGroupItems[row]); + } + + const int insertRow = rows.back() + 1; + const int lastRow = insertRow + int(rows.size()); + + beginResetModel(); + m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), clonedGroups.begin(), clonedGroups.end()); + endResetModel(); + + result.reserve(int(rows.size())); + for (int i = insertRow; i < lastRow; ++i) + { + result << index(i, 0, QModelIndex()); + } + + return result; +} + +void PageItemModel::removeSelection(const QModelIndexList& list) +{ + if (list.empty()) + { + // Jakub Melka: nothing to do + return; + } + + Modifier modifier(this); + + std::vector rows; + rows.reserve(list.size()); + std::transform(list.cbegin(), list.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); + std::sort(rows.begin(), rows.end(), std::greater()); + + beginResetModel(); + for (int row : rows) + { + m_trashBin.emplace_back(qMove(m_pageGroupItems[row])); + m_pageGroupItems.erase(std::next(m_pageGroupItems.begin(), row)); + } + endResetModel(); +} + +void PageItemModel::insertEmptyPage(const QModelIndexList& list) +{ + Modifier modifier(this); + + if (list.isEmpty()) + { + insertEmptyPage(QModelIndex()); + } + else + { + QModelIndexList listCopy = list; + std::sort(listCopy.begin(), listCopy.end()); + std::reverse(listCopy.begin(), listCopy.end()); + + for (const QModelIndex& index: listCopy) + { + insertEmptyPage(index); + } + } +} + +void PageItemModel::insertEmptyPage(const QModelIndex& index) +{ + int insertRow = index.isValid()? index.row() + 1 : int(m_pageGroupItems.size()); + + const int templateRow = index.isValid() ? index.row() : int(m_pageGroupItems.size()) - 1; + const bool isTemplateRowValid = templateRow > -1; + + PageGroupItem::GroupItem groupItem; + groupItem.pageAdditionalRotation = isTemplateRowValid ? m_pageGroupItems[templateRow].groups.back().pageAdditionalRotation : pdf::PageRotation::None; + groupItem.pageType = PT_Empty; + groupItem.rotatedPageDimensionsMM = isTemplateRowValid ? m_pageGroupItems[templateRow].groups.back().rotatedPageDimensionsMM : QSizeF(210, 297); + + PageGroupItem blankPageItem; + blankPageItem.groups.push_back(groupItem); + updateItemCaptionAndTags(blankPageItem); + + beginInsertRows(QModelIndex(), insertRow, insertRow); + m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), std::move(blankPageItem)); + endInsertRows(); +} + +std::vector PageItemModel::extractItems(std::vector& items, + const QModelIndexList& selection) const +{ + std::vector extractedItems; + + std::vector rows; + rows.reserve(selection.size()); + std::transform(selection.cbegin(), selection.cend(), std::back_inserter(rows), [](const auto& index) { return index.row(); }); + std::sort(rows.begin(), rows.end(), std::greater()); + + for (int row : rows) + { + extractedItems.insert(extractedItems.begin(), items[row].groups.cbegin(), items[row].groups.cend()); + items.erase(std::next(items.begin(), row)); + } + + return extractedItems; +} + +void PageItemModel::rotateLeft(const QModelIndexList& list) +{ + if (list.isEmpty()) + { + return; + } + + Modifier modifier(this); + + int rowMin = list.front().row(); + int rowMax = list.front().row(); + + for (const QModelIndex& index : list) + { + if (PageGroupItem* item = getItem(index)) + { + item->rotateLeft(); + } + + rowMin = qMin(rowMin, index.row()); + rowMax = qMax(rowMax, index.row()); + } + + rowMin = qMax(rowMin, 0); + rowMax = qMin(rowMax, rowCount(QModelIndex()) - 1); + + emit dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex())); +} + +void PageItemModel::rotateRight(const QModelIndexList& list) +{ + if (list.isEmpty()) + { + return; + } + + Modifier modifier(this); + + int rowMin = list.front().row(); + int rowMax = list.front().row(); + + for (const QModelIndex& index : list) + { + if (PageGroupItem* item = getItem(index)) + { + item->rotateRight(); + } + + rowMin = qMin(rowMin, index.row()); + rowMax = qMax(rowMax, index.row()); + } + + rowMin = qMax(rowMin, 0); + rowMax = qMin(rowMax, rowCount(QModelIndex()) - 1); + + emit dataChanged(index(rowMin, 0, QModelIndex()), index(rowMax, 0, QModelIndex())); +} + +PageItemModel::SelectionInfo PageItemModel::getSelectionInfo(const QModelIndexList& list) const +{ + SelectionInfo info; + + std::set documents; + std::set images; + + for (const QModelIndex& index : list) + { + const PageGroupItem* item = getItem(index); + + if (!item) + { + continue; + } + + for (const PageGroupItem::GroupItem& groupItem : item->groups) + { + switch (groupItem.pageType) + { + case pdfdocpage::PT_DocumentPage: + documents.insert(groupItem.documentIndex); + break; + + case pdfdocpage::PT_Image: + images.insert(groupItem.imageIndex); + break; + + case pdfdocpage::PT_Empty: + ++info.blankPageCount; + break; + + default: + Q_ASSERT(false); + break; + } + } + } + + info.documentCount = int(documents.size()); + info.imageCount = int(images.size()); + info.firstDocumentIndex = !documents.empty() ? *documents.begin() : 0; + + return info; +} + +void PageItemModel::regroupEvenOdd(const QModelIndexList& list) +{ + if (list.empty()) + { + return; + } + + Modifier modifier(this); + + std::vector pageGroupItems = m_pageGroupItems; + std::vector extractedItems = extractItems(pageGroupItems, list); + + auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [](const auto& item) { return item.pageIndex % 2 == 1; }); + std::vector oddItems(extractedItems.begin(), it); + std::vector evenItems(it, extractedItems.end()); + + if (!oddItems.empty()) + { + PageGroupItem item; + item.groups = std::move(oddItems); + updateItemCaptionAndTags(item); + pageGroupItems.emplace_back(std::move(item)); + } + + if (!evenItems.empty()) + { + PageGroupItem item; + item.groups = std::move(evenItems); + updateItemCaptionAndTags(item); + pageGroupItems.emplace_back(std::move(item)); + } + + if (pageGroupItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(pageGroupItems); + endResetModel(); + } +} + +void PageItemModel::regroupPaired(const QModelIndexList& list) +{ + if (list.empty()) + { + return; + } + + Modifier modifier(this); + + std::vector pageGroupItems = m_pageGroupItems; + std::vector extractedItems = extractItems(pageGroupItems, list); + + auto it = extractedItems.begin(); + while (it != extractedItems.cend()) + { + PageGroupItem item; + item.groups = { *it++ }; + + if (it != extractedItems.cend()) + { + item.groups.emplace_back(std::move(*it++)); + } + + updateItemCaptionAndTags(item); + pageGroupItems.emplace_back(std::move(item)); + } + + if (pageGroupItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(pageGroupItems); + endResetModel(); + } +} + +void PageItemModel::regroupBookmarks(const QModelIndexList& list, const std::vector& indices) +{ + if (list.empty()) + { + return; + } + + Modifier modifier(this); + + std::vector pageGroupItems = m_pageGroupItems; + std::vector extractedItems = extractItems(pageGroupItems, list); + std::sort(extractedItems.begin(), extractedItems.end(), [](const auto& l, const auto& r) { return l.pageIndex < r.pageIndex; }); + + PageGroupItem item; + + for (auto it = extractedItems.begin(); it != extractedItems.cend(); ++it) + { + PageGroupItem::GroupItem groupItem = *it; + + if (std::binary_search(indices.cbegin(), indices.cend(), groupItem.pageIndex) && + !item.groups.empty()) + { + updateItemCaptionAndTags(item); + pageGroupItems.emplace_back(std::move(item)); + item = PageGroupItem(); + } + + item.groups.push_back(groupItem); + } + + if (!item.groups.empty()) + { + updateItemCaptionAndTags(item); + pageGroupItems.emplace_back(std::move(item)); + item = PageGroupItem(); + } + + if (pageGroupItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(pageGroupItems); + endResetModel(); + } +} + +void PageItemModel::regroupAlternatingPages(const QModelIndexList& list, bool reversed) +{ + if (list.empty()) + { + return; + } + + Modifier modifier(this); + + std::vector pageGroupItems = m_pageGroupItems; + std::vector extractedItems = extractItems(pageGroupItems, list); + const int documentIndex = extractedItems.front().documentIndex; + + auto it = std::stable_partition(extractedItems.begin(), extractedItems.end(), [documentIndex](const auto& item) { return item.documentIndex == documentIndex; }); + std::vector firstDocItems(extractedItems.begin(), it); + std::vector secondDocItems(it, extractedItems.end()); + + if (reversed) + { + std::reverse(secondDocItems.begin(), secondDocItems.end()); + } + + auto itF = firstDocItems.begin(); + auto itS = secondDocItems.begin(); + + PageGroupItem item; + + while (itF != firstDocItems.cend() || itS != secondDocItems.cend()) + { + if (itF != firstDocItems.cend()) + { + item.groups.emplace_back(std::move(*itF++)); + } + + if (itS != secondDocItems.cend()) + { + item.groups.emplace_back(std::move(*itS++)); + } + } + + updateItemCaptionAndTags(item); + pageGroupItems.emplace_back(std::move(item)); + + if (pageGroupItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(pageGroupItems); + endResetModel(); + } +} + +void PageItemModel::undo() +{ + performUndoRedo(m_undoSteps, m_redoSteps); +} + +void PageItemModel::performUndoRedo(std::vector& load, + std::vector& save) +{ + if (load.empty()) + { + return; + } + + save.emplace_back(getCurrentStep()); + UndoRedoStep step = std::move(load.back()); + load.pop_back(); + updateUndoRedoSteps(); + + beginResetModel(); + m_pageGroupItems = std::move(step.pageGroupItems); + m_trashBin = std::move(step.trashBin); + endResetModel(); +} + +void PageItemModel::redo() +{ + performUndoRedo(m_redoSteps, m_undoSteps); +} + +QItemSelection PageItemModel::getSelectionImpl(std::function filter) const +{ + QItemSelection result; + + for (int i = 0; i < rowCount(QModelIndex()); ++i) + { + QModelIndex rowIndex = index(i, 0, QModelIndex()); + if (const PageGroupItem* item = getItem(rowIndex)) + { + bool isApplied = false; + for (const auto& groupItem : item->groups) + { + if (filter(groupItem)) + { + isApplied = true; + break; + } + } + + if (isApplied) + { + result.select(rowIndex, rowIndex); + } + } + } + + return result; +} + +void PageItemModel::updateUndoRedoSteps() +{ + while (m_undoSteps.size() > MAX_UNDO_REDO_STEPS) + { + m_undoSteps.erase(m_undoSteps.begin()); + } + + while (m_redoSteps.size() > MAX_UNDO_REDO_STEPS) + { + m_redoSteps.erase(m_redoSteps.begin()); + } +} + +void PageItemModel::clearUndoRedo() +{ + m_undoSteps.clear(); + m_redoSteps.clear(); +} + +void PageItemModel::createDocumentGroup(int index, const QModelIndex& insertIndex) +{ + const DocumentItem& item = m_documents.at(index); + const pdf::PDFInteger pageCount = item.document.getCatalog()->getPageCount(); + pdf::PDFClosedIntervalSet pageSet; + pageSet.addInterval(1, pageCount); + + PageGroupItem newItem; + newItem.groupName = getGroupNameFromDocument(index); + newItem.pagesCaption = pageSet.toText(true); + + if (pageCount > 1) + { + newItem.tags = QStringList() << QString("#00CC00@+%1").arg(pageCount - 1); + } + + newItem.groups.reserve(pageCount); + for (pdf::PDFInteger i = 1; i <= pageCount; ++i) + { + PageGroupItem::GroupItem groupItem; + groupItem.documentIndex = index; + groupItem.pageIndex = i; + groupItem.rotatedPageDimensionsMM = item.document.getCatalog()->getPage(i - 1)->getRotatedMediaBoxMM().size(); + newItem.groups.push_back(qMove(groupItem)); + } + + int insertRow = rowCount(QModelIndex()); + if (insertIndex.isValid()) + { + insertRow = insertIndex.row() + 1; + } + + beginInsertRows(QModelIndex(), insertRow, insertRow); + m_pageGroupItems.insert(std::next(m_pageGroupItems.begin(), insertRow), qMove(newItem)); + endInsertRows(); +} + +QString PageItemModel::getGroupNameFromDocument(int index) const +{ + if (index == -1) + { + return tr("Page Group"); + } + + const DocumentItem& item = m_documents.at(index); + + QString title = item.document.getInfo()->title; + if (!title.isEmpty()) + { + return title; + } + + QFileInfo fileInfo(item.fileName); + return fileInfo.fileName(); +} + +void PageItemModel::updateItemCaptionAndTags(PageGroupItem& item) const +{ + std::set documentIndices = item.getDocumentIndices(); + const size_t pageCount = item.groups.size(); + + if (documentIndices.size() == 1) + { + pdf::PDFClosedIntervalSet pageSet; + for (const auto& groupItem : item.groups) + { + if (groupItem.pageIndex != -1) + { + pageSet.addInterval(groupItem.pageIndex, groupItem.pageIndex); + } + } + + item.groupName = getGroupNameFromDocument(*documentIndices.begin()); + item.pagesCaption = pageSet.toText(true); + } + else + { + item.groupName = tr("Document collection"); + item.pagesCaption = tr("Page Count: %1").arg(item.groups.size()); + } + + bool hasImages = false; + bool hasEmptyPage = false; + + size_t imageCount = 0; + size_t emptyPageCount = 0; + + for (const PageGroupItem::GroupItem& group : item.groups) + { + switch (group.pageType) + { + case pdfdocpage::PT_DocumentPage: + break; + + case pdfdocpage::PT_Image: + hasImages = true; + ++imageCount; + break; + + case pdfdocpage::PT_Empty: + hasEmptyPage = true; + ++emptyPageCount; + break; + } + } + + if (imageCount == pageCount) + { + item.groupName = imageCount == 1 ? tr("Image") : tr("Images"); + } + + if (emptyPageCount == pageCount) + { + item.groupName = emptyPageCount == 1 ? tr("Blank Page") : tr("Blank Pages"); + } + + item.tags.clear(); + if (pageCount > 1) + { + item.tags << QString("#00CC00@+%1").arg(pageCount - 1); + } + if (documentIndices.size() > 1) + { + item.tags << tr("#BBBB00@Collection"); + } + if (hasEmptyPage) + { + item.tags << tr("#D98335@Blank"); + } + if (hasImages) + { + item.tags << tr("#24A5EA@Image"); + } +} + +std::set PageGroupItem::getDocumentIndices() const +{ + std::set result; + + for (const auto& groupItem : groups) + { + result.insert(groupItem.documentIndex); + } + + return result; +} + +void PageGroupItem::rotateLeft() +{ + for (auto& groupItem : groups) + { + groupItem.pageAdditionalRotation = pdf::getPageRotationRotatedLeft(groupItem.pageAdditionalRotation); + } +} + +void PageGroupItem::rotateRight() +{ + for (auto& groupItem : groups) + { + groupItem.pageAdditionalRotation = pdf::getPageRotationRotatedRight(groupItem.pageAdditionalRotation); + } +} + +QStringList PageItemModel::mimeTypes() const +{ + return { getMimeDataType() }; +} + +QMimeData* PageItemModel::mimeData(const QModelIndexList& indexes) const +{ + if (indexes.isEmpty()) + { + return nullptr; + } + + QMimeData* mimeData = new QMimeData; + + QByteArray serializedData; + { + QDataStream stream(&serializedData, QIODevice::WriteOnly); + for (const QModelIndex& index : indexes) + { + stream << index.row(); + } + } + + mimeData->setData(getMimeDataType(), serializedData); + return mimeData; +} + +bool PageItemModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + Q_UNUSED(row); + Q_UNUSED(column); + Q_UNUSED(parent); + + switch (action) + { + case Qt::CopyAction: + case Qt::MoveAction: + case Qt::IgnoreAction: + break; + + case Qt::LinkAction: + case Qt::TargetMoveAction: + return false; + + default: + Q_ASSERT(false); + break; + } + + return data && data->hasFormat(getMimeDataType()); +} + +bool PageItemModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + if (action == Qt::IgnoreAction) + { + return true; + } + + if (!data->hasFormat(getMimeDataType())) + { + return false; + } + + if (column > 0) + { + return false; + } + + Modifier modifier(this); + + int insertRow = rowCount(QModelIndex()); + if (row > -1) + { + insertRow = row; + } + else if (parent.isValid()) + { + insertRow = parent.row(); + } + + std::vector rows; + + QByteArray serializedData = data->data(getMimeDataType()); + QDataStream stream(&serializedData, QIODevice::ReadOnly); + while (!stream.atEnd()) + { + int row = -1; + stream >> row; + rows.push_back(row); + } + + qSort(rows); + + // Sanity checks on rows + if (rows.empty()) + { + return false; + } + if (rows.front() < 0 || rows.back() >= rowCount(QModelIndex())) + { + return false; + } + + std::vector newItems = m_pageGroupItems; + std::vector workItems; + + // When moving, update insert row + if (action == Qt::MoveAction) + { + const int originalInsertRow = insertRow; + if (rows.front() < originalInsertRow) + { + ++insertRow; + } + + for (int currentRow : rows) + { + if (currentRow < originalInsertRow) + { + --insertRow; + } + } + } + + workItems.reserve(rows.size()); + for (int currentRow : rows) + { + workItems.push_back(m_pageGroupItems[currentRow]); + } + + // When move, delete old page items + if (action == Qt::MoveAction) + { + for (auto it = rows.rbegin(); it != rows.rend(); ++it) + { + newItems.erase(std::next(newItems.begin(), *it)); + } + } + + // Insert work items at a given position + newItems.insert(std::next(newItems.begin(), insertRow), workItems.begin(), workItems.end()); + + if (newItems != m_pageGroupItems) + { + beginResetModel(); + m_pageGroupItems = std::move(newItems); + endResetModel(); + } + + return true; +} + +Qt::DropActions PageItemModel::supportedDropActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::DropActions PageItemModel::supportedDragActions() const +{ + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::ItemFlags PageItemModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = BaseClass::flags(index); + + if (index.isValid()) + { + flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; + } + + return flags; +} + +std::vector> PageItemModel::getAssembledPages(AssembleMode mode) const +{ + std::vector> result; + + auto createAssembledPage = [this](const PageGroupItem::GroupItem& item) + { + pdf::PDFDocumentManipulator::AssembledPage assembledPage; + + assembledPage.documentIndex = item.documentIndex; + assembledPage.imageIndex = item.imageIndex; + assembledPage.pageIndex = item.pageIndex; + + if (assembledPage.pageIndex > 0) + { + --assembledPage.pageIndex; + } + + pdf::PageRotation originalPageRotation = pdf::PageRotation::None; + if (item.pageType == PT_DocumentPage) + { + auto it = m_documents.find(item.documentIndex); + if (it != m_documents.cend()) + { + const pdf::PDFPage* page = it->second.document.getCatalog()->getPage(item.pageIndex - 1); + originalPageRotation = page->getPageRotation(); + } + } + + assembledPage.pageRotation = pdf::getPageRotationCombined(originalPageRotation, item.pageAdditionalRotation); + assembledPage.pageSize = item.rotatedPageDimensionsMM; + + return assembledPage; + }; + + switch (mode) + { + case AssembleMode::Unite: + { + std::vector unitedDocument; + + for (const auto& pageGroupItem : m_pageGroupItems) + { + for (const auto& groupItem : pageGroupItem.groups) + { + unitedDocument.emplace_back(createAssembledPage(groupItem)); + } + } + + result.emplace_back(std::move(unitedDocument)); + break; + } + + case AssembleMode::Separate: + { + for (const auto& pageGroupItem : m_pageGroupItems) + { + for (const auto& groupItem : pageGroupItem.groups) + { + result.emplace_back().emplace_back(createAssembledPage(groupItem)); + } + } + break; + } + + case AssembleMode::SeparateGrouped: + { + for (const auto& pageGroupItem : m_pageGroupItems) + { + std::vector groupDocument; + for (const auto& groupItem : pageGroupItem.groups) + { + groupDocument.emplace_back(createAssembledPage(groupItem)); + } + result.emplace_back(std::move(groupDocument)); + } + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + // Remove empty documents + result.erase(std::remove_if(result.begin(), result.end(), [](const auto& pages) { return pages.empty(); }), result.end()); + + return result; +} + +void PageItemModel::clear() +{ + beginResetModel(); + m_pageGroupItems.clear(); + m_documents.clear(); + m_trashBin.clear(); + clearUndoRedo(); + endResetModel(); +} + +PageItemModel::Modifier::Modifier(PageItemModel* model) : + m_model(model) +{ + Q_ASSERT(model); + + m_stateBeforeModification = m_model->getCurrentStep(); +} + +PageItemModel::Modifier::~Modifier() +{ + UndoRedoStep stateAfterModification = m_model->getCurrentStep(); + + if (m_stateBeforeModification != stateAfterModification) + { + m_model->m_undoSteps.emplace_back(std::move(m_stateBeforeModification)); + m_model->m_redoSteps.clear(); + m_model->updateUndoRedoSteps(); + } +} + +} // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/pageitemmodel.h b/Pdf4QtDocPageOrganizer/pageitemmodel.h index 8c3f9b2..e2d1f77 100644 --- a/Pdf4QtDocPageOrganizer/pageitemmodel.h +++ b/Pdf4QtDocPageOrganizer/pageitemmodel.h @@ -1,244 +1,244 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCPAGEORGANIZER_PAGEITEMMODEL_H -#define PDFDOCPAGEORGANIZER_PAGEITEMMODEL_H - -#include "pdfdocument.h" -#include "pdfutils.h" -#include "pdfdocumentmanipulator.h" - -#include -#include - -namespace pdfdocpage -{ - -enum PageType -{ - PT_DocumentPage, - PT_Image, - PT_Empty, - PT_Last -}; - -struct PageGroupItem -{ - QString groupName; - QString pagesCaption; - QStringList tags; - - struct GroupItem - { - auto operator<=>(const GroupItem&) const = default; - - int documentIndex = -1; - pdf::PDFInteger pageIndex = -1; - pdf::PDFInteger imageIndex = -1; - QSizeF rotatedPageDimensionsMM; ///< Rotated page dimensions, but without additional rotation - pdf::PageRotation pageAdditionalRotation = pdf::PageRotation::None; ///< Additional rotation applied to the page - PageType pageType = PT_DocumentPage; - }; - - std::vector groups; - - auto operator<=>(const PageGroupItem&) const = default; - - bool isGrouped() const { return groups.size() > 1; } - - std::set getDocumentIndices() const; - - void rotateLeft(); - void rotateRight(); -}; - -struct DocumentItem -{ - QString fileName; - pdf::PDFDocument document; -}; - -struct ImageItem -{ - QImage image; - QByteArray imageData; -}; - -class PageItemModel : public QAbstractItemModel -{ - Q_OBJECT - -private: - using BaseClass = QAbstractItemModel; - -public: - explicit PageItemModel(QObject* parent); - - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; - virtual QModelIndex parent(const QModelIndex& index) const override; - virtual int rowCount(const QModelIndex& parent) const override; - virtual int columnCount(const QModelIndex& parent) const override; - virtual QVariant data(const QModelIndex& index, int role) const override; - virtual QStringList mimeTypes() const override; - virtual QMimeData* mimeData(const QModelIndexList& indexes) const override; - virtual bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; - virtual Qt::DropActions supportedDropActions() const override; - virtual Qt::DropActions supportedDragActions() const override; - virtual Qt::ItemFlags flags(const QModelIndex& index) const override; - - enum class AssembleMode - { - Unite, - Separate, - SeparateGrouped - }; - - std::vector> getAssembledPages(AssembleMode mode) const; - - /// Clear all data and undo/redo - void clear(); - - /// Adds document to the model, inserts one single page group containing - /// the whole document. Returns index of a newly added document. If document - /// cannot be added (for example, it already exists), -1 is returned. - /// \param fileName File name - /// \param document Document - /// \param index Index, where image is inserted - /// \returns Identifier of the document (internal index) - int insertDocument(QString fileName, pdf::PDFDocument document, const QModelIndex& index); - - /// Adds image to the model, inserts one single page containing - /// the image. Returns index of a newly added image. If image - /// cannot be read from the file, -1 is returned. - /// \param fileName Image file - /// \param index Index, where image is inserted - /// \returns Identifier of the image (internal index) - int insertImage(QString fileName, const QModelIndex& index); - - /// Returns item at a given index. If item doesn't exist, - /// then nullptr is returned. - /// \param index Index - const PageGroupItem* getItem(const QModelIndex& index) const; - - /// Returns item at a given index. If item doesn't exist, - /// then nullptr is returned. - /// \param index Index - PageGroupItem* getItem(const QModelIndex& index); - - /// Returns true, if grouped item exists in the indices - bool isGrouped(const QModelIndexList& indices) const; - - /// Returns true, if trash bin is empty - bool isTrashBinEmpty() const { return m_trashBin.empty(); } - - QItemSelection getSelectionEven() const; - QItemSelection getSelectionOdd() const; - QItemSelection getSelectionPortrait() const; - QItemSelection getSelectionLandscape() const; - - void group(const QModelIndexList& list); - void ungroup(const QModelIndexList& list); - - QModelIndexList restoreRemovedItems(); - QModelIndexList cloneSelection(const QModelIndexList& list); - void removeSelection(const QModelIndexList& list); - void insertEmptyPage(const QModelIndexList& list); - - void rotateLeft(const QModelIndexList& list); - void rotateRight(const QModelIndexList& list); - - static QString getMimeDataType() { return QLatin1String("application/pagemodel.PDF4QtDocPageOrganizer"); } - - const std::map& getDocuments() const { return m_documents; } - const std::map& getImages() const { return m_images; } - - struct SelectionInfo - { - int documentCount = 0; - int imageCount = 0; - int blankPageCount = 0; - int firstDocumentIndex = 0; - - bool isDocumentOnly() const { return documentCount > 0 && imageCount == 0 && blankPageCount == 0; } - bool isSingleDocument() const { return isDocumentOnly() && documentCount == 1; } - bool isTwoDocuments() const { return isDocumentOnly() && documentCount == 2; } - }; - - SelectionInfo getSelectionInfo(const QModelIndexList& list) const; - - void regroupEvenOdd(const QModelIndexList& list); - void regroupPaired(const QModelIndexList& list); - void regroupBookmarks(const QModelIndexList& list, const std::vector& indices); - void regroupAlternatingPages(const QModelIndexList& list, bool reversed); - - bool canUndo() const { return !m_undoSteps.empty(); } - bool canRedo() const { return !m_redoSteps.empty(); } - - void undo(); - void redo(); - -private: - static const int MAX_UNDO_REDO_STEPS = 10; - - void createDocumentGroup(int index, const QModelIndex& insertIndex); - QString getGroupNameFromDocument(int index) const; - void updateItemCaptionAndTags(PageGroupItem& item) const; - void insertEmptyPage(const QModelIndex& index); - - struct UndoRedoStep - { - auto operator<=>(const UndoRedoStep&) const = default; - - std::vector pageGroupItems; - std::vector trashBin; - }; - - class Modifier - { - public: - explicit Modifier(PageItemModel* model); - ~Modifier(); - - private: - PageItemModel* m_model; - UndoRedoStep m_stateBeforeModification; - }; - - std::vector extractItems(std::vector& items, const QModelIndexList& selection) const; - - QItemSelection getSelectionImpl(std::function filter) const; - - UndoRedoStep getCurrentStep() const { return UndoRedoStep{ m_pageGroupItems, m_trashBin }; } - void updateUndoRedoSteps(); - void clearUndoRedo(); - - void performUndoRedo(std::vector& load, std::vector& save); - - std::vector m_pageGroupItems; - std::map m_documents; - std::map m_images; - std::vector m_trashBin; - - std::vector m_undoSteps; // Oldest step is first, newest step is last - std::vector m_redoSteps; -}; - -} // namespace pdfdocpage - -#endif // PDFDOCPAGEORGANIZER_PAGEITEMMODEL_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCPAGEORGANIZER_PAGEITEMMODEL_H +#define PDFDOCPAGEORGANIZER_PAGEITEMMODEL_H + +#include "pdfdocument.h" +#include "pdfutils.h" +#include "pdfdocumentmanipulator.h" + +#include +#include + +namespace pdfdocpage +{ + +enum PageType +{ + PT_DocumentPage, + PT_Image, + PT_Empty, + PT_Last +}; + +struct PageGroupItem +{ + QString groupName; + QString pagesCaption; + QStringList tags; + + struct GroupItem + { + auto operator<=>(const GroupItem&) const = default; + + int documentIndex = -1; + pdf::PDFInteger pageIndex = -1; + pdf::PDFInteger imageIndex = -1; + QSizeF rotatedPageDimensionsMM; ///< Rotated page dimensions, but without additional rotation + pdf::PageRotation pageAdditionalRotation = pdf::PageRotation::None; ///< Additional rotation applied to the page + PageType pageType = PT_DocumentPage; + }; + + std::vector groups; + + auto operator<=>(const PageGroupItem&) const = default; + + bool isGrouped() const { return groups.size() > 1; } + + std::set getDocumentIndices() const; + + void rotateLeft(); + void rotateRight(); +}; + +struct DocumentItem +{ + QString fileName; + pdf::PDFDocument document; +}; + +struct ImageItem +{ + QImage image; + QByteArray imageData; +}; + +class PageItemModel : public QAbstractItemModel +{ + Q_OBJECT + +private: + using BaseClass = QAbstractItemModel; + +public: + explicit PageItemModel(QObject* parent); + + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const override; + virtual QModelIndex parent(const QModelIndex& index) const override; + virtual int rowCount(const QModelIndex& parent) const override; + virtual int columnCount(const QModelIndex& parent) const override; + virtual QVariant data(const QModelIndex& index, int role) const override; + virtual QStringList mimeTypes() const override; + virtual QMimeData* mimeData(const QModelIndexList& indexes) const override; + virtual bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + virtual Qt::DropActions supportedDropActions() const override; + virtual Qt::DropActions supportedDragActions() const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + + enum class AssembleMode + { + Unite, + Separate, + SeparateGrouped + }; + + std::vector> getAssembledPages(AssembleMode mode) const; + + /// Clear all data and undo/redo + void clear(); + + /// Adds document to the model, inserts one single page group containing + /// the whole document. Returns index of a newly added document. If document + /// cannot be added (for example, it already exists), -1 is returned. + /// \param fileName File name + /// \param document Document + /// \param index Index, where image is inserted + /// \returns Identifier of the document (internal index) + int insertDocument(QString fileName, pdf::PDFDocument document, const QModelIndex& index); + + /// Adds image to the model, inserts one single page containing + /// the image. Returns index of a newly added image. If image + /// cannot be read from the file, -1 is returned. + /// \param fileName Image file + /// \param index Index, where image is inserted + /// \returns Identifier of the image (internal index) + int insertImage(QString fileName, const QModelIndex& index); + + /// Returns item at a given index. If item doesn't exist, + /// then nullptr is returned. + /// \param index Index + const PageGroupItem* getItem(const QModelIndex& index) const; + + /// Returns item at a given index. If item doesn't exist, + /// then nullptr is returned. + /// \param index Index + PageGroupItem* getItem(const QModelIndex& index); + + /// Returns true, if grouped item exists in the indices + bool isGrouped(const QModelIndexList& indices) const; + + /// Returns true, if trash bin is empty + bool isTrashBinEmpty() const { return m_trashBin.empty(); } + + QItemSelection getSelectionEven() const; + QItemSelection getSelectionOdd() const; + QItemSelection getSelectionPortrait() const; + QItemSelection getSelectionLandscape() const; + + void group(const QModelIndexList& list); + void ungroup(const QModelIndexList& list); + + QModelIndexList restoreRemovedItems(); + QModelIndexList cloneSelection(const QModelIndexList& list); + void removeSelection(const QModelIndexList& list); + void insertEmptyPage(const QModelIndexList& list); + + void rotateLeft(const QModelIndexList& list); + void rotateRight(const QModelIndexList& list); + + static QString getMimeDataType() { return QLatin1String("application/pagemodel.PDF4QtDocPageOrganizer"); } + + const std::map& getDocuments() const { return m_documents; } + const std::map& getImages() const { return m_images; } + + struct SelectionInfo + { + int documentCount = 0; + int imageCount = 0; + int blankPageCount = 0; + int firstDocumentIndex = 0; + + bool isDocumentOnly() const { return documentCount > 0 && imageCount == 0 && blankPageCount == 0; } + bool isSingleDocument() const { return isDocumentOnly() && documentCount == 1; } + bool isTwoDocuments() const { return isDocumentOnly() && documentCount == 2; } + }; + + SelectionInfo getSelectionInfo(const QModelIndexList& list) const; + + void regroupEvenOdd(const QModelIndexList& list); + void regroupPaired(const QModelIndexList& list); + void regroupBookmarks(const QModelIndexList& list, const std::vector& indices); + void regroupAlternatingPages(const QModelIndexList& list, bool reversed); + + bool canUndo() const { return !m_undoSteps.empty(); } + bool canRedo() const { return !m_redoSteps.empty(); } + + void undo(); + void redo(); + +private: + static const int MAX_UNDO_REDO_STEPS = 10; + + void createDocumentGroup(int index, const QModelIndex& insertIndex); + QString getGroupNameFromDocument(int index) const; + void updateItemCaptionAndTags(PageGroupItem& item) const; + void insertEmptyPage(const QModelIndex& index); + + struct UndoRedoStep + { + auto operator<=>(const UndoRedoStep&) const = default; + + std::vector pageGroupItems; + std::vector trashBin; + }; + + class Modifier + { + public: + explicit Modifier(PageItemModel* model); + ~Modifier(); + + private: + PageItemModel* m_model; + UndoRedoStep m_stateBeforeModification; + }; + + std::vector extractItems(std::vector& items, const QModelIndexList& selection) const; + + QItemSelection getSelectionImpl(std::function filter) const; + + UndoRedoStep getCurrentStep() const { return UndoRedoStep{ m_pageGroupItems, m_trashBin }; } + void updateUndoRedoSteps(); + void clearUndoRedo(); + + void performUndoRedo(std::vector& load, std::vector& save); + + std::vector m_pageGroupItems; + std::map m_documents; + std::map m_images; + std::vector m_trashBin; + + std::vector m_undoSteps; // Oldest step is first, newest step is last + std::vector m_redoSteps; +}; + +} // namespace pdfdocpage + +#endif // PDFDOCPAGEORGANIZER_PAGEITEMMODEL_H diff --git a/Pdf4QtDocPageOrganizer/resources.qrc b/Pdf4QtDocPageOrganizer/resources.qrc index c8cb34e..dbb6655 100644 --- a/Pdf4QtDocPageOrganizer/resources.qrc +++ b/Pdf4QtDocPageOrganizer/resources.qrc @@ -1,43 +1,43 @@ - - - resources/about.svg - resources/clone-selection.svg - resources/close.svg - resources/copy.svg - resources/cut.svg - resources/get-source.svg - resources/group.svg - resources/insert-empty-page.svg - resources/insert-image.svg - resources/insert-page-from-pdf.svg - resources/make-separated-document.svg - resources/make-separated-document-from-groups.svg - resources/make-united-document.svg - resources/open.svg - resources/paste.svg - resources/remove-selection.svg - resources/replace-selection.svg - resources/restore-removed-items.svg - resources/rotate-left.svg - resources/rotate-right.svg - resources/select-all.svg - resources/select-even.svg - resources/select-landscape.svg - resources/select-none.svg - resources/select-odd.svg - resources/select-portrait.svg - resources/ungroup.svg - resources/zoom-in.svg - resources/zoom-out.svg - resources/clear.svg - resources/invert-selection.svg - resources/regroup-alternating.svg - resources/regroup-alternating-reversed.svg - resources/regroup-bookmarks.svg - resources/regroup-even-odd.svg - resources/regroup-pairs.svg - resources/undo.svg - resources/redo.svg - resources/bookmark.svg - - + + + resources/about.svg + resources/clone-selection.svg + resources/close.svg + resources/copy.svg + resources/cut.svg + resources/get-source.svg + resources/group.svg + resources/insert-empty-page.svg + resources/insert-image.svg + resources/insert-page-from-pdf.svg + resources/make-separated-document.svg + resources/make-separated-document-from-groups.svg + resources/make-united-document.svg + resources/open.svg + resources/paste.svg + resources/remove-selection.svg + resources/replace-selection.svg + resources/restore-removed-items.svg + resources/rotate-left.svg + resources/rotate-right.svg + resources/select-all.svg + resources/select-even.svg + resources/select-landscape.svg + resources/select-none.svg + resources/select-odd.svg + resources/select-portrait.svg + resources/ungroup.svg + resources/zoom-in.svg + resources/zoom-out.svg + resources/clear.svg + resources/invert-selection.svg + resources/regroup-alternating.svg + resources/regroup-alternating-reversed.svg + resources/regroup-bookmarks.svg + resources/regroup-even-odd.svg + resources/regroup-pairs.svg + resources/undo.svg + resources/redo.svg + resources/bookmark.svg + + diff --git a/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.cpp b/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.cpp index 7a3c0ab..59a4ed9 100644 --- a/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.cpp +++ b/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.cpp @@ -1,162 +1,162 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "selectbookmarkstoregroupdialog.h" -#include "ui_selectbookmarkstoregroupdialog.h" - -#include "pdfitemmodels.h" -#include "pdfwidgetutils.h" - -#include - -namespace pdfdocpage -{ - -SelectBookmarksToRegroupDialog::SelectBookmarksToRegroupDialog(const pdf::PDFDocument* document, QWidget* parent) : - QDialog(parent), - ui(new Ui::SelectBookmarksToRegroupDialog), - m_document(document), - m_model(nullptr) -{ - ui->setupUi(this); - - QIcon bookmarkIcon(":/pdfdocpage/resources/bookmark.svg"); - m_model = new pdf::PDFSelectableOutlineTreeItemModel(qMove(bookmarkIcon), this); - ui->bookmarksView->setModel(m_model); - ui->bookmarksView->header()->hide(); - - m_model->setDocument(pdf::PDFModifiedDocument(const_cast(document), nullptr)); - ui->bookmarksView->expandToDepth(2); - ui->bookmarksView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->bookmarksView, &QTreeView::customContextMenuRequested, this, &SelectBookmarksToRegroupDialog::onViewContextMenuRequested); - - QSize size = pdf::PDFWidgetUtils::scaleDPI(this, QSize(400, 600)); - setMinimumSize(size); - pdf::PDFWidgetUtils::style(this); -} - -SelectBookmarksToRegroupDialog::~SelectBookmarksToRegroupDialog() -{ - delete ui; -} - -std::vector SelectBookmarksToRegroupDialog::getSelectedOutlineItems() const -{ - return m_model->getSelectedItems(); -} - -void SelectBookmarksToRegroupDialog::onViewContextMenuRequested(const QPoint& pos) -{ - QMenu menu; - menu.addAction(tr("Select All"), this, &SelectBookmarksToRegroupDialog::selectAll); - menu.addAction(tr("Deselect All"), this, &SelectBookmarksToRegroupDialog::deselectAll); - menu.addAction(tr("Invert Selection"), this, &SelectBookmarksToRegroupDialog::invertSelection); - - menu.addSeparator(); - menu.addAction(tr("Select Level 1"), this, &SelectBookmarksToRegroupDialog::selectLevel1); - menu.addAction(tr("Select Level 2"), this, &SelectBookmarksToRegroupDialog::selectLevel2); - - QModelIndex index = ui->bookmarksView->indexAt(pos); - if (index.isValid()) - { - m_menuIndex = index; - - menu.addSeparator(); - menu.addAction(tr("Select subtree"), this, &SelectBookmarksToRegroupDialog::selectSubtree); - menu.addAction(tr("Deselect subtree"), this, &SelectBookmarksToRegroupDialog::deselectSubtree); - } - menu.exec(ui->bookmarksView->mapToGlobal(pos)); -} - -void SelectBookmarksToRegroupDialog::manipulateTree(const QModelIndex& index, - const std::function& manipulator) -{ - if (index.isValid()) - { - manipulator(index); - } - - const int count = m_model->rowCount(index); - for (int i = 0; i < count; ++i) - { - QModelIndex childIndex = m_model->index(i, 0, index); - manipulateTree(childIndex, manipulator); - } -} - -std::function SelectBookmarksToRegroupDialog::createCheckByDepthManipulator(int targetDepth) const -{ - auto manipulator = [this, targetDepth](QModelIndex index) - { - int depth = 1; - QModelIndex parentIndex = index.parent(); - while (parentIndex.isValid()) - { - ++depth; - parentIndex = parentIndex.parent(); - } - - if (depth == targetDepth) - { - m_model->setData(index, Qt::Checked, Qt::CheckStateRole); - } - }; - - return manipulator; -} - -void SelectBookmarksToRegroupDialog::selectAll() -{ - manipulateTree(ui->bookmarksView->rootIndex(), [this](QModelIndex index) { m_model->setData(index, Qt::Checked, Qt::CheckStateRole); }); -} - -void SelectBookmarksToRegroupDialog::deselectAll() -{ - manipulateTree(ui->bookmarksView->rootIndex(), [this](QModelIndex index) { m_model->setData(index, Qt::Unchecked, Qt::CheckStateRole); }); -} - -void SelectBookmarksToRegroupDialog::invertSelection() -{ - auto manipulator = [this](QModelIndex index) - { - const bool isChecked = index.data(Qt::CheckStateRole).toInt() == Qt::Checked; - m_model->setData(index, isChecked ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); - }; - manipulateTree(ui->bookmarksView->rootIndex(), manipulator); -} - -void SelectBookmarksToRegroupDialog::selectLevel1() -{ - manipulateTree(ui->bookmarksView->rootIndex(), createCheckByDepthManipulator(1)); -} - -void SelectBookmarksToRegroupDialog::selectLevel2() -{ - manipulateTree(ui->bookmarksView->rootIndex(), createCheckByDepthManipulator(2)); -} - -void SelectBookmarksToRegroupDialog::selectSubtree() -{ - manipulateTree(m_menuIndex, [this](QModelIndex index) { m_model->setData(index, Qt::Checked, Qt::CheckStateRole); }); -} - -void SelectBookmarksToRegroupDialog::deselectSubtree() -{ - manipulateTree(m_menuIndex, [this](QModelIndex index) { m_model->setData(index, Qt::Unchecked, Qt::CheckStateRole); }); -} - -} // namespace pdfdocpage +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "selectbookmarkstoregroupdialog.h" +#include "ui_selectbookmarkstoregroupdialog.h" + +#include "pdfitemmodels.h" +#include "pdfwidgetutils.h" + +#include + +namespace pdfdocpage +{ + +SelectBookmarksToRegroupDialog::SelectBookmarksToRegroupDialog(const pdf::PDFDocument* document, QWidget* parent) : + QDialog(parent), + ui(new Ui::SelectBookmarksToRegroupDialog), + m_document(document), + m_model(nullptr) +{ + ui->setupUi(this); + + QIcon bookmarkIcon(":/pdfdocpage/resources/bookmark.svg"); + m_model = new pdf::PDFSelectableOutlineTreeItemModel(qMove(bookmarkIcon), this); + ui->bookmarksView->setModel(m_model); + ui->bookmarksView->header()->hide(); + + m_model->setDocument(pdf::PDFModifiedDocument(const_cast(document), nullptr)); + ui->bookmarksView->expandToDepth(2); + ui->bookmarksView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->bookmarksView, &QTreeView::customContextMenuRequested, this, &SelectBookmarksToRegroupDialog::onViewContextMenuRequested); + + QSize size = pdf::PDFWidgetUtils::scaleDPI(this, QSize(400, 600)); + setMinimumSize(size); + pdf::PDFWidgetUtils::style(this); +} + +SelectBookmarksToRegroupDialog::~SelectBookmarksToRegroupDialog() +{ + delete ui; +} + +std::vector SelectBookmarksToRegroupDialog::getSelectedOutlineItems() const +{ + return m_model->getSelectedItems(); +} + +void SelectBookmarksToRegroupDialog::onViewContextMenuRequested(const QPoint& pos) +{ + QMenu menu; + menu.addAction(tr("Select All"), this, &SelectBookmarksToRegroupDialog::selectAll); + menu.addAction(tr("Deselect All"), this, &SelectBookmarksToRegroupDialog::deselectAll); + menu.addAction(tr("Invert Selection"), this, &SelectBookmarksToRegroupDialog::invertSelection); + + menu.addSeparator(); + menu.addAction(tr("Select Level 1"), this, &SelectBookmarksToRegroupDialog::selectLevel1); + menu.addAction(tr("Select Level 2"), this, &SelectBookmarksToRegroupDialog::selectLevel2); + + QModelIndex index = ui->bookmarksView->indexAt(pos); + if (index.isValid()) + { + m_menuIndex = index; + + menu.addSeparator(); + menu.addAction(tr("Select subtree"), this, &SelectBookmarksToRegroupDialog::selectSubtree); + menu.addAction(tr("Deselect subtree"), this, &SelectBookmarksToRegroupDialog::deselectSubtree); + } + menu.exec(ui->bookmarksView->mapToGlobal(pos)); +} + +void SelectBookmarksToRegroupDialog::manipulateTree(const QModelIndex& index, + const std::function& manipulator) +{ + if (index.isValid()) + { + manipulator(index); + } + + const int count = m_model->rowCount(index); + for (int i = 0; i < count; ++i) + { + QModelIndex childIndex = m_model->index(i, 0, index); + manipulateTree(childIndex, manipulator); + } +} + +std::function SelectBookmarksToRegroupDialog::createCheckByDepthManipulator(int targetDepth) const +{ + auto manipulator = [this, targetDepth](QModelIndex index) + { + int depth = 1; + QModelIndex parentIndex = index.parent(); + while (parentIndex.isValid()) + { + ++depth; + parentIndex = parentIndex.parent(); + } + + if (depth == targetDepth) + { + m_model->setData(index, Qt::Checked, Qt::CheckStateRole); + } + }; + + return manipulator; +} + +void SelectBookmarksToRegroupDialog::selectAll() +{ + manipulateTree(ui->bookmarksView->rootIndex(), [this](QModelIndex index) { m_model->setData(index, Qt::Checked, Qt::CheckStateRole); }); +} + +void SelectBookmarksToRegroupDialog::deselectAll() +{ + manipulateTree(ui->bookmarksView->rootIndex(), [this](QModelIndex index) { m_model->setData(index, Qt::Unchecked, Qt::CheckStateRole); }); +} + +void SelectBookmarksToRegroupDialog::invertSelection() +{ + auto manipulator = [this](QModelIndex index) + { + const bool isChecked = index.data(Qt::CheckStateRole).toInt() == Qt::Checked; + m_model->setData(index, isChecked ? Qt::Unchecked : Qt::Checked, Qt::CheckStateRole); + }; + manipulateTree(ui->bookmarksView->rootIndex(), manipulator); +} + +void SelectBookmarksToRegroupDialog::selectLevel1() +{ + manipulateTree(ui->bookmarksView->rootIndex(), createCheckByDepthManipulator(1)); +} + +void SelectBookmarksToRegroupDialog::selectLevel2() +{ + manipulateTree(ui->bookmarksView->rootIndex(), createCheckByDepthManipulator(2)); +} + +void SelectBookmarksToRegroupDialog::selectSubtree() +{ + manipulateTree(m_menuIndex, [this](QModelIndex index) { m_model->setData(index, Qt::Checked, Qt::CheckStateRole); }); +} + +void SelectBookmarksToRegroupDialog::deselectSubtree() +{ + manipulateTree(m_menuIndex, [this](QModelIndex index) { m_model->setData(index, Qt::Unchecked, Qt::CheckStateRole); }); +} + +} // namespace pdfdocpage diff --git a/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.h b/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.h index ca1ee04..023f43d 100644 --- a/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.h +++ b/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.h @@ -1,73 +1,73 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCPAGEORGANIZER_SELECTBOOKMARKSTOREGROUPDIALOG_H -#define PDFDOCPAGEORGANIZER_SELECTBOOKMARKSTOREGROUPDIALOG_H - -#include "pdfdocument.h" - -#include - -namespace Ui -{ -class SelectBookmarksToRegroupDialog; -} - -namespace pdf -{ -class PDFSelectableOutlineTreeItemModel; -} - -namespace pdfdocpage -{ - -class SelectBookmarksToRegroupDialog : public QDialog -{ - Q_OBJECT - -public: - explicit SelectBookmarksToRegroupDialog(const pdf::PDFDocument* document, QWidget* parent); - virtual ~SelectBookmarksToRegroupDialog() override; - - std::vector getSelectedOutlineItems() const; - -private: - void selectAll(); - void deselectAll(); - void invertSelection(); - - void selectLevel1(); - void selectLevel2(); - - void selectSubtree(); - void deselectSubtree(); - - void onViewContextMenuRequested(const QPoint& pos); - - void manipulateTree(const QModelIndex& index, const std::function& manipulator); - - std::function createCheckByDepthManipulator(int targetDepth) const; - - Ui::SelectBookmarksToRegroupDialog* ui; - const pdf::PDFDocument* m_document; - pdf::PDFSelectableOutlineTreeItemModel* m_model; - QModelIndex m_menuIndex; -}; - -} // namespace pdfdocpage - -#endif // PDFDOCPAGEORGANIZER_SELECTBOOKMARKSTOREGROUPDIALOG_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCPAGEORGANIZER_SELECTBOOKMARKSTOREGROUPDIALOG_H +#define PDFDOCPAGEORGANIZER_SELECTBOOKMARKSTOREGROUPDIALOG_H + +#include "pdfdocument.h" + +#include + +namespace Ui +{ +class SelectBookmarksToRegroupDialog; +} + +namespace pdf +{ +class PDFSelectableOutlineTreeItemModel; +} + +namespace pdfdocpage +{ + +class SelectBookmarksToRegroupDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SelectBookmarksToRegroupDialog(const pdf::PDFDocument* document, QWidget* parent); + virtual ~SelectBookmarksToRegroupDialog() override; + + std::vector getSelectedOutlineItems() const; + +private: + void selectAll(); + void deselectAll(); + void invertSelection(); + + void selectLevel1(); + void selectLevel2(); + + void selectSubtree(); + void deselectSubtree(); + + void onViewContextMenuRequested(const QPoint& pos); + + void manipulateTree(const QModelIndex& index, const std::function& manipulator); + + std::function createCheckByDepthManipulator(int targetDepth) const; + + Ui::SelectBookmarksToRegroupDialog* ui; + const pdf::PDFDocument* m_document; + pdf::PDFSelectableOutlineTreeItemModel* m_model; + QModelIndex m_menuIndex; +}; + +} // namespace pdfdocpage + +#endif // PDFDOCPAGEORGANIZER_SELECTBOOKMARKSTOREGROUPDIALOG_H diff --git a/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.ui b/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.ui index 4e8a2ee..d7e5a99 100644 --- a/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.ui +++ b/Pdf4QtDocPageOrganizer/selectbookmarkstoregroupdialog.ui @@ -1,67 +1,67 @@ - - - SelectBookmarksToRegroupDialog - - - - 0 - 0 - 516 - 494 - - - - Select Bookmarks - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - buttonBox - accepted() - SelectBookmarksToRegroupDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - SelectBookmarksToRegroupDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - + + + SelectBookmarksToRegroupDialog + + + + 0 + 0 + 516 + 494 + + + + Select Bookmarks + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SelectBookmarksToRegroupDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SelectBookmarksToRegroupDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 2a63d49..c62a23d 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -1,289 +1,289 @@ -# Copyright (C) 2018-2021 Jakub Melka -# -# This file is part of PDF4QT. -# -# PDF4QT is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# with the written consent of the copyright owner, any later version. -# -# PDF4QT 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 Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PDF4QT. If not, see . - -QT += gui widgets - -TARGET = Pdf4QtLib -TEMPLATE = lib - -win32:TARGET_EXT = .dll -VERSION = 1.0.0 - -QMAKE_TARGET_DESCRIPTION = "PDF rendering / editing library for Qt" -QMAKE_TARGET_COPYRIGHT = "(c) Jakub Melka 2018-2021" - -DEFINES += PDF4QTLIB_LIBRARY - -# The following define makes your compiler emit warnings if you use -# any feature of Qt which has been marked as deprecated (the exact warnings -# depend on your compiler). Please consult the documentation of the -# deprecated API in order to know how to port your code away from it. -DEFINES += QT_DEPRECATED_WARNINGS - -# You can also make your code fail to compile if you use deprecated APIs. -# In order to do so, uncomment the following line. -# You can also select to disable deprecated APIs only up to a certain version of Qt. -#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - -DESTDIR = $$OUT_PWD/.. - -SOURCES += \ - sources/pdfaction.cpp \ - sources/pdfadvancedtools.cpp \ - sources/pdfannotation.cpp \ - sources/pdfblendfunction.cpp \ - sources/pdfccittfaxdecoder.cpp \ - sources/pdfcms.cpp \ - sources/pdfcompiler.cpp \ - sources/pdfdocumentbuilder.cpp \ - sources/pdfdocumentmanipulator.cpp \ - sources/pdfdocumenttextflow.cpp \ - sources/pdfdocumenttextfloweditormodel.cpp \ - sources/pdfdocumentwriter.cpp \ - sources/pdfexecutionpolicy.cpp \ - sources/pdffile.cpp \ - sources/pdfform.cpp \ - sources/pdfitemmodels.cpp \ - sources/pdfjavascriptscanner.cpp \ - sources/pdfjbig2decoder.cpp \ - sources/pdfmultimedia.cpp \ - sources/pdfobject.cpp \ - sources/pdfobjecteditormodel.cpp \ - sources/pdfobjecteditorwidget.cpp \ - sources/pdfobjectutils.cpp \ - sources/pdfoptimizer.cpp \ - sources/pdfoptionalcontent.cpp \ - sources/pdfoutline.cpp \ - sources/pdfpagenavigation.cpp \ - sources/pdfpagetransition.cpp \ - sources/pdfpainterutils.cpp \ - sources/pdfparser.cpp \ - sources/pdfdocument.cpp \ - sources/pdfdocumentreader.cpp \ - sources/pdfpattern.cpp \ - sources/pdfplugin.cpp \ - sources/pdfprogress.cpp \ - sources/pdfredact.cpp \ - sources/pdfsecurityhandler.cpp \ - sources/pdfselectpagesdialog.cpp \ - sources/pdfsignaturehandler.cpp \ - sources/pdfsnapper.cpp \ - sources/pdfstructuretree.cpp \ - sources/pdftextlayout.cpp \ - sources/pdftransparencyrenderer.cpp \ - sources/pdfutils.cpp \ - sources/pdfwidgettool.cpp \ - sources/pdfwidgetutils.cpp \ - sources/pdfxreftable.cpp \ - sources/pdfvisitor.cpp \ - sources/pdfencoding.cpp \ - sources/pdfcatalog.cpp \ - sources/pdfpage.cpp \ - sources/pdfstreamfilters.cpp \ - sources/pdfdrawspacecontroller.cpp \ - sources/pdfdrawwidget.cpp \ - sources/pdfcolorspaces.cpp \ - sources/pdfrenderer.cpp \ - sources/pdfpagecontentprocessor.cpp \ - sources/pdfpainter.cpp \ - sources/pdfrenderingerrorswidget.cpp \ - sources/pdffunction.cpp \ - sources/pdfnametounicode.cpp \ - sources/pdffont.cpp \ - sources/pdfimage.cpp - -HEADERS += \ - sources/pdfaction.h \ - sources/pdfadvancedtools.h \ - sources/pdfannotation.h \ - sources/pdfblendfunction.h \ - sources/pdfccittfaxdecoder.h \ - sources/pdfcms.h \ - sources/pdfcompiler.h \ - sources/pdfdocumentbuilder.h \ - sources/pdfdocumentdrawinterface.h \ - sources/pdfdocumentmanipulator.h \ - sources/pdfdocumenttextflow.h \ - sources/pdfdocumenttextfloweditormodel.h \ - sources/pdfdocumentwriter.h \ - sources/pdfexecutionpolicy.h \ - sources/pdffile.h \ - sources/pdfform.h \ - sources/pdfitemmodels.h \ - sources/pdfjavascriptscanner.h \ - sources/pdfjbig2decoder.h \ - sources/pdfmeshqualitysettings.h \ - sources/pdfmultimedia.h \ - sources/pdfnametreeloader.h \ - sources/pdfobject.h \ - sources/pdfobjecteditormodel.h \ - sources/pdfobjecteditorwidget.h \ - sources/pdfobjecteditorwidget_impl.h \ - sources/pdfobjectutils.h \ - sources/pdfoptimizer.h \ - sources/pdfoptionalcontent.h \ - sources/pdfoutline.h \ - sources/pdfpagenavigation.h \ - sources/pdfpagetransition.h \ - sources/pdfpainterutils.h \ - sources/pdfparser.h \ - sources/pdfglobal.h \ - sources/pdfconstants.h \ - sources/pdfdocument.h \ - sources/pdfdocumentreader.h \ - sources/pdfpattern.h \ - sources/pdfplugin.h \ - sources/pdfprogress.h \ - sources/pdfredact.h \ - sources/pdfsecurityhandler.h \ - sources/pdfselectpagesdialog.h \ - sources/pdfsignaturehandler.h \ - sources/pdfsignaturehandler_impl.h \ - sources/pdfsnapper.h \ - sources/pdfstructuretree.h \ - sources/pdftextlayout.h \ - sources/pdftransparencyrenderer.h \ - sources/pdfwidgettool.h \ - sources/pdfwidgetutils.h \ - sources/pdfxreftable.h \ - sources/pdfflatmap.h \ - sources/pdfvisitor.h \ - sources/pdfencoding.h \ - sources/pdfcatalog.h \ - sources/pdfnumbertreeloader.h \ - sources/pdfpage.h \ - sources/pdfstreamfilters.h \ - sources/pdfdrawspacecontroller.h \ - sources/pdfdrawwidget.h \ - sources/pdfflatarray.h \ - sources/pdfcolorspaces.h \ - sources/pdfrenderer.h \ - sources/pdfpagecontentprocessor.h \ - sources/pdfpainter.h \ - sources/pdfutils.h \ - sources/pdfrenderingerrorswidget.h \ - sources/pdffunction.h \ - sources/pdfnametounicode.h \ - sources/pdffont.h \ - sources/pdfexception.h \ - sources/pdfimage.h - -FORMS += \ - sources/pdfrenderingerrorswidget.ui \ - sources/pdfselectpagesdialog.ui - -RESOURCES += cmaps.qrc - -Pdf4Qt_DEPENDENCIES_PATH = $$absolute_path(../../PdfforQt-Dependencies, $$PWD) -Pdf4Qt_OPENSSL_PATH = $$absolute_path(../../Tools, $$[QT_INSTALL_PREFIX]) - -CONFIG(debug, debug|release) { -SUFFIX = d -} - -# Link to freetype library -LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/FreeType/ -lfreetype$${SUFFIX} -INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/FreeType/include -DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/FreeType/include - -# Link to OpenJPEG library -LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/OpenJPEG/lib/ -lopenjp2$${SUFFIX} -INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/OpenJPEG/include/openjpeg-2.3 -DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/OpenJPEG/include/openjpeg-2.3 -DEFINES += OPJ_STATIC - -# Link to Independent JPEG Groups libjpeg -LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/libjpeg/bin/ -ljpeg$${SUFFIX} -INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/libjpeg/include -DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/libjpeg/include - -# Link OpenSSL -LIBS += -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/lib -llibcrypto -llibssl -INCLUDEPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include -DEPENDPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include - -# Add OpenSSL to installations -openssl_lib.files = $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin/libcrypto-1_1-x64.dll $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin/libssl-1_1-x64.dll -openssl_lib.path = $$DESTDIR/install -INSTALLS += openssl_lib - -# Link zlib -LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/zlib/bin/ -lzlibstatic$${SUFFIX} -INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/zlib/include -DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/zlib/include - -# Link lcms2 -LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/lcms2/bin$${SUFFIX}/ -llcms2_static -INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/lcms2/include -DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/lcms2/include - -# ensure debug info even for RELEASE build -CONFIG += force_debug_info - -QMAKE_CXXFLAGS += /std:c++latest /utf-8 -QMAKE_RESOURCE_FLAGS += -threshold 0 -compress 9 - -PdfforQt_library.files = $$DESTDIR/Pdf4QtLib.dll -PdfforQt_library.path = $$DESTDIR/install -PdfforQt_library.CONFIG += no_check_exist - -INSTALLS += PdfforQt_library - -qt_libraries.files = $$[QT_INSTALL_BINS]/Qt?Widgets$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?Gui$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?Core$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?WinExtras$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?Svg$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?PrintSupport$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?TextToSpeech$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?Network$${SUFFIX}.dll \ - $$[QT_INSTALL_BINS]/Qt?Xml$${SUFFIX}.dll -qt_libraries.path = $$DESTDIR/install -INSTALLS += qt_libraries - -qt_plugin_platform.files = $$[QT_INSTALL_PLUGINS]/platforms/qwindows$${SUFFIX}.dll -qt_plugin_platform.path = $$DESTDIR/install/platforms -INSTALLS += qt_plugin_platform - -qt_plugin_style.files = $$[QT_INSTALL_PLUGINS]/styles/qwindowsvistastyle$${SUFFIX}.dll -qt_plugin_style.path = $$DESTDIR/install/styles -INSTALLS += qt_plugin_style - -qt_plugin_imageformat.files = $$[QT_INSTALL_PLUGINS]/imageformats/qgif$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qicns$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qico$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qjpeg$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qsvg$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qtga$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qtiff$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qwbpm$${SUFFIX}.dll \ - $$[QT_INSTALL_PLUGINS]/imageformats/qwebp$${SUFFIX}.dll -qt_plugin_imageformat.path = $$DESTDIR/install/imageformats -INSTALLS += qt_plugin_imageformat - -qt_plugin_iconengine.files = $$[QT_INSTALL_PLUGINS]/iconengines/qsvgicon$${SUFFIX}.dll -qt_plugin_iconengine.path = $$DESTDIR/install/iconengines -INSTALLS += qt_plugin_iconengine - -qt_plugin_printsupport.files = $$[QT_INSTALL_PLUGINS]/printsupport/windowsprintersupport$${SUFFIX}.dll -qt_plugin_printsupport.path = $$DESTDIR/install/printsupport -INSTALLS += qt_plugin_printsupport - -qt_plugin_texttospeech.files = $$[QT_INSTALL_PLUGINS]/texttospeech/qtexttospeech_sapi$${SUFFIX}.dll -qt_plugin_texttospeech.path = $$DESTDIR/install/texttospeech -INSTALLS += qt_plugin_texttospeech - +# Copyright (C) 2018-2021 Jakub Melka +# +# This file is part of PDF4QT. +# +# PDF4QT is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# with the written consent of the copyright owner, any later version. +# +# PDF4QT 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PDF4QT. If not, see . + +QT += gui widgets + +TARGET = Pdf4QtLib +TEMPLATE = lib + +win32:TARGET_EXT = .dll +VERSION = 1.0.0 + +QMAKE_TARGET_DESCRIPTION = "PDF rendering / editing library for Qt" +QMAKE_TARGET_COPYRIGHT = "(c) Jakub Melka 2018-2021" + +DEFINES += PDF4QTLIB_LIBRARY + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +DESTDIR = $$OUT_PWD/.. + +SOURCES += \ + sources/pdfaction.cpp \ + sources/pdfadvancedtools.cpp \ + sources/pdfannotation.cpp \ + sources/pdfblendfunction.cpp \ + sources/pdfccittfaxdecoder.cpp \ + sources/pdfcms.cpp \ + sources/pdfcompiler.cpp \ + sources/pdfdocumentbuilder.cpp \ + sources/pdfdocumentmanipulator.cpp \ + sources/pdfdocumenttextflow.cpp \ + sources/pdfdocumenttextfloweditormodel.cpp \ + sources/pdfdocumentwriter.cpp \ + sources/pdfexecutionpolicy.cpp \ + sources/pdffile.cpp \ + sources/pdfform.cpp \ + sources/pdfitemmodels.cpp \ + sources/pdfjavascriptscanner.cpp \ + sources/pdfjbig2decoder.cpp \ + sources/pdfmultimedia.cpp \ + sources/pdfobject.cpp \ + sources/pdfobjecteditormodel.cpp \ + sources/pdfobjecteditorwidget.cpp \ + sources/pdfobjectutils.cpp \ + sources/pdfoptimizer.cpp \ + sources/pdfoptionalcontent.cpp \ + sources/pdfoutline.cpp \ + sources/pdfpagenavigation.cpp \ + sources/pdfpagetransition.cpp \ + sources/pdfpainterutils.cpp \ + sources/pdfparser.cpp \ + sources/pdfdocument.cpp \ + sources/pdfdocumentreader.cpp \ + sources/pdfpattern.cpp \ + sources/pdfplugin.cpp \ + sources/pdfprogress.cpp \ + sources/pdfredact.cpp \ + sources/pdfsecurityhandler.cpp \ + sources/pdfselectpagesdialog.cpp \ + sources/pdfsignaturehandler.cpp \ + sources/pdfsnapper.cpp \ + sources/pdfstructuretree.cpp \ + sources/pdftextlayout.cpp \ + sources/pdftransparencyrenderer.cpp \ + sources/pdfutils.cpp \ + sources/pdfwidgettool.cpp \ + sources/pdfwidgetutils.cpp \ + sources/pdfxreftable.cpp \ + sources/pdfvisitor.cpp \ + sources/pdfencoding.cpp \ + sources/pdfcatalog.cpp \ + sources/pdfpage.cpp \ + sources/pdfstreamfilters.cpp \ + sources/pdfdrawspacecontroller.cpp \ + sources/pdfdrawwidget.cpp \ + sources/pdfcolorspaces.cpp \ + sources/pdfrenderer.cpp \ + sources/pdfpagecontentprocessor.cpp \ + sources/pdfpainter.cpp \ + sources/pdfrenderingerrorswidget.cpp \ + sources/pdffunction.cpp \ + sources/pdfnametounicode.cpp \ + sources/pdffont.cpp \ + sources/pdfimage.cpp + +HEADERS += \ + sources/pdfaction.h \ + sources/pdfadvancedtools.h \ + sources/pdfannotation.h \ + sources/pdfblendfunction.h \ + sources/pdfccittfaxdecoder.h \ + sources/pdfcms.h \ + sources/pdfcompiler.h \ + sources/pdfdocumentbuilder.h \ + sources/pdfdocumentdrawinterface.h \ + sources/pdfdocumentmanipulator.h \ + sources/pdfdocumenttextflow.h \ + sources/pdfdocumenttextfloweditormodel.h \ + sources/pdfdocumentwriter.h \ + sources/pdfexecutionpolicy.h \ + sources/pdffile.h \ + sources/pdfform.h \ + sources/pdfitemmodels.h \ + sources/pdfjavascriptscanner.h \ + sources/pdfjbig2decoder.h \ + sources/pdfmeshqualitysettings.h \ + sources/pdfmultimedia.h \ + sources/pdfnametreeloader.h \ + sources/pdfobject.h \ + sources/pdfobjecteditormodel.h \ + sources/pdfobjecteditorwidget.h \ + sources/pdfobjecteditorwidget_impl.h \ + sources/pdfobjectutils.h \ + sources/pdfoptimizer.h \ + sources/pdfoptionalcontent.h \ + sources/pdfoutline.h \ + sources/pdfpagenavigation.h \ + sources/pdfpagetransition.h \ + sources/pdfpainterutils.h \ + sources/pdfparser.h \ + sources/pdfglobal.h \ + sources/pdfconstants.h \ + sources/pdfdocument.h \ + sources/pdfdocumentreader.h \ + sources/pdfpattern.h \ + sources/pdfplugin.h \ + sources/pdfprogress.h \ + sources/pdfredact.h \ + sources/pdfsecurityhandler.h \ + sources/pdfselectpagesdialog.h \ + sources/pdfsignaturehandler.h \ + sources/pdfsignaturehandler_impl.h \ + sources/pdfsnapper.h \ + sources/pdfstructuretree.h \ + sources/pdftextlayout.h \ + sources/pdftransparencyrenderer.h \ + sources/pdfwidgettool.h \ + sources/pdfwidgetutils.h \ + sources/pdfxreftable.h \ + sources/pdfflatmap.h \ + sources/pdfvisitor.h \ + sources/pdfencoding.h \ + sources/pdfcatalog.h \ + sources/pdfnumbertreeloader.h \ + sources/pdfpage.h \ + sources/pdfstreamfilters.h \ + sources/pdfdrawspacecontroller.h \ + sources/pdfdrawwidget.h \ + sources/pdfflatarray.h \ + sources/pdfcolorspaces.h \ + sources/pdfrenderer.h \ + sources/pdfpagecontentprocessor.h \ + sources/pdfpainter.h \ + sources/pdfutils.h \ + sources/pdfrenderingerrorswidget.h \ + sources/pdffunction.h \ + sources/pdfnametounicode.h \ + sources/pdffont.h \ + sources/pdfexception.h \ + sources/pdfimage.h + +FORMS += \ + sources/pdfrenderingerrorswidget.ui \ + sources/pdfselectpagesdialog.ui + +RESOURCES += cmaps.qrc + +Pdf4Qt_DEPENDENCIES_PATH = $$absolute_path(../../PdfforQt-Dependencies, $$PWD) +Pdf4Qt_OPENSSL_PATH = $$absolute_path(../../Tools, $$[QT_INSTALL_PREFIX]) + +CONFIG(debug, debug|release) { +SUFFIX = d +} + +# Link to freetype library +LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/FreeType/ -lfreetype$${SUFFIX} +INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/FreeType/include +DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/FreeType/include + +# Link to OpenJPEG library +LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/OpenJPEG/lib/ -lopenjp2$${SUFFIX} +INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/OpenJPEG/include/openjpeg-2.3 +DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/OpenJPEG/include/openjpeg-2.3 +DEFINES += OPJ_STATIC + +# Link to Independent JPEG Groups libjpeg +LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/libjpeg/bin/ -ljpeg$${SUFFIX} +INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/libjpeg/include +DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/libjpeg/include + +# Link OpenSSL +LIBS += -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/lib -llibcrypto -llibssl +INCLUDEPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include +DEPENDPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include + +# Add OpenSSL to installations +openssl_lib.files = $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin/libcrypto-1_1-x64.dll $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin/libssl-1_1-x64.dll +openssl_lib.path = $$DESTDIR/install +INSTALLS += openssl_lib + +# Link zlib +LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/zlib/bin/ -lzlibstatic$${SUFFIX} +INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/zlib/include +DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/zlib/include + +# Link lcms2 +LIBS += -L$$Pdf4Qt_DEPENDENCIES_PATH/lcms2/bin$${SUFFIX}/ -llcms2_static +INCLUDEPATH += $$Pdf4Qt_DEPENDENCIES_PATH/lcms2/include +DEPENDPATH += $$Pdf4Qt_DEPENDENCIES_PATH/lcms2/include + +# ensure debug info even for RELEASE build +CONFIG += force_debug_info + +QMAKE_CXXFLAGS += /std:c++latest /utf-8 +QMAKE_RESOURCE_FLAGS += -threshold 0 -compress 9 + +PdfforQt_library.files = $$DESTDIR/Pdf4QtLib.dll +PdfforQt_library.path = $$DESTDIR/install +PdfforQt_library.CONFIG += no_check_exist + +INSTALLS += PdfforQt_library + +qt_libraries.files = $$[QT_INSTALL_BINS]/Qt?Widgets$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Gui$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Core$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?WinExtras$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Svg$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?PrintSupport$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?TextToSpeech$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Network$${SUFFIX}.dll \ + $$[QT_INSTALL_BINS]/Qt?Xml$${SUFFIX}.dll +qt_libraries.path = $$DESTDIR/install +INSTALLS += qt_libraries + +qt_plugin_platform.files = $$[QT_INSTALL_PLUGINS]/platforms/qwindows$${SUFFIX}.dll +qt_plugin_platform.path = $$DESTDIR/install/platforms +INSTALLS += qt_plugin_platform + +qt_plugin_style.files = $$[QT_INSTALL_PLUGINS]/styles/qwindowsvistastyle$${SUFFIX}.dll +qt_plugin_style.path = $$DESTDIR/install/styles +INSTALLS += qt_plugin_style + +qt_plugin_imageformat.files = $$[QT_INSTALL_PLUGINS]/imageformats/qgif$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qicns$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qico$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qjpeg$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qsvg$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qtga$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qtiff$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qwbpm$${SUFFIX}.dll \ + $$[QT_INSTALL_PLUGINS]/imageformats/qwebp$${SUFFIX}.dll +qt_plugin_imageformat.path = $$DESTDIR/install/imageformats +INSTALLS += qt_plugin_imageformat + +qt_plugin_iconengine.files = $$[QT_INSTALL_PLUGINS]/iconengines/qsvgicon$${SUFFIX}.dll +qt_plugin_iconengine.path = $$DESTDIR/install/iconengines +INSTALLS += qt_plugin_iconengine + +qt_plugin_printsupport.files = $$[QT_INSTALL_PLUGINS]/printsupport/windowsprintersupport$${SUFFIX}.dll +qt_plugin_printsupport.path = $$DESTDIR/install/printsupport +INSTALLS += qt_plugin_printsupport + +qt_plugin_texttospeech.files = $$[QT_INSTALL_PLUGINS]/texttospeech/qtexttospeech_sapi$${SUFFIX}.dll +qt_plugin_texttospeech.path = $$DESTDIR/install/texttospeech +INSTALLS += qt_plugin_texttospeech + diff --git a/Pdf4QtLib/cmaps.qrc b/Pdf4QtLib/cmaps.qrc index de2430b..d7d34b9 100644 --- a/Pdf4QtLib/cmaps.qrc +++ b/Pdf4QtLib/cmaps.qrc @@ -1,198 +1,198 @@ - - - cmaps/78-EUC-H - cmaps/78-EUC-V - cmaps/78-H - cmaps/78ms-RKSJ-H - cmaps/78ms-RKSJ-V - cmaps/78-RKSJ-H - cmaps/78-RKSJ-V - cmaps/78-V - cmaps/83pv-RKSJ-H - cmaps/90msp-RKSJ-H - cmaps/90msp-RKSJ-V - cmaps/90ms-RKSJ-H - cmaps/90ms-RKSJ-V - cmaps/90pv-RKSJ-H - cmaps/90pv-RKSJ-V - cmaps/Add-H - cmaps/Add-RKSJ-H - cmaps/Add-RKSJ-V - cmaps/Add-V - cmaps/Adobe-CNS1-0 - cmaps/Adobe-CNS1-1 - cmaps/Adobe-CNS1-2 - cmaps/Adobe-CNS1-3 - cmaps/Adobe-CNS1-4 - cmaps/Adobe-CNS1-5 - cmaps/Adobe-CNS1-6 - cmaps/Adobe-CNS1-7 - cmaps/Adobe-GB1-0 - cmaps/Adobe-GB1-1 - cmaps/Adobe-GB1-2 - cmaps/Adobe-GB1-3 - cmaps/Adobe-GB1-4 - cmaps/Adobe-GB1-5 - cmaps/Adobe-Japan1-0 - cmaps/Adobe-Japan1-1 - cmaps/Adobe-Japan1-2 - cmaps/Adobe-Japan1-3 - cmaps/Adobe-Japan1-4 - cmaps/Adobe-Japan1-5 - cmaps/Adobe-Japan1-6 - cmaps/Adobe-Japan1-7 - cmaps/Adobe-Japan2-0 - cmaps/Adobe-Korea1-0 - cmaps/Adobe-Korea1-1 - cmaps/Adobe-Korea1-2 - cmaps/Adobe-KR-0 - cmaps/Adobe-KR-1 - cmaps/Adobe-KR-2 - cmaps/Adobe-KR-3 - cmaps/Adobe-KR-4 - cmaps/Adobe-KR-5 - cmaps/Adobe-KR-6 - cmaps/Adobe-KR-7 - cmaps/Adobe-KR-8 - cmaps/Adobe-KR-9 - cmaps/B5-H - cmaps/B5pc-H - cmaps/B5pc-V - cmaps/B5-V - cmaps/CNS1-H - cmaps/CNS1-V - cmaps/CNS2-H - cmaps/CNS2-V - cmaps/CNS-EUC-H - cmaps/CNS-EUC-V - cmaps/ETen-B5-H - cmaps/ETen-B5-V - cmaps/ETenms-B5-H - cmaps/ETenms-B5-V - cmaps/ETHK-B5-H - cmaps/ETHK-B5-V - cmaps/EUC-H - cmaps/EUC-V - cmaps/Ext-H - cmaps/Ext-RKSJ-H - cmaps/Ext-RKSJ-V - cmaps/Ext-V - cmaps/GB-EUC-H - cmaps/GB-EUC-V - cmaps/GB-H - cmaps/GBK2K-H - cmaps/GBK2K-V - cmaps/GBK-EUC-H - cmaps/GBK-EUC-V - cmaps/GBKp-EUC-H - cmaps/GBKp-EUC-V - cmaps/GBpc-EUC-H - cmaps/GBpc-EUC-V - cmaps/GBT-EUC-H - cmaps/GBT-EUC-V - cmaps/GBT-H - cmaps/GBTpc-EUC-H - cmaps/GBTpc-EUC-V - cmaps/GBT-V - cmaps/GB-V - cmaps/H - cmaps/Hankaku - cmaps/Hiragana - cmaps/HKdla-B5-H - cmaps/HKdla-B5-V - cmaps/HKdlb-B5-H - cmaps/HKdlb-B5-V - cmaps/HKgccs-B5-H - cmaps/HKgccs-B5-V - cmaps/HKm314-B5-H - cmaps/HKm314-B5-V - cmaps/HKm471-B5-H - cmaps/HKm471-B5-V - cmaps/HKscs-B5-H - cmaps/HKscs-B5-V - cmaps/Hojo-EUC-H - cmaps/Hojo-EUC-V - cmaps/Hojo-H - cmaps/Hojo-V - cmaps/Identity-H - cmaps/Identity-V - cmaps/Katakana - cmaps/KSC-EUC-H - cmaps/KSC-EUC-V - cmaps/KSC-H - cmaps/KSC-Johab-H - cmaps/KSC-Johab-V - cmaps/KSCms-UHC-H - cmaps/KSCms-UHC-HW-H - cmaps/KSCms-UHC-HW-V - cmaps/KSCms-UHC-V - cmaps/KSCpc-EUC-H - cmaps/KSCpc-EUC-V - cmaps/KSC-V - cmaps/NWP-H - cmaps/NWP-V - cmaps/RKSJ-H - cmaps/RKSJ-V - cmaps/Roman - cmaps/UniAKR-UTF8-H - cmaps/UniAKR-UTF16-H - cmaps/UniAKR-UTF32-H - cmaps/UniCNS-UCS2-H - cmaps/UniCNS-UCS2-V - cmaps/UniCNS-UTF8-H - cmaps/UniCNS-UTF8-V - cmaps/UniCNS-UTF16-H - cmaps/UniCNS-UTF16-V - cmaps/UniCNS-UTF32-H - cmaps/UniCNS-UTF32-V - cmaps/UniGB-UCS2-H - cmaps/UniGB-UCS2-V - cmaps/UniGB-UTF8-H - cmaps/UniGB-UTF8-V - cmaps/UniGB-UTF16-H - cmaps/UniGB-UTF16-V - cmaps/UniGB-UTF32-H - cmaps/UniGB-UTF32-V - cmaps/UniHojo-UCS2-H - cmaps/UniHojo-UCS2-V - cmaps/UniHojo-UTF8-H - cmaps/UniHojo-UTF8-V - cmaps/UniHojo-UTF16-H - cmaps/UniHojo-UTF16-V - cmaps/UniHojo-UTF32-H - cmaps/UniHojo-UTF32-V - cmaps/UniJIS2004-UTF8-H - cmaps/UniJIS2004-UTF8-V - cmaps/UniJIS2004-UTF16-H - cmaps/UniJIS2004-UTF16-V - cmaps/UniJIS2004-UTF32-H - cmaps/UniJIS2004-UTF32-V - cmaps/UniJISPro-UCS2-HW-V - cmaps/UniJISPro-UCS2-V - cmaps/UniJISPro-UTF8-V - cmaps/UniJIS-UCS2-H - cmaps/UniJIS-UCS2-HW-H - cmaps/UniJIS-UCS2-HW-V - cmaps/UniJIS-UCS2-V - cmaps/UniJIS-UTF8-H - cmaps/UniJIS-UTF8-V - cmaps/UniJIS-UTF16-H - cmaps/UniJIS-UTF16-V - cmaps/UniJIS-UTF32-H - cmaps/UniJIS-UTF32-V - cmaps/UniJISX0213-UTF32-H - cmaps/UniJISX0213-UTF32-V - cmaps/UniJISX02132004-UTF32-H - cmaps/UniJISX02132004-UTF32-V - cmaps/UniKS-UCS2-H - cmaps/UniKS-UCS2-V - cmaps/UniKS-UTF8-H - cmaps/UniKS-UTF8-V - cmaps/UniKS-UTF16-H - cmaps/UniKS-UTF16-V - cmaps/UniKS-UTF32-H - cmaps/UniKS-UTF32-V - cmaps/V - cmaps/WP-Symbol - - + + + cmaps/78-EUC-H + cmaps/78-EUC-V + cmaps/78-H + cmaps/78ms-RKSJ-H + cmaps/78ms-RKSJ-V + cmaps/78-RKSJ-H + cmaps/78-RKSJ-V + cmaps/78-V + cmaps/83pv-RKSJ-H + cmaps/90msp-RKSJ-H + cmaps/90msp-RKSJ-V + cmaps/90ms-RKSJ-H + cmaps/90ms-RKSJ-V + cmaps/90pv-RKSJ-H + cmaps/90pv-RKSJ-V + cmaps/Add-H + cmaps/Add-RKSJ-H + cmaps/Add-RKSJ-V + cmaps/Add-V + cmaps/Adobe-CNS1-0 + cmaps/Adobe-CNS1-1 + cmaps/Adobe-CNS1-2 + cmaps/Adobe-CNS1-3 + cmaps/Adobe-CNS1-4 + cmaps/Adobe-CNS1-5 + cmaps/Adobe-CNS1-6 + cmaps/Adobe-CNS1-7 + cmaps/Adobe-GB1-0 + cmaps/Adobe-GB1-1 + cmaps/Adobe-GB1-2 + cmaps/Adobe-GB1-3 + cmaps/Adobe-GB1-4 + cmaps/Adobe-GB1-5 + cmaps/Adobe-Japan1-0 + cmaps/Adobe-Japan1-1 + cmaps/Adobe-Japan1-2 + cmaps/Adobe-Japan1-3 + cmaps/Adobe-Japan1-4 + cmaps/Adobe-Japan1-5 + cmaps/Adobe-Japan1-6 + cmaps/Adobe-Japan1-7 + cmaps/Adobe-Japan2-0 + cmaps/Adobe-Korea1-0 + cmaps/Adobe-Korea1-1 + cmaps/Adobe-Korea1-2 + cmaps/Adobe-KR-0 + cmaps/Adobe-KR-1 + cmaps/Adobe-KR-2 + cmaps/Adobe-KR-3 + cmaps/Adobe-KR-4 + cmaps/Adobe-KR-5 + cmaps/Adobe-KR-6 + cmaps/Adobe-KR-7 + cmaps/Adobe-KR-8 + cmaps/Adobe-KR-9 + cmaps/B5-H + cmaps/B5pc-H + cmaps/B5pc-V + cmaps/B5-V + cmaps/CNS1-H + cmaps/CNS1-V + cmaps/CNS2-H + cmaps/CNS2-V + cmaps/CNS-EUC-H + cmaps/CNS-EUC-V + cmaps/ETen-B5-H + cmaps/ETen-B5-V + cmaps/ETenms-B5-H + cmaps/ETenms-B5-V + cmaps/ETHK-B5-H + cmaps/ETHK-B5-V + cmaps/EUC-H + cmaps/EUC-V + cmaps/Ext-H + cmaps/Ext-RKSJ-H + cmaps/Ext-RKSJ-V + cmaps/Ext-V + cmaps/GB-EUC-H + cmaps/GB-EUC-V + cmaps/GB-H + cmaps/GBK2K-H + cmaps/GBK2K-V + cmaps/GBK-EUC-H + cmaps/GBK-EUC-V + cmaps/GBKp-EUC-H + cmaps/GBKp-EUC-V + cmaps/GBpc-EUC-H + cmaps/GBpc-EUC-V + cmaps/GBT-EUC-H + cmaps/GBT-EUC-V + cmaps/GBT-H + cmaps/GBTpc-EUC-H + cmaps/GBTpc-EUC-V + cmaps/GBT-V + cmaps/GB-V + cmaps/H + cmaps/Hankaku + cmaps/Hiragana + cmaps/HKdla-B5-H + cmaps/HKdla-B5-V + cmaps/HKdlb-B5-H + cmaps/HKdlb-B5-V + cmaps/HKgccs-B5-H + cmaps/HKgccs-B5-V + cmaps/HKm314-B5-H + cmaps/HKm314-B5-V + cmaps/HKm471-B5-H + cmaps/HKm471-B5-V + cmaps/HKscs-B5-H + cmaps/HKscs-B5-V + cmaps/Hojo-EUC-H + cmaps/Hojo-EUC-V + cmaps/Hojo-H + cmaps/Hojo-V + cmaps/Identity-H + cmaps/Identity-V + cmaps/Katakana + cmaps/KSC-EUC-H + cmaps/KSC-EUC-V + cmaps/KSC-H + cmaps/KSC-Johab-H + cmaps/KSC-Johab-V + cmaps/KSCms-UHC-H + cmaps/KSCms-UHC-HW-H + cmaps/KSCms-UHC-HW-V + cmaps/KSCms-UHC-V + cmaps/KSCpc-EUC-H + cmaps/KSCpc-EUC-V + cmaps/KSC-V + cmaps/NWP-H + cmaps/NWP-V + cmaps/RKSJ-H + cmaps/RKSJ-V + cmaps/Roman + cmaps/UniAKR-UTF8-H + cmaps/UniAKR-UTF16-H + cmaps/UniAKR-UTF32-H + cmaps/UniCNS-UCS2-H + cmaps/UniCNS-UCS2-V + cmaps/UniCNS-UTF8-H + cmaps/UniCNS-UTF8-V + cmaps/UniCNS-UTF16-H + cmaps/UniCNS-UTF16-V + cmaps/UniCNS-UTF32-H + cmaps/UniCNS-UTF32-V + cmaps/UniGB-UCS2-H + cmaps/UniGB-UCS2-V + cmaps/UniGB-UTF8-H + cmaps/UniGB-UTF8-V + cmaps/UniGB-UTF16-H + cmaps/UniGB-UTF16-V + cmaps/UniGB-UTF32-H + cmaps/UniGB-UTF32-V + cmaps/UniHojo-UCS2-H + cmaps/UniHojo-UCS2-V + cmaps/UniHojo-UTF8-H + cmaps/UniHojo-UTF8-V + cmaps/UniHojo-UTF16-H + cmaps/UniHojo-UTF16-V + cmaps/UniHojo-UTF32-H + cmaps/UniHojo-UTF32-V + cmaps/UniJIS2004-UTF8-H + cmaps/UniJIS2004-UTF8-V + cmaps/UniJIS2004-UTF16-H + cmaps/UniJIS2004-UTF16-V + cmaps/UniJIS2004-UTF32-H + cmaps/UniJIS2004-UTF32-V + cmaps/UniJISPro-UCS2-HW-V + cmaps/UniJISPro-UCS2-V + cmaps/UniJISPro-UTF8-V + cmaps/UniJIS-UCS2-H + cmaps/UniJIS-UCS2-HW-H + cmaps/UniJIS-UCS2-HW-V + cmaps/UniJIS-UCS2-V + cmaps/UniJIS-UTF8-H + cmaps/UniJIS-UTF8-V + cmaps/UniJIS-UTF16-H + cmaps/UniJIS-UTF16-V + cmaps/UniJIS-UTF32-H + cmaps/UniJIS-UTF32-V + cmaps/UniJISX0213-UTF32-H + cmaps/UniJISX0213-UTF32-V + cmaps/UniJISX02132004-UTF32-H + cmaps/UniJISX02132004-UTF32-V + cmaps/UniKS-UCS2-H + cmaps/UniKS-UCS2-V + cmaps/UniKS-UTF8-H + cmaps/UniKS-UTF8-V + cmaps/UniKS-UTF16-H + cmaps/UniKS-UTF16-V + cmaps/UniKS-UTF32-H + cmaps/UniKS-UTF32-V + cmaps/V + cmaps/WP-Symbol + + diff --git a/Pdf4QtLib/sources/pdfaction.cpp b/Pdf4QtLib/sources/pdfaction.cpp index 4d7fef1..f5a499b 100644 --- a/Pdf4QtLib/sources/pdfaction.cpp +++ b/Pdf4QtLib/sources/pdfaction.cpp @@ -1,723 +1,723 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfaction.h" -#include "pdfdocument.h" -#include "pdfexception.h" -#include "pdfencoding.h" - -namespace pdf -{ - -PDFActionPtr PDFAction::parse(const PDFObjectStorage* storage, PDFObject object) -{ - std::set usedReferences; - return parseImpl(storage, qMove(object), usedReferences); -} - -void PDFAction::apply(const std::function& callback) -{ - callback(this); - - for (const PDFActionPtr& nextAction : m_nextActions) - { - nextAction->apply(callback); - } -} - -std::vector PDFAction::getActionList() const -{ - std::vector result; - fillActionList(result); - return result; -} - -PDFActionPtr PDFAction::parseImpl(const PDFObjectStorage* storage, PDFObject object, std::set& usedReferences) -{ - if (object.isReference()) - { - PDFObjectReference reference = object.getReference(); - if (usedReferences.count(reference)) - { - throw PDFException(PDFTranslationContext::tr("Circular dependence in actions found.")); - } - usedReferences.insert(reference); - object = storage->getObjectByReference(reference); - } - - if (object.isNull()) - { - return PDFActionPtr(); - } - - if (!object.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Invalid action.")); - } - - PDFDocumentDataLoaderDecorator loader(storage); - const PDFDictionary* dictionary = object.getDictionary(); - QByteArray name = loader.readNameFromDictionary(dictionary, "S"); - - if (name == "GoTo") // Goto action - { - PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D")); - PDFDestination structureDestination = PDFDestination::parse(storage, dictionary->get("SD")); - return PDFActionPtr(new PDFActionGoTo(qMove(destination), qMove(structureDestination))); - } - else if (name == "GoToR") - { - PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D")); - PDFDestination structureDestination = PDFDestination::parse(storage, dictionary->get("SD")); - PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); - return PDFActionPtr(new PDFActionGoToR(qMove(destination), qMove(structureDestination), qMove(fileSpecification), loader.readBooleanFromDictionary(dictionary, "NewWindow", false))); - } - else if (name == "GoToE") - { - PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D")); - PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); - return PDFActionPtr(new PDFActionGoToE(qMove(destination), qMove(fileSpecification), loader.readBooleanFromDictionary(dictionary, "NewWindow", false), storage->getObject(dictionary->get("T")))); - } - else if (name == "GoToDp") - { - PDFObjectReference documentPart = loader.readReferenceFromDictionary(dictionary, "Dp"); - return PDFActionPtr(new PDFActionGoToDp(documentPart)); - } - else if (name == "Launch") - { - PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); - const bool newWindow = loader.readBooleanFromDictionary(dictionary, "NewWindow", false); - PDFActionLaunch::Win win; - - const PDFObject& winDictionaryObject = storage->getObject(dictionary->get("Win")); - if (winDictionaryObject.isDictionary()) - { - const PDFDictionary* winDictionary = winDictionaryObject.getDictionary(); - win.file = loader.readStringFromDictionary(winDictionary, "F"); - win.directory = loader.readStringFromDictionary(winDictionary, "D"); - win.operation = loader.readStringFromDictionary(winDictionary, "O"); - win.parameters = loader.readStringFromDictionary(winDictionary, "P"); - } - - return PDFActionPtr(new PDFActionLaunch(qMove(fileSpecification), newWindow, qMove(win))); - } - else if (name == "Thread") - { - PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); - PDFActionThread::Thread thread; - PDFActionThread::Bead bead; - - const PDFObject& threadObject = dictionary->get("D"); - if (threadObject.isReference()) - { - thread = threadObject.getReference(); - } - else if (threadObject.isInt()) - { - thread = threadObject.getInteger(); - } - else if (threadObject.isString()) - { - thread = PDFEncoding::convertTextString(threadObject.getString()); - } - const PDFObject& beadObject = dictionary->get("B"); - if (beadObject.isReference()) - { - bead = beadObject.getReference(); - } - else if (beadObject.isInt()) - { - bead = beadObject.getInteger(); - } - - return PDFActionPtr(new PDFActionThread(qMove(fileSpecification), qMove(thread), qMove(bead))); - } - else if (name == "URI") - { - return PDFActionPtr(new PDFActionURI(loader.readStringFromDictionary(dictionary, "URI"), loader.readBooleanFromDictionary(dictionary, "IsMap", false))); - } - else if (name == "Sound") - { - const PDFReal volume = loader.readNumberFromDictionary(dictionary, "Volume", 1.0); - const bool isSynchronous = loader.readBooleanFromDictionary(dictionary, "Synchronous", false); - const bool isRepeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false); - const bool isMix = loader.readBooleanFromDictionary(dictionary, "Mix", false); - return PDFActionPtr(new PDFActionSound(PDFSound::parse(storage, dictionary->get("Sound")), volume, isSynchronous, isRepeat, isMix)); - } - else if (name == "Movie") - { - constexpr const std::array, 4> operations = { - std::pair{ "Play", PDFActionMovie::Operation::Play }, - std::pair{ "Stop", PDFActionMovie::Operation::Stop }, - std::pair{ "Pause", PDFActionMovie::Operation::Pause }, - std::pair{ "Resume", PDFActionMovie::Operation::Resume } - }; - - // Jakub Melka: parse the movie action - PDFObject annotationObject = dictionary->get("Annotation"); - PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference(); - QString title = loader.readTextStringFromDictionary(dictionary, "T", QString()); - PDFActionMovie::Operation operation = loader.readEnumByName(dictionary->get("Operation"), operations.cbegin(), operations.cend(), PDFActionMovie::Operation::Play); - - return PDFActionPtr(new PDFActionMovie(annotation, qMove(title), operation)); - } - else if (name == "Hide") - { - std::vector annotations; - std::vector fieldNames; - - const PDFObject& object = dictionary->get("T"); - if (object.isReference()) - { - annotations = { object.getReference() }; - } - else if (object.isString()) - { - fieldNames = { loader.readTextString(object, QString()) }; - } - else if (object.isArray()) - { - const PDFArray* items = object.getArray(); - for (size_t i = 0; i < items->getCount(); ++i) - { - const PDFObject& itemObject = items->getItem(i); - if (itemObject.isReference()) - { - annotations.push_back(itemObject.getReference()); - } - else if (itemObject.isString()) - { - fieldNames.push_back(loader.readTextString(itemObject, QString())); - } - } - } - - const bool hide = loader.readBooleanFromDictionary(dictionary, "H", true); - return PDFActionPtr(new PDFActionHide(qMove(annotations), qMove(fieldNames), hide)); - } - else if (name == "Named") - { - constexpr const std::array, 4> types = { - std::pair{ "NextPage", PDFActionNamed::NamedActionType::NextPage }, - std::pair{ "PrevPage", PDFActionNamed::NamedActionType::PrevPage }, - std::pair{ "FirstPage", PDFActionNamed::NamedActionType::FirstPage }, - std::pair{ "LastPage", PDFActionNamed::NamedActionType::LastPage } - }; - - QByteArray name = loader.readNameFromDictionary(dictionary, "N"); - PDFActionNamed::NamedActionType actionType = loader.readEnumByName(dictionary->get("N"), types.cbegin(), types.cend(), PDFActionNamed::NamedActionType::Custom); - return PDFActionPtr(new PDFActionNamed(actionType, qMove(name))); - } - else if (name == "SetOCGState") - { - const bool isRadioButtonsPreserved = loader.readBooleanFromDictionary(dictionary, "PreserveRB", true); - PDFActionSetOCGState::StateChangeItems items; - - PDFObject stateArrayObject = storage->getObject(dictionary->get("State")); - if (stateArrayObject.isArray()) - { - constexpr const std::array, 3> types = { - std::pair{ "ON", PDFActionSetOCGState::SwitchType::ON }, - std::pair{ "OFF", PDFActionSetOCGState::SwitchType::OFF }, - std::pair{ "Toggle", PDFActionSetOCGState::SwitchType::Toggle } - }; - - PDFActionSetOCGState::SwitchType switchType = PDFActionSetOCGState::SwitchType::ON; - const PDFArray* stateArray = stateArrayObject.getArray(); - items.reserve(stateArray->getCount()); - for (size_t i = 0; i < stateArray->getCount(); ++i) - { - const PDFObject& item = stateArray->getItem(i); - if (item.isName()) - { - switchType = loader.readEnumByName(item, types.cbegin(), types.cend(), PDFActionSetOCGState::SwitchType::ON); - } - else if (item.isReference()) - { - items.emplace_back(switchType, item.getReference()); - } - } - } - - return PDFActionPtr(new PDFActionSetOCGState(qMove(items), isRadioButtonsPreserved)); - } - else if (name == "Rendition") - { - PDFObject annotationObject = dictionary->get("AN"); - std::optional rendition; - PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference(); - PDFActionRendition::Operation operation = static_cast(loader.readIntegerFromDictionary(dictionary, "OP", 4)); - QString javascript; - - if (dictionary->hasKey("R")) - { - rendition = PDFRendition::parse(storage, dictionary->get("R")); - } - PDFObject javascriptObject = storage->getObject(dictionary->get("JS")); - if (javascriptObject.isString()) - { - javascript = PDFEncoding::convertTextString(javascriptObject.getString()); - } - else if (javascriptObject.isStream()) - { - javascript = PDFEncoding::convertTextString(storage->getDecodedStream(javascriptObject.getStream())); - } - - return PDFActionPtr(new PDFActionRendition(qMove(rendition), annotation, operation, qMove(javascript))); - } - else if (name == "Trans") - { - return PDFActionPtr(new PDFActionTransition(PDFPageTransition::parse(storage, dictionary->get("Trans")))); - } - else if (name == "GoTo3DView") - { - return PDFActionPtr(new PDFActionGoTo3DView(dictionary->get("TA"), dictionary->get("V"))); - } - else if (name == "JavaScript") - { - QByteArray textJavaScript; - const PDFObject& javaScriptObject = storage->getObject(dictionary->get("JS")); - if (javaScriptObject.isString()) - { - textJavaScript = javaScriptObject.getString(); - } - else if (javaScriptObject.isStream()) - { - textJavaScript = storage->getDecodedStream(javaScriptObject.getStream()); - } - return PDFActionPtr(new PDFActionJavaScript(PDFEncoding::convertTextString(textJavaScript))); - } - else if (name == "RichMediaExecute") - { - PDFObjectReference richMediaAnnotation = loader.readReferenceFromDictionary(dictionary, "TA"); - PDFObjectReference richMediaInstance = loader.readReferenceFromDictionary(dictionary, "TI"); - - QString command; - PDFObject arguments; - - if (const PDFDictionary* commandDictionary = storage->getDictionaryFromObject(dictionary->get("CMD"))) - { - command = loader.readTextStringFromDictionary(commandDictionary, "C", QString()); - arguments = commandDictionary->get("A"); - } - - return PDFActionPtr(new PDFActionRichMediaExecute(richMediaAnnotation, richMediaInstance, qMove(command), qMove(arguments))); - } - else if (name == "SubmitForm") - { - PDFFormAction::FieldScope fieldScope = PDFFormAction::FieldScope::All; - PDFFileSpecification url = PDFFileSpecification::parse(storage, dictionary->get("F")); - PDFFormAction::FieldList fieldList = PDFFormAction::parseFieldList(storage, dictionary->get("Fields"), fieldScope); - PDFActionSubmitForm::SubmitFlags flags = static_cast(loader.readIntegerFromDictionary(dictionary, "Flags", 0)); - QByteArray charset = loader.readStringFromDictionary(dictionary, "CharSet"); - - if (fieldScope == PDFFormAction::FieldScope::Include && - flags.testFlag(PDFActionSubmitForm::IncludeExclude)) - { - fieldScope = PDFFormAction::FieldScope::Exclude; - } - - return PDFActionPtr(new PDFActionSubmitForm(fieldScope, qMove(fieldList), qMove(url), qMove(charset), flags)); - } - else if (name == "ResetForm") - { - PDFFormAction::FieldScope fieldScope = PDFFormAction::FieldScope::All; - PDFFormAction::FieldList fieldList = PDFFormAction::parseFieldList(storage, dictionary->get("Fields"), fieldScope); - PDFActionResetForm::ResetFlags flags = static_cast(loader.readIntegerFromDictionary(dictionary, "Flags", 0)); - - if (fieldScope == PDFFormAction::FieldScope::Include && - flags.testFlag(PDFActionResetForm::IncludeExclude)) - { - fieldScope = PDFFormAction::FieldScope::Exclude; - } - - return PDFActionPtr(new PDFActionResetForm(fieldScope, qMove(fieldList), flags)); - } - else if (name == "ImportData") - { - PDFFileSpecification file = PDFFileSpecification::parse(storage, dictionary->get("F")); - return PDFActionPtr(new PDFActionImportDataForm(qMove(file))); - } - - return PDFActionPtr(); -} - -void PDFAction::fillActionList(std::vector& actionList) const -{ - actionList.push_back(this); - - for (const PDFActionPtr& actionPointer : m_nextActions) - { - if (actionPointer) - { - actionPointer->fillActionList(actionList); - } - } -} - -PDFDestination PDFDestination::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFDestination result; - object = storage->getObject(object); - - if (object.isName() || object.isString()) - { - QByteArray name = object.getString(); - result.m_destinationType = DestinationType::Named; - result.m_name = name; - } - else if (object.isArray()) - { - const PDFArray* array = object.getArray(); - if (array->getCount() < 2) - { - return result; - } - - PDFDocumentDataLoaderDecorator loader(storage); - - // First parse page number/page index - PDFObject pageNumberObject = array->getItem(0); - if (pageNumberObject.isReference()) - { - result.m_pageReference = pageNumberObject.getReference(); - } - else if (pageNumberObject.isInt()) - { - result.m_pageIndex = pageNumberObject.getInteger(); - } - else - { - return result; - } - - QByteArray name = loader.readName(array->getItem(1)); - - size_t currentIndex = 2; - auto readNumber = [&]() - { - if (currentIndex < array->getCount()) - { - return loader.readNumber(array->getItem(currentIndex++), 0.0); - } - return 0.0; - }; - - if (name == "XYZ") - { - result.m_destinationType = DestinationType::XYZ; - result.m_left = readNumber(); - result.m_top = readNumber(); - result.m_zoom = readNumber(); - } - else if (name == "Fit") - { - result.m_destinationType = DestinationType::Fit; - } - else if (name == "FitH") - { - result.m_destinationType = DestinationType::FitH; - result.m_top = readNumber(); - } - else if (name == "FitV") - { - result.m_destinationType = DestinationType::FitV; - result.m_left = readNumber(); - } - else if (name == "FitR") - { - result.m_destinationType = DestinationType::FitR; - result.m_left = readNumber(); - result.m_bottom = readNumber(); - result.m_right = readNumber(); - result.m_top = readNumber(); - } - else if (name == "FitB") - { - result.m_destinationType = DestinationType::FitB; - } - else if (name == "FitBH") - { - result.m_destinationType = DestinationType::FitBH; - result.m_top = readNumber(); - } - else if (name == "FitBV") - { - result.m_destinationType = DestinationType::FitBV; - result.m_left = readNumber(); - } - else - { - return result; - } - } - - return result; -} - -void PDFDestination::setDestinationType(DestinationType destinationType) -{ - m_destinationType = destinationType; -} - -void PDFDestination::setLeft(PDFReal left) -{ - m_left = left; -} - -void PDFDestination::setTop(PDFReal top) -{ - m_top = top; -} - -void PDFDestination::setRight(PDFReal right) -{ - m_right = right; -} - -void PDFDestination::setBottom(PDFReal bottom) -{ - m_bottom = bottom; -} - -void PDFDestination::setZoom(PDFReal zoom) -{ - m_zoom = zoom; -} - -void PDFDestination::setName(const QByteArray& name) -{ - m_name = name; -} - -void PDFDestination::setPageReference(PDFObjectReference pageReference) -{ - m_pageReference = pageReference; -} - -void PDFDestination::setPageIndex(PDFInteger pageIndex) -{ - m_pageIndex = pageIndex; -} - -PDFDestination PDFDestination::createXYZ(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal zoom) -{ - PDFDestination result; - result.setDestinationType(DestinationType::XYZ); - result.setPageReference(page); - result.setLeft(left); - result.setTop(top); - result.setZoom(zoom); - return result; -} - -PDFDestination PDFDestination::createFit(PDFObjectReference page) -{ - PDFDestination result; - result.setDestinationType(DestinationType::Fit); - result.setPageReference(page); - return result; -} - -PDFDestination PDFDestination::createFitH(PDFObjectReference page, PDFReal top) -{ - PDFDestination result; - result.setDestinationType(DestinationType::FitH); - result.setPageReference(page); - result.setTop(top); - return result; -} - -PDFDestination PDFDestination::createFitV(PDFObjectReference page, PDFReal left) -{ - PDFDestination result; - result.setDestinationType(DestinationType::FitV); - result.setPageReference(page); - result.setLeft(left); - return result; -} - -PDFDestination PDFDestination::createFitR(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal right, PDFReal bottom) -{ - PDFDestination result; - result.setDestinationType(DestinationType::FitR); - result.setPageReference(page); - result.setLeft(left); - result.setTop(top); - result.setRight(right); - result.setBottom(bottom); - return result; -} - -PDFDestination PDFDestination::createFitB(PDFObjectReference page) -{ - PDFDestination result; - result.setDestinationType(DestinationType::FitB); - result.setPageReference(page); - return result; -} - -PDFDestination PDFDestination::createFitBH(PDFObjectReference page, PDFReal top) -{ - PDFDestination result; - result.setDestinationType(DestinationType::FitBH); - result.setPageReference(page); - result.setTop(top); - return result; -} - -PDFDestination PDFDestination::createFitBV(PDFObjectReference page, PDFReal left) -{ - PDFDestination result; - result.setDestinationType(DestinationType::FitBV); - result.setPageReference(page); - result.setLeft(left); - return result; -} - -PDFDestination PDFDestination::createNamed(const QByteArray& name) -{ - PDFDestination result; - result.setDestinationType(DestinationType::Named); - result.setName(name); - return result; -} - -bool PDFDestination::hasLeft() const -{ - switch (m_destinationType) - { - case DestinationType::XYZ: - case DestinationType::FitV: - case DestinationType::FitBV: - case DestinationType::FitR: - return true; - - default: - break; - } - - return false; -} - -bool PDFDestination::hasTop() const -{ - switch (m_destinationType) - { - case DestinationType::XYZ: - case DestinationType::FitH: - case DestinationType::FitBH: - case DestinationType::FitR: - return true; - - default: - break; - } - - return false; -} - -bool PDFDestination::hasRight() const -{ - switch (m_destinationType) - { - case DestinationType::FitR: - return true; - - default: - break; - } - - return false; -} - -bool PDFDestination::hasBottom() const -{ - switch (m_destinationType) - { - case DestinationType::FitR: - return true; - - default: - break; - } - - return false; -} - -bool PDFDestination::hasZoom() const -{ - switch (m_destinationType) - { - case DestinationType::XYZ: - return true; - - default: - break; - } - - return false; -} - -PDFFormAction::FieldList PDFFormAction::parseFieldList(const PDFObjectStorage* storage, PDFObject object, FieldScope& fieldScope) -{ - FieldList result; - - object = storage->getObject(object); - if (object.isArray()) - { - PDFDocumentDataLoaderDecorator loader(storage); - - const PDFArray* fieldsArray = object.getArray(); - for (size_t i = 0, count = fieldsArray->getCount(); i < count; ++i) - { - PDFObject fieldObject = fieldsArray->getItem(i); - if (fieldObject.isReference()) - { - result.fieldReferences.push_back(fieldObject.getReference()); - } - else if (fieldObject.isString()) - { - result.qualifiedNames.push_back(loader.readTextString(fieldObject, QString())); - } - } - } - - if (!result.isEmpty()) - { - fieldScope = FieldScope::Include; - } - - return result; -} - -QString PDFActionURI::getURIString() const -{ - return QString::fromUtf8(m_URI); -} - -void PDFActionGoTo::setDestination(const PDFDestination& destination) -{ - m_destination = destination; -} - -void PDFActionGoTo::setStructureDestination(const PDFDestination& structureDestination) -{ - m_structureDestination = structureDestination; -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfaction.h" +#include "pdfdocument.h" +#include "pdfexception.h" +#include "pdfencoding.h" + +namespace pdf +{ + +PDFActionPtr PDFAction::parse(const PDFObjectStorage* storage, PDFObject object) +{ + std::set usedReferences; + return parseImpl(storage, qMove(object), usedReferences); +} + +void PDFAction::apply(const std::function& callback) +{ + callback(this); + + for (const PDFActionPtr& nextAction : m_nextActions) + { + nextAction->apply(callback); + } +} + +std::vector PDFAction::getActionList() const +{ + std::vector result; + fillActionList(result); + return result; +} + +PDFActionPtr PDFAction::parseImpl(const PDFObjectStorage* storage, PDFObject object, std::set& usedReferences) +{ + if (object.isReference()) + { + PDFObjectReference reference = object.getReference(); + if (usedReferences.count(reference)) + { + throw PDFException(PDFTranslationContext::tr("Circular dependence in actions found.")); + } + usedReferences.insert(reference); + object = storage->getObjectByReference(reference); + } + + if (object.isNull()) + { + return PDFActionPtr(); + } + + if (!object.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Invalid action.")); + } + + PDFDocumentDataLoaderDecorator loader(storage); + const PDFDictionary* dictionary = object.getDictionary(); + QByteArray name = loader.readNameFromDictionary(dictionary, "S"); + + if (name == "GoTo") // Goto action + { + PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D")); + PDFDestination structureDestination = PDFDestination::parse(storage, dictionary->get("SD")); + return PDFActionPtr(new PDFActionGoTo(qMove(destination), qMove(structureDestination))); + } + else if (name == "GoToR") + { + PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D")); + PDFDestination structureDestination = PDFDestination::parse(storage, dictionary->get("SD")); + PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); + return PDFActionPtr(new PDFActionGoToR(qMove(destination), qMove(structureDestination), qMove(fileSpecification), loader.readBooleanFromDictionary(dictionary, "NewWindow", false))); + } + else if (name == "GoToE") + { + PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D")); + PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); + return PDFActionPtr(new PDFActionGoToE(qMove(destination), qMove(fileSpecification), loader.readBooleanFromDictionary(dictionary, "NewWindow", false), storage->getObject(dictionary->get("T")))); + } + else if (name == "GoToDp") + { + PDFObjectReference documentPart = loader.readReferenceFromDictionary(dictionary, "Dp"); + return PDFActionPtr(new PDFActionGoToDp(documentPart)); + } + else if (name == "Launch") + { + PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); + const bool newWindow = loader.readBooleanFromDictionary(dictionary, "NewWindow", false); + PDFActionLaunch::Win win; + + const PDFObject& winDictionaryObject = storage->getObject(dictionary->get("Win")); + if (winDictionaryObject.isDictionary()) + { + const PDFDictionary* winDictionary = winDictionaryObject.getDictionary(); + win.file = loader.readStringFromDictionary(winDictionary, "F"); + win.directory = loader.readStringFromDictionary(winDictionary, "D"); + win.operation = loader.readStringFromDictionary(winDictionary, "O"); + win.parameters = loader.readStringFromDictionary(winDictionary, "P"); + } + + return PDFActionPtr(new PDFActionLaunch(qMove(fileSpecification), newWindow, qMove(win))); + } + else if (name == "Thread") + { + PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F")); + PDFActionThread::Thread thread; + PDFActionThread::Bead bead; + + const PDFObject& threadObject = dictionary->get("D"); + if (threadObject.isReference()) + { + thread = threadObject.getReference(); + } + else if (threadObject.isInt()) + { + thread = threadObject.getInteger(); + } + else if (threadObject.isString()) + { + thread = PDFEncoding::convertTextString(threadObject.getString()); + } + const PDFObject& beadObject = dictionary->get("B"); + if (beadObject.isReference()) + { + bead = beadObject.getReference(); + } + else if (beadObject.isInt()) + { + bead = beadObject.getInteger(); + } + + return PDFActionPtr(new PDFActionThread(qMove(fileSpecification), qMove(thread), qMove(bead))); + } + else if (name == "URI") + { + return PDFActionPtr(new PDFActionURI(loader.readStringFromDictionary(dictionary, "URI"), loader.readBooleanFromDictionary(dictionary, "IsMap", false))); + } + else if (name == "Sound") + { + const PDFReal volume = loader.readNumberFromDictionary(dictionary, "Volume", 1.0); + const bool isSynchronous = loader.readBooleanFromDictionary(dictionary, "Synchronous", false); + const bool isRepeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false); + const bool isMix = loader.readBooleanFromDictionary(dictionary, "Mix", false); + return PDFActionPtr(new PDFActionSound(PDFSound::parse(storage, dictionary->get("Sound")), volume, isSynchronous, isRepeat, isMix)); + } + else if (name == "Movie") + { + constexpr const std::array, 4> operations = { + std::pair{ "Play", PDFActionMovie::Operation::Play }, + std::pair{ "Stop", PDFActionMovie::Operation::Stop }, + std::pair{ "Pause", PDFActionMovie::Operation::Pause }, + std::pair{ "Resume", PDFActionMovie::Operation::Resume } + }; + + // Jakub Melka: parse the movie action + PDFObject annotationObject = dictionary->get("Annotation"); + PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference(); + QString title = loader.readTextStringFromDictionary(dictionary, "T", QString()); + PDFActionMovie::Operation operation = loader.readEnumByName(dictionary->get("Operation"), operations.cbegin(), operations.cend(), PDFActionMovie::Operation::Play); + + return PDFActionPtr(new PDFActionMovie(annotation, qMove(title), operation)); + } + else if (name == "Hide") + { + std::vector annotations; + std::vector fieldNames; + + const PDFObject& object = dictionary->get("T"); + if (object.isReference()) + { + annotations = { object.getReference() }; + } + else if (object.isString()) + { + fieldNames = { loader.readTextString(object, QString()) }; + } + else if (object.isArray()) + { + const PDFArray* items = object.getArray(); + for (size_t i = 0; i < items->getCount(); ++i) + { + const PDFObject& itemObject = items->getItem(i); + if (itemObject.isReference()) + { + annotations.push_back(itemObject.getReference()); + } + else if (itemObject.isString()) + { + fieldNames.push_back(loader.readTextString(itemObject, QString())); + } + } + } + + const bool hide = loader.readBooleanFromDictionary(dictionary, "H", true); + return PDFActionPtr(new PDFActionHide(qMove(annotations), qMove(fieldNames), hide)); + } + else if (name == "Named") + { + constexpr const std::array, 4> types = { + std::pair{ "NextPage", PDFActionNamed::NamedActionType::NextPage }, + std::pair{ "PrevPage", PDFActionNamed::NamedActionType::PrevPage }, + std::pair{ "FirstPage", PDFActionNamed::NamedActionType::FirstPage }, + std::pair{ "LastPage", PDFActionNamed::NamedActionType::LastPage } + }; + + QByteArray name = loader.readNameFromDictionary(dictionary, "N"); + PDFActionNamed::NamedActionType actionType = loader.readEnumByName(dictionary->get("N"), types.cbegin(), types.cend(), PDFActionNamed::NamedActionType::Custom); + return PDFActionPtr(new PDFActionNamed(actionType, qMove(name))); + } + else if (name == "SetOCGState") + { + const bool isRadioButtonsPreserved = loader.readBooleanFromDictionary(dictionary, "PreserveRB", true); + PDFActionSetOCGState::StateChangeItems items; + + PDFObject stateArrayObject = storage->getObject(dictionary->get("State")); + if (stateArrayObject.isArray()) + { + constexpr const std::array, 3> types = { + std::pair{ "ON", PDFActionSetOCGState::SwitchType::ON }, + std::pair{ "OFF", PDFActionSetOCGState::SwitchType::OFF }, + std::pair{ "Toggle", PDFActionSetOCGState::SwitchType::Toggle } + }; + + PDFActionSetOCGState::SwitchType switchType = PDFActionSetOCGState::SwitchType::ON; + const PDFArray* stateArray = stateArrayObject.getArray(); + items.reserve(stateArray->getCount()); + for (size_t i = 0; i < stateArray->getCount(); ++i) + { + const PDFObject& item = stateArray->getItem(i); + if (item.isName()) + { + switchType = loader.readEnumByName(item, types.cbegin(), types.cend(), PDFActionSetOCGState::SwitchType::ON); + } + else if (item.isReference()) + { + items.emplace_back(switchType, item.getReference()); + } + } + } + + return PDFActionPtr(new PDFActionSetOCGState(qMove(items), isRadioButtonsPreserved)); + } + else if (name == "Rendition") + { + PDFObject annotationObject = dictionary->get("AN"); + std::optional rendition; + PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference(); + PDFActionRendition::Operation operation = static_cast(loader.readIntegerFromDictionary(dictionary, "OP", 4)); + QString javascript; + + if (dictionary->hasKey("R")) + { + rendition = PDFRendition::parse(storage, dictionary->get("R")); + } + PDFObject javascriptObject = storage->getObject(dictionary->get("JS")); + if (javascriptObject.isString()) + { + javascript = PDFEncoding::convertTextString(javascriptObject.getString()); + } + else if (javascriptObject.isStream()) + { + javascript = PDFEncoding::convertTextString(storage->getDecodedStream(javascriptObject.getStream())); + } + + return PDFActionPtr(new PDFActionRendition(qMove(rendition), annotation, operation, qMove(javascript))); + } + else if (name == "Trans") + { + return PDFActionPtr(new PDFActionTransition(PDFPageTransition::parse(storage, dictionary->get("Trans")))); + } + else if (name == "GoTo3DView") + { + return PDFActionPtr(new PDFActionGoTo3DView(dictionary->get("TA"), dictionary->get("V"))); + } + else if (name == "JavaScript") + { + QByteArray textJavaScript; + const PDFObject& javaScriptObject = storage->getObject(dictionary->get("JS")); + if (javaScriptObject.isString()) + { + textJavaScript = javaScriptObject.getString(); + } + else if (javaScriptObject.isStream()) + { + textJavaScript = storage->getDecodedStream(javaScriptObject.getStream()); + } + return PDFActionPtr(new PDFActionJavaScript(PDFEncoding::convertTextString(textJavaScript))); + } + else if (name == "RichMediaExecute") + { + PDFObjectReference richMediaAnnotation = loader.readReferenceFromDictionary(dictionary, "TA"); + PDFObjectReference richMediaInstance = loader.readReferenceFromDictionary(dictionary, "TI"); + + QString command; + PDFObject arguments; + + if (const PDFDictionary* commandDictionary = storage->getDictionaryFromObject(dictionary->get("CMD"))) + { + command = loader.readTextStringFromDictionary(commandDictionary, "C", QString()); + arguments = commandDictionary->get("A"); + } + + return PDFActionPtr(new PDFActionRichMediaExecute(richMediaAnnotation, richMediaInstance, qMove(command), qMove(arguments))); + } + else if (name == "SubmitForm") + { + PDFFormAction::FieldScope fieldScope = PDFFormAction::FieldScope::All; + PDFFileSpecification url = PDFFileSpecification::parse(storage, dictionary->get("F")); + PDFFormAction::FieldList fieldList = PDFFormAction::parseFieldList(storage, dictionary->get("Fields"), fieldScope); + PDFActionSubmitForm::SubmitFlags flags = static_cast(loader.readIntegerFromDictionary(dictionary, "Flags", 0)); + QByteArray charset = loader.readStringFromDictionary(dictionary, "CharSet"); + + if (fieldScope == PDFFormAction::FieldScope::Include && + flags.testFlag(PDFActionSubmitForm::IncludeExclude)) + { + fieldScope = PDFFormAction::FieldScope::Exclude; + } + + return PDFActionPtr(new PDFActionSubmitForm(fieldScope, qMove(fieldList), qMove(url), qMove(charset), flags)); + } + else if (name == "ResetForm") + { + PDFFormAction::FieldScope fieldScope = PDFFormAction::FieldScope::All; + PDFFormAction::FieldList fieldList = PDFFormAction::parseFieldList(storage, dictionary->get("Fields"), fieldScope); + PDFActionResetForm::ResetFlags flags = static_cast(loader.readIntegerFromDictionary(dictionary, "Flags", 0)); + + if (fieldScope == PDFFormAction::FieldScope::Include && + flags.testFlag(PDFActionResetForm::IncludeExclude)) + { + fieldScope = PDFFormAction::FieldScope::Exclude; + } + + return PDFActionPtr(new PDFActionResetForm(fieldScope, qMove(fieldList), flags)); + } + else if (name == "ImportData") + { + PDFFileSpecification file = PDFFileSpecification::parse(storage, dictionary->get("F")); + return PDFActionPtr(new PDFActionImportDataForm(qMove(file))); + } + + return PDFActionPtr(); +} + +void PDFAction::fillActionList(std::vector& actionList) const +{ + actionList.push_back(this); + + for (const PDFActionPtr& actionPointer : m_nextActions) + { + if (actionPointer) + { + actionPointer->fillActionList(actionList); + } + } +} + +PDFDestination PDFDestination::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFDestination result; + object = storage->getObject(object); + + if (object.isName() || object.isString()) + { + QByteArray name = object.getString(); + result.m_destinationType = DestinationType::Named; + result.m_name = name; + } + else if (object.isArray()) + { + const PDFArray* array = object.getArray(); + if (array->getCount() < 2) + { + return result; + } + + PDFDocumentDataLoaderDecorator loader(storage); + + // First parse page number/page index + PDFObject pageNumberObject = array->getItem(0); + if (pageNumberObject.isReference()) + { + result.m_pageReference = pageNumberObject.getReference(); + } + else if (pageNumberObject.isInt()) + { + result.m_pageIndex = pageNumberObject.getInteger(); + } + else + { + return result; + } + + QByteArray name = loader.readName(array->getItem(1)); + + size_t currentIndex = 2; + auto readNumber = [&]() + { + if (currentIndex < array->getCount()) + { + return loader.readNumber(array->getItem(currentIndex++), 0.0); + } + return 0.0; + }; + + if (name == "XYZ") + { + result.m_destinationType = DestinationType::XYZ; + result.m_left = readNumber(); + result.m_top = readNumber(); + result.m_zoom = readNumber(); + } + else if (name == "Fit") + { + result.m_destinationType = DestinationType::Fit; + } + else if (name == "FitH") + { + result.m_destinationType = DestinationType::FitH; + result.m_top = readNumber(); + } + else if (name == "FitV") + { + result.m_destinationType = DestinationType::FitV; + result.m_left = readNumber(); + } + else if (name == "FitR") + { + result.m_destinationType = DestinationType::FitR; + result.m_left = readNumber(); + result.m_bottom = readNumber(); + result.m_right = readNumber(); + result.m_top = readNumber(); + } + else if (name == "FitB") + { + result.m_destinationType = DestinationType::FitB; + } + else if (name == "FitBH") + { + result.m_destinationType = DestinationType::FitBH; + result.m_top = readNumber(); + } + else if (name == "FitBV") + { + result.m_destinationType = DestinationType::FitBV; + result.m_left = readNumber(); + } + else + { + return result; + } + } + + return result; +} + +void PDFDestination::setDestinationType(DestinationType destinationType) +{ + m_destinationType = destinationType; +} + +void PDFDestination::setLeft(PDFReal left) +{ + m_left = left; +} + +void PDFDestination::setTop(PDFReal top) +{ + m_top = top; +} + +void PDFDestination::setRight(PDFReal right) +{ + m_right = right; +} + +void PDFDestination::setBottom(PDFReal bottom) +{ + m_bottom = bottom; +} + +void PDFDestination::setZoom(PDFReal zoom) +{ + m_zoom = zoom; +} + +void PDFDestination::setName(const QByteArray& name) +{ + m_name = name; +} + +void PDFDestination::setPageReference(PDFObjectReference pageReference) +{ + m_pageReference = pageReference; +} + +void PDFDestination::setPageIndex(PDFInteger pageIndex) +{ + m_pageIndex = pageIndex; +} + +PDFDestination PDFDestination::createXYZ(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal zoom) +{ + PDFDestination result; + result.setDestinationType(DestinationType::XYZ); + result.setPageReference(page); + result.setLeft(left); + result.setTop(top); + result.setZoom(zoom); + return result; +} + +PDFDestination PDFDestination::createFit(PDFObjectReference page) +{ + PDFDestination result; + result.setDestinationType(DestinationType::Fit); + result.setPageReference(page); + return result; +} + +PDFDestination PDFDestination::createFitH(PDFObjectReference page, PDFReal top) +{ + PDFDestination result; + result.setDestinationType(DestinationType::FitH); + result.setPageReference(page); + result.setTop(top); + return result; +} + +PDFDestination PDFDestination::createFitV(PDFObjectReference page, PDFReal left) +{ + PDFDestination result; + result.setDestinationType(DestinationType::FitV); + result.setPageReference(page); + result.setLeft(left); + return result; +} + +PDFDestination PDFDestination::createFitR(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal right, PDFReal bottom) +{ + PDFDestination result; + result.setDestinationType(DestinationType::FitR); + result.setPageReference(page); + result.setLeft(left); + result.setTop(top); + result.setRight(right); + result.setBottom(bottom); + return result; +} + +PDFDestination PDFDestination::createFitB(PDFObjectReference page) +{ + PDFDestination result; + result.setDestinationType(DestinationType::FitB); + result.setPageReference(page); + return result; +} + +PDFDestination PDFDestination::createFitBH(PDFObjectReference page, PDFReal top) +{ + PDFDestination result; + result.setDestinationType(DestinationType::FitBH); + result.setPageReference(page); + result.setTop(top); + return result; +} + +PDFDestination PDFDestination::createFitBV(PDFObjectReference page, PDFReal left) +{ + PDFDestination result; + result.setDestinationType(DestinationType::FitBV); + result.setPageReference(page); + result.setLeft(left); + return result; +} + +PDFDestination PDFDestination::createNamed(const QByteArray& name) +{ + PDFDestination result; + result.setDestinationType(DestinationType::Named); + result.setName(name); + return result; +} + +bool PDFDestination::hasLeft() const +{ + switch (m_destinationType) + { + case DestinationType::XYZ: + case DestinationType::FitV: + case DestinationType::FitBV: + case DestinationType::FitR: + return true; + + default: + break; + } + + return false; +} + +bool PDFDestination::hasTop() const +{ + switch (m_destinationType) + { + case DestinationType::XYZ: + case DestinationType::FitH: + case DestinationType::FitBH: + case DestinationType::FitR: + return true; + + default: + break; + } + + return false; +} + +bool PDFDestination::hasRight() const +{ + switch (m_destinationType) + { + case DestinationType::FitR: + return true; + + default: + break; + } + + return false; +} + +bool PDFDestination::hasBottom() const +{ + switch (m_destinationType) + { + case DestinationType::FitR: + return true; + + default: + break; + } + + return false; +} + +bool PDFDestination::hasZoom() const +{ + switch (m_destinationType) + { + case DestinationType::XYZ: + return true; + + default: + break; + } + + return false; +} + +PDFFormAction::FieldList PDFFormAction::parseFieldList(const PDFObjectStorage* storage, PDFObject object, FieldScope& fieldScope) +{ + FieldList result; + + object = storage->getObject(object); + if (object.isArray()) + { + PDFDocumentDataLoaderDecorator loader(storage); + + const PDFArray* fieldsArray = object.getArray(); + for (size_t i = 0, count = fieldsArray->getCount(); i < count; ++i) + { + PDFObject fieldObject = fieldsArray->getItem(i); + if (fieldObject.isReference()) + { + result.fieldReferences.push_back(fieldObject.getReference()); + } + else if (fieldObject.isString()) + { + result.qualifiedNames.push_back(loader.readTextString(fieldObject, QString())); + } + } + } + + if (!result.isEmpty()) + { + fieldScope = FieldScope::Include; + } + + return result; +} + +QString PDFActionURI::getURIString() const +{ + return QString::fromUtf8(m_URI); +} + +void PDFActionGoTo::setDestination(const PDFDestination& destination) +{ + m_destination = destination; +} + +void PDFActionGoTo::setStructureDestination(const PDFDestination& structureDestination) +{ + m_structureDestination = structureDestination; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfaction.h b/Pdf4QtLib/sources/pdfaction.h index 25841f7..88faaaf 100644 --- a/Pdf4QtLib/sources/pdfaction.h +++ b/Pdf4QtLib/sources/pdfaction.h @@ -1,744 +1,744 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFACTION_H -#define PDFACTION_H - -#include "pdfglobal.h" -#include "pdfobject.h" -#include "pdffile.h" -#include "pdfmultimedia.h" -#include "pdfpagetransition.h" - -#include - -#include -#include -#include - -namespace pdf -{ -class PDFAction; -class PDFDocument; -class PDFObjectStorage; - -enum class ActionType -{ - GoTo, - GoToR, - GoToE, - GoToDp, - Launch, - Thread, - URI, - Sound, - Movie, - Hide, - Named, - SetOCGState, - Rendition, - Transition, - GoTo3DView, - JavaScript, - SubmitForm, - ResetForm, - ImportDataForm, - RichMediaExecute -}; - -enum class DestinationType -{ - Invalid, - Named, - XYZ, - Fit, - FitH, - FitV, - FitR, - FitB, - FitBH, - FitBV -}; - -/// Destination to the specific location of the document. Destination can also be 'Named' type, -/// in this case, destination in name tree is found and used. Important note: because structure -/// destination has almost exactly same syntax as page destination, it should be checked, -/// if indirect reference returned by function \p getPageReference references really page, -/// or some structure element. -class PDF4QTLIBSHARED_EXPORT PDFDestination -{ -public: - explicit inline PDFDestination() = default; - - DestinationType getDestinationType() const { return m_destinationType; } - PDFReal getLeft() const { return m_left; } - PDFReal getTop() const { return m_top; } - PDFReal getRight() const { return m_right; } - PDFReal getBottom() const { return m_bottom; } - PDFReal getZoom() const { return m_zoom; } - const QByteArray& getName() const { return m_name; } - PDFObjectReference getPageReference() const { return m_pageReference; } - PDFInteger getPageIndex() const { return m_pageIndex; } - - /// Parses the destination from the object. If object contains invalid destination, - /// then empty destination is returned. If object is empty, empty destination is returned. - /// \param storage Object storage - /// \param object Destination object - static PDFDestination parse(const PDFObjectStorage* storage, PDFObject object); - - void setDestinationType(DestinationType destinationType); - void setLeft(PDFReal left); - void setTop(PDFReal top); - void setRight(PDFReal right); - void setBottom(PDFReal bottom); - void setZoom(PDFReal zoom); - void setName(const QByteArray& name); - void setPageReference(PDFObjectReference pageReference); - void setPageIndex(PDFInteger pageIndex); - - static PDFDestination createXYZ(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal zoom); - static PDFDestination createFit(PDFObjectReference page); - static PDFDestination createFitH(PDFObjectReference page, PDFReal top); - static PDFDestination createFitV(PDFObjectReference page, PDFReal left); - static PDFDestination createFitR(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal right, PDFReal bottom); - static PDFDestination createFitB(PDFObjectReference page); - static PDFDestination createFitBH(PDFObjectReference page, PDFReal top); - static PDFDestination createFitBV(PDFObjectReference page, PDFReal left); - static PDFDestination createNamed(const QByteArray& name); - - bool hasLeft() const; - bool hasTop() const; - bool hasRight() const; - bool hasBottom() const; - bool hasZoom() const; - - bool isValid() const { return m_destinationType != DestinationType::Invalid; } - bool isNamedDestination() const { return m_destinationType == DestinationType::Named; } - -private: - DestinationType m_destinationType = DestinationType::Invalid; - PDFReal m_left = 0.0; - PDFReal m_top = 0.0; - PDFReal m_right = 0.0; - PDFReal m_bottom = 0.0; - PDFReal m_zoom = 0.0; - QByteArray m_name; - PDFObjectReference m_pageReference; - PDFInteger m_pageIndex = 0; -}; - -using PDFActionPtr = QSharedPointer; - -/// Base class for action types. -class PDF4QTLIBSHARED_EXPORT PDFAction -{ -public: - explicit PDFAction() = default; - virtual ~PDFAction() = default; - - /// Returns type of the action. - virtual ActionType getType() const = 0; - - /// Returns container with next actions - const std::vector& getNextActions() const { return m_nextActions; } - - /// Tries to parse the action. If serious error occurs, then exception is thrown. - /// If \p object is null object, then nullptr is returned. - /// \param storage Object storage - /// \param object Object containing the action - static PDFActionPtr parse(const PDFObjectStorage* storage, PDFObject object); - - /// Calls the lambda function with action as parameter, then following - /// the 'Next' entry, as described by PDF 1.7 specification. - void apply(const std::function& callback); - - /// Returns list of actions to be executed - std::vector getActionList() const; - -private: - static PDFActionPtr parseImpl(const PDFObjectStorage* storage, PDFObject object, std::set& usedReferences); - - void fillActionList(std::vector& actionList) const; - - std::vector m_nextActions; -}; - -/// Regular go-to action. Can contain also structure destinations, both regular page destination -/// and structure destination are present, because if structure destination fails, then -/// page destination can be used as fallback resolution. -class PDFActionGoTo : public PDFAction -{ -public: - explicit inline PDFActionGoTo(PDFDestination destination, PDFDestination structureDestination) : - m_destination(qMove(destination)), m_structureDestination(qMove(structureDestination)) { } - - virtual ActionType getType() const override { return ActionType::GoTo; } - - const PDFDestination& getDestination() const { return m_destination; } - const PDFDestination& getStructureDestination() const { return m_structureDestination; } - - void setDestination(const PDFDestination& destination); - void setStructureDestination(const PDFDestination& structureDestination); - -private: - PDFDestination m_destination; - PDFDestination m_structureDestination; -}; - -class PDFActionGoToR : public PDFAction -{ -public: - explicit inline PDFActionGoToR(PDFDestination destination, PDFDestination structureDestination, PDFFileSpecification fileSpecification, bool newWindow) : - m_destination(qMove(destination)), - m_structureDestination(qMove(structureDestination)), - m_fileSpecification(qMove(fileSpecification)), - m_newWindow(newWindow) - { - - } - - virtual ActionType getType() const override { return ActionType::GoToR; } - - const PDFDestination& getDestination() const { return m_destination; } - const PDFDestination& getStructureDestination() const { return m_structureDestination; } - const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } - bool isNewWindow() const { return m_newWindow; } - -private: - PDFDestination m_destination; - PDFDestination m_structureDestination; - PDFFileSpecification m_fileSpecification; - bool m_newWindow = false; -}; - -class PDFActionGoToE : public PDFAction -{ -public: - explicit inline PDFActionGoToE(PDFDestination destination, PDFFileSpecification fileSpecification, bool newWindow, const PDFObject& target) : - m_destination(qMove(destination)), - m_fileSpecification(qMove(fileSpecification)), - m_newWindow(newWindow), - m_target(target) - { - - } - - virtual ActionType getType() const override { return ActionType::GoToE; } - - const PDFDestination& getDestination() const { return m_destination; } - const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } - bool isNewWindow() const { return m_newWindow; } - const PDFObject& getTarget() const { return m_target; } - -private: - PDFDestination m_destination; - PDFFileSpecification m_fileSpecification; - bool m_newWindow = false; - PDFObject m_target; -}; - -/// Go to document part -class PDFActionGoToDp : public PDFAction -{ -public: - explicit inline PDFActionGoToDp(PDFObjectReference documentPart) : - m_documentPart(documentPart) { } - - virtual ActionType getType() const override { return ActionType::GoToDp; } - - PDFObjectReference getDocumentPart() const { return m_documentPart; } - -private: - PDFObjectReference m_documentPart; -}; - -class PDFActionLaunch : public PDFAction -{ -public: - - /// Specification of launched application (if not file specification is attached) - struct Win - { - QByteArray file; - QByteArray directory; - QByteArray operation; - QByteArray parameters; - }; - - explicit inline PDFActionLaunch(PDFFileSpecification fileSpecification, bool newWindow, Win win) : - m_fileSpecification(qMove(fileSpecification)), - m_newWindow(newWindow), - m_win(qMove(win)) - { - - } - - virtual ActionType getType() const override { return ActionType::Launch; } - - const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } - const Win& getWinSpecification() const { return m_win; } - bool isNewWindow() const { return m_newWindow; } - -private: - PDFFileSpecification m_fileSpecification; - bool m_newWindow = false; - Win m_win; -}; - -class PDFActionThread : public PDFAction -{ -public: - using Thread = std::variant; - using Bead = std::variant; - - explicit inline PDFActionThread(PDFFileSpecification fileSpecification, Thread thread, Bead bead) : - m_fileSpecification(qMove(fileSpecification)), - m_thread(qMove(thread)), - m_bead(qMove(bead)) - { - - } - - virtual ActionType getType() const override { return ActionType::Thread; } - - const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } - const Thread& getThread() const { return m_thread; } - const Bead& getBead() const { return m_bead; } - -private: - PDFFileSpecification m_fileSpecification; - Thread m_thread; - Bead m_bead; -}; - -class PDFActionURI : public PDFAction -{ -public: - explicit inline PDFActionURI(QByteArray URI, bool isMap) : - m_URI(qMove(URI)), - m_isMap(isMap) - { - - } - - virtual ActionType getType() const override { return ActionType::URI; } - - const QByteArray& getURI() const { return m_URI; } - bool isMap() const { return m_isMap; } - - /// Returns URI as string in unicode. If pdf document conforms - /// to PDF specification, URI is UTF-8 encoded string. - QString getURIString() const; - -private: - QByteArray m_URI; - bool m_isMap; -}; - -class PDFActionSound : public PDFAction -{ -public: - explicit inline PDFActionSound(PDFSound sound, PDFReal volume, bool isSynchronous, bool isRepeat, bool isMix) : - m_sound(qMove(sound)), - m_volume(qMove(volume)), - m_isSynchronous(isSynchronous), - m_isRepeat(isRepeat), - m_isMix(isMix) - { - - } - - virtual ActionType getType() const override { return ActionType::Sound; } - - const PDFSound* getSound() const { return &m_sound; } - PDFReal getVolume() const { return m_volume; } - bool isSynchronous() const { return m_isSynchronous; } - bool isRepeat() const { return m_isRepeat; } - bool isMix() const { return m_isMix; } - -private: - PDFSound m_sound; - PDFReal m_volume; - bool m_isSynchronous; - bool m_isRepeat; - bool m_isMix; -}; - -class PDFActionMovie : public PDFAction -{ -public: - enum class Operation - { - Play, - Stop, - Pause, - Resume - }; - - explicit inline PDFActionMovie(PDFObjectReference annotation, QString title, Operation operation) : - m_annotation(qMove(annotation)), - m_title(qMove(title)), - m_operation(operation) - { - - } - - virtual ActionType getType() const override { return ActionType::Movie; } - - PDFObjectReference getAnnotation() const { return m_annotation; } - const QString& getTitle() const { return m_title; } - Operation getOperation() const { return m_operation; } - -private: - PDFObjectReference m_annotation; - QString m_title; - Operation m_operation; -}; - -class PDFActionHide : public PDFAction -{ -public: - explicit inline PDFActionHide(std::vector&& annotations, std::vector&& fieldNames, bool hide) : - m_annotations(qMove(annotations)), - m_fieldNames(qMove(fieldNames)), - m_hide(hide) - { - - } - - virtual ActionType getType() const override { return ActionType::Hide; } - - const std::vector& getAnnotations() const { return m_annotations; } - const std::vector& getFieldNames() const { return m_fieldNames; } - bool isHide() const { return m_hide; } - -private: - std::vector m_annotations; - std::vector m_fieldNames; - bool m_hide; -}; - -class PDFActionNamed : public PDFAction -{ -public: - enum class NamedActionType - { - Custom, - NextPage, - PrevPage, - FirstPage, - LastPage - }; - - explicit inline PDFActionNamed(NamedActionType namedActionType, QByteArray&& customNamedAction) : - m_namedActionType(namedActionType), - m_customNamedAction(qMove(customNamedAction)) - { - - } - - virtual ActionType getType() const override { return ActionType::Named; } - - NamedActionType getNamedActionType() const { return m_namedActionType; } - const QByteArray& getCustomNamedAction() const { return m_customNamedAction; } - -private: - NamedActionType m_namedActionType; - QByteArray m_customNamedAction; -}; - -class PDFActionSetOCGState : public PDFAction -{ -public: - - enum class SwitchType - { - ON = 0, - OFF = 1, - Toggle = 2 - }; - - using StateChangeItem = std::pair; - using StateChangeItems = std::vector; - - explicit inline PDFActionSetOCGState(StateChangeItems&& stateChangeItems, bool isRadioButtonsPreserved) : - m_items(qMove(stateChangeItems)), - m_isRadioButtonsPreserved(isRadioButtonsPreserved) - { - - } - - virtual ActionType getType() const override { return ActionType::SetOCGState; } - - const StateChangeItems& getStateChangeItems() const { return m_items; } - bool isRadioButtonsPreserved() const { return m_isRadioButtonsPreserved; } - -private: - StateChangeItems m_items; - bool m_isRadioButtonsPreserved; -}; - -class PDFActionRendition : public PDFAction -{ -public: - - enum class Operation - { - PlayAndAssociate = 0, - Stop = 1, - Pause = 2, - Resume = 3, - Play = 4 - }; - - explicit inline PDFActionRendition(std::optional&& rendition, PDFObjectReference annotation, Operation operation, QString javascript) : - m_rendition(qMove(rendition)), - m_annotation(annotation), - m_operation(operation), - m_javascript(qMove(javascript)) - { - - } - - virtual ActionType getType() const override { return ActionType::Rendition; } - - const PDFRendition* getRendition() const { return m_rendition.has_value() ? &m_rendition.value() : nullptr; } - PDFObjectReference getAnnotation() const { return m_annotation; } - Operation getOperation() const { return m_operation; } - const QString& getJavaScript() const { return m_javascript; } - -private: - std::optional m_rendition; - PDFObjectReference m_annotation; - Operation m_operation; - QString m_javascript; -}; - -class PDFActionTransition : public PDFAction -{ -public: - explicit inline PDFActionTransition(PDFPageTransition&& transition) : - m_transition(qMove(transition)) - { - - } - - virtual ActionType getType() const override { return ActionType::Transition; } - - const PDFPageTransition& getTransition() const { return m_transition; } - -private: - PDFPageTransition m_transition; -}; - -class PDFActionGoTo3DView : public PDFAction -{ -public: - explicit PDFActionGoTo3DView(PDFObject annotation, PDFObject view) : - m_annotation(qMove(annotation)), - m_view(qMove(view)) - { - - } - - virtual ActionType getType() const override { return ActionType::GoTo3DView; } - - const PDFObject& getAnnotation() const { return m_annotation; } - const PDFObject& getView() const { return m_view; } - -private: - PDFObject m_annotation; - PDFObject m_view; -}; - -class PDFActionJavaScript : public PDFAction -{ -public: - explicit PDFActionJavaScript(const QString& javaScript) : - m_javaScript(javaScript) - { - - } - - virtual ActionType getType() const override { return ActionType::JavaScript; } - - const QString& getJavaScript() const { return m_javaScript; } - -private: - QString m_javaScript; -}; - -class PDFActionRichMediaExecute : public PDFAction -{ -public: - explicit PDFActionRichMediaExecute(PDFObjectReference richMediaAnnotation, - PDFObjectReference richMediaInstance, - QString command, - PDFObject arguments) : - m_richMediaAnnotation(richMediaAnnotation), - m_richMediaInstance(richMediaInstance), - m_command(qMove(command)), - m_arguments(qMove(arguments)) - { - - } - - virtual ActionType getType() const override { return ActionType::RichMediaExecute; } - - PDFObjectReference getRichMediaAnnotation() const { return m_richMediaAnnotation; } - PDFObjectReference getRichMediaInstance() const { return m_richMediaInstance; } - QString getCommand() const { return m_command; } - PDFObject getArguments() const { return m_arguments; } - -private: - PDFObjectReference m_richMediaAnnotation; - PDFObjectReference m_richMediaInstance; - QString m_command; - PDFObject m_arguments; -}; - - -class PDFFormAction : public PDFAction -{ -public: - enum FieldScope - { - All, ///< Perform action for all form fields - Include, ///< Perform action only on fields listed in the list - Exclude ///< Perform action on all fields except those in the list - }; - - struct FieldList - { - std::vector fieldReferences; - QStringList qualifiedNames; - - bool isEmpty() const { return fieldReferences.empty() && qualifiedNames.isEmpty(); } - }; - - explicit inline PDFFormAction(FieldScope fieldScope, FieldList fieldList) : - m_fieldScope(fieldScope), - m_fieldList(qMove(fieldList)) - { - - } - - FieldScope getFieldScope() const { return m_fieldScope; } - const FieldList& getFieldList() const { return m_fieldList; } - - /// Parses the field list from the object. If object contains invalid field list, - /// then empty field list is returned. If object is empty, empty field list is returned. - /// \param storage Object storage - /// \param object Field list array object - /// \param[out] fieldScope Result field scope - static FieldList parseFieldList(const PDFObjectStorage* storage, PDFObject object, FieldScope& fieldScope); - -protected: - FieldScope m_fieldScope = FieldScope::All; - FieldList m_fieldList; -}; - -class PDFActionSubmitForm : public PDFFormAction -{ -public: - enum SubmitFlag - { - None = 0, - IncludeExclude = 1 << 0, - IncludeNoValueFields = 1 << 1, - ExportFormat = 1 << 2, - GetMethod = 1 << 3, - SubmitCoordinates = 1 << 4, - XFDF = 1 << 5, - IncludeAppendSaves = 1 << 6, - IncludeAnnotations = 1 << 7, - SubmitPDF = 1 << 8, - CanonicalFormat = 1 << 9, - ExclNonUseAnnots = 1 << 10, - ExclFKey = 1 << 11, - EmbedForm = 1 << 13 - }; - Q_DECLARE_FLAGS(SubmitFlags, SubmitFlag) - - explicit inline PDFActionSubmitForm(FieldScope fieldScope, FieldList fieldList, PDFFileSpecification url, QByteArray charset, SubmitFlags flags) : - PDFFormAction(fieldScope, qMove(fieldList)), - m_url(qMove(url)), - m_charset(qMove(charset)), - m_flags(flags) - { - - } - - virtual ActionType getType() const override { return ActionType::SubmitForm; } - - const PDFFileSpecification& getUrl() const { return m_url; } - const QByteArray& getCharset() const { return m_charset; } - SubmitFlags getFlags() const { return m_flags; } - -private: - PDFFileSpecification m_url; - QByteArray m_charset; - SubmitFlags m_flags = None; -}; - -class PDFActionResetForm : public PDFFormAction -{ -public: - enum ResetFlag - { - None = 0, - IncludeExclude = 1 << 0, - }; - Q_DECLARE_FLAGS(ResetFlags, ResetFlag) - - - explicit inline PDFActionResetForm(FieldScope fieldScope, FieldList fieldList, ResetFlags flags) : - PDFFormAction(fieldScope, qMove(fieldList)), - m_flags(flags) - { - - } - - virtual ActionType getType() const override { return ActionType::ResetForm; } - - ResetFlags getFlags() const { return m_flags; } - -private: - ResetFlags m_flags = None; -}; - -class PDFActionImportDataForm : public PDFAction -{ -public: - - explicit inline PDFActionImportDataForm(PDFFileSpecification file) : - m_file(qMove(file)) - { - - } - - virtual ActionType getType() const override { return ActionType::ImportDataForm; } - - const PDFFileSpecification& getFile() const { return m_file; } - -private: - PDFFileSpecification m_file; -}; - -} // namespace pdf - -#endif // PDFACTION_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFACTION_H +#define PDFACTION_H + +#include "pdfglobal.h" +#include "pdfobject.h" +#include "pdffile.h" +#include "pdfmultimedia.h" +#include "pdfpagetransition.h" + +#include + +#include +#include +#include + +namespace pdf +{ +class PDFAction; +class PDFDocument; +class PDFObjectStorage; + +enum class ActionType +{ + GoTo, + GoToR, + GoToE, + GoToDp, + Launch, + Thread, + URI, + Sound, + Movie, + Hide, + Named, + SetOCGState, + Rendition, + Transition, + GoTo3DView, + JavaScript, + SubmitForm, + ResetForm, + ImportDataForm, + RichMediaExecute +}; + +enum class DestinationType +{ + Invalid, + Named, + XYZ, + Fit, + FitH, + FitV, + FitR, + FitB, + FitBH, + FitBV +}; + +/// Destination to the specific location of the document. Destination can also be 'Named' type, +/// in this case, destination in name tree is found and used. Important note: because structure +/// destination has almost exactly same syntax as page destination, it should be checked, +/// if indirect reference returned by function \p getPageReference references really page, +/// or some structure element. +class PDF4QTLIBSHARED_EXPORT PDFDestination +{ +public: + explicit inline PDFDestination() = default; + + DestinationType getDestinationType() const { return m_destinationType; } + PDFReal getLeft() const { return m_left; } + PDFReal getTop() const { return m_top; } + PDFReal getRight() const { return m_right; } + PDFReal getBottom() const { return m_bottom; } + PDFReal getZoom() const { return m_zoom; } + const QByteArray& getName() const { return m_name; } + PDFObjectReference getPageReference() const { return m_pageReference; } + PDFInteger getPageIndex() const { return m_pageIndex; } + + /// Parses the destination from the object. If object contains invalid destination, + /// then empty destination is returned. If object is empty, empty destination is returned. + /// \param storage Object storage + /// \param object Destination object + static PDFDestination parse(const PDFObjectStorage* storage, PDFObject object); + + void setDestinationType(DestinationType destinationType); + void setLeft(PDFReal left); + void setTop(PDFReal top); + void setRight(PDFReal right); + void setBottom(PDFReal bottom); + void setZoom(PDFReal zoom); + void setName(const QByteArray& name); + void setPageReference(PDFObjectReference pageReference); + void setPageIndex(PDFInteger pageIndex); + + static PDFDestination createXYZ(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal zoom); + static PDFDestination createFit(PDFObjectReference page); + static PDFDestination createFitH(PDFObjectReference page, PDFReal top); + static PDFDestination createFitV(PDFObjectReference page, PDFReal left); + static PDFDestination createFitR(PDFObjectReference page, PDFReal left, PDFReal top, PDFReal right, PDFReal bottom); + static PDFDestination createFitB(PDFObjectReference page); + static PDFDestination createFitBH(PDFObjectReference page, PDFReal top); + static PDFDestination createFitBV(PDFObjectReference page, PDFReal left); + static PDFDestination createNamed(const QByteArray& name); + + bool hasLeft() const; + bool hasTop() const; + bool hasRight() const; + bool hasBottom() const; + bool hasZoom() const; + + bool isValid() const { return m_destinationType != DestinationType::Invalid; } + bool isNamedDestination() const { return m_destinationType == DestinationType::Named; } + +private: + DestinationType m_destinationType = DestinationType::Invalid; + PDFReal m_left = 0.0; + PDFReal m_top = 0.0; + PDFReal m_right = 0.0; + PDFReal m_bottom = 0.0; + PDFReal m_zoom = 0.0; + QByteArray m_name; + PDFObjectReference m_pageReference; + PDFInteger m_pageIndex = 0; +}; + +using PDFActionPtr = QSharedPointer; + +/// Base class for action types. +class PDF4QTLIBSHARED_EXPORT PDFAction +{ +public: + explicit PDFAction() = default; + virtual ~PDFAction() = default; + + /// Returns type of the action. + virtual ActionType getType() const = 0; + + /// Returns container with next actions + const std::vector& getNextActions() const { return m_nextActions; } + + /// Tries to parse the action. If serious error occurs, then exception is thrown. + /// If \p object is null object, then nullptr is returned. + /// \param storage Object storage + /// \param object Object containing the action + static PDFActionPtr parse(const PDFObjectStorage* storage, PDFObject object); + + /// Calls the lambda function with action as parameter, then following + /// the 'Next' entry, as described by PDF 1.7 specification. + void apply(const std::function& callback); + + /// Returns list of actions to be executed + std::vector getActionList() const; + +private: + static PDFActionPtr parseImpl(const PDFObjectStorage* storage, PDFObject object, std::set& usedReferences); + + void fillActionList(std::vector& actionList) const; + + std::vector m_nextActions; +}; + +/// Regular go-to action. Can contain also structure destinations, both regular page destination +/// and structure destination are present, because if structure destination fails, then +/// page destination can be used as fallback resolution. +class PDFActionGoTo : public PDFAction +{ +public: + explicit inline PDFActionGoTo(PDFDestination destination, PDFDestination structureDestination) : + m_destination(qMove(destination)), m_structureDestination(qMove(structureDestination)) { } + + virtual ActionType getType() const override { return ActionType::GoTo; } + + const PDFDestination& getDestination() const { return m_destination; } + const PDFDestination& getStructureDestination() const { return m_structureDestination; } + + void setDestination(const PDFDestination& destination); + void setStructureDestination(const PDFDestination& structureDestination); + +private: + PDFDestination m_destination; + PDFDestination m_structureDestination; +}; + +class PDFActionGoToR : public PDFAction +{ +public: + explicit inline PDFActionGoToR(PDFDestination destination, PDFDestination structureDestination, PDFFileSpecification fileSpecification, bool newWindow) : + m_destination(qMove(destination)), + m_structureDestination(qMove(structureDestination)), + m_fileSpecification(qMove(fileSpecification)), + m_newWindow(newWindow) + { + + } + + virtual ActionType getType() const override { return ActionType::GoToR; } + + const PDFDestination& getDestination() const { return m_destination; } + const PDFDestination& getStructureDestination() const { return m_structureDestination; } + const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } + bool isNewWindow() const { return m_newWindow; } + +private: + PDFDestination m_destination; + PDFDestination m_structureDestination; + PDFFileSpecification m_fileSpecification; + bool m_newWindow = false; +}; + +class PDFActionGoToE : public PDFAction +{ +public: + explicit inline PDFActionGoToE(PDFDestination destination, PDFFileSpecification fileSpecification, bool newWindow, const PDFObject& target) : + m_destination(qMove(destination)), + m_fileSpecification(qMove(fileSpecification)), + m_newWindow(newWindow), + m_target(target) + { + + } + + virtual ActionType getType() const override { return ActionType::GoToE; } + + const PDFDestination& getDestination() const { return m_destination; } + const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } + bool isNewWindow() const { return m_newWindow; } + const PDFObject& getTarget() const { return m_target; } + +private: + PDFDestination m_destination; + PDFFileSpecification m_fileSpecification; + bool m_newWindow = false; + PDFObject m_target; +}; + +/// Go to document part +class PDFActionGoToDp : public PDFAction +{ +public: + explicit inline PDFActionGoToDp(PDFObjectReference documentPart) : + m_documentPart(documentPart) { } + + virtual ActionType getType() const override { return ActionType::GoToDp; } + + PDFObjectReference getDocumentPart() const { return m_documentPart; } + +private: + PDFObjectReference m_documentPart; +}; + +class PDFActionLaunch : public PDFAction +{ +public: + + /// Specification of launched application (if not file specification is attached) + struct Win + { + QByteArray file; + QByteArray directory; + QByteArray operation; + QByteArray parameters; + }; + + explicit inline PDFActionLaunch(PDFFileSpecification fileSpecification, bool newWindow, Win win) : + m_fileSpecification(qMove(fileSpecification)), + m_newWindow(newWindow), + m_win(qMove(win)) + { + + } + + virtual ActionType getType() const override { return ActionType::Launch; } + + const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } + const Win& getWinSpecification() const { return m_win; } + bool isNewWindow() const { return m_newWindow; } + +private: + PDFFileSpecification m_fileSpecification; + bool m_newWindow = false; + Win m_win; +}; + +class PDFActionThread : public PDFAction +{ +public: + using Thread = std::variant; + using Bead = std::variant; + + explicit inline PDFActionThread(PDFFileSpecification fileSpecification, Thread thread, Bead bead) : + m_fileSpecification(qMove(fileSpecification)), + m_thread(qMove(thread)), + m_bead(qMove(bead)) + { + + } + + virtual ActionType getType() const override { return ActionType::Thread; } + + const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } + const Thread& getThread() const { return m_thread; } + const Bead& getBead() const { return m_bead; } + +private: + PDFFileSpecification m_fileSpecification; + Thread m_thread; + Bead m_bead; +}; + +class PDFActionURI : public PDFAction +{ +public: + explicit inline PDFActionURI(QByteArray URI, bool isMap) : + m_URI(qMove(URI)), + m_isMap(isMap) + { + + } + + virtual ActionType getType() const override { return ActionType::URI; } + + const QByteArray& getURI() const { return m_URI; } + bool isMap() const { return m_isMap; } + + /// Returns URI as string in unicode. If pdf document conforms + /// to PDF specification, URI is UTF-8 encoded string. + QString getURIString() const; + +private: + QByteArray m_URI; + bool m_isMap; +}; + +class PDFActionSound : public PDFAction +{ +public: + explicit inline PDFActionSound(PDFSound sound, PDFReal volume, bool isSynchronous, bool isRepeat, bool isMix) : + m_sound(qMove(sound)), + m_volume(qMove(volume)), + m_isSynchronous(isSynchronous), + m_isRepeat(isRepeat), + m_isMix(isMix) + { + + } + + virtual ActionType getType() const override { return ActionType::Sound; } + + const PDFSound* getSound() const { return &m_sound; } + PDFReal getVolume() const { return m_volume; } + bool isSynchronous() const { return m_isSynchronous; } + bool isRepeat() const { return m_isRepeat; } + bool isMix() const { return m_isMix; } + +private: + PDFSound m_sound; + PDFReal m_volume; + bool m_isSynchronous; + bool m_isRepeat; + bool m_isMix; +}; + +class PDFActionMovie : public PDFAction +{ +public: + enum class Operation + { + Play, + Stop, + Pause, + Resume + }; + + explicit inline PDFActionMovie(PDFObjectReference annotation, QString title, Operation operation) : + m_annotation(qMove(annotation)), + m_title(qMove(title)), + m_operation(operation) + { + + } + + virtual ActionType getType() const override { return ActionType::Movie; } + + PDFObjectReference getAnnotation() const { return m_annotation; } + const QString& getTitle() const { return m_title; } + Operation getOperation() const { return m_operation; } + +private: + PDFObjectReference m_annotation; + QString m_title; + Operation m_operation; +}; + +class PDFActionHide : public PDFAction +{ +public: + explicit inline PDFActionHide(std::vector&& annotations, std::vector&& fieldNames, bool hide) : + m_annotations(qMove(annotations)), + m_fieldNames(qMove(fieldNames)), + m_hide(hide) + { + + } + + virtual ActionType getType() const override { return ActionType::Hide; } + + const std::vector& getAnnotations() const { return m_annotations; } + const std::vector& getFieldNames() const { return m_fieldNames; } + bool isHide() const { return m_hide; } + +private: + std::vector m_annotations; + std::vector m_fieldNames; + bool m_hide; +}; + +class PDFActionNamed : public PDFAction +{ +public: + enum class NamedActionType + { + Custom, + NextPage, + PrevPage, + FirstPage, + LastPage + }; + + explicit inline PDFActionNamed(NamedActionType namedActionType, QByteArray&& customNamedAction) : + m_namedActionType(namedActionType), + m_customNamedAction(qMove(customNamedAction)) + { + + } + + virtual ActionType getType() const override { return ActionType::Named; } + + NamedActionType getNamedActionType() const { return m_namedActionType; } + const QByteArray& getCustomNamedAction() const { return m_customNamedAction; } + +private: + NamedActionType m_namedActionType; + QByteArray m_customNamedAction; +}; + +class PDFActionSetOCGState : public PDFAction +{ +public: + + enum class SwitchType + { + ON = 0, + OFF = 1, + Toggle = 2 + }; + + using StateChangeItem = std::pair; + using StateChangeItems = std::vector; + + explicit inline PDFActionSetOCGState(StateChangeItems&& stateChangeItems, bool isRadioButtonsPreserved) : + m_items(qMove(stateChangeItems)), + m_isRadioButtonsPreserved(isRadioButtonsPreserved) + { + + } + + virtual ActionType getType() const override { return ActionType::SetOCGState; } + + const StateChangeItems& getStateChangeItems() const { return m_items; } + bool isRadioButtonsPreserved() const { return m_isRadioButtonsPreserved; } + +private: + StateChangeItems m_items; + bool m_isRadioButtonsPreserved; +}; + +class PDFActionRendition : public PDFAction +{ +public: + + enum class Operation + { + PlayAndAssociate = 0, + Stop = 1, + Pause = 2, + Resume = 3, + Play = 4 + }; + + explicit inline PDFActionRendition(std::optional&& rendition, PDFObjectReference annotation, Operation operation, QString javascript) : + m_rendition(qMove(rendition)), + m_annotation(annotation), + m_operation(operation), + m_javascript(qMove(javascript)) + { + + } + + virtual ActionType getType() const override { return ActionType::Rendition; } + + const PDFRendition* getRendition() const { return m_rendition.has_value() ? &m_rendition.value() : nullptr; } + PDFObjectReference getAnnotation() const { return m_annotation; } + Operation getOperation() const { return m_operation; } + const QString& getJavaScript() const { return m_javascript; } + +private: + std::optional m_rendition; + PDFObjectReference m_annotation; + Operation m_operation; + QString m_javascript; +}; + +class PDFActionTransition : public PDFAction +{ +public: + explicit inline PDFActionTransition(PDFPageTransition&& transition) : + m_transition(qMove(transition)) + { + + } + + virtual ActionType getType() const override { return ActionType::Transition; } + + const PDFPageTransition& getTransition() const { return m_transition; } + +private: + PDFPageTransition m_transition; +}; + +class PDFActionGoTo3DView : public PDFAction +{ +public: + explicit PDFActionGoTo3DView(PDFObject annotation, PDFObject view) : + m_annotation(qMove(annotation)), + m_view(qMove(view)) + { + + } + + virtual ActionType getType() const override { return ActionType::GoTo3DView; } + + const PDFObject& getAnnotation() const { return m_annotation; } + const PDFObject& getView() const { return m_view; } + +private: + PDFObject m_annotation; + PDFObject m_view; +}; + +class PDFActionJavaScript : public PDFAction +{ +public: + explicit PDFActionJavaScript(const QString& javaScript) : + m_javaScript(javaScript) + { + + } + + virtual ActionType getType() const override { return ActionType::JavaScript; } + + const QString& getJavaScript() const { return m_javaScript; } + +private: + QString m_javaScript; +}; + +class PDFActionRichMediaExecute : public PDFAction +{ +public: + explicit PDFActionRichMediaExecute(PDFObjectReference richMediaAnnotation, + PDFObjectReference richMediaInstance, + QString command, + PDFObject arguments) : + m_richMediaAnnotation(richMediaAnnotation), + m_richMediaInstance(richMediaInstance), + m_command(qMove(command)), + m_arguments(qMove(arguments)) + { + + } + + virtual ActionType getType() const override { return ActionType::RichMediaExecute; } + + PDFObjectReference getRichMediaAnnotation() const { return m_richMediaAnnotation; } + PDFObjectReference getRichMediaInstance() const { return m_richMediaInstance; } + QString getCommand() const { return m_command; } + PDFObject getArguments() const { return m_arguments; } + +private: + PDFObjectReference m_richMediaAnnotation; + PDFObjectReference m_richMediaInstance; + QString m_command; + PDFObject m_arguments; +}; + + +class PDFFormAction : public PDFAction +{ +public: + enum FieldScope + { + All, ///< Perform action for all form fields + Include, ///< Perform action only on fields listed in the list + Exclude ///< Perform action on all fields except those in the list + }; + + struct FieldList + { + std::vector fieldReferences; + QStringList qualifiedNames; + + bool isEmpty() const { return fieldReferences.empty() && qualifiedNames.isEmpty(); } + }; + + explicit inline PDFFormAction(FieldScope fieldScope, FieldList fieldList) : + m_fieldScope(fieldScope), + m_fieldList(qMove(fieldList)) + { + + } + + FieldScope getFieldScope() const { return m_fieldScope; } + const FieldList& getFieldList() const { return m_fieldList; } + + /// Parses the field list from the object. If object contains invalid field list, + /// then empty field list is returned. If object is empty, empty field list is returned. + /// \param storage Object storage + /// \param object Field list array object + /// \param[out] fieldScope Result field scope + static FieldList parseFieldList(const PDFObjectStorage* storage, PDFObject object, FieldScope& fieldScope); + +protected: + FieldScope m_fieldScope = FieldScope::All; + FieldList m_fieldList; +}; + +class PDFActionSubmitForm : public PDFFormAction +{ +public: + enum SubmitFlag + { + None = 0, + IncludeExclude = 1 << 0, + IncludeNoValueFields = 1 << 1, + ExportFormat = 1 << 2, + GetMethod = 1 << 3, + SubmitCoordinates = 1 << 4, + XFDF = 1 << 5, + IncludeAppendSaves = 1 << 6, + IncludeAnnotations = 1 << 7, + SubmitPDF = 1 << 8, + CanonicalFormat = 1 << 9, + ExclNonUseAnnots = 1 << 10, + ExclFKey = 1 << 11, + EmbedForm = 1 << 13 + }; + Q_DECLARE_FLAGS(SubmitFlags, SubmitFlag) + + explicit inline PDFActionSubmitForm(FieldScope fieldScope, FieldList fieldList, PDFFileSpecification url, QByteArray charset, SubmitFlags flags) : + PDFFormAction(fieldScope, qMove(fieldList)), + m_url(qMove(url)), + m_charset(qMove(charset)), + m_flags(flags) + { + + } + + virtual ActionType getType() const override { return ActionType::SubmitForm; } + + const PDFFileSpecification& getUrl() const { return m_url; } + const QByteArray& getCharset() const { return m_charset; } + SubmitFlags getFlags() const { return m_flags; } + +private: + PDFFileSpecification m_url; + QByteArray m_charset; + SubmitFlags m_flags = None; +}; + +class PDFActionResetForm : public PDFFormAction +{ +public: + enum ResetFlag + { + None = 0, + IncludeExclude = 1 << 0, + }; + Q_DECLARE_FLAGS(ResetFlags, ResetFlag) + + + explicit inline PDFActionResetForm(FieldScope fieldScope, FieldList fieldList, ResetFlags flags) : + PDFFormAction(fieldScope, qMove(fieldList)), + m_flags(flags) + { + + } + + virtual ActionType getType() const override { return ActionType::ResetForm; } + + ResetFlags getFlags() const { return m_flags; } + +private: + ResetFlags m_flags = None; +}; + +class PDFActionImportDataForm : public PDFAction +{ +public: + + explicit inline PDFActionImportDataForm(PDFFileSpecification file) : + m_file(qMove(file)) + { + + } + + virtual ActionType getType() const override { return ActionType::ImportDataForm; } + + const PDFFileSpecification& getFile() const { return m_file; } + +private: + PDFFileSpecification m_file; +}; + +} // namespace pdf + +#endif // PDFACTION_H diff --git a/Pdf4QtLib/sources/pdfadvancedtools.cpp b/Pdf4QtLib/sources/pdfadvancedtools.cpp index 23c2269..49f45f4 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.cpp +++ b/Pdf4QtLib/sources/pdfadvancedtools.cpp @@ -1,1268 +1,1268 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfadvancedtools.h" -#include "pdfdocumentbuilder.h" -#include "pdfdrawwidget.h" -#include "pdfutils.h" -#include "pdfcompiler.h" - -#include -#include -#include - -namespace pdf -{ - -PDFCreateStickyNoteTool::PDFCreateStickyNoteTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent) : - BaseClass(proxy, parent), - m_toolManager(toolManager), - m_actionGroup(actionGroup), - m_pickTool(nullptr), - m_icon(pdf::TextAnnotationIcon::Comment) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreateStickyNoteTool::onPointPicked); - connect(m_actionGroup, &QActionGroup::triggered, this, &PDFCreateStickyNoteTool::onActionTriggered); - - updateActions(); -} - -void PDFCreateStickyNoteTool::updateActions() -{ - BaseClass::updateActions(); - - if (m_actionGroup) - { - const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); - m_actionGroup->setEnabled(isEnabled); - - if (!isActive() && m_actionGroup->checkedAction()) - { - m_actionGroup->checkedAction()->setChecked(false); - } - } -} - -void PDFCreateStickyNoteTool::onActionTriggered(QAction* action) -{ - setActive(action && action->isChecked()); - - if (action) - { - m_icon = static_cast(action->data().toInt()); - } -} - -void PDFCreateStickyNoteTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) -{ - bool ok = false; - QString text = QInputDialog::getText(getProxy()->getWidget(), tr("Sticky note"), tr("Enter text to be displayed in the sticky note"), QLineEdit::Normal, QString(), &ok); - - if (ok && !text.isEmpty()) - { - PDFDocumentModifier modifier(getDocument()); - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - modifier.getBuilder()->createAnnotationText(page, QRectF(pagePoint, QSizeF(0, 0)), m_icon, userName, QString(), text, false); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } - else - { - m_pickTool->resetTool(); - } -} - -PDFCreateHyperlinkTool::PDFCreateHyperlinkTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_pickTool(nullptr) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateHyperlinkTool::onRectanglePicked); - - updateActions(); -} - -LinkHighlightMode PDFCreateHyperlinkTool::getHighlightMode() const -{ - return m_highlightMode; -} - -void PDFCreateHyperlinkTool::setHighlightMode(const LinkHighlightMode& highlightMode) -{ - m_highlightMode = highlightMode; -} - -void PDFCreateHyperlinkTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) -{ - bool ok = false; - QString url = QInputDialog::getText(getProxy()->getWidget(), tr("Hyperlink"), tr("Enter url address of the hyperlink"), QLineEdit::Normal, QString(), &ok); - - if (ok && !url.isEmpty()) - { - PDFDocumentModifier modifier(getDocument()); - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - modifier.getBuilder()->createAnnotationLink(page, pageRectangle, url, m_highlightMode); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } -} - -PDFCreateFreeTextTool::PDFCreateFreeTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_pickTool(nullptr) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateFreeTextTool::onRectanglePicked); - - updateActions(); -} - -void PDFCreateFreeTextTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) -{ - bool ok = false; - QString text = QInputDialog::getMultiLineText(getProxy()->getWidget(), tr("Text"), tr("Enter text for free text panel"), QString(), &ok); - - if (ok && !text.isEmpty()) - { - PDFDocumentModifier modifier(getDocument()); - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - modifier.getBuilder()->createAnnotationFreeText(page, pageRectangle, userName, QString(), text, TextAlignment(Qt::AlignLeft | Qt::AlignTop)); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } -} - -PDFCreateAnnotationTool::PDFCreateAnnotationTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent) -{ - -} - -void PDFCreateAnnotationTool::updateActions() -{ - if (QAction* action = getAction()) - { - const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); - action->setChecked(isActive()); - action->setEnabled(isEnabled); - } -} - -PDFCreateLineTypeTool::PDFCreateLineTypeTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, PDFCreateLineTypeTool::Type type, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_pickTool(nullptr), - m_type(type), - m_penWidth(1.0), - m_strokeColor(Qt::red), - m_fillColor(Qt::yellow) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreateLineTypeTool::onPointPicked); - - m_fillColor.setAlphaF(0.2); - - updateActions(); -} - -void PDFCreateLineTypeTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) -{ - Q_UNUSED(pageIndex); - Q_UNUSED(pagePoint); - - if (m_type == Type::Line && m_pickTool->getPickedPoints().size() == 2) - { - finishDefinition(); - } -} - -void PDFCreateLineTypeTool::finishDefinition() -{ - const std::vector& pickedPoints = m_pickTool->getPickedPoints(); - - switch (m_type) - { - case Type::Line: - { - if (pickedPoints.size() >= 2) - { - PDFDocumentModifier modifier(getDocument()); - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex())->getPageReference(); - modifier.getBuilder()->createAnnotationLine(page, QRectF(), pickedPoints.front(), pickedPoints.back(), m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString(), AnnotationLineEnding::None, AnnotationLineEnding::None); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } - break; - } - - case Type::PolyLine: - { - if (pickedPoints.size() >= 3) - { - PDFDocumentModifier modifier(getDocument()); - - QPolygonF polygon; - for (const QPointF& point : pickedPoints) - { - polygon << point; - } - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex())->getPageReference(); - modifier.getBuilder()->createAnnotationPolyline(page, polygon, m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString(), AnnotationLineEnding::None, AnnotationLineEnding::None); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } - break; - } - - case Type::Polygon: - { - if (pickedPoints.size() >= 3) - { - PDFDocumentModifier modifier(getDocument()); - - QPolygonF polygon; - for (const QPointF& point : pickedPoints) - { - polygon << point; - } - if (!polygon.isClosed()) - { - polygon << pickedPoints.front(); - } - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex())->getPageReference(); - PDFObjectReference annotation = modifier.getBuilder()->createAnnotationPolygon(page, polygon, m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString()); - modifier.getBuilder()->setAnnotationFillOpacity(annotation, m_fillColor.alphaF()); - modifier.getBuilder()->updateAnnotationAppearanceStreams(annotation); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } - break; - } - - default: - Q_ASSERT(false); - break; - } - - m_pickTool->resetTool(); -} - -QColor PDFCreateLineTypeTool::getFillColor() const -{ - return m_fillColor; -} - -void PDFCreateLineTypeTool::setFillColor(const QColor& fillColor) -{ - m_fillColor = fillColor; -} - -QColor PDFCreateLineTypeTool::getStrokeColor() const -{ - return m_strokeColor; -} - -void PDFCreateLineTypeTool::setStrokeColor(const QColor& strokeColor) -{ - m_strokeColor = strokeColor; -} - -PDFReal PDFCreateLineTypeTool::getPenWidth() const -{ - return m_penWidth; -} - -void PDFCreateLineTypeTool::setPenWidth(PDFReal penWidth) -{ - m_penWidth = penWidth; -} - -void PDFCreateLineTypeTool::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - switch (m_type) - { - case Type::PolyLine: - case Type::Polygon: - { - if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) - { - finishDefinition(); - event->accept(); - } - else - { - event->ignore(); - } - - break; - } - - default: - event->ignore(); - break; - } - - if (!event->isAccepted()) - { - BaseClass::keyPressEvent(widget, event); - } -} - -void PDFCreateLineTypeTool::keyReleaseEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - switch (m_type) - { - case Type::PolyLine: - case Type::Polygon: - { - if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) - { - event->accept(); - } - else - { - event->ignore(); - } - - break; - } - - default: - event->ignore(); - break; - } - - if (!event->isAccepted()) - { - BaseClass::keyReleaseEvent(widget, event); - } -} - -void PDFCreateLineTypeTool::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - Q_UNUSED(pageIndex); - Q_UNUSED(compiledPage); - Q_UNUSED(layoutGetter); - Q_UNUSED(errors); - - BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); - - if (pageIndex != m_pickTool->getPageIndex()) - { - return; - } - - const std::vector& points = m_pickTool->getPickedPoints(); - if (points.empty()) - { - return; - } - - QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); - - painter->setWorldMatrix(pagePointToDevicePointMatrix, true); - - QPen pen(m_strokeColor); - QBrush brush(m_fillColor, Qt::SolidPattern); - pen.setWidthF(m_penWidth); - painter->setPen(qMove(pen)); - painter->setBrush(qMove(brush)); - painter->setRenderHint(QPainter::Antialiasing); - - switch (m_type) - { - case Type::Line: - case Type::PolyLine: - { - for (size_t i = 1; i < points.size(); ++i) - { - painter->drawLine(points[i - 1], points[i]); - } - painter->drawLine(points.back(), mousePoint); - break; - } - - case Type::Polygon: - { - QPainterPath path; - path.moveTo(points.front()); - for (size_t i = 1; i < points.size(); ++i) - { - path.lineTo(points[i]); - } - path.lineTo(mousePoint); - path.closeSubpath(); - - painter->drawPath(path); - break; - } - - default: - Q_ASSERT(false); - break; - } -} - -PDFCreateEllipseTool::PDFCreateEllipseTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_pickTool(nullptr), - m_penWidth(1.0), - m_strokeColor(Qt::red), - m_fillColor(Qt::yellow) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); - m_pickTool->setDrawSelectionRectangle(false); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateEllipseTool::onRectanglePicked); - - m_fillColor.setAlphaF(0.2); - - updateActions(); -} - -PDFReal PDFCreateEllipseTool::getPenWidth() const -{ - return m_penWidth; -} - -void PDFCreateEllipseTool::setPenWidth(PDFReal penWidth) -{ - m_penWidth = penWidth; -} - -QColor PDFCreateEllipseTool::getStrokeColor() const -{ - return m_strokeColor; -} - -void PDFCreateEllipseTool::setStrokeColor(const QColor& strokeColor) -{ - m_strokeColor = strokeColor; -} - -QColor PDFCreateEllipseTool::getFillColor() const -{ - return m_fillColor; -} - -void PDFCreateEllipseTool::setFillColor(const QColor& fillColor) -{ - m_fillColor = fillColor; -} - -void PDFCreateEllipseTool::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); - - if (pageIndex != m_pickTool->getPageIndex()) - { - return; - } - - const std::vector& points = m_pickTool->getPickedPoints(); - if (points.empty()) - { - return; - } - - QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); - - painter->setWorldMatrix(pagePointToDevicePointMatrix, true); - - QPen pen(m_strokeColor); - QBrush brush(m_fillColor, Qt::SolidPattern); - pen.setWidthF(m_penWidth); - painter->setPen(qMove(pen)); - painter->setBrush(qMove(brush)); - painter->setRenderHint(QPainter::Antialiasing); - - QPointF point = points.front(); - qreal xMin = qMin(point.x(), mousePoint.x()); - qreal xMax = qMax(point.x(), mousePoint.x()); - qreal yMin = qMin(point.y(), mousePoint.y()); - qreal yMax = qMax(point.y(), mousePoint.y()); - qreal width = xMax - xMin; - qreal height = yMax - yMin; - - if (!qFuzzyIsNull(width) && !qFuzzyIsNull(height)) - { - QRectF rect(xMin, yMin, width, height); - painter->drawEllipse(rect); - } -} - -void PDFCreateEllipseTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) -{ - if (pageRectangle.isEmpty()) - { - return; - } - - PDFDocumentModifier modifier(getDocument()); - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - PDFObjectReference annotation = modifier.getBuilder()->createAnnotationCircle(page, pageRectangle, m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString()); - modifier.getBuilder()->setAnnotationFillOpacity(annotation, m_fillColor.alphaF()); - modifier.getBuilder()->updateAnnotationAppearanceStreams(annotation); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); -} - -PDFCreateFreehandCurveTool::PDFCreateFreehandCurveTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_pageIndex(-1), - m_penWidth(1.0), - m_strokeColor(Qt::red) -{ - -} - -void PDFCreateFreehandCurveTool::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); - - if (pageIndex != m_pageIndex || m_pickedPoints.empty()) - { - return; - } - - painter->setWorldMatrix(pagePointToDevicePointMatrix, true); - - QPen pen(m_strokeColor); - pen.setWidthF(m_penWidth); - painter->setPen(qMove(pen)); - painter->setRenderHint(QPainter::Antialiasing); - - for (size_t i = 1; i < m_pickedPoints.size(); ++i) - { - painter->drawLine(m_pickedPoints[i - 1], m_pickedPoints[i]); - } -} - -void PDFCreateFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - event->accept(); - - if (event->button() == Qt::LeftButton) - { - // Try to perform pick point - QPointF pagePoint; - PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - if (pageIndex != -1 && // We have picked some point on page - (m_pageIndex == -1 || m_pageIndex == pageIndex)) // We are under current page - { - m_pageIndex = pageIndex; - m_pickedPoints.push_back(pagePoint); - } - } - else if (event->button() == Qt::RightButton) - { - resetTool(); - } - - getProxy()->repaintNeeded(); -} - -void PDFCreateFreehandCurveTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - event->accept(); - - if (event->button() == Qt::LeftButton) - { - // Try to perform pick point - QPointF pagePoint; - PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - if (pageIndex != -1 && // We have picked some point on page - (m_pageIndex == pageIndex)) // We are under current page - { - m_pageIndex = pageIndex; - m_pickedPoints.push_back(pagePoint); - - if (m_pickedPoints.size() >= 3) - { - PDFDocumentModifier modifier(getDocument()); - - QPolygonF polygon; - for (const QPointF& point : m_pickedPoints) - { - polygon << point; - } - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pageIndex)->getPageReference(); - modifier.getBuilder()->createAnnotationPolyline(page, polygon, m_penWidth, Qt::black, m_strokeColor, userName, QString(), QString(), AnnotationLineEnding::None, AnnotationLineEnding::None); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); - } - } - - resetTool(); - } - - emit getProxy()->repaintNeeded(); -} - -void PDFCreateFreehandCurveTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - event->accept(); - - if (event->buttons() & Qt::LeftButton && m_pageIndex != -1) - { - // Try to add point to the path - QPointF pagePoint; - PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - if (pageIndex == m_pageIndex) - { - m_pickedPoints.push_back(pagePoint); - } - - getProxy()->repaintNeeded(); - } -} - -PDFReal PDFCreateFreehandCurveTool::getPenWidth() const -{ - return m_penWidth; -} - -void PDFCreateFreehandCurveTool::setPenWidth(const PDFReal& penWidth) -{ - m_penWidth = penWidth; -} - -QColor PDFCreateFreehandCurveTool::getStrokeColor() const -{ - return m_strokeColor; -} - -void PDFCreateFreehandCurveTool::setStrokeColor(const QColor& strokeColor) -{ - m_strokeColor = strokeColor; -} - -void PDFCreateFreehandCurveTool::resetTool() -{ - m_pageIndex = -1; - m_pickedPoints.clear(); -} - -PDFCreateStampTool::PDFCreateStampTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent) : - BaseClass(proxy, parent), - m_pageIndex(-1), - m_toolManager(toolManager), - m_actionGroup(actionGroup), - m_pickTool(nullptr) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreateStampTool::onPointPicked); - connect(m_actionGroup, &QActionGroup::triggered, this, &PDFCreateStampTool::onActionTriggered); - - m_stampAnnotation.setStrokingOpacity(0.5); - m_stampAnnotation.setFillingOpacity(0.5); - - updateActions(); -} - -void PDFCreateStampTool::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - Q_UNUSED(compiledPage); - Q_UNUSED(layoutGetter); - Q_UNUSED(pagePointToDevicePointMatrix); - Q_UNUSED(errors); - - if (pageIndex != m_pageIndex) - { - return; - } - - const PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex); - QRectF rectangle = m_stampAnnotation.getRectangle(); - QMatrix matrix = getProxy()->getAnnotationManager()->prepareTransformations(pagePointToDevicePointMatrix, painter->device(), m_stampAnnotation.getFlags(), page, rectangle); - painter->setWorldMatrix(matrix, true); - - AnnotationDrawParameters parameters; - parameters.painter = painter; - parameters.annotation = const_cast(&m_stampAnnotation); - parameters.key.first = PDFAppeareanceStreams::Appearance::Normal; - parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors); - - m_stampAnnotation.draw(parameters); -} - -void PDFCreateStampTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) -{ - BaseClass::mouseMoveEvent(widget, event); - - // Try to add point to the path - QPointF pagePoint; - m_pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - if (m_pageIndex != -1) - { - m_stampAnnotation.setRectangle(QRectF(pagePoint, QSizeF(0, 0))); - } -} - -void PDFCreateStampTool::updateActions() -{ - BaseClass::updateActions(); - - if (m_actionGroup) - { - const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); - m_actionGroup->setEnabled(isEnabled); - - if (!isActive() && m_actionGroup->checkedAction()) - { - m_actionGroup->checkedAction()->setChecked(false); - } - } -} - -void PDFCreateStampTool::onActionTriggered(QAction* action) -{ - setActive(action && action->isChecked()); - - if (action) - { - m_stampAnnotation.setStamp(static_cast(action->data().toInt())); - } -} - -void PDFCreateStampTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) -{ - PDFDocumentModifier modifier(getDocument()); - - QString userName = PDFSysUtils::getUserName(); - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - modifier.getBuilder()->createAnnotationStamp(page, QRectF(pagePoint, QSizeF(0, 0)), m_stampAnnotation.getStamp(), userName, QString(), QString()); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); -} - -PDFCreateHighlightTextTool::PDFCreateHighlightTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent) : - BaseClass(proxy, parent), - m_toolManager(toolManager), - m_actionGroup(actionGroup), - m_type(AnnotationType::Highlight), - m_isCursorOverText(false) -{ - connect(m_actionGroup, &QActionGroup::triggered, this, &PDFCreateHighlightTextTool::onActionTriggered); - - updateActions(); -} - -void PDFCreateHighlightTextTool::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - Q_UNUSED(compiledPage); - Q_UNUSED(errors); - - pdf::PDFTextSelectionPainter textSelectionPainter(&m_textSelection); - textSelectionPainter.draw(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix); -} - -void PDFCreateHighlightTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - if (event->button() == Qt::LeftButton) - { - QPointF pagePoint; - const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - if (pageIndex != -1) - { - m_selectionInfo.pageIndex = pageIndex; - m_selectionInfo.selectionStartPoint = pagePoint; - event->accept(); - } - else - { - m_selectionInfo = SelectionInfo(); - } - - setSelection(pdf::PDFTextSelection()); - updateCursor(); - } -} - -void PDFCreateHighlightTextTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - if (event->button() == Qt::LeftButton) - { - if (m_selectionInfo.pageIndex != -1) - { - QPointF pagePoint; - const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - - if (m_selectionInfo.pageIndex == pageIndex) - { - // Jakub Melka: handle the selection - PDFTextLayoutGetter textLayoutGetter = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); - PDFTextLayout textLayout = textLayoutGetter; - setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint)); - - QPolygonF quadrilaterals; - PDFTextSelectionPainter textSelectionPainter(&m_textSelection); - QPainterPath path = textSelectionPainter.prepareGeometry(pageIndex, textLayoutGetter, QMatrix(), &quadrilaterals); - - if (!path.isEmpty()) - { - PDFDocumentModifier modifier(getDocument()); - - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - PDFObjectReference annotationReference; - switch (m_type) - { - case AnnotationType::Highlight: - annotationReference = modifier.getBuilder()->createAnnotationHighlight(page, quadrilaterals, Qt::yellow); - modifier.getBuilder()->setAnnotationOpacity(annotationReference, 0.2); - modifier.getBuilder()->updateAnnotationAppearanceStreams(annotationReference); - break; - - case AnnotationType::Underline: - annotationReference = modifier.getBuilder()->createAnnotationUnderline(page, quadrilaterals, Qt::black); - break; - - case AnnotationType::Squiggly: - annotationReference = modifier.getBuilder()->createAnnotationSquiggly(page, quadrilaterals, Qt::red); - break; - - case AnnotationType::StrikeOut: - annotationReference = modifier.getBuilder()->createAnnotationStrikeout(page, quadrilaterals, Qt::red); - break; - - default: - Q_ASSERT(false); - break; - } - - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - } - } - - setSelection(pdf::PDFTextSelection()); - - m_selectionInfo = SelectionInfo(); - event->accept(); - updateCursor(); - } - } -} - -void PDFCreateHighlightTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - QPointF pagePoint; - const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - PDFTextLayout textLayout = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); - m_isCursorOverText = textLayout.isHoveringOverTextBlock(pagePoint); - - if (m_selectionInfo.pageIndex != -1) - { - if (m_selectionInfo.pageIndex == pageIndex) - { - // Jakub Melka: handle the selection - setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint)); - } - else - { - setSelection(pdf::PDFTextSelection()); - } - - event->accept(); - } - - updateCursor(); -} - -void PDFCreateHighlightTextTool::updateActions() -{ - BaseClass::updateActions(); - - if (m_actionGroup) - { - const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); - m_actionGroup->setEnabled(isEnabled); - - if (!isActive() && m_actionGroup->checkedAction()) - { - m_actionGroup->checkedAction()->setChecked(false); - } - } -} - -void PDFCreateHighlightTextTool::setActiveImpl(bool active) -{ - BaseClass::setActiveImpl(active); - - if (!active) - { - // Just clear the text selection - setSelection(PDFTextSelection()); - } -} - -void PDFCreateHighlightTextTool::onActionTriggered(QAction* action) -{ - setActive(action && action->isChecked()); - - if (action) - { - m_type = static_cast(action->data().toInt()); - } -} - -void PDFCreateHighlightTextTool::updateCursor() -{ - if (isActive()) - { - if (m_isCursorOverText) - { - setCursor(QCursor(Qt::IBeamCursor)); - } - else - { - setCursor(QCursor(Qt::ArrowCursor)); - } - } -} - -void PDFCreateHighlightTextTool::setSelection(PDFTextSelection&& textSelection) -{ - if (m_textSelection != textSelection) - { - m_textSelection = qMove(textSelection); - getProxy()->repaintNeeded(); - } -} - -PDFCreateRedactRectangleTool::PDFCreateRedactRectangleTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_pickTool(nullptr) -{ - m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); - m_pickTool->setSelectionRectangleColor(Qt::black); - addTool(m_pickTool); - connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateRedactRectangleTool::onRectanglePicked); - - updateActions(); -} - -void PDFCreateRedactRectangleTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) -{ - if (pageRectangle.isEmpty()) - { - return; - } - - PDFDocumentModifier modifier(getDocument()); - - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - PDFObjectReference annotation = modifier.getBuilder()->createAnnotationRedact(page, pageRectangle, Qt::black); - modifier.getBuilder()->updateAnnotationAppearanceStreams(annotation); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - - setActive(false); -} - -PDFCreateRedactTextTool::PDFCreateRedactTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : - BaseClass(proxy, action, parent), - m_toolManager(toolManager), - m_isCursorOverText(false) -{ - updateActions(); -} - -void PDFCreateRedactTextTool::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - Q_UNUSED(compiledPage); - Q_UNUSED(errors); - - pdf::PDFTextSelectionPainter textSelectionPainter(&m_textSelection); - textSelectionPainter.draw(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix); -} - -void PDFCreateRedactTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - if (event->button() == Qt::LeftButton) - { - QPointF pagePoint; - const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - if (pageIndex != -1) - { - m_selectionInfo.pageIndex = pageIndex; - m_selectionInfo.selectionStartPoint = pagePoint; - event->accept(); - } - else - { - m_selectionInfo = SelectionInfo(); - } - - setSelection(pdf::PDFTextSelection()); - updateCursor(); - } -} - -void PDFCreateRedactTextTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - if (event->button() == Qt::LeftButton) - { - if (m_selectionInfo.pageIndex != -1) - { - QPointF pagePoint; - const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - - if (m_selectionInfo.pageIndex == pageIndex) - { - // Jakub Melka: handle the selection - PDFTextLayoutGetter textLayoutGetter = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); - PDFTextLayout textLayout = textLayoutGetter; - setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint, Qt::black)); - - QPolygonF quadrilaterals; - PDFTextSelectionPainter textSelectionPainter(&m_textSelection); - QPainterPath path = textSelectionPainter.prepareGeometry(pageIndex, textLayoutGetter, QMatrix(), &quadrilaterals); - - if (!path.isEmpty()) - { - PDFDocumentModifier modifier(getDocument()); - - PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); - modifier.getBuilder()->createAnnotationRedact(page, quadrilaterals, Qt::black); - modifier.markAnnotationsChanged(); - - if (modifier.finalize()) - { - emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - } - } - - setSelection(pdf::PDFTextSelection()); - - m_selectionInfo = SelectionInfo(); - event->accept(); - updateCursor(); - } - } -} - -void PDFCreateRedactTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - QPointF pagePoint; - const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); - PDFTextLayout textLayout = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); - m_isCursorOverText = textLayout.isHoveringOverTextBlock(pagePoint); - - if (m_selectionInfo.pageIndex != -1) - { - if (m_selectionInfo.pageIndex == pageIndex) - { - // Jakub Melka: handle the selection - setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint, Qt::black)); - } - else - { - setSelection(pdf::PDFTextSelection()); - } - - event->accept(); - } - - updateCursor(); -} - -void PDFCreateRedactTextTool::updateActions() -{ - if (QAction* action = getAction()) - { - const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); - action->setChecked(isActive()); - action->setEnabled(isEnabled); - } -} - -void PDFCreateRedactTextTool::setActiveImpl(bool active) -{ - BaseClass::setActiveImpl(active); - - if (!active) - { - // Just clear the text selection - setSelection(PDFTextSelection()); - } -} - -void PDFCreateRedactTextTool::updateCursor() -{ - if (isActive()) - { - if (m_isCursorOverText) - { - setCursor(QCursor(Qt::IBeamCursor)); - } - else - { - setCursor(QCursor(Qt::ArrowCursor)); - } - } -} - -void PDFCreateRedactTextTool::setSelection(PDFTextSelection&& textSelection) -{ - if (m_textSelection != textSelection) - { - m_textSelection = qMove(textSelection); - getProxy()->repaintNeeded(); - } -} - -} // namespace pdf +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfadvancedtools.h" +#include "pdfdocumentbuilder.h" +#include "pdfdrawwidget.h" +#include "pdfutils.h" +#include "pdfcompiler.h" + +#include +#include +#include + +namespace pdf +{ + +PDFCreateStickyNoteTool::PDFCreateStickyNoteTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent) : + BaseClass(proxy, parent), + m_toolManager(toolManager), + m_actionGroup(actionGroup), + m_pickTool(nullptr), + m_icon(pdf::TextAnnotationIcon::Comment) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreateStickyNoteTool::onPointPicked); + connect(m_actionGroup, &QActionGroup::triggered, this, &PDFCreateStickyNoteTool::onActionTriggered); + + updateActions(); +} + +void PDFCreateStickyNoteTool::updateActions() +{ + BaseClass::updateActions(); + + if (m_actionGroup) + { + const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); + m_actionGroup->setEnabled(isEnabled); + + if (!isActive() && m_actionGroup->checkedAction()) + { + m_actionGroup->checkedAction()->setChecked(false); + } + } +} + +void PDFCreateStickyNoteTool::onActionTriggered(QAction* action) +{ + setActive(action && action->isChecked()); + + if (action) + { + m_icon = static_cast(action->data().toInt()); + } +} + +void PDFCreateStickyNoteTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + bool ok = false; + QString text = QInputDialog::getText(getProxy()->getWidget(), tr("Sticky note"), tr("Enter text to be displayed in the sticky note"), QLineEdit::Normal, QString(), &ok); + + if (ok && !text.isEmpty()) + { + PDFDocumentModifier modifier(getDocument()); + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + modifier.getBuilder()->createAnnotationText(page, QRectF(pagePoint, QSizeF(0, 0)), m_icon, userName, QString(), text, false); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } + else + { + m_pickTool->resetTool(); + } +} + +PDFCreateHyperlinkTool::PDFCreateHyperlinkTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_pickTool(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateHyperlinkTool::onRectanglePicked); + + updateActions(); +} + +LinkHighlightMode PDFCreateHyperlinkTool::getHighlightMode() const +{ + return m_highlightMode; +} + +void PDFCreateHyperlinkTool::setHighlightMode(const LinkHighlightMode& highlightMode) +{ + m_highlightMode = highlightMode; +} + +void PDFCreateHyperlinkTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + bool ok = false; + QString url = QInputDialog::getText(getProxy()->getWidget(), tr("Hyperlink"), tr("Enter url address of the hyperlink"), QLineEdit::Normal, QString(), &ok); + + if (ok && !url.isEmpty()) + { + PDFDocumentModifier modifier(getDocument()); + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + modifier.getBuilder()->createAnnotationLink(page, pageRectangle, url, m_highlightMode); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } +} + +PDFCreateFreeTextTool::PDFCreateFreeTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_pickTool(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateFreeTextTool::onRectanglePicked); + + updateActions(); +} + +void PDFCreateFreeTextTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + bool ok = false; + QString text = QInputDialog::getMultiLineText(getProxy()->getWidget(), tr("Text"), tr("Enter text for free text panel"), QString(), &ok); + + if (ok && !text.isEmpty()) + { + PDFDocumentModifier modifier(getDocument()); + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + modifier.getBuilder()->createAnnotationFreeText(page, pageRectangle, userName, QString(), text, TextAlignment(Qt::AlignLeft | Qt::AlignTop)); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } +} + +PDFCreateAnnotationTool::PDFCreateAnnotationTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent) +{ + +} + +void PDFCreateAnnotationTool::updateActions() +{ + if (QAction* action = getAction()) + { + const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); + action->setChecked(isActive()); + action->setEnabled(isEnabled); + } +} + +PDFCreateLineTypeTool::PDFCreateLineTypeTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, PDFCreateLineTypeTool::Type type, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_pickTool(nullptr), + m_type(type), + m_penWidth(1.0), + m_strokeColor(Qt::red), + m_fillColor(Qt::yellow) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreateLineTypeTool::onPointPicked); + + m_fillColor.setAlphaF(0.2); + + updateActions(); +} + +void PDFCreateLineTypeTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + Q_UNUSED(pageIndex); + Q_UNUSED(pagePoint); + + if (m_type == Type::Line && m_pickTool->getPickedPoints().size() == 2) + { + finishDefinition(); + } +} + +void PDFCreateLineTypeTool::finishDefinition() +{ + const std::vector& pickedPoints = m_pickTool->getPickedPoints(); + + switch (m_type) + { + case Type::Line: + { + if (pickedPoints.size() >= 2) + { + PDFDocumentModifier modifier(getDocument()); + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex())->getPageReference(); + modifier.getBuilder()->createAnnotationLine(page, QRectF(), pickedPoints.front(), pickedPoints.back(), m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString(), AnnotationLineEnding::None, AnnotationLineEnding::None); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } + break; + } + + case Type::PolyLine: + { + if (pickedPoints.size() >= 3) + { + PDFDocumentModifier modifier(getDocument()); + + QPolygonF polygon; + for (const QPointF& point : pickedPoints) + { + polygon << point; + } + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex())->getPageReference(); + modifier.getBuilder()->createAnnotationPolyline(page, polygon, m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString(), AnnotationLineEnding::None, AnnotationLineEnding::None); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } + break; + } + + case Type::Polygon: + { + if (pickedPoints.size() >= 3) + { + PDFDocumentModifier modifier(getDocument()); + + QPolygonF polygon; + for (const QPointF& point : pickedPoints) + { + polygon << point; + } + if (!polygon.isClosed()) + { + polygon << pickedPoints.front(); + } + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pickTool->getPageIndex())->getPageReference(); + PDFObjectReference annotation = modifier.getBuilder()->createAnnotationPolygon(page, polygon, m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString()); + modifier.getBuilder()->setAnnotationFillOpacity(annotation, m_fillColor.alphaF()); + modifier.getBuilder()->updateAnnotationAppearanceStreams(annotation); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } + break; + } + + default: + Q_ASSERT(false); + break; + } + + m_pickTool->resetTool(); +} + +QColor PDFCreateLineTypeTool::getFillColor() const +{ + return m_fillColor; +} + +void PDFCreateLineTypeTool::setFillColor(const QColor& fillColor) +{ + m_fillColor = fillColor; +} + +QColor PDFCreateLineTypeTool::getStrokeColor() const +{ + return m_strokeColor; +} + +void PDFCreateLineTypeTool::setStrokeColor(const QColor& strokeColor) +{ + m_strokeColor = strokeColor; +} + +PDFReal PDFCreateLineTypeTool::getPenWidth() const +{ + return m_penWidth; +} + +void PDFCreateLineTypeTool::setPenWidth(PDFReal penWidth) +{ + m_penWidth = penWidth; +} + +void PDFCreateLineTypeTool::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + switch (m_type) + { + case Type::PolyLine: + case Type::Polygon: + { + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) + { + finishDefinition(); + event->accept(); + } + else + { + event->ignore(); + } + + break; + } + + default: + event->ignore(); + break; + } + + if (!event->isAccepted()) + { + BaseClass::keyPressEvent(widget, event); + } +} + +void PDFCreateLineTypeTool::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + switch (m_type) + { + case Type::PolyLine: + case Type::Polygon: + { + if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) + { + event->accept(); + } + else + { + event->ignore(); + } + + break; + } + + default: + event->ignore(); + break; + } + + if (!event->isAccepted()) + { + BaseClass::keyReleaseEvent(widget, event); + } +} + +void PDFCreateLineTypeTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(pageIndex); + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex()) + { + return; + } + + const std::vector& points = m_pickTool->getPickedPoints(); + if (points.empty()) + { + return; + } + + QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + + QPen pen(m_strokeColor); + QBrush brush(m_fillColor, Qt::SolidPattern); + pen.setWidthF(m_penWidth); + painter->setPen(qMove(pen)); + painter->setBrush(qMove(brush)); + painter->setRenderHint(QPainter::Antialiasing); + + switch (m_type) + { + case Type::Line: + case Type::PolyLine: + { + for (size_t i = 1; i < points.size(); ++i) + { + painter->drawLine(points[i - 1], points[i]); + } + painter->drawLine(points.back(), mousePoint); + break; + } + + case Type::Polygon: + { + QPainterPath path; + path.moveTo(points.front()); + for (size_t i = 1; i < points.size(); ++i) + { + path.lineTo(points[i]); + } + path.lineTo(mousePoint); + path.closeSubpath(); + + painter->drawPath(path); + break; + } + + default: + Q_ASSERT(false); + break; + } +} + +PDFCreateEllipseTool::PDFCreateEllipseTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_pickTool(nullptr), + m_penWidth(1.0), + m_strokeColor(Qt::red), + m_fillColor(Qt::yellow) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateEllipseTool::onRectanglePicked); + + m_fillColor.setAlphaF(0.2); + + updateActions(); +} + +PDFReal PDFCreateEllipseTool::getPenWidth() const +{ + return m_penWidth; +} + +void PDFCreateEllipseTool::setPenWidth(PDFReal penWidth) +{ + m_penWidth = penWidth; +} + +QColor PDFCreateEllipseTool::getStrokeColor() const +{ + return m_strokeColor; +} + +void PDFCreateEllipseTool::setStrokeColor(const QColor& strokeColor) +{ + m_strokeColor = strokeColor; +} + +QColor PDFCreateEllipseTool::getFillColor() const +{ + return m_fillColor; +} + +void PDFCreateEllipseTool::setFillColor(const QColor& fillColor) +{ + m_fillColor = fillColor; +} + +void PDFCreateEllipseTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex()) + { + return; + } + + const std::vector& points = m_pickTool->getPickedPoints(); + if (points.empty()) + { + return; + } + + QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + + QPen pen(m_strokeColor); + QBrush brush(m_fillColor, Qt::SolidPattern); + pen.setWidthF(m_penWidth); + painter->setPen(qMove(pen)); + painter->setBrush(qMove(brush)); + painter->setRenderHint(QPainter::Antialiasing); + + QPointF point = points.front(); + qreal xMin = qMin(point.x(), mousePoint.x()); + qreal xMax = qMax(point.x(), mousePoint.x()); + qreal yMin = qMin(point.y(), mousePoint.y()); + qreal yMax = qMax(point.y(), mousePoint.y()); + qreal width = xMax - xMin; + qreal height = yMax - yMin; + + if (!qFuzzyIsNull(width) && !qFuzzyIsNull(height)) + { + QRectF rect(xMin, yMin, width, height); + painter->drawEllipse(rect); + } +} + +void PDFCreateEllipseTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + PDFDocumentModifier modifier(getDocument()); + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + PDFObjectReference annotation = modifier.getBuilder()->createAnnotationCircle(page, pageRectangle, m_penWidth, m_fillColor, m_strokeColor, userName, QString(), QString()); + modifier.getBuilder()->setAnnotationFillOpacity(annotation, m_fillColor.alphaF()); + modifier.getBuilder()->updateAnnotationAppearanceStreams(annotation); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); +} + +PDFCreateFreehandCurveTool::PDFCreateFreehandCurveTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_pageIndex(-1), + m_penWidth(1.0), + m_strokeColor(Qt::red) +{ + +} + +void PDFCreateFreehandCurveTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pageIndex || m_pickedPoints.empty()) + { + return; + } + + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + + QPen pen(m_strokeColor); + pen.setWidthF(m_penWidth); + painter->setPen(qMove(pen)); + painter->setRenderHint(QPainter::Antialiasing); + + for (size_t i = 1; i < m_pickedPoints.size(); ++i) + { + painter->drawLine(m_pickedPoints[i - 1], m_pickedPoints[i]); + } +} + +void PDFCreateFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_pageIndex == -1 || m_pageIndex == pageIndex)) // We are under current page + { + m_pageIndex = pageIndex; + m_pickedPoints.push_back(pagePoint); + } + } + else if (event->button() == Qt::RightButton) + { + resetTool(); + } + + getProxy()->repaintNeeded(); +} + +void PDFCreateFreehandCurveTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_pageIndex == pageIndex)) // We are under current page + { + m_pageIndex = pageIndex; + m_pickedPoints.push_back(pagePoint); + + if (m_pickedPoints.size() >= 3) + { + PDFDocumentModifier modifier(getDocument()); + + QPolygonF polygon; + for (const QPointF& point : m_pickedPoints) + { + polygon << point; + } + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(m_pageIndex)->getPageReference(); + modifier.getBuilder()->createAnnotationPolyline(page, polygon, m_penWidth, Qt::black, m_strokeColor, userName, QString(), QString(), AnnotationLineEnding::None, AnnotationLineEnding::None); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); + } + } + + resetTool(); + } + + emit getProxy()->repaintNeeded(); +} + +void PDFCreateFreehandCurveTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->buttons() & Qt::LeftButton && m_pageIndex != -1) + { + // Try to add point to the path + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex == m_pageIndex) + { + m_pickedPoints.push_back(pagePoint); + } + + getProxy()->repaintNeeded(); + } +} + +PDFReal PDFCreateFreehandCurveTool::getPenWidth() const +{ + return m_penWidth; +} + +void PDFCreateFreehandCurveTool::setPenWidth(const PDFReal& penWidth) +{ + m_penWidth = penWidth; +} + +QColor PDFCreateFreehandCurveTool::getStrokeColor() const +{ + return m_strokeColor; +} + +void PDFCreateFreehandCurveTool::setStrokeColor(const QColor& strokeColor) +{ + m_strokeColor = strokeColor; +} + +void PDFCreateFreehandCurveTool::resetTool() +{ + m_pageIndex = -1; + m_pickedPoints.clear(); +} + +PDFCreateStampTool::PDFCreateStampTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent) : + BaseClass(proxy, parent), + m_pageIndex(-1), + m_toolManager(toolManager), + m_actionGroup(actionGroup), + m_pickTool(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreateStampTool::onPointPicked); + connect(m_actionGroup, &QActionGroup::triggered, this, &PDFCreateStampTool::onActionTriggered); + + m_stampAnnotation.setStrokingOpacity(0.5); + m_stampAnnotation.setFillingOpacity(0.5); + + updateActions(); +} + +void PDFCreateStampTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(pagePointToDevicePointMatrix); + Q_UNUSED(errors); + + if (pageIndex != m_pageIndex) + { + return; + } + + const PDFPage* page = getDocument()->getCatalog()->getPage(pageIndex); + QRectF rectangle = m_stampAnnotation.getRectangle(); + QMatrix matrix = getProxy()->getAnnotationManager()->prepareTransformations(pagePointToDevicePointMatrix, painter->device(), m_stampAnnotation.getFlags(), page, rectangle); + painter->setWorldMatrix(matrix, true); + + AnnotationDrawParameters parameters; + parameters.painter = painter; + parameters.annotation = const_cast(&m_stampAnnotation); + parameters.key.first = PDFAppeareanceStreams::Appearance::Normal; + parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors); + + m_stampAnnotation.draw(parameters); +} + +void PDFCreateStampTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + BaseClass::mouseMoveEvent(widget, event); + + // Try to add point to the path + QPointF pagePoint; + m_pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (m_pageIndex != -1) + { + m_stampAnnotation.setRectangle(QRectF(pagePoint, QSizeF(0, 0))); + } +} + +void PDFCreateStampTool::updateActions() +{ + BaseClass::updateActions(); + + if (m_actionGroup) + { + const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); + m_actionGroup->setEnabled(isEnabled); + + if (!isActive() && m_actionGroup->checkedAction()) + { + m_actionGroup->checkedAction()->setChecked(false); + } + } +} + +void PDFCreateStampTool::onActionTriggered(QAction* action) +{ + setActive(action && action->isChecked()); + + if (action) + { + m_stampAnnotation.setStamp(static_cast(action->data().toInt())); + } +} + +void PDFCreateStampTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + PDFDocumentModifier modifier(getDocument()); + + QString userName = PDFSysUtils::getUserName(); + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + modifier.getBuilder()->createAnnotationStamp(page, QRectF(pagePoint, QSizeF(0, 0)), m_stampAnnotation.getStamp(), userName, QString(), QString()); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); +} + +PDFCreateHighlightTextTool::PDFCreateHighlightTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent) : + BaseClass(proxy, parent), + m_toolManager(toolManager), + m_actionGroup(actionGroup), + m_type(AnnotationType::Highlight), + m_isCursorOverText(false) +{ + connect(m_actionGroup, &QActionGroup::triggered, this, &PDFCreateHighlightTextTool::onActionTriggered); + + updateActions(); +} + +void PDFCreateHighlightTextTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(errors); + + pdf::PDFTextSelectionPainter textSelectionPainter(&m_textSelection); + textSelectionPainter.draw(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix); +} + +void PDFCreateHighlightTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1) + { + m_selectionInfo.pageIndex = pageIndex; + m_selectionInfo.selectionStartPoint = pagePoint; + event->accept(); + } + else + { + m_selectionInfo = SelectionInfo(); + } + + setSelection(pdf::PDFTextSelection()); + updateCursor(); + } +} + +void PDFCreateHighlightTextTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + if (m_selectionInfo.pageIndex != -1) + { + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + + if (m_selectionInfo.pageIndex == pageIndex) + { + // Jakub Melka: handle the selection + PDFTextLayoutGetter textLayoutGetter = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); + PDFTextLayout textLayout = textLayoutGetter; + setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint)); + + QPolygonF quadrilaterals; + PDFTextSelectionPainter textSelectionPainter(&m_textSelection); + QPainterPath path = textSelectionPainter.prepareGeometry(pageIndex, textLayoutGetter, QMatrix(), &quadrilaterals); + + if (!path.isEmpty()) + { + PDFDocumentModifier modifier(getDocument()); + + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + PDFObjectReference annotationReference; + switch (m_type) + { + case AnnotationType::Highlight: + annotationReference = modifier.getBuilder()->createAnnotationHighlight(page, quadrilaterals, Qt::yellow); + modifier.getBuilder()->setAnnotationOpacity(annotationReference, 0.2); + modifier.getBuilder()->updateAnnotationAppearanceStreams(annotationReference); + break; + + case AnnotationType::Underline: + annotationReference = modifier.getBuilder()->createAnnotationUnderline(page, quadrilaterals, Qt::black); + break; + + case AnnotationType::Squiggly: + annotationReference = modifier.getBuilder()->createAnnotationSquiggly(page, quadrilaterals, Qt::red); + break; + + case AnnotationType::StrikeOut: + annotationReference = modifier.getBuilder()->createAnnotationStrikeout(page, quadrilaterals, Qt::red); + break; + + default: + Q_ASSERT(false); + break; + } + + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } + } + + setSelection(pdf::PDFTextSelection()); + + m_selectionInfo = SelectionInfo(); + event->accept(); + updateCursor(); + } + } +} + +void PDFCreateHighlightTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + PDFTextLayout textLayout = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); + m_isCursorOverText = textLayout.isHoveringOverTextBlock(pagePoint); + + if (m_selectionInfo.pageIndex != -1) + { + if (m_selectionInfo.pageIndex == pageIndex) + { + // Jakub Melka: handle the selection + setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint)); + } + else + { + setSelection(pdf::PDFTextSelection()); + } + + event->accept(); + } + + updateCursor(); +} + +void PDFCreateHighlightTextTool::updateActions() +{ + BaseClass::updateActions(); + + if (m_actionGroup) + { + const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); + m_actionGroup->setEnabled(isEnabled); + + if (!isActive() && m_actionGroup->checkedAction()) + { + m_actionGroup->checkedAction()->setChecked(false); + } + } +} + +void PDFCreateHighlightTextTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (!active) + { + // Just clear the text selection + setSelection(PDFTextSelection()); + } +} + +void PDFCreateHighlightTextTool::onActionTriggered(QAction* action) +{ + setActive(action && action->isChecked()); + + if (action) + { + m_type = static_cast(action->data().toInt()); + } +} + +void PDFCreateHighlightTextTool::updateCursor() +{ + if (isActive()) + { + if (m_isCursorOverText) + { + setCursor(QCursor(Qt::IBeamCursor)); + } + else + { + setCursor(QCursor(Qt::ArrowCursor)); + } + } +} + +void PDFCreateHighlightTextTool::setSelection(PDFTextSelection&& textSelection) +{ + if (m_textSelection != textSelection) + { + m_textSelection = qMove(textSelection); + getProxy()->repaintNeeded(); + } +} + +PDFCreateRedactRectangleTool::PDFCreateRedactRectangleTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_pickTool(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setSelectionRectangleColor(Qt::black); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreateRedactRectangleTool::onRectanglePicked); + + updateActions(); +} + +void PDFCreateRedactRectangleTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + PDFDocumentModifier modifier(getDocument()); + + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + PDFObjectReference annotation = modifier.getBuilder()->createAnnotationRedact(page, pageRectangle, Qt::black); + modifier.getBuilder()->updateAnnotationAppearanceStreams(annotation); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + + setActive(false); +} + +PDFCreateRedactTextTool::PDFCreateRedactTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_toolManager(toolManager), + m_isCursorOverText(false) +{ + updateActions(); +} + +void PDFCreateRedactTextTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(errors); + + pdf::PDFTextSelectionPainter textSelectionPainter(&m_textSelection); + textSelectionPainter.draw(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix); +} + +void PDFCreateRedactTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1) + { + m_selectionInfo.pageIndex = pageIndex; + m_selectionInfo.selectionStartPoint = pagePoint; + event->accept(); + } + else + { + m_selectionInfo = SelectionInfo(); + } + + setSelection(pdf::PDFTextSelection()); + updateCursor(); + } +} + +void PDFCreateRedactTextTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (event->button() == Qt::LeftButton) + { + if (m_selectionInfo.pageIndex != -1) + { + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + + if (m_selectionInfo.pageIndex == pageIndex) + { + // Jakub Melka: handle the selection + PDFTextLayoutGetter textLayoutGetter = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); + PDFTextLayout textLayout = textLayoutGetter; + setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint, Qt::black)); + + QPolygonF quadrilaterals; + PDFTextSelectionPainter textSelectionPainter(&m_textSelection); + QPainterPath path = textSelectionPainter.prepareGeometry(pageIndex, textLayoutGetter, QMatrix(), &quadrilaterals); + + if (!path.isEmpty()) + { + PDFDocumentModifier modifier(getDocument()); + + PDFObjectReference page = getDocument()->getCatalog()->getPage(pageIndex)->getPageReference(); + modifier.getBuilder()->createAnnotationRedact(page, quadrilaterals, Qt::black); + modifier.markAnnotationsChanged(); + + if (modifier.finalize()) + { + emit m_toolManager->documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } + } + + setSelection(pdf::PDFTextSelection()); + + m_selectionInfo = SelectionInfo(); + event->accept(); + updateCursor(); + } + } +} + +void PDFCreateRedactTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + QPointF pagePoint; + const PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + PDFTextLayout textLayout = getProxy()->getTextLayoutCompiler()->getTextLayoutLazy(pageIndex); + m_isCursorOverText = textLayout.isHoveringOverTextBlock(pagePoint); + + if (m_selectionInfo.pageIndex != -1) + { + if (m_selectionInfo.pageIndex == pageIndex) + { + // Jakub Melka: handle the selection + setSelection(textLayout.createTextSelection(pageIndex, m_selectionInfo.selectionStartPoint, pagePoint, Qt::black)); + } + else + { + setSelection(pdf::PDFTextSelection()); + } + + event->accept(); + } + + updateCursor(); +} + +void PDFCreateRedactTextTool::updateActions() +{ + if (QAction* action = getAction()) + { + const bool isEnabled = getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::ModifyInteractiveItems); + action->setChecked(isActive()); + action->setEnabled(isEnabled); + } +} + +void PDFCreateRedactTextTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (!active) + { + // Just clear the text selection + setSelection(PDFTextSelection()); + } +} + +void PDFCreateRedactTextTool::updateCursor() +{ + if (isActive()) + { + if (m_isCursorOverText) + { + setCursor(QCursor(Qt::IBeamCursor)); + } + else + { + setCursor(QCursor(Qt::ArrowCursor)); + } + } +} + +void PDFCreateRedactTextTool::setSelection(PDFTextSelection&& textSelection) +{ + if (m_textSelection != textSelection) + { + m_textSelection = qMove(textSelection); + getProxy()->repaintNeeded(); + } +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfadvancedtools.h b/Pdf4QtLib/sources/pdfadvancedtools.h index 21ff273..888a7d3 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.h +++ b/Pdf4QtLib/sources/pdfadvancedtools.h @@ -1,378 +1,378 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFADVANCEDTOOLS_H -#define PDFADVANCEDTOOLS_H - -#include "pdfwidgettool.h" -#include "pdfannotation.h" - -class QActionGroup; - -namespace pdf -{ - -/// Tool that creates 'sticky note' annotations. Multiple types of sticky -/// notes are available, user can select a type of sticky note. When -/// user select a point, popup window appears and user can enter a text. -class PDF4QTLIBSHARED_EXPORT PDFCreateStickyNoteTool : public PDFWidgetTool -{ - Q_OBJECT - -private: - using BaseClass = PDFWidgetTool; - -public: - explicit PDFCreateStickyNoteTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent); - -protected: - virtual void updateActions() override; - -private: - void onActionTriggered(QAction* action); - void onPointPicked(PDFInteger pageIndex, QPointF pagePoint); - - PDFToolManager* m_toolManager; - QActionGroup* m_actionGroup; - PDFPickTool* m_pickTool; - TextAnnotationIcon m_icon; -}; - -class PDF4QTLIBSHARED_EXPORT PDFCreateAnnotationTool : public PDFWidgetTool -{ - Q_OBJECT - -private: - using BaseClass = PDFWidgetTool; - -public: - explicit PDFCreateAnnotationTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent); - -protected: - virtual void updateActions() override; -}; - -/// Tool that creates url link annotation. Multiple types of link highlights -/// are available, user can select a link highlight. When link annotation -/// is clicked, url address is triggered. -class PDF4QTLIBSHARED_EXPORT PDFCreateHyperlinkTool : public PDFCreateAnnotationTool -{ - Q_OBJECT - -private: - using BaseClass = PDFCreateAnnotationTool; - -public: - explicit PDFCreateHyperlinkTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - - LinkHighlightMode getHighlightMode() const; - void setHighlightMode(const LinkHighlightMode& highlightMode); - -private: - void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); - - PDFToolManager* m_toolManager; - PDFPickTool* m_pickTool; - LinkHighlightMode m_highlightMode = LinkHighlightMode::Outline; -}; - -/// Tool that creates free text note without callout line. -class PDF4QTLIBSHARED_EXPORT PDFCreateFreeTextTool : public PDFCreateAnnotationTool -{ - Q_OBJECT - -private: - using BaseClass = PDFCreateAnnotationTool; - -public: - explicit PDFCreateFreeTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - -private: - void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); - - PDFToolManager* m_toolManager; - PDFPickTool* m_pickTool; -}; - -/// Tool that creates line/polyline/polygon annotations. -class PDF4QTLIBSHARED_EXPORT PDFCreateLineTypeTool : public PDFCreateAnnotationTool -{ - Q_OBJECT - -private: - using BaseClass = PDFCreateAnnotationTool; - -public: - enum class Type - { - Line, - PolyLine, - Polygon - }; - - explicit PDFCreateLineTypeTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, Type type, QAction* action, QObject* parent); - - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; - virtual void drawPage(QPainter* painter, PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - PDFReal getPenWidth() const; - void setPenWidth(PDFReal penWidth); - - QColor getStrokeColor() const; - void setStrokeColor(const QColor& strokeColor); - - QColor getFillColor() const; - void setFillColor(const QColor& fillColor); - -private: - void onPointPicked(PDFInteger pageIndex, QPointF pagePoint); - void finishDefinition(); - - PDFToolManager* m_toolManager; - PDFPickTool* m_pickTool; - Type m_type; - PDFReal m_penWidth; - QColor m_strokeColor; - QColor m_fillColor; -}; - -/// Tool that creates ellipse annotation. -class PDF4QTLIBSHARED_EXPORT PDFCreateEllipseTool : public PDFCreateAnnotationTool -{ - Q_OBJECT - -private: - using BaseClass = PDFCreateAnnotationTool; - -public: - explicit PDFCreateEllipseTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - - virtual void drawPage(QPainter* painter, PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - PDFReal getPenWidth() const; - void setPenWidth(PDFReal penWidth); - - QColor getStrokeColor() const; - void setStrokeColor(const QColor& strokeColor); - - QColor getFillColor() const; - void setFillColor(const QColor& fillColor); - -private: - void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); - - PDFToolManager* m_toolManager; - PDFPickTool* m_pickTool; - PDFReal m_penWidth; - QColor m_strokeColor; - QColor m_fillColor; -}; - -class PDF4QTLIBSHARED_EXPORT PDFCreateFreehandCurveTool : public PDFCreateAnnotationTool -{ - Q_OBJECT - -private: - using BaseClass = PDFCreateAnnotationTool; - -public: - explicit PDFCreateFreehandCurveTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - - virtual void drawPage(QPainter* painter, PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; - - PDFReal getPenWidth() const; - void setPenWidth(const PDFReal& penWidth); - - QColor getStrokeColor() const; - void setStrokeColor(const QColor& strokeColor); - -private: - void resetTool(); - - PDFToolManager* m_toolManager; - PDFInteger m_pageIndex; - std::vector m_pickedPoints; - PDFReal m_penWidth; - QColor m_strokeColor; -}; - -/// Tool that creates 'stamp' annotations. Multiple types of stamps -/// are available, user can select a type of stamp (text). -class PDF4QTLIBSHARED_EXPORT PDFCreateStampTool : public PDFWidgetTool -{ - Q_OBJECT - -private: - using BaseClass = PDFWidgetTool; - -public: - explicit PDFCreateStampTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent); - - virtual void drawPage(QPainter* painter, PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; - -protected: - virtual void updateActions() override; - -private: - void onActionTriggered(QAction* action); - void onPointPicked(PDFInteger pageIndex, QPointF pagePoint); - - pdf::PDFInteger m_pageIndex; - PDFToolManager* m_toolManager; - QActionGroup* m_actionGroup; - PDFPickTool* m_pickTool; - PDFStampAnnotation m_stampAnnotation; -}; - -/// Tool for highlighting of text in document -class PDF4QTLIBSHARED_EXPORT PDFCreateHighlightTextTool : public PDFWidgetTool -{ - Q_OBJECT - -private: - using BaseClass = PDFWidgetTool; - -public: - - /// Creates new highlight text tool - /// \param proxy Proxy - /// \param type Annotation type, must be one of: Highlight, Underline, Squiggly, StrikeOut - /// \param actionGroup Action group with actions. Each action must define annotation type. - /// \param parent Parent - explicit PDFCreateHighlightTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent); - - virtual void drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; - -protected: - virtual void updateActions() override; - virtual void setActiveImpl(bool active) override; - -private: - void onActionTriggered(QAction* action); - void updateCursor(); - void setSelection(pdf::PDFTextSelection&& textSelection); - - struct SelectionInfo - { - PDFInteger pageIndex = -1; - QPointF selectionStartPoint; - }; - - PDFToolManager* m_toolManager; - QActionGroup* m_actionGroup; - AnnotationType m_type; - pdf::PDFTextSelection m_textSelection; - SelectionInfo m_selectionInfo; - bool m_isCursorOverText; -}; - -/// Tool that creates redaction annotation from rectangle. Rectangle is not -/// selected from the text, it is just any rectangle. -class PDF4QTLIBSHARED_EXPORT PDFCreateRedactRectangleTool : public PDFCreateAnnotationTool -{ - Q_OBJECT - -private: - using BaseClass = PDFCreateAnnotationTool; - -public: - explicit PDFCreateRedactRectangleTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - -private: - void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); - - PDFToolManager* m_toolManager; - PDFPickTool* m_pickTool; -}; - -/// Tool for redaction of text in document. Creates redaction annotation from text selection. -class PDF4QTLIBSHARED_EXPORT PDFCreateRedactTextTool : public PDFWidgetTool -{ - Q_OBJECT - -private: - using BaseClass = PDFWidgetTool; - -public: - explicit PDFCreateRedactTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - - virtual void drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; - -protected: - virtual void updateActions() override; - virtual void setActiveImpl(bool active) override; - -private: - void updateCursor(); - void setSelection(pdf::PDFTextSelection&& textSelection); - - struct SelectionInfo - { - PDFInteger pageIndex = -1; - QPointF selectionStartPoint; - }; - - PDFToolManager* m_toolManager; - pdf::PDFTextSelection m_textSelection; - SelectionInfo m_selectionInfo; - bool m_isCursorOverText; -}; - -} // namespace pdf - -#endif // PDFADVANCEDTOOLS_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFADVANCEDTOOLS_H +#define PDFADVANCEDTOOLS_H + +#include "pdfwidgettool.h" +#include "pdfannotation.h" + +class QActionGroup; + +namespace pdf +{ + +/// Tool that creates 'sticky note' annotations. Multiple types of sticky +/// notes are available, user can select a type of sticky note. When +/// user select a point, popup window appears and user can enter a text. +class PDF4QTLIBSHARED_EXPORT PDFCreateStickyNoteTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + explicit PDFCreateStickyNoteTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent); + +protected: + virtual void updateActions() override; + +private: + void onActionTriggered(QAction* action); + void onPointPicked(PDFInteger pageIndex, QPointF pagePoint); + + PDFToolManager* m_toolManager; + QActionGroup* m_actionGroup; + PDFPickTool* m_pickTool; + TextAnnotationIcon m_icon; +}; + +class PDF4QTLIBSHARED_EXPORT PDFCreateAnnotationTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + explicit PDFCreateAnnotationTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent); + +protected: + virtual void updateActions() override; +}; + +/// Tool that creates url link annotation. Multiple types of link highlights +/// are available, user can select a link highlight. When link annotation +/// is clicked, url address is triggered. +class PDF4QTLIBSHARED_EXPORT PDFCreateHyperlinkTool : public PDFCreateAnnotationTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreateAnnotationTool; + +public: + explicit PDFCreateHyperlinkTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); + + LinkHighlightMode getHighlightMode() const; + void setHighlightMode(const LinkHighlightMode& highlightMode); + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFToolManager* m_toolManager; + PDFPickTool* m_pickTool; + LinkHighlightMode m_highlightMode = LinkHighlightMode::Outline; +}; + +/// Tool that creates free text note without callout line. +class PDF4QTLIBSHARED_EXPORT PDFCreateFreeTextTool : public PDFCreateAnnotationTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreateAnnotationTool; + +public: + explicit PDFCreateFreeTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFToolManager* m_toolManager; + PDFPickTool* m_pickTool; +}; + +/// Tool that creates line/polyline/polygon annotations. +class PDF4QTLIBSHARED_EXPORT PDFCreateLineTypeTool : public PDFCreateAnnotationTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreateAnnotationTool; + +public: + enum class Type + { + Line, + PolyLine, + Polygon + }; + + explicit PDFCreateLineTypeTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, Type type, QAction* action, QObject* parent); + + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; + virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + PDFReal getPenWidth() const; + void setPenWidth(PDFReal penWidth); + + QColor getStrokeColor() const; + void setStrokeColor(const QColor& strokeColor); + + QColor getFillColor() const; + void setFillColor(const QColor& fillColor); + +private: + void onPointPicked(PDFInteger pageIndex, QPointF pagePoint); + void finishDefinition(); + + PDFToolManager* m_toolManager; + PDFPickTool* m_pickTool; + Type m_type; + PDFReal m_penWidth; + QColor m_strokeColor; + QColor m_fillColor; +}; + +/// Tool that creates ellipse annotation. +class PDF4QTLIBSHARED_EXPORT PDFCreateEllipseTool : public PDFCreateAnnotationTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreateAnnotationTool; + +public: + explicit PDFCreateEllipseTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); + + virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + PDFReal getPenWidth() const; + void setPenWidth(PDFReal penWidth); + + QColor getStrokeColor() const; + void setStrokeColor(const QColor& strokeColor); + + QColor getFillColor() const; + void setFillColor(const QColor& fillColor); + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFToolManager* m_toolManager; + PDFPickTool* m_pickTool; + PDFReal m_penWidth; + QColor m_strokeColor; + QColor m_fillColor; +}; + +class PDF4QTLIBSHARED_EXPORT PDFCreateFreehandCurveTool : public PDFCreateAnnotationTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreateAnnotationTool; + +public: + explicit PDFCreateFreehandCurveTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); + + virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + + PDFReal getPenWidth() const; + void setPenWidth(const PDFReal& penWidth); + + QColor getStrokeColor() const; + void setStrokeColor(const QColor& strokeColor); + +private: + void resetTool(); + + PDFToolManager* m_toolManager; + PDFInteger m_pageIndex; + std::vector m_pickedPoints; + PDFReal m_penWidth; + QColor m_strokeColor; +}; + +/// Tool that creates 'stamp' annotations. Multiple types of stamps +/// are available, user can select a type of stamp (text). +class PDF4QTLIBSHARED_EXPORT PDFCreateStampTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + explicit PDFCreateStampTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent); + + virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + +protected: + virtual void updateActions() override; + +private: + void onActionTriggered(QAction* action); + void onPointPicked(PDFInteger pageIndex, QPointF pagePoint); + + pdf::PDFInteger m_pageIndex; + PDFToolManager* m_toolManager; + QActionGroup* m_actionGroup; + PDFPickTool* m_pickTool; + PDFStampAnnotation m_stampAnnotation; +}; + +/// Tool for highlighting of text in document +class PDF4QTLIBSHARED_EXPORT PDFCreateHighlightTextTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + + /// Creates new highlight text tool + /// \param proxy Proxy + /// \param type Annotation type, must be one of: Highlight, Underline, Squiggly, StrikeOut + /// \param actionGroup Action group with actions. Each action must define annotation type. + /// \param parent Parent + explicit PDFCreateHighlightTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QActionGroup* actionGroup, QObject* parent); + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + +protected: + virtual void updateActions() override; + virtual void setActiveImpl(bool active) override; + +private: + void onActionTriggered(QAction* action); + void updateCursor(); + void setSelection(pdf::PDFTextSelection&& textSelection); + + struct SelectionInfo + { + PDFInteger pageIndex = -1; + QPointF selectionStartPoint; + }; + + PDFToolManager* m_toolManager; + QActionGroup* m_actionGroup; + AnnotationType m_type; + pdf::PDFTextSelection m_textSelection; + SelectionInfo m_selectionInfo; + bool m_isCursorOverText; +}; + +/// Tool that creates redaction annotation from rectangle. Rectangle is not +/// selected from the text, it is just any rectangle. +class PDF4QTLIBSHARED_EXPORT PDFCreateRedactRectangleTool : public PDFCreateAnnotationTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreateAnnotationTool; + +public: + explicit PDFCreateRedactRectangleTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFToolManager* m_toolManager; + PDFPickTool* m_pickTool; +}; + +/// Tool for redaction of text in document. Creates redaction annotation from text selection. +class PDF4QTLIBSHARED_EXPORT PDFCreateRedactTextTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + explicit PDFCreateRedactTextTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + +protected: + virtual void updateActions() override; + virtual void setActiveImpl(bool active) override; + +private: + void updateCursor(); + void setSelection(pdf::PDFTextSelection&& textSelection); + + struct SelectionInfo + { + PDFInteger pageIndex = -1; + QPointF selectionStartPoint; + }; + + PDFToolManager* m_toolManager; + pdf::PDFTextSelection m_textSelection; + SelectionInfo m_selectionInfo; + bool m_isCursorOverText; +}; + +} // namespace pdf + +#endif // PDFADVANCEDTOOLS_H diff --git a/Pdf4QtLib/sources/pdfannotation.cpp b/Pdf4QtLib/sources/pdfannotation.cpp index d80f66f..cd79e79 100644 --- a/Pdf4QtLib/sources/pdfannotation.cpp +++ b/Pdf4QtLib/sources/pdfannotation.cpp @@ -1,3875 +1,3875 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfannotation.h" -#include "pdfdocument.h" -#include "pdfencoding.h" -#include "pdfpainter.h" -#include "pdfdrawspacecontroller.h" -#include "pdfcms.h" -#include "pdfwidgetutils.h" -#include "pdfpagecontentprocessor.h" -#include "pdfparser.h" -#include "pdfdrawwidget.h" -#include "pdfform.h" -#include "pdfpainterutils.h" -#include "pdfdocumentbuilder.h" -#include "pdfobjecteditorwidget.h" -#include "pdfselectpagesdialog.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace pdf -{ - -PDFAnnotationBorder PDFAnnotationBorder::parseBorder(const PDFObjectStorage* storage, PDFObject object) -{ - PDFAnnotationBorder result; - object = storage->getObject(object); - - if (object.isArray()) - { - const PDFArray* array = object.getArray(); - if (array->getCount() >= 3) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_definition = Definition::Simple; - result.m_hCornerRadius = loader.readNumber(array->getItem(0), 0.0); - result.m_vCornerRadius = loader.readNumber(array->getItem(1), 0.0); - result.m_width = loader.readNumber(array->getItem(2), 1.0); - - if (array->getCount() >= 4) - { - result.m_dashPattern = loader.readNumberArray(array->getItem(3)); - } - } - } - - return result; -} - -PDFAnnotationBorder PDFAnnotationBorder::parseBS(const PDFObjectStorage* storage, PDFObject object) -{ - PDFAnnotationBorder result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_definition = Definition::BorderStyle; - result.m_width = loader.readNumberFromDictionary(dictionary, "W", 1.0); - - constexpr const std::array, 6> styles = { - std::pair{ "S", Style::Solid }, - std::pair{ "D", Style::Dashed }, - std::pair{ "B", Style::Beveled }, - std::pair{ "I", Style::Inset }, - std::pair{ "U", Style::Underline } - }; - - result.m_style = loader.readEnumByName(dictionary->get("S"), styles.begin(), styles.end(), Style::Solid); - } - - return result; -} - -PDFAnnotationBorderEffect PDFAnnotationBorderEffect::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFAnnotationBorderEffect result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_intensity = loader.readNumberFromDictionary(dictionary, "I", 0.0); - - constexpr const std::array, 2> effects = { - std::pair{ "S", Effect::None }, - std::pair{ "C", Effect::Cloudy } - }; - - result.m_effect = loader.readEnumByName(dictionary->get("S"), effects.begin(), effects.end(), Effect::None); - } - - return result; -} - -PDFAppeareanceStreams PDFAppeareanceStreams::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFAppeareanceStreams result; - - auto processSubdictionary = [&result, storage](Appearance appearance, PDFObject subdictionaryObject) - { - subdictionaryObject = storage->getObject(subdictionaryObject); - if (subdictionaryObject.isDictionary()) - { - const PDFDictionary* subdictionary = storage->getDictionaryFromObject(subdictionaryObject); - for (size_t i = 0; i < subdictionary->getCount(); ++i) - { - result.m_appearanceStreams[std::make_pair(appearance, subdictionary->getKey(i).getString())] = subdictionary->getValue(i); - } - } - else if (!subdictionaryObject.isNull()) - { - result.m_appearanceStreams[std::make_pair(appearance, QByteArray())] = subdictionaryObject; - } - }; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - processSubdictionary(Appearance::Normal, dictionary->get("N")); - processSubdictionary(Appearance::Rollover, dictionary->get("R")); - processSubdictionary(Appearance::Down, dictionary->get("D")); - } - - return result; -} - -PDFObject PDFAppeareanceStreams::getAppearance(Appearance appearance, const QByteArray& state) const -{ - Key key(appearance, state); - - auto it = m_appearanceStreams.find(key); - if (it == m_appearanceStreams.cend() && appearance != Appearance::Normal) - { - key.first = Appearance::Normal; - it = m_appearanceStreams.find(key); - } - if (it == m_appearanceStreams.cend() && !state.isEmpty()) - { - key.second = QByteArray(); - it = m_appearanceStreams.find(key); - } - - if (it != m_appearanceStreams.cend()) - { - return it->second; - } - - return PDFObject(); -} - -QByteArrayList PDFAppeareanceStreams::getAppearanceStates(Appearance appearance) const -{ - QByteArrayList result; - - for (const auto& item : m_appearanceStreams) - { - if (item.first.first != appearance) - { - continue; - } - - result << item.first.second; - } - - return result; -} - -std::vector PDFAppeareanceStreams::getAppearanceKeys() const -{ - std::vector result; - std::transform(m_appearanceStreams.cbegin(), m_appearanceStreams.cend(), std::back_inserter(result), [](const auto& item) { return item.first; }); - return result; -} - -PDFAnnotation::PDFAnnotation() : - m_flags(), - m_structParent(0) -{ - -} - -void PDFAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - Q_UNUSED(parameters); -} - -std::vector PDFAnnotation::getDrawKeys(const PDFFormManager* formManager) const -{ - Q_UNUSED(formManager); - - return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() } }; -} - -QPainter::CompositionMode PDFAnnotation::getCompositionMode() const -{ - if (PDFBlendModeInfo::isSupportedByQt(getBlendMode())) - { - return PDFBlendModeInfo::getCompositionModeFromBlendMode(getBlendMode()); - } - - return PDFBlendModeInfo::getCompositionModeFromBlendMode(BlendMode::Normal); -} - -QPainterPath PDFAnnotation::parsePath(const PDFObjectStorage* storage, const PDFDictionary* dictionary, bool closePath) -{ - QPainterPath path; - - PDFDocumentDataLoaderDecorator loader(storage); - PDFObject pathObject = storage->getObject(dictionary->get("Path")); - if (pathObject.isArray()) - { - for (const PDFObject& pathItemObject : *pathObject.getArray()) - { - std::vector pathItem = loader.readNumberArray(pathItemObject); - switch (pathItem.size()) - { - case 2: - { - QPointF point(pathItem[0], pathItem[1]); - if (path.isEmpty()) - { - path.moveTo(point); - } - else - { - path.lineTo(point); - } - break; - } - - case 4: - { - if (path.isEmpty()) - { - // First path item must be 'Move to' command - continue; - } - - path.quadTo(pathItem[0], pathItem[1], pathItem[2], pathItem[3]); - break; - } - - case 6: - { - if (path.isEmpty()) - { - // First path item must be 'Move to' command - continue; - } - - path.cubicTo(pathItem[0], pathItem[1], pathItem[2], pathItem[3], pathItem[4], pathItem[5]); - break; - } - - default: - break; - } - } - } - - if (closePath) - { - path.closeSubpath(); - } - - return path; -} - -void PDFAnnotation::setLanguage(const QString& language) -{ - m_language = language; -} - -void PDFAnnotation::setBlendMode(const BlendMode& blendMode) -{ - m_blendMode = blendMode; -} - -void PDFAnnotation::setStrokingOpacity(const PDFReal& strokingOpacity) -{ - m_strokingOpacity = strokingOpacity; -} - -void PDFAnnotation::setFillingOpacity(const PDFReal& fillingOpacity) -{ - m_fillingOpacity = fillingOpacity; -} - -void PDFAnnotation::setAssociatedFiles(const PDFObject& associatedFiles) -{ - m_associatedFiles = associatedFiles; -} - -void PDFAnnotation::setOptionalContentReference(const PDFObjectReference& optionalContentReference) -{ - m_optionalContentReference = optionalContentReference; -} - -void PDFAnnotation::setStructParent(const PDFInteger& structParent) -{ - m_structParent = structParent; -} - -void PDFAnnotation::setColor(const std::vector& color) -{ - m_color = color; -} - -void PDFAnnotation::setAnnotationBorder(const PDFAnnotationBorder& annotationBorder) -{ - m_annotationBorder = annotationBorder; -} - -void PDFAnnotation::setAppearanceState(const QByteArray& appearanceState) -{ - m_appearanceState = appearanceState; -} - -void PDFAnnotation::setAppearanceStreams(const PDFAppeareanceStreams& appearanceStreams) -{ - m_appearanceStreams = appearanceStreams; -} - -void PDFAnnotation::setFlags(const Flags& flags) -{ - m_flags = flags; -} - -void PDFAnnotation::setLastModifiedString(const QString& lastModifiedString) -{ - m_lastModifiedString = lastModifiedString; -} - -void PDFAnnotation::setLastModified(const QDateTime& lastModified) -{ - m_lastModified = lastModified; -} - -void PDFAnnotation::setName(const QString& name) -{ - m_name = name; -} - -void PDFAnnotation::setPageReference(const PDFObjectReference& pageReference) -{ - m_pageReference = pageReference; -} - -void PDFAnnotation::setContents(const QString& contents) -{ - m_contents = contents; -} - -void PDFAnnotation::setRectangle(const QRectF& rectangle) -{ - m_rectangle = rectangle; -} - -void PDFAnnotation::setSelfReference(const PDFObjectReference& selfReference) -{ - m_selfReference = selfReference; -} - -PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference) -{ - PDFObject object = storage->getObjectByReference(reference); - PDFAnnotationPtr result; - - const PDFDictionary* dictionary = storage->getDictionaryFromObject(object); - if (!dictionary) - { - return result; - } - - PDFDocumentDataLoaderDecorator loader(storage); - QRectF annotationsRectangle = loader.readRectangle(dictionary->get("Rect"), QRectF()); - - // Determine type of annotation - QByteArray subtype = loader.readNameFromDictionary(dictionary, "Subtype"); - if (subtype == "Text") - { - PDFTextAnnotation* textAnnotation = new PDFTextAnnotation; - result.reset(textAnnotation); - - textAnnotation->m_open = loader.readBooleanFromDictionary(dictionary, "Open", false); - textAnnotation->m_iconName = loader.readNameFromDictionary(dictionary, "Name"); - textAnnotation->m_state = loader.readTextStringFromDictionary(dictionary, "State", "Unmarked"); - textAnnotation->m_stateModel = loader.readTextStringFromDictionary(dictionary, "StateModel", "Marked"); - } - else if (subtype == "Link") - { - PDFLinkAnnotation* linkAnnotation = new PDFLinkAnnotation; - result.reset(linkAnnotation); - - linkAnnotation->m_action = PDFAction::parse(storage, dictionary->get("A")); - if (!linkAnnotation->m_action) - { - PDFDestination destination = PDFDestination::parse(storage, dictionary->get("Dest")); - linkAnnotation->m_action.reset(new PDFActionGoTo(destination, PDFDestination())); - } - linkAnnotation->m_previousAction = PDFAction::parse(storage, dictionary->get("PA")); - - constexpr const std::array, 4> highlightMode = { - std::pair{ "N", LinkHighlightMode::None }, - std::pair{ "I", LinkHighlightMode::Invert }, - std::pair{ "O", LinkHighlightMode::Outline }, - std::pair{ "P", LinkHighlightMode::Push } - }; - - linkAnnotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightMode.begin(), highlightMode.end(), LinkHighlightMode::Invert); - linkAnnotation->m_activationRegion = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle); - } - else if (subtype == "FreeText") - { - PDFFreeTextAnnotation* freeTextAnnotation = new PDFFreeTextAnnotation; - result.reset(freeTextAnnotation); - - constexpr const std::array, 2> intents = { - std::pair{ "FreeTextCallout", PDFFreeTextAnnotation::Intent::Callout }, - std::pair{ "FreeTextTypeWriter", PDFFreeTextAnnotation::Intent::TypeWriter } - }; - - freeTextAnnotation->m_defaultAppearance = loader.readStringFromDictionary(dictionary, "DA"); - freeTextAnnotation->m_justification = static_cast(loader.readIntegerFromDictionary(dictionary, "Q", 0)); - freeTextAnnotation->m_defaultStyleString = loader.readTextStringFromDictionary(dictionary, "DS", QString()); - freeTextAnnotation->m_calloutLine = PDFAnnotationCalloutLine::parse(storage, dictionary->get("CL")); - freeTextAnnotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFFreeTextAnnotation::Intent::None); - freeTextAnnotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE")); - - std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); - if (differenceRectangle.size() == 4) - { - freeTextAnnotation->m_textRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); - if (!freeTextAnnotation->m_textRectangle.isValid()) - { - freeTextAnnotation->m_textRectangle = QRectF(); - } - } - - std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); - if (lineEndings.size() == 2) - { - freeTextAnnotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); - freeTextAnnotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); - } - } - else if (subtype == "Line") - { - PDFLineAnnotation* lineAnnotation = new PDFLineAnnotation; - result.reset(lineAnnotation); - - std::vector line = loader.readNumberArrayFromDictionary(dictionary, "L"); - if (line.size() == 4) - { - lineAnnotation->m_line = QLineF(line[0], line[1], line[2], line[3]); - } - - std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); - if (lineEndings.size() == 2) - { - lineAnnotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); - lineAnnotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); - } - - lineAnnotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); - lineAnnotation->m_leaderLineLength = loader.readNumberFromDictionary(dictionary, "LL", 0.0); - lineAnnotation->m_leaderLineExtension = loader.readNumberFromDictionary(dictionary, "LLE", 0.0); - lineAnnotation->m_leaderLineOffset = loader.readNumberFromDictionary(dictionary, "LLO", 0.0); - lineAnnotation->m_captionRendered = loader.readBooleanFromDictionary(dictionary, "Cap", false); - lineAnnotation->m_intent = (loader.readNameFromDictionary(dictionary, "IT") == "LineDimension") ? PDFLineAnnotation::Intent::Dimension : PDFLineAnnotation::Intent::Arrow; - lineAnnotation->m_captionPosition = (loader.readNameFromDictionary(dictionary, "CP") == "Top") ? PDFLineAnnotation::CaptionPosition::Top : PDFLineAnnotation::CaptionPosition::Inline; - lineAnnotation->m_measureDictionary = storage->getObject(dictionary->get("Measure")); - - std::vector captionOffset = loader.readNumberArrayFromDictionary(dictionary, "CO"); - if (captionOffset.size() == 2) - { - lineAnnotation->m_captionOffset = QPointF(captionOffset[0], captionOffset[1]); - } - } - else if (subtype == "Square" || subtype == "Circle") - { - PDFSimpleGeometryAnnotation* annotation = new PDFSimpleGeometryAnnotation((subtype == "Square") ? AnnotationType::Square : AnnotationType::Circle); - result.reset(annotation); - - annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); - annotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE")); - - std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); - if (differenceRectangle.size() == 4) - { - annotation->m_geometryRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); - if (!annotation->m_geometryRectangle.isValid()) - { - annotation->m_geometryRectangle = QRectF(); - } - } - } - else if (subtype == "Polygon" || subtype == "PolyLine") - { - PDFPolygonalGeometryAnnotation* annotation = new PDFPolygonalGeometryAnnotation((subtype == "Polygon") ? AnnotationType::Polygon : AnnotationType::Polyline); - result.reset(annotation); - - std::vector vertices = loader.readNumberArrayFromDictionary(dictionary, "Vertices"); - const size_t count = vertices.size() / 2; - annotation->m_vertices.reserve(count); - for (size_t i = 0; i < count; ++i) - { - annotation->m_vertices.emplace_back(vertices[2 * i], vertices[2 * i + 1]); - } - - std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); - if (lineEndings.size() == 2) - { - annotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); - annotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); - } - - annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); - annotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE")); - - constexpr const std::array, 3> intents = { - std::pair{ "PolygonCloud", PDFPolygonalGeometryAnnotation::Intent::Cloud }, - std::pair{ "PolyLineDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension }, - std::pair{ "PolygonDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension } - }; - - annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFPolygonalGeometryAnnotation::Intent::None); - annotation->m_measure = storage->getObject(dictionary->get("Measure")); - annotation->m_path = parsePath(storage, dictionary, subtype == "Polygon"); - } - else if (subtype == "Highlight" || - subtype == "Underline" || - subtype == "Squiggly" || - subtype == "StrikeOut") - { - AnnotationType type = AnnotationType::Highlight; - if (subtype == "Underline") - { - type = AnnotationType::Underline; - } - else if (subtype == "Squiggly") - { - type = AnnotationType::Squiggly; - } - else if (subtype == "StrikeOut") - { - type = AnnotationType::StrikeOut; - } - - PDFHighlightAnnotation* annotation = new PDFHighlightAnnotation(type); - result.reset(annotation); - - annotation->m_highlightArea = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle); - } - else if (subtype == "Caret") - { - PDFCaretAnnotation* annotation = new PDFCaretAnnotation(); - result.reset(annotation); - - std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); - if (differenceRectangle.size() == 4) - { - annotation->m_caretRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); - if (!annotation->m_caretRectangle.isValid()) - { - annotation->m_caretRectangle = QRectF(); - } - } - - annotation->m_symbol = (loader.readNameFromDictionary(dictionary, "Sy") == "P") ? PDFCaretAnnotation::Symbol::Paragraph : PDFCaretAnnotation::Symbol::None; - } - else if (subtype == "Stamp") - { - PDFStampAnnotation* annotation = new PDFStampAnnotation(); - result.reset(annotation); - - constexpr const std::array stamps = { - std::pair{ "Approved", Stamp::Approved }, - std::pair{ "AsIs", Stamp::AsIs }, - std::pair{ "Confidential", Stamp::Confidential }, - std::pair{ "Departmental", Stamp::Departmental }, - std::pair{ "Draft", Stamp::Draft }, - std::pair{ "Experimental", Stamp::Experimental }, - std::pair{ "Expired", Stamp::Expired }, - std::pair{ "Final", Stamp::Final }, - std::pair{ "ForComment", Stamp::ForComment }, - std::pair{ "ForPublicRelease", Stamp::ForPublicRelease }, - std::pair{ "NotApproved", Stamp::NotApproved }, - std::pair{ "NotForPublicRelease", Stamp::NotForPublicRelease }, - std::pair{ "Sold", Stamp::Sold }, - std::pair{ "TopSecret", Stamp::TopSecret } - }; - - annotation->m_stamp = loader.readEnumByName(dictionary->get("Name"), stamps.begin(), stamps.end(), Stamp::Draft); - - constexpr const std::array stampsIntents = { - std::pair{ "Stamp", StampIntent::Stamp }, - std::pair{ "StampImage", StampIntent::StampImage }, - std::pair{ "StampSnapshot", StampIntent::StampSnapshot }, - }; - - annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), stampsIntents.begin(), stampsIntents.end(), StampIntent::Stamp); - } - else if (subtype == "Ink") - { - PDFInkAnnotation* annotation = new PDFInkAnnotation(); - result.reset(annotation); - - annotation->m_inkPath = parsePath(storage, dictionary, false); - if (annotation->m_inkPath.isEmpty()) - { - PDFObject inkList = storage->getObject(dictionary->get("InkList")); - if (inkList.isArray()) - { - const PDFArray* inkListArray = inkList.getArray(); - for (size_t i = 0, count = inkListArray->getCount(); i < count; ++i) - { - std::vector points = loader.readNumberArray(inkListArray->getItem(i)); - const size_t pointCount = points.size() / 2; - - for (size_t j = 0; j < pointCount; ++j) - { - QPointF point(points[j * 2], points[j * 2 + 1]); - - if (j == 0) - { - annotation->m_inkPath.moveTo(point); - } - else - { - annotation->m_inkPath.lineTo(point); - } - } - } - } - } - } - else if (subtype == "Popup") - { - PDFPopupAnnotation* annotation = new PDFPopupAnnotation(); - result.reset(annotation); - - annotation->m_opened = loader.readBooleanFromDictionary(dictionary, "Open", false); - } - else if (subtype == "FileAttachment") - { - PDFFileAttachmentAnnotation* annotation = new PDFFileAttachmentAnnotation(); - result.reset(annotation); - - annotation->m_fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("FS")); - - constexpr const std::array, 4> icons = { - std::pair{ "Graph", FileAttachmentIcon::Graph }, - std::pair{ "Paperclip", FileAttachmentIcon::Paperclip }, - std::pair{ "PushPin", FileAttachmentIcon::PushPin }, - std::pair{ "Tag", FileAttachmentIcon::Tag } - }; - - annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), FileAttachmentIcon::PushPin); - } - else if (subtype == "Redact") - { - PDFRedactAnnotation* annotation = new PDFRedactAnnotation(); - result.reset(annotation); - - annotation->m_redactionRegion = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle); - annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); - annotation->m_overlayForm = dictionary->get("RO"); - annotation->m_overlayText = loader.readTextStringFromDictionary(dictionary, "OverlayText", QString()); - annotation->m_repeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false); - annotation->m_defaultAppearance = loader.readStringFromDictionary(dictionary, "DA"); - annotation->m_justification = loader.readIntegerFromDictionary(dictionary, "Q", 0); - } - else if (subtype == "Sound") - { - PDFSoundAnnotation* annotation = new PDFSoundAnnotation(); - result.reset(annotation); - - annotation->m_sound = PDFSound::parse(storage, dictionary->get("Sound")); - - constexpr const std::array, 2> icons = { - std::pair{ "Speaker", PDFSoundAnnotation::Icon::Speaker }, - std::pair{ "Mic", PDFSoundAnnotation::Icon::Microphone } - }; - - annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), PDFSoundAnnotation::Icon::Speaker); - } - else if (subtype == "Movie") - { - PDFMovieAnnotation* annotation = new PDFMovieAnnotation(); - result.reset(annotation); - - annotation->m_movieTitle = loader.readStringFromDictionary(dictionary, "T"); - annotation->m_movie = PDFMovie::parse(storage, dictionary->get("Movie")); - - PDFObject activation = storage->getObject(dictionary->get("A")); - if (activation.isBool()) - { - annotation->m_playMovie = activation.getBool(); - } - else if (activation.isDictionary()) - { - annotation->m_playMovie = true; - annotation->m_movieActivation = PDFMovieActivation::parse(storage, activation); - } - } - else if (subtype == "Screen") - { - PDFScreenAnnotation* annotation = new PDFScreenAnnotation(); - result.reset(annotation); - - annotation->m_screenTitle = loader.readTextStringFromDictionary(dictionary, "T", QString()); - annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(storage, dictionary->get("MK")); - annotation->m_action = PDFAction::parse(storage, dictionary->get("A")); - annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, dictionary->get("AA"), dictionary->get("A")); - } - else if (subtype == "Widget") - { - PDFWidgetAnnotation* annotation = new PDFWidgetAnnotation(); - result.reset(annotation); - - constexpr const std::array, 5> highlightModes = { - std::pair{ "N", PDFWidgetAnnotation::HighlightMode::None }, - std::pair{ "I", PDFWidgetAnnotation::HighlightMode::Invert }, - std::pair{ "O", PDFWidgetAnnotation::HighlightMode::Outline }, - std::pair{ "P", PDFWidgetAnnotation::HighlightMode::Push }, - std::pair{ "T", PDFWidgetAnnotation::HighlightMode::Toggle } - }; - - annotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightModes.begin(), highlightModes.end(), PDFWidgetAnnotation::HighlightMode::Invert); - annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(storage, dictionary->get("MK")); - annotation->m_action = PDFAction::parse(storage, dictionary->get("A")); - annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, dictionary->get("AA"), dictionary->get("A")); - } - else if (subtype == "PrinterMark") - { - PDFPrinterMarkAnnotation* annotation = new PDFPrinterMarkAnnotation(); - result.reset(annotation); - } - else if (subtype == "TrapNet") - { - PDFTrapNetworkAnnotation* annotation = new PDFTrapNetworkAnnotation(); - result.reset(annotation); - } - else if (subtype == "Watermark") - { - PDFWatermarkAnnotation* annotation = new PDFWatermarkAnnotation(); - result.reset(annotation); - - if (const PDFDictionary* fixedPrintDictionary = storage->getDictionaryFromObject(dictionary->get("FixedPrint"))) - { - annotation->m_matrix = loader.readMatrixFromDictionary(fixedPrintDictionary, "Matrix", QMatrix()); - annotation->m_relativeHorizontalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "H", 0.0); - annotation->m_relativeVerticalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "V", 0.0); - } - } - else if (subtype == "Projection") - { - PDFProjectionAnnotation* annotation = new PDFProjectionAnnotation(); - result.reset(annotation); - } - else if (subtype == "3D") - { - PDF3DAnnotation* annotation = new PDF3DAnnotation(); - result.reset(annotation); - - annotation->m_stream = PDF3DStream::parse(storage, dictionary->get("3DD")); - - const std::vector& views = annotation->getStream().getViews(); - PDFObject defaultViewObject = storage->getObject(dictionary->get("DV")); - if (defaultViewObject.isDictionary()) - { - annotation->m_defaultView = PDF3DView::parse(storage, defaultViewObject); - } - else if (defaultViewObject.isInt()) - { - PDFInteger index = defaultViewObject.getInteger(); - if (index >= 0 && index < PDFInteger(views.size())) - { - annotation->m_defaultView = views[index]; - } - } - else if (defaultViewObject.isName() && !views.empty()) - { - QByteArray name = defaultViewObject.getString(); - if (name == "F") - { - annotation->m_defaultView = views.front(); - } - else if (name == "L") - { - annotation->m_defaultView = views.back(); - } - } - - annotation->m_activation = PDF3DActivation::parse(storage, dictionary->get("3DA")); - annotation->m_interactive = loader.readBooleanFromDictionary(dictionary, "3DI", true); - annotation->m_viewBox = loader.readRectangle(dictionary->get("3DB"), QRectF()); - } - else if (subtype == "RichMedia") - { - PDFRichMediaAnnotation* annotation = new PDFRichMediaAnnotation(); - result.reset(annotation); - - annotation->m_content = PDFRichMediaContent::parse(storage, dictionary->get("RichMediaContent")); - annotation->m_settings = PDFRichMediaSettings::parse(storage, dictionary->get("RichMediaSettings")); - } - - if (!result) - { - // Invalid annotation type - return result; - } - - // Load common data for annotation - result->m_selfReference = reference; - result->m_rectangle = annotationsRectangle; - result->m_contents = loader.readTextStringFromDictionary(dictionary, "Contents", QString()); - result->m_pageReference = loader.readReferenceFromDictionary(dictionary, "P"); - result->m_name = loader.readTextStringFromDictionary(dictionary, "NM", QString()); - - QByteArray string = loader.readStringFromDictionary(dictionary, "M"); - result->m_lastModified = PDFEncoding::convertToDateTime(string); - if (!result->m_lastModified.isValid()) - { - result->m_lastModifiedString = loader.readTextStringFromDictionary(dictionary, "M", QString()); - } - - result->m_flags = Flags(loader.readIntegerFromDictionary(dictionary, "F", 0)); - result->m_appearanceStreams = PDFAppeareanceStreams::parse(storage, dictionary->get("AP")); - result->m_appearanceState = loader.readNameFromDictionary(dictionary, "AS"); - - result->m_annotationBorder = PDFAnnotationBorder::parseBS(storage, dictionary->get("BS")); - if (!result->m_annotationBorder.isValid()) - { - result->m_annotationBorder = PDFAnnotationBorder::parseBorder(storage, dictionary->get("Border")); - } - result->m_color = loader.readNumberArrayFromDictionary(dictionary, "C"); - result->m_structParent = loader.readIntegerFromDictionary(dictionary, "StructParent", 0); - result->m_optionalContentReference = loader.readReferenceFromDictionary(dictionary, "OC"); - result->m_strokingOpacity = loader.readNumberFromDictionary(dictionary, "CA", 1.0); - result->m_fillingOpacity = loader.readNumberFromDictionary(dictionary, "ca", result->m_strokingOpacity); - result->m_blendMode = PDFBlendModeInfo::getBlendMode(loader.readNameFromDictionary(dictionary, "BM")); - - if (result->m_blendMode == BlendMode::Invalid) - { - result->m_blendMode = BlendMode::Normal; - } - - result->m_language = loader.readTextStringFromDictionary(dictionary, "Lang", QString()); - - if (PDFMarkupAnnotation* markupAnnotation = result->asMarkupAnnotation()) - { - markupAnnotation->m_windowTitle = loader.readTextStringFromDictionary(dictionary, "T", QString()); - markupAnnotation->m_popupAnnotation = loader.readReferenceFromDictionary(dictionary, "Popup"); - markupAnnotation->m_richTextString = loader.readTextStringFromDictionary(dictionary, "RC", QString()); - markupAnnotation->m_creationDate = PDFEncoding::convertToDateTime(loader.readStringFromDictionary(dictionary, "CreationDate")); - markupAnnotation->m_inReplyTo = loader.readReferenceFromDictionary(dictionary, "IRT"); - markupAnnotation->m_subject = loader.readTextStringFromDictionary(dictionary, "Subj", QString()); - markupAnnotation->m_replyType = (loader.readNameFromDictionary(dictionary, "RT") == "Group") ? PDFMarkupAnnotation::ReplyType::Group : PDFMarkupAnnotation::ReplyType::Reply; - markupAnnotation->m_intent = loader.readNameFromDictionary(dictionary, "IT"); - markupAnnotation->m_externalData = storage->getObject(dictionary->get("ExData")); - } - - return result; -} - -PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect) -{ - QPainterPath path; - PDFAnnotationQuadrilaterals::Quadrilaterals quadrilaterals; - - PDFDocumentDataLoaderDecorator loader(storage); - std::vector points = loader.readNumberArray(quadrilateralsObject); - const size_t quadrilateralCount = points.size() / 8; - path.reserve(int(quadrilateralCount) + 5); - quadrilaterals.reserve(quadrilateralCount); - for (size_t i = 0; i < quadrilateralCount; ++i) - { - const size_t offset = i * 8; - QPointF p1(points[offset + 0], points[offset + 1]); - QPointF p2(points[offset + 2], points[offset + 3]); - QPointF p3(points[offset + 4], points[offset + 5]); - QPointF p4(points[offset + 6], points[offset + 7]); - - path.moveTo(p1); - path.lineTo(p2); - path.lineTo(p4); - path.lineTo(p3); - path.closeSubpath(); - - quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ p1, p2, p3, p4 }); - } - - if (path.isEmpty() && annotationRect.isValid()) - { - // Jakub Melka: we are using points at the top, because PDF has inverted y axis - // against the Qt's y axis. - path.addRect(annotationRect); - quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ annotationRect.topLeft(), annotationRect.topRight(), annotationRect.bottomLeft(), annotationRect.bottomRight() }); - } - - return PDFAnnotationQuadrilaterals(qMove(path), qMove(quadrilaterals)); -} - -constexpr const std::array, 10> lineEndings = { - std::pair{ AnnotationLineEnding::None, "None" }, - std::pair{ AnnotationLineEnding::Square, "Square" }, - std::pair{ AnnotationLineEnding::Circle, "Circle" }, - std::pair{ AnnotationLineEnding::Diamond, "Diamond" }, - std::pair{ AnnotationLineEnding::OpenArrow, "OpenArrow" }, - std::pair{ AnnotationLineEnding::ClosedArrow, "ClosedArrow" }, - std::pair{ AnnotationLineEnding::Butt, "Butt" }, - std::pair{ AnnotationLineEnding::ROpenArrow, "ROpenArrow" }, - std::pair{ AnnotationLineEnding::RClosedArrow, "RClosedArrow" }, - std::pair{ AnnotationLineEnding::Slash, "Slash" } -}; - -AnnotationLineEnding PDFAnnotation::convertNameToLineEnding(const QByteArray& name) -{ - auto it = std::find_if(lineEndings.cbegin(), lineEndings.cend(), [&name](const auto& item) { return name == item.second; }); - if (it != lineEndings.cend()) - { - return it->first; - } - - return AnnotationLineEnding::None; -} - -QByteArray PDFAnnotation::convertLineEndingToName(AnnotationLineEnding lineEnding) -{ - auto it = std::find_if(lineEndings.cbegin(), lineEndings.cend(), [lineEnding](const auto& item) { return lineEnding == item.first; }); - if (it != lineEndings.cend()) - { - return it->second; - } - - return lineEndings.front().second; -} - -QColor PDFAnnotation::getStrokeColor() const -{ - return getDrawColorFromAnnotationColor(getColor(), getStrokeOpacity()); -} - -QColor PDFAnnotation::getFillColor() const -{ - return QColor(); -} - -QColor PDFAnnotation::getDrawColorFromAnnotationColor(const std::vector& color, PDFReal opacity) -{ - switch (color.size()) - { - case 1: - { - const PDFReal gray = color.back(); - return QColor::fromRgbF(gray, gray, gray, opacity); - } - - case 3: - { - const PDFReal r = color[0]; - const PDFReal g = color[1]; - const PDFReal b = color[2]; - return QColor::fromRgbF(r, g, b, opacity); - } - - case 4: - { - const PDFReal c = color[0]; - const PDFReal m = color[1]; - const PDFReal y = color[2]; - const PDFReal k = color[3]; - return QColor::fromCmykF(c, m, y, k, opacity); - } - - default: - break; - } - - QColor black(Qt::black); - black.setAlphaF(opacity); - return black; -} - -bool PDFAnnotation::isTypeEditable(AnnotationType type) -{ - switch (type) - { - case AnnotationType::Text: - case AnnotationType::Link: - case AnnotationType::FreeText: - case AnnotationType::Line: - case AnnotationType::Square: - case AnnotationType::Circle: - case AnnotationType::Polygon: - case AnnotationType::Polyline: - case AnnotationType::Highlight: - case AnnotationType::Underline: - case AnnotationType::Squiggly: - case AnnotationType::StrikeOut: - case AnnotationType::Stamp: - case AnnotationType::Caret: - case AnnotationType::Ink: - case AnnotationType::FileAttachment: - case AnnotationType::PrinterMark: - case AnnotationType::Watermark: - case AnnotationType::Redact: - return true; - - default: - break; - } - - return false; -} - -QPen PDFAnnotation::getPen() const -{ - QColor strokeColor = getStrokeColor(); - const PDFAnnotationBorder& border = getBorder(); - - if (qFuzzyIsNull(border.getWidth())) - { - // No border is drawn - return Qt::NoPen; - } - - QPen pen(strokeColor); - pen.setWidthF(border.getWidth()); - - if (!border.getDashPattern().empty()) - { - PDFLineDashPattern lineDashPattern(border.getDashPattern(), 0.0); - pen.setStyle(Qt::CustomDashLine); - pen.setDashPattern(QVector(lineDashPattern.getDashArray().begin(), lineDashPattern.getDashArray().end())); - pen.setDashOffset(lineDashPattern.getDashOffset()); - } - - return pen; -} - -QBrush PDFAnnotation::getBrush() const -{ - QColor color = getFillColor(); - if (color.isValid()) - { - return QBrush(color, Qt::SolidPattern); - } - - return QBrush(Qt::NoBrush); -} - -PDFAnnotationCalloutLine PDFAnnotationCalloutLine::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFDocumentDataLoaderDecorator loader(storage); - std::vector points = loader.readNumberArray(object); - - switch (points.size()) - { - case 4: - return PDFAnnotationCalloutLine(QPointF(points[0], points[1]), QPointF(points[2], points[3])); - - case 6: - return PDFAnnotationCalloutLine(QPointF(points[0], points[1]), QPointF(points[2], points[3]), QPointF(points[4], points[5])); - - default: - break; - } - - return PDFAnnotationCalloutLine(); -} - -PDFAnnotationAppearanceCharacteristics PDFAnnotationAppearanceCharacteristics::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFAnnotationAppearanceCharacteristics result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - result.m_rotation = loader.readIntegerFromDictionary(dictionary, "R", 0); - result.m_borderColor = loader.readNumberArrayFromDictionary(dictionary, "BC"); - result.m_backgroundColor = loader.readNumberArrayFromDictionary(dictionary, "BG"); - result.m_normalCaption = loader.readTextStringFromDictionary(dictionary, "CA", QString()); - result.m_rolloverCaption = loader.readTextStringFromDictionary(dictionary, "RC", QString()); - result.m_downCaption = loader.readTextStringFromDictionary(dictionary, "AC", QString()); - result.m_normalIcon = storage->getObject(dictionary->get("I")); - result.m_rolloverIcon = storage->getObject(dictionary->get("RI")); - result.m_downIcon = storage->getObject(dictionary->get("IX")); - result.m_iconFit = PDFAnnotationIconFitInfo::parse(storage, dictionary->get("IF")); - result.m_pushButtonMode = static_cast(loader.readIntegerFromDictionary(dictionary, "TP", PDFInteger(PDFAnnotationAppearanceCharacteristics::PushButtonMode::NoIcon))); - } - return result; -} - -PDFAnnotationIconFitInfo PDFAnnotationIconFitInfo::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFAnnotationIconFitInfo info; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - constexpr const std::array, 4> scaleConditions = { - std::pair{ "A", PDFAnnotationIconFitInfo::ScaleCondition::Always }, - std::pair{ "B", PDFAnnotationIconFitInfo::ScaleCondition::ScaleBigger }, - std::pair{ "S", PDFAnnotationIconFitInfo::ScaleCondition::ScaleSmaller }, - std::pair{ "N", PDFAnnotationIconFitInfo::ScaleCondition::Never } - }; - - constexpr const std::array, 2> scaleTypes = { - std::pair{ "A", PDFAnnotationIconFitInfo::ScaleType::Anamorphic }, - std::pair{ "P", PDFAnnotationIconFitInfo::ScaleType::Proportional } - }; - - std::vector point = loader.readNumberArrayFromDictionary(dictionary, "A"); - if (point.size() != 2) - { - point.resize(2, 0.5); - } - - info.m_scaleCondition = loader.readEnumByName(dictionary->get("SW"), scaleConditions.begin(), scaleConditions.end(), PDFAnnotationIconFitInfo::ScaleCondition::Always); - info.m_scaleType = loader.readEnumByName(dictionary->get("S"), scaleTypes.begin(), scaleTypes.end(), PDFAnnotationIconFitInfo::ScaleType::Proportional); - info.m_relativeProportionalPosition = QPointF(point[0], point[1]); - info.m_fullBox = loader.readBooleanFromDictionary(dictionary, "FB", false); - } - - return info; -} - -PDFAnnotationAdditionalActions PDFAnnotationAdditionalActions::parse(const PDFObjectStorage* storage, PDFObject object, PDFObject defaultAction) -{ - PDFAnnotationAdditionalActions result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - result.m_actions[CursorEnter] = PDFAction::parse(storage, dictionary->get("E")); - result.m_actions[CursorLeave] = PDFAction::parse(storage, dictionary->get("X")); - result.m_actions[MousePressed] = PDFAction::parse(storage, dictionary->get("D")); - result.m_actions[MouseReleased] = PDFAction::parse(storage, dictionary->get("U")); - result.m_actions[FocusIn] = PDFAction::parse(storage, dictionary->get("Fo")); - result.m_actions[FocusOut] = PDFAction::parse(storage, dictionary->get("Bl")); - result.m_actions[PageOpened] = PDFAction::parse(storage, dictionary->get("PO")); - result.m_actions[PageClosed] = PDFAction::parse(storage, dictionary->get("PC")); - result.m_actions[PageShow] = PDFAction::parse(storage, dictionary->get("PV")); - result.m_actions[PageHide] = PDFAction::parse(storage, dictionary->get("PI")); - result.m_actions[FormFieldModified] = PDFAction::parse(storage, dictionary->get("K")); - result.m_actions[FormFieldFormatted] = PDFAction::parse(storage, dictionary->get("F")); - result.m_actions[FormFieldValidated] = PDFAction::parse(storage, dictionary->get("V")); - result.m_actions[FormFieldCalculated] = PDFAction::parse(storage, dictionary->get("C")); - } - - result.m_actions[Default] = PDFAction::parse(storage, defaultAction); - return result; -} - -PDFAnnotationManager::PDFAnnotationManager(PDFFontCache* fontCache, - const PDFCMSManager* cmsManager, - const PDFOptionalContentActivity* optionalActivity, - PDFMeshQualitySettings meshQualitySettings, - PDFRenderer::Features features, - Target target, - QObject* parent) : - BaseClass(parent), - m_document(nullptr), - m_fontCache(fontCache), - m_cmsManager(cmsManager), - m_optionalActivity(optionalActivity), - m_formManager(nullptr), - m_meshQualitySettings(meshQualitySettings), - m_features(features), - m_target(target) -{ - -} - -PDFAnnotationManager::~PDFAnnotationManager() -{ - -} - -QMatrix PDFAnnotationManager::prepareTransformations(const QMatrix& pagePointToDevicePointMatrix, - QPaintDevice* device, - const PDFAnnotation::Flags annotationFlags, - const PDFPage* page, - QRectF& annotationRectangle) const -{ - // "Unrotate" user coordinate space, if NoRotate flag is set - QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix; - if (annotationFlags.testFlag(PDFAnnotation::NoRotate)) - { - PDFReal rotationAngle = 0.0; - switch (page->getPageRotation()) - { - case PageRotation::None: - break; - - case PageRotation::Rotate90: - rotationAngle = -90.0; - break; - - case PageRotation::Rotate180: - rotationAngle = -180.0; - break; - - case PageRotation::Rotate270: - rotationAngle = -270.0; - break; - - default: - Q_ASSERT(false); - break; - } - - QMatrix rotationMatrix; - rotationMatrix.rotate(-rotationAngle); - QPointF topLeft = annotationRectangle.bottomLeft(); // Do not forget, that y is upward instead of Qt - QPointF difference = topLeft - rotationMatrix.map(topLeft); - - QMatrix finalMatrix; - finalMatrix.translate(difference.x(), difference.y()); - finalMatrix.rotate(-rotationAngle); - userSpaceToDeviceSpace = finalMatrix * userSpaceToDeviceSpace; - } - - if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) - { - // Jakub Melka: we must adjust annotation rectangle to disable zoom. We calculate - // inverse zoom as square root of absolute value of determinant of scale matrix. - // Determinant corresponds approximately to zoom squared, and if we will have - // unrotated matrix, and both axes are scaled by same value, then determinant will - // be exactly zoom squared. Also, we will adjust to target device logical DPI, - // if we, for example are using 4K, or 8K monitors. - qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant())); - zoom = PDFWidgetUtils::scaleDPI_x(device, zoom); - - QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom); - unzoomedRect.translate(0, -unzoomedRect.height()); - annotationRectangle = unzoomedRect; - } - - return userSpaceToDeviceSpace; -} - -void PDFAnnotationManager::drawWidgetAnnotationHighlight(QRectF annotationRectangle, - const PDFAnnotation* annotation, - QPainter* painter, - QMatrix userSpaceToDeviceSpace) const -{ - const bool isWidget = annotation->getType() == AnnotationType::Widget; - if (m_formManager && isWidget) - { - // Is it a form field? - const PDFFormManager::FormAppearanceFlags flags = m_formManager->getAppearanceFlags(); - const bool isFocused = m_formManager->isFocused(annotation->getSelfReference()); - if (isFocused || flags.testFlag(PDFFormManager::HighlightFields) || flags.testFlag(PDFFormManager::HighlightRequiredFields)) - { - const PDFFormField* formField = m_formManager->getFormFieldForWidget(annotation->getSelfReference()); - if (!formField) - { - return; - } - - // Nekreslíme zvýraznění push buttonů - if (formField->getFieldType() == PDFFormField::FieldType::Button && - formField->getFlags().testFlag(PDFFormField::PushButton)) - { - return; - } - - QColor color; - if (flags.testFlag(PDFFormManager::HighlightFields)) - { - color = Qt::blue; - } - if (flags.testFlag(PDFFormManager::HighlightRequiredFields) && formField->getFlags().testFlag(PDFFormField::Required)) - { - color = Qt::red; - } - if (isFocused) - { - color = Qt::yellow; - } - - if (color.isValid()) - { - color.setAlphaF(0.2); - - // Draw annotation rectangle by highlight color - QPainterPath highlightArea; - highlightArea.addRect(annotationRectangle); - highlightArea = userSpaceToDeviceSpace.map(highlightArea); - painter->fillPath(highlightArea, color); - } - } - } -} - -void PDFAnnotationManager::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - Q_UNUSED(compiledPage); - Q_UNUSED(layoutGetter); - - const PageAnnotations& annotations = getPageAnnotations(pageIndex); - if (!annotations.isEmpty()) - { - const PDFPage* page = m_document->getCatalog()->getPage(pageIndex); - Q_ASSERT(page); - - PDFRenderer::Features features = m_features; - if (!features.testFlag(PDFRenderer::DisplayAnnotations)) - { - // Annotation displaying is disabled - return; - } - - Q_ASSERT(m_fontCache); - Q_ASSERT(m_cmsManager); - Q_ASSERT(m_optionalActivity); - - int fontCacheLock = 0; - const PDFCMSPointer cms = m_cmsManager->getCurrentCMS(); - m_fontCache->setCacheShrinkEnabled(&fontCacheLock, false); - - const PageAnnotation* annotationDrawnByEditor = nullptr; - for (const PageAnnotation& annotation : annotations.annotations) - { - // If annotation draw is not enabled, then skip it - if (!isAnnotationDrawEnabled(annotation)) - { - continue; - } - - if (isAnnotationDrawnByEditor(annotation)) - { - Q_ASSERT(!annotationDrawnByEditor); - annotationDrawnByEditor = &annotation; - continue; - } - - drawAnnotation(annotation, pagePointToDevicePointMatrix, page, cms.data(), false, errors, painter); - } - - if (annotationDrawnByEditor) - { - drawAnnotation(*annotationDrawnByEditor, pagePointToDevicePointMatrix, page, cms.data(), true, errors, painter); - } - - m_fontCache->setCacheShrinkEnabled(&fontCacheLock, true); - } -} - -void PDFAnnotationManager::drawAnnotation(const PageAnnotation& annotation, - const QMatrix& pagePointToDevicePointMatrix, - const PDFPage* page, - const PDFCMS* cms, - bool isEditorDrawEnabled, - QList& errors, - QPainter* painter) const -{ - try - { - PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation)); - if (!appearanceStreamObject.isStream() || isEditorDrawEnabled) - { - // Object is not valid appearance stream. We will try to draw default - // annotation appearance, but we must consider also optional content. - // We do not draw annotation, if it is not ignored and annotation - // has reference to optional content. - - drawAnnotationDirect(annotation, pagePointToDevicePointMatrix, page, cms, isEditorDrawEnabled, painter); - } - else - { - drawAnnotationUsingAppearanceStream(annotation, appearanceStreamObject, pagePointToDevicePointMatrix, page, cms, painter); - } - } - catch (PDFException exception) - { - errors.push_back(PDFRenderError(RenderErrorType::Error, exception.getMessage())); - } - catch (PDFRendererException exception) - { - errors.push_back(exception.getError()); - } -} - -bool PDFAnnotationManager::isAnnotationDrawEnabled(const PageAnnotation& annotation) const -{ - const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags(); - return !(annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden - (m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed - (m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView)) || // Target is view, and annotation is disabled for screen - annotation.annotation->isReplyTo()); // Annotation is reply to another annotation, display just the first annotation -} - -bool PDFAnnotationManager::isAnnotationDrawnByEditor(const PageAnnotation& annotation) const -{ - if (!m_formManager) - { - return false; - } - - const PDFFormFieldWidgetEditor* editor = nullptr; - if (annotation.annotation->getType() == AnnotationType::Widget) - { - editor = m_formManager->getEditor(m_formManager->getFormFieldForWidget(annotation.annotation->getSelfReference())); - return editor && editor->isEditorDrawEnabled(); - } - - return false; -} - -void PDFAnnotationManager::drawAnnotationDirect(const PageAnnotation& annotation, - const QMatrix& pagePointToDevicePointMatrix, - const PDFPage* page, - const PDFCMS* cms, - bool isEditorDrawEnabled, - QPainter* painter) const -{ - if (!m_features.testFlag(PDFRenderer::IgnoreOptionalContent) && - annotation.annotation->getOptionalContent().isValid()) - { - PDFPainter pdfPainter(painter, m_features, pagePointToDevicePointMatrix, page, m_document, m_fontCache, cms, m_optionalActivity, m_meshQualitySettings); - if (pdfPainter.isContentSuppressedByOC(annotation.annotation->getOptionalContent())) - { - return; - } - } - - QRectF annotationRectangle = annotation.annotation->getRectangle(); - { - PDFPainterStateGuard guard(painter); - painter->setRenderHint(QPainter::Antialiasing, true); - painter->setWorldMatrix(pagePointToDevicePointMatrix, true); - AnnotationDrawParameters parameters; - parameters.painter = painter; - parameters.annotation = annotation.annotation.data(); - parameters.formManager = m_formManager; - parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState()); - parameters.invertColors = m_features.testFlag(PDFRenderer::InvertColors); - annotation.annotation->draw(parameters); - - if (parameters.boundingRectangle.isValid()) - { - annotationRectangle = parameters.boundingRectangle; - } - } - - // Draw highlighting of fields, but only, if target is View, - // we do not want to render form field highlight, when we are - // printing to the printer. - if (m_target == Target::View && !isEditorDrawEnabled) - { - PDFPainterStateGuard guard(painter); - drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, pagePointToDevicePointMatrix); - } -} - -void PDFAnnotationManager::drawAnnotationUsingAppearanceStream(const PageAnnotation& annotation, - const PDFObject& appearanceStreamObject, - const QMatrix& pagePointToDevicePointMatrix, - const PDFPage* page, - const PDFCMS* cms, - QPainter* painter) const -{ - PDFDocumentDataLoaderDecorator loader(m_document); - const PDFStream* formStream = appearanceStreamObject.getStream(); - const PDFDictionary* formDictionary = formStream->getDictionary(); - - const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags(); - QRectF annotationRectangle = annotation.annotation->getRectangle(); - QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF()); - QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix()); - QByteArray content = m_document->getDecodedStream(formStream); - PDFObject resources = m_document->getObject(formDictionary->get("Resources")); - PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group")); - const PDFInteger formStructuralParentKey = loader.readIntegerFromDictionary(formDictionary, "StructParent", page->getStructureParentKey()); - - if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty()) - { - // Form bounding box is empty - return; - } - - QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle); - - PDFRenderer::Features features = m_features; - if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) - { - features.setFlag(PDFRenderer::ClipToCropBox, false); - } - - // Jakub Melka: perform algorithm 8.1, defined in PDF 1.7 reference, - // chapter 8.4.4 Appearance streams. - - // Step 1) - calculate transformed appearance box - QRectF transformedAppearanceBox = formMatrix.mapRect(formBoundingBox); - - // Step 2) - calculate matrix A, which maps from form space to annotation space - // Matrix A transforms from transformed appearance box space to the - // annotation rectangle. - const PDFReal scaleX = annotationRectangle.width() / transformedAppearanceBox.width(); - const PDFReal scaleY = annotationRectangle.height() / transformedAppearanceBox.height(); - const PDFReal translateX = annotationRectangle.left() - transformedAppearanceBox.left() * scaleX; - const PDFReal translateY = annotationRectangle.bottom() - transformedAppearanceBox.bottom() * scaleY; - QMatrix A(scaleX, 0.0, 0.0, scaleY, translateX, translateY); - - // Step 3) - compute final matrix AA - QMatrix AA = formMatrix * A; - - bool isContentVisible = false; - - // Draw annotation - { - PDFPainterStateGuard guard(painter); - PDFPainter pdfPainter(painter, features, userSpaceToDeviceSpace, page, m_document, m_fontCache, cms, m_optionalActivity, m_meshQualitySettings); - pdfPainter.initializeProcessor(); - - // Jakub Melka: we must check, that we do not display annotation disabled by optional content - PDFObjectReference oc = annotation.annotation->getOptionalContent(); - isContentVisible = !oc.isValid() || !pdfPainter.isContentSuppressedByOC(oc); - - if (isContentVisible) - { - pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content, formStructuralParentKey); - } - } - - // Draw highlighting of fields, but only, if target is View, - // we do not want to render form field highlight, when we are - // printing to the printer. - if (isContentVisible && m_target == Target::View) - { - PDFPainterStateGuard guard(painter); - painter->resetTransform(); - drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, userSpaceToDeviceSpace); - } -} - -void PDFAnnotationManager::setDocument(const PDFModifiedDocument& document) -{ - if (m_document != document) - { - m_document = document; - m_optionalActivity = document.getOptionalContentActivity(); - - if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation)) - { - m_pageAnnotations.clear(); - } - } -} - -PDFObject PDFAnnotationManager::getAppearanceStream(const PageAnnotation& pageAnnotation) const -{ - auto getAppearanceStream = [&pageAnnotation] (void) -> PDFObject - { - return pageAnnotation.annotation->getAppearanceStreams().getAppearance(pageAnnotation.appearance, pageAnnotation.annotation->getAppearanceState()); - }; - - QMutexLocker lock(&m_mutex); - return pageAnnotation.appearanceStream.get(getAppearanceStream); -} - -const PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const -{ - return const_cast(this)->getPageAnnotations(pageIndex); -} - -PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) -{ - Q_ASSERT(m_document); - - QMutexLocker lock(&m_mutex); - auto it = m_pageAnnotations.find(pageIndex); - if (it == m_pageAnnotations.cend()) - { - // Create page annotations - const PDFPage* page = m_document->getCatalog()->getPage(pageIndex); - Q_ASSERT(page); - - PageAnnotations annotations; - - const std::vector& pageAnnotations = page->getAnnotations(); - annotations.annotations.reserve(pageAnnotations.size()); - for (PDFObjectReference annotationReference : pageAnnotations) - { - PDFAnnotationPtr annotationPtr = PDFAnnotation::parse(&m_document->getStorage(), annotationReference); - if (annotationPtr) - { - PageAnnotation annotation; - annotation.annotation = qMove(annotationPtr); - annotations.annotations.emplace_back(qMove(annotation)); - } - } - - it = m_pageAnnotations.insert(std::make_pair(pageIndex, qMove(annotations))).first; - } - - return it->second; -} - -bool PDFAnnotationManager::hasAnnotation(PDFInteger pageIndex) const -{ - return !getPageAnnotations(pageIndex).isEmpty(); -} - -bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector& pageIndices) const -{ - return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1)); -} - -PDFFormManager* PDFAnnotationManager::getFormManager() const -{ - return m_formManager; -} - -void PDFAnnotationManager::setFormManager(PDFFormManager* formManager) -{ - m_formManager = formManager; -} - -PDFRenderer::Features PDFAnnotationManager::getFeatures() const -{ - return m_features; -} - -void PDFAnnotationManager::setFeatures(PDFRenderer::Features features) -{ - m_features = features; -} - -PDFMeshQualitySettings PDFAnnotationManager::getMeshQualitySettings() const -{ - return m_meshQualitySettings; -} - -void PDFAnnotationManager::setMeshQualitySettings(const PDFMeshQualitySettings& meshQualitySettings) -{ - m_meshQualitySettings = meshQualitySettings; -} - -PDFFontCache* PDFAnnotationManager::getFontCache() const -{ - return m_fontCache; -} - -void PDFAnnotationManager::setFontCache(PDFFontCache* fontCache) -{ - m_fontCache = fontCache; -} - -const PDFOptionalContentActivity* PDFAnnotationManager::getOptionalActivity() const -{ - return m_optionalActivity; -} - -void PDFAnnotationManager::setOptionalActivity(const PDFOptionalContentActivity* optionalActivity) -{ - m_optionalActivity = optionalActivity; -} - -PDFAnnotationManager::Target PDFAnnotationManager::getTarget() const -{ - return m_target; -} - -void PDFAnnotationManager::setTarget(Target target) -{ - m_target = target; -} - -PDFWidgetAnnotationManager::PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent) : - BaseClass(proxy->getFontCache(), proxy->getCMSManager(), proxy->getOptionalContentActivity(), proxy->getMeshQualitySettings(), proxy->getFeatures(), Target::View, parent), - m_proxy(proxy) -{ - Q_ASSERT(proxy); - m_proxy->registerDrawInterface(this); -} - -PDFWidgetAnnotationManager::~PDFWidgetAnnotationManager() -{ - m_proxy->unregisterDrawInterface(this); -} - -void PDFWidgetAnnotationManager::setDocument(const PDFModifiedDocument& document) -{ - BaseClass::setDocument(document); - - if (document.hasReset() || document.getFlags().testFlag(PDFModifiedDocument::Annotation)) - { - m_editableAnnotation = PDFObjectReference(); - m_editableAnnotationPage = PDFObjectReference(); - } -} - -void PDFWidgetAnnotationManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFWidgetAnnotationManager::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFWidgetAnnotationManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFWidgetAnnotationManager::mousePressEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - updateFromMouseEvent(event); - - // Show context menu? - if (event->button() == Qt::RightButton) - { - PDFWidget* widget = m_proxy->getWidget(); - std::vector currentPages = widget->getDrawWidget()->getCurrentPages(); - - if (!hasAnyPageAnnotation(currentPages)) - { - // All pages doesn't have annotation - return; - } - - m_editableAnnotation = PDFObjectReference(); - m_editableAnnotationPage = PDFObjectReference(); - for (PDFInteger pageIndex : currentPages) - { - PageAnnotations& pageAnnotations = getPageAnnotations(pageIndex); - for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) - { - if (!pageAnnotation.isHovered) - { - continue; - } - - if (!PDFAnnotation::isTypeEditable(pageAnnotation.annotation->getType())) - { - continue; - } - - m_editableAnnotation = pageAnnotation.annotation->getSelfReference(); - m_editableAnnotationPage = pageAnnotation.annotation->getPageReference(); - - if (!m_editableAnnotationPage.isValid()) - { - m_editableAnnotationPage = m_document->getCatalog()->getPage(pageIndex)->getPageReference(); - } - break; - } - } - - if (m_editableAnnotation.isValid()) - { - QMenu menu(tr("Annotation"), widget); - QAction* showPopupAction = menu.addAction(tr("Show Popup Window")); - QAction* copyAction = menu.addAction(tr("Copy to Multiple Pages")); - QAction* editAction = menu.addAction(tr("Edit")); - QAction* deleteAction = menu.addAction(tr("Delete")); - connect(showPopupAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onShowPopupAnnotation); - connect(copyAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onCopyAnnotation); - connect(editAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onEditAnnotation); - connect(deleteAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onDeleteAnnotation); - - m_editableAnnotationGlobalPosition = widget->mapToGlobal(event->pos()); - menu.exec(m_editableAnnotationGlobalPosition); - } - } -} - -void PDFWidgetAnnotationManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFWidgetAnnotationManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - updateFromMouseEvent(event); -} - -void PDFWidgetAnnotationManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) -{ - Q_UNUSED(widget); - - updateFromMouseEvent(event); -} - -void PDFWidgetAnnotationManager::wheelEvent(QWidget* widget, QWheelEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event) -{ - PDFWidget* widget = m_proxy->getWidget(); - std::vector currentPages = widget->getDrawWidget()->getCurrentPages(); - - if (!hasAnyPageAnnotation(currentPages)) - { - // All pages doesn't have annotation - return; - } - - m_tooltip = QString(); - m_cursor = std::nullopt; - bool appearanceChanged = false; - - // We must update appearance states, and update tooltip - PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); - const bool isDown = event->buttons().testFlag(Qt::LeftButton); - const PDFAppeareanceStreams::Appearance hoverAppearance = isDown ? PDFAppeareanceStreams::Appearance::Down : PDFAppeareanceStreams::Appearance::Rollover; - - for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) - { - PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex); - for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) - { - if (pageAnnotation.annotation->isReplyTo()) - { - // Annotation is reply to another annotation, do not interact with it - continue; - } - - const PDFAppeareanceStreams::Appearance oldAppearance = pageAnnotation.appearance; - QRectF annotationRect = pageAnnotation.annotation->getRectangle(); - QMatrix matrix = prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect); - QPainterPath path; - path.addRect(annotationRect); - path = matrix.map(path); - - if (path.contains(event->pos())) - { - pageAnnotation.appearance = hoverAppearance; - pageAnnotation.isHovered = true; - - // Generate tooltip - if (m_tooltip.isEmpty()) - { - const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); - if (markupAnnotation) - { - QString title = markupAnnotation->getWindowTitle(); - if (title.isEmpty()) - { - title = markupAnnotation->getSubject(); - } - if (title.isEmpty()) - { - title = PDFTranslationContext::tr("Info"); - } - - const size_t repliesCount = pageAnnotations.getReplies(pageAnnotation).size(); - if (repliesCount > 0) - { - title = PDFTranslationContext::tr("%1 (%2 replies)").arg(title).arg(repliesCount); - } - - m_tooltip = QString("

%1

%2

").arg(title, markupAnnotation->getContents()); - } - } - - const PDFAction* linkAction = nullptr; - const AnnotationType annotationType = pageAnnotation.annotation->getType(); - if (annotationType == AnnotationType::Link) - { - const PDFLinkAnnotation* linkAnnotation = dynamic_cast(pageAnnotation.annotation.data()); - Q_ASSERT(linkAnnotation); - - // We must check, if user clicked to the link area - QPainterPath activationPath = linkAnnotation->getActivationRegion().getPath(); - activationPath = snapshotItem.pageToDeviceMatrix.map(activationPath); - if (activationPath.contains(event->pos()) && linkAnnotation->getAction()) - { - m_cursor = QCursor(Qt::PointingHandCursor); - linkAction = linkAnnotation->getAction(); - } - } - if (annotationType == AnnotationType::Widget) - { - if (m_formManager && m_formManager->hasFormFieldWidgetText(pageAnnotation.annotation->getSelfReference())) - { - m_cursor = QCursor(Qt::IBeamCursor); - } - else - { - m_cursor = QCursor(Qt::ArrowCursor); - } - } - - // Generate popup window - if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) - { - const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); - if (markupAnnotation) - { - QDialog* dialog = createDialogForMarkupAnnotations(widget, pageAnnotation, pageAnnotations); - - // Set proper dialog position - according to the popup annotation. If we - // do not have popup annotation, then try to use annotations rectangle. - if (const PageAnnotation* popupAnnotation = pageAnnotations.getPopupAnnotation(pageAnnotation)) - { - QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(popupAnnotation->annotation->getRectangle().bottomLeft()).toPoint(); - popupPoint = widget->mapToGlobal(popupPoint); - dialog->move(popupPoint); - } - else if (markupAnnotation->getRectangle().isValid()) - { - QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(markupAnnotation->getRectangle().bottomRight()).toPoint(); - popupPoint = widget->mapToGlobal(popupPoint); - dialog->move(popupPoint); - } - - dialog->exec(); - } - - if (linkAction) - { - emit actionTriggered(linkAction); - } - } - } - else - { - pageAnnotation.appearance = PDFAppeareanceStreams::Appearance::Normal; - pageAnnotation.isHovered = false; - } - - const bool currentAppearanceChanged = oldAppearance != pageAnnotation.appearance; - if (currentAppearanceChanged) - { - // We have changed appearance - we must mark stream as dirty - pageAnnotation.appearanceStream.dirty(); - appearanceChanged = true; - } - } - } - - // If appearance has changed, then we must redraw the page - if (appearanceChanged) - { - emit widget->getDrawWidgetProxy()->repaintNeeded(); - } -} - -void PDFWidgetAnnotationManager::onShowPopupAnnotation() -{ - PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); - for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) - { - PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex); - for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) - { - if (pageAnnotation.annotation->isReplyTo()) - { - // Annotation is reply to another annotation, do not interact with it - continue; - } - - if (pageAnnotation.annotation->getSelfReference() == m_editableAnnotation) - { - QDialog* dialog = createDialogForMarkupAnnotations(m_proxy->getWidget(), pageAnnotation, pageAnnotations); - dialog->move(m_editableAnnotationGlobalPosition); - dialog->exec(); - return; - } - } - } -} - -void PDFWidgetAnnotationManager::onCopyAnnotation() -{ - pdf::PDFSelectPagesDialog dialog(tr("Copy Annotation"), tr("Copy Annotation onto Multiple Pages"), - m_document->getCatalog()->getPageCount(), m_proxy->getWidget()->getDrawWidget()->getCurrentPages(), m_proxy->getWidget()); - if (dialog.exec() == QDialog::Accepted) - { - std::vector pages = dialog.getSelectedPages(); - const PDFInteger currentPageIndex = m_document->getCatalog()->getPageIndexFromPageReference(m_editableAnnotationPage); - - for (PDFInteger& pageIndex : pages) - { - --pageIndex; - } - - auto it = std::find(pages.begin(), pages.end(), currentPageIndex); - if (it != pages.end()) - { - pages.erase(it); - } - - if (pages.empty()) - { - return; - } - - PDFDocumentModifier modifier(m_document); - modifier.markAnnotationsChanged(); - - for (const PDFInteger pageIndex : pages) - { - modifier.getBuilder()->copyAnnotation(m_document->getCatalog()->getPage(pageIndex)->getPageReference(), m_editableAnnotation); - } - - if (modifier.finalize()) - { - emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - } -} - -void PDFWidgetAnnotationManager::onEditAnnotation() -{ - PDFEditObjectDialog dialog(EditObjectType::Annotation, m_proxy->getWidget()); - - PDFObject originalObject = m_document->getObjectByReference(m_editableAnnotation); - dialog.setObject(originalObject); - - if (dialog.exec() == PDFEditObjectDialog::Accepted) - { - PDFObject object = dialog.getObject(); - if (object != originalObject) - { - PDFDocumentModifier modifier(m_document); - modifier.markAnnotationsChanged(); - modifier.getBuilder()->setObject(m_editableAnnotation, object); - modifier.getBuilder()->updateAnnotationAppearanceStreams(m_editableAnnotation); - - if (modifier.finalize()) - { - emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - } - } -} - -void PDFWidgetAnnotationManager::onDeleteAnnotation() -{ - if (m_editableAnnotation.isValid()) - { - PDFDocumentModifier modifier(m_document); - modifier.markAnnotationsChanged(); - modifier.getBuilder()->removeAnnotation(m_editableAnnotationPage, m_editableAnnotation); - - if (modifier.finalize()) - { - emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - } -} - -QDialog* PDFWidgetAnnotationManager::createDialogForMarkupAnnotations(PDFWidget* widget, - const PageAnnotation& pageAnnotation, - const PageAnnotations& pageAnnotations) -{ - QDialog* dialog = new QDialog(widget->getDrawWidget()->getWidget(), Qt::Popup); - dialog->setAttribute(Qt::WA_DeleteOnClose, true); - createWidgetsForMarkupAnnotations(dialog, pageAnnotation, pageAnnotations); - return dialog; -} - -void PDFWidgetAnnotationManager::createWidgetsForMarkupAnnotations(QWidget* parentWidget, - const PageAnnotation& pageAnnotation, - const PageAnnotations& pageAnnotations) -{ - std::vector replies = pageAnnotations.getReplies(pageAnnotation); - replies.insert(replies.begin(), &pageAnnotation); - - QScrollArea* scrollArea = new QScrollArea(parentWidget); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - - QVBoxLayout* layout = new QVBoxLayout(parentWidget); - layout->addWidget(scrollArea); - layout->setMargin(0); - layout->setContentsMargins(QMargins()); - - QWidget* frameWidget = new QWidget(scrollArea); - QVBoxLayout* frameLayout = new QVBoxLayout(frameWidget); - frameLayout->setMargin(0); - frameLayout->setSpacing(0); - scrollArea->setWidget(frameWidget); - - const PDFMarkupAnnotation* markupMainAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); - QColor color = markupMainAnnotation->getDrawColorFromAnnotationColor(markupMainAnnotation->getColor(), 1.0); - QColor titleColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.2, 1.0); - QColor backgroundColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.9, 1.0); - - QString style = "QGroupBox { " - "border: 2px solid black; " - "border-color: rgb(%4, %5, %6); " - "margin-top: 3ex; " - "background-color: rgb(%1, %2, %3); " - "}" - "QGroupBox::title { " - "subcontrol-origin: margin; " - "subcontrol-position: top center; " - "padding: 0px 8192px; " - "background-color: rgb(%4, %5, %6); " - "color: #FFFFFF;" - "}"; - style = style.arg(backgroundColor.red()).arg(backgroundColor.green()).arg(backgroundColor.blue()).arg(titleColor.red()).arg(titleColor.green()).arg(titleColor.blue()); - - for (const PageAnnotation* annotation : replies) - { - const PDFMarkupAnnotation* markupAnnotation = annotation->annotation->asMarkupAnnotation(); - - if (!markupAnnotation) - { - // This should not happen... - continue; - } - - QGroupBox* groupBox = new QGroupBox(scrollArea); - frameLayout->addWidget(groupBox); - - QString title = markupAnnotation->getWindowTitle(); - if (title.isEmpty()) - { - title = markupAnnotation->getSubject(); - } - - QString dateTimeString = markupAnnotation->getCreationDate().toLocalTime().toString(Qt::SystemLocaleLongDate); - title = QString("%1 (%2)").arg(title, dateTimeString).trimmed(); - - groupBox->setStyleSheet(style); - groupBox->setTitle(title); - QVBoxLayout* groupBoxLayout = new QVBoxLayout(groupBox); - - QLabel* label = new QLabel(groupBox); - label->setTextInteractionFlags(Qt::TextBrowserInteraction); - label->setWordWrap(true); - label->setText(markupAnnotation->getContents()); - label->setFixedWidth(PDFWidgetUtils::scaleDPI_x(label, 250)); - label->setMinimumHeight(label->sizeHint().height()); - groupBoxLayout->addWidget(label); - } - - frameWidget->setFixedSize(frameWidget->minimumSizeHint()); - parentWidget->setFixedSize(scrollArea->sizeHint()); -} - -void PDFSimpleGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - Q_ASSERT(parameters.painter); - parameters.boundingRectangle = getRectangle(); - - QPainter& painter = *parameters.painter; - painter.setPen(getPen()); - painter.setBrush(getBrush()); - painter.setCompositionMode(getCompositionMode()); - - switch (getType()) - { - case AnnotationType::Square: - { - const PDFAnnotationBorder& border = getBorder(); - - const PDFReal hCornerRadius = border.getHorizontalCornerRadius(); - const PDFReal vCornerRadius = border.getVerticalCornerRadius(); - const bool isRounded = !qFuzzyIsNull(hCornerRadius) || !qFuzzyIsNull(vCornerRadius); - - if (isRounded) - { - painter.drawRoundedRect(getRectangle(), hCornerRadius, vCornerRadius, Qt::AbsoluteSize); - } - else - { - painter.drawRect(getRectangle()); - } - break; - } - - case AnnotationType::Circle: - { - const PDFAnnotationBorder& border = getBorder(); - const PDFReal width = border.getWidth(); - QRectF rectangle = getRectangle(); - rectangle.adjust(width, width, -width, -width); - painter.drawEllipse(rectangle); - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } -} - -QColor PDFSimpleGeometryAnnotation::getFillColor() const -{ - return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); -} - -bool PDFMarkupAnnotation::isReplyTo() const -{ - return m_inReplyTo.isValid() && m_replyType == ReplyType::Reply; -} - -void PDFMarkupAnnotation::setWindowTitle(const QString& windowTitle) -{ - m_windowTitle = windowTitle; -} - -void PDFMarkupAnnotation::setPopupAnnotation(const PDFObjectReference& popupAnnotation) -{ - m_popupAnnotation = popupAnnotation; -} - -void PDFMarkupAnnotation::setRichTextString(const QString& richTextString) -{ - m_richTextString = richTextString; -} - -void PDFMarkupAnnotation::setCreationDate(const QDateTime& creationDate) -{ - m_creationDate = creationDate; -} - -void PDFMarkupAnnotation::setInReplyTo(PDFObjectReference inReplyTo) -{ - m_inReplyTo = inReplyTo; -} - -void PDFMarkupAnnotation::setSubject(const QString& subject) -{ - m_subject = subject; -} - -void PDFMarkupAnnotation::setIntent(const QByteArray& intent) -{ - m_intent = intent; -} - -void PDFMarkupAnnotation::setExternalData(const PDFObject& externalData) -{ - m_externalData = externalData; -} - -void PDFMarkupAnnotation::setReplyType(ReplyType replyType) -{ - m_replyType = replyType; -} - -std::vector PDFTextAnnotation::getDrawKeys(const PDFFormManager* formManager) const -{ - Q_UNUSED(formManager); - - return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() }, - PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Rollover, QByteArray() }, - PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; -} - -void PDFTextAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QColor strokeColor = QColor::fromRgbF(0.0, 0.0, 0.0, getStrokeOpacity()); - QColor fillColor = (parameters.key.first == PDFAppeareanceStreams::Appearance::Normal) ? QColor::fromRgbF(1.0, 1.0, 0.0, getFillOpacity()) : - QColor::fromRgbF(1.0, 0.0, 0.0, getFillOpacity()); - - constexpr const PDFReal rectSize = 32.0; - constexpr const PDFReal penWidth = 2.0; - - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - QRectF rectangle = getRectangle(); - rectangle.setSize(QSizeF(rectSize, rectSize)); - - QPen pen(strokeColor); - pen.setWidthF(penWidth); - painter.setPen(pen); - - painter.setBrush(QBrush(fillColor, Qt::SolidPattern)); - - QRectF ellipseRectangle = rectangle; - ellipseRectangle.adjust(penWidth, penWidth, -penWidth, -penWidth); - - // Draw the ellipse - painter.drawEllipse(ellipseRectangle); - - QFont font = QApplication::font(); - font.setPixelSize(16.0); - - QString text = getTextForIcon(m_iconName); - - QPainterPath textPath; - textPath.addText(0.0, 0.0, font, text); - textPath = QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0).map(textPath); - QRectF textBoundingRect = textPath.boundingRect(); - QPointF offset = rectangle.center() - textBoundingRect.center(); - textPath.translate(offset); - painter.fillPath(textPath, QBrush(strokeColor, Qt::SolidPattern)); - - parameters.boundingRectangle = rectangle; -} - -PDFTextAnnotation::Flags PDFTextAnnotation::getEffectiveFlags() const -{ - return getFlags() | NoZoom | NoRotate; -} - -QIcon PDFTextAnnotation::createIcon(QString key, QSize size) -{ - QIcon icon; - - QPixmap pixmap(size); - pixmap.fill(Qt::transparent); - - QRect rectangle(QPoint(0, 0), size); - rectangle.adjust(1, 1, -1, -1); - - QPainter painter(&pixmap); - painter.setRenderHint(QPainter::Antialiasing); - painter.setRenderHint(QPainter::TextAntialiasing); - - QString text = getTextForIcon(key); - - QFont font = QApplication::font(); - font.setPixelSize(size.height() * 0.75); - - QPainterPath textPath; - textPath.addText(0.0, 0.0, font, text); - QRectF textBoundingRect = textPath.boundingRect(); - QPointF offset = rectangle.center() - textBoundingRect.center(); - textPath.translate(offset); - painter.fillPath(textPath, QBrush(Qt::black, Qt::SolidPattern)); - painter.end(); - - icon.addPixmap(qMove(pixmap)); - return icon; -} - -QString PDFTextAnnotation::getTextForIcon(const QString& key) -{ - QString text = "?"; - if (key == "Comment") - { - text = QString::fromUtf16(u"\U0001F4AC"); - } - else if (key == "Help") - { - text = "?"; - } - else if (key == "Insert") - { - text = QString::fromUtf16(u"\u2380"); - } - else if (key == "Key") - { - text = QString::fromUtf16(u"\U0001F511"); - } - else if (key == "NewParagraph") - { - text = QString::fromUtf16(u"\u2606"); - } - else if (key == "Note") - { - text = QString::fromUtf16(u"\u266A"); - } - else if (key == "Paragraph") - { - text = QString::fromUtf16(u"\u00B6"); - } - return text; -} - -void PDFLineAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QLineF line = getLine(); - if (line.isNull()) - { - // Jakub Melka: do not draw empty lines - return; - } - - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - painter.setPen(getPen()); - painter.setBrush(getBrush()); - - QPainterPath boundingPath; - boundingPath.moveTo(line.p1()); - boundingPath.lineTo(line.p2()); - - LineGeometryInfo info = LineGeometryInfo::create(line); - - const PDFReal leaderLineLength = getLeaderLineLength(); - const PDFReal coefficientSigned = (leaderLineLength >= 0.0) ? 1.0 : -1.0; - const PDFReal leaderLineOffset = getLeaderLineOffset() * coefficientSigned; - const PDFReal leaderLineExtension = getLeaderLineExtension() * coefficientSigned; - const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5); - const bool hasLeaderLine = !qFuzzyIsNull(leaderLineLength) || !qFuzzyIsNull(leaderLineExtension); - - QLineF normalLine = info.transformedLine.normalVector().unitVector(); - QPointF normalVector = normalLine.p1() - normalLine.p2(); - - QLineF lineToPaint = info.transformedLine; - if (hasLeaderLine) - { - // We will draw leader lines at both start/end - QPointF p1llStart = info.transformedLine.p1() + normalVector * leaderLineOffset; - QPointF p1llEnd = info.transformedLine.p1() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension); - - QLineF llStart(p1llStart, p1llEnd); - llStart = info.LCStoGCS.map(llStart); - - boundingPath.moveTo(llStart.p1()); - boundingPath.lineTo(llStart.p2()); - painter.drawLine(llStart); - - QPointF p2llStart = info.transformedLine.p2() + normalVector * leaderLineOffset; - QPointF p2llEnd = info.transformedLine.p2() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension); - - QLineF llEnd(p2llStart, p2llEnd); - llEnd = info.LCStoGCS.map(llEnd); - - boundingPath.moveTo(llEnd.p1()); - boundingPath.lineTo(llEnd.p2()); - painter.drawLine(llEnd); - - lineToPaint.translate(normalVector * (leaderLineOffset + leaderLineLength)); - } - - QLineF lineToPaintOrig = info.LCStoGCS.map(lineToPaint); - info = LineGeometryInfo::create(lineToPaintOrig); - drawLine(info, painter, lineEndingSize, getStartLineEnding(), getEndLineEnding(), boundingPath, getCaptionOffset(), getContents(), getCaptionPosition() == CaptionPosition::Top); - - parameters.boundingRectangle = boundingPath.boundingRect(); - parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize); -} - -QColor PDFLineAnnotation::getFillColor() const -{ - return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); -} - -void PDFPolygonalGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - if (m_vertices.empty()) - { - // Jakub Melka: do not draw empty lines - return; - } - - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - painter.setPen(getPen()); - painter.setBrush(getBrush()); - - const PDFReal penWidth = painter.pen().widthF(); - switch (m_type) - { - case AnnotationType::Polygon: - { - if (m_path.isEmpty()) - { - QPolygonF polygon; - polygon.reserve(int(m_vertices.size() + 1)); - for (const QPointF& point : m_vertices) - { - polygon << point; - } - if (!polygon.isClosed()) - { - polygon << m_vertices.front(); - } - - painter.drawPolygon(polygon, Qt::OddEvenFill); - parameters.boundingRectangle = polygon.boundingRect(); - parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); - } - else - { - painter.drawPath(m_path); - parameters.boundingRectangle = m_path.boundingRect(); - parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); - } - - break; - } - - case AnnotationType::Polyline: - { - const PDFReal lineEndingSize = painter.pen().widthF() * 5.0; - QPainterPath boundingPath; - - if (m_path.isEmpty()) - { - const size_t pointCount = m_vertices.size(); - const size_t lastPoint = pointCount - 1; - for (size_t i = 1; i < pointCount; ++i) - { - if (i == 1) - { - // We draw first line - drawLine(LineGeometryInfo::create(QLineF(m_vertices[i - 1], m_vertices[i])), painter, lineEndingSize, getStartLineEnding(), AnnotationLineEnding::None, boundingPath, QPointF(), QString(), true); - } - else if (i == lastPoint) - { - // We draw last line - drawLine(LineGeometryInfo::create(QLineF(m_vertices[i - 1], m_vertices[i])), painter, lineEndingSize, AnnotationLineEnding::None, getEndLineEnding(), boundingPath, QPointF(), QString(), true); - } - else - { - QLineF line(m_vertices[i - 1], m_vertices[i]); - boundingPath.moveTo(line.p1()); - boundingPath.lineTo(line.p2()); - painter.drawLine(line); - } - } - } - else - { - const PDFReal angle = 30; - const PDFReal lineEndingHalfSize = lineEndingSize * 0.5; - const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle)); - - boundingPath = m_path; - painter.drawPath(m_path); - - QMatrix LCStoGCS_start = LineGeometryInfo::create(QLineF(m_path.pointAtPercent(0.00), m_path.pointAtPercent(0.01))).LCStoGCS; - QMatrix LCStoGCS_end = LineGeometryInfo::create(QLineF(m_path.pointAtPercent(0.99), m_path.pointAtPercent(1.00))).LCStoGCS; - - drawLineEnding(&painter, m_path.pointAtPercent(0), lineEndingSize, arrowAxisLength, getStartLineEnding(), false, LCStoGCS_start, boundingPath); - drawLineEnding(&painter, m_path.pointAtPercent(1), lineEndingSize, arrowAxisLength, getEndLineEnding(), true, LCStoGCS_end, boundingPath); - } - - parameters.boundingRectangle = boundingPath.boundingRect(); - parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize); - break; - } - - default: - Q_ASSERT(false); - break; - } -} - -QColor PDFPolygonalGeometryAnnotation::getFillColor() const -{ - return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); -} - -PDFAnnotation::LineGeometryInfo PDFAnnotation::LineGeometryInfo::create(QLineF line) -{ - LineGeometryInfo result; - result.originalLine = line; - result.transformedLine = QLineF(QPointF(0, 0), QPointF(line.length(), 0)); - - // Strategy: for simplification, we rotate the line clockwise so we will - // get the line axis equal to the x-axis. - const double angle = line.angleTo(QLineF(0, 0, 1, 0)); - - QPointF p1 = line.p1(); - - // Matrix LCStoGCS is local coordinate system of line line. It transforms - // points on the line to the global coordinate system. So, point (0, 0) will - // map onto p1 and point (length(p1-p2), 0) will map onto p2. - result.LCStoGCS = QMatrix(); - result.LCStoGCS.translate(p1.x(), p1.y()); - result.LCStoGCS.rotate(angle); - result.GCStoLCS = result.LCStoGCS.inverted(); - - return result; -} - -void PDFAnnotation::drawLineEnding(QPainter* painter, - QPointF point, - PDFReal lineEndingSize, - PDFReal arrowAxisLength, - AnnotationLineEnding ending, - bool flipAxis, - const QMatrix& LCStoGCS, - QPainterPath& boundingPath) const -{ - QPainterPath path; - const PDFReal lineEndingHalfSize = lineEndingSize * 0.5; - - switch (ending) - { - case AnnotationLineEnding::None: - break; - - case AnnotationLineEnding::Square: - { - path.addRect(-lineEndingHalfSize, -lineEndingHalfSize, lineEndingSize, lineEndingSize); - break; - } - - case AnnotationLineEnding::Circle: - { - path.addEllipse(QPointF(0, 0), lineEndingHalfSize, lineEndingHalfSize); - break; - } - - case AnnotationLineEnding::Diamond: - { - path.moveTo(0.0, -lineEndingHalfSize); - path.lineTo(lineEndingHalfSize, 0.0); - path.lineTo(0.0, +lineEndingHalfSize); - path.lineTo(-lineEndingHalfSize, 0.0); - path.closeSubpath(); - break; - } - - case AnnotationLineEnding::OpenArrow: - { - path.moveTo(0.0, 0.0); - path.lineTo(arrowAxisLength, lineEndingHalfSize); - path.moveTo(0.0, 0.0); - path.lineTo(arrowAxisLength, -lineEndingHalfSize); - break; - } - - case AnnotationLineEnding::ClosedArrow: - { - path.moveTo(0.0, 0.0); - path.lineTo(arrowAxisLength, lineEndingHalfSize); - path.lineTo(arrowAxisLength, -lineEndingHalfSize); - path.closeSubpath(); - break; - } - - case AnnotationLineEnding::Butt: - { - path.moveTo(0.0, -lineEndingHalfSize); - path.lineTo(0.0, lineEndingHalfSize); - break; - } - - case AnnotationLineEnding::ROpenArrow: - { - path.moveTo(0.0, 0.0); - path.lineTo(-arrowAxisLength, lineEndingHalfSize); - path.moveTo(0.0, 0.0); - path.lineTo(-arrowAxisLength, -lineEndingHalfSize); - break; - } - - case AnnotationLineEnding::RClosedArrow: - { - path.moveTo(0.0, 0.0); - path.lineTo(-arrowAxisLength, lineEndingHalfSize); - path.lineTo(-arrowAxisLength, -lineEndingHalfSize); - path.closeSubpath(); - break; - } - - case AnnotationLineEnding::Slash: - { - const PDFReal angle = 60; - const PDFReal lineEndingHalfSizeForSlash = lineEndingSize * 0.5; - const PDFReal slashAxisLength = lineEndingHalfSizeForSlash / qTan(qDegreesToRadians(angle)); - - path.moveTo(-slashAxisLength, -lineEndingHalfSizeForSlash); - path.lineTo(slashAxisLength, lineEndingHalfSizeForSlash); - break; - } - - default: - break; - } - - if (!path.isEmpty()) - { - // Flip the x-axis (we are drawing endpoint) - if (flipAxis && ending != AnnotationLineEnding::Slash) - { - QMatrix matrix; - matrix.scale(-1.0, 1.0); - path = matrix.map(path); - } - - path.translate(point); - path = LCStoGCS.map(path); - painter->drawPath(path); - boundingPath.addPath(path); - } -} - -void PDFAnnotation::drawLine(const PDFAnnotation::LineGeometryInfo& info, - QPainter& painter, - PDFReal lineEndingSize, - AnnotationLineEnding p1Ending, - AnnotationLineEnding p2Ending, - QPainterPath& boundingPath, - QPointF textOffset, - const QString& text, - bool textIsAboveLine) const -{ - const PDFReal angle = 30; - const PDFReal lineEndingHalfSize = lineEndingSize * 0.5; - const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle)); - - auto getOffsetFromLineEnding = [lineEndingHalfSize, arrowAxisLength](AnnotationLineEnding ending) - { - switch (ending) - { - case AnnotationLineEnding::Square: - case AnnotationLineEnding::Circle: - case AnnotationLineEnding::Diamond: - return lineEndingHalfSize; - case AnnotationLineEnding::ClosedArrow: - return arrowAxisLength; - - default: - break; - } - - return 0.0; - }; - - // Remove the offset from start/end - const PDFReal startOffset = getOffsetFromLineEnding(p1Ending); - const PDFReal endOffset = getOffsetFromLineEnding(p2Ending); - - QLineF adjustedLine = info.transformedLine; - adjustedLine.setP1(adjustedLine.p1() + QPointF(startOffset, 0)); - adjustedLine.setP2(adjustedLine.p2() - QPointF(endOffset, 0)); - - int textVerticalOffset = 0; - const bool drawText = !text.isEmpty(); - QPainterPath textPath; - - if (drawText) - { - QFont font = QApplication::font(); - font.setPixelSize(12.0); - - QFontMetricsF fontMetrics(font, painter.device()); - textVerticalOffset = fontMetrics.descent() + fontMetrics.leading(); - - textPath.addText(0, 0, font, text); - textPath = QMatrix(1, 0, 0, -1, 0, 0).map(textPath); - } - - drawLineEnding(&painter, info.transformedLine.p1(), lineEndingSize, arrowAxisLength, p1Ending, false, info.LCStoGCS, boundingPath); - drawLineEnding(&painter, info.transformedLine.p2(), lineEndingSize, arrowAxisLength, p2Ending, true, info.LCStoGCS, boundingPath); - - if (drawText && !textIsAboveLine) - { - // We will draw text in the line - QRectF textBoundingRect = textPath.controlPointRect(); - QPointF center = textBoundingRect.center(); - const qreal offsetY = center.y(); - const qreal offsetX = center.x(); - textPath.translate(textOffset + adjustedLine.center() - QPointF(offsetX, offsetY)); - textBoundingRect = textPath.controlPointRect(); - - const qreal textPadding = 3.0; - const qreal textStart = textBoundingRect.left() - textPadding; - const qreal textEnd = textBoundingRect.right() + textPadding; - - textPath = info.LCStoGCS.map(textPath); - painter.fillPath(textPath, QBrush(painter.pen().color(), Qt::SolidPattern)); - boundingPath.addPath(textPath); - - if (textStart > adjustedLine.p1().x()) - { - QLineF leftLine(adjustedLine.p1(), QPointF(textStart, adjustedLine.p2().y())); - painter.drawLine(info.LCStoGCS.map(leftLine)); - } - - if (textEnd < adjustedLine.p2().x()) - { - QLineF rightLine(QPointF(textEnd, adjustedLine.p2().y()), adjustedLine.p2()); - painter.drawLine(info.LCStoGCS.map(rightLine)); - } - - // Include whole line to the bounding path - adjustedLine = info.LCStoGCS.map(adjustedLine); - boundingPath.moveTo(adjustedLine.p1()); - boundingPath.lineTo(adjustedLine.p2()); - } - else - { - if (drawText) - { - // We will draw text above the line - QRectF textBoundingRect = textPath.controlPointRect(); - const qreal offsetY = textBoundingRect.top() - textVerticalOffset; - const qreal offsetX = textBoundingRect.center().x(); - textPath.translate(textOffset + adjustedLine.center() - QPointF(offsetX, offsetY)); - - textPath = info.LCStoGCS.map(textPath); - painter.fillPath(textPath, QBrush(painter.pen().color(), Qt::SolidPattern)); - boundingPath.addPath(textPath); - } - - adjustedLine = info.LCStoGCS.map(adjustedLine); - painter.drawLine(adjustedLine); - - boundingPath.moveTo(adjustedLine.p1()); - boundingPath.lineTo(adjustedLine.p2()); - } -} - -void PDFHighlightAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - if (m_highlightArea.isEmpty()) - { - // Jakub Melka: do not draw empty highlight area - return; - } - - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - parameters.boundingRectangle = m_highlightArea.getPath().boundingRect(); - - painter.setPen(getPen()); - painter.setBrush(getBrush()); - switch (m_type) - { - case AnnotationType::Highlight: - { - painter.setCompositionMode(QPainter::CompositionMode_Multiply); - painter.fillPath(m_highlightArea.getPath(), QBrush(getStrokeColor(), Qt::SolidPattern)); - break; - } - - case AnnotationType::Underline: - { - for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) - { - QPointF p1 = quadrilateral[0]; - QPointF p2 = quadrilateral[1]; - QLineF line(p1, p2); - painter.drawLine(line); - } - break; - } - - case AnnotationType::Squiggly: - { - // Jakub Melka: Squiggly underline - for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) - { - QPointF p1 = quadrilateral[0]; - QPointF p2 = quadrilateral[1]; - - // Calculate length (height) of quadrilateral - const PDFReal height = (QLineF(quadrilateral[0], quadrilateral[2]).length() + QLineF(quadrilateral[1], quadrilateral[3]).length()) * 0.5; - const PDFReal markSize = height / 7.0; - - // We can't assume, that line is horizontal. For example, rotated text with 45° degrees - // counterclockwise, if it is highlighted with squiggly underline, it is not horizontal line. - // So, we must calculate line geometry and transform it. - QLineF line(p1, p2); - LineGeometryInfo lineGeometryInfo = LineGeometryInfo::create(line); - - bool leadingEdge = true; - for (PDFReal x = lineGeometryInfo.transformedLine.p1().x(); x < lineGeometryInfo.transformedLine.p2().x(); x+= markSize) - { - QLineF line; - if (leadingEdge) - { - line = QLineF(x, 0.0, x + markSize, markSize); - } - else - { - // Falling edge - line = QLineF(x, markSize, x + markSize, 0.0); - } - - QLineF transformedLine = lineGeometryInfo.LCStoGCS.map(line); - painter.drawLine(transformedLine); - leadingEdge = !leadingEdge; - } - } - break; - } - - case AnnotationType::StrikeOut: - { - for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) - { - QPointF p1 = (quadrilateral[0] + quadrilateral[2]) * 0.5; - QPointF p2 = (quadrilateral[1] + quadrilateral[3]) * 0.5; - QLineF line(p1, p2); - painter.drawLine(line); - } - break; - } - - default: - break; - } - - const qreal penWidth = painter.pen().widthF(); - parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); -} - -std::vector PDFLinkAnnotation::getDrawKeys(const PDFFormManager* formManager) const -{ - Q_UNUSED(formManager); - - return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; -} - -void PDFLinkAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - if (parameters.key.first != PDFAppeareanceStreams::Appearance::Down || - m_activationRegion.isEmpty() || - m_highlightMode == LinkHighlightMode::None) - { - // Nothing to draw - return; - } - - QPainter& painter = *parameters.painter; - parameters.boundingRectangle = getRectangle(); - - switch (m_highlightMode) - { - case LinkHighlightMode::Invert: - { - // Invert all - painter.setCompositionMode(QPainter::CompositionMode_Difference); - painter.fillPath(m_activationRegion.getPath(), QBrush(Qt::white, Qt::SolidPattern)); - break; - } - - case LinkHighlightMode::Outline: - { - // Invert the border - painter.setCompositionMode(QPainter::CompositionMode_Difference); - QPen pen = getPen(); - pen.setColor(Qt::white); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - painter.drawPath(m_activationRegion.getPath()); - break; - } - - case LinkHighlightMode::Push: - { - // Draw border - painter.setCompositionMode(getCompositionMode()); - painter.setPen(getPen()); - painter.setBrush(Qt::NoBrush); - painter.drawPath(m_activationRegion.getPath()); - break; - } - - default: - Q_ASSERT(false); - break; - } -} - -PDFAnnotationDefaultAppearance PDFAnnotationDefaultAppearance::parse(const QByteArray& string) -{ - PDFAnnotationDefaultAppearance result; - PDFLexicalAnalyzer analyzer(string.constData(), string.constData() + string.size()); - - std::vector tokens; - - for (PDFLexicalAnalyzer::Token token = analyzer.fetch(); token.type != PDFLexicalAnalyzer::TokenType::EndOfFile; token = analyzer.fetch()) - { - tokens.push_back(qMove(token)); - } - - auto readNumber = [&tokens](size_t index) -> PDFReal - { - Q_ASSERT(index >= 0 && index < tokens.size()); - const PDFLexicalAnalyzer::Token& token = tokens[index]; - if (token.type == PDFLexicalAnalyzer::TokenType::Real || - token.type == PDFLexicalAnalyzer::TokenType::Integer) - { - return token.data.toDouble(); - } - - return 0.0; - }; - - for (size_t i = 0; i < tokens.size(); ++i) - { - const PDFLexicalAnalyzer::Token& token = tokens[i]; - if (token.type == PDFLexicalAnalyzer::TokenType::Command) - { - QByteArray command = token.data.toByteArray(); - if (command == "Tf") - { - if (i >= 1) - { - result.m_fontSize = readNumber(i - 1); - } - if (i >= 2) - { - result.m_fontName = tokens[i - 2].data.toByteArray(); - } - } - else if (command == "g" && i >= 1) - { - result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 1) }, 1.0); - } - else if (command == "rg" && i >= 3) - { - result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 3), readNumber(i - 2), readNumber(i - 1) }, 1.0); - } - else if (command == "k" && i >= 4) - { - result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 4), readNumber(i - 3), readNumber(i - 2), readNumber(i - 1) }, 1.0); - } - } - } - - return result; -} - -void PDFFreeTextAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - parameters.boundingRectangle = getRectangle(); - - painter.setPen(getPen()); - painter.setBrush(getBrush()); - - // Draw callout line - const PDFAnnotationCalloutLine& calloutLine = getCalloutLine(); - - switch (calloutLine.getType()) - { - case PDFAnnotationCalloutLine::Type::Invalid: - { - // Annotation doesn't have callout line - break; - } - - case PDFAnnotationCalloutLine::Type::StartEnd: - { - QPainterPath boundingPath; - QLineF line(calloutLine.getPoint(0), calloutLine.getPoint(1)); - const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5); - drawLine(LineGeometryInfo::create(line), painter, lineEndingSize, - getStartLineEnding(), getEndLineEnding(), boundingPath, - QPointF(), QString(), true); - break; - } - - case PDFAnnotationCalloutLine::Type::StartKneeEnd: - { - QPainterPath boundingPath; - - QLineF lineStart(calloutLine.getPoint(0), calloutLine.getPoint(1)); - QLineF lineEnd(calloutLine.getPoint(1), calloutLine.getPoint(2)); - - PDFReal preferredLineEndingSize = painter.pen().widthF() * 5.0; - PDFReal lineStartEndingSize = qMin(preferredLineEndingSize, lineStart.length() * 0.5); - drawLine(LineGeometryInfo::create(lineStart), painter, lineStartEndingSize, - getStartLineEnding(), AnnotationLineEnding::None, boundingPath, - QPointF(), QString(), true); - - - PDFReal lineEndEndingSize = qMin(preferredLineEndingSize, lineEnd.length() * 0.5); - drawLine(LineGeometryInfo::create(lineEnd), painter, lineEndEndingSize, - AnnotationLineEnding::None, getEndLineEnding() , boundingPath, - QPointF(), QString(), true); - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - QRectF textRect = getTextRectangle(); - if (!textRect.isValid()) - { - textRect = getRectangle(); - } - - painter.drawRect(textRect); - - // Draw text - PDFAnnotationDefaultAppearance defaultAppearance = PDFAnnotationDefaultAppearance::parse(getDefaultAppearance()); - - QFont font(defaultAppearance.getFontName()); - font.setPixelSize(defaultAppearance.getFontSize()); - painter.setPen(defaultAppearance.getFontColor()); - - Qt::Alignment alignment = Qt::AlignTop; - switch (getJustification()) - { - case PDFFreeTextAnnotation::Justification::Left: - alignment |= Qt::AlignLeft; - break; - - case PDFFreeTextAnnotation::Justification::Centered: - alignment |= Qt::AlignHCenter; - break; - - case PDFFreeTextAnnotation::Justification::Right: - alignment |= Qt::AlignRight; - break; - - default: - alignment |= Qt::AlignLeft; - Q_ASSERT(false); - break; - } - - QTextOption option(alignment); - option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); - option.setUseDesignMetrics(true); - - painter.translate(textRect.left(), textRect.bottom()); - painter.scale(1.0, -1.0); - painter.drawText(QRectF(QPointF(0, 0), textRect.size()), getContents(), option); -} - -void PDFCaretAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - parameters.boundingRectangle = getRectangle(); - - QRectF caretRect = getCaretRectangle(); - if (caretRect.isEmpty()) - { - caretRect = getRectangle(); - } - - QPointF controlPoint(caretRect.center()); - controlPoint.setY(caretRect.top()); - - QPointF topPoint = controlPoint; - topPoint.setY(caretRect.bottom()); - - QPainterPath path; - path.moveTo(caretRect.topLeft()); - path.quadTo(controlPoint, topPoint); - path.quadTo(controlPoint, caretRect.topRight()); - path.lineTo(caretRect.topLeft()); - path.closeSubpath(); - - painter.fillPath(path, QBrush(getStrokeColor(), Qt::SolidPattern)); -} - -void PDFInkAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QPainter& painter = *parameters.painter; - QPainterPath path = getInkPath(); - - painter.setPen(getPen()); - painter.setBrush(getBrush()); - painter.setCompositionMode(getCompositionMode()); - - QPainterPath boundingPath; - QPainterPath currentPath; - const int elementCount = path.elementCount(); - bool hasSpline = false; - for (int i = 0; i < elementCount; ++i) - { - QPainterPath::Element element = path.elementAt(i); - switch (element.type) - { - case QPainterPath::MoveToElement: - { - // Reset the path - if (!currentPath.isEmpty()) - { - boundingPath.addPath(currentPath); - painter.drawPath(currentPath); - currentPath.clear(); - } - currentPath.moveTo(element.x, element.y); - break; - } - - case QPainterPath::LineToElement: - { - const QPointF p0 = currentPath.currentPosition(); - const QPointF p1(element.x, element.y); - - QLineF line(p0, p1); - QPointF normal = line.normalVector().p2() - p0; - - // Jakub Melka: This computation should be clarified. We use second order Bezier curves. - // We must calculate single control point. Let B(t) is bezier curve of second order. - // Then second derivation is B''(t) = 2(p0 - 2*Pc + p1). Second derivation curve is its - // normal. So, we calculate normal vector of the line (which has norm equal to line length), - // then we get following formula: - // - // Pc = (p0 + p1) / 2 - B''(t) / 4 - // - // So, for bezier curves of second order, second derivative is constant (which is not surprising, - // because second derivative of polynomial of order 2 is also a constant). Control point is then - // used to paint the path. - QPointF controlPoint = -normal * 0.25 + (p0 + p1) * 0.5; - currentPath.quadTo(controlPoint, p1); - break; - } - - case QPainterPath::CurveToElement: - case QPainterPath::CurveToDataElement: - hasSpline = true; - break; - - default: - Q_ASSERT(false); - break; - } - - // Jakub Melka: If we have a spline, then we don't do anything... - // Just copy the spline path. - if (hasSpline) - { - currentPath = path; - break; - } - } - - // Reset the path - if (!currentPath.isEmpty()) - { - boundingPath.addPath(currentPath); - painter.drawPath(currentPath); - currentPath.clear(); - } - - const qreal penWidth = painter.pen().widthF(); - parameters.boundingRectangle = boundingPath.boundingRect(); - parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); -} - -void PDFStampAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - - QString text = getText(m_stamp); - QColor color(Qt::red); - - switch (m_stamp) - { - case Stamp::Approved: - color = Qt::green; - break; - - case Stamp::AsIs: - case Stamp::Confidential: - break; - - case Stamp::Departmental: - color = Qt::blue; - break; - - case Stamp::Draft: - break; - - case Stamp::Experimental: - color = Qt::blue; - break; - - case Stamp::Expired: - case Stamp::Final: - break; - - case Stamp::ForComment: - color = Qt::green; - break; - - case Stamp::ForPublicRelease: - color = Qt::green; - break; - - case Stamp::NotApproved: - case Stamp::NotForPublicRelease: - break; - - case Stamp::Sold: - color = Qt::blue; - break; - - case Stamp::TopSecret: - break; - - default: - Q_ASSERT(false); - break; - } - - color.setAlphaF(getFillOpacity()); - - const PDFReal textHeight = 16; - QFont font("Courier New"); - font.setBold(true); - font.setPixelSize(textHeight); - - QFontMetricsF fontMetrics(font, painter.device()); - const qreal textWidth = fontMetrics.width(text); - const qreal rectangleWidth = textWidth + 10; - const qreal rectangleHeight = textHeight * 1.2; - const qreal penWidth = 2.0; - - QRectF rectangle = getRectangle(); - rectangle.setSize(QSizeF(rectangleWidth, rectangleHeight)); - - QPen pen(color); - pen.setWidthF(penWidth); - painter.setPen(pen); - painter.setBrush(Qt::NoBrush); - - painter.drawRoundedRect(rectangle, 5, 5, Qt::AbsoluteSize); - - // Draw text - QPainterPath textPath; - textPath.addText(0, 0, font, text); - textPath = QMatrix(1, 0, 0, -1, 0, 0).map(textPath); - - QPointF center = textPath.boundingRect().center(); - textPath.translate(rectangle.center() - center); - painter.fillPath(textPath, QBrush(color, Qt::SolidPattern)); - - parameters.boundingRectangle = rectangle; - parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); -} - -QString PDFStampAnnotation::getText(Stamp stamp) -{ - QString text; - - switch (stamp) - { - case Stamp::Approved: - text = PDFTranslationContext::tr("APPROVED"); - break; - - case Stamp::AsIs: - text = PDFTranslationContext::tr("AS IS"); - break; - - case Stamp::Confidential: - text = PDFTranslationContext::tr("CONFIDENTIAL"); - break; - - case Stamp::Departmental: - text = PDFTranslationContext::tr("DEPARTMENTAL"); - break; - - case Stamp::Draft: - text = PDFTranslationContext::tr("DRAFT"); - break; - - case Stamp::Experimental: - text = PDFTranslationContext::tr("EXPERIMENTAL"); - break; - - case Stamp::Expired: - text = PDFTranslationContext::tr("EXPIRED"); - break; - - case Stamp::Final: - text = PDFTranslationContext::tr("FINAL"); - break; - - case Stamp::ForComment: - text = PDFTranslationContext::tr("FOR COMMENT"); - break; - - case Stamp::ForPublicRelease: - text = PDFTranslationContext::tr("FOR PUBLIC RELEASE"); - break; - - case Stamp::NotApproved: - text = PDFTranslationContext::tr("NOT APPROVED"); - break; - - case Stamp::NotForPublicRelease: - text = PDFTranslationContext::tr("NOT FOR PUBLIC RELEASE"); - break; - - case Stamp::Sold: - text = PDFTranslationContext::tr("SOLD"); - break; - - case Stamp::TopSecret: - text = PDFTranslationContext::tr("TOP SECRET"); - break; - - default: - Q_ASSERT(false); - break; - } - - return text; -} - -void PDFStampAnnotation::setStamp(const Stamp& stamp) -{ - m_stamp = stamp; -} - -void PDFStampAnnotation::setIntent(const StampIntent& intent) -{ - m_intent = intent; -} - -void PDFAnnotation::drawCharacterSymbol(QString text, PDFReal opacity, AnnotationDrawParameters& parameters) const -{ - QColor strokeColor = QColor::fromRgbF(0.0, 0.0, 0.0, opacity); - - constexpr const PDFReal rectSize = 24.0; - QPainter& painter = *parameters.painter; - QRectF rectangle = getRectangle(); - rectangle.setSize(QSizeF(rectSize, rectSize)); - - QFont font = QApplication::font(); - font.setPixelSize(16.0); - - QPainterPath textPath; - textPath.addText(0.0, 0.0, font, text); - textPath = QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0).map(textPath); - QRectF textBoundingRect = textPath.boundingRect(); - QPointF offset = rectangle.center() - textBoundingRect.center(); - textPath.translate(offset); - painter.fillPath(textPath, QBrush(strokeColor, Qt::SolidPattern)); - - parameters.boundingRectangle = rectangle; -} - -void PDFFileAttachmentAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QString text = "?"; - switch (getIcon()) - { - case FileAttachmentIcon::Graph: - text = QString::fromUtf16(u"\U0001F4C8"); - break; - case FileAttachmentIcon::Paperclip: - text = QString::fromUtf16(u"\U0001F4CE"); - break; - case FileAttachmentIcon::PushPin: - text = QString::fromUtf16(u"\U0001F4CC"); - break; - case FileAttachmentIcon::Tag: - text = QString::fromUtf16(u"\U0001F3F7"); - break; - - default: - Q_ASSERT(false); - break; - } - - parameters.painter->setCompositionMode(getCompositionMode()); - drawCharacterSymbol(text, getStrokeOpacity(), parameters); -} - -void PDFSoundAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - QString text = "?"; - switch (getIcon()) - { - case Icon::Speaker: - text = QString::fromUtf16(u"\U0001F508"); - break; - case Icon::Microphone: - text = QString::fromUtf16(u"\U0001F3A4"); - break; - - default: - Q_ASSERT(false); - break; - } - - parameters.painter->setCompositionMode(getCompositionMode()); - drawCharacterSymbol(text, getStrokeOpacity(), parameters); -} - -const PDFAnnotationManager::PageAnnotation* PDFAnnotationManager::PageAnnotations::getPopupAnnotation(const PageAnnotation& pageAnnotation) const -{ - const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); - if (markupAnnotation) - { - const PDFObjectReference popupAnnotation = markupAnnotation->getPopupAnnotation(); - auto it = std::find_if(annotations.cbegin(), annotations.cend(), [popupAnnotation](const PageAnnotation& pa) { return pa.annotation->getSelfReference() == popupAnnotation; }); - if (it != annotations.cend()) - { - return &*it; - } - } - - return nullptr; -} - -std::vector PDFAnnotationManager::PageAnnotations::getReplies(const PageAnnotation& pageAnnotation) const -{ - std::vector result; - - const PDFObjectReference reference = pageAnnotation.annotation->getSelfReference(); - for (size_t i = 0, count = annotations.size(); i < count; ++i) - { - const PageAnnotation& currentAnnotation = annotations[i]; - if (currentAnnotation.annotation->isReplyTo()) - { - const PDFMarkupAnnotation* markupAnnotation = currentAnnotation.annotation->asMarkupAnnotation(); - Q_ASSERT(markupAnnotation); - - if (markupAnnotation->getInReplyTo() == reference) - { - result.push_back(¤tAnnotation); - } - } - } - - auto comparator = [](const PageAnnotation* l, const PageAnnotation* r) - { - QDateTime leftDateTime = l->annotation->getLastModifiedDateTime(); - QDateTime rightDateTime = r->annotation->getLastModifiedDateTime(); - - if (const PDFMarkupAnnotation* markupL = l->annotation->asMarkupAnnotation()) - { - leftDateTime = markupL->getCreationDate(); - } - - if (const PDFMarkupAnnotation* markupR = r->annotation->asMarkupAnnotation()) - { - rightDateTime = markupR->getCreationDate(); - } - - return leftDateTime < rightDateTime; - }; - std::sort(result.begin(), result.end(), comparator); - - return result; -} - -void PDFWidgetAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - // Do not draw without form manager - if (!parameters.formManager) - { - return; - } - - // Do not draw without form field - const PDFFormField* formField = parameters.formManager->getFormFieldForWidget(getSelfReference()); - if (!formField) - { - return; - } - - PDFPainterStateGuard guard(parameters.painter); - parameters.painter->setCompositionMode(getCompositionMode()); - - const PDFFormFieldWidgetEditor* editor = parameters.formManager->getEditor(formField); - if (editor && editor->isEditorDrawEnabled()) - { - editor->draw(parameters, true); - } - else - { - switch (formField->getFieldType()) - { - case PDFFormField::FieldType::Text: - case PDFFormField::FieldType::Choice: - { - editor->draw(parameters, false); - break; - } - - case PDFFormField::FieldType::Button: - { - const PDFFormFieldButton* button = dynamic_cast(formField); - switch (button->getButtonType()) - { - case PDFFormFieldButton::ButtonType::PushButton: - { - QRectF rectangle = getRectangle(); - - if (!getContents().isEmpty()) - { - QByteArray defaultAppearance = parameters.formManager->getForm()->getDefaultAppearance().value_or(QByteArray()); - PDFAnnotationDefaultAppearance appearance = PDFAnnotationDefaultAppearance::parse(defaultAppearance); - - qreal fontSize = appearance.getFontSize(); - if (qFuzzyIsNull(fontSize)) - { - fontSize = rectangle.height() * 0.6; - } - - QFont font(appearance.getFontName()); - font.setHintingPreference(QFont::PreferNoHinting); - font.setPixelSize(qCeil(fontSize)); - font.setStyleStrategy(QFont::ForceOutline); - - QFontMetrics fontMetrics(font); - - QPainter* painter = parameters.painter; - painter->translate(rectangle.bottomLeft()); - painter->scale(1.0, -1.0); - painter->setFont(font); - - QStyleOptionButton option; - option.state = QStyle::State_Enabled; - option.rect = QRect(0, 0, qFloor(rectangle.width()), qFloor(rectangle.height())); - option.palette = QApplication::palette(); - - if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover) - { - option.state |= QStyle::State_MouseOver; - } - - if (parameters.key.first == PDFAppeareanceStreams::Appearance::Down) - { - option.state |= QStyle::State_Sunken; - } - - option.features = QStyleOptionButton::None; - option.text = getContents(); - option.fontMetrics = fontMetrics; - - QApplication::style()->drawControl(QStyle::CE_PushButton, &option, painter, nullptr); - } - else - { - // This is push button without text. Just mark the area as highlighted. - QPainter* painter = parameters.painter; - - if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover || - parameters.key.first == PDFAppeareanceStreams::Appearance::Down) - { - switch (m_highlightMode) - { - case HighlightMode::Invert: - { - // Invert all - painter->setCompositionMode(QPainter::CompositionMode_Difference); - painter->fillRect(rectangle, QBrush(Qt::white, Qt::SolidPattern)); - break; - } - - case HighlightMode::Outline: - { - // Invert the border - painter->setCompositionMode(QPainter::CompositionMode_Difference); - QPen pen = getPen(); - pen.setColor(Qt::white); - painter->setPen(pen); - painter->setBrush(Qt::NoBrush); - painter->drawRect(rectangle); - break; - } - - case HighlightMode::Push: - { - // Draw border - painter->setCompositionMode(getCompositionMode()); - painter->setPen(getPen()); - painter->setBrush(Qt::NoBrush); - painter->drawRect(rectangle); - break; - } - - default: - Q_ASSERT(false); - break; - } - } - } - break; - } - - case PDFFormFieldButton::ButtonType::RadioButton: - case PDFFormFieldButton::ButtonType::CheckBox: - { - QRectF rectangle = getRectangle(); - QPainter* painter = parameters.painter; - painter->translate(rectangle.bottomLeft()); - painter->scale(1.0, -1.0); - - QStyleOptionButton option; - option.state = QStyle::State_Enabled; - option.rect = QRect(0, 0, qFloor(rectangle.width()), qFloor(rectangle.height())); - option.palette = QApplication::palette(); - - if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover) - { - option.state |= QStyle::State_MouseOver; - } - - if (parameters.key.first == PDFAppeareanceStreams::Appearance::Down) - { - option.state |= QStyle::State_Sunken; - } - - if (parameters.key.second != "Off") - { - option.state |= QStyle::State_On; - } - else - { - option.state |= QStyle::State_Off; - } - - option.features = QStyleOptionButton::None; - option.text = QString(); - - QStyle::PrimitiveElement element = (button->getButtonType() == PDFFormFieldButton::ButtonType::CheckBox) ? QStyle::PE_IndicatorCheckBox : QStyle::PE_IndicatorRadioButton; - QApplication::style()->drawPrimitive(element, &option, painter, nullptr); - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - break; - } - - case PDFFormField::FieldType::Invalid: - case PDFFormField::FieldType::Signature: - break; - - default: - { - Q_ASSERT(false); - break; - } - } - } -} - -std::vector PDFWidgetAnnotation::getDrawKeys(const PDFFormManager* formManager) const -{ - if (!formManager) - { - return PDFAnnotation::getDrawKeys(formManager); - } - - std::vector result; - - // Try get the form field, if we find it, then determine from form field type - // the list of appearance states. - const PDFFormField* formField = formManager->getFormFieldForWidget(getSelfReference()); - if (!formField) - { - return PDFAnnotation::getDrawKeys(formManager); - } - - switch (formField->getFieldType()) - { - case PDFFormField::FieldType::Invalid: - break; - - case PDFFormField::FieldType::Button: - { - const PDFFormFieldButton* button = dynamic_cast(formField); - switch (button->getButtonType()) - { - case PDFFormFieldButton::ButtonType::PushButton: - { - result = { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() }, - PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Rollover, QByteArray() }, - PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; - break; - } - - case PDFFormFieldButton::ButtonType::RadioButton: - case PDFFormFieldButton::ButtonType::CheckBox: - { - result = getAppearanceStreams().getAppearanceKeys(); - PDFAppeareanceStreams::Key offKey{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() }; - if (std::find(result.cbegin(), result.cend(), offKey) == result.cend()) - { - result.push_back(qMove(offKey)); - } - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - break; - } - - case PDFFormField::FieldType::Text: - // Text has only default appearance - break; - - case PDFFormField::FieldType::Choice: - // Choices have always default appearance - break; - - case PDFFormField::FieldType::Signature: - // Signatures have always default appearance - break; - } - - if (result.empty()) - { - result = PDFAnnotation::getDrawKeys(formManager); - } - - return result; -} - -void PDFRedactAnnotation::draw(AnnotationDrawParameters& parameters) const -{ - if (m_redactionRegion.isEmpty()) - { - // Jakub Melka: do not draw empty redact area - return; - } - - QPainter& painter = *parameters.painter; - painter.setCompositionMode(getCompositionMode()); - parameters.boundingRectangle = m_redactionRegion.getPath().boundingRect(); - - painter.setPen(getPen()); - painter.setBrush(getBrush()); - painter.drawPath(m_redactionRegion.getPath()); - - const qreal penWidth = painter.pen().widthF(); - parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); -} - -QColor PDFRedactAnnotation::getFillColor() const -{ - return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); -} - -} // namespace pdf +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfannotation.h" +#include "pdfdocument.h" +#include "pdfencoding.h" +#include "pdfpainter.h" +#include "pdfdrawspacecontroller.h" +#include "pdfcms.h" +#include "pdfwidgetutils.h" +#include "pdfpagecontentprocessor.h" +#include "pdfparser.h" +#include "pdfdrawwidget.h" +#include "pdfform.h" +#include "pdfpainterutils.h" +#include "pdfdocumentbuilder.h" +#include "pdfobjecteditorwidget.h" +#include "pdfselectpagesdialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFAnnotationBorder PDFAnnotationBorder::parseBorder(const PDFObjectStorage* storage, PDFObject object) +{ + PDFAnnotationBorder result; + object = storage->getObject(object); + + if (object.isArray()) + { + const PDFArray* array = object.getArray(); + if (array->getCount() >= 3) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_definition = Definition::Simple; + result.m_hCornerRadius = loader.readNumber(array->getItem(0), 0.0); + result.m_vCornerRadius = loader.readNumber(array->getItem(1), 0.0); + result.m_width = loader.readNumber(array->getItem(2), 1.0); + + if (array->getCount() >= 4) + { + result.m_dashPattern = loader.readNumberArray(array->getItem(3)); + } + } + } + + return result; +} + +PDFAnnotationBorder PDFAnnotationBorder::parseBS(const PDFObjectStorage* storage, PDFObject object) +{ + PDFAnnotationBorder result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_definition = Definition::BorderStyle; + result.m_width = loader.readNumberFromDictionary(dictionary, "W", 1.0); + + constexpr const std::array, 6> styles = { + std::pair{ "S", Style::Solid }, + std::pair{ "D", Style::Dashed }, + std::pair{ "B", Style::Beveled }, + std::pair{ "I", Style::Inset }, + std::pair{ "U", Style::Underline } + }; + + result.m_style = loader.readEnumByName(dictionary->get("S"), styles.begin(), styles.end(), Style::Solid); + } + + return result; +} + +PDFAnnotationBorderEffect PDFAnnotationBorderEffect::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFAnnotationBorderEffect result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_intensity = loader.readNumberFromDictionary(dictionary, "I", 0.0); + + constexpr const std::array, 2> effects = { + std::pair{ "S", Effect::None }, + std::pair{ "C", Effect::Cloudy } + }; + + result.m_effect = loader.readEnumByName(dictionary->get("S"), effects.begin(), effects.end(), Effect::None); + } + + return result; +} + +PDFAppeareanceStreams PDFAppeareanceStreams::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFAppeareanceStreams result; + + auto processSubdictionary = [&result, storage](Appearance appearance, PDFObject subdictionaryObject) + { + subdictionaryObject = storage->getObject(subdictionaryObject); + if (subdictionaryObject.isDictionary()) + { + const PDFDictionary* subdictionary = storage->getDictionaryFromObject(subdictionaryObject); + for (size_t i = 0; i < subdictionary->getCount(); ++i) + { + result.m_appearanceStreams[std::make_pair(appearance, subdictionary->getKey(i).getString())] = subdictionary->getValue(i); + } + } + else if (!subdictionaryObject.isNull()) + { + result.m_appearanceStreams[std::make_pair(appearance, QByteArray())] = subdictionaryObject; + } + }; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + processSubdictionary(Appearance::Normal, dictionary->get("N")); + processSubdictionary(Appearance::Rollover, dictionary->get("R")); + processSubdictionary(Appearance::Down, dictionary->get("D")); + } + + return result; +} + +PDFObject PDFAppeareanceStreams::getAppearance(Appearance appearance, const QByteArray& state) const +{ + Key key(appearance, state); + + auto it = m_appearanceStreams.find(key); + if (it == m_appearanceStreams.cend() && appearance != Appearance::Normal) + { + key.first = Appearance::Normal; + it = m_appearanceStreams.find(key); + } + if (it == m_appearanceStreams.cend() && !state.isEmpty()) + { + key.second = QByteArray(); + it = m_appearanceStreams.find(key); + } + + if (it != m_appearanceStreams.cend()) + { + return it->second; + } + + return PDFObject(); +} + +QByteArrayList PDFAppeareanceStreams::getAppearanceStates(Appearance appearance) const +{ + QByteArrayList result; + + for (const auto& item : m_appearanceStreams) + { + if (item.first.first != appearance) + { + continue; + } + + result << item.first.second; + } + + return result; +} + +std::vector PDFAppeareanceStreams::getAppearanceKeys() const +{ + std::vector result; + std::transform(m_appearanceStreams.cbegin(), m_appearanceStreams.cend(), std::back_inserter(result), [](const auto& item) { return item.first; }); + return result; +} + +PDFAnnotation::PDFAnnotation() : + m_flags(), + m_structParent(0) +{ + +} + +void PDFAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + Q_UNUSED(parameters); +} + +std::vector PDFAnnotation::getDrawKeys(const PDFFormManager* formManager) const +{ + Q_UNUSED(formManager); + + return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() } }; +} + +QPainter::CompositionMode PDFAnnotation::getCompositionMode() const +{ + if (PDFBlendModeInfo::isSupportedByQt(getBlendMode())) + { + return PDFBlendModeInfo::getCompositionModeFromBlendMode(getBlendMode()); + } + + return PDFBlendModeInfo::getCompositionModeFromBlendMode(BlendMode::Normal); +} + +QPainterPath PDFAnnotation::parsePath(const PDFObjectStorage* storage, const PDFDictionary* dictionary, bool closePath) +{ + QPainterPath path; + + PDFDocumentDataLoaderDecorator loader(storage); + PDFObject pathObject = storage->getObject(dictionary->get("Path")); + if (pathObject.isArray()) + { + for (const PDFObject& pathItemObject : *pathObject.getArray()) + { + std::vector pathItem = loader.readNumberArray(pathItemObject); + switch (pathItem.size()) + { + case 2: + { + QPointF point(pathItem[0], pathItem[1]); + if (path.isEmpty()) + { + path.moveTo(point); + } + else + { + path.lineTo(point); + } + break; + } + + case 4: + { + if (path.isEmpty()) + { + // First path item must be 'Move to' command + continue; + } + + path.quadTo(pathItem[0], pathItem[1], pathItem[2], pathItem[3]); + break; + } + + case 6: + { + if (path.isEmpty()) + { + // First path item must be 'Move to' command + continue; + } + + path.cubicTo(pathItem[0], pathItem[1], pathItem[2], pathItem[3], pathItem[4], pathItem[5]); + break; + } + + default: + break; + } + } + } + + if (closePath) + { + path.closeSubpath(); + } + + return path; +} + +void PDFAnnotation::setLanguage(const QString& language) +{ + m_language = language; +} + +void PDFAnnotation::setBlendMode(const BlendMode& blendMode) +{ + m_blendMode = blendMode; +} + +void PDFAnnotation::setStrokingOpacity(const PDFReal& strokingOpacity) +{ + m_strokingOpacity = strokingOpacity; +} + +void PDFAnnotation::setFillingOpacity(const PDFReal& fillingOpacity) +{ + m_fillingOpacity = fillingOpacity; +} + +void PDFAnnotation::setAssociatedFiles(const PDFObject& associatedFiles) +{ + m_associatedFiles = associatedFiles; +} + +void PDFAnnotation::setOptionalContentReference(const PDFObjectReference& optionalContentReference) +{ + m_optionalContentReference = optionalContentReference; +} + +void PDFAnnotation::setStructParent(const PDFInteger& structParent) +{ + m_structParent = structParent; +} + +void PDFAnnotation::setColor(const std::vector& color) +{ + m_color = color; +} + +void PDFAnnotation::setAnnotationBorder(const PDFAnnotationBorder& annotationBorder) +{ + m_annotationBorder = annotationBorder; +} + +void PDFAnnotation::setAppearanceState(const QByteArray& appearanceState) +{ + m_appearanceState = appearanceState; +} + +void PDFAnnotation::setAppearanceStreams(const PDFAppeareanceStreams& appearanceStreams) +{ + m_appearanceStreams = appearanceStreams; +} + +void PDFAnnotation::setFlags(const Flags& flags) +{ + m_flags = flags; +} + +void PDFAnnotation::setLastModifiedString(const QString& lastModifiedString) +{ + m_lastModifiedString = lastModifiedString; +} + +void PDFAnnotation::setLastModified(const QDateTime& lastModified) +{ + m_lastModified = lastModified; +} + +void PDFAnnotation::setName(const QString& name) +{ + m_name = name; +} + +void PDFAnnotation::setPageReference(const PDFObjectReference& pageReference) +{ + m_pageReference = pageReference; +} + +void PDFAnnotation::setContents(const QString& contents) +{ + m_contents = contents; +} + +void PDFAnnotation::setRectangle(const QRectF& rectangle) +{ + m_rectangle = rectangle; +} + +void PDFAnnotation::setSelfReference(const PDFObjectReference& selfReference) +{ + m_selfReference = selfReference; +} + +PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference) +{ + PDFObject object = storage->getObjectByReference(reference); + PDFAnnotationPtr result; + + const PDFDictionary* dictionary = storage->getDictionaryFromObject(object); + if (!dictionary) + { + return result; + } + + PDFDocumentDataLoaderDecorator loader(storage); + QRectF annotationsRectangle = loader.readRectangle(dictionary->get("Rect"), QRectF()); + + // Determine type of annotation + QByteArray subtype = loader.readNameFromDictionary(dictionary, "Subtype"); + if (subtype == "Text") + { + PDFTextAnnotation* textAnnotation = new PDFTextAnnotation; + result.reset(textAnnotation); + + textAnnotation->m_open = loader.readBooleanFromDictionary(dictionary, "Open", false); + textAnnotation->m_iconName = loader.readNameFromDictionary(dictionary, "Name"); + textAnnotation->m_state = loader.readTextStringFromDictionary(dictionary, "State", "Unmarked"); + textAnnotation->m_stateModel = loader.readTextStringFromDictionary(dictionary, "StateModel", "Marked"); + } + else if (subtype == "Link") + { + PDFLinkAnnotation* linkAnnotation = new PDFLinkAnnotation; + result.reset(linkAnnotation); + + linkAnnotation->m_action = PDFAction::parse(storage, dictionary->get("A")); + if (!linkAnnotation->m_action) + { + PDFDestination destination = PDFDestination::parse(storage, dictionary->get("Dest")); + linkAnnotation->m_action.reset(new PDFActionGoTo(destination, PDFDestination())); + } + linkAnnotation->m_previousAction = PDFAction::parse(storage, dictionary->get("PA")); + + constexpr const std::array, 4> highlightMode = { + std::pair{ "N", LinkHighlightMode::None }, + std::pair{ "I", LinkHighlightMode::Invert }, + std::pair{ "O", LinkHighlightMode::Outline }, + std::pair{ "P", LinkHighlightMode::Push } + }; + + linkAnnotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightMode.begin(), highlightMode.end(), LinkHighlightMode::Invert); + linkAnnotation->m_activationRegion = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle); + } + else if (subtype == "FreeText") + { + PDFFreeTextAnnotation* freeTextAnnotation = new PDFFreeTextAnnotation; + result.reset(freeTextAnnotation); + + constexpr const std::array, 2> intents = { + std::pair{ "FreeTextCallout", PDFFreeTextAnnotation::Intent::Callout }, + std::pair{ "FreeTextTypeWriter", PDFFreeTextAnnotation::Intent::TypeWriter } + }; + + freeTextAnnotation->m_defaultAppearance = loader.readStringFromDictionary(dictionary, "DA"); + freeTextAnnotation->m_justification = static_cast(loader.readIntegerFromDictionary(dictionary, "Q", 0)); + freeTextAnnotation->m_defaultStyleString = loader.readTextStringFromDictionary(dictionary, "DS", QString()); + freeTextAnnotation->m_calloutLine = PDFAnnotationCalloutLine::parse(storage, dictionary->get("CL")); + freeTextAnnotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFFreeTextAnnotation::Intent::None); + freeTextAnnotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE")); + + std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); + if (differenceRectangle.size() == 4) + { + freeTextAnnotation->m_textRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); + if (!freeTextAnnotation->m_textRectangle.isValid()) + { + freeTextAnnotation->m_textRectangle = QRectF(); + } + } + + std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); + if (lineEndings.size() == 2) + { + freeTextAnnotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); + freeTextAnnotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); + } + } + else if (subtype == "Line") + { + PDFLineAnnotation* lineAnnotation = new PDFLineAnnotation; + result.reset(lineAnnotation); + + std::vector line = loader.readNumberArrayFromDictionary(dictionary, "L"); + if (line.size() == 4) + { + lineAnnotation->m_line = QLineF(line[0], line[1], line[2], line[3]); + } + + std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); + if (lineEndings.size() == 2) + { + lineAnnotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); + lineAnnotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); + } + + lineAnnotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); + lineAnnotation->m_leaderLineLength = loader.readNumberFromDictionary(dictionary, "LL", 0.0); + lineAnnotation->m_leaderLineExtension = loader.readNumberFromDictionary(dictionary, "LLE", 0.0); + lineAnnotation->m_leaderLineOffset = loader.readNumberFromDictionary(dictionary, "LLO", 0.0); + lineAnnotation->m_captionRendered = loader.readBooleanFromDictionary(dictionary, "Cap", false); + lineAnnotation->m_intent = (loader.readNameFromDictionary(dictionary, "IT") == "LineDimension") ? PDFLineAnnotation::Intent::Dimension : PDFLineAnnotation::Intent::Arrow; + lineAnnotation->m_captionPosition = (loader.readNameFromDictionary(dictionary, "CP") == "Top") ? PDFLineAnnotation::CaptionPosition::Top : PDFLineAnnotation::CaptionPosition::Inline; + lineAnnotation->m_measureDictionary = storage->getObject(dictionary->get("Measure")); + + std::vector captionOffset = loader.readNumberArrayFromDictionary(dictionary, "CO"); + if (captionOffset.size() == 2) + { + lineAnnotation->m_captionOffset = QPointF(captionOffset[0], captionOffset[1]); + } + } + else if (subtype == "Square" || subtype == "Circle") + { + PDFSimpleGeometryAnnotation* annotation = new PDFSimpleGeometryAnnotation((subtype == "Square") ? AnnotationType::Square : AnnotationType::Circle); + result.reset(annotation); + + annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); + annotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE")); + + std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); + if (differenceRectangle.size() == 4) + { + annotation->m_geometryRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); + if (!annotation->m_geometryRectangle.isValid()) + { + annotation->m_geometryRectangle = QRectF(); + } + } + } + else if (subtype == "Polygon" || subtype == "PolyLine") + { + PDFPolygonalGeometryAnnotation* annotation = new PDFPolygonalGeometryAnnotation((subtype == "Polygon") ? AnnotationType::Polygon : AnnotationType::Polyline); + result.reset(annotation); + + std::vector vertices = loader.readNumberArrayFromDictionary(dictionary, "Vertices"); + const size_t count = vertices.size() / 2; + annotation->m_vertices.reserve(count); + for (size_t i = 0; i < count; ++i) + { + annotation->m_vertices.emplace_back(vertices[2 * i], vertices[2 * i + 1]); + } + + std::vector lineEndings = loader.readNameArrayFromDictionary(dictionary, "LE"); + if (lineEndings.size() == 2) + { + annotation->m_startLineEnding = convertNameToLineEnding(lineEndings[0]); + annotation->m_endLineEnding = convertNameToLineEnding(lineEndings[1]); + } + + annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); + annotation->m_effect = PDFAnnotationBorderEffect::parse(storage, dictionary->get("BE")); + + constexpr const std::array, 3> intents = { + std::pair{ "PolygonCloud", PDFPolygonalGeometryAnnotation::Intent::Cloud }, + std::pair{ "PolyLineDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension }, + std::pair{ "PolygonDimension", PDFPolygonalGeometryAnnotation::Intent::Dimension } + }; + + annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), intents.begin(), intents.end(), PDFPolygonalGeometryAnnotation::Intent::None); + annotation->m_measure = storage->getObject(dictionary->get("Measure")); + annotation->m_path = parsePath(storage, dictionary, subtype == "Polygon"); + } + else if (subtype == "Highlight" || + subtype == "Underline" || + subtype == "Squiggly" || + subtype == "StrikeOut") + { + AnnotationType type = AnnotationType::Highlight; + if (subtype == "Underline") + { + type = AnnotationType::Underline; + } + else if (subtype == "Squiggly") + { + type = AnnotationType::Squiggly; + } + else if (subtype == "StrikeOut") + { + type = AnnotationType::StrikeOut; + } + + PDFHighlightAnnotation* annotation = new PDFHighlightAnnotation(type); + result.reset(annotation); + + annotation->m_highlightArea = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle); + } + else if (subtype == "Caret") + { + PDFCaretAnnotation* annotation = new PDFCaretAnnotation(); + result.reset(annotation); + + std::vector differenceRectangle = loader.readNumberArrayFromDictionary(dictionary, "RD"); + if (differenceRectangle.size() == 4) + { + annotation->m_caretRectangle = annotationsRectangle.adjusted(differenceRectangle[0], differenceRectangle[1], -differenceRectangle[2], -differenceRectangle[3]); + if (!annotation->m_caretRectangle.isValid()) + { + annotation->m_caretRectangle = QRectF(); + } + } + + annotation->m_symbol = (loader.readNameFromDictionary(dictionary, "Sy") == "P") ? PDFCaretAnnotation::Symbol::Paragraph : PDFCaretAnnotation::Symbol::None; + } + else if (subtype == "Stamp") + { + PDFStampAnnotation* annotation = new PDFStampAnnotation(); + result.reset(annotation); + + constexpr const std::array stamps = { + std::pair{ "Approved", Stamp::Approved }, + std::pair{ "AsIs", Stamp::AsIs }, + std::pair{ "Confidential", Stamp::Confidential }, + std::pair{ "Departmental", Stamp::Departmental }, + std::pair{ "Draft", Stamp::Draft }, + std::pair{ "Experimental", Stamp::Experimental }, + std::pair{ "Expired", Stamp::Expired }, + std::pair{ "Final", Stamp::Final }, + std::pair{ "ForComment", Stamp::ForComment }, + std::pair{ "ForPublicRelease", Stamp::ForPublicRelease }, + std::pair{ "NotApproved", Stamp::NotApproved }, + std::pair{ "NotForPublicRelease", Stamp::NotForPublicRelease }, + std::pair{ "Sold", Stamp::Sold }, + std::pair{ "TopSecret", Stamp::TopSecret } + }; + + annotation->m_stamp = loader.readEnumByName(dictionary->get("Name"), stamps.begin(), stamps.end(), Stamp::Draft); + + constexpr const std::array stampsIntents = { + std::pair{ "Stamp", StampIntent::Stamp }, + std::pair{ "StampImage", StampIntent::StampImage }, + std::pair{ "StampSnapshot", StampIntent::StampSnapshot }, + }; + + annotation->m_intent = loader.readEnumByName(dictionary->get("IT"), stampsIntents.begin(), stampsIntents.end(), StampIntent::Stamp); + } + else if (subtype == "Ink") + { + PDFInkAnnotation* annotation = new PDFInkAnnotation(); + result.reset(annotation); + + annotation->m_inkPath = parsePath(storage, dictionary, false); + if (annotation->m_inkPath.isEmpty()) + { + PDFObject inkList = storage->getObject(dictionary->get("InkList")); + if (inkList.isArray()) + { + const PDFArray* inkListArray = inkList.getArray(); + for (size_t i = 0, count = inkListArray->getCount(); i < count; ++i) + { + std::vector points = loader.readNumberArray(inkListArray->getItem(i)); + const size_t pointCount = points.size() / 2; + + for (size_t j = 0; j < pointCount; ++j) + { + QPointF point(points[j * 2], points[j * 2 + 1]); + + if (j == 0) + { + annotation->m_inkPath.moveTo(point); + } + else + { + annotation->m_inkPath.lineTo(point); + } + } + } + } + } + } + else if (subtype == "Popup") + { + PDFPopupAnnotation* annotation = new PDFPopupAnnotation(); + result.reset(annotation); + + annotation->m_opened = loader.readBooleanFromDictionary(dictionary, "Open", false); + } + else if (subtype == "FileAttachment") + { + PDFFileAttachmentAnnotation* annotation = new PDFFileAttachmentAnnotation(); + result.reset(annotation); + + annotation->m_fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("FS")); + + constexpr const std::array, 4> icons = { + std::pair{ "Graph", FileAttachmentIcon::Graph }, + std::pair{ "Paperclip", FileAttachmentIcon::Paperclip }, + std::pair{ "PushPin", FileAttachmentIcon::PushPin }, + std::pair{ "Tag", FileAttachmentIcon::Tag } + }; + + annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), FileAttachmentIcon::PushPin); + } + else if (subtype == "Redact") + { + PDFRedactAnnotation* annotation = new PDFRedactAnnotation(); + result.reset(annotation); + + annotation->m_redactionRegion = parseQuadrilaterals(storage, dictionary->get("QuadPoints"), annotationsRectangle); + annotation->m_interiorColor = loader.readNumberArrayFromDictionary(dictionary, "IC"); + annotation->m_overlayForm = dictionary->get("RO"); + annotation->m_overlayText = loader.readTextStringFromDictionary(dictionary, "OverlayText", QString()); + annotation->m_repeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false); + annotation->m_defaultAppearance = loader.readStringFromDictionary(dictionary, "DA"); + annotation->m_justification = loader.readIntegerFromDictionary(dictionary, "Q", 0); + } + else if (subtype == "Sound") + { + PDFSoundAnnotation* annotation = new PDFSoundAnnotation(); + result.reset(annotation); + + annotation->m_sound = PDFSound::parse(storage, dictionary->get("Sound")); + + constexpr const std::array, 2> icons = { + std::pair{ "Speaker", PDFSoundAnnotation::Icon::Speaker }, + std::pair{ "Mic", PDFSoundAnnotation::Icon::Microphone } + }; + + annotation->m_icon = loader.readEnumByName(dictionary->get("Name"), icons.begin(), icons.end(), PDFSoundAnnotation::Icon::Speaker); + } + else if (subtype == "Movie") + { + PDFMovieAnnotation* annotation = new PDFMovieAnnotation(); + result.reset(annotation); + + annotation->m_movieTitle = loader.readStringFromDictionary(dictionary, "T"); + annotation->m_movie = PDFMovie::parse(storage, dictionary->get("Movie")); + + PDFObject activation = storage->getObject(dictionary->get("A")); + if (activation.isBool()) + { + annotation->m_playMovie = activation.getBool(); + } + else if (activation.isDictionary()) + { + annotation->m_playMovie = true; + annotation->m_movieActivation = PDFMovieActivation::parse(storage, activation); + } + } + else if (subtype == "Screen") + { + PDFScreenAnnotation* annotation = new PDFScreenAnnotation(); + result.reset(annotation); + + annotation->m_screenTitle = loader.readTextStringFromDictionary(dictionary, "T", QString()); + annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(storage, dictionary->get("MK")); + annotation->m_action = PDFAction::parse(storage, dictionary->get("A")); + annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, dictionary->get("AA"), dictionary->get("A")); + } + else if (subtype == "Widget") + { + PDFWidgetAnnotation* annotation = new PDFWidgetAnnotation(); + result.reset(annotation); + + constexpr const std::array, 5> highlightModes = { + std::pair{ "N", PDFWidgetAnnotation::HighlightMode::None }, + std::pair{ "I", PDFWidgetAnnotation::HighlightMode::Invert }, + std::pair{ "O", PDFWidgetAnnotation::HighlightMode::Outline }, + std::pair{ "P", PDFWidgetAnnotation::HighlightMode::Push }, + std::pair{ "T", PDFWidgetAnnotation::HighlightMode::Toggle } + }; + + annotation->m_highlightMode = loader.readEnumByName(dictionary->get("H"), highlightModes.begin(), highlightModes.end(), PDFWidgetAnnotation::HighlightMode::Invert); + annotation->m_appearanceCharacteristics = PDFAnnotationAppearanceCharacteristics::parse(storage, dictionary->get("MK")); + annotation->m_action = PDFAction::parse(storage, dictionary->get("A")); + annotation->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, dictionary->get("AA"), dictionary->get("A")); + } + else if (subtype == "PrinterMark") + { + PDFPrinterMarkAnnotation* annotation = new PDFPrinterMarkAnnotation(); + result.reset(annotation); + } + else if (subtype == "TrapNet") + { + PDFTrapNetworkAnnotation* annotation = new PDFTrapNetworkAnnotation(); + result.reset(annotation); + } + else if (subtype == "Watermark") + { + PDFWatermarkAnnotation* annotation = new PDFWatermarkAnnotation(); + result.reset(annotation); + + if (const PDFDictionary* fixedPrintDictionary = storage->getDictionaryFromObject(dictionary->get("FixedPrint"))) + { + annotation->m_matrix = loader.readMatrixFromDictionary(fixedPrintDictionary, "Matrix", QMatrix()); + annotation->m_relativeHorizontalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "H", 0.0); + annotation->m_relativeVerticalOffset = loader.readNumberFromDictionary(fixedPrintDictionary, "V", 0.0); + } + } + else if (subtype == "Projection") + { + PDFProjectionAnnotation* annotation = new PDFProjectionAnnotation(); + result.reset(annotation); + } + else if (subtype == "3D") + { + PDF3DAnnotation* annotation = new PDF3DAnnotation(); + result.reset(annotation); + + annotation->m_stream = PDF3DStream::parse(storage, dictionary->get("3DD")); + + const std::vector& views = annotation->getStream().getViews(); + PDFObject defaultViewObject = storage->getObject(dictionary->get("DV")); + if (defaultViewObject.isDictionary()) + { + annotation->m_defaultView = PDF3DView::parse(storage, defaultViewObject); + } + else if (defaultViewObject.isInt()) + { + PDFInteger index = defaultViewObject.getInteger(); + if (index >= 0 && index < PDFInteger(views.size())) + { + annotation->m_defaultView = views[index]; + } + } + else if (defaultViewObject.isName() && !views.empty()) + { + QByteArray name = defaultViewObject.getString(); + if (name == "F") + { + annotation->m_defaultView = views.front(); + } + else if (name == "L") + { + annotation->m_defaultView = views.back(); + } + } + + annotation->m_activation = PDF3DActivation::parse(storage, dictionary->get("3DA")); + annotation->m_interactive = loader.readBooleanFromDictionary(dictionary, "3DI", true); + annotation->m_viewBox = loader.readRectangle(dictionary->get("3DB"), QRectF()); + } + else if (subtype == "RichMedia") + { + PDFRichMediaAnnotation* annotation = new PDFRichMediaAnnotation(); + result.reset(annotation); + + annotation->m_content = PDFRichMediaContent::parse(storage, dictionary->get("RichMediaContent")); + annotation->m_settings = PDFRichMediaSettings::parse(storage, dictionary->get("RichMediaSettings")); + } + + if (!result) + { + // Invalid annotation type + return result; + } + + // Load common data for annotation + result->m_selfReference = reference; + result->m_rectangle = annotationsRectangle; + result->m_contents = loader.readTextStringFromDictionary(dictionary, "Contents", QString()); + result->m_pageReference = loader.readReferenceFromDictionary(dictionary, "P"); + result->m_name = loader.readTextStringFromDictionary(dictionary, "NM", QString()); + + QByteArray string = loader.readStringFromDictionary(dictionary, "M"); + result->m_lastModified = PDFEncoding::convertToDateTime(string); + if (!result->m_lastModified.isValid()) + { + result->m_lastModifiedString = loader.readTextStringFromDictionary(dictionary, "M", QString()); + } + + result->m_flags = Flags(loader.readIntegerFromDictionary(dictionary, "F", 0)); + result->m_appearanceStreams = PDFAppeareanceStreams::parse(storage, dictionary->get("AP")); + result->m_appearanceState = loader.readNameFromDictionary(dictionary, "AS"); + + result->m_annotationBorder = PDFAnnotationBorder::parseBS(storage, dictionary->get("BS")); + if (!result->m_annotationBorder.isValid()) + { + result->m_annotationBorder = PDFAnnotationBorder::parseBorder(storage, dictionary->get("Border")); + } + result->m_color = loader.readNumberArrayFromDictionary(dictionary, "C"); + result->m_structParent = loader.readIntegerFromDictionary(dictionary, "StructParent", 0); + result->m_optionalContentReference = loader.readReferenceFromDictionary(dictionary, "OC"); + result->m_strokingOpacity = loader.readNumberFromDictionary(dictionary, "CA", 1.0); + result->m_fillingOpacity = loader.readNumberFromDictionary(dictionary, "ca", result->m_strokingOpacity); + result->m_blendMode = PDFBlendModeInfo::getBlendMode(loader.readNameFromDictionary(dictionary, "BM")); + + if (result->m_blendMode == BlendMode::Invalid) + { + result->m_blendMode = BlendMode::Normal; + } + + result->m_language = loader.readTextStringFromDictionary(dictionary, "Lang", QString()); + + if (PDFMarkupAnnotation* markupAnnotation = result->asMarkupAnnotation()) + { + markupAnnotation->m_windowTitle = loader.readTextStringFromDictionary(dictionary, "T", QString()); + markupAnnotation->m_popupAnnotation = loader.readReferenceFromDictionary(dictionary, "Popup"); + markupAnnotation->m_richTextString = loader.readTextStringFromDictionary(dictionary, "RC", QString()); + markupAnnotation->m_creationDate = PDFEncoding::convertToDateTime(loader.readStringFromDictionary(dictionary, "CreationDate")); + markupAnnotation->m_inReplyTo = loader.readReferenceFromDictionary(dictionary, "IRT"); + markupAnnotation->m_subject = loader.readTextStringFromDictionary(dictionary, "Subj", QString()); + markupAnnotation->m_replyType = (loader.readNameFromDictionary(dictionary, "RT") == "Group") ? PDFMarkupAnnotation::ReplyType::Group : PDFMarkupAnnotation::ReplyType::Reply; + markupAnnotation->m_intent = loader.readNameFromDictionary(dictionary, "IT"); + markupAnnotation->m_externalData = storage->getObject(dictionary->get("ExData")); + } + + return result; +} + +PDFAnnotationQuadrilaterals PDFAnnotation::parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect) +{ + QPainterPath path; + PDFAnnotationQuadrilaterals::Quadrilaterals quadrilaterals; + + PDFDocumentDataLoaderDecorator loader(storage); + std::vector points = loader.readNumberArray(quadrilateralsObject); + const size_t quadrilateralCount = points.size() / 8; + path.reserve(int(quadrilateralCount) + 5); + quadrilaterals.reserve(quadrilateralCount); + for (size_t i = 0; i < quadrilateralCount; ++i) + { + const size_t offset = i * 8; + QPointF p1(points[offset + 0], points[offset + 1]); + QPointF p2(points[offset + 2], points[offset + 3]); + QPointF p3(points[offset + 4], points[offset + 5]); + QPointF p4(points[offset + 6], points[offset + 7]); + + path.moveTo(p1); + path.lineTo(p2); + path.lineTo(p4); + path.lineTo(p3); + path.closeSubpath(); + + quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ p1, p2, p3, p4 }); + } + + if (path.isEmpty() && annotationRect.isValid()) + { + // Jakub Melka: we are using points at the top, because PDF has inverted y axis + // against the Qt's y axis. + path.addRect(annotationRect); + quadrilaterals.emplace_back(PDFAnnotationQuadrilaterals::Quadrilateral{ annotationRect.topLeft(), annotationRect.topRight(), annotationRect.bottomLeft(), annotationRect.bottomRight() }); + } + + return PDFAnnotationQuadrilaterals(qMove(path), qMove(quadrilaterals)); +} + +constexpr const std::array, 10> lineEndings = { + std::pair{ AnnotationLineEnding::None, "None" }, + std::pair{ AnnotationLineEnding::Square, "Square" }, + std::pair{ AnnotationLineEnding::Circle, "Circle" }, + std::pair{ AnnotationLineEnding::Diamond, "Diamond" }, + std::pair{ AnnotationLineEnding::OpenArrow, "OpenArrow" }, + std::pair{ AnnotationLineEnding::ClosedArrow, "ClosedArrow" }, + std::pair{ AnnotationLineEnding::Butt, "Butt" }, + std::pair{ AnnotationLineEnding::ROpenArrow, "ROpenArrow" }, + std::pair{ AnnotationLineEnding::RClosedArrow, "RClosedArrow" }, + std::pair{ AnnotationLineEnding::Slash, "Slash" } +}; + +AnnotationLineEnding PDFAnnotation::convertNameToLineEnding(const QByteArray& name) +{ + auto it = std::find_if(lineEndings.cbegin(), lineEndings.cend(), [&name](const auto& item) { return name == item.second; }); + if (it != lineEndings.cend()) + { + return it->first; + } + + return AnnotationLineEnding::None; +} + +QByteArray PDFAnnotation::convertLineEndingToName(AnnotationLineEnding lineEnding) +{ + auto it = std::find_if(lineEndings.cbegin(), lineEndings.cend(), [lineEnding](const auto& item) { return lineEnding == item.first; }); + if (it != lineEndings.cend()) + { + return it->second; + } + + return lineEndings.front().second; +} + +QColor PDFAnnotation::getStrokeColor() const +{ + return getDrawColorFromAnnotationColor(getColor(), getStrokeOpacity()); +} + +QColor PDFAnnotation::getFillColor() const +{ + return QColor(); +} + +QColor PDFAnnotation::getDrawColorFromAnnotationColor(const std::vector& color, PDFReal opacity) +{ + switch (color.size()) + { + case 1: + { + const PDFReal gray = color.back(); + return QColor::fromRgbF(gray, gray, gray, opacity); + } + + case 3: + { + const PDFReal r = color[0]; + const PDFReal g = color[1]; + const PDFReal b = color[2]; + return QColor::fromRgbF(r, g, b, opacity); + } + + case 4: + { + const PDFReal c = color[0]; + const PDFReal m = color[1]; + const PDFReal y = color[2]; + const PDFReal k = color[3]; + return QColor::fromCmykF(c, m, y, k, opacity); + } + + default: + break; + } + + QColor black(Qt::black); + black.setAlphaF(opacity); + return black; +} + +bool PDFAnnotation::isTypeEditable(AnnotationType type) +{ + switch (type) + { + case AnnotationType::Text: + case AnnotationType::Link: + case AnnotationType::FreeText: + case AnnotationType::Line: + case AnnotationType::Square: + case AnnotationType::Circle: + case AnnotationType::Polygon: + case AnnotationType::Polyline: + case AnnotationType::Highlight: + case AnnotationType::Underline: + case AnnotationType::Squiggly: + case AnnotationType::StrikeOut: + case AnnotationType::Stamp: + case AnnotationType::Caret: + case AnnotationType::Ink: + case AnnotationType::FileAttachment: + case AnnotationType::PrinterMark: + case AnnotationType::Watermark: + case AnnotationType::Redact: + return true; + + default: + break; + } + + return false; +} + +QPen PDFAnnotation::getPen() const +{ + QColor strokeColor = getStrokeColor(); + const PDFAnnotationBorder& border = getBorder(); + + if (qFuzzyIsNull(border.getWidth())) + { + // No border is drawn + return Qt::NoPen; + } + + QPen pen(strokeColor); + pen.setWidthF(border.getWidth()); + + if (!border.getDashPattern().empty()) + { + PDFLineDashPattern lineDashPattern(border.getDashPattern(), 0.0); + pen.setStyle(Qt::CustomDashLine); + pen.setDashPattern(QVector(lineDashPattern.getDashArray().begin(), lineDashPattern.getDashArray().end())); + pen.setDashOffset(lineDashPattern.getDashOffset()); + } + + return pen; +} + +QBrush PDFAnnotation::getBrush() const +{ + QColor color = getFillColor(); + if (color.isValid()) + { + return QBrush(color, Qt::SolidPattern); + } + + return QBrush(Qt::NoBrush); +} + +PDFAnnotationCalloutLine PDFAnnotationCalloutLine::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFDocumentDataLoaderDecorator loader(storage); + std::vector points = loader.readNumberArray(object); + + switch (points.size()) + { + case 4: + return PDFAnnotationCalloutLine(QPointF(points[0], points[1]), QPointF(points[2], points[3])); + + case 6: + return PDFAnnotationCalloutLine(QPointF(points[0], points[1]), QPointF(points[2], points[3]), QPointF(points[4], points[5])); + + default: + break; + } + + return PDFAnnotationCalloutLine(); +} + +PDFAnnotationAppearanceCharacteristics PDFAnnotationAppearanceCharacteristics::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFAnnotationAppearanceCharacteristics result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + result.m_rotation = loader.readIntegerFromDictionary(dictionary, "R", 0); + result.m_borderColor = loader.readNumberArrayFromDictionary(dictionary, "BC"); + result.m_backgroundColor = loader.readNumberArrayFromDictionary(dictionary, "BG"); + result.m_normalCaption = loader.readTextStringFromDictionary(dictionary, "CA", QString()); + result.m_rolloverCaption = loader.readTextStringFromDictionary(dictionary, "RC", QString()); + result.m_downCaption = loader.readTextStringFromDictionary(dictionary, "AC", QString()); + result.m_normalIcon = storage->getObject(dictionary->get("I")); + result.m_rolloverIcon = storage->getObject(dictionary->get("RI")); + result.m_downIcon = storage->getObject(dictionary->get("IX")); + result.m_iconFit = PDFAnnotationIconFitInfo::parse(storage, dictionary->get("IF")); + result.m_pushButtonMode = static_cast(loader.readIntegerFromDictionary(dictionary, "TP", PDFInteger(PDFAnnotationAppearanceCharacteristics::PushButtonMode::NoIcon))); + } + return result; +} + +PDFAnnotationIconFitInfo PDFAnnotationIconFitInfo::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFAnnotationIconFitInfo info; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + constexpr const std::array, 4> scaleConditions = { + std::pair{ "A", PDFAnnotationIconFitInfo::ScaleCondition::Always }, + std::pair{ "B", PDFAnnotationIconFitInfo::ScaleCondition::ScaleBigger }, + std::pair{ "S", PDFAnnotationIconFitInfo::ScaleCondition::ScaleSmaller }, + std::pair{ "N", PDFAnnotationIconFitInfo::ScaleCondition::Never } + }; + + constexpr const std::array, 2> scaleTypes = { + std::pair{ "A", PDFAnnotationIconFitInfo::ScaleType::Anamorphic }, + std::pair{ "P", PDFAnnotationIconFitInfo::ScaleType::Proportional } + }; + + std::vector point = loader.readNumberArrayFromDictionary(dictionary, "A"); + if (point.size() != 2) + { + point.resize(2, 0.5); + } + + info.m_scaleCondition = loader.readEnumByName(dictionary->get("SW"), scaleConditions.begin(), scaleConditions.end(), PDFAnnotationIconFitInfo::ScaleCondition::Always); + info.m_scaleType = loader.readEnumByName(dictionary->get("S"), scaleTypes.begin(), scaleTypes.end(), PDFAnnotationIconFitInfo::ScaleType::Proportional); + info.m_relativeProportionalPosition = QPointF(point[0], point[1]); + info.m_fullBox = loader.readBooleanFromDictionary(dictionary, "FB", false); + } + + return info; +} + +PDFAnnotationAdditionalActions PDFAnnotationAdditionalActions::parse(const PDFObjectStorage* storage, PDFObject object, PDFObject defaultAction) +{ + PDFAnnotationAdditionalActions result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + result.m_actions[CursorEnter] = PDFAction::parse(storage, dictionary->get("E")); + result.m_actions[CursorLeave] = PDFAction::parse(storage, dictionary->get("X")); + result.m_actions[MousePressed] = PDFAction::parse(storage, dictionary->get("D")); + result.m_actions[MouseReleased] = PDFAction::parse(storage, dictionary->get("U")); + result.m_actions[FocusIn] = PDFAction::parse(storage, dictionary->get("Fo")); + result.m_actions[FocusOut] = PDFAction::parse(storage, dictionary->get("Bl")); + result.m_actions[PageOpened] = PDFAction::parse(storage, dictionary->get("PO")); + result.m_actions[PageClosed] = PDFAction::parse(storage, dictionary->get("PC")); + result.m_actions[PageShow] = PDFAction::parse(storage, dictionary->get("PV")); + result.m_actions[PageHide] = PDFAction::parse(storage, dictionary->get("PI")); + result.m_actions[FormFieldModified] = PDFAction::parse(storage, dictionary->get("K")); + result.m_actions[FormFieldFormatted] = PDFAction::parse(storage, dictionary->get("F")); + result.m_actions[FormFieldValidated] = PDFAction::parse(storage, dictionary->get("V")); + result.m_actions[FormFieldCalculated] = PDFAction::parse(storage, dictionary->get("C")); + } + + result.m_actions[Default] = PDFAction::parse(storage, defaultAction); + return result; +} + +PDFAnnotationManager::PDFAnnotationManager(PDFFontCache* fontCache, + const PDFCMSManager* cmsManager, + const PDFOptionalContentActivity* optionalActivity, + PDFMeshQualitySettings meshQualitySettings, + PDFRenderer::Features features, + Target target, + QObject* parent) : + BaseClass(parent), + m_document(nullptr), + m_fontCache(fontCache), + m_cmsManager(cmsManager), + m_optionalActivity(optionalActivity), + m_formManager(nullptr), + m_meshQualitySettings(meshQualitySettings), + m_features(features), + m_target(target) +{ + +} + +PDFAnnotationManager::~PDFAnnotationManager() +{ + +} + +QMatrix PDFAnnotationManager::prepareTransformations(const QMatrix& pagePointToDevicePointMatrix, + QPaintDevice* device, + const PDFAnnotation::Flags annotationFlags, + const PDFPage* page, + QRectF& annotationRectangle) const +{ + // "Unrotate" user coordinate space, if NoRotate flag is set + QMatrix userSpaceToDeviceSpace = pagePointToDevicePointMatrix; + if (annotationFlags.testFlag(PDFAnnotation::NoRotate)) + { + PDFReal rotationAngle = 0.0; + switch (page->getPageRotation()) + { + case PageRotation::None: + break; + + case PageRotation::Rotate90: + rotationAngle = -90.0; + break; + + case PageRotation::Rotate180: + rotationAngle = -180.0; + break; + + case PageRotation::Rotate270: + rotationAngle = -270.0; + break; + + default: + Q_ASSERT(false); + break; + } + + QMatrix rotationMatrix; + rotationMatrix.rotate(-rotationAngle); + QPointF topLeft = annotationRectangle.bottomLeft(); // Do not forget, that y is upward instead of Qt + QPointF difference = topLeft - rotationMatrix.map(topLeft); + + QMatrix finalMatrix; + finalMatrix.translate(difference.x(), difference.y()); + finalMatrix.rotate(-rotationAngle); + userSpaceToDeviceSpace = finalMatrix * userSpaceToDeviceSpace; + } + + if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) + { + // Jakub Melka: we must adjust annotation rectangle to disable zoom. We calculate + // inverse zoom as square root of absolute value of determinant of scale matrix. + // Determinant corresponds approximately to zoom squared, and if we will have + // unrotated matrix, and both axes are scaled by same value, then determinant will + // be exactly zoom squared. Also, we will adjust to target device logical DPI, + // if we, for example are using 4K, or 8K monitors. + qreal zoom = 1.0 / qSqrt(qAbs(pagePointToDevicePointMatrix.determinant())); + zoom = PDFWidgetUtils::scaleDPI_x(device, zoom); + + QRectF unzoomedRect(annotationRectangle.bottomLeft(), annotationRectangle.size() * zoom); + unzoomedRect.translate(0, -unzoomedRect.height()); + annotationRectangle = unzoomedRect; + } + + return userSpaceToDeviceSpace; +} + +void PDFAnnotationManager::drawWidgetAnnotationHighlight(QRectF annotationRectangle, + const PDFAnnotation* annotation, + QPainter* painter, + QMatrix userSpaceToDeviceSpace) const +{ + const bool isWidget = annotation->getType() == AnnotationType::Widget; + if (m_formManager && isWidget) + { + // Is it a form field? + const PDFFormManager::FormAppearanceFlags flags = m_formManager->getAppearanceFlags(); + const bool isFocused = m_formManager->isFocused(annotation->getSelfReference()); + if (isFocused || flags.testFlag(PDFFormManager::HighlightFields) || flags.testFlag(PDFFormManager::HighlightRequiredFields)) + { + const PDFFormField* formField = m_formManager->getFormFieldForWidget(annotation->getSelfReference()); + if (!formField) + { + return; + } + + // Nekreslíme zvýraznění push buttonů + if (formField->getFieldType() == PDFFormField::FieldType::Button && + formField->getFlags().testFlag(PDFFormField::PushButton)) + { + return; + } + + QColor color; + if (flags.testFlag(PDFFormManager::HighlightFields)) + { + color = Qt::blue; + } + if (flags.testFlag(PDFFormManager::HighlightRequiredFields) && formField->getFlags().testFlag(PDFFormField::Required)) + { + color = Qt::red; + } + if (isFocused) + { + color = Qt::yellow; + } + + if (color.isValid()) + { + color.setAlphaF(0.2); + + // Draw annotation rectangle by highlight color + QPainterPath highlightArea; + highlightArea.addRect(annotationRectangle); + highlightArea = userSpaceToDeviceSpace.map(highlightArea); + painter->fillPath(highlightArea, color); + } + } + } +} + +void PDFAnnotationManager::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + + const PageAnnotations& annotations = getPageAnnotations(pageIndex); + if (!annotations.isEmpty()) + { + const PDFPage* page = m_document->getCatalog()->getPage(pageIndex); + Q_ASSERT(page); + + PDFRenderer::Features features = m_features; + if (!features.testFlag(PDFRenderer::DisplayAnnotations)) + { + // Annotation displaying is disabled + return; + } + + Q_ASSERT(m_fontCache); + Q_ASSERT(m_cmsManager); + Q_ASSERT(m_optionalActivity); + + int fontCacheLock = 0; + const PDFCMSPointer cms = m_cmsManager->getCurrentCMS(); + m_fontCache->setCacheShrinkEnabled(&fontCacheLock, false); + + const PageAnnotation* annotationDrawnByEditor = nullptr; + for (const PageAnnotation& annotation : annotations.annotations) + { + // If annotation draw is not enabled, then skip it + if (!isAnnotationDrawEnabled(annotation)) + { + continue; + } + + if (isAnnotationDrawnByEditor(annotation)) + { + Q_ASSERT(!annotationDrawnByEditor); + annotationDrawnByEditor = &annotation; + continue; + } + + drawAnnotation(annotation, pagePointToDevicePointMatrix, page, cms.data(), false, errors, painter); + } + + if (annotationDrawnByEditor) + { + drawAnnotation(*annotationDrawnByEditor, pagePointToDevicePointMatrix, page, cms.data(), true, errors, painter); + } + + m_fontCache->setCacheShrinkEnabled(&fontCacheLock, true); + } +} + +void PDFAnnotationManager::drawAnnotation(const PageAnnotation& annotation, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPage* page, + const PDFCMS* cms, + bool isEditorDrawEnabled, + QList& errors, + QPainter* painter) const +{ + try + { + PDFObject appearanceStreamObject = m_document->getObject(getAppearanceStream(annotation)); + if (!appearanceStreamObject.isStream() || isEditorDrawEnabled) + { + // Object is not valid appearance stream. We will try to draw default + // annotation appearance, but we must consider also optional content. + // We do not draw annotation, if it is not ignored and annotation + // has reference to optional content. + + drawAnnotationDirect(annotation, pagePointToDevicePointMatrix, page, cms, isEditorDrawEnabled, painter); + } + else + { + drawAnnotationUsingAppearanceStream(annotation, appearanceStreamObject, pagePointToDevicePointMatrix, page, cms, painter); + } + } + catch (PDFException exception) + { + errors.push_back(PDFRenderError(RenderErrorType::Error, exception.getMessage())); + } + catch (PDFRendererException exception) + { + errors.push_back(exception.getError()); + } +} + +bool PDFAnnotationManager::isAnnotationDrawEnabled(const PageAnnotation& annotation) const +{ + const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags(); + return !(annotationFlags.testFlag(PDFAnnotation::Hidden) || // Annotation is completely hidden + (m_target == Target::Print && !annotationFlags.testFlag(PDFAnnotation::Print)) || // Target is print and annotation is marked as not printed + (m_target == Target::View && annotationFlags.testFlag(PDFAnnotation::NoView)) || // Target is view, and annotation is disabled for screen + annotation.annotation->isReplyTo()); // Annotation is reply to another annotation, display just the first annotation +} + +bool PDFAnnotationManager::isAnnotationDrawnByEditor(const PageAnnotation& annotation) const +{ + if (!m_formManager) + { + return false; + } + + const PDFFormFieldWidgetEditor* editor = nullptr; + if (annotation.annotation->getType() == AnnotationType::Widget) + { + editor = m_formManager->getEditor(m_formManager->getFormFieldForWidget(annotation.annotation->getSelfReference())); + return editor && editor->isEditorDrawEnabled(); + } + + return false; +} + +void PDFAnnotationManager::drawAnnotationDirect(const PageAnnotation& annotation, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPage* page, + const PDFCMS* cms, + bool isEditorDrawEnabled, + QPainter* painter) const +{ + if (!m_features.testFlag(PDFRenderer::IgnoreOptionalContent) && + annotation.annotation->getOptionalContent().isValid()) + { + PDFPainter pdfPainter(painter, m_features, pagePointToDevicePointMatrix, page, m_document, m_fontCache, cms, m_optionalActivity, m_meshQualitySettings); + if (pdfPainter.isContentSuppressedByOC(annotation.annotation->getOptionalContent())) + { + return; + } + } + + QRectF annotationRectangle = annotation.annotation->getRectangle(); + { + PDFPainterStateGuard guard(painter); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + AnnotationDrawParameters parameters; + parameters.painter = painter; + parameters.annotation = annotation.annotation.data(); + parameters.formManager = m_formManager; + parameters.key = std::make_pair(annotation.appearance, annotation.annotation->getAppearanceState()); + parameters.invertColors = m_features.testFlag(PDFRenderer::InvertColors); + annotation.annotation->draw(parameters); + + if (parameters.boundingRectangle.isValid()) + { + annotationRectangle = parameters.boundingRectangle; + } + } + + // Draw highlighting of fields, but only, if target is View, + // we do not want to render form field highlight, when we are + // printing to the printer. + if (m_target == Target::View && !isEditorDrawEnabled) + { + PDFPainterStateGuard guard(painter); + drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, pagePointToDevicePointMatrix); + } +} + +void PDFAnnotationManager::drawAnnotationUsingAppearanceStream(const PageAnnotation& annotation, + const PDFObject& appearanceStreamObject, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPage* page, + const PDFCMS* cms, + QPainter* painter) const +{ + PDFDocumentDataLoaderDecorator loader(m_document); + const PDFStream* formStream = appearanceStreamObject.getStream(); + const PDFDictionary* formDictionary = formStream->getDictionary(); + + const PDFAnnotation::Flags annotationFlags = annotation.annotation->getEffectiveFlags(); + QRectF annotationRectangle = annotation.annotation->getRectangle(); + QRectF formBoundingBox = loader.readRectangle(formDictionary->get("BBox"), QRectF()); + QMatrix formMatrix = loader.readMatrixFromDictionary(formDictionary, "Matrix", QMatrix()); + QByteArray content = m_document->getDecodedStream(formStream); + PDFObject resources = m_document->getObject(formDictionary->get("Resources")); + PDFObject transparencyGroup = m_document->getObject(formDictionary->get("Group")); + const PDFInteger formStructuralParentKey = loader.readIntegerFromDictionary(formDictionary, "StructParent", page->getStructureParentKey()); + + if (formBoundingBox.isEmpty() || annotationRectangle.isEmpty()) + { + // Form bounding box is empty + return; + } + + QMatrix userSpaceToDeviceSpace = prepareTransformations(pagePointToDevicePointMatrix, painter->device(), annotationFlags, page, annotationRectangle); + + PDFRenderer::Features features = m_features; + if (annotationFlags.testFlag(PDFAnnotation::NoZoom)) + { + features.setFlag(PDFRenderer::ClipToCropBox, false); + } + + // Jakub Melka: perform algorithm 8.1, defined in PDF 1.7 reference, + // chapter 8.4.4 Appearance streams. + + // Step 1) - calculate transformed appearance box + QRectF transformedAppearanceBox = formMatrix.mapRect(formBoundingBox); + + // Step 2) - calculate matrix A, which maps from form space to annotation space + // Matrix A transforms from transformed appearance box space to the + // annotation rectangle. + const PDFReal scaleX = annotationRectangle.width() / transformedAppearanceBox.width(); + const PDFReal scaleY = annotationRectangle.height() / transformedAppearanceBox.height(); + const PDFReal translateX = annotationRectangle.left() - transformedAppearanceBox.left() * scaleX; + const PDFReal translateY = annotationRectangle.bottom() - transformedAppearanceBox.bottom() * scaleY; + QMatrix A(scaleX, 0.0, 0.0, scaleY, translateX, translateY); + + // Step 3) - compute final matrix AA + QMatrix AA = formMatrix * A; + + bool isContentVisible = false; + + // Draw annotation + { + PDFPainterStateGuard guard(painter); + PDFPainter pdfPainter(painter, features, userSpaceToDeviceSpace, page, m_document, m_fontCache, cms, m_optionalActivity, m_meshQualitySettings); + pdfPainter.initializeProcessor(); + + // Jakub Melka: we must check, that we do not display annotation disabled by optional content + PDFObjectReference oc = annotation.annotation->getOptionalContent(); + isContentVisible = !oc.isValid() || !pdfPainter.isContentSuppressedByOC(oc); + + if (isContentVisible) + { + pdfPainter.processForm(AA, formBoundingBox, resources, transparencyGroup, content, formStructuralParentKey); + } + } + + // Draw highlighting of fields, but only, if target is View, + // we do not want to render form field highlight, when we are + // printing to the printer. + if (isContentVisible && m_target == Target::View) + { + PDFPainterStateGuard guard(painter); + painter->resetTransform(); + drawWidgetAnnotationHighlight(annotationRectangle, annotation.annotation.get(), painter, userSpaceToDeviceSpace); + } +} + +void PDFAnnotationManager::setDocument(const PDFModifiedDocument& document) +{ + if (m_document != document) + { + m_document = document; + m_optionalActivity = document.getOptionalContentActivity(); + + if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation)) + { + m_pageAnnotations.clear(); + } + } +} + +PDFObject PDFAnnotationManager::getAppearanceStream(const PageAnnotation& pageAnnotation) const +{ + auto getAppearanceStream = [&pageAnnotation] (void) -> PDFObject + { + return pageAnnotation.annotation->getAppearanceStreams().getAppearance(pageAnnotation.appearance, pageAnnotation.annotation->getAppearanceState()); + }; + + QMutexLocker lock(&m_mutex); + return pageAnnotation.appearanceStream.get(getAppearanceStream); +} + +const PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) const +{ + return const_cast(this)->getPageAnnotations(pageIndex); +} + +PDFAnnotationManager::PageAnnotations& PDFAnnotationManager::getPageAnnotations(PDFInteger pageIndex) +{ + Q_ASSERT(m_document); + + QMutexLocker lock(&m_mutex); + auto it = m_pageAnnotations.find(pageIndex); + if (it == m_pageAnnotations.cend()) + { + // Create page annotations + const PDFPage* page = m_document->getCatalog()->getPage(pageIndex); + Q_ASSERT(page); + + PageAnnotations annotations; + + const std::vector& pageAnnotations = page->getAnnotations(); + annotations.annotations.reserve(pageAnnotations.size()); + for (PDFObjectReference annotationReference : pageAnnotations) + { + PDFAnnotationPtr annotationPtr = PDFAnnotation::parse(&m_document->getStorage(), annotationReference); + if (annotationPtr) + { + PageAnnotation annotation; + annotation.annotation = qMove(annotationPtr); + annotations.annotations.emplace_back(qMove(annotation)); + } + } + + it = m_pageAnnotations.insert(std::make_pair(pageIndex, qMove(annotations))).first; + } + + return it->second; +} + +bool PDFAnnotationManager::hasAnnotation(PDFInteger pageIndex) const +{ + return !getPageAnnotations(pageIndex).isEmpty(); +} + +bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector& pageIndices) const +{ + return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1)); +} + +PDFFormManager* PDFAnnotationManager::getFormManager() const +{ + return m_formManager; +} + +void PDFAnnotationManager::setFormManager(PDFFormManager* formManager) +{ + m_formManager = formManager; +} + +PDFRenderer::Features PDFAnnotationManager::getFeatures() const +{ + return m_features; +} + +void PDFAnnotationManager::setFeatures(PDFRenderer::Features features) +{ + m_features = features; +} + +PDFMeshQualitySettings PDFAnnotationManager::getMeshQualitySettings() const +{ + return m_meshQualitySettings; +} + +void PDFAnnotationManager::setMeshQualitySettings(const PDFMeshQualitySettings& meshQualitySettings) +{ + m_meshQualitySettings = meshQualitySettings; +} + +PDFFontCache* PDFAnnotationManager::getFontCache() const +{ + return m_fontCache; +} + +void PDFAnnotationManager::setFontCache(PDFFontCache* fontCache) +{ + m_fontCache = fontCache; +} + +const PDFOptionalContentActivity* PDFAnnotationManager::getOptionalActivity() const +{ + return m_optionalActivity; +} + +void PDFAnnotationManager::setOptionalActivity(const PDFOptionalContentActivity* optionalActivity) +{ + m_optionalActivity = optionalActivity; +} + +PDFAnnotationManager::Target PDFAnnotationManager::getTarget() const +{ + return m_target; +} + +void PDFAnnotationManager::setTarget(Target target) +{ + m_target = target; +} + +PDFWidgetAnnotationManager::PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent) : + BaseClass(proxy->getFontCache(), proxy->getCMSManager(), proxy->getOptionalContentActivity(), proxy->getMeshQualitySettings(), proxy->getFeatures(), Target::View, parent), + m_proxy(proxy) +{ + Q_ASSERT(proxy); + m_proxy->registerDrawInterface(this); +} + +PDFWidgetAnnotationManager::~PDFWidgetAnnotationManager() +{ + m_proxy->unregisterDrawInterface(this); +} + +void PDFWidgetAnnotationManager::setDocument(const PDFModifiedDocument& document) +{ + BaseClass::setDocument(document); + + if (document.hasReset() || document.getFlags().testFlag(PDFModifiedDocument::Annotation)) + { + m_editableAnnotation = PDFObjectReference(); + m_editableAnnotationPage = PDFObjectReference(); + } +} + +void PDFWidgetAnnotationManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + updateFromMouseEvent(event); + + // Show context menu? + if (event->button() == Qt::RightButton) + { + PDFWidget* widget = m_proxy->getWidget(); + std::vector currentPages = widget->getDrawWidget()->getCurrentPages(); + + if (!hasAnyPageAnnotation(currentPages)) + { + // All pages doesn't have annotation + return; + } + + m_editableAnnotation = PDFObjectReference(); + m_editableAnnotationPage = PDFObjectReference(); + for (PDFInteger pageIndex : currentPages) + { + PageAnnotations& pageAnnotations = getPageAnnotations(pageIndex); + for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) + { + if (!pageAnnotation.isHovered) + { + continue; + } + + if (!PDFAnnotation::isTypeEditable(pageAnnotation.annotation->getType())) + { + continue; + } + + m_editableAnnotation = pageAnnotation.annotation->getSelfReference(); + m_editableAnnotationPage = pageAnnotation.annotation->getPageReference(); + + if (!m_editableAnnotationPage.isValid()) + { + m_editableAnnotationPage = m_document->getCatalog()->getPage(pageIndex)->getPageReference(); + } + break; + } + } + + if (m_editableAnnotation.isValid()) + { + QMenu menu(tr("Annotation"), widget); + QAction* showPopupAction = menu.addAction(tr("Show Popup Window")); + QAction* copyAction = menu.addAction(tr("Copy to Multiple Pages")); + QAction* editAction = menu.addAction(tr("Edit")); + QAction* deleteAction = menu.addAction(tr("Delete")); + connect(showPopupAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onShowPopupAnnotation); + connect(copyAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onCopyAnnotation); + connect(editAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onEditAnnotation); + connect(deleteAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onDeleteAnnotation); + + m_editableAnnotationGlobalPosition = widget->mapToGlobal(event->pos()); + menu.exec(m_editableAnnotationGlobalPosition); + } + } +} + +void PDFWidgetAnnotationManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + updateFromMouseEvent(event); +} + +void PDFWidgetAnnotationManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + updateFromMouseEvent(event); +} + +void PDFWidgetAnnotationManager::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFWidgetAnnotationManager::updateFromMouseEvent(QMouseEvent* event) +{ + PDFWidget* widget = m_proxy->getWidget(); + std::vector currentPages = widget->getDrawWidget()->getCurrentPages(); + + if (!hasAnyPageAnnotation(currentPages)) + { + // All pages doesn't have annotation + return; + } + + m_tooltip = QString(); + m_cursor = std::nullopt; + bool appearanceChanged = false; + + // We must update appearance states, and update tooltip + PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); + const bool isDown = event->buttons().testFlag(Qt::LeftButton); + const PDFAppeareanceStreams::Appearance hoverAppearance = isDown ? PDFAppeareanceStreams::Appearance::Down : PDFAppeareanceStreams::Appearance::Rollover; + + for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) + { + PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex); + for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) + { + if (pageAnnotation.annotation->isReplyTo()) + { + // Annotation is reply to another annotation, do not interact with it + continue; + } + + const PDFAppeareanceStreams::Appearance oldAppearance = pageAnnotation.appearance; + QRectF annotationRect = pageAnnotation.annotation->getRectangle(); + QMatrix matrix = prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect); + QPainterPath path; + path.addRect(annotationRect); + path = matrix.map(path); + + if (path.contains(event->pos())) + { + pageAnnotation.appearance = hoverAppearance; + pageAnnotation.isHovered = true; + + // Generate tooltip + if (m_tooltip.isEmpty()) + { + const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); + if (markupAnnotation) + { + QString title = markupAnnotation->getWindowTitle(); + if (title.isEmpty()) + { + title = markupAnnotation->getSubject(); + } + if (title.isEmpty()) + { + title = PDFTranslationContext::tr("Info"); + } + + const size_t repliesCount = pageAnnotations.getReplies(pageAnnotation).size(); + if (repliesCount > 0) + { + title = PDFTranslationContext::tr("%1 (%2 replies)").arg(title).arg(repliesCount); + } + + m_tooltip = QString("

%1

%2

").arg(title, markupAnnotation->getContents()); + } + } + + const PDFAction* linkAction = nullptr; + const AnnotationType annotationType = pageAnnotation.annotation->getType(); + if (annotationType == AnnotationType::Link) + { + const PDFLinkAnnotation* linkAnnotation = dynamic_cast(pageAnnotation.annotation.data()); + Q_ASSERT(linkAnnotation); + + // We must check, if user clicked to the link area + QPainterPath activationPath = linkAnnotation->getActivationRegion().getPath(); + activationPath = snapshotItem.pageToDeviceMatrix.map(activationPath); + if (activationPath.contains(event->pos()) && linkAnnotation->getAction()) + { + m_cursor = QCursor(Qt::PointingHandCursor); + linkAction = linkAnnotation->getAction(); + } + } + if (annotationType == AnnotationType::Widget) + { + if (m_formManager && m_formManager->hasFormFieldWidgetText(pageAnnotation.annotation->getSelfReference())) + { + m_cursor = QCursor(Qt::IBeamCursor); + } + else + { + m_cursor = QCursor(Qt::ArrowCursor); + } + } + + // Generate popup window + if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) + { + const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); + if (markupAnnotation) + { + QDialog* dialog = createDialogForMarkupAnnotations(widget, pageAnnotation, pageAnnotations); + + // Set proper dialog position - according to the popup annotation. If we + // do not have popup annotation, then try to use annotations rectangle. + if (const PageAnnotation* popupAnnotation = pageAnnotations.getPopupAnnotation(pageAnnotation)) + { + QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(popupAnnotation->annotation->getRectangle().bottomLeft()).toPoint(); + popupPoint = widget->mapToGlobal(popupPoint); + dialog->move(popupPoint); + } + else if (markupAnnotation->getRectangle().isValid()) + { + QPoint popupPoint = snapshotItem.pageToDeviceMatrix.map(markupAnnotation->getRectangle().bottomRight()).toPoint(); + popupPoint = widget->mapToGlobal(popupPoint); + dialog->move(popupPoint); + } + + dialog->exec(); + } + + if (linkAction) + { + emit actionTriggered(linkAction); + } + } + } + else + { + pageAnnotation.appearance = PDFAppeareanceStreams::Appearance::Normal; + pageAnnotation.isHovered = false; + } + + const bool currentAppearanceChanged = oldAppearance != pageAnnotation.appearance; + if (currentAppearanceChanged) + { + // We have changed appearance - we must mark stream as dirty + pageAnnotation.appearanceStream.dirty(); + appearanceChanged = true; + } + } + } + + // If appearance has changed, then we must redraw the page + if (appearanceChanged) + { + emit widget->getDrawWidgetProxy()->repaintNeeded(); + } +} + +void PDFWidgetAnnotationManager::onShowPopupAnnotation() +{ + PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); + for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) + { + PageAnnotations& pageAnnotations = getPageAnnotations(snapshotItem.pageIndex); + for (PageAnnotation& pageAnnotation : pageAnnotations.annotations) + { + if (pageAnnotation.annotation->isReplyTo()) + { + // Annotation is reply to another annotation, do not interact with it + continue; + } + + if (pageAnnotation.annotation->getSelfReference() == m_editableAnnotation) + { + QDialog* dialog = createDialogForMarkupAnnotations(m_proxy->getWidget(), pageAnnotation, pageAnnotations); + dialog->move(m_editableAnnotationGlobalPosition); + dialog->exec(); + return; + } + } + } +} + +void PDFWidgetAnnotationManager::onCopyAnnotation() +{ + pdf::PDFSelectPagesDialog dialog(tr("Copy Annotation"), tr("Copy Annotation onto Multiple Pages"), + m_document->getCatalog()->getPageCount(), m_proxy->getWidget()->getDrawWidget()->getCurrentPages(), m_proxy->getWidget()); + if (dialog.exec() == QDialog::Accepted) + { + std::vector pages = dialog.getSelectedPages(); + const PDFInteger currentPageIndex = m_document->getCatalog()->getPageIndexFromPageReference(m_editableAnnotationPage); + + for (PDFInteger& pageIndex : pages) + { + --pageIndex; + } + + auto it = std::find(pages.begin(), pages.end(), currentPageIndex); + if (it != pages.end()) + { + pages.erase(it); + } + + if (pages.empty()) + { + return; + } + + PDFDocumentModifier modifier(m_document); + modifier.markAnnotationsChanged(); + + for (const PDFInteger pageIndex : pages) + { + modifier.getBuilder()->copyAnnotation(m_document->getCatalog()->getPage(pageIndex)->getPageReference(), m_editableAnnotation); + } + + if (modifier.finalize()) + { + emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } +} + +void PDFWidgetAnnotationManager::onEditAnnotation() +{ + PDFEditObjectDialog dialog(EditObjectType::Annotation, m_proxy->getWidget()); + + PDFObject originalObject = m_document->getObjectByReference(m_editableAnnotation); + dialog.setObject(originalObject); + + if (dialog.exec() == PDFEditObjectDialog::Accepted) + { + PDFObject object = dialog.getObject(); + if (object != originalObject) + { + PDFDocumentModifier modifier(m_document); + modifier.markAnnotationsChanged(); + modifier.getBuilder()->setObject(m_editableAnnotation, object); + modifier.getBuilder()->updateAnnotationAppearanceStreams(m_editableAnnotation); + + if (modifier.finalize()) + { + emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } + } +} + +void PDFWidgetAnnotationManager::onDeleteAnnotation() +{ + if (m_editableAnnotation.isValid()) + { + PDFDocumentModifier modifier(m_document); + modifier.markAnnotationsChanged(); + modifier.getBuilder()->removeAnnotation(m_editableAnnotationPage, m_editableAnnotation); + + if (modifier.finalize()) + { + emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } +} + +QDialog* PDFWidgetAnnotationManager::createDialogForMarkupAnnotations(PDFWidget* widget, + const PageAnnotation& pageAnnotation, + const PageAnnotations& pageAnnotations) +{ + QDialog* dialog = new QDialog(widget->getDrawWidget()->getWidget(), Qt::Popup); + dialog->setAttribute(Qt::WA_DeleteOnClose, true); + createWidgetsForMarkupAnnotations(dialog, pageAnnotation, pageAnnotations); + return dialog; +} + +void PDFWidgetAnnotationManager::createWidgetsForMarkupAnnotations(QWidget* parentWidget, + const PageAnnotation& pageAnnotation, + const PageAnnotations& pageAnnotations) +{ + std::vector replies = pageAnnotations.getReplies(pageAnnotation); + replies.insert(replies.begin(), &pageAnnotation); + + QScrollArea* scrollArea = new QScrollArea(parentWidget); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + QVBoxLayout* layout = new QVBoxLayout(parentWidget); + layout->addWidget(scrollArea); + layout->setMargin(0); + layout->setContentsMargins(QMargins()); + + QWidget* frameWidget = new QWidget(scrollArea); + QVBoxLayout* frameLayout = new QVBoxLayout(frameWidget); + frameLayout->setMargin(0); + frameLayout->setSpacing(0); + scrollArea->setWidget(frameWidget); + + const PDFMarkupAnnotation* markupMainAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); + QColor color = markupMainAnnotation->getDrawColorFromAnnotationColor(markupMainAnnotation->getColor(), 1.0); + QColor titleColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.2, 1.0); + QColor backgroundColor = QColor::fromHslF(color.hueF(), color.saturationF(), 0.9, 1.0); + + QString style = "QGroupBox { " + "border: 2px solid black; " + "border-color: rgb(%4, %5, %6); " + "margin-top: 3ex; " + "background-color: rgb(%1, %2, %3); " + "}" + "QGroupBox::title { " + "subcontrol-origin: margin; " + "subcontrol-position: top center; " + "padding: 0px 8192px; " + "background-color: rgb(%4, %5, %6); " + "color: #FFFFFF;" + "}"; + style = style.arg(backgroundColor.red()).arg(backgroundColor.green()).arg(backgroundColor.blue()).arg(titleColor.red()).arg(titleColor.green()).arg(titleColor.blue()); + + for (const PageAnnotation* annotation : replies) + { + const PDFMarkupAnnotation* markupAnnotation = annotation->annotation->asMarkupAnnotation(); + + if (!markupAnnotation) + { + // This should not happen... + continue; + } + + QGroupBox* groupBox = new QGroupBox(scrollArea); + frameLayout->addWidget(groupBox); + + QString title = markupAnnotation->getWindowTitle(); + if (title.isEmpty()) + { + title = markupAnnotation->getSubject(); + } + + QString dateTimeString = markupAnnotation->getCreationDate().toLocalTime().toString(Qt::SystemLocaleLongDate); + title = QString("%1 (%2)").arg(title, dateTimeString).trimmed(); + + groupBox->setStyleSheet(style); + groupBox->setTitle(title); + QVBoxLayout* groupBoxLayout = new QVBoxLayout(groupBox); + + QLabel* label = new QLabel(groupBox); + label->setTextInteractionFlags(Qt::TextBrowserInteraction); + label->setWordWrap(true); + label->setText(markupAnnotation->getContents()); + label->setFixedWidth(PDFWidgetUtils::scaleDPI_x(label, 250)); + label->setMinimumHeight(label->sizeHint().height()); + groupBoxLayout->addWidget(label); + } + + frameWidget->setFixedSize(frameWidget->minimumSizeHint()); + parentWidget->setFixedSize(scrollArea->sizeHint()); +} + +void PDFSimpleGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + Q_ASSERT(parameters.painter); + parameters.boundingRectangle = getRectangle(); + + QPainter& painter = *parameters.painter; + painter.setPen(getPen()); + painter.setBrush(getBrush()); + painter.setCompositionMode(getCompositionMode()); + + switch (getType()) + { + case AnnotationType::Square: + { + const PDFAnnotationBorder& border = getBorder(); + + const PDFReal hCornerRadius = border.getHorizontalCornerRadius(); + const PDFReal vCornerRadius = border.getVerticalCornerRadius(); + const bool isRounded = !qFuzzyIsNull(hCornerRadius) || !qFuzzyIsNull(vCornerRadius); + + if (isRounded) + { + painter.drawRoundedRect(getRectangle(), hCornerRadius, vCornerRadius, Qt::AbsoluteSize); + } + else + { + painter.drawRect(getRectangle()); + } + break; + } + + case AnnotationType::Circle: + { + const PDFAnnotationBorder& border = getBorder(); + const PDFReal width = border.getWidth(); + QRectF rectangle = getRectangle(); + rectangle.adjust(width, width, -width, -width); + painter.drawEllipse(rectangle); + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } +} + +QColor PDFSimpleGeometryAnnotation::getFillColor() const +{ + return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); +} + +bool PDFMarkupAnnotation::isReplyTo() const +{ + return m_inReplyTo.isValid() && m_replyType == ReplyType::Reply; +} + +void PDFMarkupAnnotation::setWindowTitle(const QString& windowTitle) +{ + m_windowTitle = windowTitle; +} + +void PDFMarkupAnnotation::setPopupAnnotation(const PDFObjectReference& popupAnnotation) +{ + m_popupAnnotation = popupAnnotation; +} + +void PDFMarkupAnnotation::setRichTextString(const QString& richTextString) +{ + m_richTextString = richTextString; +} + +void PDFMarkupAnnotation::setCreationDate(const QDateTime& creationDate) +{ + m_creationDate = creationDate; +} + +void PDFMarkupAnnotation::setInReplyTo(PDFObjectReference inReplyTo) +{ + m_inReplyTo = inReplyTo; +} + +void PDFMarkupAnnotation::setSubject(const QString& subject) +{ + m_subject = subject; +} + +void PDFMarkupAnnotation::setIntent(const QByteArray& intent) +{ + m_intent = intent; +} + +void PDFMarkupAnnotation::setExternalData(const PDFObject& externalData) +{ + m_externalData = externalData; +} + +void PDFMarkupAnnotation::setReplyType(ReplyType replyType) +{ + m_replyType = replyType; +} + +std::vector PDFTextAnnotation::getDrawKeys(const PDFFormManager* formManager) const +{ + Q_UNUSED(formManager); + + return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() }, + PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Rollover, QByteArray() }, + PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; +} + +void PDFTextAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QColor strokeColor = QColor::fromRgbF(0.0, 0.0, 0.0, getStrokeOpacity()); + QColor fillColor = (parameters.key.first == PDFAppeareanceStreams::Appearance::Normal) ? QColor::fromRgbF(1.0, 1.0, 0.0, getFillOpacity()) : + QColor::fromRgbF(1.0, 0.0, 0.0, getFillOpacity()); + + constexpr const PDFReal rectSize = 32.0; + constexpr const PDFReal penWidth = 2.0; + + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + QRectF rectangle = getRectangle(); + rectangle.setSize(QSizeF(rectSize, rectSize)); + + QPen pen(strokeColor); + pen.setWidthF(penWidth); + painter.setPen(pen); + + painter.setBrush(QBrush(fillColor, Qt::SolidPattern)); + + QRectF ellipseRectangle = rectangle; + ellipseRectangle.adjust(penWidth, penWidth, -penWidth, -penWidth); + + // Draw the ellipse + painter.drawEllipse(ellipseRectangle); + + QFont font = QApplication::font(); + font.setPixelSize(16.0); + + QString text = getTextForIcon(m_iconName); + + QPainterPath textPath; + textPath.addText(0.0, 0.0, font, text); + textPath = QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0).map(textPath); + QRectF textBoundingRect = textPath.boundingRect(); + QPointF offset = rectangle.center() - textBoundingRect.center(); + textPath.translate(offset); + painter.fillPath(textPath, QBrush(strokeColor, Qt::SolidPattern)); + + parameters.boundingRectangle = rectangle; +} + +PDFTextAnnotation::Flags PDFTextAnnotation::getEffectiveFlags() const +{ + return getFlags() | NoZoom | NoRotate; +} + +QIcon PDFTextAnnotation::createIcon(QString key, QSize size) +{ + QIcon icon; + + QPixmap pixmap(size); + pixmap.fill(Qt::transparent); + + QRect rectangle(QPoint(0, 0), size); + rectangle.adjust(1, 1, -1, -1); + + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing); + painter.setRenderHint(QPainter::TextAntialiasing); + + QString text = getTextForIcon(key); + + QFont font = QApplication::font(); + font.setPixelSize(size.height() * 0.75); + + QPainterPath textPath; + textPath.addText(0.0, 0.0, font, text); + QRectF textBoundingRect = textPath.boundingRect(); + QPointF offset = rectangle.center() - textBoundingRect.center(); + textPath.translate(offset); + painter.fillPath(textPath, QBrush(Qt::black, Qt::SolidPattern)); + painter.end(); + + icon.addPixmap(qMove(pixmap)); + return icon; +} + +QString PDFTextAnnotation::getTextForIcon(const QString& key) +{ + QString text = "?"; + if (key == "Comment") + { + text = QString::fromUtf16(u"\U0001F4AC"); + } + else if (key == "Help") + { + text = "?"; + } + else if (key == "Insert") + { + text = QString::fromUtf16(u"\u2380"); + } + else if (key == "Key") + { + text = QString::fromUtf16(u"\U0001F511"); + } + else if (key == "NewParagraph") + { + text = QString::fromUtf16(u"\u2606"); + } + else if (key == "Note") + { + text = QString::fromUtf16(u"\u266A"); + } + else if (key == "Paragraph") + { + text = QString::fromUtf16(u"\u00B6"); + } + return text; +} + +void PDFLineAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QLineF line = getLine(); + if (line.isNull()) + { + // Jakub Melka: do not draw empty lines + return; + } + + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + painter.setPen(getPen()); + painter.setBrush(getBrush()); + + QPainterPath boundingPath; + boundingPath.moveTo(line.p1()); + boundingPath.lineTo(line.p2()); + + LineGeometryInfo info = LineGeometryInfo::create(line); + + const PDFReal leaderLineLength = getLeaderLineLength(); + const PDFReal coefficientSigned = (leaderLineLength >= 0.0) ? 1.0 : -1.0; + const PDFReal leaderLineOffset = getLeaderLineOffset() * coefficientSigned; + const PDFReal leaderLineExtension = getLeaderLineExtension() * coefficientSigned; + const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5); + const bool hasLeaderLine = !qFuzzyIsNull(leaderLineLength) || !qFuzzyIsNull(leaderLineExtension); + + QLineF normalLine = info.transformedLine.normalVector().unitVector(); + QPointF normalVector = normalLine.p1() - normalLine.p2(); + + QLineF lineToPaint = info.transformedLine; + if (hasLeaderLine) + { + // We will draw leader lines at both start/end + QPointF p1llStart = info.transformedLine.p1() + normalVector * leaderLineOffset; + QPointF p1llEnd = info.transformedLine.p1() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension); + + QLineF llStart(p1llStart, p1llEnd); + llStart = info.LCStoGCS.map(llStart); + + boundingPath.moveTo(llStart.p1()); + boundingPath.lineTo(llStart.p2()); + painter.drawLine(llStart); + + QPointF p2llStart = info.transformedLine.p2() + normalVector * leaderLineOffset; + QPointF p2llEnd = info.transformedLine.p2() + normalVector * (leaderLineOffset + leaderLineLength + leaderLineExtension); + + QLineF llEnd(p2llStart, p2llEnd); + llEnd = info.LCStoGCS.map(llEnd); + + boundingPath.moveTo(llEnd.p1()); + boundingPath.lineTo(llEnd.p2()); + painter.drawLine(llEnd); + + lineToPaint.translate(normalVector * (leaderLineOffset + leaderLineLength)); + } + + QLineF lineToPaintOrig = info.LCStoGCS.map(lineToPaint); + info = LineGeometryInfo::create(lineToPaintOrig); + drawLine(info, painter, lineEndingSize, getStartLineEnding(), getEndLineEnding(), boundingPath, getCaptionOffset(), getContents(), getCaptionPosition() == CaptionPosition::Top); + + parameters.boundingRectangle = boundingPath.boundingRect(); + parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize); +} + +QColor PDFLineAnnotation::getFillColor() const +{ + return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); +} + +void PDFPolygonalGeometryAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + if (m_vertices.empty()) + { + // Jakub Melka: do not draw empty lines + return; + } + + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + painter.setPen(getPen()); + painter.setBrush(getBrush()); + + const PDFReal penWidth = painter.pen().widthF(); + switch (m_type) + { + case AnnotationType::Polygon: + { + if (m_path.isEmpty()) + { + QPolygonF polygon; + polygon.reserve(int(m_vertices.size() + 1)); + for (const QPointF& point : m_vertices) + { + polygon << point; + } + if (!polygon.isClosed()) + { + polygon << m_vertices.front(); + } + + painter.drawPolygon(polygon, Qt::OddEvenFill); + parameters.boundingRectangle = polygon.boundingRect(); + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); + } + else + { + painter.drawPath(m_path); + parameters.boundingRectangle = m_path.boundingRect(); + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); + } + + break; + } + + case AnnotationType::Polyline: + { + const PDFReal lineEndingSize = painter.pen().widthF() * 5.0; + QPainterPath boundingPath; + + if (m_path.isEmpty()) + { + const size_t pointCount = m_vertices.size(); + const size_t lastPoint = pointCount - 1; + for (size_t i = 1; i < pointCount; ++i) + { + if (i == 1) + { + // We draw first line + drawLine(LineGeometryInfo::create(QLineF(m_vertices[i - 1], m_vertices[i])), painter, lineEndingSize, getStartLineEnding(), AnnotationLineEnding::None, boundingPath, QPointF(), QString(), true); + } + else if (i == lastPoint) + { + // We draw last line + drawLine(LineGeometryInfo::create(QLineF(m_vertices[i - 1], m_vertices[i])), painter, lineEndingSize, AnnotationLineEnding::None, getEndLineEnding(), boundingPath, QPointF(), QString(), true); + } + else + { + QLineF line(m_vertices[i - 1], m_vertices[i]); + boundingPath.moveTo(line.p1()); + boundingPath.lineTo(line.p2()); + painter.drawLine(line); + } + } + } + else + { + const PDFReal angle = 30; + const PDFReal lineEndingHalfSize = lineEndingSize * 0.5; + const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle)); + + boundingPath = m_path; + painter.drawPath(m_path); + + QMatrix LCStoGCS_start = LineGeometryInfo::create(QLineF(m_path.pointAtPercent(0.00), m_path.pointAtPercent(0.01))).LCStoGCS; + QMatrix LCStoGCS_end = LineGeometryInfo::create(QLineF(m_path.pointAtPercent(0.99), m_path.pointAtPercent(1.00))).LCStoGCS; + + drawLineEnding(&painter, m_path.pointAtPercent(0), lineEndingSize, arrowAxisLength, getStartLineEnding(), false, LCStoGCS_start, boundingPath); + drawLineEnding(&painter, m_path.pointAtPercent(1), lineEndingSize, arrowAxisLength, getEndLineEnding(), true, LCStoGCS_end, boundingPath); + } + + parameters.boundingRectangle = boundingPath.boundingRect(); + parameters.boundingRectangle.adjust(-lineEndingSize, -lineEndingSize, lineEndingSize, lineEndingSize); + break; + } + + default: + Q_ASSERT(false); + break; + } +} + +QColor PDFPolygonalGeometryAnnotation::getFillColor() const +{ + return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); +} + +PDFAnnotation::LineGeometryInfo PDFAnnotation::LineGeometryInfo::create(QLineF line) +{ + LineGeometryInfo result; + result.originalLine = line; + result.transformedLine = QLineF(QPointF(0, 0), QPointF(line.length(), 0)); + + // Strategy: for simplification, we rotate the line clockwise so we will + // get the line axis equal to the x-axis. + const double angle = line.angleTo(QLineF(0, 0, 1, 0)); + + QPointF p1 = line.p1(); + + // Matrix LCStoGCS is local coordinate system of line line. It transforms + // points on the line to the global coordinate system. So, point (0, 0) will + // map onto p1 and point (length(p1-p2), 0) will map onto p2. + result.LCStoGCS = QMatrix(); + result.LCStoGCS.translate(p1.x(), p1.y()); + result.LCStoGCS.rotate(angle); + result.GCStoLCS = result.LCStoGCS.inverted(); + + return result; +} + +void PDFAnnotation::drawLineEnding(QPainter* painter, + QPointF point, + PDFReal lineEndingSize, + PDFReal arrowAxisLength, + AnnotationLineEnding ending, + bool flipAxis, + const QMatrix& LCStoGCS, + QPainterPath& boundingPath) const +{ + QPainterPath path; + const PDFReal lineEndingHalfSize = lineEndingSize * 0.5; + + switch (ending) + { + case AnnotationLineEnding::None: + break; + + case AnnotationLineEnding::Square: + { + path.addRect(-lineEndingHalfSize, -lineEndingHalfSize, lineEndingSize, lineEndingSize); + break; + } + + case AnnotationLineEnding::Circle: + { + path.addEllipse(QPointF(0, 0), lineEndingHalfSize, lineEndingHalfSize); + break; + } + + case AnnotationLineEnding::Diamond: + { + path.moveTo(0.0, -lineEndingHalfSize); + path.lineTo(lineEndingHalfSize, 0.0); + path.lineTo(0.0, +lineEndingHalfSize); + path.lineTo(-lineEndingHalfSize, 0.0); + path.closeSubpath(); + break; + } + + case AnnotationLineEnding::OpenArrow: + { + path.moveTo(0.0, 0.0); + path.lineTo(arrowAxisLength, lineEndingHalfSize); + path.moveTo(0.0, 0.0); + path.lineTo(arrowAxisLength, -lineEndingHalfSize); + break; + } + + case AnnotationLineEnding::ClosedArrow: + { + path.moveTo(0.0, 0.0); + path.lineTo(arrowAxisLength, lineEndingHalfSize); + path.lineTo(arrowAxisLength, -lineEndingHalfSize); + path.closeSubpath(); + break; + } + + case AnnotationLineEnding::Butt: + { + path.moveTo(0.0, -lineEndingHalfSize); + path.lineTo(0.0, lineEndingHalfSize); + break; + } + + case AnnotationLineEnding::ROpenArrow: + { + path.moveTo(0.0, 0.0); + path.lineTo(-arrowAxisLength, lineEndingHalfSize); + path.moveTo(0.0, 0.0); + path.lineTo(-arrowAxisLength, -lineEndingHalfSize); + break; + } + + case AnnotationLineEnding::RClosedArrow: + { + path.moveTo(0.0, 0.0); + path.lineTo(-arrowAxisLength, lineEndingHalfSize); + path.lineTo(-arrowAxisLength, -lineEndingHalfSize); + path.closeSubpath(); + break; + } + + case AnnotationLineEnding::Slash: + { + const PDFReal angle = 60; + const PDFReal lineEndingHalfSizeForSlash = lineEndingSize * 0.5; + const PDFReal slashAxisLength = lineEndingHalfSizeForSlash / qTan(qDegreesToRadians(angle)); + + path.moveTo(-slashAxisLength, -lineEndingHalfSizeForSlash); + path.lineTo(slashAxisLength, lineEndingHalfSizeForSlash); + break; + } + + default: + break; + } + + if (!path.isEmpty()) + { + // Flip the x-axis (we are drawing endpoint) + if (flipAxis && ending != AnnotationLineEnding::Slash) + { + QMatrix matrix; + matrix.scale(-1.0, 1.0); + path = matrix.map(path); + } + + path.translate(point); + path = LCStoGCS.map(path); + painter->drawPath(path); + boundingPath.addPath(path); + } +} + +void PDFAnnotation::drawLine(const PDFAnnotation::LineGeometryInfo& info, + QPainter& painter, + PDFReal lineEndingSize, + AnnotationLineEnding p1Ending, + AnnotationLineEnding p2Ending, + QPainterPath& boundingPath, + QPointF textOffset, + const QString& text, + bool textIsAboveLine) const +{ + const PDFReal angle = 30; + const PDFReal lineEndingHalfSize = lineEndingSize * 0.5; + const PDFReal arrowAxisLength = lineEndingHalfSize / qTan(qDegreesToRadians(angle)); + + auto getOffsetFromLineEnding = [lineEndingHalfSize, arrowAxisLength](AnnotationLineEnding ending) + { + switch (ending) + { + case AnnotationLineEnding::Square: + case AnnotationLineEnding::Circle: + case AnnotationLineEnding::Diamond: + return lineEndingHalfSize; + case AnnotationLineEnding::ClosedArrow: + return arrowAxisLength; + + default: + break; + } + + return 0.0; + }; + + // Remove the offset from start/end + const PDFReal startOffset = getOffsetFromLineEnding(p1Ending); + const PDFReal endOffset = getOffsetFromLineEnding(p2Ending); + + QLineF adjustedLine = info.transformedLine; + adjustedLine.setP1(adjustedLine.p1() + QPointF(startOffset, 0)); + adjustedLine.setP2(adjustedLine.p2() - QPointF(endOffset, 0)); + + int textVerticalOffset = 0; + const bool drawText = !text.isEmpty(); + QPainterPath textPath; + + if (drawText) + { + QFont font = QApplication::font(); + font.setPixelSize(12.0); + + QFontMetricsF fontMetrics(font, painter.device()); + textVerticalOffset = fontMetrics.descent() + fontMetrics.leading(); + + textPath.addText(0, 0, font, text); + textPath = QMatrix(1, 0, 0, -1, 0, 0).map(textPath); + } + + drawLineEnding(&painter, info.transformedLine.p1(), lineEndingSize, arrowAxisLength, p1Ending, false, info.LCStoGCS, boundingPath); + drawLineEnding(&painter, info.transformedLine.p2(), lineEndingSize, arrowAxisLength, p2Ending, true, info.LCStoGCS, boundingPath); + + if (drawText && !textIsAboveLine) + { + // We will draw text in the line + QRectF textBoundingRect = textPath.controlPointRect(); + QPointF center = textBoundingRect.center(); + const qreal offsetY = center.y(); + const qreal offsetX = center.x(); + textPath.translate(textOffset + adjustedLine.center() - QPointF(offsetX, offsetY)); + textBoundingRect = textPath.controlPointRect(); + + const qreal textPadding = 3.0; + const qreal textStart = textBoundingRect.left() - textPadding; + const qreal textEnd = textBoundingRect.right() + textPadding; + + textPath = info.LCStoGCS.map(textPath); + painter.fillPath(textPath, QBrush(painter.pen().color(), Qt::SolidPattern)); + boundingPath.addPath(textPath); + + if (textStart > adjustedLine.p1().x()) + { + QLineF leftLine(adjustedLine.p1(), QPointF(textStart, adjustedLine.p2().y())); + painter.drawLine(info.LCStoGCS.map(leftLine)); + } + + if (textEnd < adjustedLine.p2().x()) + { + QLineF rightLine(QPointF(textEnd, adjustedLine.p2().y()), adjustedLine.p2()); + painter.drawLine(info.LCStoGCS.map(rightLine)); + } + + // Include whole line to the bounding path + adjustedLine = info.LCStoGCS.map(adjustedLine); + boundingPath.moveTo(adjustedLine.p1()); + boundingPath.lineTo(adjustedLine.p2()); + } + else + { + if (drawText) + { + // We will draw text above the line + QRectF textBoundingRect = textPath.controlPointRect(); + const qreal offsetY = textBoundingRect.top() - textVerticalOffset; + const qreal offsetX = textBoundingRect.center().x(); + textPath.translate(textOffset + adjustedLine.center() - QPointF(offsetX, offsetY)); + + textPath = info.LCStoGCS.map(textPath); + painter.fillPath(textPath, QBrush(painter.pen().color(), Qt::SolidPattern)); + boundingPath.addPath(textPath); + } + + adjustedLine = info.LCStoGCS.map(adjustedLine); + painter.drawLine(adjustedLine); + + boundingPath.moveTo(adjustedLine.p1()); + boundingPath.lineTo(adjustedLine.p2()); + } +} + +void PDFHighlightAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + if (m_highlightArea.isEmpty()) + { + // Jakub Melka: do not draw empty highlight area + return; + } + + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + parameters.boundingRectangle = m_highlightArea.getPath().boundingRect(); + + painter.setPen(getPen()); + painter.setBrush(getBrush()); + switch (m_type) + { + case AnnotationType::Highlight: + { + painter.setCompositionMode(QPainter::CompositionMode_Multiply); + painter.fillPath(m_highlightArea.getPath(), QBrush(getStrokeColor(), Qt::SolidPattern)); + break; + } + + case AnnotationType::Underline: + { + for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) + { + QPointF p1 = quadrilateral[0]; + QPointF p2 = quadrilateral[1]; + QLineF line(p1, p2); + painter.drawLine(line); + } + break; + } + + case AnnotationType::Squiggly: + { + // Jakub Melka: Squiggly underline + for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) + { + QPointF p1 = quadrilateral[0]; + QPointF p2 = quadrilateral[1]; + + // Calculate length (height) of quadrilateral + const PDFReal height = (QLineF(quadrilateral[0], quadrilateral[2]).length() + QLineF(quadrilateral[1], quadrilateral[3]).length()) * 0.5; + const PDFReal markSize = height / 7.0; + + // We can't assume, that line is horizontal. For example, rotated text with 45° degrees + // counterclockwise, if it is highlighted with squiggly underline, it is not horizontal line. + // So, we must calculate line geometry and transform it. + QLineF line(p1, p2); + LineGeometryInfo lineGeometryInfo = LineGeometryInfo::create(line); + + bool leadingEdge = true; + for (PDFReal x = lineGeometryInfo.transformedLine.p1().x(); x < lineGeometryInfo.transformedLine.p2().x(); x+= markSize) + { + QLineF line; + if (leadingEdge) + { + line = QLineF(x, 0.0, x + markSize, markSize); + } + else + { + // Falling edge + line = QLineF(x, markSize, x + markSize, 0.0); + } + + QLineF transformedLine = lineGeometryInfo.LCStoGCS.map(line); + painter.drawLine(transformedLine); + leadingEdge = !leadingEdge; + } + } + break; + } + + case AnnotationType::StrikeOut: + { + for (const PDFAnnotationQuadrilaterals::Quadrilateral& quadrilateral : m_highlightArea.getQuadrilaterals()) + { + QPointF p1 = (quadrilateral[0] + quadrilateral[2]) * 0.5; + QPointF p2 = (quadrilateral[1] + quadrilateral[3]) * 0.5; + QLineF line(p1, p2); + painter.drawLine(line); + } + break; + } + + default: + break; + } + + const qreal penWidth = painter.pen().widthF(); + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); +} + +std::vector PDFLinkAnnotation::getDrawKeys(const PDFFormManager* formManager) const +{ + Q_UNUSED(formManager); + + return { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; +} + +void PDFLinkAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + if (parameters.key.first != PDFAppeareanceStreams::Appearance::Down || + m_activationRegion.isEmpty() || + m_highlightMode == LinkHighlightMode::None) + { + // Nothing to draw + return; + } + + QPainter& painter = *parameters.painter; + parameters.boundingRectangle = getRectangle(); + + switch (m_highlightMode) + { + case LinkHighlightMode::Invert: + { + // Invert all + painter.setCompositionMode(QPainter::CompositionMode_Difference); + painter.fillPath(m_activationRegion.getPath(), QBrush(Qt::white, Qt::SolidPattern)); + break; + } + + case LinkHighlightMode::Outline: + { + // Invert the border + painter.setCompositionMode(QPainter::CompositionMode_Difference); + QPen pen = getPen(); + pen.setColor(Qt::white); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawPath(m_activationRegion.getPath()); + break; + } + + case LinkHighlightMode::Push: + { + // Draw border + painter.setCompositionMode(getCompositionMode()); + painter.setPen(getPen()); + painter.setBrush(Qt::NoBrush); + painter.drawPath(m_activationRegion.getPath()); + break; + } + + default: + Q_ASSERT(false); + break; + } +} + +PDFAnnotationDefaultAppearance PDFAnnotationDefaultAppearance::parse(const QByteArray& string) +{ + PDFAnnotationDefaultAppearance result; + PDFLexicalAnalyzer analyzer(string.constData(), string.constData() + string.size()); + + std::vector tokens; + + for (PDFLexicalAnalyzer::Token token = analyzer.fetch(); token.type != PDFLexicalAnalyzer::TokenType::EndOfFile; token = analyzer.fetch()) + { + tokens.push_back(qMove(token)); + } + + auto readNumber = [&tokens](size_t index) -> PDFReal + { + Q_ASSERT(index >= 0 && index < tokens.size()); + const PDFLexicalAnalyzer::Token& token = tokens[index]; + if (token.type == PDFLexicalAnalyzer::TokenType::Real || + token.type == PDFLexicalAnalyzer::TokenType::Integer) + { + return token.data.toDouble(); + } + + return 0.0; + }; + + for (size_t i = 0; i < tokens.size(); ++i) + { + const PDFLexicalAnalyzer::Token& token = tokens[i]; + if (token.type == PDFLexicalAnalyzer::TokenType::Command) + { + QByteArray command = token.data.toByteArray(); + if (command == "Tf") + { + if (i >= 1) + { + result.m_fontSize = readNumber(i - 1); + } + if (i >= 2) + { + result.m_fontName = tokens[i - 2].data.toByteArray(); + } + } + else if (command == "g" && i >= 1) + { + result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 1) }, 1.0); + } + else if (command == "rg" && i >= 3) + { + result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 3), readNumber(i - 2), readNumber(i - 1) }, 1.0); + } + else if (command == "k" && i >= 4) + { + result.m_fontColor = PDFAnnotation::getDrawColorFromAnnotationColor({ readNumber(i - 4), readNumber(i - 3), readNumber(i - 2), readNumber(i - 1) }, 1.0); + } + } + } + + return result; +} + +void PDFFreeTextAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + parameters.boundingRectangle = getRectangle(); + + painter.setPen(getPen()); + painter.setBrush(getBrush()); + + // Draw callout line + const PDFAnnotationCalloutLine& calloutLine = getCalloutLine(); + + switch (calloutLine.getType()) + { + case PDFAnnotationCalloutLine::Type::Invalid: + { + // Annotation doesn't have callout line + break; + } + + case PDFAnnotationCalloutLine::Type::StartEnd: + { + QPainterPath boundingPath; + QLineF line(calloutLine.getPoint(0), calloutLine.getPoint(1)); + const PDFReal lineEndingSize = qMin(painter.pen().widthF() * 5.0, line.length() * 0.5); + drawLine(LineGeometryInfo::create(line), painter, lineEndingSize, + getStartLineEnding(), getEndLineEnding(), boundingPath, + QPointF(), QString(), true); + break; + } + + case PDFAnnotationCalloutLine::Type::StartKneeEnd: + { + QPainterPath boundingPath; + + QLineF lineStart(calloutLine.getPoint(0), calloutLine.getPoint(1)); + QLineF lineEnd(calloutLine.getPoint(1), calloutLine.getPoint(2)); + + PDFReal preferredLineEndingSize = painter.pen().widthF() * 5.0; + PDFReal lineStartEndingSize = qMin(preferredLineEndingSize, lineStart.length() * 0.5); + drawLine(LineGeometryInfo::create(lineStart), painter, lineStartEndingSize, + getStartLineEnding(), AnnotationLineEnding::None, boundingPath, + QPointF(), QString(), true); + + + PDFReal lineEndEndingSize = qMin(preferredLineEndingSize, lineEnd.length() * 0.5); + drawLine(LineGeometryInfo::create(lineEnd), painter, lineEndEndingSize, + AnnotationLineEnding::None, getEndLineEnding() , boundingPath, + QPointF(), QString(), true); + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + QRectF textRect = getTextRectangle(); + if (!textRect.isValid()) + { + textRect = getRectangle(); + } + + painter.drawRect(textRect); + + // Draw text + PDFAnnotationDefaultAppearance defaultAppearance = PDFAnnotationDefaultAppearance::parse(getDefaultAppearance()); + + QFont font(defaultAppearance.getFontName()); + font.setPixelSize(defaultAppearance.getFontSize()); + painter.setPen(defaultAppearance.getFontColor()); + + Qt::Alignment alignment = Qt::AlignTop; + switch (getJustification()) + { + case PDFFreeTextAnnotation::Justification::Left: + alignment |= Qt::AlignLeft; + break; + + case PDFFreeTextAnnotation::Justification::Centered: + alignment |= Qt::AlignHCenter; + break; + + case PDFFreeTextAnnotation::Justification::Right: + alignment |= Qt::AlignRight; + break; + + default: + alignment |= Qt::AlignLeft; + Q_ASSERT(false); + break; + } + + QTextOption option(alignment); + option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + option.setUseDesignMetrics(true); + + painter.translate(textRect.left(), textRect.bottom()); + painter.scale(1.0, -1.0); + painter.drawText(QRectF(QPointF(0, 0), textRect.size()), getContents(), option); +} + +void PDFCaretAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + parameters.boundingRectangle = getRectangle(); + + QRectF caretRect = getCaretRectangle(); + if (caretRect.isEmpty()) + { + caretRect = getRectangle(); + } + + QPointF controlPoint(caretRect.center()); + controlPoint.setY(caretRect.top()); + + QPointF topPoint = controlPoint; + topPoint.setY(caretRect.bottom()); + + QPainterPath path; + path.moveTo(caretRect.topLeft()); + path.quadTo(controlPoint, topPoint); + path.quadTo(controlPoint, caretRect.topRight()); + path.lineTo(caretRect.topLeft()); + path.closeSubpath(); + + painter.fillPath(path, QBrush(getStrokeColor(), Qt::SolidPattern)); +} + +void PDFInkAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QPainter& painter = *parameters.painter; + QPainterPath path = getInkPath(); + + painter.setPen(getPen()); + painter.setBrush(getBrush()); + painter.setCompositionMode(getCompositionMode()); + + QPainterPath boundingPath; + QPainterPath currentPath; + const int elementCount = path.elementCount(); + bool hasSpline = false; + for (int i = 0; i < elementCount; ++i) + { + QPainterPath::Element element = path.elementAt(i); + switch (element.type) + { + case QPainterPath::MoveToElement: + { + // Reset the path + if (!currentPath.isEmpty()) + { + boundingPath.addPath(currentPath); + painter.drawPath(currentPath); + currentPath.clear(); + } + currentPath.moveTo(element.x, element.y); + break; + } + + case QPainterPath::LineToElement: + { + const QPointF p0 = currentPath.currentPosition(); + const QPointF p1(element.x, element.y); + + QLineF line(p0, p1); + QPointF normal = line.normalVector().p2() - p0; + + // Jakub Melka: This computation should be clarified. We use second order Bezier curves. + // We must calculate single control point. Let B(t) is bezier curve of second order. + // Then second derivation is B''(t) = 2(p0 - 2*Pc + p1). Second derivation curve is its + // normal. So, we calculate normal vector of the line (which has norm equal to line length), + // then we get following formula: + // + // Pc = (p0 + p1) / 2 - B''(t) / 4 + // + // So, for bezier curves of second order, second derivative is constant (which is not surprising, + // because second derivative of polynomial of order 2 is also a constant). Control point is then + // used to paint the path. + QPointF controlPoint = -normal * 0.25 + (p0 + p1) * 0.5; + currentPath.quadTo(controlPoint, p1); + break; + } + + case QPainterPath::CurveToElement: + case QPainterPath::CurveToDataElement: + hasSpline = true; + break; + + default: + Q_ASSERT(false); + break; + } + + // Jakub Melka: If we have a spline, then we don't do anything... + // Just copy the spline path. + if (hasSpline) + { + currentPath = path; + break; + } + } + + // Reset the path + if (!currentPath.isEmpty()) + { + boundingPath.addPath(currentPath); + painter.drawPath(currentPath); + currentPath.clear(); + } + + const qreal penWidth = painter.pen().widthF(); + parameters.boundingRectangle = boundingPath.boundingRect(); + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); +} + +void PDFStampAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + + QString text = getText(m_stamp); + QColor color(Qt::red); + + switch (m_stamp) + { + case Stamp::Approved: + color = Qt::green; + break; + + case Stamp::AsIs: + case Stamp::Confidential: + break; + + case Stamp::Departmental: + color = Qt::blue; + break; + + case Stamp::Draft: + break; + + case Stamp::Experimental: + color = Qt::blue; + break; + + case Stamp::Expired: + case Stamp::Final: + break; + + case Stamp::ForComment: + color = Qt::green; + break; + + case Stamp::ForPublicRelease: + color = Qt::green; + break; + + case Stamp::NotApproved: + case Stamp::NotForPublicRelease: + break; + + case Stamp::Sold: + color = Qt::blue; + break; + + case Stamp::TopSecret: + break; + + default: + Q_ASSERT(false); + break; + } + + color.setAlphaF(getFillOpacity()); + + const PDFReal textHeight = 16; + QFont font("Courier New"); + font.setBold(true); + font.setPixelSize(textHeight); + + QFontMetricsF fontMetrics(font, painter.device()); + const qreal textWidth = fontMetrics.width(text); + const qreal rectangleWidth = textWidth + 10; + const qreal rectangleHeight = textHeight * 1.2; + const qreal penWidth = 2.0; + + QRectF rectangle = getRectangle(); + rectangle.setSize(QSizeF(rectangleWidth, rectangleHeight)); + + QPen pen(color); + pen.setWidthF(penWidth); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + + painter.drawRoundedRect(rectangle, 5, 5, Qt::AbsoluteSize); + + // Draw text + QPainterPath textPath; + textPath.addText(0, 0, font, text); + textPath = QMatrix(1, 0, 0, -1, 0, 0).map(textPath); + + QPointF center = textPath.boundingRect().center(); + textPath.translate(rectangle.center() - center); + painter.fillPath(textPath, QBrush(color, Qt::SolidPattern)); + + parameters.boundingRectangle = rectangle; + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); +} + +QString PDFStampAnnotation::getText(Stamp stamp) +{ + QString text; + + switch (stamp) + { + case Stamp::Approved: + text = PDFTranslationContext::tr("APPROVED"); + break; + + case Stamp::AsIs: + text = PDFTranslationContext::tr("AS IS"); + break; + + case Stamp::Confidential: + text = PDFTranslationContext::tr("CONFIDENTIAL"); + break; + + case Stamp::Departmental: + text = PDFTranslationContext::tr("DEPARTMENTAL"); + break; + + case Stamp::Draft: + text = PDFTranslationContext::tr("DRAFT"); + break; + + case Stamp::Experimental: + text = PDFTranslationContext::tr("EXPERIMENTAL"); + break; + + case Stamp::Expired: + text = PDFTranslationContext::tr("EXPIRED"); + break; + + case Stamp::Final: + text = PDFTranslationContext::tr("FINAL"); + break; + + case Stamp::ForComment: + text = PDFTranslationContext::tr("FOR COMMENT"); + break; + + case Stamp::ForPublicRelease: + text = PDFTranslationContext::tr("FOR PUBLIC RELEASE"); + break; + + case Stamp::NotApproved: + text = PDFTranslationContext::tr("NOT APPROVED"); + break; + + case Stamp::NotForPublicRelease: + text = PDFTranslationContext::tr("NOT FOR PUBLIC RELEASE"); + break; + + case Stamp::Sold: + text = PDFTranslationContext::tr("SOLD"); + break; + + case Stamp::TopSecret: + text = PDFTranslationContext::tr("TOP SECRET"); + break; + + default: + Q_ASSERT(false); + break; + } + + return text; +} + +void PDFStampAnnotation::setStamp(const Stamp& stamp) +{ + m_stamp = stamp; +} + +void PDFStampAnnotation::setIntent(const StampIntent& intent) +{ + m_intent = intent; +} + +void PDFAnnotation::drawCharacterSymbol(QString text, PDFReal opacity, AnnotationDrawParameters& parameters) const +{ + QColor strokeColor = QColor::fromRgbF(0.0, 0.0, 0.0, opacity); + + constexpr const PDFReal rectSize = 24.0; + QPainter& painter = *parameters.painter; + QRectF rectangle = getRectangle(); + rectangle.setSize(QSizeF(rectSize, rectSize)); + + QFont font = QApplication::font(); + font.setPixelSize(16.0); + + QPainterPath textPath; + textPath.addText(0.0, 0.0, font, text); + textPath = QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0).map(textPath); + QRectF textBoundingRect = textPath.boundingRect(); + QPointF offset = rectangle.center() - textBoundingRect.center(); + textPath.translate(offset); + painter.fillPath(textPath, QBrush(strokeColor, Qt::SolidPattern)); + + parameters.boundingRectangle = rectangle; +} + +void PDFFileAttachmentAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QString text = "?"; + switch (getIcon()) + { + case FileAttachmentIcon::Graph: + text = QString::fromUtf16(u"\U0001F4C8"); + break; + case FileAttachmentIcon::Paperclip: + text = QString::fromUtf16(u"\U0001F4CE"); + break; + case FileAttachmentIcon::PushPin: + text = QString::fromUtf16(u"\U0001F4CC"); + break; + case FileAttachmentIcon::Tag: + text = QString::fromUtf16(u"\U0001F3F7"); + break; + + default: + Q_ASSERT(false); + break; + } + + parameters.painter->setCompositionMode(getCompositionMode()); + drawCharacterSymbol(text, getStrokeOpacity(), parameters); +} + +void PDFSoundAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + QString text = "?"; + switch (getIcon()) + { + case Icon::Speaker: + text = QString::fromUtf16(u"\U0001F508"); + break; + case Icon::Microphone: + text = QString::fromUtf16(u"\U0001F3A4"); + break; + + default: + Q_ASSERT(false); + break; + } + + parameters.painter->setCompositionMode(getCompositionMode()); + drawCharacterSymbol(text, getStrokeOpacity(), parameters); +} + +const PDFAnnotationManager::PageAnnotation* PDFAnnotationManager::PageAnnotations::getPopupAnnotation(const PageAnnotation& pageAnnotation) const +{ + const PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); + if (markupAnnotation) + { + const PDFObjectReference popupAnnotation = markupAnnotation->getPopupAnnotation(); + auto it = std::find_if(annotations.cbegin(), annotations.cend(), [popupAnnotation](const PageAnnotation& pa) { return pa.annotation->getSelfReference() == popupAnnotation; }); + if (it != annotations.cend()) + { + return &*it; + } + } + + return nullptr; +} + +std::vector PDFAnnotationManager::PageAnnotations::getReplies(const PageAnnotation& pageAnnotation) const +{ + std::vector result; + + const PDFObjectReference reference = pageAnnotation.annotation->getSelfReference(); + for (size_t i = 0, count = annotations.size(); i < count; ++i) + { + const PageAnnotation& currentAnnotation = annotations[i]; + if (currentAnnotation.annotation->isReplyTo()) + { + const PDFMarkupAnnotation* markupAnnotation = currentAnnotation.annotation->asMarkupAnnotation(); + Q_ASSERT(markupAnnotation); + + if (markupAnnotation->getInReplyTo() == reference) + { + result.push_back(¤tAnnotation); + } + } + } + + auto comparator = [](const PageAnnotation* l, const PageAnnotation* r) + { + QDateTime leftDateTime = l->annotation->getLastModifiedDateTime(); + QDateTime rightDateTime = r->annotation->getLastModifiedDateTime(); + + if (const PDFMarkupAnnotation* markupL = l->annotation->asMarkupAnnotation()) + { + leftDateTime = markupL->getCreationDate(); + } + + if (const PDFMarkupAnnotation* markupR = r->annotation->asMarkupAnnotation()) + { + rightDateTime = markupR->getCreationDate(); + } + + return leftDateTime < rightDateTime; + }; + std::sort(result.begin(), result.end(), comparator); + + return result; +} + +void PDFWidgetAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + // Do not draw without form manager + if (!parameters.formManager) + { + return; + } + + // Do not draw without form field + const PDFFormField* formField = parameters.formManager->getFormFieldForWidget(getSelfReference()); + if (!formField) + { + return; + } + + PDFPainterStateGuard guard(parameters.painter); + parameters.painter->setCompositionMode(getCompositionMode()); + + const PDFFormFieldWidgetEditor* editor = parameters.formManager->getEditor(formField); + if (editor && editor->isEditorDrawEnabled()) + { + editor->draw(parameters, true); + } + else + { + switch (formField->getFieldType()) + { + case PDFFormField::FieldType::Text: + case PDFFormField::FieldType::Choice: + { + editor->draw(parameters, false); + break; + } + + case PDFFormField::FieldType::Button: + { + const PDFFormFieldButton* button = dynamic_cast(formField); + switch (button->getButtonType()) + { + case PDFFormFieldButton::ButtonType::PushButton: + { + QRectF rectangle = getRectangle(); + + if (!getContents().isEmpty()) + { + QByteArray defaultAppearance = parameters.formManager->getForm()->getDefaultAppearance().value_or(QByteArray()); + PDFAnnotationDefaultAppearance appearance = PDFAnnotationDefaultAppearance::parse(defaultAppearance); + + qreal fontSize = appearance.getFontSize(); + if (qFuzzyIsNull(fontSize)) + { + fontSize = rectangle.height() * 0.6; + } + + QFont font(appearance.getFontName()); + font.setHintingPreference(QFont::PreferNoHinting); + font.setPixelSize(qCeil(fontSize)); + font.setStyleStrategy(QFont::ForceOutline); + + QFontMetrics fontMetrics(font); + + QPainter* painter = parameters.painter; + painter->translate(rectangle.bottomLeft()); + painter->scale(1.0, -1.0); + painter->setFont(font); + + QStyleOptionButton option; + option.state = QStyle::State_Enabled; + option.rect = QRect(0, 0, qFloor(rectangle.width()), qFloor(rectangle.height())); + option.palette = QApplication::palette(); + + if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover) + { + option.state |= QStyle::State_MouseOver; + } + + if (parameters.key.first == PDFAppeareanceStreams::Appearance::Down) + { + option.state |= QStyle::State_Sunken; + } + + option.features = QStyleOptionButton::None; + option.text = getContents(); + option.fontMetrics = fontMetrics; + + QApplication::style()->drawControl(QStyle::CE_PushButton, &option, painter, nullptr); + } + else + { + // This is push button without text. Just mark the area as highlighted. + QPainter* painter = parameters.painter; + + if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover || + parameters.key.first == PDFAppeareanceStreams::Appearance::Down) + { + switch (m_highlightMode) + { + case HighlightMode::Invert: + { + // Invert all + painter->setCompositionMode(QPainter::CompositionMode_Difference); + painter->fillRect(rectangle, QBrush(Qt::white, Qt::SolidPattern)); + break; + } + + case HighlightMode::Outline: + { + // Invert the border + painter->setCompositionMode(QPainter::CompositionMode_Difference); + QPen pen = getPen(); + pen.setColor(Qt::white); + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rectangle); + break; + } + + case HighlightMode::Push: + { + // Draw border + painter->setCompositionMode(getCompositionMode()); + painter->setPen(getPen()); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rectangle); + break; + } + + default: + Q_ASSERT(false); + break; + } + } + } + break; + } + + case PDFFormFieldButton::ButtonType::RadioButton: + case PDFFormFieldButton::ButtonType::CheckBox: + { + QRectF rectangle = getRectangle(); + QPainter* painter = parameters.painter; + painter->translate(rectangle.bottomLeft()); + painter->scale(1.0, -1.0); + + QStyleOptionButton option; + option.state = QStyle::State_Enabled; + option.rect = QRect(0, 0, qFloor(rectangle.width()), qFloor(rectangle.height())); + option.palette = QApplication::palette(); + + if (parameters.key.first == PDFAppeareanceStreams::Appearance::Rollover) + { + option.state |= QStyle::State_MouseOver; + } + + if (parameters.key.first == PDFAppeareanceStreams::Appearance::Down) + { + option.state |= QStyle::State_Sunken; + } + + if (parameters.key.second != "Off") + { + option.state |= QStyle::State_On; + } + else + { + option.state |= QStyle::State_Off; + } + + option.features = QStyleOptionButton::None; + option.text = QString(); + + QStyle::PrimitiveElement element = (button->getButtonType() == PDFFormFieldButton::ButtonType::CheckBox) ? QStyle::PE_IndicatorCheckBox : QStyle::PE_IndicatorRadioButton; + QApplication::style()->drawPrimitive(element, &option, painter, nullptr); + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + break; + } + + case PDFFormField::FieldType::Invalid: + case PDFFormField::FieldType::Signature: + break; + + default: + { + Q_ASSERT(false); + break; + } + } + } +} + +std::vector PDFWidgetAnnotation::getDrawKeys(const PDFFormManager* formManager) const +{ + if (!formManager) + { + return PDFAnnotation::getDrawKeys(formManager); + } + + std::vector result; + + // Try get the form field, if we find it, then determine from form field type + // the list of appearance states. + const PDFFormField* formField = formManager->getFormFieldForWidget(getSelfReference()); + if (!formField) + { + return PDFAnnotation::getDrawKeys(formManager); + } + + switch (formField->getFieldType()) + { + case PDFFormField::FieldType::Invalid: + break; + + case PDFFormField::FieldType::Button: + { + const PDFFormFieldButton* button = dynamic_cast(formField); + switch (button->getButtonType()) + { + case PDFFormFieldButton::ButtonType::PushButton: + { + result = { PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() }, + PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Rollover, QByteArray() }, + PDFAppeareanceStreams::Key{ PDFAppeareanceStreams::Appearance::Down, QByteArray() } }; + break; + } + + case PDFFormFieldButton::ButtonType::RadioButton: + case PDFFormFieldButton::ButtonType::CheckBox: + { + result = getAppearanceStreams().getAppearanceKeys(); + PDFAppeareanceStreams::Key offKey{ PDFAppeareanceStreams::Appearance::Normal, QByteArray() }; + if (std::find(result.cbegin(), result.cend(), offKey) == result.cend()) + { + result.push_back(qMove(offKey)); + } + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + break; + } + + case PDFFormField::FieldType::Text: + // Text has only default appearance + break; + + case PDFFormField::FieldType::Choice: + // Choices have always default appearance + break; + + case PDFFormField::FieldType::Signature: + // Signatures have always default appearance + break; + } + + if (result.empty()) + { + result = PDFAnnotation::getDrawKeys(formManager); + } + + return result; +} + +void PDFRedactAnnotation::draw(AnnotationDrawParameters& parameters) const +{ + if (m_redactionRegion.isEmpty()) + { + // Jakub Melka: do not draw empty redact area + return; + } + + QPainter& painter = *parameters.painter; + painter.setCompositionMode(getCompositionMode()); + parameters.boundingRectangle = m_redactionRegion.getPath().boundingRect(); + + painter.setPen(getPen()); + painter.setBrush(getBrush()); + painter.drawPath(m_redactionRegion.getPath()); + + const qreal penWidth = painter.pen().widthF(); + parameters.boundingRectangle.adjust(-penWidth, -penWidth, penWidth, penWidth); +} + +QColor PDFRedactAnnotation::getFillColor() const +{ + return getDrawColorFromAnnotationColor(getInteriorColor(), getFillOpacity()); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfannotation.h b/Pdf4QtLib/sources/pdfannotation.h index 2b72ec2..1df6dcf 100644 --- a/Pdf4QtLib/sources/pdfannotation.h +++ b/Pdf4QtLib/sources/pdfannotation.h @@ -1,1697 +1,1697 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFANNOTATION_H -#define PDFANNOTATION_H - -#include "pdfutils.h" -#include "pdfobject.h" -#include "pdfaction.h" -#include "pdffile.h" -#include "pdfcms.h" -#include "pdfmultimedia.h" -#include "pdfmeshqualitysettings.h" -#include "pdfdocumentdrawinterface.h" -#include "pdfrenderer.h" -#include "pdfblendfunction.h" -#include "pdfdocument.h" - -#include -#include - -#include - -class QKeyEvent; -class QMouseEvent; -class QWheelEvent; - -namespace pdf -{ -class PDFWidget; -class PDFObjectStorage; -class PDFDrawWidgetProxy; -class PDFFontCache; -class PDFFormManager; -class PDFModifiedDocument; -class PDFOptionalContentActivity; -class PDFFormFieldWidgetEditor; -class PDFModifiedDocument; - -using TextAlignment = Qt::Alignment; -using Polygons = std::vector; - -enum class AnnotationType -{ - Invalid, - Text, - Link, - FreeText, - Line, - Square, - Circle, - Polygon, - Polyline, - Highlight, - Underline, - Squiggly, - StrikeOut, - Stamp, - Caret, - Ink, - Popup, - FileAttachment, - Sound, - Movie, - Widget, - Screen, - PrinterMark, - TrapNet, - Watermark, - Redact, - Projection, - _3D, - RichMedia -}; - -enum class AnnotationLineEnding -{ - None, - Square, - Circle, - Diamond, - OpenArrow, - ClosedArrow, - Butt, - ROpenArrow, - RClosedArrow, - Slash -}; - -/// Represents annotation's border. Two main definition exists, one is older, -/// called \p Simple, the other one is defined in BS dictionary of the annotation. -class PDFAnnotationBorder -{ -public: - explicit inline PDFAnnotationBorder() = default; - - enum class Definition - { - Invalid, - Simple, - BorderStyle - }; - - enum class Style - { - Solid, - Dashed, - Beveled, - Inset, - Underline - }; - - /// Parses the annotation border from the array. If object contains invalid annotation border, - /// then default annotation border is returned. If object is empty, empty annotation border is returned. - /// \param storage Object storage - /// \param object Border object - static PDFAnnotationBorder parseBorder(const PDFObjectStorage* storage, PDFObject object); - - /// Parses the annotation border from the BS dictionary. If object contains invalid annotation border, - /// then default annotation border is returned. If object is empty, empty annotation border is returned. - /// \param storage Object storage - /// \param object Border object - static PDFAnnotationBorder parseBS(const PDFObjectStorage* storage, PDFObject object); - - /// Returns true, if object is correctly defined - bool isValid() const { return m_definition != Definition::Invalid; } - - Definition getDefinition() const { return m_definition; } - Style getStyle() const { return m_style; } - PDFReal getHorizontalCornerRadius() const { return m_hCornerRadius; } - PDFReal getVerticalCornerRadius() const { return m_vCornerRadius; } - PDFReal getWidth() const { return m_width; } - const std::vector& getDashPattern() const { return m_dashPattern; } - -private: - Definition m_definition = Definition::Invalid; - Style m_style = Style::Solid; - PDFReal m_hCornerRadius = 0.0; - PDFReal m_vCornerRadius = 0.0; - PDFReal m_width = 1.0; - std::vector m_dashPattern; -}; - -using AnnotationBorderStyle = PDFAnnotationBorder::Style; - -/// Annotation border effect -class PDFAnnotationBorderEffect -{ -public: - explicit inline PDFAnnotationBorderEffect() = default; - - enum class Effect - { - None, - Cloudy - }; - - /// Parses the annotation border effect from the object. If object contains invalid annotation border effect, - /// then default annotation border effect is returned. If object is empty, also default annotation border effect is returned. - /// \param storage Object storage - /// \param object Border effect object - static PDFAnnotationBorderEffect parse(const PDFObjectStorage* storage, PDFObject object); - -private: - Effect m_effect = Effect::None; - PDFReal m_intensity = 0.0; -}; - -/// Storage which handles appearance streams of annotations. Appeareance streams are divided -/// to three main categories - normal, rollower and down. Each category can have different -/// states, for example, checkbox can have on/off state. This container can also resolve -/// queries to obtain appropriate appearance stream. -class PDFAppeareanceStreams -{ -public: - explicit inline PDFAppeareanceStreams() = default; - - enum class Appearance - { - Normal, - Rollover, - Down - }; - - using Key = std::pair; - - /// Parses annotation appearance streams from the object. If object is invalid, then - /// empty appearance stream is constructed. - /// \param storage Object storage - /// \param object Appearance streams object - static PDFAppeareanceStreams parse(const PDFObjectStorage* storage, PDFObject object); - - /// Tries to search for appearance stream for given appearance. If no appearance is found, - /// then null object is returned. - /// \param appearance Appearance type - PDFObject getAppearance(Appearance appearance = Appearance::Normal) const { return getAppearance(appearance, QByteArray()); } - - /// Tries to resolve appearance stream for given appearance and state. If no appearance is found, - /// then null object is returned. - /// \param appearance Appearance type - /// \param state State name - PDFObject getAppearance(Appearance appearance, const QByteArray& state) const; - - /// Returns list of appearance states for given appearance - /// \param appearance Appearance - QByteArrayList getAppearanceStates(Appearance appearance) const; - - /// Returns list of appearance keys - std::vector getAppearanceKeys() const; - -private: - std::map m_appearanceStreams; -}; - -/// Represents annotation's active region, it is used also to -/// determine underline lines. -class PDFAnnotationQuadrilaterals -{ -public: - using Quadrilateral = std::array; - using Quadrilaterals = std::vector; - - inline explicit PDFAnnotationQuadrilaterals() = default; - inline explicit PDFAnnotationQuadrilaterals(QPainterPath&& path, Quadrilaterals&& quadrilaterals) : - m_path(qMove(path)), - m_quadrilaterals(qMove(quadrilaterals)) - { - - } - - const QPainterPath& getPath() const { return m_path; } - const Quadrilaterals& getQuadrilaterals() const { return m_quadrilaterals; } - bool isEmpty() const { return m_path.isEmpty(); } - -private: - QPainterPath m_path; - Quadrilaterals m_quadrilaterals; -}; - -/// Represents callout line (line from annotation to some point) -class PDFAnnotationCalloutLine -{ -public: - - enum class Type - { - Invalid, - StartEnd, - StartKneeEnd - }; - - inline explicit PDFAnnotationCalloutLine() = default; - inline explicit PDFAnnotationCalloutLine(QPointF start, QPointF end) : - m_type(Type::StartEnd), - m_points({start, end}) - { - - } - - inline explicit PDFAnnotationCalloutLine(QPointF start, QPointF knee, QPointF end) : - m_type(Type::StartKneeEnd), - m_points({start, knee, end}) - { - - } - - /// Parses annotation callout line from the object. If object is invalid, then - /// invalid callout line is constructed. - /// \param storage Object storage - /// \param object Callout line object - static PDFAnnotationCalloutLine parse(const PDFObjectStorage* storage, PDFObject object); - - bool isValid() const { return m_type != Type::Invalid; } - Type getType() const { return m_type; } - - QPointF getPoint(int index) const { return m_points.at(index); } - -private: - Type m_type = Type::Invalid; - std::array m_points; -}; - -/// Information about annotation icon fitting (in the widget) -class PDFAnnotationIconFitInfo -{ -public: - inline explicit PDFAnnotationIconFitInfo() = default; - - enum class ScaleCondition - { - Always, - ScaleBigger, - ScaleSmaller, - Never - }; - - enum class ScaleType - { - Anamorphic, ///< Do not keep aspect ratio, fit whole annotation rectangle - Proportional ///< Keep aspect ratio, annotation rectangle may not be filled fully with icon - }; - - /// Parses annotation appearance icon fit info from the object. If object is invalid, then - /// default appearance icon fit info is constructed. - /// \param storage Object storage - /// \param object Appearance icon fit info object - static PDFAnnotationIconFitInfo parse(const PDFObjectStorage* storage, PDFObject object); - -private: - ScaleCondition m_scaleCondition = ScaleCondition::Always; - ScaleType m_scaleType = ScaleType::Proportional; - QPointF m_relativeProportionalPosition = QPointF(0.5, 0.5); - bool m_fullBox = false; -}; - -/// Additional appearance characteristics used for constructing of appearance -/// stream to display annotation on the screen (or just paint it). -class PDFAnnotationAppearanceCharacteristics -{ -public: - inline explicit PDFAnnotationAppearanceCharacteristics() = default; - - enum class PushButtonMode - { - NoIcon, - NoCaption, - IconWithCaptionBelow, - IconWithCaptionAbove, - IconWithCaptionRight, - IconWithCaptionLeft, - IconWithCaptionOverlaid - }; - - /// Number of degrees by which the widget annotation is rotated - /// counterclockwise relative to the page. - PDFInteger getRotation() const { return m_rotation; } - const std::vector& getBorderColor() const { return m_borderColor; } - const std::vector& getBackgroundColor() const { return m_backgroundColor; } - const QString& getNormalCaption() const { return m_normalCaption; } - const QString& getRolloverCaption() const { return m_rolloverCaption; } - const QString& getDownCaption() const { return m_downCaption; } - const PDFObject& getNormalIcon() const { return m_normalIcon; } - const PDFObject& getRolloverIcon() const { return m_rolloverIcon; } - const PDFObject& getDownIcon() const { return m_downIcon; } - const PDFAnnotationIconFitInfo& getIconFit() const { return m_iconFit; } - PushButtonMode getPushButtonMode() const { return m_pushButtonMode; } - - /// Parses annotation appearance characteristics from the object. If object is invalid, then - /// default appearance characteristics is constructed. - /// \param storage Object storage - /// \param object Appearance characteristics object - static PDFAnnotationAppearanceCharacteristics parse(const PDFObjectStorage* storage, PDFObject object); - -private: - PDFInteger m_rotation = 0; - std::vector m_borderColor; - std::vector m_backgroundColor; - QString m_normalCaption; - QString m_rolloverCaption; - QString m_downCaption; - PDFObject m_normalIcon; - PDFObject m_rolloverIcon; - PDFObject m_downIcon; - PDFAnnotationIconFitInfo m_iconFit; - PushButtonMode m_pushButtonMode = PushButtonMode::NoIcon; -}; - -/// Storage for annotation additional actions -class PDFAnnotationAdditionalActions -{ -public: - - enum Action - { - CursorEnter, - CursorLeave, - MousePressed, - MouseReleased, - FocusIn, - FocusOut, - PageOpened, - PageClosed, - PageShow, - PageHide, - FormFieldModified, - FormFieldFormatted, - FormFieldValidated, - FormFieldCalculated, - Default, - End - }; - - inline explicit PDFAnnotationAdditionalActions() = default; - - /// Returns action for given type. If action is invalid, - /// or not present, nullptr is returned. - /// \param action Action type - const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } - - /// Returns array with all actions - const std::array& getActions() const { return m_actions; } - - /// Parses annotation additional actions from the object. If object is invalid, then - /// empty additional actions is constructed. - /// \param storage Object storage - /// \param object Additional actions object - /// \param defaultAction Default action of object (defined in "A" entry of annotation dictionary) - static PDFAnnotationAdditionalActions parse(const PDFObjectStorage* storage, PDFObject object, PDFObject defaultAction); - -private: - std::array m_actions; -}; - -/// Annotation default appearance -class PDFAnnotationDefaultAppearance -{ -public: - explicit inline PDFAnnotationDefaultAppearance() = default; - - /// Parses appearance string. If error occurs, then default appearance - /// string is constructed. - static PDFAnnotationDefaultAppearance parse(const QByteArray& string); - - const QByteArray& getFontName() const { return m_fontName; } - PDFReal getFontSize() const { return m_fontSize; } - QColor getFontColor() const { return m_fontColor; } - -private: - QByteArray m_fontName; - PDFReal m_fontSize = 8.0; - QColor m_fontColor = Qt::black; -}; - -class PDFAnnotation; -class PDFMarkupAnnotation; -class PDFTextAnnotation; - -using PDFAnnotationPtr = QSharedPointer; - -struct AnnotationDrawParameters -{ - /// Painter, onto which is annotation graphics drawn - QPainter* painter = nullptr; - - /// Pointer to annotation (if draw is delegated to other objects, - /// for example, form manager, then maybe pointer to annotation - /// is needed). - PDFAnnotation* annotation = nullptr; - - /// Pointer to form manager (if forms are drawn) - const PDFFormManager* formManager = nullptr; - - /// Output parameter. Marks annotation's graphics bounding - /// rectangle (it can be different/adjusted from original - /// annotation bounding rectangle, in that case, it must be adjusted). - /// If this rectangle is invalid, then appearance content stream - /// is assumed to be empty. - QRectF boundingRectangle; - - /// Appeareance mode (normal/rollover/down, and appearance state) - PDFAppeareanceStreams::Key key; - - /// Invert colors? - bool invertColors = false; -}; - -/// Base class for all annotation types. Represents PDF annotation object. -/// Annotations are various enhancements to pages graphical representation, -/// such as graphics, text, highlight or multimedia content, such as sounds, -/// videos and 3D annotations. -class PDFAnnotation -{ -public: - explicit PDFAnnotation(); - virtual ~PDFAnnotation() = default; - - enum Flag : uint - { - None = 0x0000, - Invisible = 0x0001, ///< If set, do not display unknown annotations using their AP dictionary - Hidden = 0x0002, ///< If set, do not display annotation and do not show popup windows (completely hidden) - Print = 0x0004, ///< If set, print annotation - NoZoom = 0x0008, ///< Do not apply page zoom while displaying annotation rectangle - NoRotate = 0x0010, ///< Do not rotate annotation's appearance to match orientation of the page - NoView = 0x0020, ///< Do not display annotation on the screen (it still can be printed) - ReadOnly = 0x0040, ///< Do not allow interacting with the user (and disallow also mouse interaction) - Locked = 0x0080, ///< Do not allow to delete/modify annotation by user - ToggleNoView = 0x0100, ///< If set, invert the interpretation of NoView flag - LockedContents = 0x0200, ///< Do not allow to modify contents of the annotation - }; - Q_DECLARE_FLAGS(Flags, Flag) - - virtual AnnotationType getType() const = 0; - - virtual PDFMarkupAnnotation* asMarkupAnnotation() { return nullptr; } - virtual const PDFMarkupAnnotation* asMarkupAnnotation() const { return nullptr; } - virtual bool isReplyTo() const { return false; } - - /// Draws the annotation using parameters. Annotation is drawn onto painter, - /// but actual graphics can be drawn outside of annotation's rectangle. - /// In that case, adjusted annotation's rectangle is passed to the parameters. - /// Painter must use annotation's coordinate system (for example, line points - /// must match in both in painter and this annotation). - /// \param parameters Graphics parameters - virtual void draw(AnnotationDrawParameters& parameters) const; - - /// Returns a list of appearance states, which must be created for this annotation - virtual std::vector getDrawKeys(const PDFFormManager* formManager) const; - - /// Returns effective flags (some annotations can behave as they have always - /// set some flags, such as NoZoom and NoRotate) - virtual Flags getEffectiveFlags() const { return getFlags(); } - - PDFObjectReference getSelfReference() const { return m_selfReference; } - const QRectF& getRectangle() const { return m_rectangle; } - const QString& getContents() const { return m_contents; } - PDFObjectReference getPageReference() const { return m_pageReference; } - const QString& getName() const { return m_name; } - const QDateTime& getLastModifiedDateTime() const { return m_lastModified; } - const QString& getLastModifiedString() const { return m_lastModifiedString; } - Flags getFlags() const { return m_flags; } - const PDFAppeareanceStreams& getAppearanceStreams() const { return m_appearanceStreams; } - const QByteArray& getAppearanceState() const { return m_appearanceState; } - const PDFAnnotationBorder& getBorder() const { return m_annotationBorder; } - const std::vector& getColor() const { return m_color; } - PDFInteger getStructuralParent() const { return m_structParent; } - PDFObjectReference getOptionalContent() const { return m_optionalContentReference; } - const PDFObject& getAssociatedFiles() const { return m_associatedFiles; } - PDFReal getFillOpacity() const { return m_fillingOpacity; } - PDFReal getStrokeOpacity() const { return m_strokingOpacity; } - BlendMode getBlendMode() const { return m_blendMode; } - const QString& getLanguage() const { return m_language; } - - void setSelfReference(const PDFObjectReference& selfReference); - void setRectangle(const QRectF& rectangle); - void setContents(const QString& contents); - void setPageReference(const PDFObjectReference& pageReference); - void setName(const QString& name); - void setLastModified(const QDateTime& lastModified); - void setLastModifiedString(const QString& lastModifiedString); - void setFlags(const Flags& flags); - void setAppearanceStreams(const PDFAppeareanceStreams& appearanceStreams); - void setAppearanceState(const QByteArray& appearanceState); - void setAnnotationBorder(const PDFAnnotationBorder& annotationBorder); - void setColor(const std::vector& color); - void setStructParent(const PDFInteger& structParent); - void setOptionalContentReference(const PDFObjectReference& optionalContentReference); - void setAssociatedFiles(const PDFObject& associatedFiles); - void setFillingOpacity(const PDFReal& fillingOpacity); - void setStrokingOpacity(const PDFReal& strokingOpacity); - void setBlendMode(const BlendMode& blendMode); - void setLanguage(const QString& language); - - /// Returns current composition mode. If blend mode is not supported by Qt, - /// then normal composition mode is returned. - QPainter::CompositionMode getCompositionMode() const; - - /// Parses annotation from the object. If error occurs, then nullptr is returned. - /// \param storage Object storage - /// \param reference Annotation object reference - static PDFAnnotationPtr parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - /// Parses quadrilaterals and fills them in the painter path. If no quadrilaterals are defined, - /// then annotation rectangle is used. If annotation rectangle is also invalid, - /// then empty painter path is used. - /// \param storage Object storage - /// \param quadrilateralsObject Object with quadrilaterals definition - /// \param annotationRect Annotation rectangle - static PDFAnnotationQuadrilaterals parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect); - - /// Converts name to line ending. If appropriate line ending for name is not found, - /// then None line ending is returned. - /// \param name Name of the line ending - static AnnotationLineEnding convertNameToLineEnding(const QByteArray& name); - - /// Converts line ending to name. - /// \param lineEnding Line ending - static QByteArray convertLineEndingToName(AnnotationLineEnding lineEnding); - - /// Returns draw color from defined annotation color. If color is incorrectly - /// defined, then black color is returned. - /// \param color Color (can have 1, 3 and 4 components) - /// \param opacity Opacity - static QColor getDrawColorFromAnnotationColor(const std::vector& color, PDFReal opacity); - - /// Returns true, if annotation is editable - /// \param type Annotation type - static bool isTypeEditable(AnnotationType type); - -protected: - virtual QColor getStrokeColor() const; - virtual QColor getFillColor() const; - - struct LineGeometryInfo - { - /// Original line - QLineF originalLine; - - /// Transformed line - QLineF transformedLine; - - /// Matrix LCStoGCS is local coordinate system of line originalLine. It transforms - /// points on the line to the global coordinate system. So, point (0, 0) will - /// map onto p1 and point (originalLine.length(), 0) will map onto p2. - QMatrix LCStoGCS; - - /// Inverted matrix of LCStoGCS. It maps global coordinate system to local - /// coordinate system of the original line. - QMatrix GCStoLCS; - - static LineGeometryInfo create(QLineF line); - }; - - /// Returns pen from border settings and annotation color - QPen getPen() const; - - /// Returns brush from interior color. If annotation doesn't have - /// a brush, then empty brush is returned. - QBrush getBrush() const; - - /// Draw line ending at given point, using parameters. Line ending appearance - /// is constructed given parameters \p lineEndingSize, \p arrowAxisLength - /// and \p ending. Parameter \p flipAxis controls, if we are drawing at the - /// start (false), or at the end (true) of the line. User can specify matrix, - /// which maps from local coordinate system of the line to the global coordinate - /// system. Also, bouding path is updated. - /// \param painter Painter - /// \param point Point, at which line ending is being drawn - /// \param lineEndingSize Line ending size - /// \param arrowAxisLength Length of the arrow - /// \param ending Type of ending - /// \param flipAxis Flip axis to draw end of line? - /// \param LCStoGCS Transformation from local coordinate system of line to global coordinate system - /// \pram boundingPath Bounding path to be updated - void drawLineEnding(QPainter* painter, - QPointF point, - PDFReal lineEndingSize, - PDFReal arrowAxisLength, - AnnotationLineEnding ending, - bool flipAxis, - const QMatrix& LCStoGCS, - QPainterPath& boundingPath) const; - - /// Draw line using given parameters and painter. Line is specified - /// by its geometry information. Painter must be set to global coordinates. - /// Bounding path is also updated, it is specified in global coordinates, - /// not in line local coordinate system. We consider p1 as start point of - /// the line, and p2 as the end point. The painter must have proper QPen - /// and QBrush setted, this function uses current pen/brush to paint the line. - /// \param info Line geometry info - /// \param painter Painter - /// \param lineEndingSize Line ending size - /// \param p1Ending Line endpoint graphics at p1 - /// \param p2Ending Line endpoint graphics at p2 - /// \param boundingPath Bounding path in global coordinate system - /// \param textOffset Additional text offset - /// \param text Text, which should be printed along the line - /// \param textIsAboveLine Text should be printed above line - void drawLine(const LineGeometryInfo& info, - QPainter& painter, - PDFReal lineEndingSize, - AnnotationLineEnding p1Ending, - AnnotationLineEnding p2Ending, - QPainterPath& boundingPath, - QPointF textOffset, - const QString& text, - bool textIsAboveLine) const; - - /// Draws character unicode symbol using text - /// \param text Text to be drawn - /// \param opacity Opacity - /// \param parameters Draw parameters - void drawCharacterSymbol(QString text, PDFReal opacity, AnnotationDrawParameters& parameters) const; - - /// Parses path. If path is incorrect, empty path is returned. - /// \param storage Storage - /// \param dictionary Annotation's dictionary - /// \param closePath Close path when finishing? - static QPainterPath parsePath(const PDFObjectStorage* storage, const PDFDictionary* dictionary, bool closePath); - -private: - PDFObjectReference m_selfReference; ///< Reference to self - QRectF m_rectangle; ///< Annotation rectangle, in page coordinates, "Rect" entry - QString m_contents; ///< Text to be displayed to the user (or alternate text), "Content" entry - PDFObjectReference m_pageReference; ///< Reference to annotation's page, "P" entry - QString m_name; ///< Unique name (in page context) for the annotation, "NM" entry - QDateTime m_lastModified; ///< Date and time, when annotation was last modified, "M" entry - QString m_lastModifiedString; ///< Date and time, in text format - Flags m_flags; ///< Annotation flags - PDFAppeareanceStreams m_appearanceStreams; ///< Appearance streams, "AP" entry - QByteArray m_appearanceState; ///< Appearance state, "AS" entry - PDFAnnotationBorder m_annotationBorder; ///< Annotation border, "Border" entry - std::vector m_color; ///< Color (for example, title bar of popup window), "C" entry - PDFInteger m_structParent; ///< Structural parent identifier, "StructParent" entry - PDFObjectReference m_optionalContentReference; ///< Reference to optional content, "OC" entry - PDFObject m_associatedFiles; - PDFReal m_fillingOpacity = 1.0; - PDFReal m_strokingOpacity = 1.0; - BlendMode m_blendMode = BlendMode::Normal; - QString m_language; -}; - -/// Markup annotation object, used to mark up contents of PDF documents. Markup annotations -/// can have various types, as free text (just text displayed on page), annotations with popup -/// windows, and special annotations, such as multimedia annotations. -class PDFMarkupAnnotation : public PDFAnnotation -{ -public: - explicit inline PDFMarkupAnnotation() = default; - - virtual PDFMarkupAnnotation* asMarkupAnnotation() override { return this; } - virtual const PDFMarkupAnnotation* asMarkupAnnotation() const override { return this; } - virtual bool isReplyTo() const override; - - enum class ReplyType - { - Reply, - Group - }; - - const QString& getWindowTitle() const { return m_windowTitle; } - PDFObjectReference getPopupAnnotation() const { return m_popupAnnotation; } - const QString& getRichTextString() const { return m_richTextString; } - const QDateTime& getCreationDate() const { return m_creationDate; } - PDFObjectReference getInReplyTo() const { return m_inReplyTo; } - const QString& getSubject() const { return m_subject; } - ReplyType getReplyType() const { return m_replyType; } - const QByteArray& getIntent() const { return m_intent; } - const PDFObject& getExternalData() const { return m_externalData; } - - void setWindowTitle(const QString& windowTitle); - void setPopupAnnotation(const PDFObjectReference& popupAnnotation); - void setRichTextString(const QString& richTextString); - void setCreationDate(const QDateTime& creationDate); - void setInReplyTo(PDFObjectReference inReplyTo); - void setSubject(const QString& subject); - void setReplyType(ReplyType replyType); - void setIntent(const QByteArray& intent); - void setExternalData(const PDFObject& externalData); - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QString m_windowTitle; - PDFObjectReference m_popupAnnotation; - QString m_richTextString; - QDateTime m_creationDate; - PDFObjectReference m_inReplyTo; - QString m_subject; - ReplyType m_replyType = ReplyType::Reply; - QByteArray m_intent; - PDFObject m_externalData; -}; - -enum class TextAnnotationIcon -{ - Comment, - Help, - Insert, - Key, - NewParagraph, - Note, - Paragraph -}; - -/// Text annotation represents note attached to a specific point in the PDF -/// document. It appears as icon, and it is not zoomed, or rotated (behaves -/// as if flag NoZoom and NoRotate were set). When this annotation is opened, -/// it displays popup window containing the text of the note, font and size -/// is implementation dependent by viewer application. -class PDF4QTLIBSHARED_EXPORT PDFTextAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFTextAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Text; } - virtual std::vector getDrawKeys(const PDFFormManager* formManager) const override; - virtual void draw(AnnotationDrawParameters& parameters) const override; - virtual Flags getEffectiveFlags() const override; - - bool isOpen() const { return m_open; } - const QByteArray& getIconName() const { return m_iconName; } - const QString& getState() const { return m_state; } - const QString& getStateModel() const { return m_stateModel; } - - static QIcon createIcon(QString key, QSize size); - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - static QString getTextForIcon(const QString& key); - - bool m_open = false; - QByteArray m_iconName; - QString m_state; - QString m_stateModel; -}; - -enum class LinkHighlightMode -{ - None, - Invert, - Outline, - Push -}; - -/// Link annotation represents hypertext link to a destination to elsewhere -/// in the document, or action to be performed. -class PDFLinkAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFLinkAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Link; } - virtual std::vector getDrawKeys(const PDFFormManager* formManager) const override; - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const PDFAction* getAction() const { return m_action.data(); } - LinkHighlightMode getHighlightMode() const { return m_highlightMode; } - const PDFAction* getURIAction() const { return m_previousAction.data(); } - const PDFAnnotationQuadrilaterals& getActivationRegion() const { return m_activationRegion; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - PDFActionPtr m_action; - LinkHighlightMode m_highlightMode = LinkHighlightMode::Invert; - PDFActionPtr m_previousAction; - PDFAnnotationQuadrilaterals m_activationRegion; -}; - -/// Free text annotation displays text directly on the page. Free text doesn't have -/// open/close state, text is always visible. -class PDFFreeTextAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFFreeTextAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::FreeText; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - enum class Justification - { - Left, - Centered, - Right - }; - - enum class Intent - { - None, - Callout, - TypeWriter - }; - - const QByteArray& getDefaultAppearance() const { return m_defaultAppearance; } - Justification getJustification() const { return m_justification; } - const QString& getDefaultStyle() const { return m_defaultStyleString; } - const PDFAnnotationCalloutLine& getCalloutLine() const { return m_calloutLine; } - Intent getIntent() const { return m_intent; } - const QRectF& getTextRectangle() const { return m_textRectangle; } - const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } - AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } - AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QByteArray m_defaultAppearance; - Justification m_justification = Justification::Left; - QString m_defaultStyleString; - PDFAnnotationCalloutLine m_calloutLine; - Intent m_intent = Intent::None; - QRectF m_textRectangle; - PDFAnnotationBorderEffect m_effect; - AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; - AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; -}; - -/// Line annotation, draws straight line on the page (in most simple form), or -/// it can display, for example, dimensions with perpendicular lines at the line -/// endings. Caption text can also be displayed. -class PDFLineAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFLineAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Line; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - enum class Intent - { - Arrow, - Dimension - }; - - enum class CaptionPosition - { - Inline, - Top - }; - - const QLineF& getLine() const { return m_line; } - AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } - AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } - const std::vector& getInteriorColor() const { return m_interiorColor; } - PDFReal getLeaderLineLength() const { return m_leaderLineLength; } - PDFReal getLeaderLineExtension() const { return m_leaderLineExtension; } - PDFReal getLeaderLineOffset() const { return m_leaderLineOffset; } - bool isCaptionRendered() const { return m_captionRendered; } - Intent getIntent() const { return m_intent; } - CaptionPosition getCaptionPosition() const { return m_captionPosition; } - const PDFObject& getMeasureDictionary() const { return m_measureDictionary; } - const QPointF& getCaptionOffset() const { return m_captionOffset; } - -protected: - virtual QColor getFillColor() const override; - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QLineF m_line; - AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; - AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; - std::vector m_interiorColor; - PDFReal m_leaderLineLength = 0.0; - PDFReal m_leaderLineExtension = 0.0; - PDFReal m_leaderLineOffset = 0.0; - bool m_captionRendered = false; - Intent m_intent = Intent::Arrow; - CaptionPosition m_captionPosition = CaptionPosition::Inline; - PDFObject m_measureDictionary; - QPointF m_captionOffset; -}; - -/// Simple geometry annotation. -/// Square and circle annotations displays rectangle or ellipse on the page. -/// Name is a bit strange (because rectangle may not be a square or circle is not ellipse), -/// but it is defined in PDF specification, so we will use these terms. -class PDFSimpleGeometryAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFSimpleGeometryAnnotation(AnnotationType type) : - m_type(type) - { - - } - - virtual AnnotationType getType() const override { return m_type; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const std::vector& getInteriorColor() const { return m_interiorColor; } - const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } - const QRectF& getGeometryRectangle() const { return m_geometryRectangle; } - -protected: - virtual QColor getFillColor() const override; - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - AnnotationType m_type; - std::vector m_interiorColor; - PDFAnnotationBorderEffect m_effect; - QRectF m_geometryRectangle; -}; - -/// Polygonal geometry, consists of polygon or polyline geometry. Polygon annotation -/// displays closed polygon (potentially filled), polyline annotation displays -/// polyline, which is not closed. -class PDFPolygonalGeometryAnnotation : public PDFMarkupAnnotation -{ -public: - enum class Intent - { - None, - Cloud, - Dimension - }; - - inline explicit PDFPolygonalGeometryAnnotation(AnnotationType type) : - m_type(type), - m_intent(Intent::None) - { - - } - - virtual AnnotationType getType() const override { return m_type; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const std::vector& getVertices() const { return m_vertices; } - AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } - AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } - const std::vector& getInteriorColor() const { return m_interiorColor; } - const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } - Intent getIntent() const { return m_intent; } - const PDFObject& getMeasure() const { return m_measure; } - const QPainterPath& getPath() const { return m_path; } - -protected: - virtual QColor getFillColor() const override; - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - AnnotationType m_type; - std::vector m_vertices; - AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; - AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; - std::vector m_interiorColor; - PDFAnnotationBorderEffect m_effect; - Intent m_intent; - PDFObject m_measure; - QPainterPath m_path; -}; - -/// Annotation for text highlighting. Can highlight, underline, strikeout, -/// or squiggly underline the text. -class PDFHighlightAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFHighlightAnnotation(AnnotationType type) : - m_type(type) - { - - } - - virtual AnnotationType getType() const override { return m_type; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const PDFAnnotationQuadrilaterals& getHiglightArea() const { return m_highlightArea; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - AnnotationType m_type; - PDFAnnotationQuadrilaterals m_highlightArea; -}; - -/// Annotation for visual symbol that indicates presence of text edits. -class PDFCaretAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFCaretAnnotation() = default; - - enum class Symbol - { - None, - Paragraph - }; - - virtual AnnotationType getType() const override { return AnnotationType::Caret; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const QRectF& getCaretRectangle() const { return m_caretRectangle; } - Symbol getSymbol() const { return m_symbol; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QRectF m_caretRectangle; - Symbol m_symbol = Symbol::None; -}; - -enum class Stamp -{ - Approved, - AsIs, - Confidential, - Departmental, - Draft, - Experimental, - Expired, - Final, - ForComment, - ForPublicRelease, - NotApproved, - NotForPublicRelease, - Sold, - TopSecret -}; - -enum class StampIntent -{ - Stamp, - StampImage, - StampSnapshot -}; - -/// Annotation for stamps. Displays text or graphics intended to look -/// as if they were stamped on the paper. -class PDF4QTLIBSHARED_EXPORT PDFStampAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFStampAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Stamp; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - Stamp getStamp() const { return m_stamp; } - StampIntent getIntent() const { return m_intent; } - - void setStamp(const Stamp& stamp); - void setIntent(const StampIntent& intent); - - static QString getText(Stamp stamp); - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - Stamp m_stamp = Stamp::Draft; - StampIntent m_intent = StampIntent::Stamp; -}; - -/// Ink annotation. Represents a path composed of disjoint polygons. -class PDFInkAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFInkAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Ink; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const QPainterPath& getInkPath() const { return m_inkPath; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QPainterPath m_inkPath; -}; - -/// Popup annotation. Displays text in popup window for markup annotations. -/// This annotation contains field to associated annotation, for which -/// is window displayed, and window state (open/closed). -class PDFPopupAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFPopupAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Popup; } - - bool isOpened() const { return m_opened; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - bool m_opened = false; -}; - -enum class FileAttachmentIcon -{ - Graph, - Paperclip, - PushPin, - Tag -}; - -/// File attachment annotation contains reference to (embedded or external) file. -/// So it is a link to specified file. Activating annotation enables user to view -/// or store attached file in the filesystem. -class PDFFileAttachmentAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFFileAttachmentAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::FileAttachment; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } - FileAttachmentIcon getIcon() const { return m_icon; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - PDFFileSpecification m_fileSpecification; - FileAttachmentIcon m_icon = FileAttachmentIcon::PushPin; -}; - -/// Sound annotation contains sound, which is played, when -/// annotation is activated. -class PDFSoundAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFSoundAnnotation() = default; - - enum class Icon - { - Speaker, - Microphone - }; - - virtual AnnotationType getType() const override { return AnnotationType::Sound; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const PDFSound& getSound() const { return m_sound; } - Icon getIcon() const { return m_icon; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - PDFSound m_sound; - Icon m_icon = Icon::Speaker; -}; - -/// Movie annotation contains movie or sound, which is played, when -/// annotation is activated. -class PDFMovieAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFMovieAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Movie; } - - const QString& getMovieTitle() const { return m_movieTitle; } - bool isMovieToBePlayed() const { return m_playMovie; } - const PDFMovie& getMovie() const { return m_movie; } - const PDFMovieActivation& getMovieActivation() const { return m_movieActivation; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QString m_movieTitle; - bool m_playMovie = true; - PDFMovie m_movie; - PDFMovieActivation m_movieActivation; -}; - -/// Screen action represents area of page in which is media played. -/// See also Rendition actions and their relationship to this annotation. -class PDFScreenAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFScreenAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Screen; } - - const QString& getScreenTitle() const { return m_screenTitle; } - const PDFAnnotationAppearanceCharacteristics& getAppearanceCharacteristics() const { return m_appearanceCharacteristics; } - const PDFAction* getAction() const { return m_action.get(); } - const PDFAnnotationAdditionalActions& getAdditionalActions() const { return m_additionalActions; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QString m_screenTitle; - PDFAnnotationAppearanceCharacteristics m_appearanceCharacteristics; - PDFActionPtr m_action; - PDFAnnotationAdditionalActions m_additionalActions; -}; - -/// Widget annotation represents form fields for interactive forms. -/// Annotation's dictionary is merged with form field dictionary, -/// it can be done, because dictionaries doesn't overlap. -class PDFWidgetAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFWidgetAnnotation() = default; - - enum class HighlightMode - { - None, - Invert, - Outline, - Push, - Toggle - }; - - virtual AnnotationType getType() const override { return AnnotationType::Widget; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - virtual std::vector getDrawKeys(const PDFFormManager* formManager) const override; - - HighlightMode getHighlightMode() const { return m_highlightMode; } - const PDFAnnotationAppearanceCharacteristics& getAppearanceCharacteristics() const { return m_appearanceCharacteristics; } - const PDFAction* getAction() const { return m_action.get(); } - const PDFAnnotationAdditionalActions& getAdditionalActions() const { return m_additionalActions; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - HighlightMode m_highlightMode = HighlightMode::Invert; - PDFAnnotationAppearanceCharacteristics m_appearanceCharacteristics; - PDFActionPtr m_action; - PDFAnnotationAdditionalActions m_additionalActions; -}; - -/// Printer mark annotation represents graphics symbol, mark, or other -/// graphic feature to assist printing production. -class PDFPrinterMarkAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFPrinterMarkAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::PrinterMark; } -}; - -/// Trapping characteristics for the page -class PDFTrapNetworkAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFTrapNetworkAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::TrapNet; } -}; - -/// Watermark annotation represents watermark displayed on the page, -/// for example, if it is printed. Watermarks are displayed at fixed -/// position and size on the page. -class PDFWatermarkAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFWatermarkAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Watermark; } - - const QMatrix& getMatrix() const { return m_matrix; } - PDFReal getRelativeHorizontalOffset() const { return m_relativeHorizontalOffset; } - PDFReal getRelativeVerticalOffset() const { return m_relativeVerticalOffset; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - QMatrix m_matrix; - PDFReal m_relativeHorizontalOffset = 0.0; - PDFReal m_relativeVerticalOffset = 0.0; -}; - -/// Redaction annotation represents content selection, which should -/// be removed from the document. -class PDFRedactAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFRedactAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Redact; } - virtual void draw(AnnotationDrawParameters& parameters) const override; - - const PDFAnnotationQuadrilaterals& getRedactionRegion() const { return m_redactionRegion; } - const std::vector& getInteriorColor() const { return m_interiorColor; } - const PDFObject& getOverlay() const { return m_overlayForm; } - const QString& getOverlayText() const { return m_overlayText; } - bool isRepeat() const { return m_repeat; } - const QByteArray& getDefaultAppearance() const { return m_defaultAppearance; } - PDFInteger getJustification() const { return m_justification; } - -protected: - virtual QColor getFillColor() const override; - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - PDFAnnotationQuadrilaterals m_redactionRegion; - std::vector m_interiorColor; - PDFObject m_overlayForm; ///< Overlay form object - QString m_overlayText; - bool m_repeat = false; - QByteArray m_defaultAppearance; - PDFInteger m_justification = 0; -}; - -class PDFProjectionAnnotation : public PDFMarkupAnnotation -{ -public: - inline explicit PDFProjectionAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::Projection; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); -}; - -/// 3D annotations represents 3D scene, which can be viewed in the application. -class PDF3DAnnotation : public PDFAnnotation -{ -public: - inline explicit PDF3DAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::_3D; } - - const PDF3DStream& getStream() const { return m_stream; } - const std::optional& getDefaultView() const { return m_defaultView; } - const PDF3DActivation& getActivation() const { return m_activation; } - bool isInteractive() const { return m_interactive; } - QRectF getViewBox() const { return m_viewBox; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - PDF3DStream m_stream; - std::optional m_defaultView; - PDF3DActivation m_activation; - bool m_interactive = true; - QRectF m_viewBox; -}; - -/// Rich media annotations can be video, audio, or other multimedia presentations. -/// The application should provide additional functionality to control rich media, -/// such as buttons to play/pause/stop video etc. This annotation consists -/// of contents and settings, settings are optional. -class PDFRichMediaAnnotation : public PDFAnnotation -{ -public: - inline explicit PDFRichMediaAnnotation() = default; - - virtual AnnotationType getType() const override { return AnnotationType::RichMedia; } - - const PDFRichMediaContent* getContent() const { return &m_content; } - const PDFRichMediaSettings* getSettings() const { return &m_settings; } - -private: - friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); - - PDFRichMediaContent m_content; - PDFRichMediaSettings m_settings; -}; - -/// Annotation manager manages annotations for document's pages. Each page -/// can have multiple annotations, and this object caches them. Also, -/// this object builds annotation's appearance streams, if necessary. This -/// manager is intended to non-gui rendering. If widget annotation manager is used, -/// then this object is not thread safe. -class PDF4QTLIBSHARED_EXPORT PDFAnnotationManager : public QObject, public IDocumentDrawInterface -{ - Q_OBJECT - -private: - using BaseClass = QObject; - - void drawWidgetAnnotationHighlight(QRectF annotationRectangle, PDFObjectReference annotationReference, QPainter* painter, QMatrix userSpaceToDeviceSpace) const; - -public: - - enum class Target - { - View, - Print - }; - - explicit PDFAnnotationManager(PDFFontCache* fontCache, - const PDFCMSManager* cmsManager, - const PDFOptionalContentActivity* optionalActivity, - PDFMeshQualitySettings meshQualitySettings, - PDFRenderer::Features features, - Target target, - QObject* parent); - virtual ~PDFAnnotationManager() override; - - virtual void drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const override; - - /// Set document - /// \param document New document - virtual void setDocument(const PDFModifiedDocument& document); - - Target getTarget() const; - void setTarget(Target target); - - const PDFOptionalContentActivity* getOptionalActivity() const; - void setOptionalActivity(const PDFOptionalContentActivity* optionalActivity); - - PDFFontCache* getFontCache() const; - void setFontCache(PDFFontCache* fontCache); - - PDFMeshQualitySettings getMeshQualitySettings() const; - void setMeshQualitySettings(const PDFMeshQualitySettings& meshQualitySettings); - - PDFRenderer::Features getFeatures() const; - void setFeatures(PDFRenderer::Features features); - - PDFFormManager* getFormManager() const; - void setFormManager(PDFFormManager* formManager); - - struct PageAnnotation - { - PDFAppeareanceStreams::Appearance appearance = PDFAppeareanceStreams::Appearance::Normal; - PDFAnnotationPtr annotation; - bool isHovered = false; - - /// This mutable appearance stream is protected by main mutex - mutable PDFCachedItem appearanceStream; - }; - - struct PageAnnotations - { - bool isEmpty() const { return annotations.empty(); } - - /// Returns popup annotation for given annotation, if annotation doesn't have - /// popup annotation attached, then nullptr is returned. - /// \param pageAnnotation Page annotation - /// \returns Popup annotation or nullptr - const PageAnnotation* getPopupAnnotation(const PageAnnotation& pageAnnotation) const; - - /// Returns a list of page annotations, which are replies to this page annotation. - /// If page annotation doesn't have any replies, then empty list is returned. Replies - /// are returned in order, in which they were created. - /// \param pageAnnotation Page annotation - /// \returns List of replies - std::vector getReplies(const PageAnnotation& pageAnnotation) const; - - std::vector annotations; - }; - - /// Prepares annotation transformations for rendering - /// \param pagePointToDevicePointMatrix Page point to device point matrix - /// \param device Paint device, onto which will be annotation rendered - /// \param annotationFlags Annotation flags - /// \param page Page - /// \param[in,out] annotationRectangle Input/output annotation rectangle - QMatrix prepareTransformations(const QMatrix& pagePointToDevicePointMatrix, - QPaintDevice* device, - const PDFAnnotation::Flags annotationFlags, - const PDFPage* page, - QRectF& annotationRectangle) const; - - /// Returns current appearance stream for given page annotation - /// \param pageAnnotation Page annotation - PDFObject getAppearanceStream(const PageAnnotation& pageAnnotation) const; - - /// Returns constant reference to page annotation for given page index. - /// This function requires, that pointer to m_document is valid. - /// \param pageIndex Page index (must point to valid page) - const PageAnnotations& getPageAnnotations(PDFInteger pageIndex) const; - - /// Returns reference to page annotation for given page index. - /// This function requires, that pointer to m_document is valid. - /// \param pageIndex Page index (must point to valid page) - PageAnnotations& getPageAnnotations(PDFInteger pageIndex); - - /// Returns true, if given page has any annotation - bool hasAnnotation(PDFInteger pageIndex) const; - - /// Returns true, if any page in the given indices has annotation - bool hasAnyPageAnnotation(const std::vector& pageIndices) const; - -protected: - void drawWidgetAnnotationHighlight(QRectF annotationRectangle, - const PDFAnnotation* annotation, - QPainter* painter, - QMatrix userSpaceToDeviceSpace) const; - - /// Returns true, if given annotation should be drawn - /// \param annotation Annotation - bool isAnnotationDrawEnabled(const PageAnnotation& annotation) const; - - /// Returns true, if annotation is drawn by editor (from form widget) - /// \param annotation Annotation - bool isAnnotationDrawnByEditor(const PageAnnotation& annotation) const; - - /// Draws annotation - /// \param pageAnnotation Page annotation - /// \param pagePointToDevicePointMatrix Page point to device point matrix - /// \param page Page - /// \param cms Color management system - /// \param isEditorDrawEnabled Is editor draw enabled? - /// \param errors Errors list (where draw errors are stored) - /// \param painter Painter - void drawAnnotation(const PageAnnotation& annotation, - const QMatrix& pagePointToDevicePointMatrix, - const PDFPage* page, - const PDFCMS* cms, - bool isEditorDrawEnabled, - QList& errors, - QPainter* painter) const; - - /// Draws annotation by direct drawing, not using annotation's - /// appearance stream. - /// \param pageAnnotation Page annotation - /// \param pagePointToDevicePointMatrix Page point to device point matrix - /// \param page Page - /// \param cms Color management system - /// \param isEditorDrawEnabled Is annotation drawn by form widget editor? - /// \param painter Painter - void drawAnnotationDirect(const PageAnnotation& annotation, - const QMatrix& pagePointToDevicePointMatrix, - const PDFPage* page, - const PDFCMS* cms, - bool isEditorDrawEnabled, - QPainter* painter) const; - - /// Draws annotation using annotation's appearance stream. - /// \param pageAnnotation Page annotation - /// \param appearanceStreamObject Object with appearance stream - /// \param pagePointToDevicePointMatrix Page point to device point matrix - /// \param page Page - /// \param cms Color management system - /// \param painter Painter - void drawAnnotationUsingAppearanceStream(const PageAnnotation& annotation, - const PDFObject& appearanceStreamObject, - const QMatrix& pagePointToDevicePointMatrix, - const PDFPage* page, - const PDFCMS* cms, - QPainter* painter) const; - - const PDFDocument* m_document; - - PDFFontCache* m_fontCache; - const PDFCMSManager* m_cmsManager; - const PDFOptionalContentActivity* m_optionalActivity; - PDFFormManager* m_formManager; - PDFMeshQualitySettings m_meshQualitySettings; - PDFRenderer::Features m_features; - - mutable QMutex m_mutex; - mutable std::map m_pageAnnotations; - Target m_target = Target::View; -}; - -/// Annotation manager for GUI rendering, it also manages annotations widgets -/// for parent widget. -class PDF4QTLIBSHARED_EXPORT PDFWidgetAnnotationManager : public PDFAnnotationManager, public IDrawWidgetInputInterface -{ - Q_OBJECT - -private: - using BaseClass = PDFAnnotationManager; - -public: - explicit PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent); - virtual ~PDFWidgetAnnotationManager() override; - - virtual void setDocument(const PDFModifiedDocument& document) override; - - virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; - virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; - - /// Returns tooltip generated from annotation - virtual QString getTooltip() const override { return m_tooltip; } - - /// Returns current cursor - virtual const std::optional& getCursor() const override { return m_cursor; } - - virtual int getInputPriority() const override { return AnnotationPriority; } - -signals: - void actionTriggered(const PDFAction* action); - void documentModified(PDFModifiedDocument document); - -private: - void updateFromMouseEvent(QMouseEvent* event); - - void onShowPopupAnnotation(); - void onCopyAnnotation(); - void onEditAnnotation(); - void onDeleteAnnotation(); - - /// Creates dialog for markup annotations. This function is used only for markup annotations, - /// do not use them for other annotations (function can crash). - /// \param widget Dialog's parent widget - /// \param pageAnnotation Markup annotation - /// \param pageAnnotations Page annotations - QDialog* createDialogForMarkupAnnotations(PDFWidget* widget, - const PageAnnotation& pageAnnotation, - const PageAnnotations& pageAnnotations); - - /// Creates widgets for markup annotation main popup widget. Also sets - /// default size of parent widget. - /// \param parentWidget Parent widget, where widgets are created - /// \param pageAnnotation Markup annotation - /// \param pageAnnotations Page annotations - void createWidgetsForMarkupAnnotations(QWidget* parentWidget, - const PageAnnotation& pageAnnotation, - const PageAnnotations& pageAnnotations); - - PDFDrawWidgetProxy* m_proxy; - QString m_tooltip; - std::optional m_cursor; - QPoint m_editableAnnotationGlobalPosition; ///< Position, where action on annotation was executed - PDFObjectReference m_editableAnnotation; ///< Annotation to be edited or deleted - PDFObjectReference m_editableAnnotationPage; ///< Page of annotation above -}; - -} // namespace pdf - -#endif // PDFANNOTATION_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFANNOTATION_H +#define PDFANNOTATION_H + +#include "pdfutils.h" +#include "pdfobject.h" +#include "pdfaction.h" +#include "pdffile.h" +#include "pdfcms.h" +#include "pdfmultimedia.h" +#include "pdfmeshqualitysettings.h" +#include "pdfdocumentdrawinterface.h" +#include "pdfrenderer.h" +#include "pdfblendfunction.h" +#include "pdfdocument.h" + +#include +#include + +#include + +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +namespace pdf +{ +class PDFWidget; +class PDFObjectStorage; +class PDFDrawWidgetProxy; +class PDFFontCache; +class PDFFormManager; +class PDFModifiedDocument; +class PDFOptionalContentActivity; +class PDFFormFieldWidgetEditor; +class PDFModifiedDocument; + +using TextAlignment = Qt::Alignment; +using Polygons = std::vector; + +enum class AnnotationType +{ + Invalid, + Text, + Link, + FreeText, + Line, + Square, + Circle, + Polygon, + Polyline, + Highlight, + Underline, + Squiggly, + StrikeOut, + Stamp, + Caret, + Ink, + Popup, + FileAttachment, + Sound, + Movie, + Widget, + Screen, + PrinterMark, + TrapNet, + Watermark, + Redact, + Projection, + _3D, + RichMedia +}; + +enum class AnnotationLineEnding +{ + None, + Square, + Circle, + Diamond, + OpenArrow, + ClosedArrow, + Butt, + ROpenArrow, + RClosedArrow, + Slash +}; + +/// Represents annotation's border. Two main definition exists, one is older, +/// called \p Simple, the other one is defined in BS dictionary of the annotation. +class PDFAnnotationBorder +{ +public: + explicit inline PDFAnnotationBorder() = default; + + enum class Definition + { + Invalid, + Simple, + BorderStyle + }; + + enum class Style + { + Solid, + Dashed, + Beveled, + Inset, + Underline + }; + + /// Parses the annotation border from the array. If object contains invalid annotation border, + /// then default annotation border is returned. If object is empty, empty annotation border is returned. + /// \param storage Object storage + /// \param object Border object + static PDFAnnotationBorder parseBorder(const PDFObjectStorage* storage, PDFObject object); + + /// Parses the annotation border from the BS dictionary. If object contains invalid annotation border, + /// then default annotation border is returned. If object is empty, empty annotation border is returned. + /// \param storage Object storage + /// \param object Border object + static PDFAnnotationBorder parseBS(const PDFObjectStorage* storage, PDFObject object); + + /// Returns true, if object is correctly defined + bool isValid() const { return m_definition != Definition::Invalid; } + + Definition getDefinition() const { return m_definition; } + Style getStyle() const { return m_style; } + PDFReal getHorizontalCornerRadius() const { return m_hCornerRadius; } + PDFReal getVerticalCornerRadius() const { return m_vCornerRadius; } + PDFReal getWidth() const { return m_width; } + const std::vector& getDashPattern() const { return m_dashPattern; } + +private: + Definition m_definition = Definition::Invalid; + Style m_style = Style::Solid; + PDFReal m_hCornerRadius = 0.0; + PDFReal m_vCornerRadius = 0.0; + PDFReal m_width = 1.0; + std::vector m_dashPattern; +}; + +using AnnotationBorderStyle = PDFAnnotationBorder::Style; + +/// Annotation border effect +class PDFAnnotationBorderEffect +{ +public: + explicit inline PDFAnnotationBorderEffect() = default; + + enum class Effect + { + None, + Cloudy + }; + + /// Parses the annotation border effect from the object. If object contains invalid annotation border effect, + /// then default annotation border effect is returned. If object is empty, also default annotation border effect is returned. + /// \param storage Object storage + /// \param object Border effect object + static PDFAnnotationBorderEffect parse(const PDFObjectStorage* storage, PDFObject object); + +private: + Effect m_effect = Effect::None; + PDFReal m_intensity = 0.0; +}; + +/// Storage which handles appearance streams of annotations. Appeareance streams are divided +/// to three main categories - normal, rollower and down. Each category can have different +/// states, for example, checkbox can have on/off state. This container can also resolve +/// queries to obtain appropriate appearance stream. +class PDFAppeareanceStreams +{ +public: + explicit inline PDFAppeareanceStreams() = default; + + enum class Appearance + { + Normal, + Rollover, + Down + }; + + using Key = std::pair; + + /// Parses annotation appearance streams from the object. If object is invalid, then + /// empty appearance stream is constructed. + /// \param storage Object storage + /// \param object Appearance streams object + static PDFAppeareanceStreams parse(const PDFObjectStorage* storage, PDFObject object); + + /// Tries to search for appearance stream for given appearance. If no appearance is found, + /// then null object is returned. + /// \param appearance Appearance type + PDFObject getAppearance(Appearance appearance = Appearance::Normal) const { return getAppearance(appearance, QByteArray()); } + + /// Tries to resolve appearance stream for given appearance and state. If no appearance is found, + /// then null object is returned. + /// \param appearance Appearance type + /// \param state State name + PDFObject getAppearance(Appearance appearance, const QByteArray& state) const; + + /// Returns list of appearance states for given appearance + /// \param appearance Appearance + QByteArrayList getAppearanceStates(Appearance appearance) const; + + /// Returns list of appearance keys + std::vector getAppearanceKeys() const; + +private: + std::map m_appearanceStreams; +}; + +/// Represents annotation's active region, it is used also to +/// determine underline lines. +class PDFAnnotationQuadrilaterals +{ +public: + using Quadrilateral = std::array; + using Quadrilaterals = std::vector; + + inline explicit PDFAnnotationQuadrilaterals() = default; + inline explicit PDFAnnotationQuadrilaterals(QPainterPath&& path, Quadrilaterals&& quadrilaterals) : + m_path(qMove(path)), + m_quadrilaterals(qMove(quadrilaterals)) + { + + } + + const QPainterPath& getPath() const { return m_path; } + const Quadrilaterals& getQuadrilaterals() const { return m_quadrilaterals; } + bool isEmpty() const { return m_path.isEmpty(); } + +private: + QPainterPath m_path; + Quadrilaterals m_quadrilaterals; +}; + +/// Represents callout line (line from annotation to some point) +class PDFAnnotationCalloutLine +{ +public: + + enum class Type + { + Invalid, + StartEnd, + StartKneeEnd + }; + + inline explicit PDFAnnotationCalloutLine() = default; + inline explicit PDFAnnotationCalloutLine(QPointF start, QPointF end) : + m_type(Type::StartEnd), + m_points({start, end}) + { + + } + + inline explicit PDFAnnotationCalloutLine(QPointF start, QPointF knee, QPointF end) : + m_type(Type::StartKneeEnd), + m_points({start, knee, end}) + { + + } + + /// Parses annotation callout line from the object. If object is invalid, then + /// invalid callout line is constructed. + /// \param storage Object storage + /// \param object Callout line object + static PDFAnnotationCalloutLine parse(const PDFObjectStorage* storage, PDFObject object); + + bool isValid() const { return m_type != Type::Invalid; } + Type getType() const { return m_type; } + + QPointF getPoint(int index) const { return m_points.at(index); } + +private: + Type m_type = Type::Invalid; + std::array m_points; +}; + +/// Information about annotation icon fitting (in the widget) +class PDFAnnotationIconFitInfo +{ +public: + inline explicit PDFAnnotationIconFitInfo() = default; + + enum class ScaleCondition + { + Always, + ScaleBigger, + ScaleSmaller, + Never + }; + + enum class ScaleType + { + Anamorphic, ///< Do not keep aspect ratio, fit whole annotation rectangle + Proportional ///< Keep aspect ratio, annotation rectangle may not be filled fully with icon + }; + + /// Parses annotation appearance icon fit info from the object. If object is invalid, then + /// default appearance icon fit info is constructed. + /// \param storage Object storage + /// \param object Appearance icon fit info object + static PDFAnnotationIconFitInfo parse(const PDFObjectStorage* storage, PDFObject object); + +private: + ScaleCondition m_scaleCondition = ScaleCondition::Always; + ScaleType m_scaleType = ScaleType::Proportional; + QPointF m_relativeProportionalPosition = QPointF(0.5, 0.5); + bool m_fullBox = false; +}; + +/// Additional appearance characteristics used for constructing of appearance +/// stream to display annotation on the screen (or just paint it). +class PDFAnnotationAppearanceCharacteristics +{ +public: + inline explicit PDFAnnotationAppearanceCharacteristics() = default; + + enum class PushButtonMode + { + NoIcon, + NoCaption, + IconWithCaptionBelow, + IconWithCaptionAbove, + IconWithCaptionRight, + IconWithCaptionLeft, + IconWithCaptionOverlaid + }; + + /// Number of degrees by which the widget annotation is rotated + /// counterclockwise relative to the page. + PDFInteger getRotation() const { return m_rotation; } + const std::vector& getBorderColor() const { return m_borderColor; } + const std::vector& getBackgroundColor() const { return m_backgroundColor; } + const QString& getNormalCaption() const { return m_normalCaption; } + const QString& getRolloverCaption() const { return m_rolloverCaption; } + const QString& getDownCaption() const { return m_downCaption; } + const PDFObject& getNormalIcon() const { return m_normalIcon; } + const PDFObject& getRolloverIcon() const { return m_rolloverIcon; } + const PDFObject& getDownIcon() const { return m_downIcon; } + const PDFAnnotationIconFitInfo& getIconFit() const { return m_iconFit; } + PushButtonMode getPushButtonMode() const { return m_pushButtonMode; } + + /// Parses annotation appearance characteristics from the object. If object is invalid, then + /// default appearance characteristics is constructed. + /// \param storage Object storage + /// \param object Appearance characteristics object + static PDFAnnotationAppearanceCharacteristics parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFInteger m_rotation = 0; + std::vector m_borderColor; + std::vector m_backgroundColor; + QString m_normalCaption; + QString m_rolloverCaption; + QString m_downCaption; + PDFObject m_normalIcon; + PDFObject m_rolloverIcon; + PDFObject m_downIcon; + PDFAnnotationIconFitInfo m_iconFit; + PushButtonMode m_pushButtonMode = PushButtonMode::NoIcon; +}; + +/// Storage for annotation additional actions +class PDFAnnotationAdditionalActions +{ +public: + + enum Action + { + CursorEnter, + CursorLeave, + MousePressed, + MouseReleased, + FocusIn, + FocusOut, + PageOpened, + PageClosed, + PageShow, + PageHide, + FormFieldModified, + FormFieldFormatted, + FormFieldValidated, + FormFieldCalculated, + Default, + End + }; + + inline explicit PDFAnnotationAdditionalActions() = default; + + /// Returns action for given type. If action is invalid, + /// or not present, nullptr is returned. + /// \param action Action type + const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } + + /// Returns array with all actions + const std::array& getActions() const { return m_actions; } + + /// Parses annotation additional actions from the object. If object is invalid, then + /// empty additional actions is constructed. + /// \param storage Object storage + /// \param object Additional actions object + /// \param defaultAction Default action of object (defined in "A" entry of annotation dictionary) + static PDFAnnotationAdditionalActions parse(const PDFObjectStorage* storage, PDFObject object, PDFObject defaultAction); + +private: + std::array m_actions; +}; + +/// Annotation default appearance +class PDFAnnotationDefaultAppearance +{ +public: + explicit inline PDFAnnotationDefaultAppearance() = default; + + /// Parses appearance string. If error occurs, then default appearance + /// string is constructed. + static PDFAnnotationDefaultAppearance parse(const QByteArray& string); + + const QByteArray& getFontName() const { return m_fontName; } + PDFReal getFontSize() const { return m_fontSize; } + QColor getFontColor() const { return m_fontColor; } + +private: + QByteArray m_fontName; + PDFReal m_fontSize = 8.0; + QColor m_fontColor = Qt::black; +}; + +class PDFAnnotation; +class PDFMarkupAnnotation; +class PDFTextAnnotation; + +using PDFAnnotationPtr = QSharedPointer; + +struct AnnotationDrawParameters +{ + /// Painter, onto which is annotation graphics drawn + QPainter* painter = nullptr; + + /// Pointer to annotation (if draw is delegated to other objects, + /// for example, form manager, then maybe pointer to annotation + /// is needed). + PDFAnnotation* annotation = nullptr; + + /// Pointer to form manager (if forms are drawn) + const PDFFormManager* formManager = nullptr; + + /// Output parameter. Marks annotation's graphics bounding + /// rectangle (it can be different/adjusted from original + /// annotation bounding rectangle, in that case, it must be adjusted). + /// If this rectangle is invalid, then appearance content stream + /// is assumed to be empty. + QRectF boundingRectangle; + + /// Appeareance mode (normal/rollover/down, and appearance state) + PDFAppeareanceStreams::Key key; + + /// Invert colors? + bool invertColors = false; +}; + +/// Base class for all annotation types. Represents PDF annotation object. +/// Annotations are various enhancements to pages graphical representation, +/// such as graphics, text, highlight or multimedia content, such as sounds, +/// videos and 3D annotations. +class PDFAnnotation +{ +public: + explicit PDFAnnotation(); + virtual ~PDFAnnotation() = default; + + enum Flag : uint + { + None = 0x0000, + Invisible = 0x0001, ///< If set, do not display unknown annotations using their AP dictionary + Hidden = 0x0002, ///< If set, do not display annotation and do not show popup windows (completely hidden) + Print = 0x0004, ///< If set, print annotation + NoZoom = 0x0008, ///< Do not apply page zoom while displaying annotation rectangle + NoRotate = 0x0010, ///< Do not rotate annotation's appearance to match orientation of the page + NoView = 0x0020, ///< Do not display annotation on the screen (it still can be printed) + ReadOnly = 0x0040, ///< Do not allow interacting with the user (and disallow also mouse interaction) + Locked = 0x0080, ///< Do not allow to delete/modify annotation by user + ToggleNoView = 0x0100, ///< If set, invert the interpretation of NoView flag + LockedContents = 0x0200, ///< Do not allow to modify contents of the annotation + }; + Q_DECLARE_FLAGS(Flags, Flag) + + virtual AnnotationType getType() const = 0; + + virtual PDFMarkupAnnotation* asMarkupAnnotation() { return nullptr; } + virtual const PDFMarkupAnnotation* asMarkupAnnotation() const { return nullptr; } + virtual bool isReplyTo() const { return false; } + + /// Draws the annotation using parameters. Annotation is drawn onto painter, + /// but actual graphics can be drawn outside of annotation's rectangle. + /// In that case, adjusted annotation's rectangle is passed to the parameters. + /// Painter must use annotation's coordinate system (for example, line points + /// must match in both in painter and this annotation). + /// \param parameters Graphics parameters + virtual void draw(AnnotationDrawParameters& parameters) const; + + /// Returns a list of appearance states, which must be created for this annotation + virtual std::vector getDrawKeys(const PDFFormManager* formManager) const; + + /// Returns effective flags (some annotations can behave as they have always + /// set some flags, such as NoZoom and NoRotate) + virtual Flags getEffectiveFlags() const { return getFlags(); } + + PDFObjectReference getSelfReference() const { return m_selfReference; } + const QRectF& getRectangle() const { return m_rectangle; } + const QString& getContents() const { return m_contents; } + PDFObjectReference getPageReference() const { return m_pageReference; } + const QString& getName() const { return m_name; } + const QDateTime& getLastModifiedDateTime() const { return m_lastModified; } + const QString& getLastModifiedString() const { return m_lastModifiedString; } + Flags getFlags() const { return m_flags; } + const PDFAppeareanceStreams& getAppearanceStreams() const { return m_appearanceStreams; } + const QByteArray& getAppearanceState() const { return m_appearanceState; } + const PDFAnnotationBorder& getBorder() const { return m_annotationBorder; } + const std::vector& getColor() const { return m_color; } + PDFInteger getStructuralParent() const { return m_structParent; } + PDFObjectReference getOptionalContent() const { return m_optionalContentReference; } + const PDFObject& getAssociatedFiles() const { return m_associatedFiles; } + PDFReal getFillOpacity() const { return m_fillingOpacity; } + PDFReal getStrokeOpacity() const { return m_strokingOpacity; } + BlendMode getBlendMode() const { return m_blendMode; } + const QString& getLanguage() const { return m_language; } + + void setSelfReference(const PDFObjectReference& selfReference); + void setRectangle(const QRectF& rectangle); + void setContents(const QString& contents); + void setPageReference(const PDFObjectReference& pageReference); + void setName(const QString& name); + void setLastModified(const QDateTime& lastModified); + void setLastModifiedString(const QString& lastModifiedString); + void setFlags(const Flags& flags); + void setAppearanceStreams(const PDFAppeareanceStreams& appearanceStreams); + void setAppearanceState(const QByteArray& appearanceState); + void setAnnotationBorder(const PDFAnnotationBorder& annotationBorder); + void setColor(const std::vector& color); + void setStructParent(const PDFInteger& structParent); + void setOptionalContentReference(const PDFObjectReference& optionalContentReference); + void setAssociatedFiles(const PDFObject& associatedFiles); + void setFillingOpacity(const PDFReal& fillingOpacity); + void setStrokingOpacity(const PDFReal& strokingOpacity); + void setBlendMode(const BlendMode& blendMode); + void setLanguage(const QString& language); + + /// Returns current composition mode. If blend mode is not supported by Qt, + /// then normal composition mode is returned. + QPainter::CompositionMode getCompositionMode() const; + + /// Parses annotation from the object. If error occurs, then nullptr is returned. + /// \param storage Object storage + /// \param reference Annotation object reference + static PDFAnnotationPtr parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + /// Parses quadrilaterals and fills them in the painter path. If no quadrilaterals are defined, + /// then annotation rectangle is used. If annotation rectangle is also invalid, + /// then empty painter path is used. + /// \param storage Object storage + /// \param quadrilateralsObject Object with quadrilaterals definition + /// \param annotationRect Annotation rectangle + static PDFAnnotationQuadrilaterals parseQuadrilaterals(const PDFObjectStorage* storage, PDFObject quadrilateralsObject, const QRectF annotationRect); + + /// Converts name to line ending. If appropriate line ending for name is not found, + /// then None line ending is returned. + /// \param name Name of the line ending + static AnnotationLineEnding convertNameToLineEnding(const QByteArray& name); + + /// Converts line ending to name. + /// \param lineEnding Line ending + static QByteArray convertLineEndingToName(AnnotationLineEnding lineEnding); + + /// Returns draw color from defined annotation color. If color is incorrectly + /// defined, then black color is returned. + /// \param color Color (can have 1, 3 and 4 components) + /// \param opacity Opacity + static QColor getDrawColorFromAnnotationColor(const std::vector& color, PDFReal opacity); + + /// Returns true, if annotation is editable + /// \param type Annotation type + static bool isTypeEditable(AnnotationType type); + +protected: + virtual QColor getStrokeColor() const; + virtual QColor getFillColor() const; + + struct LineGeometryInfo + { + /// Original line + QLineF originalLine; + + /// Transformed line + QLineF transformedLine; + + /// Matrix LCStoGCS is local coordinate system of line originalLine. It transforms + /// points on the line to the global coordinate system. So, point (0, 0) will + /// map onto p1 and point (originalLine.length(), 0) will map onto p2. + QMatrix LCStoGCS; + + /// Inverted matrix of LCStoGCS. It maps global coordinate system to local + /// coordinate system of the original line. + QMatrix GCStoLCS; + + static LineGeometryInfo create(QLineF line); + }; + + /// Returns pen from border settings and annotation color + QPen getPen() const; + + /// Returns brush from interior color. If annotation doesn't have + /// a brush, then empty brush is returned. + QBrush getBrush() const; + + /// Draw line ending at given point, using parameters. Line ending appearance + /// is constructed given parameters \p lineEndingSize, \p arrowAxisLength + /// and \p ending. Parameter \p flipAxis controls, if we are drawing at the + /// start (false), or at the end (true) of the line. User can specify matrix, + /// which maps from local coordinate system of the line to the global coordinate + /// system. Also, bouding path is updated. + /// \param painter Painter + /// \param point Point, at which line ending is being drawn + /// \param lineEndingSize Line ending size + /// \param arrowAxisLength Length of the arrow + /// \param ending Type of ending + /// \param flipAxis Flip axis to draw end of line? + /// \param LCStoGCS Transformation from local coordinate system of line to global coordinate system + /// \pram boundingPath Bounding path to be updated + void drawLineEnding(QPainter* painter, + QPointF point, + PDFReal lineEndingSize, + PDFReal arrowAxisLength, + AnnotationLineEnding ending, + bool flipAxis, + const QMatrix& LCStoGCS, + QPainterPath& boundingPath) const; + + /// Draw line using given parameters and painter. Line is specified + /// by its geometry information. Painter must be set to global coordinates. + /// Bounding path is also updated, it is specified in global coordinates, + /// not in line local coordinate system. We consider p1 as start point of + /// the line, and p2 as the end point. The painter must have proper QPen + /// and QBrush setted, this function uses current pen/brush to paint the line. + /// \param info Line geometry info + /// \param painter Painter + /// \param lineEndingSize Line ending size + /// \param p1Ending Line endpoint graphics at p1 + /// \param p2Ending Line endpoint graphics at p2 + /// \param boundingPath Bounding path in global coordinate system + /// \param textOffset Additional text offset + /// \param text Text, which should be printed along the line + /// \param textIsAboveLine Text should be printed above line + void drawLine(const LineGeometryInfo& info, + QPainter& painter, + PDFReal lineEndingSize, + AnnotationLineEnding p1Ending, + AnnotationLineEnding p2Ending, + QPainterPath& boundingPath, + QPointF textOffset, + const QString& text, + bool textIsAboveLine) const; + + /// Draws character unicode symbol using text + /// \param text Text to be drawn + /// \param opacity Opacity + /// \param parameters Draw parameters + void drawCharacterSymbol(QString text, PDFReal opacity, AnnotationDrawParameters& parameters) const; + + /// Parses path. If path is incorrect, empty path is returned. + /// \param storage Storage + /// \param dictionary Annotation's dictionary + /// \param closePath Close path when finishing? + static QPainterPath parsePath(const PDFObjectStorage* storage, const PDFDictionary* dictionary, bool closePath); + +private: + PDFObjectReference m_selfReference; ///< Reference to self + QRectF m_rectangle; ///< Annotation rectangle, in page coordinates, "Rect" entry + QString m_contents; ///< Text to be displayed to the user (or alternate text), "Content" entry + PDFObjectReference m_pageReference; ///< Reference to annotation's page, "P" entry + QString m_name; ///< Unique name (in page context) for the annotation, "NM" entry + QDateTime m_lastModified; ///< Date and time, when annotation was last modified, "M" entry + QString m_lastModifiedString; ///< Date and time, in text format + Flags m_flags; ///< Annotation flags + PDFAppeareanceStreams m_appearanceStreams; ///< Appearance streams, "AP" entry + QByteArray m_appearanceState; ///< Appearance state, "AS" entry + PDFAnnotationBorder m_annotationBorder; ///< Annotation border, "Border" entry + std::vector m_color; ///< Color (for example, title bar of popup window), "C" entry + PDFInteger m_structParent; ///< Structural parent identifier, "StructParent" entry + PDFObjectReference m_optionalContentReference; ///< Reference to optional content, "OC" entry + PDFObject m_associatedFiles; + PDFReal m_fillingOpacity = 1.0; + PDFReal m_strokingOpacity = 1.0; + BlendMode m_blendMode = BlendMode::Normal; + QString m_language; +}; + +/// Markup annotation object, used to mark up contents of PDF documents. Markup annotations +/// can have various types, as free text (just text displayed on page), annotations with popup +/// windows, and special annotations, such as multimedia annotations. +class PDFMarkupAnnotation : public PDFAnnotation +{ +public: + explicit inline PDFMarkupAnnotation() = default; + + virtual PDFMarkupAnnotation* asMarkupAnnotation() override { return this; } + virtual const PDFMarkupAnnotation* asMarkupAnnotation() const override { return this; } + virtual bool isReplyTo() const override; + + enum class ReplyType + { + Reply, + Group + }; + + const QString& getWindowTitle() const { return m_windowTitle; } + PDFObjectReference getPopupAnnotation() const { return m_popupAnnotation; } + const QString& getRichTextString() const { return m_richTextString; } + const QDateTime& getCreationDate() const { return m_creationDate; } + PDFObjectReference getInReplyTo() const { return m_inReplyTo; } + const QString& getSubject() const { return m_subject; } + ReplyType getReplyType() const { return m_replyType; } + const QByteArray& getIntent() const { return m_intent; } + const PDFObject& getExternalData() const { return m_externalData; } + + void setWindowTitle(const QString& windowTitle); + void setPopupAnnotation(const PDFObjectReference& popupAnnotation); + void setRichTextString(const QString& richTextString); + void setCreationDate(const QDateTime& creationDate); + void setInReplyTo(PDFObjectReference inReplyTo); + void setSubject(const QString& subject); + void setReplyType(ReplyType replyType); + void setIntent(const QByteArray& intent); + void setExternalData(const PDFObject& externalData); + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QString m_windowTitle; + PDFObjectReference m_popupAnnotation; + QString m_richTextString; + QDateTime m_creationDate; + PDFObjectReference m_inReplyTo; + QString m_subject; + ReplyType m_replyType = ReplyType::Reply; + QByteArray m_intent; + PDFObject m_externalData; +}; + +enum class TextAnnotationIcon +{ + Comment, + Help, + Insert, + Key, + NewParagraph, + Note, + Paragraph +}; + +/// Text annotation represents note attached to a specific point in the PDF +/// document. It appears as icon, and it is not zoomed, or rotated (behaves +/// as if flag NoZoom and NoRotate were set). When this annotation is opened, +/// it displays popup window containing the text of the note, font and size +/// is implementation dependent by viewer application. +class PDF4QTLIBSHARED_EXPORT PDFTextAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFTextAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Text; } + virtual std::vector getDrawKeys(const PDFFormManager* formManager) const override; + virtual void draw(AnnotationDrawParameters& parameters) const override; + virtual Flags getEffectiveFlags() const override; + + bool isOpen() const { return m_open; } + const QByteArray& getIconName() const { return m_iconName; } + const QString& getState() const { return m_state; } + const QString& getStateModel() const { return m_stateModel; } + + static QIcon createIcon(QString key, QSize size); + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + static QString getTextForIcon(const QString& key); + + bool m_open = false; + QByteArray m_iconName; + QString m_state; + QString m_stateModel; +}; + +enum class LinkHighlightMode +{ + None, + Invert, + Outline, + Push +}; + +/// Link annotation represents hypertext link to a destination to elsewhere +/// in the document, or action to be performed. +class PDFLinkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFLinkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Link; } + virtual std::vector getDrawKeys(const PDFFormManager* formManager) const override; + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const PDFAction* getAction() const { return m_action.data(); } + LinkHighlightMode getHighlightMode() const { return m_highlightMode; } + const PDFAction* getURIAction() const { return m_previousAction.data(); } + const PDFAnnotationQuadrilaterals& getActivationRegion() const { return m_activationRegion; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + PDFActionPtr m_action; + LinkHighlightMode m_highlightMode = LinkHighlightMode::Invert; + PDFActionPtr m_previousAction; + PDFAnnotationQuadrilaterals m_activationRegion; +}; + +/// Free text annotation displays text directly on the page. Free text doesn't have +/// open/close state, text is always visible. +class PDFFreeTextAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFFreeTextAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::FreeText; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + enum class Justification + { + Left, + Centered, + Right + }; + + enum class Intent + { + None, + Callout, + TypeWriter + }; + + const QByteArray& getDefaultAppearance() const { return m_defaultAppearance; } + Justification getJustification() const { return m_justification; } + const QString& getDefaultStyle() const { return m_defaultStyleString; } + const PDFAnnotationCalloutLine& getCalloutLine() const { return m_calloutLine; } + Intent getIntent() const { return m_intent; } + const QRectF& getTextRectangle() const { return m_textRectangle; } + const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } + AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } + AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QByteArray m_defaultAppearance; + Justification m_justification = Justification::Left; + QString m_defaultStyleString; + PDFAnnotationCalloutLine m_calloutLine; + Intent m_intent = Intent::None; + QRectF m_textRectangle; + PDFAnnotationBorderEffect m_effect; + AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; + AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; +}; + +/// Line annotation, draws straight line on the page (in most simple form), or +/// it can display, for example, dimensions with perpendicular lines at the line +/// endings. Caption text can also be displayed. +class PDFLineAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFLineAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Line; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + enum class Intent + { + Arrow, + Dimension + }; + + enum class CaptionPosition + { + Inline, + Top + }; + + const QLineF& getLine() const { return m_line; } + AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } + AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } + const std::vector& getInteriorColor() const { return m_interiorColor; } + PDFReal getLeaderLineLength() const { return m_leaderLineLength; } + PDFReal getLeaderLineExtension() const { return m_leaderLineExtension; } + PDFReal getLeaderLineOffset() const { return m_leaderLineOffset; } + bool isCaptionRendered() const { return m_captionRendered; } + Intent getIntent() const { return m_intent; } + CaptionPosition getCaptionPosition() const { return m_captionPosition; } + const PDFObject& getMeasureDictionary() const { return m_measureDictionary; } + const QPointF& getCaptionOffset() const { return m_captionOffset; } + +protected: + virtual QColor getFillColor() const override; + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QLineF m_line; + AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; + AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; + std::vector m_interiorColor; + PDFReal m_leaderLineLength = 0.0; + PDFReal m_leaderLineExtension = 0.0; + PDFReal m_leaderLineOffset = 0.0; + bool m_captionRendered = false; + Intent m_intent = Intent::Arrow; + CaptionPosition m_captionPosition = CaptionPosition::Inline; + PDFObject m_measureDictionary; + QPointF m_captionOffset; +}; + +/// Simple geometry annotation. +/// Square and circle annotations displays rectangle or ellipse on the page. +/// Name is a bit strange (because rectangle may not be a square or circle is not ellipse), +/// but it is defined in PDF specification, so we will use these terms. +class PDFSimpleGeometryAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFSimpleGeometryAnnotation(AnnotationType type) : + m_type(type) + { + + } + + virtual AnnotationType getType() const override { return m_type; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const std::vector& getInteriorColor() const { return m_interiorColor; } + const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } + const QRectF& getGeometryRectangle() const { return m_geometryRectangle; } + +protected: + virtual QColor getFillColor() const override; + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + AnnotationType m_type; + std::vector m_interiorColor; + PDFAnnotationBorderEffect m_effect; + QRectF m_geometryRectangle; +}; + +/// Polygonal geometry, consists of polygon or polyline geometry. Polygon annotation +/// displays closed polygon (potentially filled), polyline annotation displays +/// polyline, which is not closed. +class PDFPolygonalGeometryAnnotation : public PDFMarkupAnnotation +{ +public: + enum class Intent + { + None, + Cloud, + Dimension + }; + + inline explicit PDFPolygonalGeometryAnnotation(AnnotationType type) : + m_type(type), + m_intent(Intent::None) + { + + } + + virtual AnnotationType getType() const override { return m_type; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const std::vector& getVertices() const { return m_vertices; } + AnnotationLineEnding getStartLineEnding() const { return m_startLineEnding; } + AnnotationLineEnding getEndLineEnding() const { return m_endLineEnding; } + const std::vector& getInteriorColor() const { return m_interiorColor; } + const PDFAnnotationBorderEffect& getBorderEffect() const { return m_effect; } + Intent getIntent() const { return m_intent; } + const PDFObject& getMeasure() const { return m_measure; } + const QPainterPath& getPath() const { return m_path; } + +protected: + virtual QColor getFillColor() const override; + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + AnnotationType m_type; + std::vector m_vertices; + AnnotationLineEnding m_startLineEnding = AnnotationLineEnding::None; + AnnotationLineEnding m_endLineEnding = AnnotationLineEnding::None; + std::vector m_interiorColor; + PDFAnnotationBorderEffect m_effect; + Intent m_intent; + PDFObject m_measure; + QPainterPath m_path; +}; + +/// Annotation for text highlighting. Can highlight, underline, strikeout, +/// or squiggly underline the text. +class PDFHighlightAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFHighlightAnnotation(AnnotationType type) : + m_type(type) + { + + } + + virtual AnnotationType getType() const override { return m_type; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const PDFAnnotationQuadrilaterals& getHiglightArea() const { return m_highlightArea; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + AnnotationType m_type; + PDFAnnotationQuadrilaterals m_highlightArea; +}; + +/// Annotation for visual symbol that indicates presence of text edits. +class PDFCaretAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFCaretAnnotation() = default; + + enum class Symbol + { + None, + Paragraph + }; + + virtual AnnotationType getType() const override { return AnnotationType::Caret; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const QRectF& getCaretRectangle() const { return m_caretRectangle; } + Symbol getSymbol() const { return m_symbol; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QRectF m_caretRectangle; + Symbol m_symbol = Symbol::None; +}; + +enum class Stamp +{ + Approved, + AsIs, + Confidential, + Departmental, + Draft, + Experimental, + Expired, + Final, + ForComment, + ForPublicRelease, + NotApproved, + NotForPublicRelease, + Sold, + TopSecret +}; + +enum class StampIntent +{ + Stamp, + StampImage, + StampSnapshot +}; + +/// Annotation for stamps. Displays text or graphics intended to look +/// as if they were stamped on the paper. +class PDF4QTLIBSHARED_EXPORT PDFStampAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFStampAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Stamp; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + Stamp getStamp() const { return m_stamp; } + StampIntent getIntent() const { return m_intent; } + + void setStamp(const Stamp& stamp); + void setIntent(const StampIntent& intent); + + static QString getText(Stamp stamp); + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + Stamp m_stamp = Stamp::Draft; + StampIntent m_intent = StampIntent::Stamp; +}; + +/// Ink annotation. Represents a path composed of disjoint polygons. +class PDFInkAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFInkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Ink; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const QPainterPath& getInkPath() const { return m_inkPath; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QPainterPath m_inkPath; +}; + +/// Popup annotation. Displays text in popup window for markup annotations. +/// This annotation contains field to associated annotation, for which +/// is window displayed, and window state (open/closed). +class PDFPopupAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFPopupAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Popup; } + + bool isOpened() const { return m_opened; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + bool m_opened = false; +}; + +enum class FileAttachmentIcon +{ + Graph, + Paperclip, + PushPin, + Tag +}; + +/// File attachment annotation contains reference to (embedded or external) file. +/// So it is a link to specified file. Activating annotation enables user to view +/// or store attached file in the filesystem. +class PDFFileAttachmentAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFFileAttachmentAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::FileAttachment; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } + FileAttachmentIcon getIcon() const { return m_icon; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + PDFFileSpecification m_fileSpecification; + FileAttachmentIcon m_icon = FileAttachmentIcon::PushPin; +}; + +/// Sound annotation contains sound, which is played, when +/// annotation is activated. +class PDFSoundAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFSoundAnnotation() = default; + + enum class Icon + { + Speaker, + Microphone + }; + + virtual AnnotationType getType() const override { return AnnotationType::Sound; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const PDFSound& getSound() const { return m_sound; } + Icon getIcon() const { return m_icon; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + PDFSound m_sound; + Icon m_icon = Icon::Speaker; +}; + +/// Movie annotation contains movie or sound, which is played, when +/// annotation is activated. +class PDFMovieAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFMovieAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Movie; } + + const QString& getMovieTitle() const { return m_movieTitle; } + bool isMovieToBePlayed() const { return m_playMovie; } + const PDFMovie& getMovie() const { return m_movie; } + const PDFMovieActivation& getMovieActivation() const { return m_movieActivation; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QString m_movieTitle; + bool m_playMovie = true; + PDFMovie m_movie; + PDFMovieActivation m_movieActivation; +}; + +/// Screen action represents area of page in which is media played. +/// See also Rendition actions and their relationship to this annotation. +class PDFScreenAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFScreenAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Screen; } + + const QString& getScreenTitle() const { return m_screenTitle; } + const PDFAnnotationAppearanceCharacteristics& getAppearanceCharacteristics() const { return m_appearanceCharacteristics; } + const PDFAction* getAction() const { return m_action.get(); } + const PDFAnnotationAdditionalActions& getAdditionalActions() const { return m_additionalActions; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QString m_screenTitle; + PDFAnnotationAppearanceCharacteristics m_appearanceCharacteristics; + PDFActionPtr m_action; + PDFAnnotationAdditionalActions m_additionalActions; +}; + +/// Widget annotation represents form fields for interactive forms. +/// Annotation's dictionary is merged with form field dictionary, +/// it can be done, because dictionaries doesn't overlap. +class PDFWidgetAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFWidgetAnnotation() = default; + + enum class HighlightMode + { + None, + Invert, + Outline, + Push, + Toggle + }; + + virtual AnnotationType getType() const override { return AnnotationType::Widget; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + virtual std::vector getDrawKeys(const PDFFormManager* formManager) const override; + + HighlightMode getHighlightMode() const { return m_highlightMode; } + const PDFAnnotationAppearanceCharacteristics& getAppearanceCharacteristics() const { return m_appearanceCharacteristics; } + const PDFAction* getAction() const { return m_action.get(); } + const PDFAnnotationAdditionalActions& getAdditionalActions() const { return m_additionalActions; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + HighlightMode m_highlightMode = HighlightMode::Invert; + PDFAnnotationAppearanceCharacteristics m_appearanceCharacteristics; + PDFActionPtr m_action; + PDFAnnotationAdditionalActions m_additionalActions; +}; + +/// Printer mark annotation represents graphics symbol, mark, or other +/// graphic feature to assist printing production. +class PDFPrinterMarkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFPrinterMarkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::PrinterMark; } +}; + +/// Trapping characteristics for the page +class PDFTrapNetworkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFTrapNetworkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::TrapNet; } +}; + +/// Watermark annotation represents watermark displayed on the page, +/// for example, if it is printed. Watermarks are displayed at fixed +/// position and size on the page. +class PDFWatermarkAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFWatermarkAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Watermark; } + + const QMatrix& getMatrix() const { return m_matrix; } + PDFReal getRelativeHorizontalOffset() const { return m_relativeHorizontalOffset; } + PDFReal getRelativeVerticalOffset() const { return m_relativeVerticalOffset; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + QMatrix m_matrix; + PDFReal m_relativeHorizontalOffset = 0.0; + PDFReal m_relativeVerticalOffset = 0.0; +}; + +/// Redaction annotation represents content selection, which should +/// be removed from the document. +class PDFRedactAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFRedactAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Redact; } + virtual void draw(AnnotationDrawParameters& parameters) const override; + + const PDFAnnotationQuadrilaterals& getRedactionRegion() const { return m_redactionRegion; } + const std::vector& getInteriorColor() const { return m_interiorColor; } + const PDFObject& getOverlay() const { return m_overlayForm; } + const QString& getOverlayText() const { return m_overlayText; } + bool isRepeat() const { return m_repeat; } + const QByteArray& getDefaultAppearance() const { return m_defaultAppearance; } + PDFInteger getJustification() const { return m_justification; } + +protected: + virtual QColor getFillColor() const override; + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + PDFAnnotationQuadrilaterals m_redactionRegion; + std::vector m_interiorColor; + PDFObject m_overlayForm; ///< Overlay form object + QString m_overlayText; + bool m_repeat = false; + QByteArray m_defaultAppearance; + PDFInteger m_justification = 0; +}; + +class PDFProjectionAnnotation : public PDFMarkupAnnotation +{ +public: + inline explicit PDFProjectionAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::Projection; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); +}; + +/// 3D annotations represents 3D scene, which can be viewed in the application. +class PDF3DAnnotation : public PDFAnnotation +{ +public: + inline explicit PDF3DAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::_3D; } + + const PDF3DStream& getStream() const { return m_stream; } + const std::optional& getDefaultView() const { return m_defaultView; } + const PDF3DActivation& getActivation() const { return m_activation; } + bool isInteractive() const { return m_interactive; } + QRectF getViewBox() const { return m_viewBox; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + PDF3DStream m_stream; + std::optional m_defaultView; + PDF3DActivation m_activation; + bool m_interactive = true; + QRectF m_viewBox; +}; + +/// Rich media annotations can be video, audio, or other multimedia presentations. +/// The application should provide additional functionality to control rich media, +/// such as buttons to play/pause/stop video etc. This annotation consists +/// of contents and settings, settings are optional. +class PDFRichMediaAnnotation : public PDFAnnotation +{ +public: + inline explicit PDFRichMediaAnnotation() = default; + + virtual AnnotationType getType() const override { return AnnotationType::RichMedia; } + + const PDFRichMediaContent* getContent() const { return &m_content; } + const PDFRichMediaSettings* getSettings() const { return &m_settings; } + +private: + friend PDFAnnotationPtr PDFAnnotation::parse(const PDFObjectStorage* storage, PDFObjectReference reference); + + PDFRichMediaContent m_content; + PDFRichMediaSettings m_settings; +}; + +/// Annotation manager manages annotations for document's pages. Each page +/// can have multiple annotations, and this object caches them. Also, +/// this object builds annotation's appearance streams, if necessary. This +/// manager is intended to non-gui rendering. If widget annotation manager is used, +/// then this object is not thread safe. +class PDF4QTLIBSHARED_EXPORT PDFAnnotationManager : public QObject, public IDocumentDrawInterface +{ + Q_OBJECT + +private: + using BaseClass = QObject; + + void drawWidgetAnnotationHighlight(QRectF annotationRectangle, PDFObjectReference annotationReference, QPainter* painter, QMatrix userSpaceToDeviceSpace) const; + +public: + + enum class Target + { + View, + Print + }; + + explicit PDFAnnotationManager(PDFFontCache* fontCache, + const PDFCMSManager* cmsManager, + const PDFOptionalContentActivity* optionalActivity, + PDFMeshQualitySettings meshQualitySettings, + PDFRenderer::Features features, + Target target, + QObject* parent); + virtual ~PDFAnnotationManager() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + /// Set document + /// \param document New document + virtual void setDocument(const PDFModifiedDocument& document); + + Target getTarget() const; + void setTarget(Target target); + + const PDFOptionalContentActivity* getOptionalActivity() const; + void setOptionalActivity(const PDFOptionalContentActivity* optionalActivity); + + PDFFontCache* getFontCache() const; + void setFontCache(PDFFontCache* fontCache); + + PDFMeshQualitySettings getMeshQualitySettings() const; + void setMeshQualitySettings(const PDFMeshQualitySettings& meshQualitySettings); + + PDFRenderer::Features getFeatures() const; + void setFeatures(PDFRenderer::Features features); + + PDFFormManager* getFormManager() const; + void setFormManager(PDFFormManager* formManager); + + struct PageAnnotation + { + PDFAppeareanceStreams::Appearance appearance = PDFAppeareanceStreams::Appearance::Normal; + PDFAnnotationPtr annotation; + bool isHovered = false; + + /// This mutable appearance stream is protected by main mutex + mutable PDFCachedItem appearanceStream; + }; + + struct PageAnnotations + { + bool isEmpty() const { return annotations.empty(); } + + /// Returns popup annotation for given annotation, if annotation doesn't have + /// popup annotation attached, then nullptr is returned. + /// \param pageAnnotation Page annotation + /// \returns Popup annotation or nullptr + const PageAnnotation* getPopupAnnotation(const PageAnnotation& pageAnnotation) const; + + /// Returns a list of page annotations, which are replies to this page annotation. + /// If page annotation doesn't have any replies, then empty list is returned. Replies + /// are returned in order, in which they were created. + /// \param pageAnnotation Page annotation + /// \returns List of replies + std::vector getReplies(const PageAnnotation& pageAnnotation) const; + + std::vector annotations; + }; + + /// Prepares annotation transformations for rendering + /// \param pagePointToDevicePointMatrix Page point to device point matrix + /// \param device Paint device, onto which will be annotation rendered + /// \param annotationFlags Annotation flags + /// \param page Page + /// \param[in,out] annotationRectangle Input/output annotation rectangle + QMatrix prepareTransformations(const QMatrix& pagePointToDevicePointMatrix, + QPaintDevice* device, + const PDFAnnotation::Flags annotationFlags, + const PDFPage* page, + QRectF& annotationRectangle) const; + + /// Returns current appearance stream for given page annotation + /// \param pageAnnotation Page annotation + PDFObject getAppearanceStream(const PageAnnotation& pageAnnotation) const; + + /// Returns constant reference to page annotation for given page index. + /// This function requires, that pointer to m_document is valid. + /// \param pageIndex Page index (must point to valid page) + const PageAnnotations& getPageAnnotations(PDFInteger pageIndex) const; + + /// Returns reference to page annotation for given page index. + /// This function requires, that pointer to m_document is valid. + /// \param pageIndex Page index (must point to valid page) + PageAnnotations& getPageAnnotations(PDFInteger pageIndex); + + /// Returns true, if given page has any annotation + bool hasAnnotation(PDFInteger pageIndex) const; + + /// Returns true, if any page in the given indices has annotation + bool hasAnyPageAnnotation(const std::vector& pageIndices) const; + +protected: + void drawWidgetAnnotationHighlight(QRectF annotationRectangle, + const PDFAnnotation* annotation, + QPainter* painter, + QMatrix userSpaceToDeviceSpace) const; + + /// Returns true, if given annotation should be drawn + /// \param annotation Annotation + bool isAnnotationDrawEnabled(const PageAnnotation& annotation) const; + + /// Returns true, if annotation is drawn by editor (from form widget) + /// \param annotation Annotation + bool isAnnotationDrawnByEditor(const PageAnnotation& annotation) const; + + /// Draws annotation + /// \param pageAnnotation Page annotation + /// \param pagePointToDevicePointMatrix Page point to device point matrix + /// \param page Page + /// \param cms Color management system + /// \param isEditorDrawEnabled Is editor draw enabled? + /// \param errors Errors list (where draw errors are stored) + /// \param painter Painter + void drawAnnotation(const PageAnnotation& annotation, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPage* page, + const PDFCMS* cms, + bool isEditorDrawEnabled, + QList& errors, + QPainter* painter) const; + + /// Draws annotation by direct drawing, not using annotation's + /// appearance stream. + /// \param pageAnnotation Page annotation + /// \param pagePointToDevicePointMatrix Page point to device point matrix + /// \param page Page + /// \param cms Color management system + /// \param isEditorDrawEnabled Is annotation drawn by form widget editor? + /// \param painter Painter + void drawAnnotationDirect(const PageAnnotation& annotation, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPage* page, + const PDFCMS* cms, + bool isEditorDrawEnabled, + QPainter* painter) const; + + /// Draws annotation using annotation's appearance stream. + /// \param pageAnnotation Page annotation + /// \param appearanceStreamObject Object with appearance stream + /// \param pagePointToDevicePointMatrix Page point to device point matrix + /// \param page Page + /// \param cms Color management system + /// \param painter Painter + void drawAnnotationUsingAppearanceStream(const PageAnnotation& annotation, + const PDFObject& appearanceStreamObject, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPage* page, + const PDFCMS* cms, + QPainter* painter) const; + + const PDFDocument* m_document; + + PDFFontCache* m_fontCache; + const PDFCMSManager* m_cmsManager; + const PDFOptionalContentActivity* m_optionalActivity; + PDFFormManager* m_formManager; + PDFMeshQualitySettings m_meshQualitySettings; + PDFRenderer::Features m_features; + + mutable QMutex m_mutex; + mutable std::map m_pageAnnotations; + Target m_target = Target::View; +}; + +/// Annotation manager for GUI rendering, it also manages annotations widgets +/// for parent widget. +class PDF4QTLIBSHARED_EXPORT PDFWidgetAnnotationManager : public PDFAnnotationManager, public IDrawWidgetInputInterface +{ + Q_OBJECT + +private: + using BaseClass = PDFAnnotationManager; + +public: + explicit PDFWidgetAnnotationManager(PDFDrawWidgetProxy* proxy, QObject* parent); + virtual ~PDFWidgetAnnotationManager() override; + + virtual void setDocument(const PDFModifiedDocument& document) override; + + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; + + /// Returns tooltip generated from annotation + virtual QString getTooltip() const override { return m_tooltip; } + + /// Returns current cursor + virtual const std::optional& getCursor() const override { return m_cursor; } + + virtual int getInputPriority() const override { return AnnotationPriority; } + +signals: + void actionTriggered(const PDFAction* action); + void documentModified(PDFModifiedDocument document); + +private: + void updateFromMouseEvent(QMouseEvent* event); + + void onShowPopupAnnotation(); + void onCopyAnnotation(); + void onEditAnnotation(); + void onDeleteAnnotation(); + + /// Creates dialog for markup annotations. This function is used only for markup annotations, + /// do not use them for other annotations (function can crash). + /// \param widget Dialog's parent widget + /// \param pageAnnotation Markup annotation + /// \param pageAnnotations Page annotations + QDialog* createDialogForMarkupAnnotations(PDFWidget* widget, + const PageAnnotation& pageAnnotation, + const PageAnnotations& pageAnnotations); + + /// Creates widgets for markup annotation main popup widget. Also sets + /// default size of parent widget. + /// \param parentWidget Parent widget, where widgets are created + /// \param pageAnnotation Markup annotation + /// \param pageAnnotations Page annotations + void createWidgetsForMarkupAnnotations(QWidget* parentWidget, + const PageAnnotation& pageAnnotation, + const PageAnnotations& pageAnnotations); + + PDFDrawWidgetProxy* m_proxy; + QString m_tooltip; + std::optional m_cursor; + QPoint m_editableAnnotationGlobalPosition; ///< Position, where action on annotation was executed + PDFObjectReference m_editableAnnotation; ///< Annotation to be edited or deleted + PDFObjectReference m_editableAnnotationPage; ///< Page of annotation above +}; + +} // namespace pdf + +#endif // PDFANNOTATION_H diff --git a/Pdf4QtLib/sources/pdfblendfunction.cpp b/Pdf4QtLib/sources/pdfblendfunction.cpp index 1358e71..7eb3caa 100644 --- a/Pdf4QtLib/sources/pdfblendfunction.cpp +++ b/Pdf4QtLib/sources/pdfblendfunction.cpp @@ -1,625 +1,625 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfblendfunction.h" - -#include - -namespace pdf -{ - -constexpr const std::pair BLEND_MODE_INFOS[] = -{ - { "Normal", BlendMode::Normal }, - { "Multiply", BlendMode::Multiply }, - { "Screen", BlendMode::Screen }, - { "Overlay", BlendMode::Overlay }, - { "Darken", BlendMode::Darken }, - { "Lighten", BlendMode::Lighten }, - { "ColorDodge", BlendMode::ColorDodge }, - { "ColorBurn", BlendMode::ColorBurn }, - { "HardLight", BlendMode::HardLight }, - { "SoftLight", BlendMode::SoftLight }, - { "Difference", BlendMode::Difference }, - { "Exclusion", BlendMode::Exclusion }, - { "Hue", BlendMode::Hue }, - { "Saturation", BlendMode::Saturation }, - { "Color", BlendMode::Color }, - { "Luminosity", BlendMode::Luminosity }, - { "Compatible", BlendMode::Compatible } -}; - -BlendMode PDFBlendModeInfo::getBlendMode(const QByteArray& name) -{ - for (const std::pair& info : BLEND_MODE_INFOS) - { - if (info.first == name) - { - return info.second; - } - } - - return BlendMode::Invalid; -} - -bool PDFBlendModeInfo::isSupportedByQt(BlendMode mode) -{ - switch (mode) - { - case BlendMode::Normal: - case BlendMode::Multiply: - case BlendMode::Screen: - case BlendMode::Overlay: - case BlendMode::Darken: - case BlendMode::Lighten: - case BlendMode::ColorDodge: - case BlendMode::ColorBurn: - case BlendMode::HardLight: - case BlendMode::SoftLight: - case BlendMode::Difference: - case BlendMode::Exclusion: - case BlendMode::Compatible: - return true; - - default: - return false; - } -} - -bool PDFBlendModeInfo::isSeparable(BlendMode mode) -{ - switch (mode) - { - case BlendMode::Normal: - case BlendMode::Multiply: - case BlendMode::Screen: - case BlendMode::Overlay: - case BlendMode::Darken: - case BlendMode::Lighten: - case BlendMode::ColorDodge: - case BlendMode::ColorBurn: - case BlendMode::HardLight: - case BlendMode::SoftLight: - case BlendMode::Difference: - case BlendMode::Exclusion: - case BlendMode::Compatible: - case BlendMode::Overprint_SelectBackdrop: - case BlendMode::Overprint_SelectNonZeroSourceOrBackdrop: - case BlendMode::Overprint_SelectNonOneSourceOrBackdrop: - return true; - - case BlendMode::Hue: - case BlendMode::Saturation: - case BlendMode::Color: - case BlendMode::Luminosity: - return false; - - default: - Q_ASSERT(false); - return false; - } -} - -bool PDFBlendModeInfo::isWhitePreserving(BlendMode mode) -{ - if (!isSeparable(mode)) - { - return false; - } - - if (mode == BlendMode::Difference || mode == BlendMode::Exclusion) - { - return false; - } - - return true; -} - -QPainter::CompositionMode PDFBlendModeInfo::getCompositionModeFromBlendMode(BlendMode mode) -{ - switch (mode) - { - case BlendMode::Normal: - return QPainter::CompositionMode_SourceOver; - case BlendMode::Multiply: - return QPainter::CompositionMode_Multiply; - case BlendMode::Screen: - return QPainter::CompositionMode_Screen; - case BlendMode::Overlay: - return QPainter::CompositionMode_Overlay; - case BlendMode::Darken: - return QPainter::CompositionMode_Darken; - case BlendMode::Lighten: - return QPainter::CompositionMode_Lighten; - case BlendMode::ColorDodge: - return QPainter::CompositionMode_ColorDodge; - case BlendMode::ColorBurn: - return QPainter::CompositionMode_ColorBurn; - case BlendMode::HardLight: - return QPainter::CompositionMode_HardLight; - case BlendMode::SoftLight: - return QPainter::CompositionMode_SoftLight; - case BlendMode::Difference: - return QPainter::CompositionMode_Difference; - case BlendMode::Exclusion: - return QPainter::CompositionMode_Exclusion; - case BlendMode::Compatible: - return QPainter::CompositionMode_SourceOver; - - default: - break; - } - - return QPainter::CompositionMode_SourceOver; -} - -QString PDFBlendModeInfo::getBlendModeName(BlendMode mode) -{ - for (const std::pair& info : BLEND_MODE_INFOS) - { - if (info.second == mode) - { - return QString::fromLatin1(info.first); - } - } - - return "Unknown"; -} - -QString PDFBlendModeInfo::getBlendModeTranslatedName(BlendMode mode) -{ - switch (mode) - { - case BlendMode::Normal: - case BlendMode::Compatible: - return PDFTranslationContext::tr("Normal"); - case BlendMode::Multiply: - return PDFTranslationContext::tr("Multiply"); - case BlendMode::Screen: - return PDFTranslationContext::tr("Screen"); - case BlendMode::Overlay: - return PDFTranslationContext::tr("Overlay"); - case BlendMode::Darken: - return PDFTranslationContext::tr("Darken"); - case BlendMode::Lighten: - return PDFTranslationContext::tr("Lighten"); - case BlendMode::ColorDodge: - return PDFTranslationContext::tr("ColorDodge"); - case BlendMode::ColorBurn: - return PDFTranslationContext::tr("ColorBurn"); - case BlendMode::HardLight: - return PDFTranslationContext::tr("HardLight"); - case BlendMode::SoftLight: - return PDFTranslationContext::tr("SoftLight"); - case BlendMode::Difference: - return PDFTranslationContext::tr("Difference"); - case BlendMode::Exclusion: - return PDFTranslationContext::tr("Exclusion"); - case BlendMode::Hue: - return PDFTranslationContext::tr("Hue"); - case BlendMode::Saturation: - return PDFTranslationContext::tr("Saturation"); - case BlendMode::Color: - return PDFTranslationContext::tr("Color"); - case BlendMode::Luminosity: - return PDFTranslationContext::tr("Luminosity"); - - default: - break; - } - - return PDFTranslationContext::tr("Unknown"); -} - -std::vector PDFBlendModeInfo::getBlendModes() -{ - return - { - BlendMode::Normal, - BlendMode::Multiply, - BlendMode::Screen, - BlendMode::Overlay, - BlendMode::Darken, - BlendMode::Lighten, - BlendMode::ColorDodge, - BlendMode::ColorBurn, - BlendMode::HardLight, - BlendMode::SoftLight, - BlendMode::Difference, - BlendMode::Exclusion, - BlendMode::Hue, - BlendMode::Saturation, - BlendMode::Color, - BlendMode::Luminosity - }; -} - -PDFColorComponent PDFBlendFunction::blend(BlendMode mode, PDFColorComponent Cb, PDFColorComponent Cs) -{ - switch (mode) - { - case BlendMode::Normal: - case BlendMode::Compatible: - return Cs; - - case BlendMode::Multiply: - return Cb * Cs; - - case BlendMode::Screen: - return Cb + Cs - Cb * Cs; - - case BlendMode::Overlay: - return blend(BlendMode::HardLight, Cs, Cb); - - case BlendMode::Darken: - return qMin(Cb, Cs); - - case BlendMode::Lighten: - return qMax(Cb, Cs); - - case BlendMode::ColorDodge: - { - if (qFuzzyIsNull(Cb)) - { - return 0.0f; - } - - const PDFColorComponent CsInverted = 1.0f - Cs; - if (Cb >= CsInverted) - { - return 1.0f; - } - - return Cb / CsInverted; - } - - case BlendMode::ColorBurn: - { - const PDFColorComponent CbInverted = 1.0f - Cb; - if (qFuzzyIsNull(CbInverted)) - { - return 1.0f; - } - - if (CbInverted >= Cs) - { - return 0.0f; - } - - return 1.0f - CbInverted / Cs; - } - - case BlendMode::HardLight: - { - if (Cs <= 0.5f) - { - return blend(BlendMode::Multiply, Cb, 2.0f * Cs); - } - else - { - return blend(BlendMode::Screen, Cb, 2.0f * Cs - 1.0f); - } - } - - case BlendMode::SoftLight: - { - if (Cs <= 0.5f) - { - return Cb - (1.0f - 2.0f * Cs) * Cb * (1.0f - Cb); - } - else - { - PDFColorComponent D = 0.0f; - if (Cb <= 0.25) - { - D = ((16.0f * Cb - 12.0f) * Cb + 4.0f) * Cb; - } - else - { - D = std::sqrt(Cb); - } - return Cb + (2.0f * Cs - 1.0f) * (D - Cb); - } - } - - case BlendMode::Difference: - return qAbs(Cb - Cs); - - case BlendMode::Exclusion: - return Cb + Cs - 2.0f * Cb * Cs; - - case BlendMode::Overprint_SelectBackdrop: - return Cb; - - case BlendMode::Overprint_SelectNonZeroSourceOrBackdrop: - { - if (qFuzzyIsNull(Cs)) - { - return Cb; - } - - return Cs; - } - - case BlendMode::Overprint_SelectNonOneSourceOrBackdrop: - { - if (qFuzzyIsNull(1.0f - Cs)) - { - return Cb; - } - - return Cs; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - return Cs; -} - -PDFRGB PDFBlendFunction::blend_Hue(PDFRGB Cb, PDFRGB Cs) -{ - return nonseparable_SetLum(nonseparable_SetSat(Cs, nonseparable_Sat(Cb)), nonseparable_Lum(Cb)); -} - -PDFRGB PDFBlendFunction::blend_Saturation(PDFRGB Cb, PDFRGB Cs) -{ - return nonseparable_SetLum(nonseparable_SetSat(Cb, nonseparable_Sat(Cs)), nonseparable_Lum(Cb)); -} - -PDFRGB PDFBlendFunction::blend_Color(PDFRGB Cb, PDFRGB Cs) -{ - return nonseparable_SetLum(Cs, nonseparable_Lum(Cb)); -} - -PDFRGB PDFBlendFunction::blend_Luminosity(PDFRGB Cb, PDFRGB Cs) -{ - return nonseparable_SetLum(Cb, nonseparable_Lum(Cs)); -} - -PDFGray PDFBlendFunction::blend_Hue(PDFGray Cb, PDFGray Cs) -{ - return nonseparable_rgb2gray(blend_Hue(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); -} - -PDFGray PDFBlendFunction::blend_Saturation(PDFGray Cb, PDFGray Cs) -{ - return nonseparable_rgb2gray(blend_Saturation(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); -} - -PDFGray PDFBlendFunction::blend_Color(PDFGray Cb, PDFGray Cs) -{ - return nonseparable_rgb2gray(blend_Color(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); -} - -PDFGray PDFBlendFunction::blend_Luminosity(PDFGray Cb, PDFGray Cs) -{ - return nonseparable_rgb2gray(blend_Luminosity(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); -} - -PDFCMYK PDFBlendFunction::blend_Hue(PDFCMYK Cb, PDFCMYK Cs) -{ - return nonseparable_rgb2cmyk(blend_Hue(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); -} - -PDFCMYK PDFBlendFunction::blend_Saturation(PDFCMYK Cb, PDFCMYK Cs) -{ - return nonseparable_rgb2cmyk(blend_Saturation(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); -} - -PDFCMYK PDFBlendFunction::blend_Color(PDFCMYK Cb, PDFCMYK Cs) -{ - return nonseparable_rgb2cmyk(blend_Color(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); -} - -PDFCMYK PDFBlendFunction::blend_Luminosity(PDFCMYK Cb, PDFCMYK Cs) -{ - return nonseparable_rgb2cmyk(blend_Luminosity(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cs[3]); -} - -PDFGray PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFGray Cb, PDFGray Cs) -{ - switch (mode) - { - case BlendMode::Hue: - return blend_Hue(Cb, Cs); - - case BlendMode::Saturation: - return blend_Saturation(Cb, Cs); - - case BlendMode::Color: - return blend_Color(Cb, Cs); - - case BlendMode::Luminosity: - return blend_Luminosity(Cb, Cs); - - default: - Q_ASSERT(false); - break; - } - - return Cs; -} - -PDFRGB PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFRGB Cb, PDFRGB Cs) -{ - switch (mode) - { - case BlendMode::Hue: - return blend_Hue(Cb, Cs); - - case BlendMode::Saturation: - return blend_Saturation(Cb, Cs); - - case BlendMode::Color: - return blend_Color(Cb, Cs); - - case BlendMode::Luminosity: - return blend_Luminosity(Cb, Cs); - - default: - Q_ASSERT(false); - break; - } - - return Cs; -} - -PDFCMYK PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK Cs) -{ - switch (mode) - { - case BlendMode::Hue: - return blend_Hue(Cb, Cs); - - case BlendMode::Saturation: - return blend_Saturation(Cb, Cs); - - case BlendMode::Color: - return blend_Color(Cb, Cs); - - case BlendMode::Luminosity: - return blend_Luminosity(Cb, Cs); - - default: - Q_ASSERT(false); - break; - } - - return Cs; -} - -PDFColorComponent PDFBlendFunction::getLuminosity(PDFGray gray) -{ - return nonseparable_Lum(nonseparable_gray2rgb(gray)); -} - -PDFColorComponent PDFBlendFunction::getLuminosity(PDFRGB rgb) -{ - return nonseparable_Lum(rgb); -} - -PDFColorComponent PDFBlendFunction::getLuminosity(PDFCMYK cmyk) -{ - // This is according to chapter 11.5.3, deriving soft mask from a group luminosity - return 1.0f - qMin(1.0f, 0.30f * cmyk[0] + 0.59f * cmyk[1] + 0.11f * cmyk[2] + cmyk[3]); -} - -PDFRGB PDFBlendFunction::nonseparable_gray2rgb(PDFGray gray) -{ - return nonseparable_SetLum(PDFRGB{ 0.0f, 0.0f, 0.0f }, gray); -} - -PDFGray PDFBlendFunction::nonseparable_rgb2gray(PDFRGB rgb) -{ - // Just convert to luminosity - return nonseparable_Lum(rgb); -} - -PDFRGB PDFBlendFunction::nonseparable_cmyk2rgb(PDFCMYK cmyk) -{ - return PDFRGB{ 1.0f - cmyk[0], 1.0f - cmyk[1], 1.0f - cmyk[2] }; -} - -PDFCMYK PDFBlendFunction::nonseparable_rgb2cmyk(PDFRGB rgb, PDFColorComponent K) -{ - return PDFCMYK{ 1.0f - rgb[0], 1.0f - rgb[1], 1.0f - rgb[2], K }; -} - -PDFColorComponent PDFBlendFunction::nonseparable_Lum(PDFRGB rgb) -{ - return 0.30 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]; -} - -PDFColorComponent PDFBlendFunction::nonseparable_Sat(PDFRGB rgb) -{ - const PDFColorComponent min = *std::min_element(rgb.cbegin(), rgb.cend()); - const PDFColorComponent max = *std::max_element(rgb.cbegin(), rgb.cend()); - return max - min; -} - -PDFRGB PDFBlendFunction::nonseparable_SetLum(PDFRGB C, PDFColorComponent l) -{ - const PDFColorComponent d = l - nonseparable_Lum(C); - PDFRGB result = C; - result[0] += d; - result[1] += d; - result[2] += d; - return nonseparable_ClipColor(result); -} - -PDFRGB PDFBlendFunction::nonseparable_SetSat(PDFRGB C, PDFColorComponent s) -{ - auto it_min = std::min_element(C.begin(), C.end()); - auto it_max = std::max_element(C.begin(), C.end()); - auto it_mid = C.end(); - for (auto it = C.begin(); it != C.end(); ++it) - { - if (it != it_min && it != it_max) - { - it_mid = it; - break; - } - } - Q_ASSERT(it_mid != C.end()); - - PDFRGB result = C; - if (*it_max > *it_min) - { - *it_mid = (*it_mid - *it_min) * s / (*it_max - *it_min); - *it_max = s; - result = C; - } - else - { - std::fill(result.begin(), result.end(), 0.0f); - } - - return result; -} - -PDFRGB PDFBlendFunction::nonseparable_ClipColor(PDFRGB C) -{ - PDFRGB result = C; - const PDFColorComponent l = nonseparable_Lum(C); - const PDFColorComponent n = *std::min_element(C.cbegin(), C.cend()); - const PDFColorComponent x = *std::max_element(C.cbegin(), C.cend()); - - if (n < 0.0f) - { - const PDFColorComponent factor = 1.0f / (l - n); - result[0] = l + (result[0] - l) * l * factor; - result[1] = l + (result[1] - l) * l * factor; - result[2] = l + (result[2] - l) * l * factor; - } - - if (x > 1.0f) - { - const PDFColorComponent factor = 1.0f / (x - l); - result[0] = l + (result[0] - l) * (1.0f - l) * factor; - result[1] = l + (result[1] - l) * (1.0f - l) * factor; - result[2] = l + (result[2] - l) * (1.0f - l) * factor; - } - - return result; -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfblendfunction.h" + +#include + +namespace pdf +{ + +constexpr const std::pair BLEND_MODE_INFOS[] = +{ + { "Normal", BlendMode::Normal }, + { "Multiply", BlendMode::Multiply }, + { "Screen", BlendMode::Screen }, + { "Overlay", BlendMode::Overlay }, + { "Darken", BlendMode::Darken }, + { "Lighten", BlendMode::Lighten }, + { "ColorDodge", BlendMode::ColorDodge }, + { "ColorBurn", BlendMode::ColorBurn }, + { "HardLight", BlendMode::HardLight }, + { "SoftLight", BlendMode::SoftLight }, + { "Difference", BlendMode::Difference }, + { "Exclusion", BlendMode::Exclusion }, + { "Hue", BlendMode::Hue }, + { "Saturation", BlendMode::Saturation }, + { "Color", BlendMode::Color }, + { "Luminosity", BlendMode::Luminosity }, + { "Compatible", BlendMode::Compatible } +}; + +BlendMode PDFBlendModeInfo::getBlendMode(const QByteArray& name) +{ + for (const std::pair& info : BLEND_MODE_INFOS) + { + if (info.first == name) + { + return info.second; + } + } + + return BlendMode::Invalid; +} + +bool PDFBlendModeInfo::isSupportedByQt(BlendMode mode) +{ + switch (mode) + { + case BlendMode::Normal: + case BlendMode::Multiply: + case BlendMode::Screen: + case BlendMode::Overlay: + case BlendMode::Darken: + case BlendMode::Lighten: + case BlendMode::ColorDodge: + case BlendMode::ColorBurn: + case BlendMode::HardLight: + case BlendMode::SoftLight: + case BlendMode::Difference: + case BlendMode::Exclusion: + case BlendMode::Compatible: + return true; + + default: + return false; + } +} + +bool PDFBlendModeInfo::isSeparable(BlendMode mode) +{ + switch (mode) + { + case BlendMode::Normal: + case BlendMode::Multiply: + case BlendMode::Screen: + case BlendMode::Overlay: + case BlendMode::Darken: + case BlendMode::Lighten: + case BlendMode::ColorDodge: + case BlendMode::ColorBurn: + case BlendMode::HardLight: + case BlendMode::SoftLight: + case BlendMode::Difference: + case BlendMode::Exclusion: + case BlendMode::Compatible: + case BlendMode::Overprint_SelectBackdrop: + case BlendMode::Overprint_SelectNonZeroSourceOrBackdrop: + case BlendMode::Overprint_SelectNonOneSourceOrBackdrop: + return true; + + case BlendMode::Hue: + case BlendMode::Saturation: + case BlendMode::Color: + case BlendMode::Luminosity: + return false; + + default: + Q_ASSERT(false); + return false; + } +} + +bool PDFBlendModeInfo::isWhitePreserving(BlendMode mode) +{ + if (!isSeparable(mode)) + { + return false; + } + + if (mode == BlendMode::Difference || mode == BlendMode::Exclusion) + { + return false; + } + + return true; +} + +QPainter::CompositionMode PDFBlendModeInfo::getCompositionModeFromBlendMode(BlendMode mode) +{ + switch (mode) + { + case BlendMode::Normal: + return QPainter::CompositionMode_SourceOver; + case BlendMode::Multiply: + return QPainter::CompositionMode_Multiply; + case BlendMode::Screen: + return QPainter::CompositionMode_Screen; + case BlendMode::Overlay: + return QPainter::CompositionMode_Overlay; + case BlendMode::Darken: + return QPainter::CompositionMode_Darken; + case BlendMode::Lighten: + return QPainter::CompositionMode_Lighten; + case BlendMode::ColorDodge: + return QPainter::CompositionMode_ColorDodge; + case BlendMode::ColorBurn: + return QPainter::CompositionMode_ColorBurn; + case BlendMode::HardLight: + return QPainter::CompositionMode_HardLight; + case BlendMode::SoftLight: + return QPainter::CompositionMode_SoftLight; + case BlendMode::Difference: + return QPainter::CompositionMode_Difference; + case BlendMode::Exclusion: + return QPainter::CompositionMode_Exclusion; + case BlendMode::Compatible: + return QPainter::CompositionMode_SourceOver; + + default: + break; + } + + return QPainter::CompositionMode_SourceOver; +} + +QString PDFBlendModeInfo::getBlendModeName(BlendMode mode) +{ + for (const std::pair& info : BLEND_MODE_INFOS) + { + if (info.second == mode) + { + return QString::fromLatin1(info.first); + } + } + + return "Unknown"; +} + +QString PDFBlendModeInfo::getBlendModeTranslatedName(BlendMode mode) +{ + switch (mode) + { + case BlendMode::Normal: + case BlendMode::Compatible: + return PDFTranslationContext::tr("Normal"); + case BlendMode::Multiply: + return PDFTranslationContext::tr("Multiply"); + case BlendMode::Screen: + return PDFTranslationContext::tr("Screen"); + case BlendMode::Overlay: + return PDFTranslationContext::tr("Overlay"); + case BlendMode::Darken: + return PDFTranslationContext::tr("Darken"); + case BlendMode::Lighten: + return PDFTranslationContext::tr("Lighten"); + case BlendMode::ColorDodge: + return PDFTranslationContext::tr("ColorDodge"); + case BlendMode::ColorBurn: + return PDFTranslationContext::tr("ColorBurn"); + case BlendMode::HardLight: + return PDFTranslationContext::tr("HardLight"); + case BlendMode::SoftLight: + return PDFTranslationContext::tr("SoftLight"); + case BlendMode::Difference: + return PDFTranslationContext::tr("Difference"); + case BlendMode::Exclusion: + return PDFTranslationContext::tr("Exclusion"); + case BlendMode::Hue: + return PDFTranslationContext::tr("Hue"); + case BlendMode::Saturation: + return PDFTranslationContext::tr("Saturation"); + case BlendMode::Color: + return PDFTranslationContext::tr("Color"); + case BlendMode::Luminosity: + return PDFTranslationContext::tr("Luminosity"); + + default: + break; + } + + return PDFTranslationContext::tr("Unknown"); +} + +std::vector PDFBlendModeInfo::getBlendModes() +{ + return + { + BlendMode::Normal, + BlendMode::Multiply, + BlendMode::Screen, + BlendMode::Overlay, + BlendMode::Darken, + BlendMode::Lighten, + BlendMode::ColorDodge, + BlendMode::ColorBurn, + BlendMode::HardLight, + BlendMode::SoftLight, + BlendMode::Difference, + BlendMode::Exclusion, + BlendMode::Hue, + BlendMode::Saturation, + BlendMode::Color, + BlendMode::Luminosity + }; +} + +PDFColorComponent PDFBlendFunction::blend(BlendMode mode, PDFColorComponent Cb, PDFColorComponent Cs) +{ + switch (mode) + { + case BlendMode::Normal: + case BlendMode::Compatible: + return Cs; + + case BlendMode::Multiply: + return Cb * Cs; + + case BlendMode::Screen: + return Cb + Cs - Cb * Cs; + + case BlendMode::Overlay: + return blend(BlendMode::HardLight, Cs, Cb); + + case BlendMode::Darken: + return qMin(Cb, Cs); + + case BlendMode::Lighten: + return qMax(Cb, Cs); + + case BlendMode::ColorDodge: + { + if (qFuzzyIsNull(Cb)) + { + return 0.0f; + } + + const PDFColorComponent CsInverted = 1.0f - Cs; + if (Cb >= CsInverted) + { + return 1.0f; + } + + return Cb / CsInverted; + } + + case BlendMode::ColorBurn: + { + const PDFColorComponent CbInverted = 1.0f - Cb; + if (qFuzzyIsNull(CbInverted)) + { + return 1.0f; + } + + if (CbInverted >= Cs) + { + return 0.0f; + } + + return 1.0f - CbInverted / Cs; + } + + case BlendMode::HardLight: + { + if (Cs <= 0.5f) + { + return blend(BlendMode::Multiply, Cb, 2.0f * Cs); + } + else + { + return blend(BlendMode::Screen, Cb, 2.0f * Cs - 1.0f); + } + } + + case BlendMode::SoftLight: + { + if (Cs <= 0.5f) + { + return Cb - (1.0f - 2.0f * Cs) * Cb * (1.0f - Cb); + } + else + { + PDFColorComponent D = 0.0f; + if (Cb <= 0.25) + { + D = ((16.0f * Cb - 12.0f) * Cb + 4.0f) * Cb; + } + else + { + D = std::sqrt(Cb); + } + return Cb + (2.0f * Cs - 1.0f) * (D - Cb); + } + } + + case BlendMode::Difference: + return qAbs(Cb - Cs); + + case BlendMode::Exclusion: + return Cb + Cs - 2.0f * Cb * Cs; + + case BlendMode::Overprint_SelectBackdrop: + return Cb; + + case BlendMode::Overprint_SelectNonZeroSourceOrBackdrop: + { + if (qFuzzyIsNull(Cs)) + { + return Cb; + } + + return Cs; + } + + case BlendMode::Overprint_SelectNonOneSourceOrBackdrop: + { + if (qFuzzyIsNull(1.0f - Cs)) + { + return Cb; + } + + return Cs; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + return Cs; +} + +PDFRGB PDFBlendFunction::blend_Hue(PDFRGB Cb, PDFRGB Cs) +{ + return nonseparable_SetLum(nonseparable_SetSat(Cs, nonseparable_Sat(Cb)), nonseparable_Lum(Cb)); +} + +PDFRGB PDFBlendFunction::blend_Saturation(PDFRGB Cb, PDFRGB Cs) +{ + return nonseparable_SetLum(nonseparable_SetSat(Cb, nonseparable_Sat(Cs)), nonseparable_Lum(Cb)); +} + +PDFRGB PDFBlendFunction::blend_Color(PDFRGB Cb, PDFRGB Cs) +{ + return nonseparable_SetLum(Cs, nonseparable_Lum(Cb)); +} + +PDFRGB PDFBlendFunction::blend_Luminosity(PDFRGB Cb, PDFRGB Cs) +{ + return nonseparable_SetLum(Cb, nonseparable_Lum(Cs)); +} + +PDFGray PDFBlendFunction::blend_Hue(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Hue(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFGray PDFBlendFunction::blend_Saturation(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Saturation(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFGray PDFBlendFunction::blend_Color(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Color(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFGray PDFBlendFunction::blend_Luminosity(PDFGray Cb, PDFGray Cs) +{ + return nonseparable_rgb2gray(blend_Luminosity(nonseparable_gray2rgb(Cb), nonseparable_gray2rgb(Cs))); +} + +PDFCMYK PDFBlendFunction::blend_Hue(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Hue(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); +} + +PDFCMYK PDFBlendFunction::blend_Saturation(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Saturation(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); +} + +PDFCMYK PDFBlendFunction::blend_Color(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Color(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cb[3]); +} + +PDFCMYK PDFBlendFunction::blend_Luminosity(PDFCMYK Cb, PDFCMYK Cs) +{ + return nonseparable_rgb2cmyk(blend_Luminosity(nonseparable_cmyk2rgb(Cb), nonseparable_cmyk2rgb(Cs)), Cs[3]); +} + +PDFGray PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFGray Cb, PDFGray Cs) +{ + switch (mode) + { + case BlendMode::Hue: + return blend_Hue(Cb, Cs); + + case BlendMode::Saturation: + return blend_Saturation(Cb, Cs); + + case BlendMode::Color: + return blend_Color(Cb, Cs); + + case BlendMode::Luminosity: + return blend_Luminosity(Cb, Cs); + + default: + Q_ASSERT(false); + break; + } + + return Cs; +} + +PDFRGB PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFRGB Cb, PDFRGB Cs) +{ + switch (mode) + { + case BlendMode::Hue: + return blend_Hue(Cb, Cs); + + case BlendMode::Saturation: + return blend_Saturation(Cb, Cs); + + case BlendMode::Color: + return blend_Color(Cb, Cs); + + case BlendMode::Luminosity: + return blend_Luminosity(Cb, Cs); + + default: + Q_ASSERT(false); + break; + } + + return Cs; +} + +PDFCMYK PDFBlendFunction::blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK Cs) +{ + switch (mode) + { + case BlendMode::Hue: + return blend_Hue(Cb, Cs); + + case BlendMode::Saturation: + return blend_Saturation(Cb, Cs); + + case BlendMode::Color: + return blend_Color(Cb, Cs); + + case BlendMode::Luminosity: + return blend_Luminosity(Cb, Cs); + + default: + Q_ASSERT(false); + break; + } + + return Cs; +} + +PDFColorComponent PDFBlendFunction::getLuminosity(PDFGray gray) +{ + return nonseparable_Lum(nonseparable_gray2rgb(gray)); +} + +PDFColorComponent PDFBlendFunction::getLuminosity(PDFRGB rgb) +{ + return nonseparable_Lum(rgb); +} + +PDFColorComponent PDFBlendFunction::getLuminosity(PDFCMYK cmyk) +{ + // This is according to chapter 11.5.3, deriving soft mask from a group luminosity + return 1.0f - qMin(1.0f, 0.30f * cmyk[0] + 0.59f * cmyk[1] + 0.11f * cmyk[2] + cmyk[3]); +} + +PDFRGB PDFBlendFunction::nonseparable_gray2rgb(PDFGray gray) +{ + return nonseparable_SetLum(PDFRGB{ 0.0f, 0.0f, 0.0f }, gray); +} + +PDFGray PDFBlendFunction::nonseparable_rgb2gray(PDFRGB rgb) +{ + // Just convert to luminosity + return nonseparable_Lum(rgb); +} + +PDFRGB PDFBlendFunction::nonseparable_cmyk2rgb(PDFCMYK cmyk) +{ + return PDFRGB{ 1.0f - cmyk[0], 1.0f - cmyk[1], 1.0f - cmyk[2] }; +} + +PDFCMYK PDFBlendFunction::nonseparable_rgb2cmyk(PDFRGB rgb, PDFColorComponent K) +{ + return PDFCMYK{ 1.0f - rgb[0], 1.0f - rgb[1], 1.0f - rgb[2], K }; +} + +PDFColorComponent PDFBlendFunction::nonseparable_Lum(PDFRGB rgb) +{ + return 0.30 * rgb[0] + 0.59 * rgb[1] + 0.11 * rgb[2]; +} + +PDFColorComponent PDFBlendFunction::nonseparable_Sat(PDFRGB rgb) +{ + const PDFColorComponent min = *std::min_element(rgb.cbegin(), rgb.cend()); + const PDFColorComponent max = *std::max_element(rgb.cbegin(), rgb.cend()); + return max - min; +} + +PDFRGB PDFBlendFunction::nonseparable_SetLum(PDFRGB C, PDFColorComponent l) +{ + const PDFColorComponent d = l - nonseparable_Lum(C); + PDFRGB result = C; + result[0] += d; + result[1] += d; + result[2] += d; + return nonseparable_ClipColor(result); +} + +PDFRGB PDFBlendFunction::nonseparable_SetSat(PDFRGB C, PDFColorComponent s) +{ + auto it_min = std::min_element(C.begin(), C.end()); + auto it_max = std::max_element(C.begin(), C.end()); + auto it_mid = C.end(); + for (auto it = C.begin(); it != C.end(); ++it) + { + if (it != it_min && it != it_max) + { + it_mid = it; + break; + } + } + Q_ASSERT(it_mid != C.end()); + + PDFRGB result = C; + if (*it_max > *it_min) + { + *it_mid = (*it_mid - *it_min) * s / (*it_max - *it_min); + *it_max = s; + result = C; + } + else + { + std::fill(result.begin(), result.end(), 0.0f); + } + + return result; +} + +PDFRGB PDFBlendFunction::nonseparable_ClipColor(PDFRGB C) +{ + PDFRGB result = C; + const PDFColorComponent l = nonseparable_Lum(C); + const PDFColorComponent n = *std::min_element(C.cbegin(), C.cend()); + const PDFColorComponent x = *std::max_element(C.cbegin(), C.cend()); + + if (n < 0.0f) + { + const PDFColorComponent factor = 1.0f / (l - n); + result[0] = l + (result[0] - l) * l * factor; + result[1] = l + (result[1] - l) * l * factor; + result[2] = l + (result[2] - l) * l * factor; + } + + if (x > 1.0f) + { + const PDFColorComponent factor = 1.0f / (x - l); + result[0] = l + (result[0] - l) * (1.0f - l) * factor; + result[1] = l + (result[1] - l) * (1.0f - l) * factor; + result[2] = l + (result[2] - l) * (1.0f - l) * factor; + } + + return result; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfblendfunction.h b/Pdf4QtLib/sources/pdfblendfunction.h index 8152a49..3f6ede5 100644 --- a/Pdf4QtLib/sources/pdfblendfunction.h +++ b/Pdf4QtLib/sources/pdfblendfunction.h @@ -1,224 +1,224 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFBLENDFUNCTION_H -#define PDFBLENDFUNCTION_H - -#include "pdfglobal.h" - -#include - -namespace pdf -{ - -enum class BlendMode -{ - // Separable blending modes - Normal, ///< Select source color, backdrop is ignored: B(source, dest) = source - Multiply, ///< Backdrop and source colors are multiplied: B(source, dest) = source * dest - Screen, ///< B(source, dest) = 1.0 - (1.0 - source) * (1.0 - dest) + source + dest - source * dest - Overlay, ///< B(source, dest) = HardLight(dest, source) - Darken, ///< Selects the darker of the colors. B(source, dest) = qMin(source, dest) - Lighten, ///< Selects the lighter of the colors. B(source, dest) = qMax(source, dest) - ColorDodge, ///< Brightens the backdrop color reflecting the source color. B(source, dest) = qMin(1.0, dest / (1.0 - source)), or 1.0, if source is 1.0 - ColorBurn, ///< Darkens the backdrop color reflecting the source color. B(source, dest) = 1.0 - qMin(1.0, (1.0 - dest) / source), or 0.0, if source is 0.0 - HardLight, ///< Multiply or screen the color, depending on the source value. - SoftLight, ///< Darkens or lightens the color, depending on the source value. - Difference, ///< Subtract the darker of colors from the lighter color. B(source, dest) = qAbs(source - dest) - Exclusion, ///< B(source, dest) = source + dest - 2 * source * dest - - // Non-separable blending modes - Hue, - Saturation, - Color, - Luminosity, - - // For compatibility - older PDF specification specifies Compatible mode, which is equal - // to normal. It should be recognized for sake of compatibility. - Compatible, ///< Equals to normal - - // Special blend modes for handling overprint. Used only internally. - Overprint_SelectBackdrop, - Overprint_SelectNonZeroSourceOrBackdrop, - Overprint_SelectNonOneSourceOrBackdrop, - - // Invalid blending mode - for internal purposes only - Invalid -}; - -class PDFBlendModeInfo -{ -public: - PDFBlendModeInfo() = delete; - - /// Returns blend mode from the specified string. If string is invalid, - /// then \p Invalid blend mode is returned. - /// \param name Name of the blend mode - static BlendMode getBlendMode(const QByteArray& name); - - /// Returns true, if blend mode is supported by Qt drawing subsystem - /// \param mode Blend mode - static bool isSupportedByQt(BlendMode mode); - - /// Returns true, if blend mode is separable - static bool isSeparable(BlendMode mode); - - /// Returns true, if blend mode is white-preserving (i.e. B(1.0, 1.0) == 1.0) - static bool isWhitePreserving(BlendMode mode); - - /// Returns composition mode for Qt drawing subsystem from blend mode defined - /// in PDF standard. If blend mode is not supported by Qt drawing subsystem, then default - /// composition mode is returned. - /// \param mode Blend mode - static QPainter::CompositionMode getCompositionModeFromBlendMode(BlendMode mode); - - /// Returns blend mode name - /// \param mode Blend mode - static QString getBlendModeName(BlendMode mode); - - /// Returns blend mode translated name - /// \param mode Blend mode - static QString getBlendModeTranslatedName(BlendMode mode); - - /// Returns vector of all blend modes, excluding duplicate ones (for example, - /// Compatible mode is equal to Normal blend mode) - static std::vector getBlendModes(); -}; - -/// Class grouping together blend functions. Can also blend non-separable blend modes, -/// such as Color, Hue, Saturation and Luminosity, according 11.3.5.3 of PDF 2.0 specification. -class PDFBlendFunction -{ -public: - PDFBlendFunction() = delete; - - /// Blend function used to blend separable blend modes - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFColorComponent blend(BlendMode mode, PDFColorComponent Cb, PDFColorComponent Cs); - - /// Blend non-separable hue function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFRGB blend_Hue(PDFRGB Cb, PDFRGB Cs); - - /// Blend non-separable saturation function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFRGB blend_Saturation(PDFRGB Cb, PDFRGB Cs); - - /// Blend non-separable color function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFRGB blend_Color(PDFRGB Cb, PDFRGB Cs); - - /// Blend non-separable luminosity function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFRGB blend_Luminosity(PDFRGB Cb, PDFRGB Cs); - - /// Blend non-separable hue function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFGray blend_Hue(PDFGray Cb, PDFGray Cs); - - /// Blend non-separable saturation function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFGray blend_Saturation(PDFGray Cb, PDFGray Cs); - - /// Blend non-separable color function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFGray blend_Color(PDFGray Cb, PDFGray Cs); - - /// Blend non-separable luminosity function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFGray blend_Luminosity(PDFGray Cb, PDFGray Cs); - - /// Blend non-separable hue function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFCMYK blend_Hue(PDFCMYK Cb, PDFCMYK Cs); - - /// Blend non-separable saturation function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFCMYK blend_Saturation(PDFCMYK Cb, PDFCMYK Cs); - - /// Blend non-separable color function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFCMYK blend_Color(PDFCMYK Cb, PDFCMYK Cs); - - /// Blend non-separable luminosity function - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFCMYK blend_Luminosity(PDFCMYK Cb, PDFCMYK Cs); - - /// Blend non-separabe. It is incorrect to call this function - /// with blend mode, which is separable. - /// \param mode Non-separable blend mode - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFGray blend_Nonseparable(BlendMode mode, PDFGray Cb, PDFGray Cs); - - /// Blend non-separabe. It is incorrect to call this function - /// with blend mode, which is separable. - /// \param mode Non-separable blend mode - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFRGB blend_Nonseparable(BlendMode mode, PDFRGB Cb, PDFRGB Cs); - - /// Blend non-separabe. It is incorrect to call this function - /// with blend mode, which is separable. - /// \param mode Non-separable blend mode - /// \param Cb Backdrop color - /// \param Cs Source color - static PDFCMYK blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK Cs); - - /// Get luminosity from color value - /// \param gray Color value - static PDFColorComponent getLuminosity(PDFGray gray); - - /// Get luminosity from color value - /// \param rgb Color value - static PDFColorComponent getLuminosity(PDFRGB rgb); - - /// Get luminosity from color value - /// \param cmyk Color value - static PDFColorComponent getLuminosity(PDFCMYK cmyk); - - /// Union function - static constexpr PDFColorComponent blend_Union(PDFColorComponent b, PDFColorComponent s) { return b + s - b * s; } - -private: - static PDFRGB nonseparable_gray2rgb(PDFGray gray); - static PDFGray nonseparable_rgb2gray(PDFRGB rgb); - static PDFRGB nonseparable_cmyk2rgb(PDFCMYK cmyk); - static PDFCMYK nonseparable_rgb2cmyk(PDFRGB rgb, PDFColorComponent K); - static PDFColorComponent nonseparable_Lum(PDFRGB rgb); - static PDFColorComponent nonseparable_Sat(PDFRGB rgb); - static PDFRGB nonseparable_SetLum(PDFRGB C, PDFColorComponent l); - static PDFRGB nonseparable_SetSat(PDFRGB C, PDFColorComponent s); - static PDFRGB nonseparable_ClipColor(PDFRGB C); -}; - -} // namespace pdf - -#endif // PDFBLENDFUNCTION_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFBLENDFUNCTION_H +#define PDFBLENDFUNCTION_H + +#include "pdfglobal.h" + +#include + +namespace pdf +{ + +enum class BlendMode +{ + // Separable blending modes + Normal, ///< Select source color, backdrop is ignored: B(source, dest) = source + Multiply, ///< Backdrop and source colors are multiplied: B(source, dest) = source * dest + Screen, ///< B(source, dest) = 1.0 - (1.0 - source) * (1.0 - dest) + source + dest - source * dest + Overlay, ///< B(source, dest) = HardLight(dest, source) + Darken, ///< Selects the darker of the colors. B(source, dest) = qMin(source, dest) + Lighten, ///< Selects the lighter of the colors. B(source, dest) = qMax(source, dest) + ColorDodge, ///< Brightens the backdrop color reflecting the source color. B(source, dest) = qMin(1.0, dest / (1.0 - source)), or 1.0, if source is 1.0 + ColorBurn, ///< Darkens the backdrop color reflecting the source color. B(source, dest) = 1.0 - qMin(1.0, (1.0 - dest) / source), or 0.0, if source is 0.0 + HardLight, ///< Multiply or screen the color, depending on the source value. + SoftLight, ///< Darkens or lightens the color, depending on the source value. + Difference, ///< Subtract the darker of colors from the lighter color. B(source, dest) = qAbs(source - dest) + Exclusion, ///< B(source, dest) = source + dest - 2 * source * dest + + // Non-separable blending modes + Hue, + Saturation, + Color, + Luminosity, + + // For compatibility - older PDF specification specifies Compatible mode, which is equal + // to normal. It should be recognized for sake of compatibility. + Compatible, ///< Equals to normal + + // Special blend modes for handling overprint. Used only internally. + Overprint_SelectBackdrop, + Overprint_SelectNonZeroSourceOrBackdrop, + Overprint_SelectNonOneSourceOrBackdrop, + + // Invalid blending mode - for internal purposes only + Invalid +}; + +class PDFBlendModeInfo +{ +public: + PDFBlendModeInfo() = delete; + + /// Returns blend mode from the specified string. If string is invalid, + /// then \p Invalid blend mode is returned. + /// \param name Name of the blend mode + static BlendMode getBlendMode(const QByteArray& name); + + /// Returns true, if blend mode is supported by Qt drawing subsystem + /// \param mode Blend mode + static bool isSupportedByQt(BlendMode mode); + + /// Returns true, if blend mode is separable + static bool isSeparable(BlendMode mode); + + /// Returns true, if blend mode is white-preserving (i.e. B(1.0, 1.0) == 1.0) + static bool isWhitePreserving(BlendMode mode); + + /// Returns composition mode for Qt drawing subsystem from blend mode defined + /// in PDF standard. If blend mode is not supported by Qt drawing subsystem, then default + /// composition mode is returned. + /// \param mode Blend mode + static QPainter::CompositionMode getCompositionModeFromBlendMode(BlendMode mode); + + /// Returns blend mode name + /// \param mode Blend mode + static QString getBlendModeName(BlendMode mode); + + /// Returns blend mode translated name + /// \param mode Blend mode + static QString getBlendModeTranslatedName(BlendMode mode); + + /// Returns vector of all blend modes, excluding duplicate ones (for example, + /// Compatible mode is equal to Normal blend mode) + static std::vector getBlendModes(); +}; + +/// Class grouping together blend functions. Can also blend non-separable blend modes, +/// such as Color, Hue, Saturation and Luminosity, according 11.3.5.3 of PDF 2.0 specification. +class PDFBlendFunction +{ +public: + PDFBlendFunction() = delete; + + /// Blend function used to blend separable blend modes + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFColorComponent blend(BlendMode mode, PDFColorComponent Cb, PDFColorComponent Cs); + + /// Blend non-separable hue function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFRGB blend_Hue(PDFRGB Cb, PDFRGB Cs); + + /// Blend non-separable saturation function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFRGB blend_Saturation(PDFRGB Cb, PDFRGB Cs); + + /// Blend non-separable color function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFRGB blend_Color(PDFRGB Cb, PDFRGB Cs); + + /// Blend non-separable luminosity function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFRGB blend_Luminosity(PDFRGB Cb, PDFRGB Cs); + + /// Blend non-separable hue function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Hue(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable saturation function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Saturation(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable color function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Color(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable luminosity function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Luminosity(PDFGray Cb, PDFGray Cs); + + /// Blend non-separable hue function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Hue(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separable saturation function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Saturation(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separable color function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Color(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separable luminosity function + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Luminosity(PDFCMYK Cb, PDFCMYK Cs); + + /// Blend non-separabe. It is incorrect to call this function + /// with blend mode, which is separable. + /// \param mode Non-separable blend mode + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFGray blend_Nonseparable(BlendMode mode, PDFGray Cb, PDFGray Cs); + + /// Blend non-separabe. It is incorrect to call this function + /// with blend mode, which is separable. + /// \param mode Non-separable blend mode + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFRGB blend_Nonseparable(BlendMode mode, PDFRGB Cb, PDFRGB Cs); + + /// Blend non-separabe. It is incorrect to call this function + /// with blend mode, which is separable. + /// \param mode Non-separable blend mode + /// \param Cb Backdrop color + /// \param Cs Source color + static PDFCMYK blend_Nonseparable(BlendMode mode, PDFCMYK Cb, PDFCMYK Cs); + + /// Get luminosity from color value + /// \param gray Color value + static PDFColorComponent getLuminosity(PDFGray gray); + + /// Get luminosity from color value + /// \param rgb Color value + static PDFColorComponent getLuminosity(PDFRGB rgb); + + /// Get luminosity from color value + /// \param cmyk Color value + static PDFColorComponent getLuminosity(PDFCMYK cmyk); + + /// Union function + static constexpr PDFColorComponent blend_Union(PDFColorComponent b, PDFColorComponent s) { return b + s - b * s; } + +private: + static PDFRGB nonseparable_gray2rgb(PDFGray gray); + static PDFGray nonseparable_rgb2gray(PDFRGB rgb); + static PDFRGB nonseparable_cmyk2rgb(PDFCMYK cmyk); + static PDFCMYK nonseparable_rgb2cmyk(PDFRGB rgb, PDFColorComponent K); + static PDFColorComponent nonseparable_Lum(PDFRGB rgb); + static PDFColorComponent nonseparable_Sat(PDFRGB rgb); + static PDFRGB nonseparable_SetLum(PDFRGB C, PDFColorComponent l); + static PDFRGB nonseparable_SetSat(PDFRGB C, PDFColorComponent s); + static PDFRGB nonseparable_ClipColor(PDFRGB C); +}; + +} // namespace pdf + +#endif // PDFBLENDFUNCTION_H diff --git a/Pdf4QtLib/sources/pdfcatalog.cpp b/Pdf4QtLib/sources/pdfcatalog.cpp index 445e963..12b0ba2 100644 --- a/Pdf4QtLib/sources/pdfcatalog.cpp +++ b/Pdf4QtLib/sources/pdfcatalog.cpp @@ -1,1136 +1,1136 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfcatalog.h" -#include "pdfdocument.h" -#include "pdfexception.h" -#include "pdfnumbertreeloader.h" -#include "pdfnametreeloader.h" -#include "pdfencoding.h" - -namespace pdf -{ - -// Entries for "Info" entry in trailer dictionary -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TITLE = "Title"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_AUTHOR = "Author"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_SUBJECT = "Subject"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_KEYWORDS = "Keywords"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATOR = "Creator"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_PRODUCER = "Producer"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE = "CreationDate"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE = "ModDate"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED = "Trapped"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE = "True"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE = "False"; -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN = "Unknown"; - -static constexpr const char* PDF_VIEWER_PREFERENCES_DICTIONARY = "ViewerPreferences"; -static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR = "HideToolbar"; -static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_MENUBAR = "HideMenubar"; -static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_WINDOW_UI = "HideWindowUI"; -static constexpr const char* PDF_VIEWER_PREFERENCES_FIT_WINDOW = "FitWindow"; -static constexpr const char* PDF_VIEWER_PREFERENCES_CENTER_WINDOW = "CenterWindow"; -static constexpr const char* PDF_VIEWER_PREFERENCES_DISPLAY_DOCUMENT_TITLE = "DisplayDocTitle"; -static constexpr const char* PDF_VIEWER_PREFERENCES_NON_FULLSCREEN_PAGE_MODE = "NonFullScreenPageMode"; -static constexpr const char* PDF_VIEWER_PREFERENCES_DIRECTION = "Direction"; -static constexpr const char* PDF_VIEWER_PREFERENCES_VIEW_AREA = "ViewArea"; -static constexpr const char* PDF_VIEWER_PREFERENCES_VIEW_CLIP = "ViewClip"; -static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_AREA = "PrintArea"; -static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_CLIP = "PrintClip"; -static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_SCALING = "PrintScaling"; -static constexpr const char* PDF_VIEWER_PREFERENCES_DUPLEX = "Duplex"; -static constexpr const char* PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE = "PickTrayByPDFSize"; -static constexpr const char* PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES = "NumCopies"; -static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE = "PrintPageRange"; - -size_t PDFCatalog::getPageIndexFromPageReference(PDFObjectReference reference) const -{ - auto it = std::find_if(m_pages.cbegin(), m_pages.cend(), [reference](const PDFPage& page) { return page.getPageReference() == reference; }); - if (it != m_pages.cend()) - { - return std::distance(m_pages.cbegin(), it); - } - - return INVALID_PAGE_INDEX; -} - -const PDFDestination* PDFCatalog::getNamedDestination(const QByteArray& key) const -{ - auto it = m_namedDestinations.find(key); - if (it != m_namedDestinations.cend()) - { - return &it->second; - } - - return nullptr; -} - -PDFActionPtr PDFCatalog::getNamedJavaScriptAction(const QByteArray& key) const -{ - auto it = m_namedJavaScriptActions.find(key); - if (it != m_namedJavaScriptActions.cend()) - { - return it->second; - } - - return nullptr; -} - -PDFObject PDFCatalog::getNamedAppearanceStream(const QByteArray& key) const -{ - auto it = m_namedAppearanceStreams.find(key); - if (it != m_namedAppearanceStreams.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFObject PDFCatalog::getNamedPage(const QByteArray& key) const -{ - auto it = m_namedPages.find(key); - if (it != m_namedPages.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFObject PDFCatalog::getNamedTemplate(const QByteArray& key) const -{ - auto it = m_namedTemplates.find(key); - if (it != m_namedTemplates.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFObject PDFCatalog::getNamedDigitalIdentifier(const QByteArray& key) const -{ - auto it = m_namedDigitalIdentifiers.find(key); - if (it != m_namedDigitalIdentifiers.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFObject PDFCatalog::getNamedUrl(const QByteArray& key) const -{ - auto it = m_namedUniformResourceLocators.find(key); - if (it != m_namedUniformResourceLocators.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFObject PDFCatalog::getNamedAlternateRepresentation(const QByteArray& key) const -{ - auto it = m_namedAlternateRepresentations.find(key); - if (it != m_namedAlternateRepresentations.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFObject PDFCatalog::getNamedRendition(const QByteArray& key) const -{ - auto it = m_namedRenditions.find(key); - if (it != m_namedRenditions.cend()) - { - return it->second; - } - - return PDFObject(); -} - -PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* document) -{ - if (!catalog.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Catalog must be a dictionary.")); - } - - const PDFDictionary* catalogDictionary = catalog.getDictionary(); - Q_ASSERT(catalogDictionary); - - PDFCatalog catalogObject; - catalogObject.m_viewerPreferences = PDFViewerPreferences::parse(catalog, document); - catalogObject.m_pages = PDFPage::parse(&document->getStorage(), catalogDictionary->get("Pages")); - catalogObject.m_pageLabels = PDFNumberTreeLoader::parse(&document->getStorage(), catalogDictionary->get("PageLabels")); - - if (catalogDictionary->hasKey("OCProperties")) - { - catalogObject.m_optionalContentProperties = PDFOptionalContentProperties::create(document, catalogDictionary->get("OCProperties")); - } - - if (catalogDictionary->hasKey("Outlines")) - { - catalogObject.m_outlineRoot = PDFOutlineItem::parse(&document->getStorage(), catalogDictionary->get("Outlines")); - } - - if (catalogDictionary->hasKey("OpenAction")) - { - PDFObject openAction = document->getObject(catalogDictionary->get("OpenAction")); - if (openAction.isArray()) - { - catalogObject.m_openAction.reset(new PDFActionGoTo(PDFDestination::parse(&document->getStorage(), openAction), PDFDestination())); - } - if (openAction.isDictionary()) - { - catalogObject.m_openAction = PDFAction::parse(&document->getStorage(), openAction); - } - } - - PDFDocumentDataLoaderDecorator loader(document); - if (catalogDictionary->hasKey("PageLayout")) - { - constexpr const std::array, 6> pageLayouts = { - std::pair{ "SinglePage", PageLayout::SinglePage }, - std::pair{ "OneColumn", PageLayout::OneColumn }, - std::pair{ "TwoColumnLeft", PageLayout::TwoColumnLeft }, - std::pair{ "TwoColumnRight", PageLayout::TwoColumnRight }, - std::pair{ "TwoPageLeft", PageLayout::TwoPagesLeft }, - std::pair{ "TwoPageRight", PageLayout::TwoPagesRight } - }; - - catalogObject.m_pageLayout = loader.readEnumByName(catalogDictionary->get("PageLayout"), pageLayouts.begin(), pageLayouts.end(), PageLayout::SinglePage); - } - - if (catalogDictionary->hasKey("PageMode")) - { - constexpr const std::array, 6> pageModes = { - std::pair{ "UseNone", PageMode::UseNone }, - std::pair{ "UseOutlines", PageMode::UseOutlines }, - std::pair{ "UseThumbs", PageMode::UseThumbnails }, - std::pair{ "FullScreen", PageMode::Fullscreen }, - std::pair{ "UseOC", PageMode::UseOptionalContent }, - std::pair{ "UseAttachments", PageMode::UseAttachments } - }; - - catalogObject.m_pageMode = loader.readEnumByName(catalogDictionary->get("PageMode"), pageModes.begin(), pageModes.end(), PageMode::UseNone); - } - - if (const PDFDictionary* actionDictionary = document->getDictionaryFromObject(catalogDictionary->get("AA"))) - { - catalogObject.m_documentActions[WillClose] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WC")); - catalogObject.m_documentActions[WillSave] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WS")); - catalogObject.m_documentActions[DidSave] = PDFAction::parse(&document->getStorage(), actionDictionary->get("DS")); - catalogObject.m_documentActions[WillPrint] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WP")); - catalogObject.m_documentActions[DidPrint] = PDFAction::parse(&document->getStorage(), actionDictionary->get("DP")); - } - - catalogObject.m_version = loader.readNameFromDictionary(catalogDictionary, "Version"); - - if (const PDFDictionary* namesDictionary = document->getDictionaryFromObject(catalogDictionary->get("Names"))) - { - auto parseDestination = [](const PDFObjectStorage* storage, PDFObject object) - { - object = storage->getObject(object); - if (object.isDictionary()) - { - object = object.getDictionary()->get("D"); - } - - return PDFDestination::parse(storage, qMove(object)); - }; - - auto getObject = [](const PDFObjectStorage*, PDFObject object) - { - return object; - }; - - catalogObject.m_namedDestinations = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Dests"), parseDestination); - catalogObject.m_namedAppearanceStreams = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("AP"), getObject); - catalogObject.m_namedJavaScriptActions = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("JavaScript"), &PDFAction::parse); - catalogObject.m_namedPages = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Pages"), getObject); - catalogObject.m_namedTemplates = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Templates"), getObject); - catalogObject.m_namedDigitalIdentifiers = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("IDS"), getObject); - catalogObject.m_namedUniformResourceLocators = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("URLS"), getObject); - catalogObject.m_namedEmbeddedFiles = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("EmbeddedFiles"), &PDFFileSpecification::parse); - catalogObject.m_namedAlternateRepresentations = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("AlternatePresentations"), getObject); - catalogObject.m_namedRenditions = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Renditions"), getObject); - } - - // Examine "Dests" dictionary - if (const PDFDictionary* destsDictionary = document->getDictionaryFromObject(catalogDictionary->get("Dests"))) - { - const size_t count = destsDictionary->getCount(); - for (size_t i = 0; i < count; ++i) - { - catalogObject.m_namedDestinations[destsDictionary->getKey(i).getString()] = PDFDestination::parse(&document->getStorage(), destsDictionary->getValue(i)); - } - } - - // Examine "URI" dictionary - if (const PDFDictionary* URIDictionary = document->getDictionaryFromObject(catalogDictionary->get("URI"))) - { - catalogObject.m_baseURI = loader.readStringFromDictionary(URIDictionary, "Base"); - } - - catalogObject.m_formObject = catalogDictionary->get("AcroForm"); - catalogObject.m_extensions = PDFDeveloperExtensions::parse(catalogDictionary->get("Extensions"), document); - catalogObject.m_documentSecurityStore = PDFDocumentSecurityStore::parse(catalogDictionary->get("DSS"), document); - catalogObject.m_threads = loader.readObjectList(catalogDictionary->get("Threads")); - catalogObject.m_metadata = catalogDictionary->get("Metadata"); - - // Examine mark info dictionary - catalogObject.m_markInfoFlags = MarkInfo_None; - if (const PDFDictionary* markInfoDictionary = document->getDictionaryFromObject(catalogDictionary->get("MarkInfo"))) - { - catalogObject.m_markInfoFlags.setFlag(MarkInfo_Marked, loader.readBooleanFromDictionary(markInfoDictionary, "Marked", false)); - catalogObject.m_markInfoFlags.setFlag(MarkInfo_UserProperties, loader.readBooleanFromDictionary(markInfoDictionary, "UserProperties", false)); - catalogObject.m_markInfoFlags.setFlag(MarkInfo_Suspects, loader.readBooleanFromDictionary(markInfoDictionary, "Suspects", false)); - } - - catalogObject.m_structureTreeRoot = catalogDictionary->get("StructTreeRoot"); - catalogObject.m_language = loader.readTextStringFromDictionary(catalogDictionary, "Lang", QString()); - catalogObject.m_webCaptureInfo = PDFWebCaptureInfo::parse(catalogDictionary->get("SpiderInfo"), &document->getStorage()); - catalogObject.m_outputIntents = loader.readObjectList(catalogDictionary->get("OutputIntents")); - catalogObject.m_pieceInfo = catalogDictionary->get("PieceInfo"); - catalogObject.m_perms = catalogDictionary->get("Perms"); - catalogObject.m_legalAttestation = PDFLegalAttestation::parse(&document->getStorage(), catalogDictionary->get("Legal")); - catalogObject.m_requirements = catalogDictionary->get("Requirements"); - catalogObject.m_collection = catalogDictionary->get("Collection"); - catalogObject.m_xfaNeedsRendering = loader.readBooleanFromDictionary(catalogDictionary, "NeedsRendering", false); - catalogObject.m_associatedFiles = catalogDictionary->get("AF"); - catalogObject.m_documentPartRoot = catalogDictionary->get("DPartRoot"); - - return catalogObject; -} - -PDFViewerPreferences PDFViewerPreferences::parse(const PDFObject& catalogDictionary, const PDFDocument* document) -{ - PDFViewerPreferences result; - - if (!catalogDictionary.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Catalog must be a dictionary.")); - } - - const PDFDictionary* dictionary = catalogDictionary.getDictionary(); - if (dictionary->hasKey(PDF_VIEWER_PREFERENCES_DICTIONARY)) - { - const PDFObject& viewerPreferencesObject = document->getObject(dictionary->get(PDF_VIEWER_PREFERENCES_DICTIONARY)); - if (viewerPreferencesObject.isDictionary()) - { - // Load the viewer preferences object - const PDFDictionary* viewerPreferencesDictionary = viewerPreferencesObject.getDictionary(); - - auto addFlag = [&result, viewerPreferencesDictionary, document] (const char* name, OptionFlag flag) - { - const PDFObject& flagObject = document->getObject(viewerPreferencesDictionary->get(name)); - if (!flagObject.isNull()) - { - if (flagObject.isBool()) - { - result.m_optionFlags.setFlag(flag, flagObject.getBool()); - } - else - { - throw PDFException(PDFTranslationContext::tr("Expected boolean value.")); - } - } - }; - addFlag(PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR, HideToolbar); - addFlag(PDF_VIEWER_PREFERENCES_HIDE_MENUBAR, HideMenubar); - addFlag(PDF_VIEWER_PREFERENCES_HIDE_WINDOW_UI, HideWindowUI); - addFlag(PDF_VIEWER_PREFERENCES_FIT_WINDOW, FitWindow); - addFlag(PDF_VIEWER_PREFERENCES_CENTER_WINDOW, CenterWindow); - addFlag(PDF_VIEWER_PREFERENCES_DISPLAY_DOCUMENT_TITLE, DisplayDocTitle); - addFlag(PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE, PickTrayByPDFSize); - - // Non-fullscreen page mode - const PDFObject& nonFullscreenPageMode = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_NON_FULLSCREEN_PAGE_MODE)); - if (!nonFullscreenPageMode.isNull()) - { - if (!nonFullscreenPageMode.isName()) - { - throw PDFException(PDFTranslationContext::tr("Expected name.")); - } - - QByteArray enumName = nonFullscreenPageMode.getString(); - if (enumName == "UseNone") - { - result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseNone; - } - else if (enumName == "UseOutlines") - { - result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseOutline; - } - else if (enumName == "UseThumbs") - { - result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseThumbnails; - } - else if (enumName == "UseOC") - { - result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseOptionalContent; - } - else - { - throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); - } - } - - // Direction - const PDFObject& direction = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_DIRECTION)); - if (!direction.isNull()) - { - if (!direction.isName()) - { - throw PDFException(PDFTranslationContext::tr("Expected name.")); - } - - QByteArray enumName = direction.getString(); - if (enumName == "L2R") - { - result.m_direction = Direction::LeftToRight; - } - else if (enumName == "R2L") - { - result.m_direction = Direction::RightToLeft; - } - else - { - throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); - } - } - - auto addProperty = [&result, viewerPreferencesDictionary, document] (const char* name, Properties property) - { - const PDFObject& propertyObject = document->getObject(viewerPreferencesDictionary->get(name)); - if (!propertyObject.isNull()) - { - if (propertyObject.isName()) - { - result.m_properties[property] = propertyObject.getString(); - } - else - { - throw PDFException(PDFTranslationContext::tr("Expected name.")); - } - } - }; - addProperty(PDF_VIEWER_PREFERENCES_VIEW_AREA, ViewArea); - addProperty(PDF_VIEWER_PREFERENCES_VIEW_CLIP, ViewClip); - addProperty(PDF_VIEWER_PREFERENCES_PRINT_AREA, PrintArea); - addProperty(PDF_VIEWER_PREFERENCES_PRINT_CLIP, PrintClip); - - // Print scaling - const PDFObject& printScaling = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_PRINT_SCALING)); - if (!printScaling.isNull()) - { - if (!printScaling.isName()) - { - throw PDFException(PDFTranslationContext::tr("Expected name.")); - } - - QByteArray enumName = printScaling.getString(); - if (enumName == "None") - { - result.m_printScaling = PrintScaling::None; - } - else if (enumName == "AppDefault") - { - result.m_printScaling = PrintScaling::AppDefault; - } - else - { - throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); - } - } - - // Duplex - const PDFObject& duplex = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_DUPLEX)); - if (!duplex.isNull()) - { - if (!duplex.isName()) - { - throw PDFException(PDFTranslationContext::tr("Expected name.")); - } - - QByteArray enumName = duplex.getString(); - if (enumName == "Simplex") - { - result.m_duplex = Duplex::Simplex; - } - else if (enumName == "DuplexFlipShortEdge") - { - result.m_duplex = Duplex::DuplexFlipShortEdge; - } - else if (enumName == "DuplexFlipLongEdge") - { - result.m_duplex = Duplex::DuplexFlipLongEdge; - } - else - { - throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); - } - } - - // Print page range - const PDFObject& printPageRange = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE)); - if (!printPageRange.isNull()) - { - if (!duplex.isArray()) - { - throw PDFException(PDFTranslationContext::tr("Expected array of integers.")); - } - - // According to PDF Reference 1.7, this entry is ignored in following cases: - // 1) Array size is odd - // 2) Array contains negative numbers - // - // But what should we do, if we get 0? Pages in the PDF file are numbered from 1. - // So if this situation occur, we also ignore the entry. - const PDFArray* array = duplex.getArray(); - - const size_t count = array->getCount(); - if (count % 2 == 0 && count > 0) - { - bool badPageNumber = false; - int scanned = 0; - PDFInteger start = -1; - - for (size_t i = 0; i < count; ++i) - { - const PDFObject& number = document->getObject(array->getItem(i)); - if (number.isInt()) - { - PDFInteger current = number.getInteger(); - - if (current <= 0) - { - badPageNumber = true; - break; - } - - switch (scanned++) - { - case 0: - { - start = current; - break; - } - - case 1: - { - scanned = 0; - result.m_printPageRanges.emplace_back(start, current); - break; - } - - default: - Q_ASSERT(false); - } - } - else - { - throw PDFException(PDFTranslationContext::tr("Expected integer.")); - } - } - - // Did we get negative or zero value? If yes, clear the range. - if (badPageNumber) - { - result.m_printPageRanges.clear(); - } - } - } - - // Number of copies - const PDFObject& numberOfCopies = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES)); - if (!numberOfCopies.isNull()) - { - if (numberOfCopies.isInt()) - { - result.m_numberOfCopies = numberOfCopies.getInteger(); - } - else - { - throw PDFException(PDFTranslationContext::tr("Expected integer.")); - } - } - - // Enforce - PDFDocumentDataLoaderDecorator loader(document); - std::vector enforce = loader.readNameArrayFromDictionary(viewerPreferencesDictionary, "Enforce"); - result.m_optionFlags.setFlag(EnforcePrintScaling, std::find(enforce.cbegin(), enforce.cend(), "PrintScaling") != enforce.cend()); - } - else if (!viewerPreferencesObject.isNull()) - { - throw PDFException(PDFTranslationContext::tr("Viewer preferences must be a dictionary.")); - } - } - - return result; -} - -PDFPageLabel PDFPageLabel::parse(PDFInteger pageIndex, const PDFObjectStorage* storage, const PDFObject& object) -{ - const PDFObject& dereferencedObject = storage->getObject(object); - if (dereferencedObject.isDictionary()) - { - std::array, 5> numberingStyles = { std::pair{ "D", NumberingStyle::DecimalArabic}, - std::pair{ "R", NumberingStyle::UppercaseRoman }, - std::pair{ "r", NumberingStyle::LowercaseRoman }, - std::pair{ "A", NumberingStyle::UppercaseLetters}, - std::pair{ "a", NumberingStyle::LowercaseLetters} }; - - const PDFDictionary* dictionary = dereferencedObject.getDictionary(); - const PDFDocumentDataLoaderDecorator loader(storage); - const NumberingStyle numberingStyle = loader.readEnumByName(dictionary->get("S"), numberingStyles.cbegin(), numberingStyles.cend(), NumberingStyle::None); - const QString prefix = loader.readTextString(dictionary->get("P"), QString()); - const PDFInteger startNumber = loader.readInteger(dictionary->get("St"), 1); - return PDFPageLabel(numberingStyle, prefix, pageIndex, startNumber); - } - else - { - throw PDFException(PDFTranslationContext::tr("Expected page label dictionary.")); - } - - return PDFPageLabel(); -} - -const PDFDocumentSecurityStore::SecurityStoreItem* PDFDocumentSecurityStore::getItem(const QByteArray& hash) const -{ - auto it = m_VRI.find(hash); - if (it != m_VRI.cend()) - { - return &it->second; - } - - return getMasterItem(); -} - -PDFDocumentSecurityStore PDFDocumentSecurityStore::parse(const PDFObject& object, const PDFDocument* document) -{ - PDFDocumentSecurityStore store; - - try - { - if (const PDFDictionary* dssDictionary = document->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(document); - - auto getDecodedStreams = [document, &loader](const PDFObject& object) -> std::vector - { - std::vector result; - - std::vector references = loader.readReferenceArray(object); - result.reserve(references.size()); - for (const PDFObjectReference& reference : references) - { - PDFObject object = document->getObjectByReference(reference); - if (object.isStream()) - { - result.emplace_back(document->getDecodedStream(object.getStream())); - } - } - - return result; - }; - - store.m_master.Cert = getDecodedStreams(dssDictionary->get("Certs")); - store.m_master.OCSP = getDecodedStreams(dssDictionary->get("OCSPs")); - store.m_master.CRL = getDecodedStreams(dssDictionary->get("CRLs")); - - if (const PDFDictionary* vriDictionary = document->getDictionaryFromObject(dssDictionary->get("VRI"))) - { - for (size_t i = 0, count = vriDictionary->getCount(); i < count; ++i) - { - const PDFObject& vriItemObject = vriDictionary->getValue(i); - if (const PDFDictionary* vriItemDictionary = document->getDictionaryFromObject(vriItemObject)) - { - QByteArray key = vriDictionary->getKey(i).getString(); - - SecurityStoreItem& item = store.m_VRI[key]; - item.Cert = getDecodedStreams(vriItemDictionary->get("Cert")); - item.CRL = getDecodedStreams(vriItemDictionary->get("CRL")); - item.OCSP = getDecodedStreams(vriItemDictionary->get("OCSP")); - item.created = PDFEncoding::convertToDateTime(loader.readStringFromDictionary(vriDictionary, "TU")); - - PDFObject timestampObject = document->getObject(vriItemDictionary->get("TS")); - if (timestampObject.isStream()) - { - item.timestamp = document->getDecodedStream(timestampObject.getStream()); - } - } - } - } - } - } - catch (PDFException) - { - return PDFDocumentSecurityStore(); - } - - return store; -} - -PDFDeveloperExtensions PDFDeveloperExtensions::parse(const PDFObject& object, const PDFDocument* document) -{ - PDFDeveloperExtensions extensions; - - if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) - { - const size_t extensionsCount = dictionary->getCount(); - extensions.m_extensions.reserve(extensionsCount); - for (size_t i = 0; i < extensionsCount; ++i) - { - // Skip type entry - if (dictionary->getKey(i) == "Type") - { - continue; - } - - if (const PDFDictionary* extensionsDictionary = document->getDictionaryFromObject(dictionary->getValue(i))) - { - PDFDocumentDataLoaderDecorator loader(document); - - Extension extension; - extension.name = dictionary->getKey(i).getString(); - extension.baseVersion = loader.readNameFromDictionary(extensionsDictionary, "BaseName"); - extension.extensionLevel = loader.readIntegerFromDictionary(extensionsDictionary, "ExtensionLevel", 0); - extension.url = loader.readStringFromDictionary(extensionsDictionary, "URL"); - extensions.m_extensions.emplace_back(qMove(extension)); - } - } - } - - return extensions; -} - -PDFDocumentInfo PDFDocumentInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) -{ - PDFDocumentInfo info; - - if (const PDFDictionary* infoDictionary = storage->getDictionaryFromObject(object)) - { - auto readTextString = [storage, infoDictionary](const char* entry, QString& fillEntry) - { - if (infoDictionary->hasKey(entry)) - { - const PDFObject& stringObject = storage->getObject(infoDictionary->get(entry)); - if (stringObject.isString()) - { - // We have succesfully read the string, convert it according to encoding - fillEntry = PDFEncoding::convertTextString(stringObject.getString()); - } - else if (!stringObject.isNull()) - { - throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String expected.")); - } - } - }; - readTextString(PDF_DOCUMENT_INFO_ENTRY_TITLE, info.title); - readTextString(PDF_DOCUMENT_INFO_ENTRY_AUTHOR, info.author); - readTextString(PDF_DOCUMENT_INFO_ENTRY_SUBJECT, info.subject); - readTextString(PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, info.keywords); - readTextString(PDF_DOCUMENT_INFO_ENTRY_CREATOR, info.creator); - readTextString(PDF_DOCUMENT_INFO_ENTRY_PRODUCER, info.producer); - - auto readDate= [storage, infoDictionary](const char* entry, QDateTime& fillEntry) - { - if (infoDictionary->hasKey(entry)) - { - const PDFObject& stringObject = storage->getObject(infoDictionary->get(entry)); - if (stringObject.isString()) - { - // We have succesfully read the string, convert it to date time - fillEntry = PDFEncoding::convertToDateTime(stringObject.getString()); - - if (!fillEntry.isValid()) - { - throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); - } - } - else if (!stringObject.isNull()) - { - throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); - } - } - }; - readDate(PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, info.creationDate); - readDate(PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, info.modifiedDate); - - if (infoDictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)) - { - const PDFObject& nameObject = storage->getObject(infoDictionary->get(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)); - if (nameObject.isName()) - { - const QByteArray& name = nameObject.getString(); - if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE) - { - info.trapped = Trapped::True; - } - else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE) - { - info.trapped = Trapped::False; - } - else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN) - { - info.trapped = Trapped::Unknown; - } - else - { - throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); - } - } - else if (nameObject.isBool()) - { - info.trapped = nameObject.getBool() ? Trapped::True : Trapped::False; - } - else - { - throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); - } - } - - // Scan for extra items - constexpr const char* PREDEFINED_ITEMS[] = { PDF_DOCUMENT_INFO_ENTRY_TITLE, PDF_DOCUMENT_INFO_ENTRY_AUTHOR, PDF_DOCUMENT_INFO_ENTRY_SUBJECT, - PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, PDF_DOCUMENT_INFO_ENTRY_CREATOR, PDF_DOCUMENT_INFO_ENTRY_PRODUCER, - PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, PDF_DOCUMENT_INFO_ENTRY_TRAPPED }; - for (size_t i = 0; i < infoDictionary->getCount(); ++i) - { - QByteArray key = infoDictionary->getKey(i).getString(); - if (std::none_of(std::begin(PREDEFINED_ITEMS), std::end(PREDEFINED_ITEMS), [&key](const char* item) { return item == key; })) - { - const PDFObject& value = storage->getObject(infoDictionary->getValue(i)); - if (value.isString()) - { - const QByteArray& stringValue = value.getString(); - QDateTime dateTime = PDFEncoding::convertToDateTime(stringValue); - if (dateTime.isValid()) - { - info.extra[key] = dateTime; - } - else - { - info.extra[key] = PDFEncoding::convertTextString(stringValue); - } - } - } - } - } - - return info; -} - -PDFArticleThread PDFArticleThread::parse(const PDFObjectStorage* storage, const PDFObject& object) -{ - PDFArticleThread result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - PDFObjectReference firstBeadReference = loader.readReferenceFromDictionary(dictionary, "F"); - - std::set visitedBeads; - PDFObjectReference currentBead = firstBeadReference; - - while (!visitedBeads.count(currentBead)) - { - visitedBeads.insert(currentBead); - - // Read bead - if (const PDFDictionary* beadDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(currentBead))) - { - Bead bead; - bead.self = currentBead; - bead.thread = loader.readReferenceFromDictionary(beadDictionary, "T"); - bead.next = loader.readReferenceFromDictionary(beadDictionary, "N"); - bead.previous = loader.readReferenceFromDictionary(beadDictionary, "V"); - bead.page = loader.readReferenceFromDictionary(beadDictionary, "P"); - bead.rect = loader.readRectangle(beadDictionary->get("R"), QRectF()); - - currentBead = bead.next; - result.m_beads.push_back(bead); - } - else - { - // current bead will be the same, the cycle will break - } - } - - result.m_information = PDFDocumentInfo::parse(dictionary->get("I"), storage); - result.m_metadata = loader.readReferenceFromDictionary(dictionary, "Metadata"); - } - - return result; -} - -PDFWebCaptureInfo PDFWebCaptureInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) -{ - PDFWebCaptureInfo result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_version = loader.readNameFromDictionary(dictionary, "V"); - result.m_commands = loader.readReferenceArrayFromDictionary(dictionary, "C"); - } - - return result; -} - -PDFOutputIntent PDFOutputIntent::parse(const PDFObjectStorage* storage, const PDFObject& object) -{ - PDFOutputIntent result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_subtype = loader.readNameFromDictionary(dictionary, "S"); - result.m_outputCondition = loader.readTextStringFromDictionary(dictionary, "OutputCondition", QString()); - result.m_outputConditionIdentifier = loader.readTextStringFromDictionary(dictionary, "OutputConditionIdentifier", QString()); - result.m_registryName = loader.readTextStringFromDictionary(dictionary, "RegistryName", QString()); - result.m_info = loader.readTextStringFromDictionary(dictionary, "Info", QString()); - result.m_destOutputProfile = dictionary->get("DestOutputProfile"); - result.m_destOutputProfileRef = PDFOutputIntentICCProfileInfo::parse(dictionary->get("DestOutputProfileRef"), storage); - result.m_mixingHints = dictionary->get("MixingHints"); - result.m_spectralData = dictionary->get("SpectralData"); - } - - return result; -} - -PDFOutputIntentICCProfileInfo PDFOutputIntentICCProfileInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) -{ - PDFOutputIntentICCProfileInfo result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_checkSum = loader.readStringFromDictionary(dictionary, "CheckSum"); - result.m_colorants = loader.readNameArrayFromDictionary(dictionary, "ColorantTable"); - result.m_iccVersion = loader.readStringFromDictionary(dictionary, "ICCVersion"); - result.m_signature = loader.readStringFromDictionary(dictionary, "ProfileCS"); - result.m_profileName = loader.readTextStringFromDictionary(dictionary, "ProfileName", QString()); - result.m_urls = dictionary->get("URLs"); - } - - return result; -} - -std::optional PDFLegalAttestation::parse(const PDFObjectStorage* storage, const PDFObject& object) -{ - std::optional result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - result = PDFLegalAttestation(); - - result->m_entries[JavaScriptActions] = loader.readIntegerFromDictionary(dictionary, "JavaScriptActions", 0); - result->m_entries[LaunchActions] = loader.readIntegerFromDictionary(dictionary, "LaunchActions", 0); - result->m_entries[URIActions] = loader.readIntegerFromDictionary(dictionary, "URIActions", 0); - result->m_entries[MovieActions] = loader.readIntegerFromDictionary(dictionary, "MovieActions", 0); - result->m_entries[SoundActions] = loader.readIntegerFromDictionary(dictionary, "SoundActions", 0); - result->m_entries[HideAnnotationActions] = loader.readIntegerFromDictionary(dictionary, "HideAnnotationActions", 0); - result->m_entries[GoToRemoteActions] = loader.readIntegerFromDictionary(dictionary, "GoToRemoteActions", 0); - result->m_entries[AlternateImages] = loader.readIntegerFromDictionary(dictionary, "AlternateImages", 0); - result->m_entries[ExternalStreams] = loader.readIntegerFromDictionary(dictionary, "ExternalStreams", 0); - result->m_entries[TrueTypeFonts] = loader.readIntegerFromDictionary(dictionary, "TrueTypeFonts", 0); - result->m_entries[ExternalRefXobjects] = loader.readIntegerFromDictionary(dictionary, "ExternalRefXobjects", 0); - result->m_entries[ExternalOPIdicts] = loader.readIntegerFromDictionary(dictionary, "ExternalOPIdicts", 0); - result->m_entries[NonEmbeddedFonts] = loader.readIntegerFromDictionary(dictionary, "NonEmbeddedFonts", 0); - result->m_entries[DevDepGS_OP] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_OP", 0); - result->m_entries[DevDepGS_HT] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_HT", 0); - result->m_entries[DevDepGS_TR] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_TR", 0); - result->m_entries[DevDepGS_UCR] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_UCR", 0); - result->m_entries[DevDepGS_BG] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_BG", 0); - result->m_entries[DevDepGS_FL] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_FL", 0); - result->m_hasOptionalContent = loader.readBooleanFromDictionary(dictionary, "OptionalContent", false); - result->m_attestation = loader.readTextStringFromDictionary(dictionary, "Attestation", QString()); - } - - return result; -} - -PDFDocumentRequirements::ValidationResult PDFDocumentRequirements::validate(Requirements supported) const -{ - ValidationResult result; - - QStringList unsatisfiedRequirements; - for (const RequirementEntry& entry : m_requirements) - { - if (entry.requirement == None) - { - // Unrecognized entry, just add the penalty - result.penalty += entry.penalty; - continue; - } - - if (!supported.testFlag(entry.requirement)) - { - result.penalty += entry.penalty; - unsatisfiedRequirements << getRequirementName(entry.requirement); - } - } - - if (!unsatisfiedRequirements.isEmpty()) - { - result.message = PDFTranslationContext::tr("Required features %1 are unsupported. Document processing can be limited.").arg(unsatisfiedRequirements.join(", ")); - } - - return result; -} - -QString PDFDocumentRequirements::getRequirementName(Requirement requirement) -{ - switch (requirement) - { - case OCInteract: - return PDFTranslationContext::tr("Optional Content User Interaction"); - case OCAutoStates: - return PDFTranslationContext::tr("Optional Content Usage"); - case AcroFormInteract: - return PDFTranslationContext::tr("Acrobat Forms"); - case Navigation: - return PDFTranslationContext::tr("Navigation"); - case Markup: - return PDFTranslationContext::tr("Markup Annotations"); - case _3DMarkup: - return PDFTranslationContext::tr("Markup of 3D Content"); - case Multimedia: - return PDFTranslationContext::tr("Multimedia"); - case U3D: - return PDFTranslationContext::tr("U3D Format of PDF 3D"); - case PRC: - return PDFTranslationContext::tr("PRC Format of PDF 3D"); - case Action: - return PDFTranslationContext::tr("Actions"); - case EnableJavaScripts: - return PDFTranslationContext::tr("JavaScript"); - case Attachment: - return PDFTranslationContext::tr("Attached Files"); - case AttachmentEditing: - return PDFTranslationContext::tr("Attached Files Modification"); - case Collection: - return PDFTranslationContext::tr("Collections of Attached Files"); - case CollectionEditing: - return PDFTranslationContext::tr("Collections of Attached Files (editation)"); - case DigSigValidation: - return PDFTranslationContext::tr("Digital Signature Validation"); - case DigSig: - return PDFTranslationContext::tr("Apply Digital Signature"); - case DigSigMDP: - return PDFTranslationContext::tr("Digital Signature Validation (with MDP)"); - case RichMedia: - return PDFTranslationContext::tr("Rich Media"); - case Geospatial2D: - return PDFTranslationContext::tr("Geospatial 2D Features"); - case Geospatial3D: - return PDFTranslationContext::tr("Geospatial 3D Features"); - case DPartInteract: - return PDFTranslationContext::tr("Navigation for Document Parts"); - case SeparationSimulation: - return PDFTranslationContext::tr("Separation Simulation"); - case Transitions: - return PDFTranslationContext::tr("Transitions/Presentations"); - case Encryption: - return PDFTranslationContext::tr("Encryption"); - - default: - Q_ASSERT(false); - break; - } - - return QString(); -} - -PDFDocumentRequirements PDFDocumentRequirements::parse(const PDFObjectStorage* storage, const PDFObject& object) -{ - PDFDocumentRequirements requirements; - - PDFDocumentDataLoaderDecorator loader(storage); - requirements.m_requirements = loader.readObjectList(object); - - return requirements; -} - -PDFDocumentRequirements::RequirementEntry PDFDocumentRequirements::RequirementEntry::parse(const PDFObjectStorage* storage, const PDFObject& object) -{ - RequirementEntry entry; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - constexpr const std::array requirementTypes = { - std::pair{ "OCInteract", OCInteract }, - std::pair{ "OCAutoStates", OCAutoStates }, - std::pair{ "AcroFormInteract", AcroFormInteract }, - std::pair{ "Navigation", Navigation }, - std::pair{ "Markup", Markup }, - std::pair{ "3DMarkup", _3DMarkup }, - std::pair{ "Multimedia", Multimedia }, - std::pair{ "U3D", U3D }, - std::pair{ "PRC", PRC }, - std::pair{ "Action", Action }, - std::pair{ "EnableJavaScripts", EnableJavaScripts }, - std::pair{ "Attachment", Attachment }, - std::pair{ "AttachmentEditing", AttachmentEditing }, - std::pair{ "Collection", Collection }, - std::pair{ "CollectionEditing", CollectionEditing }, - std::pair{ "DigSigValidation", DigSigValidation }, - std::pair{ "DigSig", DigSig }, - std::pair{ "DigSigMDP", DigSigMDP }, - std::pair{ "RichMedia", RichMedia }, - std::pair{ "Geospatial2D", Geospatial2D }, - std::pair{ "Geospatial3D", Geospatial3D }, - std::pair{ "DPartInteract", DPartInteract }, - std::pair{ "SeparationSimulation", SeparationSimulation }, - std::pair{ "Transitions", Transitions }, - std::pair{ "Encryption", Encryption } - }; - - entry.requirement = loader.readEnumByName(dictionary->get("S"), requirementTypes.begin(), requirementTypes.end(), None); - entry.handler = dictionary->get("RH"); - entry.version = loader.readNameFromDictionary(dictionary, "V"); - entry.penalty = loader.readIntegerFromDictionary(dictionary, "Penalty", 100); - } - - return entry; -} - -PDFPageAdditionalActions PDFPageAdditionalActions::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFPageAdditionalActions result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - result.m_actions[Open] = PDFAction::parse(storage, dictionary->get("O")); - result.m_actions[Close] = PDFAction::parse(storage, dictionary->get("C")); - } - - return result; -} - -} // namespace pdf +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfcatalog.h" +#include "pdfdocument.h" +#include "pdfexception.h" +#include "pdfnumbertreeloader.h" +#include "pdfnametreeloader.h" +#include "pdfencoding.h" + +namespace pdf +{ + +// Entries for "Info" entry in trailer dictionary +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TITLE = "Title"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_AUTHOR = "Author"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_SUBJECT = "Subject"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_KEYWORDS = "Keywords"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATOR = "Creator"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_PRODUCER = "Producer"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE = "CreationDate"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE = "ModDate"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED = "Trapped"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE = "True"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE = "False"; +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN = "Unknown"; + +static constexpr const char* PDF_VIEWER_PREFERENCES_DICTIONARY = "ViewerPreferences"; +static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR = "HideToolbar"; +static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_MENUBAR = "HideMenubar"; +static constexpr const char* PDF_VIEWER_PREFERENCES_HIDE_WINDOW_UI = "HideWindowUI"; +static constexpr const char* PDF_VIEWER_PREFERENCES_FIT_WINDOW = "FitWindow"; +static constexpr const char* PDF_VIEWER_PREFERENCES_CENTER_WINDOW = "CenterWindow"; +static constexpr const char* PDF_VIEWER_PREFERENCES_DISPLAY_DOCUMENT_TITLE = "DisplayDocTitle"; +static constexpr const char* PDF_VIEWER_PREFERENCES_NON_FULLSCREEN_PAGE_MODE = "NonFullScreenPageMode"; +static constexpr const char* PDF_VIEWER_PREFERENCES_DIRECTION = "Direction"; +static constexpr const char* PDF_VIEWER_PREFERENCES_VIEW_AREA = "ViewArea"; +static constexpr const char* PDF_VIEWER_PREFERENCES_VIEW_CLIP = "ViewClip"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_AREA = "PrintArea"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_CLIP = "PrintClip"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_SCALING = "PrintScaling"; +static constexpr const char* PDF_VIEWER_PREFERENCES_DUPLEX = "Duplex"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE = "PickTrayByPDFSize"; +static constexpr const char* PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES = "NumCopies"; +static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE = "PrintPageRange"; + +size_t PDFCatalog::getPageIndexFromPageReference(PDFObjectReference reference) const +{ + auto it = std::find_if(m_pages.cbegin(), m_pages.cend(), [reference](const PDFPage& page) { return page.getPageReference() == reference; }); + if (it != m_pages.cend()) + { + return std::distance(m_pages.cbegin(), it); + } + + return INVALID_PAGE_INDEX; +} + +const PDFDestination* PDFCatalog::getNamedDestination(const QByteArray& key) const +{ + auto it = m_namedDestinations.find(key); + if (it != m_namedDestinations.cend()) + { + return &it->second; + } + + return nullptr; +} + +PDFActionPtr PDFCatalog::getNamedJavaScriptAction(const QByteArray& key) const +{ + auto it = m_namedJavaScriptActions.find(key); + if (it != m_namedJavaScriptActions.cend()) + { + return it->second; + } + + return nullptr; +} + +PDFObject PDFCatalog::getNamedAppearanceStream(const QByteArray& key) const +{ + auto it = m_namedAppearanceStreams.find(key); + if (it != m_namedAppearanceStreams.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFObject PDFCatalog::getNamedPage(const QByteArray& key) const +{ + auto it = m_namedPages.find(key); + if (it != m_namedPages.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFObject PDFCatalog::getNamedTemplate(const QByteArray& key) const +{ + auto it = m_namedTemplates.find(key); + if (it != m_namedTemplates.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFObject PDFCatalog::getNamedDigitalIdentifier(const QByteArray& key) const +{ + auto it = m_namedDigitalIdentifiers.find(key); + if (it != m_namedDigitalIdentifiers.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFObject PDFCatalog::getNamedUrl(const QByteArray& key) const +{ + auto it = m_namedUniformResourceLocators.find(key); + if (it != m_namedUniformResourceLocators.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFObject PDFCatalog::getNamedAlternateRepresentation(const QByteArray& key) const +{ + auto it = m_namedAlternateRepresentations.find(key); + if (it != m_namedAlternateRepresentations.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFObject PDFCatalog::getNamedRendition(const QByteArray& key) const +{ + auto it = m_namedRenditions.find(key); + if (it != m_namedRenditions.cend()) + { + return it->second; + } + + return PDFObject(); +} + +PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* document) +{ + if (!catalog.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Catalog must be a dictionary.")); + } + + const PDFDictionary* catalogDictionary = catalog.getDictionary(); + Q_ASSERT(catalogDictionary); + + PDFCatalog catalogObject; + catalogObject.m_viewerPreferences = PDFViewerPreferences::parse(catalog, document); + catalogObject.m_pages = PDFPage::parse(&document->getStorage(), catalogDictionary->get("Pages")); + catalogObject.m_pageLabels = PDFNumberTreeLoader::parse(&document->getStorage(), catalogDictionary->get("PageLabels")); + + if (catalogDictionary->hasKey("OCProperties")) + { + catalogObject.m_optionalContentProperties = PDFOptionalContentProperties::create(document, catalogDictionary->get("OCProperties")); + } + + if (catalogDictionary->hasKey("Outlines")) + { + catalogObject.m_outlineRoot = PDFOutlineItem::parse(&document->getStorage(), catalogDictionary->get("Outlines")); + } + + if (catalogDictionary->hasKey("OpenAction")) + { + PDFObject openAction = document->getObject(catalogDictionary->get("OpenAction")); + if (openAction.isArray()) + { + catalogObject.m_openAction.reset(new PDFActionGoTo(PDFDestination::parse(&document->getStorage(), openAction), PDFDestination())); + } + if (openAction.isDictionary()) + { + catalogObject.m_openAction = PDFAction::parse(&document->getStorage(), openAction); + } + } + + PDFDocumentDataLoaderDecorator loader(document); + if (catalogDictionary->hasKey("PageLayout")) + { + constexpr const std::array, 6> pageLayouts = { + std::pair{ "SinglePage", PageLayout::SinglePage }, + std::pair{ "OneColumn", PageLayout::OneColumn }, + std::pair{ "TwoColumnLeft", PageLayout::TwoColumnLeft }, + std::pair{ "TwoColumnRight", PageLayout::TwoColumnRight }, + std::pair{ "TwoPageLeft", PageLayout::TwoPagesLeft }, + std::pair{ "TwoPageRight", PageLayout::TwoPagesRight } + }; + + catalogObject.m_pageLayout = loader.readEnumByName(catalogDictionary->get("PageLayout"), pageLayouts.begin(), pageLayouts.end(), PageLayout::SinglePage); + } + + if (catalogDictionary->hasKey("PageMode")) + { + constexpr const std::array, 6> pageModes = { + std::pair{ "UseNone", PageMode::UseNone }, + std::pair{ "UseOutlines", PageMode::UseOutlines }, + std::pair{ "UseThumbs", PageMode::UseThumbnails }, + std::pair{ "FullScreen", PageMode::Fullscreen }, + std::pair{ "UseOC", PageMode::UseOptionalContent }, + std::pair{ "UseAttachments", PageMode::UseAttachments } + }; + + catalogObject.m_pageMode = loader.readEnumByName(catalogDictionary->get("PageMode"), pageModes.begin(), pageModes.end(), PageMode::UseNone); + } + + if (const PDFDictionary* actionDictionary = document->getDictionaryFromObject(catalogDictionary->get("AA"))) + { + catalogObject.m_documentActions[WillClose] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WC")); + catalogObject.m_documentActions[WillSave] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WS")); + catalogObject.m_documentActions[DidSave] = PDFAction::parse(&document->getStorage(), actionDictionary->get("DS")); + catalogObject.m_documentActions[WillPrint] = PDFAction::parse(&document->getStorage(), actionDictionary->get("WP")); + catalogObject.m_documentActions[DidPrint] = PDFAction::parse(&document->getStorage(), actionDictionary->get("DP")); + } + + catalogObject.m_version = loader.readNameFromDictionary(catalogDictionary, "Version"); + + if (const PDFDictionary* namesDictionary = document->getDictionaryFromObject(catalogDictionary->get("Names"))) + { + auto parseDestination = [](const PDFObjectStorage* storage, PDFObject object) + { + object = storage->getObject(object); + if (object.isDictionary()) + { + object = object.getDictionary()->get("D"); + } + + return PDFDestination::parse(storage, qMove(object)); + }; + + auto getObject = [](const PDFObjectStorage*, PDFObject object) + { + return object; + }; + + catalogObject.m_namedDestinations = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Dests"), parseDestination); + catalogObject.m_namedAppearanceStreams = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("AP"), getObject); + catalogObject.m_namedJavaScriptActions = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("JavaScript"), &PDFAction::parse); + catalogObject.m_namedPages = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Pages"), getObject); + catalogObject.m_namedTemplates = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Templates"), getObject); + catalogObject.m_namedDigitalIdentifiers = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("IDS"), getObject); + catalogObject.m_namedUniformResourceLocators = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("URLS"), getObject); + catalogObject.m_namedEmbeddedFiles = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("EmbeddedFiles"), &PDFFileSpecification::parse); + catalogObject.m_namedAlternateRepresentations = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("AlternatePresentations"), getObject); + catalogObject.m_namedRenditions = PDFNameTreeLoader::parse(&document->getStorage(), namesDictionary->get("Renditions"), getObject); + } + + // Examine "Dests" dictionary + if (const PDFDictionary* destsDictionary = document->getDictionaryFromObject(catalogDictionary->get("Dests"))) + { + const size_t count = destsDictionary->getCount(); + for (size_t i = 0; i < count; ++i) + { + catalogObject.m_namedDestinations[destsDictionary->getKey(i).getString()] = PDFDestination::parse(&document->getStorage(), destsDictionary->getValue(i)); + } + } + + // Examine "URI" dictionary + if (const PDFDictionary* URIDictionary = document->getDictionaryFromObject(catalogDictionary->get("URI"))) + { + catalogObject.m_baseURI = loader.readStringFromDictionary(URIDictionary, "Base"); + } + + catalogObject.m_formObject = catalogDictionary->get("AcroForm"); + catalogObject.m_extensions = PDFDeveloperExtensions::parse(catalogDictionary->get("Extensions"), document); + catalogObject.m_documentSecurityStore = PDFDocumentSecurityStore::parse(catalogDictionary->get("DSS"), document); + catalogObject.m_threads = loader.readObjectList(catalogDictionary->get("Threads")); + catalogObject.m_metadata = catalogDictionary->get("Metadata"); + + // Examine mark info dictionary + catalogObject.m_markInfoFlags = MarkInfo_None; + if (const PDFDictionary* markInfoDictionary = document->getDictionaryFromObject(catalogDictionary->get("MarkInfo"))) + { + catalogObject.m_markInfoFlags.setFlag(MarkInfo_Marked, loader.readBooleanFromDictionary(markInfoDictionary, "Marked", false)); + catalogObject.m_markInfoFlags.setFlag(MarkInfo_UserProperties, loader.readBooleanFromDictionary(markInfoDictionary, "UserProperties", false)); + catalogObject.m_markInfoFlags.setFlag(MarkInfo_Suspects, loader.readBooleanFromDictionary(markInfoDictionary, "Suspects", false)); + } + + catalogObject.m_structureTreeRoot = catalogDictionary->get("StructTreeRoot"); + catalogObject.m_language = loader.readTextStringFromDictionary(catalogDictionary, "Lang", QString()); + catalogObject.m_webCaptureInfo = PDFWebCaptureInfo::parse(catalogDictionary->get("SpiderInfo"), &document->getStorage()); + catalogObject.m_outputIntents = loader.readObjectList(catalogDictionary->get("OutputIntents")); + catalogObject.m_pieceInfo = catalogDictionary->get("PieceInfo"); + catalogObject.m_perms = catalogDictionary->get("Perms"); + catalogObject.m_legalAttestation = PDFLegalAttestation::parse(&document->getStorage(), catalogDictionary->get("Legal")); + catalogObject.m_requirements = catalogDictionary->get("Requirements"); + catalogObject.m_collection = catalogDictionary->get("Collection"); + catalogObject.m_xfaNeedsRendering = loader.readBooleanFromDictionary(catalogDictionary, "NeedsRendering", false); + catalogObject.m_associatedFiles = catalogDictionary->get("AF"); + catalogObject.m_documentPartRoot = catalogDictionary->get("DPartRoot"); + + return catalogObject; +} + +PDFViewerPreferences PDFViewerPreferences::parse(const PDFObject& catalogDictionary, const PDFDocument* document) +{ + PDFViewerPreferences result; + + if (!catalogDictionary.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Catalog must be a dictionary.")); + } + + const PDFDictionary* dictionary = catalogDictionary.getDictionary(); + if (dictionary->hasKey(PDF_VIEWER_PREFERENCES_DICTIONARY)) + { + const PDFObject& viewerPreferencesObject = document->getObject(dictionary->get(PDF_VIEWER_PREFERENCES_DICTIONARY)); + if (viewerPreferencesObject.isDictionary()) + { + // Load the viewer preferences object + const PDFDictionary* viewerPreferencesDictionary = viewerPreferencesObject.getDictionary(); + + auto addFlag = [&result, viewerPreferencesDictionary, document] (const char* name, OptionFlag flag) + { + const PDFObject& flagObject = document->getObject(viewerPreferencesDictionary->get(name)); + if (!flagObject.isNull()) + { + if (flagObject.isBool()) + { + result.m_optionFlags.setFlag(flag, flagObject.getBool()); + } + else + { + throw PDFException(PDFTranslationContext::tr("Expected boolean value.")); + } + } + }; + addFlag(PDF_VIEWER_PREFERENCES_HIDE_TOOLBAR, HideToolbar); + addFlag(PDF_VIEWER_PREFERENCES_HIDE_MENUBAR, HideMenubar); + addFlag(PDF_VIEWER_PREFERENCES_HIDE_WINDOW_UI, HideWindowUI); + addFlag(PDF_VIEWER_PREFERENCES_FIT_WINDOW, FitWindow); + addFlag(PDF_VIEWER_PREFERENCES_CENTER_WINDOW, CenterWindow); + addFlag(PDF_VIEWER_PREFERENCES_DISPLAY_DOCUMENT_TITLE, DisplayDocTitle); + addFlag(PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE, PickTrayByPDFSize); + + // Non-fullscreen page mode + const PDFObject& nonFullscreenPageMode = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_NON_FULLSCREEN_PAGE_MODE)); + if (!nonFullscreenPageMode.isNull()) + { + if (!nonFullscreenPageMode.isName()) + { + throw PDFException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = nonFullscreenPageMode.getString(); + if (enumName == "UseNone") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseNone; + } + else if (enumName == "UseOutlines") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseOutline; + } + else if (enumName == "UseThumbs") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseThumbnails; + } + else if (enumName == "UseOC") + { + result.m_nonFullScreenPageMode = NonFullScreenPageMode::UseOptionalContent; + } + else + { + throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + // Direction + const PDFObject& direction = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_DIRECTION)); + if (!direction.isNull()) + { + if (!direction.isName()) + { + throw PDFException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = direction.getString(); + if (enumName == "L2R") + { + result.m_direction = Direction::LeftToRight; + } + else if (enumName == "R2L") + { + result.m_direction = Direction::RightToLeft; + } + else + { + throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + auto addProperty = [&result, viewerPreferencesDictionary, document] (const char* name, Properties property) + { + const PDFObject& propertyObject = document->getObject(viewerPreferencesDictionary->get(name)); + if (!propertyObject.isNull()) + { + if (propertyObject.isName()) + { + result.m_properties[property] = propertyObject.getString(); + } + else + { + throw PDFException(PDFTranslationContext::tr("Expected name.")); + } + } + }; + addProperty(PDF_VIEWER_PREFERENCES_VIEW_AREA, ViewArea); + addProperty(PDF_VIEWER_PREFERENCES_VIEW_CLIP, ViewClip); + addProperty(PDF_VIEWER_PREFERENCES_PRINT_AREA, PrintArea); + addProperty(PDF_VIEWER_PREFERENCES_PRINT_CLIP, PrintClip); + + // Print scaling + const PDFObject& printScaling = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_PRINT_SCALING)); + if (!printScaling.isNull()) + { + if (!printScaling.isName()) + { + throw PDFException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = printScaling.getString(); + if (enumName == "None") + { + result.m_printScaling = PrintScaling::None; + } + else if (enumName == "AppDefault") + { + result.m_printScaling = PrintScaling::AppDefault; + } + else + { + throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + // Duplex + const PDFObject& duplex = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_DUPLEX)); + if (!duplex.isNull()) + { + if (!duplex.isName()) + { + throw PDFException(PDFTranslationContext::tr("Expected name.")); + } + + QByteArray enumName = duplex.getString(); + if (enumName == "Simplex") + { + result.m_duplex = Duplex::Simplex; + } + else if (enumName == "DuplexFlipShortEdge") + { + result.m_duplex = Duplex::DuplexFlipShortEdge; + } + else if (enumName == "DuplexFlipLongEdge") + { + result.m_duplex = Duplex::DuplexFlipLongEdge; + } + else + { + throw PDFException(PDFTranslationContext::tr("Unknown viewer preferences settings.")); + } + } + + // Print page range + const PDFObject& printPageRange = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE)); + if (!printPageRange.isNull()) + { + if (!duplex.isArray()) + { + throw PDFException(PDFTranslationContext::tr("Expected array of integers.")); + } + + // According to PDF Reference 1.7, this entry is ignored in following cases: + // 1) Array size is odd + // 2) Array contains negative numbers + // + // But what should we do, if we get 0? Pages in the PDF file are numbered from 1. + // So if this situation occur, we also ignore the entry. + const PDFArray* array = duplex.getArray(); + + const size_t count = array->getCount(); + if (count % 2 == 0 && count > 0) + { + bool badPageNumber = false; + int scanned = 0; + PDFInteger start = -1; + + for (size_t i = 0; i < count; ++i) + { + const PDFObject& number = document->getObject(array->getItem(i)); + if (number.isInt()) + { + PDFInteger current = number.getInteger(); + + if (current <= 0) + { + badPageNumber = true; + break; + } + + switch (scanned++) + { + case 0: + { + start = current; + break; + } + + case 1: + { + scanned = 0; + result.m_printPageRanges.emplace_back(start, current); + break; + } + + default: + Q_ASSERT(false); + } + } + else + { + throw PDFException(PDFTranslationContext::tr("Expected integer.")); + } + } + + // Did we get negative or zero value? If yes, clear the range. + if (badPageNumber) + { + result.m_printPageRanges.clear(); + } + } + } + + // Number of copies + const PDFObject& numberOfCopies = document->getObject(viewerPreferencesDictionary->get(PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES)); + if (!numberOfCopies.isNull()) + { + if (numberOfCopies.isInt()) + { + result.m_numberOfCopies = numberOfCopies.getInteger(); + } + else + { + throw PDFException(PDFTranslationContext::tr("Expected integer.")); + } + } + + // Enforce + PDFDocumentDataLoaderDecorator loader(document); + std::vector enforce = loader.readNameArrayFromDictionary(viewerPreferencesDictionary, "Enforce"); + result.m_optionFlags.setFlag(EnforcePrintScaling, std::find(enforce.cbegin(), enforce.cend(), "PrintScaling") != enforce.cend()); + } + else if (!viewerPreferencesObject.isNull()) + { + throw PDFException(PDFTranslationContext::tr("Viewer preferences must be a dictionary.")); + } + } + + return result; +} + +PDFPageLabel PDFPageLabel::parse(PDFInteger pageIndex, const PDFObjectStorage* storage, const PDFObject& object) +{ + const PDFObject& dereferencedObject = storage->getObject(object); + if (dereferencedObject.isDictionary()) + { + std::array, 5> numberingStyles = { std::pair{ "D", NumberingStyle::DecimalArabic}, + std::pair{ "R", NumberingStyle::UppercaseRoman }, + std::pair{ "r", NumberingStyle::LowercaseRoman }, + std::pair{ "A", NumberingStyle::UppercaseLetters}, + std::pair{ "a", NumberingStyle::LowercaseLetters} }; + + const PDFDictionary* dictionary = dereferencedObject.getDictionary(); + const PDFDocumentDataLoaderDecorator loader(storage); + const NumberingStyle numberingStyle = loader.readEnumByName(dictionary->get("S"), numberingStyles.cbegin(), numberingStyles.cend(), NumberingStyle::None); + const QString prefix = loader.readTextString(dictionary->get("P"), QString()); + const PDFInteger startNumber = loader.readInteger(dictionary->get("St"), 1); + return PDFPageLabel(numberingStyle, prefix, pageIndex, startNumber); + } + else + { + throw PDFException(PDFTranslationContext::tr("Expected page label dictionary.")); + } + + return PDFPageLabel(); +} + +const PDFDocumentSecurityStore::SecurityStoreItem* PDFDocumentSecurityStore::getItem(const QByteArray& hash) const +{ + auto it = m_VRI.find(hash); + if (it != m_VRI.cend()) + { + return &it->second; + } + + return getMasterItem(); +} + +PDFDocumentSecurityStore PDFDocumentSecurityStore::parse(const PDFObject& object, const PDFDocument* document) +{ + PDFDocumentSecurityStore store; + + try + { + if (const PDFDictionary* dssDictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + + auto getDecodedStreams = [document, &loader](const PDFObject& object) -> std::vector + { + std::vector result; + + std::vector references = loader.readReferenceArray(object); + result.reserve(references.size()); + for (const PDFObjectReference& reference : references) + { + PDFObject object = document->getObjectByReference(reference); + if (object.isStream()) + { + result.emplace_back(document->getDecodedStream(object.getStream())); + } + } + + return result; + }; + + store.m_master.Cert = getDecodedStreams(dssDictionary->get("Certs")); + store.m_master.OCSP = getDecodedStreams(dssDictionary->get("OCSPs")); + store.m_master.CRL = getDecodedStreams(dssDictionary->get("CRLs")); + + if (const PDFDictionary* vriDictionary = document->getDictionaryFromObject(dssDictionary->get("VRI"))) + { + for (size_t i = 0, count = vriDictionary->getCount(); i < count; ++i) + { + const PDFObject& vriItemObject = vriDictionary->getValue(i); + if (const PDFDictionary* vriItemDictionary = document->getDictionaryFromObject(vriItemObject)) + { + QByteArray key = vriDictionary->getKey(i).getString(); + + SecurityStoreItem& item = store.m_VRI[key]; + item.Cert = getDecodedStreams(vriItemDictionary->get("Cert")); + item.CRL = getDecodedStreams(vriItemDictionary->get("CRL")); + item.OCSP = getDecodedStreams(vriItemDictionary->get("OCSP")); + item.created = PDFEncoding::convertToDateTime(loader.readStringFromDictionary(vriDictionary, "TU")); + + PDFObject timestampObject = document->getObject(vriItemDictionary->get("TS")); + if (timestampObject.isStream()) + { + item.timestamp = document->getDecodedStream(timestampObject.getStream()); + } + } + } + } + } + } + catch (PDFException) + { + return PDFDocumentSecurityStore(); + } + + return store; +} + +PDFDeveloperExtensions PDFDeveloperExtensions::parse(const PDFObject& object, const PDFDocument* document) +{ + PDFDeveloperExtensions extensions; + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(object)) + { + const size_t extensionsCount = dictionary->getCount(); + extensions.m_extensions.reserve(extensionsCount); + for (size_t i = 0; i < extensionsCount; ++i) + { + // Skip type entry + if (dictionary->getKey(i) == "Type") + { + continue; + } + + if (const PDFDictionary* extensionsDictionary = document->getDictionaryFromObject(dictionary->getValue(i))) + { + PDFDocumentDataLoaderDecorator loader(document); + + Extension extension; + extension.name = dictionary->getKey(i).getString(); + extension.baseVersion = loader.readNameFromDictionary(extensionsDictionary, "BaseName"); + extension.extensionLevel = loader.readIntegerFromDictionary(extensionsDictionary, "ExtensionLevel", 0); + extension.url = loader.readStringFromDictionary(extensionsDictionary, "URL"); + extensions.m_extensions.emplace_back(qMove(extension)); + } + } + } + + return extensions; +} + +PDFDocumentInfo PDFDocumentInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) +{ + PDFDocumentInfo info; + + if (const PDFDictionary* infoDictionary = storage->getDictionaryFromObject(object)) + { + auto readTextString = [storage, infoDictionary](const char* entry, QString& fillEntry) + { + if (infoDictionary->hasKey(entry)) + { + const PDFObject& stringObject = storage->getObject(infoDictionary->get(entry)); + if (stringObject.isString()) + { + // We have succesfully read the string, convert it according to encoding + fillEntry = PDFEncoding::convertTextString(stringObject.getString()); + } + else if (!stringObject.isNull()) + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String expected.")); + } + } + }; + readTextString(PDF_DOCUMENT_INFO_ENTRY_TITLE, info.title); + readTextString(PDF_DOCUMENT_INFO_ENTRY_AUTHOR, info.author); + readTextString(PDF_DOCUMENT_INFO_ENTRY_SUBJECT, info.subject); + readTextString(PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, info.keywords); + readTextString(PDF_DOCUMENT_INFO_ENTRY_CREATOR, info.creator); + readTextString(PDF_DOCUMENT_INFO_ENTRY_PRODUCER, info.producer); + + auto readDate= [storage, infoDictionary](const char* entry, QDateTime& fillEntry) + { + if (infoDictionary->hasKey(entry)) + { + const PDFObject& stringObject = storage->getObject(infoDictionary->get(entry)); + if (stringObject.isString()) + { + // We have succesfully read the string, convert it to date time + fillEntry = PDFEncoding::convertToDateTime(stringObject.getString()); + + if (!fillEntry.isValid()) + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); + } + } + else if (!stringObject.isNull()) + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. String with date time format expected.")); + } + } + }; + readDate(PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, info.creationDate); + readDate(PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, info.modifiedDate); + + if (infoDictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)) + { + const PDFObject& nameObject = storage->getObject(infoDictionary->get(PDF_DOCUMENT_INFO_ENTRY_TRAPPED)); + if (nameObject.isName()) + { + const QByteArray& name = nameObject.getString(); + if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_TRUE) + { + info.trapped = Trapped::True; + } + else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_FALSE) + { + info.trapped = Trapped::False; + } + else if (name == PDF_DOCUMENT_INFO_ENTRY_TRAPPED_UNKNOWN) + { + info.trapped = Trapped::Unknown; + } + else + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); + } + } + else if (nameObject.isBool()) + { + info.trapped = nameObject.getBool() ? Trapped::True : Trapped::False; + } + else + { + throw PDFException(PDFTranslationContext::tr("Bad format of document info entry in trailer dictionary. Trapping information expected")); + } + } + + // Scan for extra items + constexpr const char* PREDEFINED_ITEMS[] = { PDF_DOCUMENT_INFO_ENTRY_TITLE, PDF_DOCUMENT_INFO_ENTRY_AUTHOR, PDF_DOCUMENT_INFO_ENTRY_SUBJECT, + PDF_DOCUMENT_INFO_ENTRY_KEYWORDS, PDF_DOCUMENT_INFO_ENTRY_CREATOR, PDF_DOCUMENT_INFO_ENTRY_PRODUCER, + PDF_DOCUMENT_INFO_ENTRY_CREATION_DATE, PDF_DOCUMENT_INFO_ENTRY_MODIFIED_DATE, PDF_DOCUMENT_INFO_ENTRY_TRAPPED }; + for (size_t i = 0; i < infoDictionary->getCount(); ++i) + { + QByteArray key = infoDictionary->getKey(i).getString(); + if (std::none_of(std::begin(PREDEFINED_ITEMS), std::end(PREDEFINED_ITEMS), [&key](const char* item) { return item == key; })) + { + const PDFObject& value = storage->getObject(infoDictionary->getValue(i)); + if (value.isString()) + { + const QByteArray& stringValue = value.getString(); + QDateTime dateTime = PDFEncoding::convertToDateTime(stringValue); + if (dateTime.isValid()) + { + info.extra[key] = dateTime; + } + else + { + info.extra[key] = PDFEncoding::convertTextString(stringValue); + } + } + } + } + } + + return info; +} + +PDFArticleThread PDFArticleThread::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + PDFArticleThread result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + PDFObjectReference firstBeadReference = loader.readReferenceFromDictionary(dictionary, "F"); + + std::set visitedBeads; + PDFObjectReference currentBead = firstBeadReference; + + while (!visitedBeads.count(currentBead)) + { + visitedBeads.insert(currentBead); + + // Read bead + if (const PDFDictionary* beadDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(currentBead))) + { + Bead bead; + bead.self = currentBead; + bead.thread = loader.readReferenceFromDictionary(beadDictionary, "T"); + bead.next = loader.readReferenceFromDictionary(beadDictionary, "N"); + bead.previous = loader.readReferenceFromDictionary(beadDictionary, "V"); + bead.page = loader.readReferenceFromDictionary(beadDictionary, "P"); + bead.rect = loader.readRectangle(beadDictionary->get("R"), QRectF()); + + currentBead = bead.next; + result.m_beads.push_back(bead); + } + else + { + // current bead will be the same, the cycle will break + } + } + + result.m_information = PDFDocumentInfo::parse(dictionary->get("I"), storage); + result.m_metadata = loader.readReferenceFromDictionary(dictionary, "Metadata"); + } + + return result; +} + +PDFWebCaptureInfo PDFWebCaptureInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) +{ + PDFWebCaptureInfo result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_version = loader.readNameFromDictionary(dictionary, "V"); + result.m_commands = loader.readReferenceArrayFromDictionary(dictionary, "C"); + } + + return result; +} + +PDFOutputIntent PDFOutputIntent::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + PDFOutputIntent result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_subtype = loader.readNameFromDictionary(dictionary, "S"); + result.m_outputCondition = loader.readTextStringFromDictionary(dictionary, "OutputCondition", QString()); + result.m_outputConditionIdentifier = loader.readTextStringFromDictionary(dictionary, "OutputConditionIdentifier", QString()); + result.m_registryName = loader.readTextStringFromDictionary(dictionary, "RegistryName", QString()); + result.m_info = loader.readTextStringFromDictionary(dictionary, "Info", QString()); + result.m_destOutputProfile = dictionary->get("DestOutputProfile"); + result.m_destOutputProfileRef = PDFOutputIntentICCProfileInfo::parse(dictionary->get("DestOutputProfileRef"), storage); + result.m_mixingHints = dictionary->get("MixingHints"); + result.m_spectralData = dictionary->get("SpectralData"); + } + + return result; +} + +PDFOutputIntentICCProfileInfo PDFOutputIntentICCProfileInfo::parse(const PDFObject& object, const PDFObjectStorage* storage) +{ + PDFOutputIntentICCProfileInfo result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_checkSum = loader.readStringFromDictionary(dictionary, "CheckSum"); + result.m_colorants = loader.readNameArrayFromDictionary(dictionary, "ColorantTable"); + result.m_iccVersion = loader.readStringFromDictionary(dictionary, "ICCVersion"); + result.m_signature = loader.readStringFromDictionary(dictionary, "ProfileCS"); + result.m_profileName = loader.readTextStringFromDictionary(dictionary, "ProfileName", QString()); + result.m_urls = dictionary->get("URLs"); + } + + return result; +} + +std::optional PDFLegalAttestation::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + std::optional result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + result = PDFLegalAttestation(); + + result->m_entries[JavaScriptActions] = loader.readIntegerFromDictionary(dictionary, "JavaScriptActions", 0); + result->m_entries[LaunchActions] = loader.readIntegerFromDictionary(dictionary, "LaunchActions", 0); + result->m_entries[URIActions] = loader.readIntegerFromDictionary(dictionary, "URIActions", 0); + result->m_entries[MovieActions] = loader.readIntegerFromDictionary(dictionary, "MovieActions", 0); + result->m_entries[SoundActions] = loader.readIntegerFromDictionary(dictionary, "SoundActions", 0); + result->m_entries[HideAnnotationActions] = loader.readIntegerFromDictionary(dictionary, "HideAnnotationActions", 0); + result->m_entries[GoToRemoteActions] = loader.readIntegerFromDictionary(dictionary, "GoToRemoteActions", 0); + result->m_entries[AlternateImages] = loader.readIntegerFromDictionary(dictionary, "AlternateImages", 0); + result->m_entries[ExternalStreams] = loader.readIntegerFromDictionary(dictionary, "ExternalStreams", 0); + result->m_entries[TrueTypeFonts] = loader.readIntegerFromDictionary(dictionary, "TrueTypeFonts", 0); + result->m_entries[ExternalRefXobjects] = loader.readIntegerFromDictionary(dictionary, "ExternalRefXobjects", 0); + result->m_entries[ExternalOPIdicts] = loader.readIntegerFromDictionary(dictionary, "ExternalOPIdicts", 0); + result->m_entries[NonEmbeddedFonts] = loader.readIntegerFromDictionary(dictionary, "NonEmbeddedFonts", 0); + result->m_entries[DevDepGS_OP] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_OP", 0); + result->m_entries[DevDepGS_HT] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_HT", 0); + result->m_entries[DevDepGS_TR] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_TR", 0); + result->m_entries[DevDepGS_UCR] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_UCR", 0); + result->m_entries[DevDepGS_BG] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_BG", 0); + result->m_entries[DevDepGS_FL] = loader.readIntegerFromDictionary(dictionary, "DevDepGS_FL", 0); + result->m_hasOptionalContent = loader.readBooleanFromDictionary(dictionary, "OptionalContent", false); + result->m_attestation = loader.readTextStringFromDictionary(dictionary, "Attestation", QString()); + } + + return result; +} + +PDFDocumentRequirements::ValidationResult PDFDocumentRequirements::validate(Requirements supported) const +{ + ValidationResult result; + + QStringList unsatisfiedRequirements; + for (const RequirementEntry& entry : m_requirements) + { + if (entry.requirement == None) + { + // Unrecognized entry, just add the penalty + result.penalty += entry.penalty; + continue; + } + + if (!supported.testFlag(entry.requirement)) + { + result.penalty += entry.penalty; + unsatisfiedRequirements << getRequirementName(entry.requirement); + } + } + + if (!unsatisfiedRequirements.isEmpty()) + { + result.message = PDFTranslationContext::tr("Required features %1 are unsupported. Document processing can be limited.").arg(unsatisfiedRequirements.join(", ")); + } + + return result; +} + +QString PDFDocumentRequirements::getRequirementName(Requirement requirement) +{ + switch (requirement) + { + case OCInteract: + return PDFTranslationContext::tr("Optional Content User Interaction"); + case OCAutoStates: + return PDFTranslationContext::tr("Optional Content Usage"); + case AcroFormInteract: + return PDFTranslationContext::tr("Acrobat Forms"); + case Navigation: + return PDFTranslationContext::tr("Navigation"); + case Markup: + return PDFTranslationContext::tr("Markup Annotations"); + case _3DMarkup: + return PDFTranslationContext::tr("Markup of 3D Content"); + case Multimedia: + return PDFTranslationContext::tr("Multimedia"); + case U3D: + return PDFTranslationContext::tr("U3D Format of PDF 3D"); + case PRC: + return PDFTranslationContext::tr("PRC Format of PDF 3D"); + case Action: + return PDFTranslationContext::tr("Actions"); + case EnableJavaScripts: + return PDFTranslationContext::tr("JavaScript"); + case Attachment: + return PDFTranslationContext::tr("Attached Files"); + case AttachmentEditing: + return PDFTranslationContext::tr("Attached Files Modification"); + case Collection: + return PDFTranslationContext::tr("Collections of Attached Files"); + case CollectionEditing: + return PDFTranslationContext::tr("Collections of Attached Files (editation)"); + case DigSigValidation: + return PDFTranslationContext::tr("Digital Signature Validation"); + case DigSig: + return PDFTranslationContext::tr("Apply Digital Signature"); + case DigSigMDP: + return PDFTranslationContext::tr("Digital Signature Validation (with MDP)"); + case RichMedia: + return PDFTranslationContext::tr("Rich Media"); + case Geospatial2D: + return PDFTranslationContext::tr("Geospatial 2D Features"); + case Geospatial3D: + return PDFTranslationContext::tr("Geospatial 3D Features"); + case DPartInteract: + return PDFTranslationContext::tr("Navigation for Document Parts"); + case SeparationSimulation: + return PDFTranslationContext::tr("Separation Simulation"); + case Transitions: + return PDFTranslationContext::tr("Transitions/Presentations"); + case Encryption: + return PDFTranslationContext::tr("Encryption"); + + default: + Q_ASSERT(false); + break; + } + + return QString(); +} + +PDFDocumentRequirements PDFDocumentRequirements::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + PDFDocumentRequirements requirements; + + PDFDocumentDataLoaderDecorator loader(storage); + requirements.m_requirements = loader.readObjectList(object); + + return requirements; +} + +PDFDocumentRequirements::RequirementEntry PDFDocumentRequirements::RequirementEntry::parse(const PDFObjectStorage* storage, const PDFObject& object) +{ + RequirementEntry entry; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + constexpr const std::array requirementTypes = { + std::pair{ "OCInteract", OCInteract }, + std::pair{ "OCAutoStates", OCAutoStates }, + std::pair{ "AcroFormInteract", AcroFormInteract }, + std::pair{ "Navigation", Navigation }, + std::pair{ "Markup", Markup }, + std::pair{ "3DMarkup", _3DMarkup }, + std::pair{ "Multimedia", Multimedia }, + std::pair{ "U3D", U3D }, + std::pair{ "PRC", PRC }, + std::pair{ "Action", Action }, + std::pair{ "EnableJavaScripts", EnableJavaScripts }, + std::pair{ "Attachment", Attachment }, + std::pair{ "AttachmentEditing", AttachmentEditing }, + std::pair{ "Collection", Collection }, + std::pair{ "CollectionEditing", CollectionEditing }, + std::pair{ "DigSigValidation", DigSigValidation }, + std::pair{ "DigSig", DigSig }, + std::pair{ "DigSigMDP", DigSigMDP }, + std::pair{ "RichMedia", RichMedia }, + std::pair{ "Geospatial2D", Geospatial2D }, + std::pair{ "Geospatial3D", Geospatial3D }, + std::pair{ "DPartInteract", DPartInteract }, + std::pair{ "SeparationSimulation", SeparationSimulation }, + std::pair{ "Transitions", Transitions }, + std::pair{ "Encryption", Encryption } + }; + + entry.requirement = loader.readEnumByName(dictionary->get("S"), requirementTypes.begin(), requirementTypes.end(), None); + entry.handler = dictionary->get("RH"); + entry.version = loader.readNameFromDictionary(dictionary, "V"); + entry.penalty = loader.readIntegerFromDictionary(dictionary, "Penalty", 100); + } + + return entry; +} + +PDFPageAdditionalActions PDFPageAdditionalActions::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFPageAdditionalActions result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + result.m_actions[Open] = PDFAction::parse(storage, dictionary->get("O")); + result.m_actions[Close] = PDFAction::parse(storage, dictionary->get("C")); + } + + return result; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfcatalog.h b/Pdf4QtLib/sources/pdfcatalog.h index e137f46..d23def5 100644 --- a/Pdf4QtLib/sources/pdfcatalog.h +++ b/Pdf4QtLib/sources/pdfcatalog.h @@ -1,766 +1,766 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFCATALOG_H -#define PDFCATALOG_H - -#include "pdfobject.h" -#include "pdfpage.h" -#include "pdfoptionalcontent.h" -#include "pdfoutline.h" -#include "pdfaction.h" - -#include - -#include -#include -#include - -namespace pdf -{ - -class PDFDocument; - -/// Defines page layout. Default value is SinglePage. This enum specifies the page layout -/// to be used in viewer application. -enum class PageLayout -{ - SinglePage, ///< Display one page at time (single page on screen) - OneColumn, ///< Displays pages in one column (continuous mode) - TwoColumnLeft, ///< Display pages in two continuous columns, odd numbered pages are on the left - TwoColumnRight, ///< Display pages in two continuous columns, even numbered pages are on the left - TwoPagesLeft, ///< Display two pages on the screen, odd numbered pages are on the left - TwoPagesRight ///< Display two pages on the screen, even numbered pages are on the left -}; - -/// Specifies, how the document should be displayed in the viewer application. -enum class PageMode -{ - UseNone, ///< Default value, neither document outline or thumbnails are visible - UseOutlines, ///< Document outline window is selected and visible - UseThumbnails, ///< Document thumbnails window is selected and visible - Fullscreen, ///< Use fullscreen mode, no menu bars, window controls, or any other window visible (presentation mode) - UseOptionalContent, ///< Optional content group window is selected and visible - UseAttachments, ///< Attachments window is selected and visible -}; - -/// Represents page numbering definition object -class PDFPageLabel -{ -public: - - enum class NumberingStyle - { - None, ///< This means, only prefix is used, no numbering - DecimalArabic, - UppercaseRoman, - LowercaseRoman, - UppercaseLetters, - LowercaseLetters - }; - - explicit inline PDFPageLabel() : - m_numberingType(NumberingStyle::None), - m_prefix(), - m_pageIndex(0), - m_startNumber(0) - { - - } - - explicit inline PDFPageLabel(NumberingStyle numberingType, const QString& prefix, PDFInteger pageIndex, PDFInteger startNumber) : - m_numberingType(numberingType), - m_prefix(prefix), - m_pageIndex(pageIndex), - m_startNumber(startNumber) - { - - } - - /// Comparison operator, works only with page indices (because they should be unique) - bool operator<(const PDFPageLabel& other) const { return m_pageIndex < other.m_pageIndex; } - - NumberingStyle getNumberingStyle() const { return m_numberingType; } - const QString& getPrefix() const { return m_prefix; } - PDFInteger getPageIndex() const { return m_pageIndex; } - PDFInteger getPageStartNumber() const { return m_startNumber; } - - /// Parses page label object from PDF object, according to PDF Reference 1.7, Table 8.10 - static PDFPageLabel parse(PDFInteger pageIndex, const PDFObjectStorage* storage, const PDFObject& object); - -private: - NumberingStyle m_numberingType; - QString m_prefix; - PDFInteger m_pageIndex; - PDFInteger m_startNumber; -}; - -/// Info about the document. Title, Author, Keywords... It also stores "extra" -/// values, which are in info dictionary. They can be either strings, or date -/// time (QString or QDateTime). -struct PDFDocumentInfo -{ - /// Indicates, that document was modified that it includes trapping information. - /// See PDF Reference 1.7, Section 10.10.5 "Trapping Support". - enum class Trapped - { - True, ///< Fully trapped - False, ///< Not yet trapped - Unknown ///< Either unknown, or it has been trapped partly, not fully - }; - - /// Parses info from catalog dictionary. If object cannot be parsed, or error occurs, - /// then exception is thrown. This function may throw exceptions, if error occured. - /// \param object Object containing info dictionary - /// \param storage Storage of objects - static PDFDocumentInfo parse(const PDFObject& object, const PDFObjectStorage* storage); - - QString title; - QString author; - QString subject; - QString keywords; - QString creator; - QString producer; - QDateTime creationDate; - QDateTime modifiedDate; - Trapped trapped = Trapped::Unknown; - PDFVersion version; - std::map extra; -}; - -class PDFViewerPreferences -{ -public: - - enum OptionFlag - { - None = 0x0000, ///< Empty flag - HideToolbar = 0x0001, ///< Hide toolbar - HideMenubar = 0x0002, ///< Hide menubar - HideWindowUI = 0x0004, ///< Hide window UI (for example scrollbars, navigation controls, etc.) - FitWindow = 0x0008, ///< Resize window to fit first displayed page - CenterWindow = 0x0010, ///< Position of the document's window should be centered on the screen - DisplayDocTitle = 0x0020, ///< Display documents title instead of file name (introduced in PDF 1.4) - PickTrayByPDFSize = 0x0040, ///< Pick tray by PDF size (printing option) - EnforcePrintScaling = 0x0080 ///< Enforce print scaling settings - }; - - Q_DECLARE_FLAGS(OptionFlags, OptionFlag) - - /// This enum specifies, how to display document, when exiting full screen mode. - enum class NonFullScreenPageMode - { - UseNone, - UseOutline, - UseThumbnails, - UseOptionalContent - }; - - /// Predominant reading order of text. - enum class Direction - { - LeftToRight, ///< Default - RightToLeft ///< Reading order is right to left. Also used for vertical writing systems (Chinese/Japan etc.) - }; - - /// Printer settings - paper handling option to use when printing the document. - enum class Duplex - { - None, - Simplex, - DuplexFlipShortEdge, - DuplexFlipLongEdge - }; - - enum class PrintScaling - { - None, - AppDefault - }; - - enum Properties - { - ViewArea, - ViewClip, - PrintArea, - PrintClip, - EndProperties - }; - - inline PDFViewerPreferences() = default; - - inline PDFViewerPreferences(const PDFViewerPreferences&) = default; - inline PDFViewerPreferences(PDFViewerPreferences&&) = default; - - inline PDFViewerPreferences& operator=(const PDFViewerPreferences&) = default; - inline PDFViewerPreferences& operator=(PDFViewerPreferences&&) = default; - - /// Parses viewer preferences from catalog dictionary. If object cannot be parsed, or error occurs, - /// then exception is thrown. - static PDFViewerPreferences parse(const PDFObject& catalogDictionary, const PDFDocument* document); - - OptionFlags getOptions() const { return m_optionFlags; } - const QByteArray& getProperty(Properties property) const { return m_properties.at(property); } - NonFullScreenPageMode getNonFullScreenPageMode() const { return m_nonFullScreenPageMode; } - Direction getDirection() const { return m_direction; } - Duplex getDuplex() const { return m_duplex; } - PrintScaling getPrintScaling() const { return m_printScaling; } - const std::vector>& getPrintPageRanges() const { return m_printPageRanges; } - PDFInteger getNumberOfCopies() const { return m_numberOfCopies; } - -private: - OptionFlags m_optionFlags = None; - std::array m_properties; - NonFullScreenPageMode m_nonFullScreenPageMode = NonFullScreenPageMode::UseNone; - Direction m_direction = Direction::LeftToRight; - Duplex m_duplex = Duplex::None; - PrintScaling m_printScaling = PrintScaling::AppDefault; - std::vector> m_printPageRanges; - PDFInteger m_numberOfCopies = 1; -}; - -/// Document security store. Contains certificates, CRLs, OCSPs, and -/// other data for signature validation. -class PDF4QTLIBSHARED_EXPORT PDFDocumentSecurityStore -{ -public: - explicit inline PDFDocumentSecurityStore() = default; - - struct SecurityStoreItem - { - std::vector Cert; - std::vector CRL; - std::vector OCSP; - QDateTime created; - QByteArray timestamp; - }; - - /// Returns master item. Return value is never nullptr. - const SecurityStoreItem* getMasterItem() const { return &m_master; } - - /// Get item using hash. If item is not found, master item is returned. - const SecurityStoreItem* getItem(const QByteArray& hash) const; - - /// Parses document security store from catalog dictionary. If object cannot be parsed, or error occurs, - /// then empty object is returned. - static PDFDocumentSecurityStore parse(const PDFObject& object, const PDFDocument* document); - -private: - SecurityStoreItem m_master; - std::map m_VRI; -}; - -/// Article thread. Each thread contains beads, which can be across multiple pages. -class PDFArticleThread -{ -public: - explicit inline PDFArticleThread() = default; - - struct Bead - { - PDFObjectReference self; - PDFObjectReference thread; - PDFObjectReference next; - PDFObjectReference previous; - PDFObjectReference page; - QRectF rect; - }; - using Beads = std::vector; - using Information = PDFDocumentInfo; - - const Beads& getBeads() const { return m_beads; } - const Information& getInformation() const { return m_information; } - const PDFObjectReference& getMetadata() const { return m_metadata; } - - /// Parses article thread from object. If object cannot be parsed, or error occurs, - /// then empty object is returned. - /// \param storage Storage - /// \param object Object - static PDFArticleThread parse(const PDFObjectStorage* storage, const PDFObject& object); - -private: - Beads m_beads; - Information m_information; - PDFObjectReference m_metadata; -}; - -/// Document extensions. Contains information about developer's extensions -/// used in document. -class PDF4QTLIBSHARED_EXPORT PDFDeveloperExtensions -{ -public: - explicit PDFDeveloperExtensions() = default; - - struct Extension - { - QByteArray name; - QByteArray baseVersion; - PDFInteger extensionLevel = 0; - QByteArray url; - }; - - using Extensions = std::vector; - - /// Returns list of extensions - const Extensions& getExtensions() const { return m_extensions; } - - /// Parses extensions from catalog dictionary. If object cannot be parsed, or error occurs, - /// then empty object is returned, no exception is thrown. - /// \param object Extensions dictionary - /// \param document Document - static PDFDeveloperExtensions parse(const PDFObject& object, const PDFDocument* document); - -private: - Extensions m_extensions; -}; - -/// Web capture info -class PDF4QTLIBSHARED_EXPORT PDFWebCaptureInfo -{ -public: - explicit PDFWebCaptureInfo() = default; - - const QByteArray& getVersion() const { return m_version; } - const std::vector& getCommands() const { return m_commands; } - - /// Parses web capture info from catalog dictionary. If object cannot be parsed, or error occurs, - /// then empty object is returned, no exception is thrown. - /// \param object Spider info dictionary - /// \param storage Storage - static PDFWebCaptureInfo parse(const PDFObject& object, const PDFObjectStorage* storage); - -private: - QByteArray m_version; - std::vector m_commands; -}; - -class PDF4QTLIBSHARED_EXPORT PDFOutputIntentICCProfileInfo -{ -public: - explicit PDFOutputIntentICCProfileInfo() = default; - - const QByteArray& getChecksum() const { return m_checkSum; } - const std::vector& getColorants() const { return m_colorants; } - const QByteArray& getIccVersion() const { return m_iccVersion; } - const QByteArray& getSignature() const { return m_signature; } - const QString& getProfileName() const { return m_profileName; } - const PDFObject& getUrls() const { return m_urls; } - - /// Parses icc profile info from object. If object cannot be parsed, or error occurs, - /// then empty object is returned, no exception is thrown. - /// \param object Output intent dictionary - /// \param storage Storage - static PDFOutputIntentICCProfileInfo parse(const PDFObject& object, const PDFObjectStorage* storage); - -private: - QByteArray m_checkSum; - std::vector m_colorants; - QByteArray m_iccVersion; - QByteArray m_signature; - QString m_profileName; - PDFObject m_urls; -}; - -/// Output intent -class PDF4QTLIBSHARED_EXPORT PDFOutputIntent -{ -public: - explicit PDFOutputIntent() = default; - - const QByteArray& getSubtype() const { return m_subtype; } - const QString& getOutputCondition() const { return m_outputCondition; } - const QString& getOutputConditionIdentifier() const { return m_outputConditionIdentifier; } - const QString& getRegistryName() const { return m_registryName; } - const QString& getInfo() const { return m_info; } - const PDFObject& getOutputProfile() const { return m_destOutputProfile; } - const PDFOutputIntentICCProfileInfo& getOutputProfileInfo() const { return m_destOutputProfileRef; } - const PDFObject& getMixingHints() const { return m_mixingHints; } - const PDFObject& getSpectralData() const { return m_spectralData; } - - /// Parses output intent from object. If object cannot be parsed, or error occurs, - /// then empty object is returned, no exception is thrown. - /// \param object Output intent dictionary - /// \param storage Storage - static PDFOutputIntent parse(const PDFObjectStorage* storage, const PDFObject& object); - -private: - QByteArray m_subtype; - QString m_outputCondition; - QString m_outputConditionIdentifier; - QString m_registryName; - QString m_info; - PDFObject m_destOutputProfile; - PDFOutputIntentICCProfileInfo m_destOutputProfileRef; - PDFObject m_mixingHints; - PDFObject m_spectralData; -}; - -/// Legal attestations -class PDF4QTLIBSHARED_EXPORT PDFLegalAttestation -{ -public: - explicit inline PDFLegalAttestation() = default; - - enum Entry - { - JavaScriptActions, - LaunchActions, - URIActions, - MovieActions, - SoundActions, - HideAnnotationActions, - GoToRemoteActions, - AlternateImages, - ExternalStreams, - TrueTypeFonts, - ExternalRefXobjects, - ExternalOPIdicts, - NonEmbeddedFonts, - DevDepGS_OP, - DevDepGS_HT, - DevDepGS_TR, - DevDepGS_UCR, - DevDepGS_BG, - DevDepGS_FL, - LastEntry - }; - - PDFInteger getEntry(Entry entry) const { return m_entries.at(entry); } - bool hasOptionalContent() const { return m_hasOptionalContent; } - const QString& getAttestationText() const { return m_attestation; } - - /// Parses legal attestation from object. If object cannot be parsed, or error occurs, - /// then no object is returned, no exception is thrown. - /// \param object Legal attestation dictionary - /// \param storage Storage - static std::optional parse(const PDFObjectStorage* storage, const PDFObject& object); - -private: - std::array m_entries = { }; - bool m_hasOptionalContent = false; - QString m_attestation; -}; - -/// Document can contain requirements for viewer application. This class -/// verifies, if this library and viewer application satisfies these requirements -/// and returns result. -class PDF4QTLIBSHARED_EXPORT PDFDocumentRequirements -{ -public: - - enum Requirement - { - None = 0x00000000, - OCInteract = 0x00000001, - OCAutoStates = 0x00000002, - AcroFormInteract = 0x00000004, - Navigation = 0x00000008, - Markup = 0x00000010, - _3DMarkup = 0x00000020, - Multimedia = 0x00000040, - U3D = 0x00000080, - PRC = 0x00000100, - Action = 0x00000200, - EnableJavaScripts = 0x00000400, - Attachment = 0x00000800, - AttachmentEditing = 0x00001000, - Collection = 0x00002000, - CollectionEditing = 0x00004000, - DigSigValidation = 0x00008000, - DigSig = 0x00010000, - DigSigMDP = 0x00020000, - RichMedia = 0x00040000, - Geospatial2D = 0x00080000, - Geospatial3D = 0x00100000, - DPartInteract = 0x00200000, - SeparationSimulation = 0x00400000, - Transitions = 0x00800000, - Encryption = 0x01000000 - }; - - Q_DECLARE_FLAGS(Requirements, Requirement) - - struct RequirementEntry - { - Requirement requirement = None; - PDFInteger penalty = 100; - QByteArray version; - PDFObject handler; - - static RequirementEntry parse(const PDFObjectStorage* storage, const PDFObject& object); - }; - - struct ValidationResult - { - Requirements unsatisfied = None; - PDFInteger penalty = 0; - QString message; - - bool isOk() const { return penalty < 100; } - bool isError() const { return !isOk(); } - bool isWarning() const { return isOk() && !message.isEmpty(); } - }; - - /// Validates requirements against supported requirements - ValidationResult validate(Requirements supported) const; - - /// Returns string version of requirement - static QString getRequirementName(Requirement requirement); - - /// Parses document requirements. If error occurs, empty - /// document requirements are returned. - /// \param storage Storage - /// \param object Object - static PDFDocumentRequirements parse(const PDFObjectStorage* storage, const PDFObject& object); - -private: - std::vector m_requirements; -}; - -/// Storage for page additional actions -class PDFPageAdditionalActions -{ -public: - - enum Action - { - Open, - Close, - End - }; - - inline explicit PDFPageAdditionalActions() = default; - - /// Returns action for given type. If action is invalid, - /// or not present, nullptr is returned. - /// \param action Action type - const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } - - /// Returns array with all actions - const std::array& getActions() const { return m_actions; } - - /// Parses page additional actions from the object. If object is invalid, then - /// empty additional actions is constructed. - /// \param storage Object storage - /// \param object Additional actions object - static PDFPageAdditionalActions parse(const PDFObjectStorage* storage, PDFObject object); - -private: - std::array m_actions; -}; - -class PDF4QTLIBSHARED_EXPORT PDFCatalog -{ -public: - inline PDFCatalog() = default; - - inline PDFCatalog(const PDFCatalog&) = default; - inline PDFCatalog(PDFCatalog&&) = default; - - inline PDFCatalog& operator=(const PDFCatalog&) = default; - inline PDFCatalog& operator=(PDFCatalog&&) = default; - - static constexpr const size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); - - enum DocumentAction - { - WillClose, - WillSave, - DidSave, - WillPrint, - DidPrint, - LastDocumentAction - }; - - /// Returns viewer preferences of the application - const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; } - - /// Returns the page count - size_t getPageCount() const { return m_pages.size(); } - - /// Returns the page - const PDFPage* getPage(size_t index) const { return &m_pages.at(index); } - - /// Returns page index. If page is not found, then INVALID_PAGE_INDEX is returned. - size_t getPageIndexFromPageReference(PDFObjectReference reference) const; - - /// Returns optional content properties - const PDFOptionalContentProperties* getOptionalContentProperties() const { return &m_optionalContentProperties; } - - /// Returns root pointer for outline items - QSharedPointer getOutlineRootPtr() const { return m_outlineRoot; } - - /// Returns action, which should be performed - const PDFAction* getOpenAction() const { return m_openAction.data(); } - - /// Returns version of the PDF specification, to which the document conforms. - const QByteArray& getVersion() const { return m_version; } - - PageLayout getPageLayout() const { return m_pageLayout; } - PageMode getPageMode() const { return m_pageMode; } - const QByteArray& getBaseURI() const { return m_baseURI; } - const std::map& getEmbeddedFiles() const { return m_namedEmbeddedFiles; } - const PDFObject& getFormObject() const { return m_formObject; } - const PDFDeveloperExtensions& getExtensions() const { return m_extensions; } - const PDFDocumentSecurityStore& getDocumentSecurityStore() const { return m_documentSecurityStore; } - const std::vector& getArticleThreads() const { return m_threads; } - const PDFAction* getDocumentAction(DocumentAction action) const { return m_documentActions.at(action).get(); } - const auto& getDocumentActions() const { return m_documentActions; } - const PDFObject& getMetadata() const { return m_metadata; } - const PDFObject& getStructureTreeRoot() const { return m_structureTreeRoot; } - const QString& getLanguage() const { return m_language; } - const PDFWebCaptureInfo& getWebCaptureInfo() const { return m_webCaptureInfo; } - const std::vector& getOutputIntents() const { return m_outputIntents; } - const PDFObject& getPieceInfo() const { return m_pieceInfo; } - const PDFObject& getPerms() const { return m_perms; } - const PDFLegalAttestation* getLegalAttestation() const { return m_legalAttestation.has_value() ? &m_legalAttestation.value() : nullptr; } - const PDFObject& getRequirements() const { return m_requirements; } - const PDFObject& getCollection() const { return m_collection; } - bool isXFANeedsRendering() const { return m_xfaNeedsRendering; } - const PDFObject& getAssociatedFiles() const { return m_associatedFiles; } - const PDFObject& getDocumentPartRoot() const { return m_documentPartRoot; } - const std::map& getNamedDestinations() const { return m_namedDestinations; } - - /// Is document marked to have structure tree conforming to tagged document convention? - bool isLogicalStructureMarked() const { return m_markInfoFlags.testFlag(MarkInfo_Marked); } - - /// Is document marked to have structure tree with user attributes? - bool isLogicalStructureUserPropertiesUsed() const { return m_markInfoFlags.testFlag(MarkInfo_UserProperties); } - - /// Is document marked to have structure tree not completely conforming to standard? - bool isLogicalStructureSuspects() const { return m_markInfoFlags.testFlag(MarkInfo_Suspects); } - - /// Returns destination using the key. If destination with the key is not found, - /// then nullptr is returned. - /// \param key Destination key - /// \returns Pointer to the destination, or nullptr - const PDFDestination* getNamedDestination(const QByteArray& key) const; - - /// Returns javascript action using the key. If javascript action is not found, - /// then nullptr is returned. - /// \param key Action key - /// \returns Javascript action, or nullptr - PDFActionPtr getNamedJavaScriptAction(const QByteArray& key) const; - - /// Returns appearance stream using the key. If appearance stream is not found, - /// then empty object is returned. - /// \param key Appearance stream key - /// \returns Appearance, or nullptr - PDFObject getNamedAppearanceStream(const QByteArray& key) const; - - /// Returns named page using the key. If named page is not found, - /// then empty object is returned. - /// \param key Page key - /// \returns Page, or nullptr - PDFObject getNamedPage(const QByteArray& key) const; - - /// Returns named template using the key. If named template is not found, - /// then empty object is returned. - /// \param key Template key - /// \returns Template, or nullptr - PDFObject getNamedTemplate(const QByteArray& key) const; - - /// Returns named digital identifier using the key. If named digital identifier is not found, - /// then empty object is returned. Digital identifiers are used in Web Capture functionality. - /// See also PDF 2.0 specification. - /// \param key Digital identifier key - /// \returns Digital identifier, or nullptr - PDFObject getNamedDigitalIdentifier(const QByteArray& key) const; - - /// Returns named url using the key. If named url is not found, - /// then empty object is returned. Urls are used in Web Capture functionality. - /// See also PDF 2.0 specification. - /// \param key Url key - /// \returns Url, or nullptr - PDFObject getNamedUrl(const QByteArray& key) const; - - /// Returns named alternate representation using the key. If named alternate representation is not found, - /// then empty object is returned. - /// \param key Alternate representation key - /// \returns Alternate representation, or nullptr - PDFObject getNamedAlternateRepresentation(const QByteArray& key) const; - - /// Returns named rendition using the key. If named rendition is not found, - /// then empty object is returned. - /// \param key Rendition key - /// \returns Rendition, or nullptr - PDFObject getNamedRendition(const QByteArray& key) const; - - /// Returns all named JavaScript actions - const std::map& getNamedJavaScriptActions() const { return m_namedJavaScriptActions; } - - /// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs, - /// then exception is thrown. - static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document); - -private: - - enum MarkInfoFlag : uint8_t - { - MarkInfo_None = 0x0000, - MarkInfo_Marked = 0x0001, ///< Document conforms to tagged PDF convention - MarkInfo_UserProperties = 0x0002, ///< Structure tree contains user properties - MarkInfo_Suspects = 0x0004, ///< Suspects - }; - Q_DECLARE_FLAGS(MarkInfoFlags, MarkInfoFlag) - - QByteArray m_version; - PDFViewerPreferences m_viewerPreferences; - std::vector m_pages; - std::vector m_pageLabels; - PDFOptionalContentProperties m_optionalContentProperties; - QSharedPointer m_outlineRoot; - PDFActionPtr m_openAction; - std::array m_documentActions; - PageLayout m_pageLayout = PageLayout::SinglePage; - PageMode m_pageMode = PageMode::UseNone; - QByteArray m_baseURI; - PDFObject m_formObject; - PDFObject m_structureTreeRoot; - PDFDeveloperExtensions m_extensions; - PDFDocumentSecurityStore m_documentSecurityStore; - std::vector m_threads; - PDFObject m_metadata; - MarkInfoFlags m_markInfoFlags = MarkInfo_None; - QString m_language; - PDFWebCaptureInfo m_webCaptureInfo; - std::vector m_outputIntents; - PDFObject m_pieceInfo; - PDFObject m_perms; - std::optional m_legalAttestation; - PDFObject m_requirements; - PDFObject m_collection; - bool m_xfaNeedsRendering = false; - PDFObject m_associatedFiles; - PDFObject m_documentPartRoot; - - // Maps from Names dictionary - std::map m_namedDestinations; - std::map m_namedAppearanceStreams; - std::map m_namedJavaScriptActions; - std::map m_namedPages; - std::map m_namedTemplates; - std::map m_namedDigitalIdentifiers; - std::map m_namedUniformResourceLocators; - std::map m_namedEmbeddedFiles; - std::map m_namedAlternateRepresentations; - std::map m_namedRenditions; -}; - -} // namespace pdf - -#endif // PDFCATALOG_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFCATALOG_H +#define PDFCATALOG_H + +#include "pdfobject.h" +#include "pdfpage.h" +#include "pdfoptionalcontent.h" +#include "pdfoutline.h" +#include "pdfaction.h" + +#include + +#include +#include +#include + +namespace pdf +{ + +class PDFDocument; + +/// Defines page layout. Default value is SinglePage. This enum specifies the page layout +/// to be used in viewer application. +enum class PageLayout +{ + SinglePage, ///< Display one page at time (single page on screen) + OneColumn, ///< Displays pages in one column (continuous mode) + TwoColumnLeft, ///< Display pages in two continuous columns, odd numbered pages are on the left + TwoColumnRight, ///< Display pages in two continuous columns, even numbered pages are on the left + TwoPagesLeft, ///< Display two pages on the screen, odd numbered pages are on the left + TwoPagesRight ///< Display two pages on the screen, even numbered pages are on the left +}; + +/// Specifies, how the document should be displayed in the viewer application. +enum class PageMode +{ + UseNone, ///< Default value, neither document outline or thumbnails are visible + UseOutlines, ///< Document outline window is selected and visible + UseThumbnails, ///< Document thumbnails window is selected and visible + Fullscreen, ///< Use fullscreen mode, no menu bars, window controls, or any other window visible (presentation mode) + UseOptionalContent, ///< Optional content group window is selected and visible + UseAttachments, ///< Attachments window is selected and visible +}; + +/// Represents page numbering definition object +class PDFPageLabel +{ +public: + + enum class NumberingStyle + { + None, ///< This means, only prefix is used, no numbering + DecimalArabic, + UppercaseRoman, + LowercaseRoman, + UppercaseLetters, + LowercaseLetters + }; + + explicit inline PDFPageLabel() : + m_numberingType(NumberingStyle::None), + m_prefix(), + m_pageIndex(0), + m_startNumber(0) + { + + } + + explicit inline PDFPageLabel(NumberingStyle numberingType, const QString& prefix, PDFInteger pageIndex, PDFInteger startNumber) : + m_numberingType(numberingType), + m_prefix(prefix), + m_pageIndex(pageIndex), + m_startNumber(startNumber) + { + + } + + /// Comparison operator, works only with page indices (because they should be unique) + bool operator<(const PDFPageLabel& other) const { return m_pageIndex < other.m_pageIndex; } + + NumberingStyle getNumberingStyle() const { return m_numberingType; } + const QString& getPrefix() const { return m_prefix; } + PDFInteger getPageIndex() const { return m_pageIndex; } + PDFInteger getPageStartNumber() const { return m_startNumber; } + + /// Parses page label object from PDF object, according to PDF Reference 1.7, Table 8.10 + static PDFPageLabel parse(PDFInteger pageIndex, const PDFObjectStorage* storage, const PDFObject& object); + +private: + NumberingStyle m_numberingType; + QString m_prefix; + PDFInteger m_pageIndex; + PDFInteger m_startNumber; +}; + +/// Info about the document. Title, Author, Keywords... It also stores "extra" +/// values, which are in info dictionary. They can be either strings, or date +/// time (QString or QDateTime). +struct PDFDocumentInfo +{ + /// Indicates, that document was modified that it includes trapping information. + /// See PDF Reference 1.7, Section 10.10.5 "Trapping Support". + enum class Trapped + { + True, ///< Fully trapped + False, ///< Not yet trapped + Unknown ///< Either unknown, or it has been trapped partly, not fully + }; + + /// Parses info from catalog dictionary. If object cannot be parsed, or error occurs, + /// then exception is thrown. This function may throw exceptions, if error occured. + /// \param object Object containing info dictionary + /// \param storage Storage of objects + static PDFDocumentInfo parse(const PDFObject& object, const PDFObjectStorage* storage); + + QString title; + QString author; + QString subject; + QString keywords; + QString creator; + QString producer; + QDateTime creationDate; + QDateTime modifiedDate; + Trapped trapped = Trapped::Unknown; + PDFVersion version; + std::map extra; +}; + +class PDFViewerPreferences +{ +public: + + enum OptionFlag + { + None = 0x0000, ///< Empty flag + HideToolbar = 0x0001, ///< Hide toolbar + HideMenubar = 0x0002, ///< Hide menubar + HideWindowUI = 0x0004, ///< Hide window UI (for example scrollbars, navigation controls, etc.) + FitWindow = 0x0008, ///< Resize window to fit first displayed page + CenterWindow = 0x0010, ///< Position of the document's window should be centered on the screen + DisplayDocTitle = 0x0020, ///< Display documents title instead of file name (introduced in PDF 1.4) + PickTrayByPDFSize = 0x0040, ///< Pick tray by PDF size (printing option) + EnforcePrintScaling = 0x0080 ///< Enforce print scaling settings + }; + + Q_DECLARE_FLAGS(OptionFlags, OptionFlag) + + /// This enum specifies, how to display document, when exiting full screen mode. + enum class NonFullScreenPageMode + { + UseNone, + UseOutline, + UseThumbnails, + UseOptionalContent + }; + + /// Predominant reading order of text. + enum class Direction + { + LeftToRight, ///< Default + RightToLeft ///< Reading order is right to left. Also used for vertical writing systems (Chinese/Japan etc.) + }; + + /// Printer settings - paper handling option to use when printing the document. + enum class Duplex + { + None, + Simplex, + DuplexFlipShortEdge, + DuplexFlipLongEdge + }; + + enum class PrintScaling + { + None, + AppDefault + }; + + enum Properties + { + ViewArea, + ViewClip, + PrintArea, + PrintClip, + EndProperties + }; + + inline PDFViewerPreferences() = default; + + inline PDFViewerPreferences(const PDFViewerPreferences&) = default; + inline PDFViewerPreferences(PDFViewerPreferences&&) = default; + + inline PDFViewerPreferences& operator=(const PDFViewerPreferences&) = default; + inline PDFViewerPreferences& operator=(PDFViewerPreferences&&) = default; + + /// Parses viewer preferences from catalog dictionary. If object cannot be parsed, or error occurs, + /// then exception is thrown. + static PDFViewerPreferences parse(const PDFObject& catalogDictionary, const PDFDocument* document); + + OptionFlags getOptions() const { return m_optionFlags; } + const QByteArray& getProperty(Properties property) const { return m_properties.at(property); } + NonFullScreenPageMode getNonFullScreenPageMode() const { return m_nonFullScreenPageMode; } + Direction getDirection() const { return m_direction; } + Duplex getDuplex() const { return m_duplex; } + PrintScaling getPrintScaling() const { return m_printScaling; } + const std::vector>& getPrintPageRanges() const { return m_printPageRanges; } + PDFInteger getNumberOfCopies() const { return m_numberOfCopies; } + +private: + OptionFlags m_optionFlags = None; + std::array m_properties; + NonFullScreenPageMode m_nonFullScreenPageMode = NonFullScreenPageMode::UseNone; + Direction m_direction = Direction::LeftToRight; + Duplex m_duplex = Duplex::None; + PrintScaling m_printScaling = PrintScaling::AppDefault; + std::vector> m_printPageRanges; + PDFInteger m_numberOfCopies = 1; +}; + +/// Document security store. Contains certificates, CRLs, OCSPs, and +/// other data for signature validation. +class PDF4QTLIBSHARED_EXPORT PDFDocumentSecurityStore +{ +public: + explicit inline PDFDocumentSecurityStore() = default; + + struct SecurityStoreItem + { + std::vector Cert; + std::vector CRL; + std::vector OCSP; + QDateTime created; + QByteArray timestamp; + }; + + /// Returns master item. Return value is never nullptr. + const SecurityStoreItem* getMasterItem() const { return &m_master; } + + /// Get item using hash. If item is not found, master item is returned. + const SecurityStoreItem* getItem(const QByteArray& hash) const; + + /// Parses document security store from catalog dictionary. If object cannot be parsed, or error occurs, + /// then empty object is returned. + static PDFDocumentSecurityStore parse(const PDFObject& object, const PDFDocument* document); + +private: + SecurityStoreItem m_master; + std::map m_VRI; +}; + +/// Article thread. Each thread contains beads, which can be across multiple pages. +class PDFArticleThread +{ +public: + explicit inline PDFArticleThread() = default; + + struct Bead + { + PDFObjectReference self; + PDFObjectReference thread; + PDFObjectReference next; + PDFObjectReference previous; + PDFObjectReference page; + QRectF rect; + }; + using Beads = std::vector; + using Information = PDFDocumentInfo; + + const Beads& getBeads() const { return m_beads; } + const Information& getInformation() const { return m_information; } + const PDFObjectReference& getMetadata() const { return m_metadata; } + + /// Parses article thread from object. If object cannot be parsed, or error occurs, + /// then empty object is returned. + /// \param storage Storage + /// \param object Object + static PDFArticleThread parse(const PDFObjectStorage* storage, const PDFObject& object); + +private: + Beads m_beads; + Information m_information; + PDFObjectReference m_metadata; +}; + +/// Document extensions. Contains information about developer's extensions +/// used in document. +class PDF4QTLIBSHARED_EXPORT PDFDeveloperExtensions +{ +public: + explicit PDFDeveloperExtensions() = default; + + struct Extension + { + QByteArray name; + QByteArray baseVersion; + PDFInteger extensionLevel = 0; + QByteArray url; + }; + + using Extensions = std::vector; + + /// Returns list of extensions + const Extensions& getExtensions() const { return m_extensions; } + + /// Parses extensions from catalog dictionary. If object cannot be parsed, or error occurs, + /// then empty object is returned, no exception is thrown. + /// \param object Extensions dictionary + /// \param document Document + static PDFDeveloperExtensions parse(const PDFObject& object, const PDFDocument* document); + +private: + Extensions m_extensions; +}; + +/// Web capture info +class PDF4QTLIBSHARED_EXPORT PDFWebCaptureInfo +{ +public: + explicit PDFWebCaptureInfo() = default; + + const QByteArray& getVersion() const { return m_version; } + const std::vector& getCommands() const { return m_commands; } + + /// Parses web capture info from catalog dictionary. If object cannot be parsed, or error occurs, + /// then empty object is returned, no exception is thrown. + /// \param object Spider info dictionary + /// \param storage Storage + static PDFWebCaptureInfo parse(const PDFObject& object, const PDFObjectStorage* storage); + +private: + QByteArray m_version; + std::vector m_commands; +}; + +class PDF4QTLIBSHARED_EXPORT PDFOutputIntentICCProfileInfo +{ +public: + explicit PDFOutputIntentICCProfileInfo() = default; + + const QByteArray& getChecksum() const { return m_checkSum; } + const std::vector& getColorants() const { return m_colorants; } + const QByteArray& getIccVersion() const { return m_iccVersion; } + const QByteArray& getSignature() const { return m_signature; } + const QString& getProfileName() const { return m_profileName; } + const PDFObject& getUrls() const { return m_urls; } + + /// Parses icc profile info from object. If object cannot be parsed, or error occurs, + /// then empty object is returned, no exception is thrown. + /// \param object Output intent dictionary + /// \param storage Storage + static PDFOutputIntentICCProfileInfo parse(const PDFObject& object, const PDFObjectStorage* storage); + +private: + QByteArray m_checkSum; + std::vector m_colorants; + QByteArray m_iccVersion; + QByteArray m_signature; + QString m_profileName; + PDFObject m_urls; +}; + +/// Output intent +class PDF4QTLIBSHARED_EXPORT PDFOutputIntent +{ +public: + explicit PDFOutputIntent() = default; + + const QByteArray& getSubtype() const { return m_subtype; } + const QString& getOutputCondition() const { return m_outputCondition; } + const QString& getOutputConditionIdentifier() const { return m_outputConditionIdentifier; } + const QString& getRegistryName() const { return m_registryName; } + const QString& getInfo() const { return m_info; } + const PDFObject& getOutputProfile() const { return m_destOutputProfile; } + const PDFOutputIntentICCProfileInfo& getOutputProfileInfo() const { return m_destOutputProfileRef; } + const PDFObject& getMixingHints() const { return m_mixingHints; } + const PDFObject& getSpectralData() const { return m_spectralData; } + + /// Parses output intent from object. If object cannot be parsed, or error occurs, + /// then empty object is returned, no exception is thrown. + /// \param object Output intent dictionary + /// \param storage Storage + static PDFOutputIntent parse(const PDFObjectStorage* storage, const PDFObject& object); + +private: + QByteArray m_subtype; + QString m_outputCondition; + QString m_outputConditionIdentifier; + QString m_registryName; + QString m_info; + PDFObject m_destOutputProfile; + PDFOutputIntentICCProfileInfo m_destOutputProfileRef; + PDFObject m_mixingHints; + PDFObject m_spectralData; +}; + +/// Legal attestations +class PDF4QTLIBSHARED_EXPORT PDFLegalAttestation +{ +public: + explicit inline PDFLegalAttestation() = default; + + enum Entry + { + JavaScriptActions, + LaunchActions, + URIActions, + MovieActions, + SoundActions, + HideAnnotationActions, + GoToRemoteActions, + AlternateImages, + ExternalStreams, + TrueTypeFonts, + ExternalRefXobjects, + ExternalOPIdicts, + NonEmbeddedFonts, + DevDepGS_OP, + DevDepGS_HT, + DevDepGS_TR, + DevDepGS_UCR, + DevDepGS_BG, + DevDepGS_FL, + LastEntry + }; + + PDFInteger getEntry(Entry entry) const { return m_entries.at(entry); } + bool hasOptionalContent() const { return m_hasOptionalContent; } + const QString& getAttestationText() const { return m_attestation; } + + /// Parses legal attestation from object. If object cannot be parsed, or error occurs, + /// then no object is returned, no exception is thrown. + /// \param object Legal attestation dictionary + /// \param storage Storage + static std::optional parse(const PDFObjectStorage* storage, const PDFObject& object); + +private: + std::array m_entries = { }; + bool m_hasOptionalContent = false; + QString m_attestation; +}; + +/// Document can contain requirements for viewer application. This class +/// verifies, if this library and viewer application satisfies these requirements +/// and returns result. +class PDF4QTLIBSHARED_EXPORT PDFDocumentRequirements +{ +public: + + enum Requirement + { + None = 0x00000000, + OCInteract = 0x00000001, + OCAutoStates = 0x00000002, + AcroFormInteract = 0x00000004, + Navigation = 0x00000008, + Markup = 0x00000010, + _3DMarkup = 0x00000020, + Multimedia = 0x00000040, + U3D = 0x00000080, + PRC = 0x00000100, + Action = 0x00000200, + EnableJavaScripts = 0x00000400, + Attachment = 0x00000800, + AttachmentEditing = 0x00001000, + Collection = 0x00002000, + CollectionEditing = 0x00004000, + DigSigValidation = 0x00008000, + DigSig = 0x00010000, + DigSigMDP = 0x00020000, + RichMedia = 0x00040000, + Geospatial2D = 0x00080000, + Geospatial3D = 0x00100000, + DPartInteract = 0x00200000, + SeparationSimulation = 0x00400000, + Transitions = 0x00800000, + Encryption = 0x01000000 + }; + + Q_DECLARE_FLAGS(Requirements, Requirement) + + struct RequirementEntry + { + Requirement requirement = None; + PDFInteger penalty = 100; + QByteArray version; + PDFObject handler; + + static RequirementEntry parse(const PDFObjectStorage* storage, const PDFObject& object); + }; + + struct ValidationResult + { + Requirements unsatisfied = None; + PDFInteger penalty = 0; + QString message; + + bool isOk() const { return penalty < 100; } + bool isError() const { return !isOk(); } + bool isWarning() const { return isOk() && !message.isEmpty(); } + }; + + /// Validates requirements against supported requirements + ValidationResult validate(Requirements supported) const; + + /// Returns string version of requirement + static QString getRequirementName(Requirement requirement); + + /// Parses document requirements. If error occurs, empty + /// document requirements are returned. + /// \param storage Storage + /// \param object Object + static PDFDocumentRequirements parse(const PDFObjectStorage* storage, const PDFObject& object); + +private: + std::vector m_requirements; +}; + +/// Storage for page additional actions +class PDFPageAdditionalActions +{ +public: + + enum Action + { + Open, + Close, + End + }; + + inline explicit PDFPageAdditionalActions() = default; + + /// Returns action for given type. If action is invalid, + /// or not present, nullptr is returned. + /// \param action Action type + const PDFAction* getAction(Action action) const { return m_actions.at(action).get(); } + + /// Returns array with all actions + const std::array& getActions() const { return m_actions; } + + /// Parses page additional actions from the object. If object is invalid, then + /// empty additional actions is constructed. + /// \param storage Object storage + /// \param object Additional actions object + static PDFPageAdditionalActions parse(const PDFObjectStorage* storage, PDFObject object); + +private: + std::array m_actions; +}; + +class PDF4QTLIBSHARED_EXPORT PDFCatalog +{ +public: + inline PDFCatalog() = default; + + inline PDFCatalog(const PDFCatalog&) = default; + inline PDFCatalog(PDFCatalog&&) = default; + + inline PDFCatalog& operator=(const PDFCatalog&) = default; + inline PDFCatalog& operator=(PDFCatalog&&) = default; + + static constexpr const size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); + + enum DocumentAction + { + WillClose, + WillSave, + DidSave, + WillPrint, + DidPrint, + LastDocumentAction + }; + + /// Returns viewer preferences of the application + const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; } + + /// Returns the page count + size_t getPageCount() const { return m_pages.size(); } + + /// Returns the page + const PDFPage* getPage(size_t index) const { return &m_pages.at(index); } + + /// Returns page index. If page is not found, then INVALID_PAGE_INDEX is returned. + size_t getPageIndexFromPageReference(PDFObjectReference reference) const; + + /// Returns optional content properties + const PDFOptionalContentProperties* getOptionalContentProperties() const { return &m_optionalContentProperties; } + + /// Returns root pointer for outline items + QSharedPointer getOutlineRootPtr() const { return m_outlineRoot; } + + /// Returns action, which should be performed + const PDFAction* getOpenAction() const { return m_openAction.data(); } + + /// Returns version of the PDF specification, to which the document conforms. + const QByteArray& getVersion() const { return m_version; } + + PageLayout getPageLayout() const { return m_pageLayout; } + PageMode getPageMode() const { return m_pageMode; } + const QByteArray& getBaseURI() const { return m_baseURI; } + const std::map& getEmbeddedFiles() const { return m_namedEmbeddedFiles; } + const PDFObject& getFormObject() const { return m_formObject; } + const PDFDeveloperExtensions& getExtensions() const { return m_extensions; } + const PDFDocumentSecurityStore& getDocumentSecurityStore() const { return m_documentSecurityStore; } + const std::vector& getArticleThreads() const { return m_threads; } + const PDFAction* getDocumentAction(DocumentAction action) const { return m_documentActions.at(action).get(); } + const auto& getDocumentActions() const { return m_documentActions; } + const PDFObject& getMetadata() const { return m_metadata; } + const PDFObject& getStructureTreeRoot() const { return m_structureTreeRoot; } + const QString& getLanguage() const { return m_language; } + const PDFWebCaptureInfo& getWebCaptureInfo() const { return m_webCaptureInfo; } + const std::vector& getOutputIntents() const { return m_outputIntents; } + const PDFObject& getPieceInfo() const { return m_pieceInfo; } + const PDFObject& getPerms() const { return m_perms; } + const PDFLegalAttestation* getLegalAttestation() const { return m_legalAttestation.has_value() ? &m_legalAttestation.value() : nullptr; } + const PDFObject& getRequirements() const { return m_requirements; } + const PDFObject& getCollection() const { return m_collection; } + bool isXFANeedsRendering() const { return m_xfaNeedsRendering; } + const PDFObject& getAssociatedFiles() const { return m_associatedFiles; } + const PDFObject& getDocumentPartRoot() const { return m_documentPartRoot; } + const std::map& getNamedDestinations() const { return m_namedDestinations; } + + /// Is document marked to have structure tree conforming to tagged document convention? + bool isLogicalStructureMarked() const { return m_markInfoFlags.testFlag(MarkInfo_Marked); } + + /// Is document marked to have structure tree with user attributes? + bool isLogicalStructureUserPropertiesUsed() const { return m_markInfoFlags.testFlag(MarkInfo_UserProperties); } + + /// Is document marked to have structure tree not completely conforming to standard? + bool isLogicalStructureSuspects() const { return m_markInfoFlags.testFlag(MarkInfo_Suspects); } + + /// Returns destination using the key. If destination with the key is not found, + /// then nullptr is returned. + /// \param key Destination key + /// \returns Pointer to the destination, or nullptr + const PDFDestination* getNamedDestination(const QByteArray& key) const; + + /// Returns javascript action using the key. If javascript action is not found, + /// then nullptr is returned. + /// \param key Action key + /// \returns Javascript action, or nullptr + PDFActionPtr getNamedJavaScriptAction(const QByteArray& key) const; + + /// Returns appearance stream using the key. If appearance stream is not found, + /// then empty object is returned. + /// \param key Appearance stream key + /// \returns Appearance, or nullptr + PDFObject getNamedAppearanceStream(const QByteArray& key) const; + + /// Returns named page using the key. If named page is not found, + /// then empty object is returned. + /// \param key Page key + /// \returns Page, or nullptr + PDFObject getNamedPage(const QByteArray& key) const; + + /// Returns named template using the key. If named template is not found, + /// then empty object is returned. + /// \param key Template key + /// \returns Template, or nullptr + PDFObject getNamedTemplate(const QByteArray& key) const; + + /// Returns named digital identifier using the key. If named digital identifier is not found, + /// then empty object is returned. Digital identifiers are used in Web Capture functionality. + /// See also PDF 2.0 specification. + /// \param key Digital identifier key + /// \returns Digital identifier, or nullptr + PDFObject getNamedDigitalIdentifier(const QByteArray& key) const; + + /// Returns named url using the key. If named url is not found, + /// then empty object is returned. Urls are used in Web Capture functionality. + /// See also PDF 2.0 specification. + /// \param key Url key + /// \returns Url, or nullptr + PDFObject getNamedUrl(const QByteArray& key) const; + + /// Returns named alternate representation using the key. If named alternate representation is not found, + /// then empty object is returned. + /// \param key Alternate representation key + /// \returns Alternate representation, or nullptr + PDFObject getNamedAlternateRepresentation(const QByteArray& key) const; + + /// Returns named rendition using the key. If named rendition is not found, + /// then empty object is returned. + /// \param key Rendition key + /// \returns Rendition, or nullptr + PDFObject getNamedRendition(const QByteArray& key) const; + + /// Returns all named JavaScript actions + const std::map& getNamedJavaScriptActions() const { return m_namedJavaScriptActions; } + + /// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs, + /// then exception is thrown. + static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document); + +private: + + enum MarkInfoFlag : uint8_t + { + MarkInfo_None = 0x0000, + MarkInfo_Marked = 0x0001, ///< Document conforms to tagged PDF convention + MarkInfo_UserProperties = 0x0002, ///< Structure tree contains user properties + MarkInfo_Suspects = 0x0004, ///< Suspects + }; + Q_DECLARE_FLAGS(MarkInfoFlags, MarkInfoFlag) + + QByteArray m_version; + PDFViewerPreferences m_viewerPreferences; + std::vector m_pages; + std::vector m_pageLabels; + PDFOptionalContentProperties m_optionalContentProperties; + QSharedPointer m_outlineRoot; + PDFActionPtr m_openAction; + std::array m_documentActions; + PageLayout m_pageLayout = PageLayout::SinglePage; + PageMode m_pageMode = PageMode::UseNone; + QByteArray m_baseURI; + PDFObject m_formObject; + PDFObject m_structureTreeRoot; + PDFDeveloperExtensions m_extensions; + PDFDocumentSecurityStore m_documentSecurityStore; + std::vector m_threads; + PDFObject m_metadata; + MarkInfoFlags m_markInfoFlags = MarkInfo_None; + QString m_language; + PDFWebCaptureInfo m_webCaptureInfo; + std::vector m_outputIntents; + PDFObject m_pieceInfo; + PDFObject m_perms; + std::optional m_legalAttestation; + PDFObject m_requirements; + PDFObject m_collection; + bool m_xfaNeedsRendering = false; + PDFObject m_associatedFiles; + PDFObject m_documentPartRoot; + + // Maps from Names dictionary + std::map m_namedDestinations; + std::map m_namedAppearanceStreams; + std::map m_namedJavaScriptActions; + std::map m_namedPages; + std::map m_namedTemplates; + std::map m_namedDigitalIdentifiers; + std::map m_namedUniformResourceLocators; + std::map m_namedEmbeddedFiles; + std::map m_namedAlternateRepresentations; + std::map m_namedRenditions; +}; + +} // namespace pdf + +#endif // PDFCATALOG_H diff --git a/Pdf4QtLib/sources/pdfccittfaxdecoder.cpp b/Pdf4QtLib/sources/pdfccittfaxdecoder.cpp index 7efc7cd..216c111 100644 --- a/Pdf4QtLib/sources/pdfccittfaxdecoder.cpp +++ b/Pdf4QtLib/sources/pdfccittfaxdecoder.cpp @@ -1,711 +1,711 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#include "pdfccittfaxdecoder.h" -#include "pdfexception.h" - -namespace pdf -{ - -template -constexpr uint8_t operator "" _bitlength() -{ - return sizeof...(Digits); -} - -enum CCITT_2D_Code_Mode -{ - Pass, - Horizontal, - Vertical_3L, - Vertical_2L, - Vertical_1L, - Vertical_0, - Vertical_1R, - Vertical_2R, - Vertical_3R, - Invalid -}; - -struct PDFCCITT2DModeInfo -{ - CCITT_2D_Code_Mode mode; - uint16_t code; - uint8_t bits; -}; - -static constexpr uint8_t MAX_2D_MODE_BIT_LENGTH = 7; - -static constexpr PDFCCITT2DModeInfo CCITT_2D_CODE_MODES[] = -{ - { Pass, 0b0001, 0001_bitlength }, - { Horizontal, 0b001, 001_bitlength }, - { Vertical_3L, 0b0000010, 0000010_bitlength }, - { Vertical_2L, 0b000010, 000010_bitlength }, - { Vertical_1L, 0b010, 010_bitlength }, - { Vertical_0, 0b1, 1_bitlength }, - { Vertical_1R, 0b011, 011_bitlength }, - { Vertical_2R, 0b000011, 000011_bitlength }, - { Vertical_3R, 0b0000011, 0000011_bitlength } -}; - -struct PDFCCITTCode -{ - uint16_t length; - uint16_t code; - uint8_t bits; -}; - -static constexpr uint8_t MAX_CODE_BIT_LENGTH = 12; - -static constexpr PDFCCITTCode CCITT_WHITE_CODES[] = { - -// Terminating white codes - - { 0, 0b00110101, 00110101_bitlength }, - { 1, 0b000111, 000111_bitlength }, - { 2, 0b0111, 0111_bitlength }, - { 3, 0b1000, 1000_bitlength }, - { 4, 0b1011, 1011_bitlength }, - { 5, 0b1100, 1100_bitlength }, - { 6, 0b1110, 1110_bitlength }, - { 7, 0b1111, 1111_bitlength }, - { 8, 0b10011, 10011_bitlength }, - { 9, 0b10100, 10100_bitlength }, - { 10, 0b00111, 00111_bitlength }, - { 11, 0b01000, 01000_bitlength }, - { 12, 0b001000, 001000_bitlength }, - { 13, 0b000011, 000011_bitlength }, - { 14, 0b110100, 110100_bitlength }, - { 15, 0b110101, 110101_bitlength }, - { 16, 0b101010, 101010_bitlength }, - { 17, 0b101011, 101011_bitlength }, - { 18, 0b0100111, 0100111_bitlength }, - { 19, 0b0001100, 0001100_bitlength }, - { 20, 0b0001000, 0001000_bitlength }, - { 21, 0b0010111, 0010111_bitlength }, - { 22, 0b0000011, 0000011_bitlength }, - { 23, 0b0000100, 0000100_bitlength }, - { 24, 0b0101000, 0101000_bitlength }, - { 25, 0b0101011, 0101011_bitlength }, - { 26, 0b0010011, 0010011_bitlength }, - { 27, 0b0100100, 0100100_bitlength }, - { 28, 0b0011000, 0011000_bitlength }, - { 29, 0b00000010, 00000010_bitlength }, - { 30, 0b00000011, 00000011_bitlength }, - { 31, 0b00011010, 00011010_bitlength }, - { 32, 0b00011011, 00011011_bitlength }, - { 33, 0b00010010, 00010010_bitlength }, - { 34, 0b00010011, 00010011_bitlength }, - { 35, 0b00010100, 00010100_bitlength }, - { 36, 0b00010101, 00010101_bitlength }, - { 37, 0b00010110, 00010110_bitlength }, - { 38, 0b00010111, 00010111_bitlength }, - { 39, 0b00101000, 00101000_bitlength }, - { 40, 0b00101001, 00101001_bitlength }, - { 41, 0b00101010, 00101010_bitlength }, - { 42, 0b00101011, 00101011_bitlength }, - { 43, 0b00101100, 00101100_bitlength }, - { 44, 0b00101101, 00101101_bitlength }, - { 45, 0b00000100, 00000100_bitlength }, - { 46, 0b00000101, 00000101_bitlength }, - { 47, 0b00001010, 00001010_bitlength }, - { 48, 0b00001011, 00001011_bitlength }, - { 49, 0b01010010, 01010010_bitlength }, - { 50, 0b01010011, 01010011_bitlength }, - { 51, 0b01010100, 01010100_bitlength }, - { 52, 0b01010101, 01010101_bitlength }, - { 53, 0b00100100, 00100100_bitlength }, - { 54, 0b00100101, 00100101_bitlength }, - { 55, 0b01011000, 01011000_bitlength }, - { 56, 0b01011001, 01011001_bitlength }, - { 57, 0b01011010, 01011010_bitlength }, - { 58, 0b01011011, 01011011_bitlength }, - { 59, 0b01001010, 01001010_bitlength }, - { 60, 0b01001011, 01001011_bitlength }, - { 61, 0b00110010, 00110010_bitlength }, - { 62, 0b00110011, 00110011_bitlength }, - { 63, 0b00110100, 00110100_bitlength }, - -// Make up white codes - doesn't terminate - { 64, 0b11011, 11011_bitlength }, - { 128, 0b10010, 10010_bitlength }, - { 192, 0b010111, 010111_bitlength }, - { 256, 0b0110111, 0110111_bitlength }, - { 320, 0b00110110, 00110110_bitlength }, - { 384, 0b00110111, 00110111_bitlength }, - { 448, 0b01100100, 01100100_bitlength }, - { 512, 0b01100101, 01100101_bitlength }, - { 576, 0b01101000, 01101000_bitlength }, - { 640, 0b01100111, 01100111_bitlength }, - { 704, 0b011001100, 011001100_bitlength }, - { 768, 0b011001101, 011001101_bitlength }, - { 832, 0b011010010, 011010010_bitlength }, - { 896, 0b011010011, 011010011_bitlength }, - { 960, 0b011010100, 011010100_bitlength }, - { 1024, 0b011010101, 011010101_bitlength }, - { 1088, 0b011010110, 011010110_bitlength }, - { 1152, 0b011010111, 011010111_bitlength }, - { 1216, 0b011011000, 011011000_bitlength }, - { 1280, 0b011011001, 011011001_bitlength }, - { 1344, 0b011011010, 011011010_bitlength }, - { 1408, 0b011011011, 011011011_bitlength }, - { 1472, 0b010011000, 010011000_bitlength }, - { 1536, 0b010011001, 010011001_bitlength }, - { 1600, 0b010011010, 010011010_bitlength }, - { 1664, 0b011000, 011000_bitlength }, - { 1728, 0b010011011, 010011011_bitlength }, - { 1792, 0b00000001000, 00000001000_bitlength }, - { 1856, 0b00000001100, 00000001100_bitlength }, - { 1920, 0b00000001101, 00000001101_bitlength }, - { 1984, 0b000000010010, 000000010010_bitlength }, - { 2048, 0b000000010011, 000000010011_bitlength }, - { 2112, 0b000000010100, 000000010100_bitlength }, - { 2176, 0b000000010101, 000000010101_bitlength }, - { 2240, 0b000000010110, 000000010110_bitlength }, - { 2304, 0b000000010111, 000000010111_bitlength }, - { 2368, 0b000000011100, 000000011100_bitlength }, - { 2432, 0b000000011101, 000000011101_bitlength }, - { 2496, 0b000000011110, 000000011110_bitlength }, - { 2560, 0b000000011111, 000000011111_bitlength } -}; - -static constexpr PDFCCITTCode CCITT_BLACK_CODES[] = { -// Terminating black codes - - { 0, 0b0000110111, 0000110111_bitlength }, - { 1, 0b010, 010_bitlength }, - { 2, 0b11, 11_bitlength }, - { 3, 0b10, 10_bitlength }, - { 4, 0b011, 011_bitlength }, - { 5, 0b0011, 0011_bitlength }, - { 6, 0b0010, 0010_bitlength }, - { 7, 0b00011, 00011_bitlength }, - { 8, 0b000101, 000101_bitlength }, - { 9, 0b000100, 000100_bitlength }, - { 10, 0b0000100, 0000100_bitlength }, - { 11, 0b0000101, 0000101_bitlength }, - { 12, 0b0000111, 0000111_bitlength }, - { 13, 0b00000100, 00000100_bitlength }, - { 14, 0b00000111, 00000111_bitlength }, - { 15, 0b000011000, 000011000_bitlength }, - { 16, 0b0000010111, 0000010111_bitlength }, - { 17, 0b0000011000, 0000011000_bitlength }, - { 18, 0b0000001000, 0000001000_bitlength }, - { 19, 0b00001100111, 00001100111_bitlength }, - { 20, 0b00001101000, 00001101000_bitlength }, - { 21, 0b00001101100, 00001101100_bitlength }, - { 22, 0b00000110111, 00000110111_bitlength }, - { 23, 0b00000101000, 00000101000_bitlength }, - { 24, 0b00000010111, 00000010111_bitlength }, - { 25, 0b00000011000, 00000011000_bitlength }, - { 26, 0b000011001010, 000011001010_bitlength }, - { 27, 0b000011001011, 000011001011_bitlength }, - { 28, 0b000011001100, 000011001100_bitlength }, - { 29, 0b000011001101, 000011001101_bitlength }, - { 30, 0b000001101000, 000001101000_bitlength }, - { 31, 0b000001101001, 000001101001_bitlength }, - { 32, 0b000001101010, 000001101010_bitlength }, - { 33, 0b000001101011, 000001101011_bitlength }, - { 34, 0b000011010010, 000011010010_bitlength }, - { 35, 0b000011010011, 000011010011_bitlength }, - { 36, 0b000011010100, 000011010100_bitlength }, - { 37, 0b000011010101, 000011010101_bitlength }, - { 38, 0b000011010110, 000011010110_bitlength }, - { 39, 0b000011010111, 000011010111_bitlength }, - { 40, 0b000001101100, 000001101100_bitlength }, - { 41, 0b000001101101, 000001101101_bitlength }, - { 42, 0b000011011010, 000011011010_bitlength }, - { 43, 0b000011011011, 000011011011_bitlength }, - { 44, 0b000001010100, 000001010100_bitlength }, - { 45, 0b000001010101, 000001010101_bitlength }, - { 46, 0b000001010110, 000001010110_bitlength }, - { 47, 0b000001010111, 000001010111_bitlength }, - { 48, 0b000001100100, 000001100100_bitlength }, - { 49, 0b000001100101, 000001100101_bitlength }, - { 50, 0b000001010010, 000001010010_bitlength }, - { 51, 0b000001010011, 000001010011_bitlength }, - { 52, 0b000000100100, 000000100100_bitlength }, - { 53, 0b000000110111, 000000110111_bitlength }, - { 54, 0b000000111000, 000000111000_bitlength }, - { 55, 0b000000100111, 000000100111_bitlength }, - { 56, 0b000000101000, 000000101000_bitlength }, - { 57, 0b000001011000, 000001011000_bitlength }, - { 58, 0b000001011001, 000001011001_bitlength }, - { 59, 0b000000101011, 000000101011_bitlength }, - { 60, 0b000000101100, 000000101100_bitlength }, - { 61, 0b000001011010, 000001011010_bitlength }, - { 62, 0b000001100110, 000001100110_bitlength }, - { 63, 0b000001100111, 000001100111_bitlength }, - -// Make up white codes - doesn't terminate - - { 64, 0b0000001111, 0000001111_bitlength }, - { 128, 0b000011001000, 000011001000_bitlength }, - { 192, 0b000011001001, 000011001001_bitlength }, - { 256, 0b000001011011, 000001011011_bitlength }, - { 320, 0b000000110011, 000000110011_bitlength }, - { 384, 0b000000110100, 000000110100_bitlength }, - { 448, 0b000000110101, 000000110101_bitlength }, - { 512, 0b0000001101100, 0000001101100_bitlength }, - { 576, 0b0000001101101, 0000001101101_bitlength }, - { 640, 0b0000001001010, 0000001001010_bitlength }, - { 704, 0b0000001001011, 0000001001011_bitlength }, - { 768, 0b0000001001100, 0000001001100_bitlength }, - { 832, 0b0000001001101, 0000001001101_bitlength }, - { 896, 0b0000001110010, 0000001110010_bitlength }, - { 960, 0b0000001110011, 0000001110011_bitlength }, - { 1024, 0b0000001110100, 0000001110100_bitlength }, - { 1088, 0b0000001110101, 0000001110101_bitlength }, - { 1152, 0b0000001110110, 0000001110110_bitlength }, - { 1216, 0b0000001110111, 0000001110111_bitlength }, - { 1280, 0b0000001010010, 0000001010010_bitlength }, - { 1344, 0b0000001010011, 0000001010011_bitlength }, - { 1408, 0b0000001010100, 0000001010100_bitlength }, - { 1472, 0b0000001010101, 0000001010101_bitlength }, - { 1536, 0b0000001011010, 0000001011010_bitlength }, - { 1600, 0b0000001011011, 0000001011011_bitlength }, - { 1664, 0b0000001100100, 0000001100100_bitlength }, - { 1728, 0b0000001100101, 0000001100101_bitlength }, - { 1792, 0b00000001000, 00000001000_bitlength }, - { 1856, 0b00000001100, 00000001100_bitlength }, - { 1920, 0b00000001101, 00000001101_bitlength }, - { 1984, 0b000000010010, 000000010010_bitlength }, - { 2048, 0b000000010011, 000000010011_bitlength }, - { 2112, 0b000000010100, 000000010100_bitlength }, - { 2176, 0b000000010101, 000000010101_bitlength }, - { 2240, 0b000000010110, 000000010110_bitlength }, - { 2304, 0b000000010111, 000000010111_bitlength }, - { 2368, 0b000000011100, 000000011100_bitlength }, - { 2432, 0b000000011101, 000000011101_bitlength }, - { 2496, 0b000000011110, 000000011110_bitlength }, - { 2560, 0b000000011111, 000000011111_bitlength } -}; - -PDFCCITTFaxDecoder::PDFCCITTFaxDecoder(const QByteArray* stream, const PDFCCITTFaxDecoderParameters& parameters) : - m_reader(stream, 1), - m_parameters(parameters) -{ - -} - -PDFImageData PDFCCITTFaxDecoder::decode() -{ - PDFBitWriter writer(1); - std::vector codingLine; - std::vector referenceLine; - - int row = 0; - const size_t lineSize = m_parameters.columns + 2; - codingLine.resize(lineSize, m_parameters.columns); - referenceLine.resize(lineSize, m_parameters.columns); - bool isUsing2DEncoding = m_parameters.K < 0; - bool isEndOfLineOccured = m_parameters.hasEndOfLine; - codingLine[0] = 0; - - auto updateIsUsing2DEncoding = [this, &isUsing2DEncoding]() - { - if (m_parameters.K > 0) - { - // Mixed encoding - isUsing2DEncoding = !m_reader.read(1); - } - }; - - isEndOfLineOccured = skipFillAndEOL() || isEndOfLineOccured; - updateIsUsing2DEncoding(); - - while (!m_reader.isAtEnd()) - { - int a0_index = 0; - bool isCurrentPixelBlack = false; - - if (isUsing2DEncoding) - { - int b1_index = 0; - - // 2D encoding - while (codingLine[a0_index] < m_parameters.columns) - { - CCITT_2D_Code_Mode mode = get2DMode(); - switch (mode) - { - case Pass: - { - // In this mode, we set a0 to the b2 (from reference line). In pass mode, - // we do not change pixel color. Why we are adding 2 to the b1_index? - // We want to skip both b1, b2, because they will be left of new a0. - const size_t b2_index = b1_index + 1; - if (b2_index < referenceLine.size()) - { - addPixels(codingLine, a0_index, referenceLine[b2_index], isCurrentPixelBlack, false); - - if (referenceLine[b2_index] < m_parameters.columns) - { - b1_index += 2; - - if (b1_index >= referenceLine.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid pass encoding data in CCITT stream.")); - } - } - } - else - { - throw PDFException(PDFTranslationContext::tr("CCITT b2 index out of range.")); - } - - break; - } - - case Horizontal: - { - // We scan two sequence length. - const int a0a1 = getRunLength(!isCurrentPixelBlack); - const int a1a2 = getRunLength(isCurrentPixelBlack); - - addPixels(codingLine, a0_index, codingLine[a0_index] + a0a1, isCurrentPixelBlack, false); - addPixels(codingLine, a0_index, codingLine[a0_index] + a1a2, !isCurrentPixelBlack, false); - - while (referenceLine[b1_index] <= codingLine[a0_index] && referenceLine[b1_index] < m_parameters.columns) - { - // We do not want to change the color (b1 should have opposite color of a0, - // should be first changing element of reference line right of a0). - b1_index += 2; - - if (b1_index >= referenceLine.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid horizontal encoding data in CCITT stream.")); - } - } - - break; - } - - case Vertical_3L: - case Vertical_2L: - case Vertical_1L: - case Vertical_0: - case Vertical_1R: - case Vertical_2R: - case Vertical_3R: - { - const int32_t a1 = static_cast(referenceLine[b1_index]) + mode - static_cast(Vertical_0); - - if (a1 < 0 || a1 > m_parameters.columns) - { - throw PDFException(PDFTranslationContext::tr("Invalid vertical encoding data in CCITT stream.")); - } - - const bool isNegativeOffset = mode < Vertical_0; - addPixels(codingLine, a0_index, static_cast(a1), isCurrentPixelBlack, isNegativeOffset); - isCurrentPixelBlack = !isCurrentPixelBlack; - - if (codingLine[a0_index] < m_parameters.columns) - { - // We must upgrade b1 index in such a way that it is first index - // of opposite color, than a0 index. If we are using negative offsets, then - // current position can move backward, and so we must look for first b1 index, - // which is of opposite color, than a0. So we decrease index by 1. But what to do, - // if we have b1 index equal to zero? In this case, we add -1 + 2 = 1 index, so we do it in - // same way, as positive/zero shift. - b1_index += (isNegativeOffset && b1_index > 0) ? -1 : 1; - - // Why we have this check, if same check is in while cycle? Because if we are adding - // to the b1_index, we can go outside of reference line range. - if (b1_index >= referenceLine.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid vertical encoding data in CCITT stream.")); - } - - while (referenceLine[b1_index] <= codingLine[a0_index] && referenceLine[b1_index] < m_parameters.columns) - { - // We do not want to change the color (b1 should have opposite color of a0, - // should be first changing element of reference line right of a0). - b1_index += 2; - - if (b1_index >= referenceLine.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid vertical encoding data in CCITT stream.")); - } - } - } - - break; - } - - default: - Q_ASSERT(false); - break; - } - } - } - else - { - // Simple 1D encoding - while (codingLine[a0_index] < m_parameters.columns) - { - const uint32_t sequenceLength = getRunLength(!isCurrentPixelBlack); - addPixels(codingLine, a0_index, codingLine[a0_index] + sequenceLength, isCurrentPixelBlack, false); - isCurrentPixelBlack = !isCurrentPixelBlack; - } - } - - // Write the line to the output buffer - isCurrentPixelBlack = false; - int index = 0; - for (int i = 0; i < m_parameters.columns; ++i) - { - if (i == codingLine[index]) - { - isCurrentPixelBlack = !isCurrentPixelBlack; - ++index; - } - - writer.write(isCurrentPixelBlack ? 0 : 1); - } - writer.finishLine(); - - ++row; - - // Check if we have reached desired number of rows (and end-of-block mode - // is not set). If yes, then break the reading. - if (!m_parameters.hasEndOfBlock && row == m_parameters.rows) - { - // We have reached number of rows, stop reading the data - break; - } - - bool foundEndOfLine = false; - if (m_parameters.hasEndOfLine) - { - // End of line is required, try to scan it (until end of stream is reached). - while (!m_reader.isAtEnd()) - { - if (m_reader.look(12) == 1) - { - m_reader.read(12); - foundEndOfLine = true; - break; - } - else - { - m_reader.read(1); - } - } - } - else if (!m_parameters.hasEncodedByteAlign) - { - // Skip fill zeros and possibly find EOL - foundEndOfLine = skipFillAndEOL(); - } - - // If end of line is found, be do not perform align to bytes (end of line - // has perference against byte align) - if (m_parameters.hasEncodedByteAlign && !foundEndOfLine) - { - m_reader.alignToBytes(); - } - - if (m_reader.isAtEnd()) - { - // Have we finished reading? - break; - } - - updateIsUsing2DEncoding(); - - if (m_parameters.hasEndOfBlock) - { - if (!m_parameters.hasEndOfLine && m_parameters.hasEncodedByteAlign) - { - // We do not have look for EOL above. We check, if we get end-of-facsimile-block signal. - // It consists of two consecutive EOLs (see specification). - if (m_reader.look(24) == 0x1001) - { - // End of block found, stop reading the data - break; - } - } - else if (foundEndOfLine) - { - if (m_reader.look(12) == 1) - { - // End of block found, stop reading the data - break; - } - } - } - - std::swap(codingLine, referenceLine); - std::fill(codingLine.begin(), codingLine.end(), m_parameters.columns); - std::fill(std::next(referenceLine.begin(), a0_index + 1), referenceLine.end(), m_parameters.columns); - codingLine[0] = 0; - } - - Q_ASSERT(m_parameters.decode.size() == 2); - std::vector decode; - if (m_parameters.hasBlackIsOne) - { - decode = { m_parameters.decode[1], m_parameters.decode[0] }; - } - else - { - decode = { m_parameters.decode[0], m_parameters.decode[1] }; - } - - return PDFImageData(1, 1, m_parameters.columns, row, (m_parameters.columns + 7) / 8, m_parameters.maskingType, writer.takeByteArray(), { }, qMove(decode), { }); -} - -void PDFCCITTFaxDecoder::skipFill() -{ - // This functions skips zero bits (because codewords have at most 12 bits, - // we use 12 bit lookahead to ensure, that we do not broke data sequence). - - while (!m_reader.isAtEnd() && m_reader.look(12) == 0) - { - m_reader.read(1); - } -} - -bool PDFCCITTFaxDecoder::skipEOL() -{ - if (m_reader.look(12) == 1) - { - m_reader.read(12); - return true; - } - - return false; -} - -void PDFCCITTFaxDecoder::addPixels(std::vector& line, int& a0_index, int a1, bool isCurrentPixelBlack, bool isA1LeftOfA0Allowed) -{ - if (a1 > line[a0_index]) - { - if (a1 > m_parameters.columns) - { - throw PDFException(PDFTranslationContext::tr("Invalid index of CCITT changing element a1: a1 = %1, columns = %2.").arg(a1).arg(m_parameters.columns)); - } - - // If we are changing the color, increment a0_index. a0_index == 0 is white, a0_index == 1 is black, etc., - // sequence of white and black runs alternates. - if ((a0_index & 1) != isCurrentPixelBlack) - { - ++a0_index; - } - - line[a0_index] = a1; - } - else if (isA1LeftOfA0Allowed && a1 < line[a0_index]) - { - // We want to find first index, for which it holds: - // a1 > line[a0_index - 1], so if we set line[a0_index] = a1, - // then we get a valid increasing sequence. - while (a0_index > 0 && a1 <= line[a0_index - 1]) - { - --a0_index; - } - line[a0_index] = a1; - } -} - -uint32_t PDFCCITTFaxDecoder::getRunLength(bool white) -{ - uint32_t value = 0; - - while (true) - { - uint32_t currentValue = 0; - if (white) - { - currentValue = getWhiteCode(); - } - else - { - currentValue = getBlackCode(); - } - value += currentValue; - - if (currentValue < 64) - { - break; - } - } - - return value; -} - -uint32_t PDFCCITTFaxDecoder::getWhiteCode() -{ - return getCode(CCITT_WHITE_CODES, std::size(CCITT_WHITE_CODES)); -} - -uint32_t PDFCCITTFaxDecoder::getBlackCode() -{ - return getCode(CCITT_BLACK_CODES, std::size(CCITT_BLACK_CODES)); -} - -uint32_t PDFCCITTFaxDecoder::getCode(const PDFCCITTCode* codes, size_t codeCount) -{ - uint32_t code = 0; - uint8_t bits = 0; - - while (bits <= MAX_CODE_BIT_LENGTH) - { - code = (code << 1) + m_reader.read(1); - ++bits; - - for (size_t i = 0; i < codeCount; ++i) - { - const PDFCCITTCode& currentCode = codes[i]; - if (currentCode.bits == bits && currentCode.code == code) - { - return currentCode.length; - } - } - } - - throw PDFException(PDFTranslationContext::tr("Invalid CCITT run length code word.")); - return 0; -} - -CCITT_2D_Code_Mode PDFCCITTFaxDecoder::get2DMode() -{ - uint32_t code = 0; - uint8_t bits = 0; - - while (bits <= MAX_2D_MODE_BIT_LENGTH) - { - code = (code << 1) + m_reader.read(1); - ++bits; - - for (const PDFCCITT2DModeInfo& info : CCITT_2D_CODE_MODES) - { - if (info.bits == bits && info.code == code) - { - return info.mode; - } - } - } - - throw PDFException(PDFTranslationContext::tr("Invalid CCITT 2D mode.")); - return Invalid; -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#include "pdfccittfaxdecoder.h" +#include "pdfexception.h" + +namespace pdf +{ + +template +constexpr uint8_t operator "" _bitlength() +{ + return sizeof...(Digits); +} + +enum CCITT_2D_Code_Mode +{ + Pass, + Horizontal, + Vertical_3L, + Vertical_2L, + Vertical_1L, + Vertical_0, + Vertical_1R, + Vertical_2R, + Vertical_3R, + Invalid +}; + +struct PDFCCITT2DModeInfo +{ + CCITT_2D_Code_Mode mode; + uint16_t code; + uint8_t bits; +}; + +static constexpr uint8_t MAX_2D_MODE_BIT_LENGTH = 7; + +static constexpr PDFCCITT2DModeInfo CCITT_2D_CODE_MODES[] = +{ + { Pass, 0b0001, 0001_bitlength }, + { Horizontal, 0b001, 001_bitlength }, + { Vertical_3L, 0b0000010, 0000010_bitlength }, + { Vertical_2L, 0b000010, 000010_bitlength }, + { Vertical_1L, 0b010, 010_bitlength }, + { Vertical_0, 0b1, 1_bitlength }, + { Vertical_1R, 0b011, 011_bitlength }, + { Vertical_2R, 0b000011, 000011_bitlength }, + { Vertical_3R, 0b0000011, 0000011_bitlength } +}; + +struct PDFCCITTCode +{ + uint16_t length; + uint16_t code; + uint8_t bits; +}; + +static constexpr uint8_t MAX_CODE_BIT_LENGTH = 12; + +static constexpr PDFCCITTCode CCITT_WHITE_CODES[] = { + +// Terminating white codes + + { 0, 0b00110101, 00110101_bitlength }, + { 1, 0b000111, 000111_bitlength }, + { 2, 0b0111, 0111_bitlength }, + { 3, 0b1000, 1000_bitlength }, + { 4, 0b1011, 1011_bitlength }, + { 5, 0b1100, 1100_bitlength }, + { 6, 0b1110, 1110_bitlength }, + { 7, 0b1111, 1111_bitlength }, + { 8, 0b10011, 10011_bitlength }, + { 9, 0b10100, 10100_bitlength }, + { 10, 0b00111, 00111_bitlength }, + { 11, 0b01000, 01000_bitlength }, + { 12, 0b001000, 001000_bitlength }, + { 13, 0b000011, 000011_bitlength }, + { 14, 0b110100, 110100_bitlength }, + { 15, 0b110101, 110101_bitlength }, + { 16, 0b101010, 101010_bitlength }, + { 17, 0b101011, 101011_bitlength }, + { 18, 0b0100111, 0100111_bitlength }, + { 19, 0b0001100, 0001100_bitlength }, + { 20, 0b0001000, 0001000_bitlength }, + { 21, 0b0010111, 0010111_bitlength }, + { 22, 0b0000011, 0000011_bitlength }, + { 23, 0b0000100, 0000100_bitlength }, + { 24, 0b0101000, 0101000_bitlength }, + { 25, 0b0101011, 0101011_bitlength }, + { 26, 0b0010011, 0010011_bitlength }, + { 27, 0b0100100, 0100100_bitlength }, + { 28, 0b0011000, 0011000_bitlength }, + { 29, 0b00000010, 00000010_bitlength }, + { 30, 0b00000011, 00000011_bitlength }, + { 31, 0b00011010, 00011010_bitlength }, + { 32, 0b00011011, 00011011_bitlength }, + { 33, 0b00010010, 00010010_bitlength }, + { 34, 0b00010011, 00010011_bitlength }, + { 35, 0b00010100, 00010100_bitlength }, + { 36, 0b00010101, 00010101_bitlength }, + { 37, 0b00010110, 00010110_bitlength }, + { 38, 0b00010111, 00010111_bitlength }, + { 39, 0b00101000, 00101000_bitlength }, + { 40, 0b00101001, 00101001_bitlength }, + { 41, 0b00101010, 00101010_bitlength }, + { 42, 0b00101011, 00101011_bitlength }, + { 43, 0b00101100, 00101100_bitlength }, + { 44, 0b00101101, 00101101_bitlength }, + { 45, 0b00000100, 00000100_bitlength }, + { 46, 0b00000101, 00000101_bitlength }, + { 47, 0b00001010, 00001010_bitlength }, + { 48, 0b00001011, 00001011_bitlength }, + { 49, 0b01010010, 01010010_bitlength }, + { 50, 0b01010011, 01010011_bitlength }, + { 51, 0b01010100, 01010100_bitlength }, + { 52, 0b01010101, 01010101_bitlength }, + { 53, 0b00100100, 00100100_bitlength }, + { 54, 0b00100101, 00100101_bitlength }, + { 55, 0b01011000, 01011000_bitlength }, + { 56, 0b01011001, 01011001_bitlength }, + { 57, 0b01011010, 01011010_bitlength }, + { 58, 0b01011011, 01011011_bitlength }, + { 59, 0b01001010, 01001010_bitlength }, + { 60, 0b01001011, 01001011_bitlength }, + { 61, 0b00110010, 00110010_bitlength }, + { 62, 0b00110011, 00110011_bitlength }, + { 63, 0b00110100, 00110100_bitlength }, + +// Make up white codes - doesn't terminate + { 64, 0b11011, 11011_bitlength }, + { 128, 0b10010, 10010_bitlength }, + { 192, 0b010111, 010111_bitlength }, + { 256, 0b0110111, 0110111_bitlength }, + { 320, 0b00110110, 00110110_bitlength }, + { 384, 0b00110111, 00110111_bitlength }, + { 448, 0b01100100, 01100100_bitlength }, + { 512, 0b01100101, 01100101_bitlength }, + { 576, 0b01101000, 01101000_bitlength }, + { 640, 0b01100111, 01100111_bitlength }, + { 704, 0b011001100, 011001100_bitlength }, + { 768, 0b011001101, 011001101_bitlength }, + { 832, 0b011010010, 011010010_bitlength }, + { 896, 0b011010011, 011010011_bitlength }, + { 960, 0b011010100, 011010100_bitlength }, + { 1024, 0b011010101, 011010101_bitlength }, + { 1088, 0b011010110, 011010110_bitlength }, + { 1152, 0b011010111, 011010111_bitlength }, + { 1216, 0b011011000, 011011000_bitlength }, + { 1280, 0b011011001, 011011001_bitlength }, + { 1344, 0b011011010, 011011010_bitlength }, + { 1408, 0b011011011, 011011011_bitlength }, + { 1472, 0b010011000, 010011000_bitlength }, + { 1536, 0b010011001, 010011001_bitlength }, + { 1600, 0b010011010, 010011010_bitlength }, + { 1664, 0b011000, 011000_bitlength }, + { 1728, 0b010011011, 010011011_bitlength }, + { 1792, 0b00000001000, 00000001000_bitlength }, + { 1856, 0b00000001100, 00000001100_bitlength }, + { 1920, 0b00000001101, 00000001101_bitlength }, + { 1984, 0b000000010010, 000000010010_bitlength }, + { 2048, 0b000000010011, 000000010011_bitlength }, + { 2112, 0b000000010100, 000000010100_bitlength }, + { 2176, 0b000000010101, 000000010101_bitlength }, + { 2240, 0b000000010110, 000000010110_bitlength }, + { 2304, 0b000000010111, 000000010111_bitlength }, + { 2368, 0b000000011100, 000000011100_bitlength }, + { 2432, 0b000000011101, 000000011101_bitlength }, + { 2496, 0b000000011110, 000000011110_bitlength }, + { 2560, 0b000000011111, 000000011111_bitlength } +}; + +static constexpr PDFCCITTCode CCITT_BLACK_CODES[] = { +// Terminating black codes + + { 0, 0b0000110111, 0000110111_bitlength }, + { 1, 0b010, 010_bitlength }, + { 2, 0b11, 11_bitlength }, + { 3, 0b10, 10_bitlength }, + { 4, 0b011, 011_bitlength }, + { 5, 0b0011, 0011_bitlength }, + { 6, 0b0010, 0010_bitlength }, + { 7, 0b00011, 00011_bitlength }, + { 8, 0b000101, 000101_bitlength }, + { 9, 0b000100, 000100_bitlength }, + { 10, 0b0000100, 0000100_bitlength }, + { 11, 0b0000101, 0000101_bitlength }, + { 12, 0b0000111, 0000111_bitlength }, + { 13, 0b00000100, 00000100_bitlength }, + { 14, 0b00000111, 00000111_bitlength }, + { 15, 0b000011000, 000011000_bitlength }, + { 16, 0b0000010111, 0000010111_bitlength }, + { 17, 0b0000011000, 0000011000_bitlength }, + { 18, 0b0000001000, 0000001000_bitlength }, + { 19, 0b00001100111, 00001100111_bitlength }, + { 20, 0b00001101000, 00001101000_bitlength }, + { 21, 0b00001101100, 00001101100_bitlength }, + { 22, 0b00000110111, 00000110111_bitlength }, + { 23, 0b00000101000, 00000101000_bitlength }, + { 24, 0b00000010111, 00000010111_bitlength }, + { 25, 0b00000011000, 00000011000_bitlength }, + { 26, 0b000011001010, 000011001010_bitlength }, + { 27, 0b000011001011, 000011001011_bitlength }, + { 28, 0b000011001100, 000011001100_bitlength }, + { 29, 0b000011001101, 000011001101_bitlength }, + { 30, 0b000001101000, 000001101000_bitlength }, + { 31, 0b000001101001, 000001101001_bitlength }, + { 32, 0b000001101010, 000001101010_bitlength }, + { 33, 0b000001101011, 000001101011_bitlength }, + { 34, 0b000011010010, 000011010010_bitlength }, + { 35, 0b000011010011, 000011010011_bitlength }, + { 36, 0b000011010100, 000011010100_bitlength }, + { 37, 0b000011010101, 000011010101_bitlength }, + { 38, 0b000011010110, 000011010110_bitlength }, + { 39, 0b000011010111, 000011010111_bitlength }, + { 40, 0b000001101100, 000001101100_bitlength }, + { 41, 0b000001101101, 000001101101_bitlength }, + { 42, 0b000011011010, 000011011010_bitlength }, + { 43, 0b000011011011, 000011011011_bitlength }, + { 44, 0b000001010100, 000001010100_bitlength }, + { 45, 0b000001010101, 000001010101_bitlength }, + { 46, 0b000001010110, 000001010110_bitlength }, + { 47, 0b000001010111, 000001010111_bitlength }, + { 48, 0b000001100100, 000001100100_bitlength }, + { 49, 0b000001100101, 000001100101_bitlength }, + { 50, 0b000001010010, 000001010010_bitlength }, + { 51, 0b000001010011, 000001010011_bitlength }, + { 52, 0b000000100100, 000000100100_bitlength }, + { 53, 0b000000110111, 000000110111_bitlength }, + { 54, 0b000000111000, 000000111000_bitlength }, + { 55, 0b000000100111, 000000100111_bitlength }, + { 56, 0b000000101000, 000000101000_bitlength }, + { 57, 0b000001011000, 000001011000_bitlength }, + { 58, 0b000001011001, 000001011001_bitlength }, + { 59, 0b000000101011, 000000101011_bitlength }, + { 60, 0b000000101100, 000000101100_bitlength }, + { 61, 0b000001011010, 000001011010_bitlength }, + { 62, 0b000001100110, 000001100110_bitlength }, + { 63, 0b000001100111, 000001100111_bitlength }, + +// Make up white codes - doesn't terminate + + { 64, 0b0000001111, 0000001111_bitlength }, + { 128, 0b000011001000, 000011001000_bitlength }, + { 192, 0b000011001001, 000011001001_bitlength }, + { 256, 0b000001011011, 000001011011_bitlength }, + { 320, 0b000000110011, 000000110011_bitlength }, + { 384, 0b000000110100, 000000110100_bitlength }, + { 448, 0b000000110101, 000000110101_bitlength }, + { 512, 0b0000001101100, 0000001101100_bitlength }, + { 576, 0b0000001101101, 0000001101101_bitlength }, + { 640, 0b0000001001010, 0000001001010_bitlength }, + { 704, 0b0000001001011, 0000001001011_bitlength }, + { 768, 0b0000001001100, 0000001001100_bitlength }, + { 832, 0b0000001001101, 0000001001101_bitlength }, + { 896, 0b0000001110010, 0000001110010_bitlength }, + { 960, 0b0000001110011, 0000001110011_bitlength }, + { 1024, 0b0000001110100, 0000001110100_bitlength }, + { 1088, 0b0000001110101, 0000001110101_bitlength }, + { 1152, 0b0000001110110, 0000001110110_bitlength }, + { 1216, 0b0000001110111, 0000001110111_bitlength }, + { 1280, 0b0000001010010, 0000001010010_bitlength }, + { 1344, 0b0000001010011, 0000001010011_bitlength }, + { 1408, 0b0000001010100, 0000001010100_bitlength }, + { 1472, 0b0000001010101, 0000001010101_bitlength }, + { 1536, 0b0000001011010, 0000001011010_bitlength }, + { 1600, 0b0000001011011, 0000001011011_bitlength }, + { 1664, 0b0000001100100, 0000001100100_bitlength }, + { 1728, 0b0000001100101, 0000001100101_bitlength }, + { 1792, 0b00000001000, 00000001000_bitlength }, + { 1856, 0b00000001100, 00000001100_bitlength }, + { 1920, 0b00000001101, 00000001101_bitlength }, + { 1984, 0b000000010010, 000000010010_bitlength }, + { 2048, 0b000000010011, 000000010011_bitlength }, + { 2112, 0b000000010100, 000000010100_bitlength }, + { 2176, 0b000000010101, 000000010101_bitlength }, + { 2240, 0b000000010110, 000000010110_bitlength }, + { 2304, 0b000000010111, 000000010111_bitlength }, + { 2368, 0b000000011100, 000000011100_bitlength }, + { 2432, 0b000000011101, 000000011101_bitlength }, + { 2496, 0b000000011110, 000000011110_bitlength }, + { 2560, 0b000000011111, 000000011111_bitlength } +}; + +PDFCCITTFaxDecoder::PDFCCITTFaxDecoder(const QByteArray* stream, const PDFCCITTFaxDecoderParameters& parameters) : + m_reader(stream, 1), + m_parameters(parameters) +{ + +} + +PDFImageData PDFCCITTFaxDecoder::decode() +{ + PDFBitWriter writer(1); + std::vector codingLine; + std::vector referenceLine; + + int row = 0; + const size_t lineSize = m_parameters.columns + 2; + codingLine.resize(lineSize, m_parameters.columns); + referenceLine.resize(lineSize, m_parameters.columns); + bool isUsing2DEncoding = m_parameters.K < 0; + bool isEndOfLineOccured = m_parameters.hasEndOfLine; + codingLine[0] = 0; + + auto updateIsUsing2DEncoding = [this, &isUsing2DEncoding]() + { + if (m_parameters.K > 0) + { + // Mixed encoding + isUsing2DEncoding = !m_reader.read(1); + } + }; + + isEndOfLineOccured = skipFillAndEOL() || isEndOfLineOccured; + updateIsUsing2DEncoding(); + + while (!m_reader.isAtEnd()) + { + int a0_index = 0; + bool isCurrentPixelBlack = false; + + if (isUsing2DEncoding) + { + int b1_index = 0; + + // 2D encoding + while (codingLine[a0_index] < m_parameters.columns) + { + CCITT_2D_Code_Mode mode = get2DMode(); + switch (mode) + { + case Pass: + { + // In this mode, we set a0 to the b2 (from reference line). In pass mode, + // we do not change pixel color. Why we are adding 2 to the b1_index? + // We want to skip both b1, b2, because they will be left of new a0. + const size_t b2_index = b1_index + 1; + if (b2_index < referenceLine.size()) + { + addPixels(codingLine, a0_index, referenceLine[b2_index], isCurrentPixelBlack, false); + + if (referenceLine[b2_index] < m_parameters.columns) + { + b1_index += 2; + + if (b1_index >= referenceLine.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid pass encoding data in CCITT stream.")); + } + } + } + else + { + throw PDFException(PDFTranslationContext::tr("CCITT b2 index out of range.")); + } + + break; + } + + case Horizontal: + { + // We scan two sequence length. + const int a0a1 = getRunLength(!isCurrentPixelBlack); + const int a1a2 = getRunLength(isCurrentPixelBlack); + + addPixels(codingLine, a0_index, codingLine[a0_index] + a0a1, isCurrentPixelBlack, false); + addPixels(codingLine, a0_index, codingLine[a0_index] + a1a2, !isCurrentPixelBlack, false); + + while (referenceLine[b1_index] <= codingLine[a0_index] && referenceLine[b1_index] < m_parameters.columns) + { + // We do not want to change the color (b1 should have opposite color of a0, + // should be first changing element of reference line right of a0). + b1_index += 2; + + if (b1_index >= referenceLine.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid horizontal encoding data in CCITT stream.")); + } + } + + break; + } + + case Vertical_3L: + case Vertical_2L: + case Vertical_1L: + case Vertical_0: + case Vertical_1R: + case Vertical_2R: + case Vertical_3R: + { + const int32_t a1 = static_cast(referenceLine[b1_index]) + mode - static_cast(Vertical_0); + + if (a1 < 0 || a1 > m_parameters.columns) + { + throw PDFException(PDFTranslationContext::tr("Invalid vertical encoding data in CCITT stream.")); + } + + const bool isNegativeOffset = mode < Vertical_0; + addPixels(codingLine, a0_index, static_cast(a1), isCurrentPixelBlack, isNegativeOffset); + isCurrentPixelBlack = !isCurrentPixelBlack; + + if (codingLine[a0_index] < m_parameters.columns) + { + // We must upgrade b1 index in such a way that it is first index + // of opposite color, than a0 index. If we are using negative offsets, then + // current position can move backward, and so we must look for first b1 index, + // which is of opposite color, than a0. So we decrease index by 1. But what to do, + // if we have b1 index equal to zero? In this case, we add -1 + 2 = 1 index, so we do it in + // same way, as positive/zero shift. + b1_index += (isNegativeOffset && b1_index > 0) ? -1 : 1; + + // Why we have this check, if same check is in while cycle? Because if we are adding + // to the b1_index, we can go outside of reference line range. + if (b1_index >= referenceLine.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid vertical encoding data in CCITT stream.")); + } + + while (referenceLine[b1_index] <= codingLine[a0_index] && referenceLine[b1_index] < m_parameters.columns) + { + // We do not want to change the color (b1 should have opposite color of a0, + // should be first changing element of reference line right of a0). + b1_index += 2; + + if (b1_index >= referenceLine.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid vertical encoding data in CCITT stream.")); + } + } + } + + break; + } + + default: + Q_ASSERT(false); + break; + } + } + } + else + { + // Simple 1D encoding + while (codingLine[a0_index] < m_parameters.columns) + { + const uint32_t sequenceLength = getRunLength(!isCurrentPixelBlack); + addPixels(codingLine, a0_index, codingLine[a0_index] + sequenceLength, isCurrentPixelBlack, false); + isCurrentPixelBlack = !isCurrentPixelBlack; + } + } + + // Write the line to the output buffer + isCurrentPixelBlack = false; + int index = 0; + for (int i = 0; i < m_parameters.columns; ++i) + { + if (i == codingLine[index]) + { + isCurrentPixelBlack = !isCurrentPixelBlack; + ++index; + } + + writer.write(isCurrentPixelBlack ? 0 : 1); + } + writer.finishLine(); + + ++row; + + // Check if we have reached desired number of rows (and end-of-block mode + // is not set). If yes, then break the reading. + if (!m_parameters.hasEndOfBlock && row == m_parameters.rows) + { + // We have reached number of rows, stop reading the data + break; + } + + bool foundEndOfLine = false; + if (m_parameters.hasEndOfLine) + { + // End of line is required, try to scan it (until end of stream is reached). + while (!m_reader.isAtEnd()) + { + if (m_reader.look(12) == 1) + { + m_reader.read(12); + foundEndOfLine = true; + break; + } + else + { + m_reader.read(1); + } + } + } + else if (!m_parameters.hasEncodedByteAlign) + { + // Skip fill zeros and possibly find EOL + foundEndOfLine = skipFillAndEOL(); + } + + // If end of line is found, be do not perform align to bytes (end of line + // has perference against byte align) + if (m_parameters.hasEncodedByteAlign && !foundEndOfLine) + { + m_reader.alignToBytes(); + } + + if (m_reader.isAtEnd()) + { + // Have we finished reading? + break; + } + + updateIsUsing2DEncoding(); + + if (m_parameters.hasEndOfBlock) + { + if (!m_parameters.hasEndOfLine && m_parameters.hasEncodedByteAlign) + { + // We do not have look for EOL above. We check, if we get end-of-facsimile-block signal. + // It consists of two consecutive EOLs (see specification). + if (m_reader.look(24) == 0x1001) + { + // End of block found, stop reading the data + break; + } + } + else if (foundEndOfLine) + { + if (m_reader.look(12) == 1) + { + // End of block found, stop reading the data + break; + } + } + } + + std::swap(codingLine, referenceLine); + std::fill(codingLine.begin(), codingLine.end(), m_parameters.columns); + std::fill(std::next(referenceLine.begin(), a0_index + 1), referenceLine.end(), m_parameters.columns); + codingLine[0] = 0; + } + + Q_ASSERT(m_parameters.decode.size() == 2); + std::vector decode; + if (m_parameters.hasBlackIsOne) + { + decode = { m_parameters.decode[1], m_parameters.decode[0] }; + } + else + { + decode = { m_parameters.decode[0], m_parameters.decode[1] }; + } + + return PDFImageData(1, 1, m_parameters.columns, row, (m_parameters.columns + 7) / 8, m_parameters.maskingType, writer.takeByteArray(), { }, qMove(decode), { }); +} + +void PDFCCITTFaxDecoder::skipFill() +{ + // This functions skips zero bits (because codewords have at most 12 bits, + // we use 12 bit lookahead to ensure, that we do not broke data sequence). + + while (!m_reader.isAtEnd() && m_reader.look(12) == 0) + { + m_reader.read(1); + } +} + +bool PDFCCITTFaxDecoder::skipEOL() +{ + if (m_reader.look(12) == 1) + { + m_reader.read(12); + return true; + } + + return false; +} + +void PDFCCITTFaxDecoder::addPixels(std::vector& line, int& a0_index, int a1, bool isCurrentPixelBlack, bool isA1LeftOfA0Allowed) +{ + if (a1 > line[a0_index]) + { + if (a1 > m_parameters.columns) + { + throw PDFException(PDFTranslationContext::tr("Invalid index of CCITT changing element a1: a1 = %1, columns = %2.").arg(a1).arg(m_parameters.columns)); + } + + // If we are changing the color, increment a0_index. a0_index == 0 is white, a0_index == 1 is black, etc., + // sequence of white and black runs alternates. + if ((a0_index & 1) != isCurrentPixelBlack) + { + ++a0_index; + } + + line[a0_index] = a1; + } + else if (isA1LeftOfA0Allowed && a1 < line[a0_index]) + { + // We want to find first index, for which it holds: + // a1 > line[a0_index - 1], so if we set line[a0_index] = a1, + // then we get a valid increasing sequence. + while (a0_index > 0 && a1 <= line[a0_index - 1]) + { + --a0_index; + } + line[a0_index] = a1; + } +} + +uint32_t PDFCCITTFaxDecoder::getRunLength(bool white) +{ + uint32_t value = 0; + + while (true) + { + uint32_t currentValue = 0; + if (white) + { + currentValue = getWhiteCode(); + } + else + { + currentValue = getBlackCode(); + } + value += currentValue; + + if (currentValue < 64) + { + break; + } + } + + return value; +} + +uint32_t PDFCCITTFaxDecoder::getWhiteCode() +{ + return getCode(CCITT_WHITE_CODES, std::size(CCITT_WHITE_CODES)); +} + +uint32_t PDFCCITTFaxDecoder::getBlackCode() +{ + return getCode(CCITT_BLACK_CODES, std::size(CCITT_BLACK_CODES)); +} + +uint32_t PDFCCITTFaxDecoder::getCode(const PDFCCITTCode* codes, size_t codeCount) +{ + uint32_t code = 0; + uint8_t bits = 0; + + while (bits <= MAX_CODE_BIT_LENGTH) + { + code = (code << 1) + m_reader.read(1); + ++bits; + + for (size_t i = 0; i < codeCount; ++i) + { + const PDFCCITTCode& currentCode = codes[i]; + if (currentCode.bits == bits && currentCode.code == code) + { + return currentCode.length; + } + } + } + + throw PDFException(PDFTranslationContext::tr("Invalid CCITT run length code word.")); + return 0; +} + +CCITT_2D_Code_Mode PDFCCITTFaxDecoder::get2DMode() +{ + uint32_t code = 0; + uint8_t bits = 0; + + while (bits <= MAX_2D_MODE_BIT_LENGTH) + { + code = (code << 1) + m_reader.read(1); + ++bits; + + for (const PDFCCITT2DModeInfo& info : CCITT_2D_CODE_MODES) + { + if (info.bits == bits && info.code == code) + { + return info.mode; + } + } + } + + throw PDFException(PDFTranslationContext::tr("Invalid CCITT 2D mode.")); + return Invalid; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfccittfaxdecoder.h b/Pdf4QtLib/sources/pdfccittfaxdecoder.h index e7108ae..d36361c 100644 --- a/Pdf4QtLib/sources/pdfccittfaxdecoder.h +++ b/Pdf4QtLib/sources/pdfccittfaxdecoder.h @@ -1,121 +1,121 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFCCITTFAXDECODER_H -#define PDFCCITTFAXDECODER_H - -#include "pdfutils.h" -#include "pdfimage.h" - -namespace pdf -{ - -struct PDFCCITTCode; - -struct PDFCCITTFaxDecoderParameters -{ - /// Type of encoding. Has this meaning: - /// K < 0 - pure two dimensional encoding (Group 4) - /// K = 0 - pure one dimensional encoding - /// K > 0 - mixed encoding; one dimensional encoded line can be followed by at most K - 1 two dimensional encoded lines - PDFInteger K = 0; - - /// Pixel width of the image. Default value is 1728. - PDFInteger columns = 1728; - - /// Pixel height of the image. This value can be zero or be absent, in this case, - /// end of block pattern must be present and end the stream. - PDFInteger rows = 0; - - /// This parameter is ignored in this library. If positive, and \p hasEndOfLine is true, - /// and K is nonnegative, then if error occurs, end-of-line pattern is searched and - /// data are copied from previous line, or are set to white, if previous line is also damaged. - PDFInteger damagedRowsBeforeError = 0; - - /// Flag indicating, that end of line patterns are required in the encoded data. - /// Stream filter must always accept end of line patterns, but require them only, - /// if this flag is set to true. - bool hasEndOfLine = false; - - /// Flag indicating that lines are byte aligned, i.e. 0 bits are inserted before each line - /// to achieve byte alignment. - bool hasEncodedByteAlign = false; - - /// Flag indicating, that filter expects the data be terminated by end of block bit pattern. - /// In this case, \p rows parameter is ignored. Otherwise, rows parameter is used, or image - /// is terminated by end of data stream, whichever occurs first. The end of block is marked - /// as end-of-facsimile block (EOFB), or return to control (RTC), according the K parameter. - bool hasEndOfBlock = true; - - /// If this flag is true, then 1 means black pixel, 0 white pixel. Otherwise, if false, - /// then 0 means black pixel and 1 white pixel. - bool hasBlackIsOne = false; - - /// Decode - std::vector decode; - - /// Masking type - PDFImageData::MaskingType maskingType = PDFImageData::MaskingType::None; -}; - -enum CCITT_2D_Code_Mode; - -class PDFCCITTFaxDecoder -{ -public: - explicit PDFCCITTFaxDecoder(const QByteArray* stream, const PDFCCITTFaxDecoderParameters& parameters); - - PDFImageData decode(); - - const PDFBitReader* getReader() const { return &m_reader; } - -private: - /// Skip zero bits at the start - void skipFill(); - - /// Skip end-of-line, if occured. Returns true, if EOL was skipped. - bool skipEOL(); - - /// Skip fill bits and then try to skip EOL. If EOL is found, then - /// true is returned, otherwise false is returned. - bool skipFillAndEOL() { skipFill(); return skipEOL(); } - - /// Add pixels to the line. - /// \param line Line with changing element indices - /// \param a0_index Reference changing element index (index to the \p line array) - /// \param a1 Current changing element index (column index, not index to the \p line array) - /// \param isCurrentPixelBlack Are pixels black? - /// \param isA1LeftOfA0Allowed Allow a1 to be left of a0 (not a0_index, but line[a0_index], which is a0) - void addPixels(std::vector& line, int& a0_index, int a1, bool isCurrentPixelBlack, bool isA1LeftOfA0Allowed); - - /// Get 2D mode from the stream - CCITT_2D_Code_Mode get2DMode(); - - uint32_t getRunLength(bool white); - - uint32_t getWhiteCode(); - uint32_t getBlackCode(); - - uint32_t getCode(const PDFCCITTCode* codes, size_t codeCount); - - PDFBitReader m_reader; - PDFCCITTFaxDecoderParameters m_parameters; -}; - -} // namespace pdf - -#endif // PDFCCITTFAXDECODER_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFCCITTFAXDECODER_H +#define PDFCCITTFAXDECODER_H + +#include "pdfutils.h" +#include "pdfimage.h" + +namespace pdf +{ + +struct PDFCCITTCode; + +struct PDFCCITTFaxDecoderParameters +{ + /// Type of encoding. Has this meaning: + /// K < 0 - pure two dimensional encoding (Group 4) + /// K = 0 - pure one dimensional encoding + /// K > 0 - mixed encoding; one dimensional encoded line can be followed by at most K - 1 two dimensional encoded lines + PDFInteger K = 0; + + /// Pixel width of the image. Default value is 1728. + PDFInteger columns = 1728; + + /// Pixel height of the image. This value can be zero or be absent, in this case, + /// end of block pattern must be present and end the stream. + PDFInteger rows = 0; + + /// This parameter is ignored in this library. If positive, and \p hasEndOfLine is true, + /// and K is nonnegative, then if error occurs, end-of-line pattern is searched and + /// data are copied from previous line, or are set to white, if previous line is also damaged. + PDFInteger damagedRowsBeforeError = 0; + + /// Flag indicating, that end of line patterns are required in the encoded data. + /// Stream filter must always accept end of line patterns, but require them only, + /// if this flag is set to true. + bool hasEndOfLine = false; + + /// Flag indicating that lines are byte aligned, i.e. 0 bits are inserted before each line + /// to achieve byte alignment. + bool hasEncodedByteAlign = false; + + /// Flag indicating, that filter expects the data be terminated by end of block bit pattern. + /// In this case, \p rows parameter is ignored. Otherwise, rows parameter is used, or image + /// is terminated by end of data stream, whichever occurs first. The end of block is marked + /// as end-of-facsimile block (EOFB), or return to control (RTC), according the K parameter. + bool hasEndOfBlock = true; + + /// If this flag is true, then 1 means black pixel, 0 white pixel. Otherwise, if false, + /// then 0 means black pixel and 1 white pixel. + bool hasBlackIsOne = false; + + /// Decode + std::vector decode; + + /// Masking type + PDFImageData::MaskingType maskingType = PDFImageData::MaskingType::None; +}; + +enum CCITT_2D_Code_Mode; + +class PDFCCITTFaxDecoder +{ +public: + explicit PDFCCITTFaxDecoder(const QByteArray* stream, const PDFCCITTFaxDecoderParameters& parameters); + + PDFImageData decode(); + + const PDFBitReader* getReader() const { return &m_reader; } + +private: + /// Skip zero bits at the start + void skipFill(); + + /// Skip end-of-line, if occured. Returns true, if EOL was skipped. + bool skipEOL(); + + /// Skip fill bits and then try to skip EOL. If EOL is found, then + /// true is returned, otherwise false is returned. + bool skipFillAndEOL() { skipFill(); return skipEOL(); } + + /// Add pixels to the line. + /// \param line Line with changing element indices + /// \param a0_index Reference changing element index (index to the \p line array) + /// \param a1 Current changing element index (column index, not index to the \p line array) + /// \param isCurrentPixelBlack Are pixels black? + /// \param isA1LeftOfA0Allowed Allow a1 to be left of a0 (not a0_index, but line[a0_index], which is a0) + void addPixels(std::vector& line, int& a0_index, int a1, bool isCurrentPixelBlack, bool isA1LeftOfA0Allowed); + + /// Get 2D mode from the stream + CCITT_2D_Code_Mode get2DMode(); + + uint32_t getRunLength(bool white); + + uint32_t getWhiteCode(); + uint32_t getBlackCode(); + + uint32_t getCode(const PDFCCITTCode* codes, size_t codeCount); + + PDFBitReader m_reader; + PDFCCITTFaxDecoderParameters m_parameters; +}; + +} // namespace pdf + +#endif // PDFCCITTFAXDECODER_H diff --git a/Pdf4QtLib/sources/pdfcms.cpp b/Pdf4QtLib/sources/pdfcms.cpp index 6d39909..91b4ddd 100644 --- a/Pdf4QtLib/sources/pdfcms.cpp +++ b/Pdf4QtLib/sources/pdfcms.cpp @@ -1,1947 +1,1947 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfcms.h" -#include "pdfdocument.h" -#include "pdfexecutionpolicy.h" - -#include -#include - -#pragma warning(push) -#pragma warning(disable:5033) -#define CMS_NO_REGISTER_KEYWORD -#include -#include -#pragma warning(pop) - - -#ifdef Q_OS_WIN -#define NOMINMAX -#include -#include -#pragma comment(lib, "Mscms") -#endif - -#include - -namespace pdf -{ - -class PDFLittleCMS : public PDFCMS -{ -public: - explicit PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings); - virtual ~PDFLittleCMS() override; - - virtual bool isCompatible(const PDFCMSSettings& settings) const override; - virtual QColor getPaperColor() const override; - virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromDeviceGray(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; - virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override; - -private: - void init(); - - static int installCmsPlugins(); - - static cmsBool optimizePipeline(cmsPipeline** Lut, - cmsUInt32Number Intent, - cmsUInt32Number* InputFormat, - cmsUInt32Number* OutputFormat, - cmsUInt32Number* dwFlags); - - enum Profile - { - Output, - Gray, - RGB, - CMYK, - XYZ, - SoftProofing, - ProfileCount - }; - - /// Returns true, if we are doing soft-proofing - bool isSoftProofing() const; - - /// Creates a profile using provided id and a list of profile descriptors. - /// If profile can't be created, then null handle is returned. If \p preferOutputProfile - /// is set to true, and given profile is not output profile, then first output profile - /// is being selected. - /// \param id Id of color profile - /// \param profileDescriptors Profile descriptor list - /// \param preferOutputProfile - cmsHPROFILE createProfile(const QString& id, const PDFColorProfileIdentifiers& profileDescriptors, bool preferOutputProfile) const; - - /// Gets transform from cache. If transform doesn't exist, then it is created. - /// \param profile Color profile - /// \param intent Rendering intent - /// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used - cmsHTRANSFORM getTransform(Profile profile, RenderingIntent intent, bool isRGB888Buffer) const; - - /// Gets transform for ICC profile from cache. If transform doesn't exist, then it is created. - /// \param iccData Data of icc profile - /// \param iccID Icc profile id - /// \param renderingIntent Rendering intent - /// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used - cmsHTRANSFORM getTransformFromICCProfile(const QByteArray& iccData, const QByteArray& iccID, RenderingIntent renderingIntent, bool isRGB888Buffer) const; - - /// Returns transformation flags according to the current settings - cmsUInt32Number getTransformationFlags() const; - - /// Calculates effective rendering intent. If rendering intent is auto, - /// then \p intent is used, otherwise intent is overriden. - RenderingIntent getEffectiveRenderingIntent(RenderingIntent intent) const; - - /// Gets transform from cache key. - /// \param profile Color profile - /// \param intent Rendering intent - /// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used - static constexpr int getCacheKey(Profile profile, RenderingIntent intent, bool isRGB888Buffer) { return ((int(intent) * ProfileCount + profile) << 1) + (isRGB888Buffer ? 1 : 0); } - - /// Returns little CMS rendering intent - /// \param intent Rendering intent - static cmsUInt32Number getLittleCMSRenderingIntent(RenderingIntent intent); - - /// Returns little CMS data format for profile - /// \param profile Color profile handle - static cmsUInt32Number getProfileDataFormat(cmsHPROFILE profile); - - /// Returns color from output color. Clamps invalid rgb output values to range [0.0, 1.0]. - /// \param color01 Rgb color (range 0-1 is assumed). - static QColor getColorFromOutputColor(std::array color01); - - /// Returns transform key for transformation between various color spaces - static QByteArray getTransformColorSpaceKey(const ColorSpaceTransformParams& params); - - cmsHTRANSFORM getTransformBetweenColorSpaces(const ColorSpaceTransformParams& params) const; - - const PDFCMSManager* m_manager; - PDFCMSSettings m_settings; - QColor m_paperColor; - std::array m_profiles; - - mutable QReadWriteLock m_transformationCacheLock; - mutable std::unordered_map m_transformationCache; - - mutable QReadWriteLock m_customIccProfileCacheLock; - mutable std::map, cmsHTRANSFORM> m_customIccProfileCache; - - mutable QReadWriteLock m_transformColorSpaceCacheLock; - mutable std::map m_transformColorSpaceCache; -}; - -bool PDFLittleCMS::fillRGBBufferFromDeviceGray(const std::vector& colors, - RenderingIntent intent, - unsigned char* outputBuffer, - PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(Gray, getEffectiveRenderingIntent(intent), true); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed.")); - return false; - } - - if (cmsGetTransformInputFormat(transform) == TYPE_GRAY_FLT) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); - cmsDoTransform(transform, colors.data(), outputBuffer, static_cast(colors.size())); - return true; - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed - invalid data format.")); - } - - return false; -} - -bool PDFLittleCMS::fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(RGB, getEffectiveRenderingIntent(intent), true); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed.")); - return false; - } - - const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform); - if (inputFormat == TYPE_RGB_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); - cmsDoTransform(transform, colors.data(), outputBuffer, static_cast(colors.size()) / T_CHANNELS(inputFormat)); - return true; - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed - invalid data format.")); - } - - return false; -} - -bool PDFLittleCMS::fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(CMYK, getEffectiveRenderingIntent(intent), true); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed.")); - return false; - } - - const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform); - if (inputFormat == TYPE_CMYK_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); - std::vector fixedColors = colors; - for (size_t i = 0, count = fixedColors.size(); i < count; ++i) - { - fixedColors[i] = fixedColors[i] * 100.0f; - } - cmsDoTransform(transform, fixedColors.data(), outputBuffer, static_cast(colors.size()) / T_CHANNELS(inputFormat)); - return true; - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed - invalid data format.")); - } - - return false; -} - -bool PDFLittleCMS::fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(XYZ, getEffectiveRenderingIntent(intent), true); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed.")); - return false; - } - - const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform); - if (inputFormat == TYPE_XYZ_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); - - PDFColorComponentMatrix_3x3 adaptationMatrix = PDFChromaticAdaptationXYZ::createWhitepointChromaticAdaptation(getDefaultXYZWhitepoint(), whitePoint, m_settings.colorAdaptationXYZ); - std::vector fixedColors = colors; - - const size_t count = fixedColors.size() / 3; - for (size_t i = 0; i < count; ++i) - { - const size_t indexX = i * 3; - const size_t indexY = i * 3 + 1; - const size_t indexZ = i * 3 + 2; - const PDFColor3 sourceXYZ = { fixedColors[indexX], fixedColors[indexY], fixedColors[indexZ] }; - const PDFColor3 adaptedXYZ = adaptationMatrix * sourceXYZ; - fixedColors[indexX] = adaptedXYZ[0]; - fixedColors[indexY] = adaptedXYZ[1]; - fixedColors[indexZ] = adaptedXYZ[2]; - } - - cmsDoTransform(transform, fixedColors.data(), outputBuffer, static_cast(colors.size()) / T_CHANNELS(inputFormat)); - return true; - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed - invalid data format.")); - } - - return false; -} - -bool PDFLittleCMS::fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransformFromICCProfile(iccData, iccID, renderingIntent, true); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed.")); - return false; - } - - const cmsUInt32Number format = cmsGetTransformInputFormat(transform); - const cmsUInt32Number channels = T_CHANNELS(format); - const cmsUInt32Number colorSpace = T_COLORSPACE(format); - const bool isCMYK = colorSpace == PT_CMYK; - const float* inputColors = colors.data(); - std::vector cmykColors; - - if (isCMYK) - { - cmykColors = colors; - for (size_t i = 0; i < cmykColors.size(); ++i) - { - cmykColors[i] = cmykColors[i] * 100.0f; - } - inputColors = cmykColors.data(); - } - - if (colors.size() % channels == 0) - { - const cmsUInt32Number pixels = static_cast(colors.size()) / channels; - cmsDoTransform(transform, inputColors, outputBuffer, pixels); - return true; - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed - invalid data format.")); - } - - return false; -} - -bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& params) const -{ - PDFCMS::ColorSpaceTransformParams transformedParams = params; - transformedParams.intent = getEffectiveRenderingIntent(transformedParams.intent); - - cmsHTRANSFORM transform = getTransformBetweenColorSpaces(transformedParams); - if (!transform) - { - return false; - } - - const cmsUInt32Number inputProfileFormat = cmsGetTransformInputFormat(transform); - const cmsUInt32Number inputChannels = T_CHANNELS(inputProfileFormat); - const cmsUInt32Number inputColorSpace = T_COLORSPACE(inputProfileFormat); - const bool isInputCMYK = inputColorSpace == PT_CMYK; - const float* inputColors = params.input.begin(); - std::vector cmykColors; - - const cmsUInt32Number outputProfileFormat = cmsGetTransformOutputFormat(transform); - const cmsUInt32Number outputChannels = T_CHANNELS(outputProfileFormat); - const cmsUInt32Number outputColorSpace = T_COLORSPACE(outputProfileFormat); - const bool isOutputCMYK = outputColorSpace == PT_CMYK; - - if (isInputCMYK) - { - cmykColors = std::vector(params.input.cbegin(), params.input.cend()); - for (size_t i = 0; i < cmykColors.size(); ++i) - { - cmykColors[i] = cmykColors[i] * 100.0f; - } - inputColors = cmykColors.data(); - } - - const cmsUInt32Number inputPixelCount = static_cast(params.input.size()) / inputChannels; - const cmsUInt32Number outputPixelCount = static_cast(params.output.size()) / outputChannels; - - if (params.input.size() % inputChannels == 0 && - params.output.size() % outputChannels == 0 && - inputPixelCount == outputPixelCount) - { - PDFColorBuffer outputBuffer = params.output; - - if (inputPixelCount > params.multithreadingThreshold) - { - struct TransformInfo - { - inline TransformInfo(const float* source, float* target, cmsUInt32Number pixelCount) : - source(source), - target(target), - pixelCount(pixelCount) - { - - } - - const float* source = nullptr; - float* target = nullptr; - cmsUInt32Number pixelCount = 0; - }; - - const cmsUInt32Number blockSize = 4096; - std::vector infos; - infos.reserve(inputPixelCount / blockSize + 1); - - const float* sourcePointer = inputColors; - float* targetPointer = outputBuffer.begin(); - - cmsUInt32Number remainingPixelCount = inputPixelCount; - while (remainingPixelCount > 0) - { - const cmsUInt32Number currentPixelCount = qMin(blockSize, remainingPixelCount); - infos.emplace_back(sourcePointer, targetPointer, currentPixelCount); - sourcePointer += currentPixelCount * inputChannels; - targetPointer += currentPixelCount * outputChannels; - remainingPixelCount -= currentPixelCount; - } - - auto processEntry = [transform](const TransformInfo& info) - { - cmsDoTransform(transform, info.source, info.target, info.pixelCount); - }; - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, infos.begin(), infos.end(), processEntry); - } - else - { - // Single-threaded transform - cmsDoTransform(transform, inputColors, outputBuffer.begin(), inputPixelCount); - } - - if (isOutputCMYK) - { - const PDFColorComponent colorQuotient = 1.0f / 100.0f; - for (PDFColorComponent& color : outputBuffer) - { - color *= colorQuotient; - } - } - - return true; - } - else - { - return false; - } - - return false; -} - -PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings) : - m_manager(manager), - m_settings(settings), - m_paperColor(Qt::white), - m_profiles() -{ - static const int installed = installCmsPlugins(); - Q_UNUSED(installed); - - init(); -} - -PDFLittleCMS::~PDFLittleCMS() -{ - for (const auto& transformItem : m_transformationCache) - { - cmsHTRANSFORM transform = transformItem.second; - if (transform) - { - cmsDeleteTransform(transform); - } - } - - for (const auto& transformItem : m_customIccProfileCache) - { - cmsHTRANSFORM transform = transformItem.second; - if (transform) - { - cmsDeleteTransform(transform); - } - } - - for (const auto& transformItem : m_transformColorSpaceCache) - { - cmsHTRANSFORM transform = transformItem.second; - if (transform) - { - cmsDeleteTransform(transform); - } - } - - for (cmsHPROFILE profile : m_profiles) - { - if (profile) - { - cmsCloseProfile(profile); - } - } -} - -bool PDFLittleCMS::isCompatible(const PDFCMSSettings& settings) const -{ - return m_settings == settings; -} - -QColor PDFLittleCMS::getPaperColor() const -{ - return m_paperColor; -} - -QColor PDFLittleCMS::getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(Gray, getEffectiveRenderingIntent(intent), false); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed.")); - return QColor(); - } - - if (cmsGetTransformInputFormat(transform) == TYPE_GRAY_FLT && color.size() == 1) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); - - const float grayColor = color[0]; - std::array rgbOutputColor = { }; - cmsDoTransform(transform, &grayColor, rgbOutputColor.data(), 1); - return getColorFromOutputColor(rgbOutputColor); - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed - invalid data format.")); - } - - return QColor(); -} - -QColor PDFLittleCMS::getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(RGB, getEffectiveRenderingIntent(intent), false); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed.")); - return QColor(); - } - - if (cmsGetTransformInputFormat(transform) == TYPE_RGB_FLT && color.size() == 3) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); - - std::array rgbInputColor = { color[0], color[1], color[2] }; - std::array rgbOutputColor = { }; - cmsDoTransform(transform, rgbInputColor.data(), rgbOutputColor.data(), 1); - return getColorFromOutputColor(rgbOutputColor); - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed - invalid data format.")); - } - - return QColor(); -} - -QColor PDFLittleCMS::getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(CMYK, getEffectiveRenderingIntent(intent), false); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed.")); - return QColor(); - } - - if (cmsGetTransformInputFormat(transform) == TYPE_CMYK_FLT && color.size() == 4) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); - - std::array cmykInputColor = { color[0] * 100.0f, color[1] * 100.0f, color[2] * 100.0f, color[3] * 100.0f }; - std::array rgbOutputColor = { }; - cmsDoTransform(transform, cmykInputColor.data(), rgbOutputColor.data(), 1); - return getColorFromOutputColor(rgbOutputColor); - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed - invalid data format.")); - } - - return QColor(); -} - -QColor PDFLittleCMS::getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransform(XYZ, getEffectiveRenderingIntent(intent), false); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed.")); - return QColor(); - } - - if (cmsGetTransformInputFormat(transform) == TYPE_XYZ_FLT && color.size() == 3) - { - Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); - - const PDFColorComponentMatrix_3x3 adaptationMatrix = PDFChromaticAdaptationXYZ::createWhitepointChromaticAdaptation(getDefaultXYZWhitepoint(), whitePoint, m_settings.colorAdaptationXYZ); - const PDFColor3 xyzInputColor = adaptationMatrix * color; - std::array rgbOutputColor = { }; - cmsDoTransform(transform, xyzInputColor.data(), rgbOutputColor.data(), 1); - return getColorFromOutputColor(rgbOutputColor); - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed - invalid data format.")); - } - - return QColor(); -} - -cmsHTRANSFORM PDFLittleCMS::getTransformFromICCProfile(const QByteArray& iccData, const QByteArray& iccID, RenderingIntent renderingIntent, bool isRGB888Buffer) const -{ - RenderingIntent effectiveRenderingIntent = getEffectiveRenderingIntent(renderingIntent); - const auto key = std::make_pair(iccID + (isRGB888Buffer ? "RGB_888" : "FLT"), effectiveRenderingIntent); - QReadLocker lock(&m_customIccProfileCacheLock); - auto it = m_customIccProfileCache.find(key); - if (it == m_customIccProfileCache.cend()) - { - lock.unlock(); - QWriteLocker writeLock(&m_customIccProfileCacheLock); - - // Now, we have locked cache for writing. We must find out, - // if some other thread doesn't created the transformation already. - it = m_customIccProfileCache.find(key); - if (it == m_customIccProfileCache.cend()) - { - cmsHTRANSFORM transform = cmsHTRANSFORM(); - cmsHPROFILE profile = cmsOpenProfileFromMem(iccData.data(), iccData.size()); - if (profile) - { - if (const cmsUInt32Number inputDataFormat = getProfileDataFormat(profile)) - { - cmsUInt32Number lcmsIntent = getLittleCMSRenderingIntent(effectiveRenderingIntent); - - if (isSoftProofing()) - { - cmsHPROFILE proofingProfile = m_profiles[SoftProofing]; - RenderingIntent proofingIntent = m_settings.proofingIntent; - if (m_settings.proofingIntent == RenderingIntent::Auto) - { - proofingIntent = effectiveRenderingIntent; - } - - transform = cmsCreateProofingTransform(profile, inputDataFormat, m_profiles[Output], isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, proofingProfile, - lcmsIntent, getLittleCMSRenderingIntent(proofingIntent), getTransformationFlags()); - } - else - { - transform = cmsCreateTransform(profile, inputDataFormat, m_profiles[Output], isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, lcmsIntent, getTransformationFlags()); - } - } - cmsCloseProfile(profile); - } - - it = m_customIccProfileCache.insert(std::make_pair(key, transform)).first; - } - - return it->second; - } - else - { - return it->second; - } - - return cmsHTRANSFORM(); -} - -QColor PDFLittleCMS::getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const -{ - cmsHTRANSFORM transform = getTransformFromICCProfile(iccData, iccID, renderingIntent, false); - - if (!transform) - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed.")); - return QColor(); - } - - std::array inputBuffer = { }; - const cmsUInt32Number format = cmsGetTransformInputFormat(transform); - const cmsUInt32Number channels = T_CHANNELS(format); - const cmsUInt32Number colorSpace = T_COLORSPACE(format); - const bool isCMYK = colorSpace == PT_CMYK; - if (channels == color.size() && channels <= inputBuffer.size()) - { - for (size_t i = 0; i < color.size(); ++i) - { - inputBuffer[i] = isCMYK ? color[i] * 100.0f : color[i]; - } - - std::array rgbOutputColor = { }; - cmsDoTransform(transform, inputBuffer.data(), rgbOutputColor.data(), 1); - return getColorFromOutputColor(rgbOutputColor); - } - else - { - reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed - invalid data format.")); - } - - return QColor(); -} - -void PDFLittleCMS::init() -{ - // Jakub Melka: initialize all color profiles - m_profiles[Output] = createProfile(m_settings.outputCS, m_manager->getOutputProfiles(), false); - m_profiles[Gray] = createProfile(m_settings.deviceGray, m_manager->getGrayProfiles(), m_settings.isConsiderOutputIntent); - m_profiles[RGB] = createProfile(m_settings.deviceRGB, m_manager->getRGBProfiles(), m_settings.isConsiderOutputIntent); - m_profiles[CMYK] = createProfile(m_settings.deviceCMYK, m_manager->getCMYKProfiles(), m_settings.isConsiderOutputIntent); - m_profiles[SoftProofing] = createProfile(m_settings.softProofingProfile, m_manager->getCMYKProfiles(), false); - m_profiles[XYZ] = cmsCreateXYZProfile(); - - cmsUInt16Number outOfGamutR = m_settings.outOfGamutColor.redF() * 0xFFFF; - cmsUInt16Number outOfGamutG = m_settings.outOfGamutColor.greenF() * 0xFFFF; - cmsUInt16Number outOfGamutB = m_settings.outOfGamutColor.blueF() * 0xFFFF; - - cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = { outOfGamutR, outOfGamutG, outOfGamutB }; - cmsSetAlarmCodes(alarmCodes); - - if (m_settings.isWhitePaperColorTransformed) - { - m_paperColor = getColorFromDeviceRGB(PDFColor(1.0f, 1.0f, 1.0f), RenderingIntent::AbsoluteColorimetric, nullptr); - - // We must check color of the paper, it can be invalid, if error occurs... - if (!m_paperColor.isValid()) - { - m_paperColor = QColor(Qt::white); - } - } - - // 64 should be enough, because we can have 4 input color spaces (gray, RGB, CMYK and XYZ), - // and 4 rendering intents. We have 4 * 4 = 16 input tables, so 64 will suffice enough - // (because we then have 25% load factor). - m_transformationCache.reserve(64); -} - -int PDFLittleCMS::installCmsPlugins() -{ - static cmsPluginOptimization optimizationPlugin = { }; - optimizationPlugin.base.Magic = cmsPluginMagicNumber; - optimizationPlugin.base.Type = cmsPluginOptimizationSig; - optimizationPlugin.base.Next = nullptr; - optimizationPlugin.base.ExpectedVersion = LCMS_VERSION; - optimizationPlugin.OptimizePtr = &PDFLittleCMS::optimizePipeline; - - cmsPlugin(&optimizationPlugin); - - return 0; -} - -cmsBool PDFLittleCMS::optimizePipeline(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags) -{ - if (!(*dwFlags & cmsFLAGS_LOWRESPRECALC)) - { - // Optimize only on low resolution precalculation - return FALSE; - } - - Q_UNUSED(Intent); - - // We will find, if we can optimize... - bool shouldOptimize = false; - for (auto stage = cmsPipelineGetPtrToFirstStage(*Lut); stage; stage = cmsStageNext(stage)) - { - if (cmsStageType(stage) == cmsSigCurveSetElemType) - { - _cmsStageToneCurvesData* data = reinterpret_cast<_cmsStageToneCurvesData*>(cmsStageData(stage)); - for (cmsUInt32Number i = 0; i < data->nCurves; ++i) - { - const cmsToneCurve* curve = data->TheCurves[i]; - const cmsInt32Number type = cmsGetToneCurveParametricType(curve); - - if (type != 0 && !cmsIsToneCurveMultisegment(curve)) - { - shouldOptimize = true; - } - } - } - } - - if (shouldOptimize) - { - cmsContext contextId = cmsGetPipelineContextID(*Lut); - cmsPipeline* pipeline = cmsPipelineAlloc(contextId, T_CHANNELS(*InputFormat), T_CHANNELS(*OutputFormat)); - if (!pipeline) - { - return FALSE; - } - - for (auto stage = cmsPipelineGetPtrToFirstStage(*Lut); stage; stage = cmsStageNext(stage)) - { - if (cmsStageType(stage) == cmsSigCurveSetElemType) - { - _cmsStageToneCurvesData* data = reinterpret_cast<_cmsStageToneCurvesData*>(cmsStageData(stage)); - std::vector curves(data->nCurves, nullptr); - - for (cmsUInt32Number i = 0; i < data->nCurves; ++i) - { - const cmsToneCurve* curve = data->TheCurves[i]; - const cmsInt32Number type = cmsGetToneCurveParametricType(curve); - - if (type != 0 && !cmsIsToneCurveMultisegment(curve)) - { - std::array segments = { }; - - const cmsFloat64Number* params = cmsGetToneCurveParams(curve); - - cmsCurveSegment& s1 = segments[0]; - cmsCurveSegment& s2 = segments[1]; - cmsCurveSegment& s3 = segments[2]; - - const cmsFloat32Number eps = cmsFloat32Number(1e-5); - const cmsFloat32Number low = 0.0f - eps; - const cmsFloat32Number high = 1.0f + eps; - - s1.Type = type; - s1.nGridPoints = 0; - s1.SampledPoints = nullptr; - std::copy(params, params + (sizeof(s1.Params) / sizeof(*s1.Params)), s1.Params); - s1.x0 = std::numeric_limits::min(); - s1.x1 = low; - - const cmsUInt32Number gridPoints = 1024; - const cmsFloat32Number factor = 1.0f / cmsFloat32Number(gridPoints - 1); - - s2.Type = 0; - s2.nGridPoints = gridPoints; - s2.SampledPoints = static_cast(_cmsCalloc(contextId, gridPoints, sizeof(*s2.SampledPoints))); - std::copy(params, params + (sizeof(s2.Params) / sizeof(*s2.Params)), s2.Params); - s2.x0 = low; - s2.x1 = high; - - for (cmsUInt32Number i = 0; i < gridPoints; ++i) - { - const cmsFloat32Number x = i * factor; - s2.SampledPoints[i] = cmsEvalToneCurveFloat(curve, interpolate(x, 0.0, 1.0, low, high)); - } - - s3.Type = type; - s3.nGridPoints = 0; - s3.SampledPoints = nullptr; - std::copy(params, params + (sizeof(s3.Params) / sizeof(*s3.Params)), s3.Params); - s3.x0 = high; - s3.x1 = std::numeric_limits::max(); - - curves[i] = cmsBuildSegmentedToneCurve(contextId, cmsUInt32Number(segments.size()), segments.data()); - - _cmsFree(contextId, s2.SampledPoints); - } - else - { - curves[i] = cmsDupToneCurve(curve); - } - } - - cmsStageAllocToneCurves(contextId, cmsFloat32Number(curves.size()), curves.data()); - - for (cmsToneCurve* curve : curves) - { - cmsFreeToneCurve(curve); - } - } - else - { - cmsPipelineInsertStage(pipeline, cmsAT_END, cmsStageDup(stage)); - } - } - - cmsPipelineFree(*Lut); - *Lut = pipeline; - } - - return FALSE; -} - -bool PDFLittleCMS::isSoftProofing() const -{ - return (m_settings.isSoftProofing || m_settings.isGamutChecking) && m_profiles[SoftProofing]; -} - -cmsHPROFILE PDFLittleCMS::createProfile(const QString& id, const PDFColorProfileIdentifiers& profileDescriptors, bool preferOutputProfile) const -{ - auto it = std::find_if(profileDescriptors.cbegin(), profileDescriptors.cend(), [&id](const PDFColorProfileIdentifier& identifier) { return identifier.id == id; }); - if (preferOutputProfile && it != profileDescriptors.end()) - { - const PDFColorProfileIdentifier& identifier = *it; - if (!identifier.isOutputIntentProfile) - { - // Find first output intent color profile - auto itOutputIntentColorProfile = std::find_if(profileDescriptors.cbegin(), profileDescriptors.cend(), [](const PDFColorProfileIdentifier& identifier) { return identifier.isOutputIntentProfile; }); - if (itOutputIntentColorProfile != profileDescriptors.end()) - { - it = itOutputIntentColorProfile; - } - } - } - - if (it != profileDescriptors.cend()) - { - const PDFColorProfileIdentifier& identifier = *it; - switch (identifier.type) - { - case PDFColorProfileIdentifier::Type::Gray: - { - cmsCIExyY whitePoint{ }; - if (cmsWhitePointFromTemp(&whitePoint, identifier.temperature)) - { - cmsToneCurve* gammaCurve = cmsBuildGamma(cmsContext(), identifier.gamma); - cmsHPROFILE profile = cmsCreateGrayProfile(&whitePoint, gammaCurve); - cmsFreeToneCurve(gammaCurve); - return profile; - } - break; - } - - case PDFColorProfileIdentifier::Type::sRGB: - return cmsCreate_sRGBProfile(); - - case PDFColorProfileIdentifier::Type::RGB: - { - cmsCIExyY whitePoint{ }; - if (cmsWhitePointFromTemp(&whitePoint, identifier.temperature)) - { - cmsCIExyYTRIPLE primaries; - primaries.Red = { identifier.primaryR.x(), identifier.primaryR.y(), 1.0 }; - primaries.Green = { identifier.primaryG.x(), identifier.primaryG.y(), 1.0 }; - primaries.Blue = { identifier.primaryB.x(), identifier.primaryB.y(), 1.0 }; - cmsToneCurve* gammaCurve = cmsBuildGamma(cmsContext(), identifier.gamma); - cmsToneCurve* toneCurves[3] = { gammaCurve, cmsDupToneCurve(gammaCurve), cmsDupToneCurve(gammaCurve) }; - cmsHPROFILE profile = cmsCreateRGBProfile(&whitePoint, &primaries, toneCurves); - cmsFreeToneCurveTriple(toneCurves); - return profile; - } - break; - } - case PDFColorProfileIdentifier::Type::FileGray: - case PDFColorProfileIdentifier::Type::FileRGB: - case PDFColorProfileIdentifier::Type::FileCMYK: - { - QFile file(identifier.id); - if (file.open(QFile::ReadOnly)) - { - QByteArray fileContent = file.readAll(); - file.close(); - - return cmsOpenProfileFromMem(fileContent.data(), fileContent.size()); - } - - break; - } - - case PDFColorProfileIdentifier::Type::MemoryGray: - case PDFColorProfileIdentifier::Type::MemoryRGB: - case PDFColorProfileIdentifier::Type::MemoryCMYK: - return cmsOpenProfileFromMem(identifier.profileMemoryData.data(), identifier.profileMemoryData.size()); - - default: - Q_ASSERT(false); - break; - } - } - - return cmsHPROFILE(); -} - -cmsHTRANSFORM PDFLittleCMS::getTransform(Profile profile, RenderingIntent intent, bool isRGB888Buffer) const -{ - const int key = getCacheKey(profile, intent, isRGB888Buffer); - - QReadLocker lock(&m_transformationCacheLock); - auto it = m_transformationCache.find(key); - if (it == m_transformationCache.cend()) - { - lock.unlock(); - QWriteLocker writeLock(&m_transformationCacheLock); - - // Now, we have locked cache for writing. We must find out, - // if some other thread doesn't created the transformation already. - it = m_transformationCache.find(key); - if (it == m_transformationCache.cend()) - { - cmsHTRANSFORM transform = cmsHTRANSFORM(); - cmsHPROFILE input = m_profiles[profile]; - cmsHPROFILE output = m_profiles[Output]; - - if (input && output) - { - if (isSoftProofing()) - { - cmsHPROFILE proofingProfile = m_profiles[SoftProofing]; - RenderingIntent proofingIntent = m_settings.proofingIntent; - if (m_settings.proofingIntent == RenderingIntent::Auto) - { - proofingIntent = intent; - } - - transform = cmsCreateProofingTransform(input, getProfileDataFormat(input), output, isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, proofingProfile, - getLittleCMSRenderingIntent(intent), getLittleCMSRenderingIntent(proofingIntent), getTransformationFlags()); - } - else - { - transform = cmsCreateTransform(input, getProfileDataFormat(input), output, isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, getLittleCMSRenderingIntent(intent), getTransformationFlags()); - } - } - - it = m_transformationCache.insert(std::make_pair(key, transform)).first; - } - - // We must return it here to avoid race condition (after current block, - // lock is not locked, because we unlocked lock for reading). - return it->second; - } - - return it->second; -} - -cmsUInt32Number PDFLittleCMS::getTransformationFlags() const -{ - // Flag cmsFLAGS_NONEGATIVES is used here to avoid invalid transformation - // between CMYK color space and RGB color space in the Ghent output suite examples. - cmsUInt32Number flags = cmsFLAGS_NOCACHE | cmsFLAGS_NONEGATIVES; - - if (m_settings.isBlackPointCompensationActive) - { - flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; - } - - switch (m_settings.accuracy) - { - case PDFCMSSettings::Accuracy::Low: - flags |= cmsFLAGS_LOWRESPRECALC; - break; - - case PDFCMSSettings::Accuracy::Medium: - break; - - case PDFCMSSettings::Accuracy::High: - flags |= cmsFLAGS_HIGHRESPRECALC; - break; - - default: - Q_ASSERT(false); - break; - } - - if (m_settings.isGamutChecking) - { - flags |= cmsFLAGS_GAMUTCHECK; - } - - if (m_settings.isSoftProofing) - { - flags |= cmsFLAGS_SOFTPROOFING; - } - - return flags; -} - -RenderingIntent PDFLittleCMS::getEffectiveRenderingIntent(RenderingIntent intent) const -{ - if (m_settings.intent != RenderingIntent::Auto) - { - return m_settings.intent; - } - - return intent; -} - -cmsUInt32Number PDFLittleCMS::getLittleCMSRenderingIntent(RenderingIntent intent) -{ - switch (intent) - { - case RenderingIntent::Perceptual: - return INTENT_PERCEPTUAL; - - case RenderingIntent::AbsoluteColorimetric: - return INTENT_ABSOLUTE_COLORIMETRIC; - - case RenderingIntent::RelativeColorimetric: - return INTENT_RELATIVE_COLORIMETRIC; - - case RenderingIntent::Saturation: - return INTENT_SATURATION; - - default: - Q_ASSERT(false); - break; - } - - return INTENT_PERCEPTUAL; -} - -cmsUInt32Number PDFLittleCMS::getProfileDataFormat(cmsHPROFILE profile) -{ - cmsColorSpaceSignature signature = cmsGetColorSpace(profile); - switch (signature) - { - case cmsSigGrayData: - return TYPE_GRAY_FLT; - - case cmsSigRgbData: - return TYPE_RGB_FLT; - - case cmsSigCmykData: - return TYPE_CMYK_FLT; - - case cmsSigXYZData: - return TYPE_XYZ_FLT; - - default: - break; - } - - return 0; -} - -QColor PDFLittleCMS::getColorFromOutputColor(std::array color01) -{ - QColor color(QColor::Rgb); - color.setRgbF(qBound(0.0f, color01[0], 1.0f), qBound(0.0f, color01[1], 1.0f), qBound(0.0f, color01[2], 1.0f)); - return color; -} - -QByteArray PDFLittleCMS::getTransformColorSpaceKey(const PDFCMS::ColorSpaceTransformParams& params) -{ - QByteArray key; - - QBuffer buffer(&key); - buffer.open(QBuffer::WriteOnly); - - QDataStream stream(&buffer); - stream << params.sourceType; - stream << params.sourceIccId; - stream << params.targetType; - stream << params.targetIccId; - stream << params.intent; - - buffer.close(); - - return key; -} - -cmsHTRANSFORM PDFLittleCMS::getTransformBetweenColorSpaces(const PDFCMS::ColorSpaceTransformParams& params) const -{ - QByteArray key = getTransformColorSpaceKey(params); - QReadLocker lock(&m_transformColorSpaceCacheLock); - auto it = m_transformColorSpaceCache.find(key); - if (it == m_transformColorSpaceCache.cend()) - { - lock.unlock(); - QWriteLocker writeLock(&m_transformColorSpaceCacheLock); - - // Now, we have locked cache for writing. We must find out, - // if some other thread doesn't created the transformation already. - it = m_transformColorSpaceCache.find(key); - if (it == m_transformColorSpaceCache.cend()) - { - cmsHPROFILE inputProfile = cmsHPROFILE(); - cmsHPROFILE outputProfile = cmsHPROFILE(); - cmsHTRANSFORM transform = cmsHTRANSFORM(); - - switch (params.sourceType) - { - case ColorSpaceType::DeviceGray: - inputProfile = m_profiles[Gray]; - break; - - case ColorSpaceType::DeviceRGB: - inputProfile = m_profiles[RGB]; - break; - - case ColorSpaceType::DeviceCMYK: - inputProfile = m_profiles[CMYK]; - break; - - case ColorSpaceType::XYZ: - inputProfile = m_profiles[XYZ]; - break; - - case ColorSpaceType::ICC: - inputProfile = cmsOpenProfileFromMem(params.sourceIccData.data(), params.sourceIccData.size()); - break; - - default: - Q_ASSERT(false); - break; - } - - switch (params.targetType) - { - case ColorSpaceType::DeviceGray: - outputProfile = m_profiles[Gray]; - break; - - case ColorSpaceType::DeviceRGB: - outputProfile = m_profiles[RGB]; - break; - - case ColorSpaceType::DeviceCMYK: - outputProfile = m_profiles[CMYK]; - break; - - case ColorSpaceType::XYZ: - outputProfile = m_profiles[XYZ]; - break; - - case ColorSpaceType::ICC: - outputProfile = cmsOpenProfileFromMem(params.targetIccData.data(), params.targetIccData.size()); - break; - - default: - Q_ASSERT(false); - break; - } - - if (inputProfile && outputProfile) - { - transform = cmsCreateTransform(inputProfile, getProfileDataFormat(inputProfile), outputProfile, getProfileDataFormat(outputProfile), getLittleCMSRenderingIntent(params.intent), getTransformationFlags()); - } - - if (params.sourceType == ColorSpaceType::ICC) - { - cmsCloseProfile(inputProfile); - } - - if (params.targetType == ColorSpaceType::ICC) - { - cmsCloseProfile(outputProfile); - } - - it = m_transformColorSpaceCache.insert(std::make_pair(key, transform)).first; - } - - return it->second; - } - else - { - return it->second; - } - - return cmsHTRANSFORM(); -} - -QString getInfoFromProfile(cmsHPROFILE profile, cmsInfoType infoType) -{ - QLocale locale; - QString country = QLocale::countryToString(locale.country()); - QString language = QLocale::languageToString(locale.language()); - - char countryCode[3] = { }; - char languageCode[3] = { }; - if (country.size() == 2) - { - countryCode[0] = country[0].toLatin1(); - countryCode[1] = country[1].toLatin1(); - } - if (language.size() == 2) - { - languageCode[0] = language[0].toLatin1(); - languageCode[1] = language[1].toLatin1(); - } - - // Jakub Melka: try to get profile info from current language/country. - // If it fails, then pick any language/any country. - cmsUInt32Number bufferSize = cmsGetProfileInfo(profile, infoType, languageCode, countryCode, nullptr, 0); - if (bufferSize) - { - std::vector buffer(bufferSize, 0); - cmsGetProfileInfo(profile, infoType, languageCode, countryCode, buffer.data(), static_cast(buffer.size())); - return QString::fromWCharArray(buffer.data()); - } - - bufferSize = cmsGetProfileInfo(profile, infoType, cmsNoLanguage, cmsNoCountry, nullptr, 0); - if (bufferSize) - { - std::vector buffer(bufferSize, 0); - cmsGetProfileInfo(profile, infoType, cmsNoLanguage, cmsNoCountry, buffer.data(), static_cast(buffer.size())); - return QString::fromWCharArray(buffer.data()); - } - - return QString(); -} - -bool PDFCMSGeneric::isCompatible(const PDFCMSSettings& settings) const -{ - return settings.system == PDFCMSSettings::System::Generic; -} - -QColor PDFCMSGeneric::getPaperColor() const -{ - return QColor(Qt::white); -} - -QColor PDFCMSGeneric::getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(color); - Q_UNUSED(intent); - Q_UNUSED(reporter); - return QColor(); -} - -QColor PDFCMSGeneric::getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(color); - Q_UNUSED(intent); - Q_UNUSED(reporter); - return QColor(); -} - -QColor PDFCMSGeneric::getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(color); - Q_UNUSED(intent); - Q_UNUSED(reporter); - return QColor(); -} - -QColor PDFCMSGeneric::getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(color); - Q_UNUSED(intent); - Q_UNUSED(reporter); - Q_UNUSED(whitePoint); - return QColor(); -} - -QColor PDFCMSGeneric::getColorFromICC(const PDFColor& color, - RenderingIntent renderingIntent, - const QByteArray& iccID, - const QByteArray& iccData, - PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(color); - Q_UNUSED(renderingIntent); - Q_UNUSED(iccID); - Q_UNUSED(iccData); - Q_UNUSED(reporter); - return QColor(); -} - -bool PDFCMSGeneric::fillRGBBufferFromDeviceGray(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(colors); - Q_UNUSED(intent); - Q_UNUSED(outputBuffer); - Q_UNUSED(reporter); - return false; -} - -bool PDFCMSGeneric::fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(colors); - Q_UNUSED(intent); - Q_UNUSED(outputBuffer); - Q_UNUSED(reporter); - return false; -} - -bool PDFCMSGeneric::fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(colors); - Q_UNUSED(intent); - Q_UNUSED(outputBuffer); - Q_UNUSED(reporter); - return false; -} - -bool PDFCMSGeneric::fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(whitePoint); - Q_UNUSED(colors); - Q_UNUSED(intent); - Q_UNUSED(outputBuffer); - Q_UNUSED(reporter); - return false; -} - -bool PDFCMSGeneric::fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(colors); - Q_UNUSED(renderingIntent); - Q_UNUSED(outputBuffer); - Q_UNUSED(iccID); - Q_UNUSED(iccData); - Q_UNUSED(reporter); - return false; -} - -bool PDFCMSGeneric::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& params) const -{ - Q_UNUSED(params); - return false; -} - -PDFCMSManager::PDFCMSManager(QObject* parent) : - BaseClass(parent), - m_document(nullptr), - m_mutex(QMutex::Recursive) -{ - -} - -PDFCMSPointer PDFCMSManager::getCurrentCMS() const -{ - QMutexLocker lock(&m_mutex); - return m_CMS.get(this, &PDFCMSManager::getCurrentCMSImpl); -} - -void PDFCMSManager::setSettings(const PDFCMSSettings& settings) -{ - if (m_settings != settings) - { - // We must ensure, that mutex is not locked, while we are - // sending signal about CMS change. - { - QMutexLocker lock(&m_mutex); - m_settings = settings; - clearCache(); - } - - emit colorManagementSystemChanged(); - } -} - -const PDFColorProfileIdentifiers& PDFCMSManager::getOutputProfiles() const -{ - QMutexLocker lock(&m_mutex); - return m_outputProfiles.get(this, &PDFCMSManager::getOutputProfilesImpl); -} - -const PDFColorProfileIdentifiers& PDFCMSManager::getGrayProfiles() const -{ - QMutexLocker lock(&m_mutex); - return m_grayProfiles.get(this, &PDFCMSManager::getGrayProfilesImpl); -} - -const PDFColorProfileIdentifiers& PDFCMSManager::getRGBProfiles() const -{ - QMutexLocker lock(&m_mutex); - return m_RGBProfiles.get(this, &PDFCMSManager::getRGBProfilesImpl); -} - -const PDFColorProfileIdentifiers& PDFCMSManager::getCMYKProfiles() const -{ - QMutexLocker lock(&m_mutex); - return m_CMYKProfiles.get(this, &PDFCMSManager::getCMYKProfilesImpl); -} - -const PDFColorProfileIdentifiers& PDFCMSManager::getExternalProfiles() const -{ - // Jakub Melka: do not protect this by mutex, this function is private - // and must be called only from mutex-protected code. - return m_externalProfiles.get(this, &PDFCMSManager::getExternalProfilesImpl); -} - -PDFCMSSettings PDFCMSManager::getDefaultSettings() const -{ - PDFCMSSettings settings; - - auto getFirstProfileId = [](const PDFColorProfileIdentifiers& identifiers) - { - if (!identifiers.empty()) - { - return identifiers.front().id; - } - return QString(); - }; - - settings.system = PDFCMSSettings::System::LittleCMS2; - settings.outputCS = getFirstProfileId(getOutputProfiles()); - settings.deviceGray = getFirstProfileId(getGrayProfiles()); - settings.deviceRGB = getFirstProfileId(getRGBProfiles()); - settings.deviceCMYK = getFirstProfileId(getCMYKProfiles()); - - return settings; -} - -void PDFCMSManager::setDocument(const PDFDocument* document) -{ - std::optional lock; - lock.emplace(&m_mutex); - - if (m_document == document) - { - return; - } - - m_document = document; - - int i = 0; - PDFColorProfileIdentifiers outputIntentProfiles; - - if (m_document) - { - for (const PDFOutputIntent& outputIntent : m_document->getCatalog()->getOutputIntents()) - { - QByteArray content; - - try - { - // Try to read the profile from the output intent stream. If it fails, then do nothing. - PDFObject outputProfileObject = m_document->getObject(outputIntent.getOutputProfile()); - if (outputProfileObject.isStream()) - { - content = m_document->getDecodedStream(outputProfileObject.getStream()); - } - } - catch (PDFException) - { - continue; - } - - if (content.isEmpty()) - { - // Decoding of output profile failed. Continue - // with next output profile. - continue; - } - - cmsHPROFILE profile = cmsOpenProfileFromMem(content.data(), content.size()); - if (profile) - { - PDFColorProfileIdentifier::Type csiType = PDFColorProfileIdentifier::Type::Invalid; - const cmsColorSpaceSignature colorSpace = cmsGetColorSpace(profile); - switch (colorSpace) - { - case cmsSigGrayData: - csiType = PDFColorProfileIdentifier::Type::MemoryGray; - break; - - case cmsSigRgbData: - csiType = PDFColorProfileIdentifier::Type::MemoryRGB; - break; - - case cmsSigCmykData: - csiType = PDFColorProfileIdentifier::Type::MemoryCMYK; - break; - - default: - break; - } - - QString description = getInfoFromProfile(profile, cmsInfoDescription); - cmsCloseProfile(profile); - - // If we have a valid profile, then add it - if (csiType != PDFColorProfileIdentifier::Type::Invalid) - { - outputIntentProfiles.emplace_back(PDFColorProfileIdentifier::createOutputIntent(csiType, qMove(description), QString("@@OUTPUT_INTENT_PROFILE_%1").arg(++i), qMove(content))); - } - } - } - } - - bool outputIntentProfilesChanged = false; - if (m_outputIntentProfiles != outputIntentProfiles) - { - m_outputIntentProfiles = qMove(outputIntentProfiles); - clearCache(); - outputIntentProfilesChanged = true; - } - - if (outputIntentProfilesChanged) - { - lock = std::nullopt; - emit colorManagementSystemChanged(); - } -} - -QString PDFCMSManager::getSystemName(PDFCMSSettings::System system) -{ - switch (system) - { - case PDFCMSSettings::System::Generic: - return tr("Generic"); - - case PDFCMSSettings::System::LittleCMS2: - { - const int major = LCMS_VERSION / 1000; - const int minor = (LCMS_VERSION % 1000) / 10; - return tr("Little CMS %1.%2").arg(major).arg(minor); - } - - default: - { - Q_ASSERT(false); - break; - } - } - - return QString(); -} - -PDFCMSPointer PDFCMSManager::getCurrentCMSImpl() const -{ - switch (m_settings.system) - { - case PDFCMSSettings::System::Generic: - return PDFCMSPointer(new PDFCMSGeneric()); - - case PDFCMSSettings::System::LittleCMS2: - return PDFCMSPointer(new PDFLittleCMS(this, m_settings)); - - default: - Q_ASSERT(false); - break; - } - - return PDFCMSPointer(new PDFCMSGeneric()); -} - -void PDFCMSManager::clearCache() -{ - QMutexLocker lock(&m_mutex); - m_CMS.dirty(); - m_outputProfiles.dirty(); - m_grayProfiles.dirty(); - m_RGBProfiles.dirty(); - m_CMYKProfiles.dirty(); - m_externalProfiles.dirty(); -} - -PDFColorProfileIdentifiers PDFCMSManager::getOutputProfilesImpl() const -{ - // Currently, we only support sRGB output color profile. - return { PDFColorProfileIdentifier::createSRGB() }; -} - -PDFColorProfileIdentifiers PDFCMSManager::getGrayProfilesImpl() const -{ - // Jakub Melka: We create gray profiles for temperature 5000K, 6500K and 9300K. - // We also use linear gamma and gamma value 2.2. - PDFColorProfileIdentifiers result = - { - PDFColorProfileIdentifier::createGray(tr("Gray D65, γ = 2.2"), "@GENERIC_Gray_D65_g22", 6500.0, 2.2), - PDFColorProfileIdentifier::createGray(tr("Gray D50, γ = 2.2"), "@GENERIC_Gray_D50_g22", 5000.0, 2.2), - PDFColorProfileIdentifier::createGray(tr("Gray D93, γ = 2.2"), "@GENERIC_Gray_D93_g22", 9300.0, 2.2), - PDFColorProfileIdentifier::createGray(tr("Gray D65, γ = 1.0 (linear)"), "@GENERIC_Gray_D65_g10", 6500.0, 1.0), - PDFColorProfileIdentifier::createGray(tr("Gray D50, γ = 1.0 (linear)"), "@GENERIC_Gray_D50_g10", 5000.0, 1.0), - PDFColorProfileIdentifier::createGray(tr("Gray D93, γ = 1.0 (linear)"), "@GENERIC_Gray_D93_g10", 9300.0, 1.0) - }; - - PDFColorProfileIdentifiers externalGrayProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileGray); - result.insert(result.end(), std::make_move_iterator(externalGrayProfiles.begin()), std::make_move_iterator(externalGrayProfiles.end())); - PDFColorProfileIdentifiers outputIntentRGBProfiles = getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type::MemoryGray); - result.insert(result.end(), std::make_move_iterator(outputIntentRGBProfiles.begin()), std::make_move_iterator(outputIntentRGBProfiles.end())); - return result; -} - -PDFColorProfileIdentifiers PDFCMSManager::getRGBProfilesImpl() const -{ - // Jakub Melka: We create RGB profiles for common standards and also for - // default standard sRGB. See https://en.wikipedia.org/wiki/Color_spaces_with_RGB_primaries. - PDFColorProfileIdentifiers result = - { - PDFColorProfileIdentifier::createSRGB(), - PDFColorProfileIdentifier::createRGB(tr("HDTV (ITU-R BT.709)"), "@GENERIC_RGB_HDTV", 6500, QPointF(0.64, 0.33), QPointF(0.30, 0.60), QPointF(0.15, 0.06), 20.0 / 9.0), - PDFColorProfileIdentifier::createRGB(tr("Adobe RGB 1998"), "@GENERIC_RGB_Adobe1998", 6500, QPointF(0.64, 0.33), QPointF(0.30, 0.60), QPointF(0.15, 0.06), 563.0 / 256.0), - PDFColorProfileIdentifier::createRGB(tr("PAL / SECAM"), "@GENERIC_RGB_PalSecam", 6500, QPointF(0.64, 0.33), QPointF(0.29, 0.60), QPointF(0.15, 0.06), 14.0 / 5.0), - PDFColorProfileIdentifier::createRGB(tr("NTSC"), "@GENERIC_RGB_NTSC", 6500, QPointF(0.64, 0.34), QPointF(0.31, 0.595), QPointF(0.155, 0.07), 20.0 / 9.0), - PDFColorProfileIdentifier::createRGB(tr("Adobe Wide Gamut RGB"), "@GENERIC_RGB_AdobeWideGamut", 5000, QPointF(0.735, 0.265), QPointF(0.115, 0.826), QPointF(0.157, 0.018), 563.0 / 256.0), - PDFColorProfileIdentifier::createRGB(tr("ProPhoto RGB"), "@GENERIC_RGB_ProPhoto", 5000, QPointF(0.7347, 0.2653), QPointF(0.1596, 0.8404), QPointF(0.0366, 0.0001), 9.0 / 5.0) - }; - - PDFColorProfileIdentifiers externalRGBProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileRGB); - result.insert(result.end(), externalRGBProfiles.begin(), externalRGBProfiles.end()); - PDFColorProfileIdentifiers outputIntentRGBProfiles = getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type::MemoryRGB); - result.insert(result.end(), std::make_move_iterator(outputIntentRGBProfiles.begin()), std::make_move_iterator(outputIntentRGBProfiles.end())); - return result; -} - -PDFColorProfileIdentifiers PDFCMSManager::getCMYKProfilesImpl() const -{ - PDFColorProfileIdentifiers result; - - PDFColorProfileIdentifiers externalCMYKProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileCMYK); - result.insert(result.end(), externalCMYKProfiles.begin(), externalCMYKProfiles.end()); - PDFColorProfileIdentifiers outputIntentCMYKProfiles = getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type::MemoryCMYK); - result.insert(result.end(), std::make_move_iterator(outputIntentCMYKProfiles.begin()), std::make_move_iterator(outputIntentCMYKProfiles.end())); - - return result; -} - -PDFColorProfileIdentifiers PDFCMSManager::getExternalColorProfiles(QString profileDirectory) const -{ - PDFColorProfileIdentifiers result; - - QDir directory(profileDirectory); - QDir applicationDirectory(QApplication::applicationDirPath()); - if (!profileDirectory.isEmpty() && directory.exists()) - { - QStringList iccProfiles = directory.entryList({ "*.icc" }, QDir::Files | QDir::Readable | QDir::NoDotAndDotDot, QDir::NoSort); - for (const QString& fileName : iccProfiles) - { - QString filePath = QDir::cleanPath(applicationDirectory.relativeFilePath(directory.absoluteFilePath(fileName))); - - // Try to read the profile from the file. If it fails, then do nothing. - QFile file(filePath); - if (file.open(QFile::ReadOnly)) - { - QByteArray content = file.readAll(); - file.close(); - - cmsHPROFILE profile = cmsOpenProfileFromMem(content.data(), content.size()); - if (profile) - { - PDFColorProfileIdentifier::Type csiType = PDFColorProfileIdentifier::Type::Invalid; - const cmsColorSpaceSignature colorSpace = cmsGetColorSpace(profile); - switch (colorSpace) - { - case cmsSigGrayData: - csiType = PDFColorProfileIdentifier::Type::FileGray; - break; - - case cmsSigRgbData: - csiType = PDFColorProfileIdentifier::Type::FileRGB; - break; - - case cmsSigCmykData: - csiType = PDFColorProfileIdentifier::Type::FileCMYK; - break; - - default: - break; - } - - QString description = getInfoFromProfile(profile, cmsInfoDescription); - cmsCloseProfile(profile); - - // If we have a valid profile, then add it - if (csiType != PDFColorProfileIdentifier::Type::Invalid) - { - result.emplace_back(PDFColorProfileIdentifier::createFile(csiType, qMove(description), filePath)); - } - } - } - } - } - - return result; -} - -PDFColorProfileIdentifiers PDFCMSManager::getExternalProfilesImpl() const -{ - PDFColorProfileIdentifiers result; - - QStringList directories(m_settings.profileDirectory); - -#ifdef Q_OS_WIN - std::array buffer = { }; - DWORD bufferSize = DWORD(buffer.size() * sizeof(WCHAR)); - if (GetColorDirectoryW(NULL, buffer.data(), &bufferSize)) - { - const DWORD charactersWithNull = bufferSize / sizeof(WCHAR); - const DWORD charactersWithoutNull = bufferSize > 0 ? charactersWithNull - 1 : 0; - - QString directory = QString::fromWCharArray(buffer.data(), int(charactersWithoutNull)); - directories << QDir::fromNativeSeparators(directory); - } -#endif - - for (const QString& directory : directories) - { - PDFColorProfileIdentifiers externalProfiles = getExternalColorProfiles(directory); - result.insert(result.end(), externalProfiles.begin(), externalProfiles.end()); - } - - return result; -} - -PDFColorProfileIdentifiers PDFCMSManager::getFilteredExternalProfiles(PDFColorProfileIdentifier::Type type) const -{ - PDFColorProfileIdentifiers result; - const PDFColorProfileIdentifiers& externalProfiles = getExternalProfiles(); - std::copy_if(externalProfiles.cbegin(), externalProfiles.cend(), std::back_inserter(result), [type](const PDFColorProfileIdentifier& identifier) { return identifier.type == type; }); - return result; -} - -PDFColorProfileIdentifiers PDFCMSManager::getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type type) const -{ - PDFColorProfileIdentifiers result; - std::copy_if(m_outputIntentProfiles.cbegin(), m_outputIntentProfiles.cend(), std::back_inserter(result), [type](const PDFColorProfileIdentifier& identifier) { return identifier.type == type; }); - return result; -} - -PDFColorProfileIdentifier PDFColorProfileIdentifier::createGray(QString name, QString id, PDFReal temperature, PDFReal gamma) -{ - PDFColorProfileIdentifier result; - result.type = Type::Gray; - result.name = qMove(name); - result.id = qMove(id); - result.temperature = temperature; - result.gamma = gamma; - return result; -} - -PDFColorProfileIdentifier PDFColorProfileIdentifier::createSRGB() -{ - PDFColorProfileIdentifier result; - result.type = Type::sRGB; - result.name = PDFCMSManager::tr("sRGB"); - result.id = "@GENERIC_sRGB"; - return result; -} - -PDFColorProfileIdentifier PDFColorProfileIdentifier::createRGB(QString name, QString id, PDFReal temperature, QPointF primaryR, QPointF primaryG, QPointF primaryB, PDFReal gamma) -{ - PDFColorProfileIdentifier result; - result.type = Type::RGB; - result.name = qMove(name); - result.id = qMove(id); - result.temperature = temperature; - result.primaryR = primaryR; - result.primaryG = primaryG; - result.primaryB = primaryB; - result.gamma = gamma; - return result; -} - -PDFColorProfileIdentifier PDFColorProfileIdentifier::createFile(Type type, QString name, QString id) -{ - PDFColorProfileIdentifier result; - result.type = type; - result.name = qMove(name); - result.id = qMove(id); - return result; -} - -PDFColorProfileIdentifier PDFColorProfileIdentifier::createOutputIntent(PDFColorProfileIdentifier::Type type, QString name, QString id, QByteArray profileData) -{ - PDFColorProfileIdentifier result; - result.type = type; - result.name = qMove(name); - result.id = qMove(id); - result.profileMemoryData = qMove(profileData); - result.isOutputIntentProfile = true; - return result; -} - -PDFColor3 PDFCMS::getDefaultXYZWhitepoint() -{ - const cmsCIEXYZ* whitePoint = cmsD50_XYZ(); - return PDFColor3{ PDFColorComponent(whitePoint->X), PDFColorComponent(whitePoint->Y), PDFColorComponent(whitePoint->Z) }; -} - -PDFColorComponentMatrix_3x3 PDFChromaticAdaptationXYZ::createWhitepointChromaticAdaptation(const PDFColor3& targetWhitePoint, - const PDFColor3& sourceWhitePoint, - PDFCMSSettings::ColorAdaptationXYZ method) -{ - PDFColorComponentMatrix_3x3 matrix; - matrix.makeIdentity(); - - switch (method) - { - case pdf::PDFCMSSettings::ColorAdaptationXYZ::None: - // No scaling performed, just return identity matrix - break; - - case pdf::PDFCMSSettings::ColorAdaptationXYZ::XYZScaling: - matrix.makeDiagonal(std::array{ targetWhitePoint[0] / sourceWhitePoint[0], - targetWhitePoint[1] / sourceWhitePoint[1], - targetWhitePoint[2] / sourceWhitePoint[2] - }); - break; - - case pdf::PDFCMSSettings::ColorAdaptationXYZ::CAT97: - { - // CAT97 matrix, as defined in https://en.wikipedia.org/wiki/LMS_color_space - constexpr PDFColorComponentMatrix_3x3 cat97Matrix( - 0.8562f, 0.3372f, -0.1934f, - -0.8360f, 1.8327f, 0.0033f, - 0.0357f, -0.0469f, 1.0112f); - - // Inverse of CAT97 matrix (using wxMaxima to compute it) - constexpr PDFColorComponentMatrix_3x3 inverseCat97Matrix( - 0.9873999149199271f, -0.1768250198556842f, 0.1894251049357571f, - 0.4504351090445315f, 0.464932897752711f, 0.08463199320275755f, - -0.01396832510725165f, 0.027806572501434f, 0.9861617526058175f); - - PDFColor3 adaptedTargetWhitePoint = cat97Matrix * targetWhitePoint; - PDFColor3 adaptedSourceWhitePoint = cat97Matrix * sourceWhitePoint; - PDFColor3 gain = { - adaptedTargetWhitePoint[0] / adaptedSourceWhitePoint[0], - adaptedTargetWhitePoint[1] / adaptedSourceWhitePoint[1], - adaptedTargetWhitePoint[2] / adaptedSourceWhitePoint[2] - }; - - PDFColorComponentMatrix_3x3 gainMatrix; - gainMatrix.makeDiagonal(gain); - - matrix = inverseCat97Matrix * gainMatrix * cat97Matrix; - break; - } - - case pdf::PDFCMSSettings::ColorAdaptationXYZ::CAT02: - { - // CAT02 matrix, as defined in https://en.wikipedia.org/wiki/LMS_color_space - constexpr PDFColorComponentMatrix_3x3 cat02Matrix( - 0.7328f, 0.4296f, -0.1624f, - -0.7036f, 1.6975f, 0.0061f, - 0.0030f, 0.0136f, 0.9834f); - - // Inverse of CAT02 matrix (using wxMaxima to compute it) - constexpr PDFColorComponentMatrix_3x3 inverseCat02Matrix( - 1.096123820835514f, -0.2788690002182872f, 0.182745179382773f, - 0.4543690419753592f, 0.4735331543074117f, 0.0720978037172291f, - -0.009627608738429352f, -0.005698031216113419f, 1.015325639954543f); - - PDFColor3 adaptedTargetWhitePoint = cat02Matrix * targetWhitePoint; - PDFColor3 adaptedSourceWhitePoint = cat02Matrix * sourceWhitePoint; - PDFColor3 gain = { - adaptedTargetWhitePoint[0] / adaptedSourceWhitePoint[0], - adaptedTargetWhitePoint[1] / adaptedSourceWhitePoint[1], - adaptedTargetWhitePoint[2] / adaptedSourceWhitePoint[2] - }; - - PDFColorComponentMatrix_3x3 gainMatrix; - gainMatrix.makeDiagonal(gain); - - matrix = inverseCat02Matrix * gainMatrix * cat02Matrix; - break; - } - - case pdf::PDFCMSSettings::ColorAdaptationXYZ::Bradford: - { - // Bradford matrix, as defined in https://en.wikipedia.org/wiki/LMS_color_space - constexpr PDFColorComponentMatrix_3x3 bradfordMatrix( - 0.8951f, 0.2264f, -0.1614f, - -0.7502f, 1.7135f, 0.0367f, - 0.0389f, -0.0685f, 1.0296f); - - // Inverse of bradford matrix (using wxMaxima to compute it) - constexpr PDFColorComponentMatrix_3x3 inverseBradfordMatrix( - 1.004360519274085f, -0.1262294327613208f, 0.1619428982062721f, - 0.4399123264001572f, 0.527481594455384f, 0.05015858096782513f, - -0.008678739162151443f, 0.03986287311053728f, 0.9684695843590444f); - - PDFColor3 adaptedTargetWhitePoint = bradfordMatrix * targetWhitePoint; - PDFColor3 adaptedSourceWhitePoint = bradfordMatrix * sourceWhitePoint; - PDFColor3 gain = { - adaptedTargetWhitePoint[0] / adaptedSourceWhitePoint[0], - adaptedTargetWhitePoint[1] / adaptedSourceWhitePoint[1], - adaptedTargetWhitePoint[2] / adaptedSourceWhitePoint[2] - }; - - PDFColorComponentMatrix_3x3 gainMatrix; - gainMatrix.makeDiagonal(gain); - - matrix = inverseBradfordMatrix * gainMatrix * bradfordMatrix; - break; - } - - default: - Q_ASSERT(false); - break; - } - - return matrix; -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfcms.h" +#include "pdfdocument.h" +#include "pdfexecutionpolicy.h" + +#include +#include + +#pragma warning(push) +#pragma warning(disable:5033) +#define CMS_NO_REGISTER_KEYWORD +#include +#include +#pragma warning(pop) + + +#ifdef Q_OS_WIN +#define NOMINMAX +#include +#include +#pragma comment(lib, "Mscms") +#endif + +#include + +namespace pdf +{ + +class PDFLittleCMS : public PDFCMS +{ +public: + explicit PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings); + virtual ~PDFLittleCMS() override; + + virtual bool isCompatible(const PDFCMSSettings& settings) const override; + virtual QColor getPaperColor() const override; + virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromDeviceGray(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; + virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override; + +private: + void init(); + + static int installCmsPlugins(); + + static cmsBool optimizePipeline(cmsPipeline** Lut, + cmsUInt32Number Intent, + cmsUInt32Number* InputFormat, + cmsUInt32Number* OutputFormat, + cmsUInt32Number* dwFlags); + + enum Profile + { + Output, + Gray, + RGB, + CMYK, + XYZ, + SoftProofing, + ProfileCount + }; + + /// Returns true, if we are doing soft-proofing + bool isSoftProofing() const; + + /// Creates a profile using provided id and a list of profile descriptors. + /// If profile can't be created, then null handle is returned. If \p preferOutputProfile + /// is set to true, and given profile is not output profile, then first output profile + /// is being selected. + /// \param id Id of color profile + /// \param profileDescriptors Profile descriptor list + /// \param preferOutputProfile + cmsHPROFILE createProfile(const QString& id, const PDFColorProfileIdentifiers& profileDescriptors, bool preferOutputProfile) const; + + /// Gets transform from cache. If transform doesn't exist, then it is created. + /// \param profile Color profile + /// \param intent Rendering intent + /// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used + cmsHTRANSFORM getTransform(Profile profile, RenderingIntent intent, bool isRGB888Buffer) const; + + /// Gets transform for ICC profile from cache. If transform doesn't exist, then it is created. + /// \param iccData Data of icc profile + /// \param iccID Icc profile id + /// \param renderingIntent Rendering intent + /// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used + cmsHTRANSFORM getTransformFromICCProfile(const QByteArray& iccData, const QByteArray& iccID, RenderingIntent renderingIntent, bool isRGB888Buffer) const; + + /// Returns transformation flags according to the current settings + cmsUInt32Number getTransformationFlags() const; + + /// Calculates effective rendering intent. If rendering intent is auto, + /// then \p intent is used, otherwise intent is overriden. + RenderingIntent getEffectiveRenderingIntent(RenderingIntent intent) const; + + /// Gets transform from cache key. + /// \param profile Color profile + /// \param intent Rendering intent + /// \param isRGB888Buffer If true, 8-bit RGB output buffer is used, otherwise FLOAT RGB output buffer is used + static constexpr int getCacheKey(Profile profile, RenderingIntent intent, bool isRGB888Buffer) { return ((int(intent) * ProfileCount + profile) << 1) + (isRGB888Buffer ? 1 : 0); } + + /// Returns little CMS rendering intent + /// \param intent Rendering intent + static cmsUInt32Number getLittleCMSRenderingIntent(RenderingIntent intent); + + /// Returns little CMS data format for profile + /// \param profile Color profile handle + static cmsUInt32Number getProfileDataFormat(cmsHPROFILE profile); + + /// Returns color from output color. Clamps invalid rgb output values to range [0.0, 1.0]. + /// \param color01 Rgb color (range 0-1 is assumed). + static QColor getColorFromOutputColor(std::array color01); + + /// Returns transform key for transformation between various color spaces + static QByteArray getTransformColorSpaceKey(const ColorSpaceTransformParams& params); + + cmsHTRANSFORM getTransformBetweenColorSpaces(const ColorSpaceTransformParams& params) const; + + const PDFCMSManager* m_manager; + PDFCMSSettings m_settings; + QColor m_paperColor; + std::array m_profiles; + + mutable QReadWriteLock m_transformationCacheLock; + mutable std::unordered_map m_transformationCache; + + mutable QReadWriteLock m_customIccProfileCacheLock; + mutable std::map, cmsHTRANSFORM> m_customIccProfileCache; + + mutable QReadWriteLock m_transformColorSpaceCacheLock; + mutable std::map m_transformColorSpaceCache; +}; + +bool PDFLittleCMS::fillRGBBufferFromDeviceGray(const std::vector& colors, + RenderingIntent intent, + unsigned char* outputBuffer, + PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(Gray, getEffectiveRenderingIntent(intent), true); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed.")); + return false; + } + + if (cmsGetTransformInputFormat(transform) == TYPE_GRAY_FLT) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); + cmsDoTransform(transform, colors.data(), outputBuffer, static_cast(colors.size())); + return true; + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed - invalid data format.")); + } + + return false; +} + +bool PDFLittleCMS::fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(RGB, getEffectiveRenderingIntent(intent), true); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed.")); + return false; + } + + const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform); + if (inputFormat == TYPE_RGB_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); + cmsDoTransform(transform, colors.data(), outputBuffer, static_cast(colors.size()) / T_CHANNELS(inputFormat)); + return true; + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed - invalid data format.")); + } + + return false; +} + +bool PDFLittleCMS::fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(CMYK, getEffectiveRenderingIntent(intent), true); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed.")); + return false; + } + + const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform); + if (inputFormat == TYPE_CMYK_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); + std::vector fixedColors = colors; + for (size_t i = 0, count = fixedColors.size(); i < count; ++i) + { + fixedColors[i] = fixedColors[i] * 100.0f; + } + cmsDoTransform(transform, fixedColors.data(), outputBuffer, static_cast(colors.size()) / T_CHANNELS(inputFormat)); + return true; + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed - invalid data format.")); + } + + return false; +} + +bool PDFLittleCMS::fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(XYZ, getEffectiveRenderingIntent(intent), true); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed.")); + return false; + } + + const cmsUInt32Number inputFormat = cmsGetTransformInputFormat(transform); + if (inputFormat == TYPE_XYZ_FLT && (colors.size()) % T_CHANNELS(inputFormat) == 0) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_8); + + PDFColorComponentMatrix_3x3 adaptationMatrix = PDFChromaticAdaptationXYZ::createWhitepointChromaticAdaptation(getDefaultXYZWhitepoint(), whitePoint, m_settings.colorAdaptationXYZ); + std::vector fixedColors = colors; + + const size_t count = fixedColors.size() / 3; + for (size_t i = 0; i < count; ++i) + { + const size_t indexX = i * 3; + const size_t indexY = i * 3 + 1; + const size_t indexZ = i * 3 + 2; + const PDFColor3 sourceXYZ = { fixedColors[indexX], fixedColors[indexY], fixedColors[indexZ] }; + const PDFColor3 adaptedXYZ = adaptationMatrix * sourceXYZ; + fixedColors[indexX] = adaptedXYZ[0]; + fixedColors[indexY] = adaptedXYZ[1]; + fixedColors[indexZ] = adaptedXYZ[2]; + } + + cmsDoTransform(transform, fixedColors.data(), outputBuffer, static_cast(colors.size()) / T_CHANNELS(inputFormat)); + return true; + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed - invalid data format.")); + } + + return false; +} + +bool PDFLittleCMS::fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransformFromICCProfile(iccData, iccID, renderingIntent, true); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed.")); + return false; + } + + const cmsUInt32Number format = cmsGetTransformInputFormat(transform); + const cmsUInt32Number channels = T_CHANNELS(format); + const cmsUInt32Number colorSpace = T_COLORSPACE(format); + const bool isCMYK = colorSpace == PT_CMYK; + const float* inputColors = colors.data(); + std::vector cmykColors; + + if (isCMYK) + { + cmykColors = colors; + for (size_t i = 0; i < cmykColors.size(); ++i) + { + cmykColors[i] = cmykColors[i] * 100.0f; + } + inputColors = cmykColors.data(); + } + + if (colors.size() % channels == 0) + { + const cmsUInt32Number pixels = static_cast(colors.size()) / channels; + cmsDoTransform(transform, inputColors, outputBuffer, pixels); + return true; + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed - invalid data format.")); + } + + return false; +} + +bool PDFLittleCMS::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& params) const +{ + PDFCMS::ColorSpaceTransformParams transformedParams = params; + transformedParams.intent = getEffectiveRenderingIntent(transformedParams.intent); + + cmsHTRANSFORM transform = getTransformBetweenColorSpaces(transformedParams); + if (!transform) + { + return false; + } + + const cmsUInt32Number inputProfileFormat = cmsGetTransformInputFormat(transform); + const cmsUInt32Number inputChannels = T_CHANNELS(inputProfileFormat); + const cmsUInt32Number inputColorSpace = T_COLORSPACE(inputProfileFormat); + const bool isInputCMYK = inputColorSpace == PT_CMYK; + const float* inputColors = params.input.begin(); + std::vector cmykColors; + + const cmsUInt32Number outputProfileFormat = cmsGetTransformOutputFormat(transform); + const cmsUInt32Number outputChannels = T_CHANNELS(outputProfileFormat); + const cmsUInt32Number outputColorSpace = T_COLORSPACE(outputProfileFormat); + const bool isOutputCMYK = outputColorSpace == PT_CMYK; + + if (isInputCMYK) + { + cmykColors = std::vector(params.input.cbegin(), params.input.cend()); + for (size_t i = 0; i < cmykColors.size(); ++i) + { + cmykColors[i] = cmykColors[i] * 100.0f; + } + inputColors = cmykColors.data(); + } + + const cmsUInt32Number inputPixelCount = static_cast(params.input.size()) / inputChannels; + const cmsUInt32Number outputPixelCount = static_cast(params.output.size()) / outputChannels; + + if (params.input.size() % inputChannels == 0 && + params.output.size() % outputChannels == 0 && + inputPixelCount == outputPixelCount) + { + PDFColorBuffer outputBuffer = params.output; + + if (inputPixelCount > params.multithreadingThreshold) + { + struct TransformInfo + { + inline TransformInfo(const float* source, float* target, cmsUInt32Number pixelCount) : + source(source), + target(target), + pixelCount(pixelCount) + { + + } + + const float* source = nullptr; + float* target = nullptr; + cmsUInt32Number pixelCount = 0; + }; + + const cmsUInt32Number blockSize = 4096; + std::vector infos; + infos.reserve(inputPixelCount / blockSize + 1); + + const float* sourcePointer = inputColors; + float* targetPointer = outputBuffer.begin(); + + cmsUInt32Number remainingPixelCount = inputPixelCount; + while (remainingPixelCount > 0) + { + const cmsUInt32Number currentPixelCount = qMin(blockSize, remainingPixelCount); + infos.emplace_back(sourcePointer, targetPointer, currentPixelCount); + sourcePointer += currentPixelCount * inputChannels; + targetPointer += currentPixelCount * outputChannels; + remainingPixelCount -= currentPixelCount; + } + + auto processEntry = [transform](const TransformInfo& info) + { + cmsDoTransform(transform, info.source, info.target, info.pixelCount); + }; + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, infos.begin(), infos.end(), processEntry); + } + else + { + // Single-threaded transform + cmsDoTransform(transform, inputColors, outputBuffer.begin(), inputPixelCount); + } + + if (isOutputCMYK) + { + const PDFColorComponent colorQuotient = 1.0f / 100.0f; + for (PDFColorComponent& color : outputBuffer) + { + color *= colorQuotient; + } + } + + return true; + } + else + { + return false; + } + + return false; +} + +PDFLittleCMS::PDFLittleCMS(const PDFCMSManager* manager, const PDFCMSSettings& settings) : + m_manager(manager), + m_settings(settings), + m_paperColor(Qt::white), + m_profiles() +{ + static const int installed = installCmsPlugins(); + Q_UNUSED(installed); + + init(); +} + +PDFLittleCMS::~PDFLittleCMS() +{ + for (const auto& transformItem : m_transformationCache) + { + cmsHTRANSFORM transform = transformItem.second; + if (transform) + { + cmsDeleteTransform(transform); + } + } + + for (const auto& transformItem : m_customIccProfileCache) + { + cmsHTRANSFORM transform = transformItem.second; + if (transform) + { + cmsDeleteTransform(transform); + } + } + + for (const auto& transformItem : m_transformColorSpaceCache) + { + cmsHTRANSFORM transform = transformItem.second; + if (transform) + { + cmsDeleteTransform(transform); + } + } + + for (cmsHPROFILE profile : m_profiles) + { + if (profile) + { + cmsCloseProfile(profile); + } + } +} + +bool PDFLittleCMS::isCompatible(const PDFCMSSettings& settings) const +{ + return m_settings == settings; +} + +QColor PDFLittleCMS::getPaperColor() const +{ + return m_paperColor; +} + +QColor PDFLittleCMS::getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(Gray, getEffectiveRenderingIntent(intent), false); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed.")); + return QColor(); + } + + if (cmsGetTransformInputFormat(transform) == TYPE_GRAY_FLT && color.size() == 1) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); + + const float grayColor = color[0]; + std::array rgbOutputColor = { }; + cmsDoTransform(transform, &grayColor, rgbOutputColor.data(), 1); + return getColorFromOutputColor(rgbOutputColor); + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from gray to output device using CMS failed - invalid data format.")); + } + + return QColor(); +} + +QColor PDFLittleCMS::getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(RGB, getEffectiveRenderingIntent(intent), false); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed.")); + return QColor(); + } + + if (cmsGetTransformInputFormat(transform) == TYPE_RGB_FLT && color.size() == 3) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); + + std::array rgbInputColor = { color[0], color[1], color[2] }; + std::array rgbOutputColor = { }; + cmsDoTransform(transform, rgbInputColor.data(), rgbOutputColor.data(), 1); + return getColorFromOutputColor(rgbOutputColor); + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from RGB to output device using CMS failed - invalid data format.")); + } + + return QColor(); +} + +QColor PDFLittleCMS::getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(CMYK, getEffectiveRenderingIntent(intent), false); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed.")); + return QColor(); + } + + if (cmsGetTransformInputFormat(transform) == TYPE_CMYK_FLT && color.size() == 4) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); + + std::array cmykInputColor = { color[0] * 100.0f, color[1] * 100.0f, color[2] * 100.0f, color[3] * 100.0f }; + std::array rgbOutputColor = { }; + cmsDoTransform(transform, cmykInputColor.data(), rgbOutputColor.data(), 1); + return getColorFromOutputColor(rgbOutputColor); + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from CMYK to output device using CMS failed - invalid data format.")); + } + + return QColor(); +} + +QColor PDFLittleCMS::getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransform(XYZ, getEffectiveRenderingIntent(intent), false); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed.")); + return QColor(); + } + + if (cmsGetTransformInputFormat(transform) == TYPE_XYZ_FLT && color.size() == 3) + { + Q_ASSERT(cmsGetTransformOutputFormat(transform) == TYPE_RGB_FLT); + + const PDFColorComponentMatrix_3x3 adaptationMatrix = PDFChromaticAdaptationXYZ::createWhitepointChromaticAdaptation(getDefaultXYZWhitepoint(), whitePoint, m_settings.colorAdaptationXYZ); + const PDFColor3 xyzInputColor = adaptationMatrix * color; + std::array rgbOutputColor = { }; + cmsDoTransform(transform, xyzInputColor.data(), rgbOutputColor.data(), 1); + return getColorFromOutputColor(rgbOutputColor); + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from XYZ to output device using CMS failed - invalid data format.")); + } + + return QColor(); +} + +cmsHTRANSFORM PDFLittleCMS::getTransformFromICCProfile(const QByteArray& iccData, const QByteArray& iccID, RenderingIntent renderingIntent, bool isRGB888Buffer) const +{ + RenderingIntent effectiveRenderingIntent = getEffectiveRenderingIntent(renderingIntent); + const auto key = std::make_pair(iccID + (isRGB888Buffer ? "RGB_888" : "FLT"), effectiveRenderingIntent); + QReadLocker lock(&m_customIccProfileCacheLock); + auto it = m_customIccProfileCache.find(key); + if (it == m_customIccProfileCache.cend()) + { + lock.unlock(); + QWriteLocker writeLock(&m_customIccProfileCacheLock); + + // Now, we have locked cache for writing. We must find out, + // if some other thread doesn't created the transformation already. + it = m_customIccProfileCache.find(key); + if (it == m_customIccProfileCache.cend()) + { + cmsHTRANSFORM transform = cmsHTRANSFORM(); + cmsHPROFILE profile = cmsOpenProfileFromMem(iccData.data(), iccData.size()); + if (profile) + { + if (const cmsUInt32Number inputDataFormat = getProfileDataFormat(profile)) + { + cmsUInt32Number lcmsIntent = getLittleCMSRenderingIntent(effectiveRenderingIntent); + + if (isSoftProofing()) + { + cmsHPROFILE proofingProfile = m_profiles[SoftProofing]; + RenderingIntent proofingIntent = m_settings.proofingIntent; + if (m_settings.proofingIntent == RenderingIntent::Auto) + { + proofingIntent = effectiveRenderingIntent; + } + + transform = cmsCreateProofingTransform(profile, inputDataFormat, m_profiles[Output], isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, proofingProfile, + lcmsIntent, getLittleCMSRenderingIntent(proofingIntent), getTransformationFlags()); + } + else + { + transform = cmsCreateTransform(profile, inputDataFormat, m_profiles[Output], isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, lcmsIntent, getTransformationFlags()); + } + } + cmsCloseProfile(profile); + } + + it = m_customIccProfileCache.insert(std::make_pair(key, transform)).first; + } + + return it->second; + } + else + { + return it->second; + } + + return cmsHTRANSFORM(); +} + +QColor PDFLittleCMS::getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const +{ + cmsHTRANSFORM transform = getTransformFromICCProfile(iccData, iccID, renderingIntent, false); + + if (!transform) + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed.")); + return QColor(); + } + + std::array inputBuffer = { }; + const cmsUInt32Number format = cmsGetTransformInputFormat(transform); + const cmsUInt32Number channels = T_CHANNELS(format); + const cmsUInt32Number colorSpace = T_COLORSPACE(format); + const bool isCMYK = colorSpace == PT_CMYK; + if (channels == color.size() && channels <= inputBuffer.size()) + { + for (size_t i = 0; i < color.size(); ++i) + { + inputBuffer[i] = isCMYK ? color[i] * 100.0f : color[i]; + } + + std::array rgbOutputColor = { }; + cmsDoTransform(transform, inputBuffer.data(), rgbOutputColor.data(), 1); + return getColorFromOutputColor(rgbOutputColor); + } + else + { + reporter->reportRenderErrorOnce(RenderErrorType::Error, PDFTranslationContext::tr("Conversion from icc profile space to output device using CMS failed - invalid data format.")); + } + + return QColor(); +} + +void PDFLittleCMS::init() +{ + // Jakub Melka: initialize all color profiles + m_profiles[Output] = createProfile(m_settings.outputCS, m_manager->getOutputProfiles(), false); + m_profiles[Gray] = createProfile(m_settings.deviceGray, m_manager->getGrayProfiles(), m_settings.isConsiderOutputIntent); + m_profiles[RGB] = createProfile(m_settings.deviceRGB, m_manager->getRGBProfiles(), m_settings.isConsiderOutputIntent); + m_profiles[CMYK] = createProfile(m_settings.deviceCMYK, m_manager->getCMYKProfiles(), m_settings.isConsiderOutputIntent); + m_profiles[SoftProofing] = createProfile(m_settings.softProofingProfile, m_manager->getCMYKProfiles(), false); + m_profiles[XYZ] = cmsCreateXYZProfile(); + + cmsUInt16Number outOfGamutR = m_settings.outOfGamutColor.redF() * 0xFFFF; + cmsUInt16Number outOfGamutG = m_settings.outOfGamutColor.greenF() * 0xFFFF; + cmsUInt16Number outOfGamutB = m_settings.outOfGamutColor.blueF() * 0xFFFF; + + cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = { outOfGamutR, outOfGamutG, outOfGamutB }; + cmsSetAlarmCodes(alarmCodes); + + if (m_settings.isWhitePaperColorTransformed) + { + m_paperColor = getColorFromDeviceRGB(PDFColor(1.0f, 1.0f, 1.0f), RenderingIntent::AbsoluteColorimetric, nullptr); + + // We must check color of the paper, it can be invalid, if error occurs... + if (!m_paperColor.isValid()) + { + m_paperColor = QColor(Qt::white); + } + } + + // 64 should be enough, because we can have 4 input color spaces (gray, RGB, CMYK and XYZ), + // and 4 rendering intents. We have 4 * 4 = 16 input tables, so 64 will suffice enough + // (because we then have 25% load factor). + m_transformationCache.reserve(64); +} + +int PDFLittleCMS::installCmsPlugins() +{ + static cmsPluginOptimization optimizationPlugin = { }; + optimizationPlugin.base.Magic = cmsPluginMagicNumber; + optimizationPlugin.base.Type = cmsPluginOptimizationSig; + optimizationPlugin.base.Next = nullptr; + optimizationPlugin.base.ExpectedVersion = LCMS_VERSION; + optimizationPlugin.OptimizePtr = &PDFLittleCMS::optimizePipeline; + + cmsPlugin(&optimizationPlugin); + + return 0; +} + +cmsBool PDFLittleCMS::optimizePipeline(cmsPipeline** Lut, cmsUInt32Number Intent, cmsUInt32Number* InputFormat, cmsUInt32Number* OutputFormat, cmsUInt32Number* dwFlags) +{ + if (!(*dwFlags & cmsFLAGS_LOWRESPRECALC)) + { + // Optimize only on low resolution precalculation + return FALSE; + } + + Q_UNUSED(Intent); + + // We will find, if we can optimize... + bool shouldOptimize = false; + for (auto stage = cmsPipelineGetPtrToFirstStage(*Lut); stage; stage = cmsStageNext(stage)) + { + if (cmsStageType(stage) == cmsSigCurveSetElemType) + { + _cmsStageToneCurvesData* data = reinterpret_cast<_cmsStageToneCurvesData*>(cmsStageData(stage)); + for (cmsUInt32Number i = 0; i < data->nCurves; ++i) + { + const cmsToneCurve* curve = data->TheCurves[i]; + const cmsInt32Number type = cmsGetToneCurveParametricType(curve); + + if (type != 0 && !cmsIsToneCurveMultisegment(curve)) + { + shouldOptimize = true; + } + } + } + } + + if (shouldOptimize) + { + cmsContext contextId = cmsGetPipelineContextID(*Lut); + cmsPipeline* pipeline = cmsPipelineAlloc(contextId, T_CHANNELS(*InputFormat), T_CHANNELS(*OutputFormat)); + if (!pipeline) + { + return FALSE; + } + + for (auto stage = cmsPipelineGetPtrToFirstStage(*Lut); stage; stage = cmsStageNext(stage)) + { + if (cmsStageType(stage) == cmsSigCurveSetElemType) + { + _cmsStageToneCurvesData* data = reinterpret_cast<_cmsStageToneCurvesData*>(cmsStageData(stage)); + std::vector curves(data->nCurves, nullptr); + + for (cmsUInt32Number i = 0; i < data->nCurves; ++i) + { + const cmsToneCurve* curve = data->TheCurves[i]; + const cmsInt32Number type = cmsGetToneCurveParametricType(curve); + + if (type != 0 && !cmsIsToneCurveMultisegment(curve)) + { + std::array segments = { }; + + const cmsFloat64Number* params = cmsGetToneCurveParams(curve); + + cmsCurveSegment& s1 = segments[0]; + cmsCurveSegment& s2 = segments[1]; + cmsCurveSegment& s3 = segments[2]; + + const cmsFloat32Number eps = cmsFloat32Number(1e-5); + const cmsFloat32Number low = 0.0f - eps; + const cmsFloat32Number high = 1.0f + eps; + + s1.Type = type; + s1.nGridPoints = 0; + s1.SampledPoints = nullptr; + std::copy(params, params + (sizeof(s1.Params) / sizeof(*s1.Params)), s1.Params); + s1.x0 = std::numeric_limits::min(); + s1.x1 = low; + + const cmsUInt32Number gridPoints = 1024; + const cmsFloat32Number factor = 1.0f / cmsFloat32Number(gridPoints - 1); + + s2.Type = 0; + s2.nGridPoints = gridPoints; + s2.SampledPoints = static_cast(_cmsCalloc(contextId, gridPoints, sizeof(*s2.SampledPoints))); + std::copy(params, params + (sizeof(s2.Params) / sizeof(*s2.Params)), s2.Params); + s2.x0 = low; + s2.x1 = high; + + for (cmsUInt32Number i = 0; i < gridPoints; ++i) + { + const cmsFloat32Number x = i * factor; + s2.SampledPoints[i] = cmsEvalToneCurveFloat(curve, interpolate(x, 0.0, 1.0, low, high)); + } + + s3.Type = type; + s3.nGridPoints = 0; + s3.SampledPoints = nullptr; + std::copy(params, params + (sizeof(s3.Params) / sizeof(*s3.Params)), s3.Params); + s3.x0 = high; + s3.x1 = std::numeric_limits::max(); + + curves[i] = cmsBuildSegmentedToneCurve(contextId, cmsUInt32Number(segments.size()), segments.data()); + + _cmsFree(contextId, s2.SampledPoints); + } + else + { + curves[i] = cmsDupToneCurve(curve); + } + } + + cmsStageAllocToneCurves(contextId, cmsFloat32Number(curves.size()), curves.data()); + + for (cmsToneCurve* curve : curves) + { + cmsFreeToneCurve(curve); + } + } + else + { + cmsPipelineInsertStage(pipeline, cmsAT_END, cmsStageDup(stage)); + } + } + + cmsPipelineFree(*Lut); + *Lut = pipeline; + } + + return FALSE; +} + +bool PDFLittleCMS::isSoftProofing() const +{ + return (m_settings.isSoftProofing || m_settings.isGamutChecking) && m_profiles[SoftProofing]; +} + +cmsHPROFILE PDFLittleCMS::createProfile(const QString& id, const PDFColorProfileIdentifiers& profileDescriptors, bool preferOutputProfile) const +{ + auto it = std::find_if(profileDescriptors.cbegin(), profileDescriptors.cend(), [&id](const PDFColorProfileIdentifier& identifier) { return identifier.id == id; }); + if (preferOutputProfile && it != profileDescriptors.end()) + { + const PDFColorProfileIdentifier& identifier = *it; + if (!identifier.isOutputIntentProfile) + { + // Find first output intent color profile + auto itOutputIntentColorProfile = std::find_if(profileDescriptors.cbegin(), profileDescriptors.cend(), [](const PDFColorProfileIdentifier& identifier) { return identifier.isOutputIntentProfile; }); + if (itOutputIntentColorProfile != profileDescriptors.end()) + { + it = itOutputIntentColorProfile; + } + } + } + + if (it != profileDescriptors.cend()) + { + const PDFColorProfileIdentifier& identifier = *it; + switch (identifier.type) + { + case PDFColorProfileIdentifier::Type::Gray: + { + cmsCIExyY whitePoint{ }; + if (cmsWhitePointFromTemp(&whitePoint, identifier.temperature)) + { + cmsToneCurve* gammaCurve = cmsBuildGamma(cmsContext(), identifier.gamma); + cmsHPROFILE profile = cmsCreateGrayProfile(&whitePoint, gammaCurve); + cmsFreeToneCurve(gammaCurve); + return profile; + } + break; + } + + case PDFColorProfileIdentifier::Type::sRGB: + return cmsCreate_sRGBProfile(); + + case PDFColorProfileIdentifier::Type::RGB: + { + cmsCIExyY whitePoint{ }; + if (cmsWhitePointFromTemp(&whitePoint, identifier.temperature)) + { + cmsCIExyYTRIPLE primaries; + primaries.Red = { identifier.primaryR.x(), identifier.primaryR.y(), 1.0 }; + primaries.Green = { identifier.primaryG.x(), identifier.primaryG.y(), 1.0 }; + primaries.Blue = { identifier.primaryB.x(), identifier.primaryB.y(), 1.0 }; + cmsToneCurve* gammaCurve = cmsBuildGamma(cmsContext(), identifier.gamma); + cmsToneCurve* toneCurves[3] = { gammaCurve, cmsDupToneCurve(gammaCurve), cmsDupToneCurve(gammaCurve) }; + cmsHPROFILE profile = cmsCreateRGBProfile(&whitePoint, &primaries, toneCurves); + cmsFreeToneCurveTriple(toneCurves); + return profile; + } + break; + } + case PDFColorProfileIdentifier::Type::FileGray: + case PDFColorProfileIdentifier::Type::FileRGB: + case PDFColorProfileIdentifier::Type::FileCMYK: + { + QFile file(identifier.id); + if (file.open(QFile::ReadOnly)) + { + QByteArray fileContent = file.readAll(); + file.close(); + + return cmsOpenProfileFromMem(fileContent.data(), fileContent.size()); + } + + break; + } + + case PDFColorProfileIdentifier::Type::MemoryGray: + case PDFColorProfileIdentifier::Type::MemoryRGB: + case PDFColorProfileIdentifier::Type::MemoryCMYK: + return cmsOpenProfileFromMem(identifier.profileMemoryData.data(), identifier.profileMemoryData.size()); + + default: + Q_ASSERT(false); + break; + } + } + + return cmsHPROFILE(); +} + +cmsHTRANSFORM PDFLittleCMS::getTransform(Profile profile, RenderingIntent intent, bool isRGB888Buffer) const +{ + const int key = getCacheKey(profile, intent, isRGB888Buffer); + + QReadLocker lock(&m_transformationCacheLock); + auto it = m_transformationCache.find(key); + if (it == m_transformationCache.cend()) + { + lock.unlock(); + QWriteLocker writeLock(&m_transformationCacheLock); + + // Now, we have locked cache for writing. We must find out, + // if some other thread doesn't created the transformation already. + it = m_transformationCache.find(key); + if (it == m_transformationCache.cend()) + { + cmsHTRANSFORM transform = cmsHTRANSFORM(); + cmsHPROFILE input = m_profiles[profile]; + cmsHPROFILE output = m_profiles[Output]; + + if (input && output) + { + if (isSoftProofing()) + { + cmsHPROFILE proofingProfile = m_profiles[SoftProofing]; + RenderingIntent proofingIntent = m_settings.proofingIntent; + if (m_settings.proofingIntent == RenderingIntent::Auto) + { + proofingIntent = intent; + } + + transform = cmsCreateProofingTransform(input, getProfileDataFormat(input), output, isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, proofingProfile, + getLittleCMSRenderingIntent(intent), getLittleCMSRenderingIntent(proofingIntent), getTransformationFlags()); + } + else + { + transform = cmsCreateTransform(input, getProfileDataFormat(input), output, isRGB888Buffer ? TYPE_RGB_8 : TYPE_RGB_FLT, getLittleCMSRenderingIntent(intent), getTransformationFlags()); + } + } + + it = m_transformationCache.insert(std::make_pair(key, transform)).first; + } + + // We must return it here to avoid race condition (after current block, + // lock is not locked, because we unlocked lock for reading). + return it->second; + } + + return it->second; +} + +cmsUInt32Number PDFLittleCMS::getTransformationFlags() const +{ + // Flag cmsFLAGS_NONEGATIVES is used here to avoid invalid transformation + // between CMYK color space and RGB color space in the Ghent output suite examples. + cmsUInt32Number flags = cmsFLAGS_NOCACHE | cmsFLAGS_NONEGATIVES; + + if (m_settings.isBlackPointCompensationActive) + { + flags |= cmsFLAGS_BLACKPOINTCOMPENSATION; + } + + switch (m_settings.accuracy) + { + case PDFCMSSettings::Accuracy::Low: + flags |= cmsFLAGS_LOWRESPRECALC; + break; + + case PDFCMSSettings::Accuracy::Medium: + break; + + case PDFCMSSettings::Accuracy::High: + flags |= cmsFLAGS_HIGHRESPRECALC; + break; + + default: + Q_ASSERT(false); + break; + } + + if (m_settings.isGamutChecking) + { + flags |= cmsFLAGS_GAMUTCHECK; + } + + if (m_settings.isSoftProofing) + { + flags |= cmsFLAGS_SOFTPROOFING; + } + + return flags; +} + +RenderingIntent PDFLittleCMS::getEffectiveRenderingIntent(RenderingIntent intent) const +{ + if (m_settings.intent != RenderingIntent::Auto) + { + return m_settings.intent; + } + + return intent; +} + +cmsUInt32Number PDFLittleCMS::getLittleCMSRenderingIntent(RenderingIntent intent) +{ + switch (intent) + { + case RenderingIntent::Perceptual: + return INTENT_PERCEPTUAL; + + case RenderingIntent::AbsoluteColorimetric: + return INTENT_ABSOLUTE_COLORIMETRIC; + + case RenderingIntent::RelativeColorimetric: + return INTENT_RELATIVE_COLORIMETRIC; + + case RenderingIntent::Saturation: + return INTENT_SATURATION; + + default: + Q_ASSERT(false); + break; + } + + return INTENT_PERCEPTUAL; +} + +cmsUInt32Number PDFLittleCMS::getProfileDataFormat(cmsHPROFILE profile) +{ + cmsColorSpaceSignature signature = cmsGetColorSpace(profile); + switch (signature) + { + case cmsSigGrayData: + return TYPE_GRAY_FLT; + + case cmsSigRgbData: + return TYPE_RGB_FLT; + + case cmsSigCmykData: + return TYPE_CMYK_FLT; + + case cmsSigXYZData: + return TYPE_XYZ_FLT; + + default: + break; + } + + return 0; +} + +QColor PDFLittleCMS::getColorFromOutputColor(std::array color01) +{ + QColor color(QColor::Rgb); + color.setRgbF(qBound(0.0f, color01[0], 1.0f), qBound(0.0f, color01[1], 1.0f), qBound(0.0f, color01[2], 1.0f)); + return color; +} + +QByteArray PDFLittleCMS::getTransformColorSpaceKey(const PDFCMS::ColorSpaceTransformParams& params) +{ + QByteArray key; + + QBuffer buffer(&key); + buffer.open(QBuffer::WriteOnly); + + QDataStream stream(&buffer); + stream << params.sourceType; + stream << params.sourceIccId; + stream << params.targetType; + stream << params.targetIccId; + stream << params.intent; + + buffer.close(); + + return key; +} + +cmsHTRANSFORM PDFLittleCMS::getTransformBetweenColorSpaces(const PDFCMS::ColorSpaceTransformParams& params) const +{ + QByteArray key = getTransformColorSpaceKey(params); + QReadLocker lock(&m_transformColorSpaceCacheLock); + auto it = m_transformColorSpaceCache.find(key); + if (it == m_transformColorSpaceCache.cend()) + { + lock.unlock(); + QWriteLocker writeLock(&m_transformColorSpaceCacheLock); + + // Now, we have locked cache for writing. We must find out, + // if some other thread doesn't created the transformation already. + it = m_transformColorSpaceCache.find(key); + if (it == m_transformColorSpaceCache.cend()) + { + cmsHPROFILE inputProfile = cmsHPROFILE(); + cmsHPROFILE outputProfile = cmsHPROFILE(); + cmsHTRANSFORM transform = cmsHTRANSFORM(); + + switch (params.sourceType) + { + case ColorSpaceType::DeviceGray: + inputProfile = m_profiles[Gray]; + break; + + case ColorSpaceType::DeviceRGB: + inputProfile = m_profiles[RGB]; + break; + + case ColorSpaceType::DeviceCMYK: + inputProfile = m_profiles[CMYK]; + break; + + case ColorSpaceType::XYZ: + inputProfile = m_profiles[XYZ]; + break; + + case ColorSpaceType::ICC: + inputProfile = cmsOpenProfileFromMem(params.sourceIccData.data(), params.sourceIccData.size()); + break; + + default: + Q_ASSERT(false); + break; + } + + switch (params.targetType) + { + case ColorSpaceType::DeviceGray: + outputProfile = m_profiles[Gray]; + break; + + case ColorSpaceType::DeviceRGB: + outputProfile = m_profiles[RGB]; + break; + + case ColorSpaceType::DeviceCMYK: + outputProfile = m_profiles[CMYK]; + break; + + case ColorSpaceType::XYZ: + outputProfile = m_profiles[XYZ]; + break; + + case ColorSpaceType::ICC: + outputProfile = cmsOpenProfileFromMem(params.targetIccData.data(), params.targetIccData.size()); + break; + + default: + Q_ASSERT(false); + break; + } + + if (inputProfile && outputProfile) + { + transform = cmsCreateTransform(inputProfile, getProfileDataFormat(inputProfile), outputProfile, getProfileDataFormat(outputProfile), getLittleCMSRenderingIntent(params.intent), getTransformationFlags()); + } + + if (params.sourceType == ColorSpaceType::ICC) + { + cmsCloseProfile(inputProfile); + } + + if (params.targetType == ColorSpaceType::ICC) + { + cmsCloseProfile(outputProfile); + } + + it = m_transformColorSpaceCache.insert(std::make_pair(key, transform)).first; + } + + return it->second; + } + else + { + return it->second; + } + + return cmsHTRANSFORM(); +} + +QString getInfoFromProfile(cmsHPROFILE profile, cmsInfoType infoType) +{ + QLocale locale; + QString country = QLocale::countryToString(locale.country()); + QString language = QLocale::languageToString(locale.language()); + + char countryCode[3] = { }; + char languageCode[3] = { }; + if (country.size() == 2) + { + countryCode[0] = country[0].toLatin1(); + countryCode[1] = country[1].toLatin1(); + } + if (language.size() == 2) + { + languageCode[0] = language[0].toLatin1(); + languageCode[1] = language[1].toLatin1(); + } + + // Jakub Melka: try to get profile info from current language/country. + // If it fails, then pick any language/any country. + cmsUInt32Number bufferSize = cmsGetProfileInfo(profile, infoType, languageCode, countryCode, nullptr, 0); + if (bufferSize) + { + std::vector buffer(bufferSize, 0); + cmsGetProfileInfo(profile, infoType, languageCode, countryCode, buffer.data(), static_cast(buffer.size())); + return QString::fromWCharArray(buffer.data()); + } + + bufferSize = cmsGetProfileInfo(profile, infoType, cmsNoLanguage, cmsNoCountry, nullptr, 0); + if (bufferSize) + { + std::vector buffer(bufferSize, 0); + cmsGetProfileInfo(profile, infoType, cmsNoLanguage, cmsNoCountry, buffer.data(), static_cast(buffer.size())); + return QString::fromWCharArray(buffer.data()); + } + + return QString(); +} + +bool PDFCMSGeneric::isCompatible(const PDFCMSSettings& settings) const +{ + return settings.system == PDFCMSSettings::System::Generic; +} + +QColor PDFCMSGeneric::getPaperColor() const +{ + return QColor(Qt::white); +} + +QColor PDFCMSGeneric::getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(color); + Q_UNUSED(intent); + Q_UNUSED(reporter); + return QColor(); +} + +QColor PDFCMSGeneric::getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(color); + Q_UNUSED(intent); + Q_UNUSED(reporter); + return QColor(); +} + +QColor PDFCMSGeneric::getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(color); + Q_UNUSED(intent); + Q_UNUSED(reporter); + return QColor(); +} + +QColor PDFCMSGeneric::getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(color); + Q_UNUSED(intent); + Q_UNUSED(reporter); + Q_UNUSED(whitePoint); + return QColor(); +} + +QColor PDFCMSGeneric::getColorFromICC(const PDFColor& color, + RenderingIntent renderingIntent, + const QByteArray& iccID, + const QByteArray& iccData, + PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(color); + Q_UNUSED(renderingIntent); + Q_UNUSED(iccID); + Q_UNUSED(iccData); + Q_UNUSED(reporter); + return QColor(); +} + +bool PDFCMSGeneric::fillRGBBufferFromDeviceGray(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(colors); + Q_UNUSED(intent); + Q_UNUSED(outputBuffer); + Q_UNUSED(reporter); + return false; +} + +bool PDFCMSGeneric::fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(colors); + Q_UNUSED(intent); + Q_UNUSED(outputBuffer); + Q_UNUSED(reporter); + return false; +} + +bool PDFCMSGeneric::fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(colors); + Q_UNUSED(intent); + Q_UNUSED(outputBuffer); + Q_UNUSED(reporter); + return false; +} + +bool PDFCMSGeneric::fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(whitePoint); + Q_UNUSED(colors); + Q_UNUSED(intent); + Q_UNUSED(outputBuffer); + Q_UNUSED(reporter); + return false; +} + +bool PDFCMSGeneric::fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(colors); + Q_UNUSED(renderingIntent); + Q_UNUSED(outputBuffer); + Q_UNUSED(iccID); + Q_UNUSED(iccData); + Q_UNUSED(reporter); + return false; +} + +bool PDFCMSGeneric::transformColorSpace(const PDFCMS::ColorSpaceTransformParams& params) const +{ + Q_UNUSED(params); + return false; +} + +PDFCMSManager::PDFCMSManager(QObject* parent) : + BaseClass(parent), + m_document(nullptr), + m_mutex(QMutex::Recursive) +{ + +} + +PDFCMSPointer PDFCMSManager::getCurrentCMS() const +{ + QMutexLocker lock(&m_mutex); + return m_CMS.get(this, &PDFCMSManager::getCurrentCMSImpl); +} + +void PDFCMSManager::setSettings(const PDFCMSSettings& settings) +{ + if (m_settings != settings) + { + // We must ensure, that mutex is not locked, while we are + // sending signal about CMS change. + { + QMutexLocker lock(&m_mutex); + m_settings = settings; + clearCache(); + } + + emit colorManagementSystemChanged(); + } +} + +const PDFColorProfileIdentifiers& PDFCMSManager::getOutputProfiles() const +{ + QMutexLocker lock(&m_mutex); + return m_outputProfiles.get(this, &PDFCMSManager::getOutputProfilesImpl); +} + +const PDFColorProfileIdentifiers& PDFCMSManager::getGrayProfiles() const +{ + QMutexLocker lock(&m_mutex); + return m_grayProfiles.get(this, &PDFCMSManager::getGrayProfilesImpl); +} + +const PDFColorProfileIdentifiers& PDFCMSManager::getRGBProfiles() const +{ + QMutexLocker lock(&m_mutex); + return m_RGBProfiles.get(this, &PDFCMSManager::getRGBProfilesImpl); +} + +const PDFColorProfileIdentifiers& PDFCMSManager::getCMYKProfiles() const +{ + QMutexLocker lock(&m_mutex); + return m_CMYKProfiles.get(this, &PDFCMSManager::getCMYKProfilesImpl); +} + +const PDFColorProfileIdentifiers& PDFCMSManager::getExternalProfiles() const +{ + // Jakub Melka: do not protect this by mutex, this function is private + // and must be called only from mutex-protected code. + return m_externalProfiles.get(this, &PDFCMSManager::getExternalProfilesImpl); +} + +PDFCMSSettings PDFCMSManager::getDefaultSettings() const +{ + PDFCMSSettings settings; + + auto getFirstProfileId = [](const PDFColorProfileIdentifiers& identifiers) + { + if (!identifiers.empty()) + { + return identifiers.front().id; + } + return QString(); + }; + + settings.system = PDFCMSSettings::System::LittleCMS2; + settings.outputCS = getFirstProfileId(getOutputProfiles()); + settings.deviceGray = getFirstProfileId(getGrayProfiles()); + settings.deviceRGB = getFirstProfileId(getRGBProfiles()); + settings.deviceCMYK = getFirstProfileId(getCMYKProfiles()); + + return settings; +} + +void PDFCMSManager::setDocument(const PDFDocument* document) +{ + std::optional lock; + lock.emplace(&m_mutex); + + if (m_document == document) + { + return; + } + + m_document = document; + + int i = 0; + PDFColorProfileIdentifiers outputIntentProfiles; + + if (m_document) + { + for (const PDFOutputIntent& outputIntent : m_document->getCatalog()->getOutputIntents()) + { + QByteArray content; + + try + { + // Try to read the profile from the output intent stream. If it fails, then do nothing. + PDFObject outputProfileObject = m_document->getObject(outputIntent.getOutputProfile()); + if (outputProfileObject.isStream()) + { + content = m_document->getDecodedStream(outputProfileObject.getStream()); + } + } + catch (PDFException) + { + continue; + } + + if (content.isEmpty()) + { + // Decoding of output profile failed. Continue + // with next output profile. + continue; + } + + cmsHPROFILE profile = cmsOpenProfileFromMem(content.data(), content.size()); + if (profile) + { + PDFColorProfileIdentifier::Type csiType = PDFColorProfileIdentifier::Type::Invalid; + const cmsColorSpaceSignature colorSpace = cmsGetColorSpace(profile); + switch (colorSpace) + { + case cmsSigGrayData: + csiType = PDFColorProfileIdentifier::Type::MemoryGray; + break; + + case cmsSigRgbData: + csiType = PDFColorProfileIdentifier::Type::MemoryRGB; + break; + + case cmsSigCmykData: + csiType = PDFColorProfileIdentifier::Type::MemoryCMYK; + break; + + default: + break; + } + + QString description = getInfoFromProfile(profile, cmsInfoDescription); + cmsCloseProfile(profile); + + // If we have a valid profile, then add it + if (csiType != PDFColorProfileIdentifier::Type::Invalid) + { + outputIntentProfiles.emplace_back(PDFColorProfileIdentifier::createOutputIntent(csiType, qMove(description), QString("@@OUTPUT_INTENT_PROFILE_%1").arg(++i), qMove(content))); + } + } + } + } + + bool outputIntentProfilesChanged = false; + if (m_outputIntentProfiles != outputIntentProfiles) + { + m_outputIntentProfiles = qMove(outputIntentProfiles); + clearCache(); + outputIntentProfilesChanged = true; + } + + if (outputIntentProfilesChanged) + { + lock = std::nullopt; + emit colorManagementSystemChanged(); + } +} + +QString PDFCMSManager::getSystemName(PDFCMSSettings::System system) +{ + switch (system) + { + case PDFCMSSettings::System::Generic: + return tr("Generic"); + + case PDFCMSSettings::System::LittleCMS2: + { + const int major = LCMS_VERSION / 1000; + const int minor = (LCMS_VERSION % 1000) / 10; + return tr("Little CMS %1.%2").arg(major).arg(minor); + } + + default: + { + Q_ASSERT(false); + break; + } + } + + return QString(); +} + +PDFCMSPointer PDFCMSManager::getCurrentCMSImpl() const +{ + switch (m_settings.system) + { + case PDFCMSSettings::System::Generic: + return PDFCMSPointer(new PDFCMSGeneric()); + + case PDFCMSSettings::System::LittleCMS2: + return PDFCMSPointer(new PDFLittleCMS(this, m_settings)); + + default: + Q_ASSERT(false); + break; + } + + return PDFCMSPointer(new PDFCMSGeneric()); +} + +void PDFCMSManager::clearCache() +{ + QMutexLocker lock(&m_mutex); + m_CMS.dirty(); + m_outputProfiles.dirty(); + m_grayProfiles.dirty(); + m_RGBProfiles.dirty(); + m_CMYKProfiles.dirty(); + m_externalProfiles.dirty(); +} + +PDFColorProfileIdentifiers PDFCMSManager::getOutputProfilesImpl() const +{ + // Currently, we only support sRGB output color profile. + return { PDFColorProfileIdentifier::createSRGB() }; +} + +PDFColorProfileIdentifiers PDFCMSManager::getGrayProfilesImpl() const +{ + // Jakub Melka: We create gray profiles for temperature 5000K, 6500K and 9300K. + // We also use linear gamma and gamma value 2.2. + PDFColorProfileIdentifiers result = + { + PDFColorProfileIdentifier::createGray(tr("Gray D65, γ = 2.2"), "@GENERIC_Gray_D65_g22", 6500.0, 2.2), + PDFColorProfileIdentifier::createGray(tr("Gray D50, γ = 2.2"), "@GENERIC_Gray_D50_g22", 5000.0, 2.2), + PDFColorProfileIdentifier::createGray(tr("Gray D93, γ = 2.2"), "@GENERIC_Gray_D93_g22", 9300.0, 2.2), + PDFColorProfileIdentifier::createGray(tr("Gray D65, γ = 1.0 (linear)"), "@GENERIC_Gray_D65_g10", 6500.0, 1.0), + PDFColorProfileIdentifier::createGray(tr("Gray D50, γ = 1.0 (linear)"), "@GENERIC_Gray_D50_g10", 5000.0, 1.0), + PDFColorProfileIdentifier::createGray(tr("Gray D93, γ = 1.0 (linear)"), "@GENERIC_Gray_D93_g10", 9300.0, 1.0) + }; + + PDFColorProfileIdentifiers externalGrayProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileGray); + result.insert(result.end(), std::make_move_iterator(externalGrayProfiles.begin()), std::make_move_iterator(externalGrayProfiles.end())); + PDFColorProfileIdentifiers outputIntentRGBProfiles = getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type::MemoryGray); + result.insert(result.end(), std::make_move_iterator(outputIntentRGBProfiles.begin()), std::make_move_iterator(outputIntentRGBProfiles.end())); + return result; +} + +PDFColorProfileIdentifiers PDFCMSManager::getRGBProfilesImpl() const +{ + // Jakub Melka: We create RGB profiles for common standards and also for + // default standard sRGB. See https://en.wikipedia.org/wiki/Color_spaces_with_RGB_primaries. + PDFColorProfileIdentifiers result = + { + PDFColorProfileIdentifier::createSRGB(), + PDFColorProfileIdentifier::createRGB(tr("HDTV (ITU-R BT.709)"), "@GENERIC_RGB_HDTV", 6500, QPointF(0.64, 0.33), QPointF(0.30, 0.60), QPointF(0.15, 0.06), 20.0 / 9.0), + PDFColorProfileIdentifier::createRGB(tr("Adobe RGB 1998"), "@GENERIC_RGB_Adobe1998", 6500, QPointF(0.64, 0.33), QPointF(0.30, 0.60), QPointF(0.15, 0.06), 563.0 / 256.0), + PDFColorProfileIdentifier::createRGB(tr("PAL / SECAM"), "@GENERIC_RGB_PalSecam", 6500, QPointF(0.64, 0.33), QPointF(0.29, 0.60), QPointF(0.15, 0.06), 14.0 / 5.0), + PDFColorProfileIdentifier::createRGB(tr("NTSC"), "@GENERIC_RGB_NTSC", 6500, QPointF(0.64, 0.34), QPointF(0.31, 0.595), QPointF(0.155, 0.07), 20.0 / 9.0), + PDFColorProfileIdentifier::createRGB(tr("Adobe Wide Gamut RGB"), "@GENERIC_RGB_AdobeWideGamut", 5000, QPointF(0.735, 0.265), QPointF(0.115, 0.826), QPointF(0.157, 0.018), 563.0 / 256.0), + PDFColorProfileIdentifier::createRGB(tr("ProPhoto RGB"), "@GENERIC_RGB_ProPhoto", 5000, QPointF(0.7347, 0.2653), QPointF(0.1596, 0.8404), QPointF(0.0366, 0.0001), 9.0 / 5.0) + }; + + PDFColorProfileIdentifiers externalRGBProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileRGB); + result.insert(result.end(), externalRGBProfiles.begin(), externalRGBProfiles.end()); + PDFColorProfileIdentifiers outputIntentRGBProfiles = getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type::MemoryRGB); + result.insert(result.end(), std::make_move_iterator(outputIntentRGBProfiles.begin()), std::make_move_iterator(outputIntentRGBProfiles.end())); + return result; +} + +PDFColorProfileIdentifiers PDFCMSManager::getCMYKProfilesImpl() const +{ + PDFColorProfileIdentifiers result; + + PDFColorProfileIdentifiers externalCMYKProfiles = getFilteredExternalProfiles(PDFColorProfileIdentifier::Type::FileCMYK); + result.insert(result.end(), externalCMYKProfiles.begin(), externalCMYKProfiles.end()); + PDFColorProfileIdentifiers outputIntentCMYKProfiles = getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type::MemoryCMYK); + result.insert(result.end(), std::make_move_iterator(outputIntentCMYKProfiles.begin()), std::make_move_iterator(outputIntentCMYKProfiles.end())); + + return result; +} + +PDFColorProfileIdentifiers PDFCMSManager::getExternalColorProfiles(QString profileDirectory) const +{ + PDFColorProfileIdentifiers result; + + QDir directory(profileDirectory); + QDir applicationDirectory(QApplication::applicationDirPath()); + if (!profileDirectory.isEmpty() && directory.exists()) + { + QStringList iccProfiles = directory.entryList({ "*.icc" }, QDir::Files | QDir::Readable | QDir::NoDotAndDotDot, QDir::NoSort); + for (const QString& fileName : iccProfiles) + { + QString filePath = QDir::cleanPath(applicationDirectory.relativeFilePath(directory.absoluteFilePath(fileName))); + + // Try to read the profile from the file. If it fails, then do nothing. + QFile file(filePath); + if (file.open(QFile::ReadOnly)) + { + QByteArray content = file.readAll(); + file.close(); + + cmsHPROFILE profile = cmsOpenProfileFromMem(content.data(), content.size()); + if (profile) + { + PDFColorProfileIdentifier::Type csiType = PDFColorProfileIdentifier::Type::Invalid; + const cmsColorSpaceSignature colorSpace = cmsGetColorSpace(profile); + switch (colorSpace) + { + case cmsSigGrayData: + csiType = PDFColorProfileIdentifier::Type::FileGray; + break; + + case cmsSigRgbData: + csiType = PDFColorProfileIdentifier::Type::FileRGB; + break; + + case cmsSigCmykData: + csiType = PDFColorProfileIdentifier::Type::FileCMYK; + break; + + default: + break; + } + + QString description = getInfoFromProfile(profile, cmsInfoDescription); + cmsCloseProfile(profile); + + // If we have a valid profile, then add it + if (csiType != PDFColorProfileIdentifier::Type::Invalid) + { + result.emplace_back(PDFColorProfileIdentifier::createFile(csiType, qMove(description), filePath)); + } + } + } + } + } + + return result; +} + +PDFColorProfileIdentifiers PDFCMSManager::getExternalProfilesImpl() const +{ + PDFColorProfileIdentifiers result; + + QStringList directories(m_settings.profileDirectory); + +#ifdef Q_OS_WIN + std::array buffer = { }; + DWORD bufferSize = DWORD(buffer.size() * sizeof(WCHAR)); + if (GetColorDirectoryW(NULL, buffer.data(), &bufferSize)) + { + const DWORD charactersWithNull = bufferSize / sizeof(WCHAR); + const DWORD charactersWithoutNull = bufferSize > 0 ? charactersWithNull - 1 : 0; + + QString directory = QString::fromWCharArray(buffer.data(), int(charactersWithoutNull)); + directories << QDir::fromNativeSeparators(directory); + } +#endif + + for (const QString& directory : directories) + { + PDFColorProfileIdentifiers externalProfiles = getExternalColorProfiles(directory); + result.insert(result.end(), externalProfiles.begin(), externalProfiles.end()); + } + + return result; +} + +PDFColorProfileIdentifiers PDFCMSManager::getFilteredExternalProfiles(PDFColorProfileIdentifier::Type type) const +{ + PDFColorProfileIdentifiers result; + const PDFColorProfileIdentifiers& externalProfiles = getExternalProfiles(); + std::copy_if(externalProfiles.cbegin(), externalProfiles.cend(), std::back_inserter(result), [type](const PDFColorProfileIdentifier& identifier) { return identifier.type == type; }); + return result; +} + +PDFColorProfileIdentifiers PDFCMSManager::getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type type) const +{ + PDFColorProfileIdentifiers result; + std::copy_if(m_outputIntentProfiles.cbegin(), m_outputIntentProfiles.cend(), std::back_inserter(result), [type](const PDFColorProfileIdentifier& identifier) { return identifier.type == type; }); + return result; +} + +PDFColorProfileIdentifier PDFColorProfileIdentifier::createGray(QString name, QString id, PDFReal temperature, PDFReal gamma) +{ + PDFColorProfileIdentifier result; + result.type = Type::Gray; + result.name = qMove(name); + result.id = qMove(id); + result.temperature = temperature; + result.gamma = gamma; + return result; +} + +PDFColorProfileIdentifier PDFColorProfileIdentifier::createSRGB() +{ + PDFColorProfileIdentifier result; + result.type = Type::sRGB; + result.name = PDFCMSManager::tr("sRGB"); + result.id = "@GENERIC_sRGB"; + return result; +} + +PDFColorProfileIdentifier PDFColorProfileIdentifier::createRGB(QString name, QString id, PDFReal temperature, QPointF primaryR, QPointF primaryG, QPointF primaryB, PDFReal gamma) +{ + PDFColorProfileIdentifier result; + result.type = Type::RGB; + result.name = qMove(name); + result.id = qMove(id); + result.temperature = temperature; + result.primaryR = primaryR; + result.primaryG = primaryG; + result.primaryB = primaryB; + result.gamma = gamma; + return result; +} + +PDFColorProfileIdentifier PDFColorProfileIdentifier::createFile(Type type, QString name, QString id) +{ + PDFColorProfileIdentifier result; + result.type = type; + result.name = qMove(name); + result.id = qMove(id); + return result; +} + +PDFColorProfileIdentifier PDFColorProfileIdentifier::createOutputIntent(PDFColorProfileIdentifier::Type type, QString name, QString id, QByteArray profileData) +{ + PDFColorProfileIdentifier result; + result.type = type; + result.name = qMove(name); + result.id = qMove(id); + result.profileMemoryData = qMove(profileData); + result.isOutputIntentProfile = true; + return result; +} + +PDFColor3 PDFCMS::getDefaultXYZWhitepoint() +{ + const cmsCIEXYZ* whitePoint = cmsD50_XYZ(); + return PDFColor3{ PDFColorComponent(whitePoint->X), PDFColorComponent(whitePoint->Y), PDFColorComponent(whitePoint->Z) }; +} + +PDFColorComponentMatrix_3x3 PDFChromaticAdaptationXYZ::createWhitepointChromaticAdaptation(const PDFColor3& targetWhitePoint, + const PDFColor3& sourceWhitePoint, + PDFCMSSettings::ColorAdaptationXYZ method) +{ + PDFColorComponentMatrix_3x3 matrix; + matrix.makeIdentity(); + + switch (method) + { + case pdf::PDFCMSSettings::ColorAdaptationXYZ::None: + // No scaling performed, just return identity matrix + break; + + case pdf::PDFCMSSettings::ColorAdaptationXYZ::XYZScaling: + matrix.makeDiagonal(std::array{ targetWhitePoint[0] / sourceWhitePoint[0], + targetWhitePoint[1] / sourceWhitePoint[1], + targetWhitePoint[2] / sourceWhitePoint[2] + }); + break; + + case pdf::PDFCMSSettings::ColorAdaptationXYZ::CAT97: + { + // CAT97 matrix, as defined in https://en.wikipedia.org/wiki/LMS_color_space + constexpr PDFColorComponentMatrix_3x3 cat97Matrix( + 0.8562f, 0.3372f, -0.1934f, + -0.8360f, 1.8327f, 0.0033f, + 0.0357f, -0.0469f, 1.0112f); + + // Inverse of CAT97 matrix (using wxMaxima to compute it) + constexpr PDFColorComponentMatrix_3x3 inverseCat97Matrix( + 0.9873999149199271f, -0.1768250198556842f, 0.1894251049357571f, + 0.4504351090445315f, 0.464932897752711f, 0.08463199320275755f, + -0.01396832510725165f, 0.027806572501434f, 0.9861617526058175f); + + PDFColor3 adaptedTargetWhitePoint = cat97Matrix * targetWhitePoint; + PDFColor3 adaptedSourceWhitePoint = cat97Matrix * sourceWhitePoint; + PDFColor3 gain = { + adaptedTargetWhitePoint[0] / adaptedSourceWhitePoint[0], + adaptedTargetWhitePoint[1] / adaptedSourceWhitePoint[1], + adaptedTargetWhitePoint[2] / adaptedSourceWhitePoint[2] + }; + + PDFColorComponentMatrix_3x3 gainMatrix; + gainMatrix.makeDiagonal(gain); + + matrix = inverseCat97Matrix * gainMatrix * cat97Matrix; + break; + } + + case pdf::PDFCMSSettings::ColorAdaptationXYZ::CAT02: + { + // CAT02 matrix, as defined in https://en.wikipedia.org/wiki/LMS_color_space + constexpr PDFColorComponentMatrix_3x3 cat02Matrix( + 0.7328f, 0.4296f, -0.1624f, + -0.7036f, 1.6975f, 0.0061f, + 0.0030f, 0.0136f, 0.9834f); + + // Inverse of CAT02 matrix (using wxMaxima to compute it) + constexpr PDFColorComponentMatrix_3x3 inverseCat02Matrix( + 1.096123820835514f, -0.2788690002182872f, 0.182745179382773f, + 0.4543690419753592f, 0.4735331543074117f, 0.0720978037172291f, + -0.009627608738429352f, -0.005698031216113419f, 1.015325639954543f); + + PDFColor3 adaptedTargetWhitePoint = cat02Matrix * targetWhitePoint; + PDFColor3 adaptedSourceWhitePoint = cat02Matrix * sourceWhitePoint; + PDFColor3 gain = { + adaptedTargetWhitePoint[0] / adaptedSourceWhitePoint[0], + adaptedTargetWhitePoint[1] / adaptedSourceWhitePoint[1], + adaptedTargetWhitePoint[2] / adaptedSourceWhitePoint[2] + }; + + PDFColorComponentMatrix_3x3 gainMatrix; + gainMatrix.makeDiagonal(gain); + + matrix = inverseCat02Matrix * gainMatrix * cat02Matrix; + break; + } + + case pdf::PDFCMSSettings::ColorAdaptationXYZ::Bradford: + { + // Bradford matrix, as defined in https://en.wikipedia.org/wiki/LMS_color_space + constexpr PDFColorComponentMatrix_3x3 bradfordMatrix( + 0.8951f, 0.2264f, -0.1614f, + -0.7502f, 1.7135f, 0.0367f, + 0.0389f, -0.0685f, 1.0296f); + + // Inverse of bradford matrix (using wxMaxima to compute it) + constexpr PDFColorComponentMatrix_3x3 inverseBradfordMatrix( + 1.004360519274085f, -0.1262294327613208f, 0.1619428982062721f, + 0.4399123264001572f, 0.527481594455384f, 0.05015858096782513f, + -0.008678739162151443f, 0.03986287311053728f, 0.9684695843590444f); + + PDFColor3 adaptedTargetWhitePoint = bradfordMatrix * targetWhitePoint; + PDFColor3 adaptedSourceWhitePoint = bradfordMatrix * sourceWhitePoint; + PDFColor3 gain = { + adaptedTargetWhitePoint[0] / adaptedSourceWhitePoint[0], + adaptedTargetWhitePoint[1] / adaptedSourceWhitePoint[1], + adaptedTargetWhitePoint[2] / adaptedSourceWhitePoint[2] + }; + + PDFColorComponentMatrix_3x3 gainMatrix; + gainMatrix.makeDiagonal(gain); + + matrix = inverseBradfordMatrix * gainMatrix * bradfordMatrix; + break; + } + + default: + Q_ASSERT(false); + break; + } + + return matrix; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfcms.h b/Pdf4QtLib/sources/pdfcms.h index 19650ca..e8e4467 100644 --- a/Pdf4QtLib/sources/pdfcms.h +++ b/Pdf4QtLib/sources/pdfcms.h @@ -1,436 +1,436 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFCMS_H -#define PDFCMS_H - -#include "pdfglobal.h" -#include "pdfcolorspaces.h" -#include "pdfexception.h" -#include "pdfutils.h" - -#include -#include - -#include - -namespace pdf -{ - -/// This simple structure stores settings for color management system, and what -/// color management system should be used. At default, two color management -/// system are available - generic (which uses default imprecise color management), -/// and CMS using engine LittleCMS2, which was written by Marti Maria, and is -/// linked as separate library. -struct PDFCMSSettings -{ - /// Type of color management system - enum class System - { - Generic, - LittleCMS2 - }; - - /// Controls accuracy of the color transformations. High accuracy - /// could mean high memory consumption, but better color accuracy, - /// low accuracy means low memory consumption and low color accuracy. - enum class Accuracy - { - Low, - Medium, - High - }; - - enum class ColorAdaptationXYZ - { - None, - XYZScaling, - CAT97, - CAT02, - Bradford - }; - - bool operator==(const PDFCMSSettings&) const = default; - - System system = System::Generic; - Accuracy accuracy = Accuracy::Medium; - RenderingIntent intent = RenderingIntent::Auto; - RenderingIntent proofingIntent = RenderingIntent::RelativeColorimetric; - ColorAdaptationXYZ colorAdaptationXYZ = ColorAdaptationXYZ::Bradford; - bool isBlackPointCompensationActive = true; - bool isWhitePaperColorTransformed = false; - bool isGamutChecking = false; - bool isSoftProofing = false; - bool isConsiderOutputIntent = true; - QColor outOfGamutColor = Qt::red; ///< Color, which marks out-of-gamut when soft-proofing is proceeded - QString outputCS; ///< Output (rendering) color space - QString deviceGray; ///< Identifiers for color space (device gray) - QString deviceRGB; ///< Identifiers for color space (device RGB) - QString deviceCMYK; ///< Identifiers for color space (device CMYK) - QString softProofingProfile; ///< Identifiers for soft proofing profile - QString profileDirectory; ///< Directory containing color profiles -}; - -/// Color management system base class. It contains functions to transform -/// colors from various color system to device color system. If color management -/// system can't handle color transform, it should return invalid color. -class PDFCMS -{ -public: - explicit inline PDFCMS() = default; - virtual ~PDFCMS() = default; - - /// This function should decide, if color management system is compatible with these - /// settings (so, it transforms colors according to this setting). If this - /// function returns false, then this color management system should be replaced - /// by newly created one, according these settings. - virtual bool isCompatible(const PDFCMSSettings& settings) const = 0; - - /// Returns color of the white paper - virtual QColor getPaperColor() const = 0; - - /// Converts color in Device Gray color space to the target device - /// color space. If error occurs, then invalid color is returned. - /// Caller then should handle this - try to convert color as accurate - /// as possible. - /// \param color Single color channel value - /// \param intent Rendering intent - /// \param reporter Render error reporter (used, when color transform fails) - virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; - - /// Converts color in Device RGB color space to the target device - /// color space. If error occurs, then invalid color is returned. - /// Caller then should handle this - try to convert color as accurate - /// as possible. - /// \param color Three color channel value (R,G,B channel) - /// \param intent Rendering intent - /// \param reporter Render error reporter (used, when color transform fails) - virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; - - /// Converts color in Device CMYK color space to the target device - /// color space. If error occurs, then invalid color is returned. - /// Caller then should handle this - try to convert color as accurate - /// as possible. - /// \param color Four color channel value (C,M,Y,K channel) - /// \param intent Rendering intent - /// \param reporter Render error reporter (used, when color transform fails) - virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; - - /// Converts color in XYZ color space to the target device - /// color space. If error occurs, then invalid color is returned. - /// Caller then should handle this - try to convert color as accurate - /// as possible. - /// \param whitePoint White point of source XYZ color space - /// \param Three color channel value (X,Y,Z channel) - /// \param intent Rendering intent - /// \param reporter Render error reporter (used, when color transform fails) - virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, - const PDFColor3& color, - RenderingIntent intent, - PDFRenderErrorReporter* reporter) const = 0; - - /// Computes color from ICC color profile - /// \param color Input color - /// \param iccID Unique ICC profile identifier - /// \param iccData Color profile data - /// \param reporter Render error reporter (used, when color transform fails) - virtual QColor getColorFromICC(const PDFColor& color, - RenderingIntent renderingIntent, - const QByteArray& iccID, - const QByteArray& iccData, - PDFRenderErrorReporter* reporter) const = 0; - - /// Fills colors in Device Gray color space to the RGB buffer. If error occurs, then false is returned. - /// Caller then should handle this - try to convert color as accurate as possible. - /// \param color Gray values - /// \param intent Rendering intent - /// \param outputBuffer Output buffer in format RGB_888 (8-bit RGB values) - /// \param reporter Render error reporter (used, when color transform fails) - virtual bool fillRGBBufferFromDeviceGray(const std::vector& colors, - RenderingIntent intent, - unsigned char* outputBuffer, - PDFRenderErrorReporter* reporter) const = 0; - - /// Fills colors in Device RGB color space to RGB buffer. If error occurs, then false is returned. - /// Caller then should handle this - try to convert color as accurate as possible. - /// \param colors Buffer with three color channels, so it has pixels * tuple(R, G, B) size - /// \param intent Rendering intent - /// \param outputBuffer Output buffer in format RGB_888 (8-bit RGB values) - /// \param reporter Render error reporter (used, when color transform fails) - virtual bool fillRGBBufferFromDeviceRGB(const std::vector& colors, - RenderingIntent intent, - unsigned char* outputBuffer, - PDFRenderErrorReporter* reporter) const = 0; - - /// Fills colors in Device CMYK color space to the RGB buffer. If error occurs, then false is returned. - /// Caller then should handle this - try to convert color as accurate as possible. - /// \param colors FBuffer with four color channels (C,M,Y,K channel), so it has pixels * tuple(C, M, Y, K) size - /// \param intent Rendering intent - /// \param outputBuffer Output buffer in format RGB_888 (8-bit RGB values) - /// \param reporter Render error reporter (used, when color transform fails) - virtual bool fillRGBBufferFromDeviceCMYK(const std::vector& colors, - RenderingIntent intent, - unsigned char* outputBuffer, - PDFRenderErrorReporter* reporter) const = 0; - - /// Fills colors in XYZ color space to the RGB buffer. If error occurs, then false is returned. - /// Caller then should handle this - try to convert color as accurate as possible. - /// \param whitePoint White point of source XYZ color space - /// \param Three color channel value (X,Y,Z channel) - /// \param intent Rendering intent - /// \param reporter Render error reporter (used, when color transform fails) - virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, - const std::vector& colors, - RenderingIntent intent, - unsigned char* outputBuffer, - PDFRenderErrorReporter* reporter) const = 0; - - /// Fills RGB buffer from ICC color profile colors - /// \param colors Input colors - /// \param iccID Unique ICC profile identifier - /// \param iccData Color profile data - /// \param reporter Render error reporter (used, when color transform fails) - virtual bool fillRGBBufferFromICC(const std::vector& colors, - RenderingIntent renderingIntent, - unsigned char* outputBuffer, - const QByteArray& iccID, - const QByteArray& iccData, - PDFRenderErrorReporter* reporter) const = 0; - - enum ColorSpaceType - { - Invalid, - DeviceGray, - DeviceRGB, - DeviceCMYK, - XYZ, - ICC - }; - - struct ColorSpaceTransformParams - { - ColorSpaceType sourceType = ColorSpaceType::Invalid; - ColorSpaceType targetType = ColorSpaceType::Invalid; - - QByteArray sourceIccId; - QByteArray targetIccId; - - QByteArray sourceIccData; - QByteArray targetIccData; - - PDFColorBuffer input; - PDFColorBuffer output; - - RenderingIntent intent = RenderingIntent::Unknown; - - PDFInteger multithreadingThreshold = 4096; - }; - - /// Transforms color between two color spaces. Doesn't do soft-proofing, - /// it just transforms two float buffers from input color space to output color space. - virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const = 0; - - /// Get D50 white point for XYZ color space - static PDFColor3 getDefaultXYZWhitepoint(); -}; - -using PDFCMSPointer = QSharedPointer; - -class PDFCMSGeneric : public PDFCMS -{ -public: - explicit inline PDFCMSGeneric() = default; - - virtual bool isCompatible(const PDFCMSSettings& settings) const override; - virtual QColor getPaperColor() const override; - virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual QColor getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromDeviceGray(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; - virtual bool fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; - virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override; -}; - -struct PDFColorProfileIdentifier -{ - enum class Type - { - Gray, - sRGB, - RGB, - FileGray, - FileRGB, - FileCMYK, - MemoryGray, - MemoryRGB, - MemoryCMYK, - Invalid - }; - - bool operator==(const PDFColorProfileIdentifier&) const = default; - bool operator!=(const PDFColorProfileIdentifier&) const = default; - - Type type = Type::sRGB; - QString name; - QString id; - PDFReal temperature = 6500.0; - QPointF primaryR; - QPointF primaryG; - QPointF primaryB; - PDFReal gamma = 1.0; - bool isOutputIntentProfile = false; - - /// Data for MemoryGray, MemoryRGB and MemoryCMYK - QByteArray profileMemoryData; - - /// Creates gray color profile identifier - /// \param name Name of color profile - /// \param id Identifier of color profile - /// \param temperature White point temperature - /// \param gamma Gamma correction - static PDFColorProfileIdentifier createGray(QString name, QString id, PDFReal temperature, PDFReal gamma); - - /// Creates sRGB color profile identifier - static PDFColorProfileIdentifier createSRGB(); - - /// Creates RGB color space identifier - /// \param name Name of color profile - /// \param id Identifier of color profile - /// \param temperature White point temperature - /// \param primaryR Primary red - /// \param primaryG Primary green - /// \param primaryB Primary blue - /// \param gamma Gamma correction - static PDFColorProfileIdentifier createRGB(QString name, QString id, PDFReal temperature, QPointF primaryR, QPointF primaryG, QPointF primaryB, PDFReal gamma); - - /// Create file color profile identifier - static PDFColorProfileIdentifier createFile(Type type, QString name, QString id); - - /// Create memory output intent color profile identifier - static PDFColorProfileIdentifier createOutputIntent(Type type, QString name, QString id, QByteArray profileData); -}; - -using PDFColorProfileIdentifiers = std::vector; - -/// Manager, that manages current color management system and also list -/// of usable input and output color profiles. It has color profiles -/// for outout device, and color profiles for input (gray/RGB/CMYK). -/// It also handles settings, and it's changes. Constant functions -/// is save to call from multiple threads, this also holds for some -/// non-constant functions - manager is protected by mutexes. -class PDF4QTLIBSHARED_EXPORT PDFCMSManager : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - explicit PDFCMSManager(QObject* parent); - - /// Returns current CMS. This function possibly creates CMS, - /// of no CMS is found. - PDFCMSPointer getCurrentCMS() const; - - const PDFCMSSettings& getSettings() const { return m_settings; } - void setSettings(const PDFCMSSettings& settings); - - const PDFColorProfileIdentifiers& getOutputProfiles() const; - const PDFColorProfileIdentifiers& getGrayProfiles() const; - const PDFColorProfileIdentifiers& getRGBProfiles() const; - const PDFColorProfileIdentifiers& getCMYKProfiles() const; - - /// Returns default color management settings - PDFCMSSettings getDefaultSettings() const; - - /// Set current document. Document is examined for output - /// rendering intents and they can be used when rendering. - /// \param document Document - void setDocument(const PDFDocument* document); - - /// Get translated name for color management system - /// \param system System - static QString getSystemName(PDFCMSSettings::System system); - -signals: - void colorManagementSystemChanged(); - -private: - /// Creates new CMS based on current settings - PDFCMSPointer getCurrentCMSImpl() const; - - /// Clear cached items - void clearCache(); - - /// This function returns external profiles. It is not protected by mutex, - /// so it is not thread-safe. For this reason, it is not in public - /// interface. - const PDFColorProfileIdentifiers& getExternalProfiles() const; - - PDFColorProfileIdentifiers getOutputProfilesImpl() const; - PDFColorProfileIdentifiers getGrayProfilesImpl() const; - PDFColorProfileIdentifiers getRGBProfilesImpl() const; - PDFColorProfileIdentifiers getCMYKProfilesImpl() const; - PDFColorProfileIdentifiers getExternalProfilesImpl() const; - - /// Returns filtered list of external profiles (list are filtered by type, - /// so, for example, only CMYK profiles are returned) - /// \param type Type of profile - PDFColorProfileIdentifiers getFilteredExternalProfiles(PDFColorProfileIdentifier::Type type) const; - - /// Returns filtered list of output intent profiles (list are filtered by type, - /// so, for example, only CMYK profiles are returned) - /// \param type Type of profile - PDFColorProfileIdentifiers getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type type) const; - - /// Gets list of color profiles from external directory - /// \param profileDirectory Directory with profiles - PDFColorProfileIdentifiers getExternalColorProfiles(QString profileDirectory) const; - - PDFCMSSettings m_settings; - const PDFDocument* m_document; - PDFColorProfileIdentifiers m_outputIntentProfiles; - - mutable QMutex m_mutex; - mutable PDFCachedItem m_CMS; - mutable PDFCachedItem m_outputProfiles; - mutable PDFCachedItem m_grayProfiles; - mutable PDFCachedItem m_RGBProfiles; - mutable PDFCachedItem m_CMYKProfiles; - mutable PDFCachedItem m_externalProfiles; -}; - -/// Class providing chromatic adaptation of whitepoints -/// using various method. -class PDFChromaticAdaptationXYZ -{ -public: - PDFChromaticAdaptationXYZ() = delete; - - static PDFColorComponentMatrix_3x3 createWhitepointChromaticAdaptation(const PDFColor3& targetWhitePoint, - const PDFColor3& sourceWhitePoint, - PDFCMSSettings::ColorAdaptationXYZ method); -}; - -} // namespace pdf - -#endif // PDFCMS_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFCMS_H +#define PDFCMS_H + +#include "pdfglobal.h" +#include "pdfcolorspaces.h" +#include "pdfexception.h" +#include "pdfutils.h" + +#include +#include + +#include + +namespace pdf +{ + +/// This simple structure stores settings for color management system, and what +/// color management system should be used. At default, two color management +/// system are available - generic (which uses default imprecise color management), +/// and CMS using engine LittleCMS2, which was written by Marti Maria, and is +/// linked as separate library. +struct PDFCMSSettings +{ + /// Type of color management system + enum class System + { + Generic, + LittleCMS2 + }; + + /// Controls accuracy of the color transformations. High accuracy + /// could mean high memory consumption, but better color accuracy, + /// low accuracy means low memory consumption and low color accuracy. + enum class Accuracy + { + Low, + Medium, + High + }; + + enum class ColorAdaptationXYZ + { + None, + XYZScaling, + CAT97, + CAT02, + Bradford + }; + + bool operator==(const PDFCMSSettings&) const = default; + + System system = System::Generic; + Accuracy accuracy = Accuracy::Medium; + RenderingIntent intent = RenderingIntent::Auto; + RenderingIntent proofingIntent = RenderingIntent::RelativeColorimetric; + ColorAdaptationXYZ colorAdaptationXYZ = ColorAdaptationXYZ::Bradford; + bool isBlackPointCompensationActive = true; + bool isWhitePaperColorTransformed = false; + bool isGamutChecking = false; + bool isSoftProofing = false; + bool isConsiderOutputIntent = true; + QColor outOfGamutColor = Qt::red; ///< Color, which marks out-of-gamut when soft-proofing is proceeded + QString outputCS; ///< Output (rendering) color space + QString deviceGray; ///< Identifiers for color space (device gray) + QString deviceRGB; ///< Identifiers for color space (device RGB) + QString deviceCMYK; ///< Identifiers for color space (device CMYK) + QString softProofingProfile; ///< Identifiers for soft proofing profile + QString profileDirectory; ///< Directory containing color profiles +}; + +/// Color management system base class. It contains functions to transform +/// colors from various color system to device color system. If color management +/// system can't handle color transform, it should return invalid color. +class PDFCMS +{ +public: + explicit inline PDFCMS() = default; + virtual ~PDFCMS() = default; + + /// This function should decide, if color management system is compatible with these + /// settings (so, it transforms colors according to this setting). If this + /// function returns false, then this color management system should be replaced + /// by newly created one, according these settings. + virtual bool isCompatible(const PDFCMSSettings& settings) const = 0; + + /// Returns color of the white paper + virtual QColor getPaperColor() const = 0; + + /// Converts color in Device Gray color space to the target device + /// color space. If error occurs, then invalid color is returned. + /// Caller then should handle this - try to convert color as accurate + /// as possible. + /// \param color Single color channel value + /// \param intent Rendering intent + /// \param reporter Render error reporter (used, when color transform fails) + virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; + + /// Converts color in Device RGB color space to the target device + /// color space. If error occurs, then invalid color is returned. + /// Caller then should handle this - try to convert color as accurate + /// as possible. + /// \param color Three color channel value (R,G,B channel) + /// \param intent Rendering intent + /// \param reporter Render error reporter (used, when color transform fails) + virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; + + /// Converts color in Device CMYK color space to the target device + /// color space. If error occurs, then invalid color is returned. + /// Caller then should handle this - try to convert color as accurate + /// as possible. + /// \param color Four color channel value (C,M,Y,K channel) + /// \param intent Rendering intent + /// \param reporter Render error reporter (used, when color transform fails) + virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const = 0; + + /// Converts color in XYZ color space to the target device + /// color space. If error occurs, then invalid color is returned. + /// Caller then should handle this - try to convert color as accurate + /// as possible. + /// \param whitePoint White point of source XYZ color space + /// \param Three color channel value (X,Y,Z channel) + /// \param intent Rendering intent + /// \param reporter Render error reporter (used, when color transform fails) + virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, + const PDFColor3& color, + RenderingIntent intent, + PDFRenderErrorReporter* reporter) const = 0; + + /// Computes color from ICC color profile + /// \param color Input color + /// \param iccID Unique ICC profile identifier + /// \param iccData Color profile data + /// \param reporter Render error reporter (used, when color transform fails) + virtual QColor getColorFromICC(const PDFColor& color, + RenderingIntent renderingIntent, + const QByteArray& iccID, + const QByteArray& iccData, + PDFRenderErrorReporter* reporter) const = 0; + + /// Fills colors in Device Gray color space to the RGB buffer. If error occurs, then false is returned. + /// Caller then should handle this - try to convert color as accurate as possible. + /// \param color Gray values + /// \param intent Rendering intent + /// \param outputBuffer Output buffer in format RGB_888 (8-bit RGB values) + /// \param reporter Render error reporter (used, when color transform fails) + virtual bool fillRGBBufferFromDeviceGray(const std::vector& colors, + RenderingIntent intent, + unsigned char* outputBuffer, + PDFRenderErrorReporter* reporter) const = 0; + + /// Fills colors in Device RGB color space to RGB buffer. If error occurs, then false is returned. + /// Caller then should handle this - try to convert color as accurate as possible. + /// \param colors Buffer with three color channels, so it has pixels * tuple(R, G, B) size + /// \param intent Rendering intent + /// \param outputBuffer Output buffer in format RGB_888 (8-bit RGB values) + /// \param reporter Render error reporter (used, when color transform fails) + virtual bool fillRGBBufferFromDeviceRGB(const std::vector& colors, + RenderingIntent intent, + unsigned char* outputBuffer, + PDFRenderErrorReporter* reporter) const = 0; + + /// Fills colors in Device CMYK color space to the RGB buffer. If error occurs, then false is returned. + /// Caller then should handle this - try to convert color as accurate as possible. + /// \param colors FBuffer with four color channels (C,M,Y,K channel), so it has pixels * tuple(C, M, Y, K) size + /// \param intent Rendering intent + /// \param outputBuffer Output buffer in format RGB_888 (8-bit RGB values) + /// \param reporter Render error reporter (used, when color transform fails) + virtual bool fillRGBBufferFromDeviceCMYK(const std::vector& colors, + RenderingIntent intent, + unsigned char* outputBuffer, + PDFRenderErrorReporter* reporter) const = 0; + + /// Fills colors in XYZ color space to the RGB buffer. If error occurs, then false is returned. + /// Caller then should handle this - try to convert color as accurate as possible. + /// \param whitePoint White point of source XYZ color space + /// \param Three color channel value (X,Y,Z channel) + /// \param intent Rendering intent + /// \param reporter Render error reporter (used, when color transform fails) + virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, + const std::vector& colors, + RenderingIntent intent, + unsigned char* outputBuffer, + PDFRenderErrorReporter* reporter) const = 0; + + /// Fills RGB buffer from ICC color profile colors + /// \param colors Input colors + /// \param iccID Unique ICC profile identifier + /// \param iccData Color profile data + /// \param reporter Render error reporter (used, when color transform fails) + virtual bool fillRGBBufferFromICC(const std::vector& colors, + RenderingIntent renderingIntent, + unsigned char* outputBuffer, + const QByteArray& iccID, + const QByteArray& iccData, + PDFRenderErrorReporter* reporter) const = 0; + + enum ColorSpaceType + { + Invalid, + DeviceGray, + DeviceRGB, + DeviceCMYK, + XYZ, + ICC + }; + + struct ColorSpaceTransformParams + { + ColorSpaceType sourceType = ColorSpaceType::Invalid; + ColorSpaceType targetType = ColorSpaceType::Invalid; + + QByteArray sourceIccId; + QByteArray targetIccId; + + QByteArray sourceIccData; + QByteArray targetIccData; + + PDFColorBuffer input; + PDFColorBuffer output; + + RenderingIntent intent = RenderingIntent::Unknown; + + PDFInteger multithreadingThreshold = 4096; + }; + + /// Transforms color between two color spaces. Doesn't do soft-proofing, + /// it just transforms two float buffers from input color space to output color space. + virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const = 0; + + /// Get D50 white point for XYZ color space + static PDFColor3 getDefaultXYZWhitepoint(); +}; + +using PDFCMSPointer = QSharedPointer; + +class PDFCMSGeneric : public PDFCMS +{ +public: + explicit inline PDFCMSGeneric() = default; + + virtual bool isCompatible(const PDFCMSSettings& settings) const override; + virtual QColor getPaperColor() const override; + virtual QColor getColorFromDeviceGray(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromDeviceRGB(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromDeviceCMYK(const PDFColor& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromXYZ(const PDFColor3& whitePoint, const PDFColor3& color, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual QColor getColorFromICC(const PDFColor& color, RenderingIntent renderingIntent, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromDeviceGray(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromDeviceRGB(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromDeviceCMYK(const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromXYZ(const PDFColor3& whitePoint, const std::vector& colors, RenderingIntent intent, unsigned char* outputBuffer, PDFRenderErrorReporter* reporter) const override; + virtual bool fillRGBBufferFromICC(const std::vector& colors, RenderingIntent renderingIntent, unsigned char* outputBuffer, const QByteArray& iccID, const QByteArray& iccData, PDFRenderErrorReporter* reporter) const override; + virtual bool transformColorSpace(const ColorSpaceTransformParams& params) const override; +}; + +struct PDFColorProfileIdentifier +{ + enum class Type + { + Gray, + sRGB, + RGB, + FileGray, + FileRGB, + FileCMYK, + MemoryGray, + MemoryRGB, + MemoryCMYK, + Invalid + }; + + bool operator==(const PDFColorProfileIdentifier&) const = default; + bool operator!=(const PDFColorProfileIdentifier&) const = default; + + Type type = Type::sRGB; + QString name; + QString id; + PDFReal temperature = 6500.0; + QPointF primaryR; + QPointF primaryG; + QPointF primaryB; + PDFReal gamma = 1.0; + bool isOutputIntentProfile = false; + + /// Data for MemoryGray, MemoryRGB and MemoryCMYK + QByteArray profileMemoryData; + + /// Creates gray color profile identifier + /// \param name Name of color profile + /// \param id Identifier of color profile + /// \param temperature White point temperature + /// \param gamma Gamma correction + static PDFColorProfileIdentifier createGray(QString name, QString id, PDFReal temperature, PDFReal gamma); + + /// Creates sRGB color profile identifier + static PDFColorProfileIdentifier createSRGB(); + + /// Creates RGB color space identifier + /// \param name Name of color profile + /// \param id Identifier of color profile + /// \param temperature White point temperature + /// \param primaryR Primary red + /// \param primaryG Primary green + /// \param primaryB Primary blue + /// \param gamma Gamma correction + static PDFColorProfileIdentifier createRGB(QString name, QString id, PDFReal temperature, QPointF primaryR, QPointF primaryG, QPointF primaryB, PDFReal gamma); + + /// Create file color profile identifier + static PDFColorProfileIdentifier createFile(Type type, QString name, QString id); + + /// Create memory output intent color profile identifier + static PDFColorProfileIdentifier createOutputIntent(Type type, QString name, QString id, QByteArray profileData); +}; + +using PDFColorProfileIdentifiers = std::vector; + +/// Manager, that manages current color management system and also list +/// of usable input and output color profiles. It has color profiles +/// for outout device, and color profiles for input (gray/RGB/CMYK). +/// It also handles settings, and it's changes. Constant functions +/// is save to call from multiple threads, this also holds for some +/// non-constant functions - manager is protected by mutexes. +class PDF4QTLIBSHARED_EXPORT PDFCMSManager : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFCMSManager(QObject* parent); + + /// Returns current CMS. This function possibly creates CMS, + /// of no CMS is found. + PDFCMSPointer getCurrentCMS() const; + + const PDFCMSSettings& getSettings() const { return m_settings; } + void setSettings(const PDFCMSSettings& settings); + + const PDFColorProfileIdentifiers& getOutputProfiles() const; + const PDFColorProfileIdentifiers& getGrayProfiles() const; + const PDFColorProfileIdentifiers& getRGBProfiles() const; + const PDFColorProfileIdentifiers& getCMYKProfiles() const; + + /// Returns default color management settings + PDFCMSSettings getDefaultSettings() const; + + /// Set current document. Document is examined for output + /// rendering intents and they can be used when rendering. + /// \param document Document + void setDocument(const PDFDocument* document); + + /// Get translated name for color management system + /// \param system System + static QString getSystemName(PDFCMSSettings::System system); + +signals: + void colorManagementSystemChanged(); + +private: + /// Creates new CMS based on current settings + PDFCMSPointer getCurrentCMSImpl() const; + + /// Clear cached items + void clearCache(); + + /// This function returns external profiles. It is not protected by mutex, + /// so it is not thread-safe. For this reason, it is not in public + /// interface. + const PDFColorProfileIdentifiers& getExternalProfiles() const; + + PDFColorProfileIdentifiers getOutputProfilesImpl() const; + PDFColorProfileIdentifiers getGrayProfilesImpl() const; + PDFColorProfileIdentifiers getRGBProfilesImpl() const; + PDFColorProfileIdentifiers getCMYKProfilesImpl() const; + PDFColorProfileIdentifiers getExternalProfilesImpl() const; + + /// Returns filtered list of external profiles (list are filtered by type, + /// so, for example, only CMYK profiles are returned) + /// \param type Type of profile + PDFColorProfileIdentifiers getFilteredExternalProfiles(PDFColorProfileIdentifier::Type type) const; + + /// Returns filtered list of output intent profiles (list are filtered by type, + /// so, for example, only CMYK profiles are returned) + /// \param type Type of profile + PDFColorProfileIdentifiers getFilteredOutputIntentProfiles(PDFColorProfileIdentifier::Type type) const; + + /// Gets list of color profiles from external directory + /// \param profileDirectory Directory with profiles + PDFColorProfileIdentifiers getExternalColorProfiles(QString profileDirectory) const; + + PDFCMSSettings m_settings; + const PDFDocument* m_document; + PDFColorProfileIdentifiers m_outputIntentProfiles; + + mutable QMutex m_mutex; + mutable PDFCachedItem m_CMS; + mutable PDFCachedItem m_outputProfiles; + mutable PDFCachedItem m_grayProfiles; + mutable PDFCachedItem m_RGBProfiles; + mutable PDFCachedItem m_CMYKProfiles; + mutable PDFCachedItem m_externalProfiles; +}; + +/// Class providing chromatic adaptation of whitepoints +/// using various method. +class PDFChromaticAdaptationXYZ +{ +public: + PDFChromaticAdaptationXYZ() = delete; + + static PDFColorComponentMatrix_3x3 createWhitepointChromaticAdaptation(const PDFColor3& targetWhitePoint, + const PDFColor3& sourceWhitePoint, + PDFCMSSettings::ColorAdaptationXYZ method); +}; + +} // namespace pdf + +#endif // PDFCMS_H diff --git a/Pdf4QtLib/sources/pdfcolorspaces.cpp b/Pdf4QtLib/sources/pdfcolorspaces.cpp index ee3f0dc..e38b031 100644 --- a/Pdf4QtLib/sources/pdfcolorspaces.cpp +++ b/Pdf4QtLib/sources/pdfcolorspaces.cpp @@ -1,2540 +1,2540 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfcolorspaces.h" -#include "pdfobject.h" -#include "pdfdocument.h" -#include "pdfexception.h" -#include "pdfutils.h" -#include "pdfpattern.h" -#include "pdfcms.h" -#include "pdfexecutionpolicy.h" - -#include - -#include - -namespace pdf -{ - -static PDFColorComponent getDeterminant(const PDFColorComponentMatrix_3x3& matrix) -{ - const PDFColorComponent a_11 = matrix.getValue(0, 0); - const PDFColorComponent a_12 = matrix.getValue(0, 1); - const PDFColorComponent a_13 = matrix.getValue(0, 2); - const PDFColorComponent a_21 = matrix.getValue(1, 0); - const PDFColorComponent a_22 = matrix.getValue(1, 1); - const PDFColorComponent a_23 = matrix.getValue(1, 2); - const PDFColorComponent a_31 = matrix.getValue(2, 0); - const PDFColorComponent a_32 = matrix.getValue(2, 1); - const PDFColorComponent a_33 = matrix.getValue(2, 2); - - return -a_13* a_22 * a_31 + a_12 * a_23 * a_31 + a_13 * a_21 * a_32 - a_11 * a_23 * a_32 - a_12 * a_21 * a_33 + a_11 * a_22 * a_33; -} - -PDFColorComponentMatrix_3x3 getInverseMatrix(const PDFColorComponentMatrix_3x3& matrix) -{ - const PDFColorComponent a_11 = matrix.getValue(0, 0); - const PDFColorComponent a_12 = matrix.getValue(0, 1); - const PDFColorComponent a_13 = matrix.getValue(0, 2); - const PDFColorComponent a_21 = matrix.getValue(1, 0); - const PDFColorComponent a_22 = matrix.getValue(1, 1); - const PDFColorComponent a_23 = matrix.getValue(1, 2); - const PDFColorComponent a_31 = matrix.getValue(2, 0); - const PDFColorComponent a_32 = matrix.getValue(2, 1); - const PDFColorComponent a_33 = matrix.getValue(2, 2); - - const PDFColorComponent determinant = -a_13* a_22 * a_31 + a_12 * a_23 * a_31 + a_13 * a_21 * a_32 - a_11 * a_23 * a_32 - a_12 * a_21 * a_33 + a_11 * a_22 * a_33; - const PDFColorComponent coefficient = !qIsNull(determinant) ? 1.0 / determinant : 0.0; - - PDFColorComponentMatrix_3x3 inversedMatrix { a_22 * a_33 - a_23 * a_32, a_13 * a_32 - a_12 * a_33, a_12 * a_23 - a_13 * a_22, - a_23 * a_31 - a_21 * a_33, a_11 * a_33 - a_13 * a_31, a_13 * a_21 - a_11 * a_23, - a_21 * a_32 - a_22 * a_31, a_12 * a_31 - a_11 * a_32, a_11 * a_22 - a_12 * a_21 }; - inversedMatrix.multiplyByFactor(coefficient); - return inversedMatrix; -} - -PDFColor PDFDeviceGrayColorSpace::getDefaultColorOriginal() const -{ - return PDFColor(0.0f); -} - -QColor PDFDeviceGrayColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(cms); - Q_ASSERT(color.size() == getColorComponentCount()); - Q_UNUSED(isRange01); - - PDFColorComponent component = clip01(color[0]); - - // If color management system handles the color transformation, then use it, - // otherwise fall back to the generic case. - QColor cmsColor = cms->getColorFromDeviceGray(color, intent, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - QColor result(QColor::Rgb); - result.setRgbF(component, component, component, 1.0); - return result; -} - -size_t PDFDeviceGrayColorSpace::getColorComponentCount() const -{ - return 1; -} - -void PDFDeviceGrayColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - if (!cms->fillRGBBufferFromDeviceGray(colors, intent, outputBuffer, reporter)) - { - PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); - } -} - -PDFColor PDFDeviceRGBColorSpace::getDefaultColorOriginal() const -{ - return PDFColor(0.0f, 0.0f, 0.0f); -} - -QColor PDFDeviceRGBColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(color.size() == getColorComponentCount()); - Q_UNUSED(isRange01); - - PDFColorComponent r = clip01(color[0]); - PDFColorComponent g = clip01(color[1]); - PDFColorComponent b = clip01(color[2]); - - PDFColor clippedColor(r, g, b); - QColor cmsColor = cms->getColorFromDeviceRGB(clippedColor, intent, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - QColor result(QColor::Rgb); - result.setRgbF(r, g, b, 1.0); - return result; -} - -size_t PDFDeviceRGBColorSpace::getColorComponentCount() const -{ - return 3; -} - -void PDFDeviceRGBColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - if (!cms->fillRGBBufferFromDeviceRGB(colors, intent, outputBuffer, reporter)) - { - PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); - } -} - -PDFColor PDFDeviceCMYKColorSpace::getDefaultColorOriginal() const -{ - return PDFColor(0.0f, 0.0f, 0.0f, 1.0f); -} - -QColor PDFDeviceCMYKColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(color.size() == getColorComponentCount()); - Q_UNUSED(isRange01); - - PDFColorComponent c = clip01(color[0]); - PDFColorComponent m = clip01(color[1]); - PDFColorComponent y = clip01(color[2]); - PDFColorComponent k = clip01(color[3]); - - PDFColor clippedColor(c, m, y, k); - QColor cmsColor = cms->getColorFromDeviceCMYK(clippedColor, intent, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - QColor result(QColor::Cmyk); - result.setCmykF(c, m, y, k, 1.0); - return result; -} - -size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const -{ - return 4; -} - -void PDFDeviceCMYKColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - if (!cms->fillRGBBufferFromDeviceCMYK(colors, intent, outputBuffer, reporter)) - { - PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); - } -} - -bool PDFAbstractColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - return getColorSpace() == other->getColorSpace(); -} - -bool PDFAbstractColorSpace::isBlendColorSpace() const -{ - switch (getColorSpace()) - { - case pdf::PDFAbstractColorSpace::ColorSpace::DeviceGray: - case pdf::PDFAbstractColorSpace::ColorSpace::DeviceRGB: - case pdf::PDFAbstractColorSpace::ColorSpace::DeviceCMYK: - case pdf::PDFAbstractColorSpace::ColorSpace::CalGray: - case pdf::PDFAbstractColorSpace::ColorSpace::CalRGB: - case pdf::PDFAbstractColorSpace::ColorSpace::ICCBased: - return true; - - default: - return false; - } -} - -QColor PDFAbstractColorSpace::getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - return getColor(getDefaultColorOriginal(), cms, intent, reporter, true); -} - -QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData, - const PDFImageData& softMask, - const PDFCMS* cms, - RenderingIntent intent, - PDFRenderErrorReporter* reporter) const -{ - if (imageData.isValid()) - { - switch (imageData.getMaskingType()) - { - case PDFImageData::MaskingType::None: - { - QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888); - image.fill(QColor(Qt::white)); - - unsigned int componentCount = imageData.getComponents(); - if (componentCount != getColorComponentCount()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); - } - - const std::vector& decode = imageData.getDecode(); - if (!decode.empty() && decode.size() != componentCount * 2) - { - throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); - } - - const unsigned int imageWidth = imageData.getWidth(); - const unsigned int imageHeight = imageData.getHeight(); - - QMutex exceptionMutex; - std::optional exception; - - auto transformPixelLine = [&](unsigned int i) - { - try - { - PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); - reader.seek(i * imageData.getStride()); - - const double max = reader.max(); - const double coefficient = 1.0 / max; - unsigned char* outputLine = image.scanLine(i); - - std::vector inputColors(imageWidth * componentCount, 0.0f); - auto itInputColor = inputColors.begin(); - for (unsigned int j = 0; j < imageData.getWidth(); ++j) - { - for (unsigned int k = 0; k < componentCount; ++k) - { - PDFReal value = reader.read(); - - // Interpolate value, if it is not empty - if (!decode.empty()) - { - *itInputColor++ = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); - } - else - { - *itInputColor++ = value * coefficient; - } - } - } - - fillRGBBuffer(inputColors, outputLine, intent, cms, reporter); - } - catch (PDFException lineException) - { - QMutexLocker lock(&exceptionMutex); - if (!exception) - { - exception = lineException; - } - } - }; - - auto range = PDFIntegerRange(0, imageHeight); - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), transformPixelLine); - - if (exception) - { - throw *exception; - } - - return image; - } - - case PDFImageData::MaskingType::SoftMask: - { - const bool hasMatte = !softMask.getMatte().empty(); - QImage image(imageData.getWidth(), imageData.getHeight(), hasMatte ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888); - image.fill(QColor(Qt::white)); - - unsigned int componentCount = imageData.getComponents(); - if (componentCount != getColorComponentCount()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); - } - - const std::vector& decode = imageData.getDecode(); - if (!decode.empty() && decode.size() != componentCount * 2) - { - throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); - } - - const unsigned int imageWidth = imageData.getWidth(); - const unsigned int imageHeight = imageData.getHeight(); - - QImage alphaMask = createAlphaMask(softMask); - if (alphaMask.size() != image.size()) - { - // Scale the alpha mask, if it is masked - alphaMask = alphaMask.scaled(image.size()); - } - - QMutex exceptionMutex; - std::optional exception; - - auto transformPixelLine = [&](unsigned int i) - { - try - { - PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); - reader.seek(i * imageData.getStride()); - - const double max = reader.max(); - const double coefficient = 1.0 / max; - unsigned char* outputLine = image.scanLine(i); - unsigned char* alphaLine = alphaMask.scanLine(i); - - std::vector inputColors(imageWidth * componentCount, 0.0f); - std::vector outputColors(imageWidth * 3, 0); - - auto itInputColor = inputColors.begin(); - for (unsigned int j = 0; j < imageData.getWidth(); ++j) - { - for (unsigned int k = 0; k < componentCount; ++k) - { - PDFReal value = reader.read(); - - // Interpolate value, if it is not empty - if (!decode.empty()) - { - *itInputColor++ = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); - } - else - { - *itInputColor++ = value * coefficient; - } - } - } - - fillRGBBuffer(inputColors, outputColors.data(), intent, cms, reporter); - - const unsigned char* transformedLine = outputColors.data(); - for (unsigned int i = 0; i < imageWidth; ++i) - { - *outputLine++ = *transformedLine++; - *outputLine++ = *transformedLine++; - *outputLine++ = *transformedLine++; - *outputLine++ = *alphaLine++; - } - } - catch (PDFException lineException) - { - QMutexLocker lock(&exceptionMutex); - if (!exception) - { - exception = lineException; - } - } - }; - - auto range = PDFIntegerRange(0, imageHeight); - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), transformPixelLine); - - if (exception) - { - throw *exception; - } - - return image; - } - - case PDFImageData::MaskingType::ColorKeyMasking: - { - QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGBA8888); - image.fill(QColor(Qt::transparent)); - - unsigned int componentCount = imageData.getComponents(); - if (componentCount != getColorComponentCount()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); - } - - Q_ASSERT(componentCount > 0); - const std::vector& colorKeyMask = imageData.getColorKeyMask(); - if (colorKeyMask.size() / 2 != componentCount) - { - throw PDFException(PDFTranslationContext::tr("Invalid number of color components in color key mask. Expected %1, provided %2.").arg(2 * componentCount).arg(colorKeyMask.size())); - } - - const std::vector& decode = imageData.getDecode(); - if (!decode.empty() && decode.size() != componentCount * 2) - { - throw PDFException(PDFTranslationContext::tr("Invalid size of the decoded array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); - } - - PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); - - PDFColor color; - color.resize(componentCount); - - const double max = reader.max(); - const double coefficient = 1.0 / max; - for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) - { - reader.seek(i * imageData.getStride()); - unsigned char* outputLine = image.scanLine(i); - - for (unsigned int j = 0; j < imageData.getWidth(); ++j) - { - // Number of masked-out colors - unsigned int maskedColors = 0; - - for (unsigned int k = 0; k < componentCount; ++k) - { - PDFBitReader::Value value = reader.read(); - - // Interpolate value, if decode is not empty - if (!decode.empty()) - { - color[k] = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); - } - else - { - color[k] = value * coefficient; - } - - Q_ASSERT(2 * k + 1 < colorKeyMask.size()); - if (static_cast::type::value_type>(value) >= colorKeyMask[2 * k] && - static_cast::type::value_type>(value) <= colorKeyMask[2 * k + 1]) - { - ++maskedColors; - } - } - - QColor transformedColor = getColor(color, cms, intent, reporter, true); - QRgb rgb = transformedColor.rgb(); - - *outputLine++ = qRed(rgb); - *outputLine++ = qGreen(rgb); - *outputLine++ = qBlue(rgb); - *outputLine++ = (maskedColors == componentCount) ? 0x00 : 0xFF; - } - } - - return image; - } - - default: - { - throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image masking not implemented!")); - } - } - } - - return QImage(); -} - -void PDFAbstractColorSpace::fillRGBBuffer(const std::vector& colors, - unsigned char* outputBuffer, - RenderingIntent intent, - const PDFCMS* cms, - PDFRenderErrorReporter* reporter) const -{ - // Generic solution - size_t colorComponentCount = getColorComponentCount(); - size_t pixels = colors.size() / colorComponentCount; - - auto it = colors.cbegin(); - for (size_t i = 0; i < pixels; ++i) - { - PDFColor color; - color.resize(colorComponentCount); - for (size_t j = 0; j < colorComponentCount; ++j) - { - color[j] = *it++; - } - QColor transformedColor = getColor(color, cms, intent, reporter, true); - QRgb rgb = transformedColor.rgb(); - - *outputBuffer++ = qRed(rgb); - *outputBuffer++ = qGreen(rgb); - *outputBuffer++ = qBlue(rgb); - } -} - -QColor PDFAbstractColorSpace::getCheckedColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - if (getColorComponentCount() != color.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid number of color components. Expected number is %1, actual number is %2.").arg(static_cast(getColorComponentCount())).arg(static_cast(color.size()))); - } - - return getColor(color, cms, intent, reporter, true); -} - -QImage PDFAbstractColorSpace::createAlphaMask(const PDFImageData& softMask) -{ - if (softMask.getMaskingType() != PDFImageData::MaskingType::None) - { - throw PDFException(PDFTranslationContext::tr("Soft mask can't have masking.")); - } - - if (softMask.getWidth() < 1 || softMask.getHeight() < 1) - { - throw PDFException(PDFTranslationContext::tr("Invalid size of soft mask.")); - } - - QImage image(softMask.getWidth(), softMask.getHeight(), QImage::Format_Alpha8); - - unsigned int componentCount = softMask.getComponents(); - if (componentCount != 1) - { - throw PDFException(PDFTranslationContext::tr("Soft mask should have only 1 color component (alpha) instead of %1.").arg(componentCount)); - } - - const std::vector& decode = softMask.getDecode(); - if (!decode.empty() && decode.size() != componentCount * 2) - { - throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); - } - - PDFBitReader reader(&softMask.getData(), softMask.getBitsPerComponent()); - - PDFColor color; - color.resize(componentCount); - - const double max = reader.max(); - const double coefficient = 1.0 / max; - for (unsigned int i = 0, rowCount = softMask.getHeight(); i < rowCount; ++i) - { - reader.seek(i * softMask.getStride()); - unsigned char* outputLine = image.scanLine(i); - - for (unsigned int j = 0; j < softMask.getWidth(); ++j) - { - PDFReal alpha = 0.0; - - PDFReal value = reader.read(); - - // Interpolate value, if it is not empty - if (!decode.empty()) - { - alpha = interpolate(value, 0.0, max, decode[0], decode[1]); - } - else - { - alpha = value * coefficient; - } - - alpha = qBound(0.0, alpha, 1.0); - uint8_t alphaCoded = alpha * 255; - *outputLine++ = alphaCoded; - } - } - - return image; -} - -PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFObject& colorSpace) -{ - std::set usedNames; - return createColorSpaceImpl(colorSpaceDictionary, document, colorSpace, COLOR_SPACE_MAX_LEVEL_OF_RECURSION, usedNames); -} - -PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const QByteArray& name) -{ - std::set usedNames; - return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, name, COLOR_SPACE_MAX_LEVEL_OF_RECURSION, usedNames); -} - -PDFColor PDFAbstractColorSpace::convertToColor(const std::vector& components) -{ - PDFColor result; - - for (PDFReal component : components) - { - result.push_back(component); - } - - return result; -} - -bool PDFAbstractColorSpace::isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance) -{ - const size_t size = color1.size(); - if (size != color2.size()) - { - return false; - } - - for (size_t i = 0; i < size; ++i) - { - if (std::fabs(color1[i] - color2[i]) > tolerance) - { - return false; - } - } - - return true; -} - -PDFColor PDFAbstractColorSpace::mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio) -{ - const size_t size = color1.size(); - Q_ASSERT(size == color2.size()); - - PDFColor result; - result.resize(size); - for (size_t i = 0; i < size; ++i) - { - result[i] = color1[i] * (1.0 - ratio) + color2[i] * ratio; - } - - return result; -} - -bool PDFAbstractColorSpace::transform(const PDFAbstractColorSpace* source, - const PDFAbstractColorSpace* target, - const PDFCMS* cms, - RenderingIntent intent, - const PDFColorBuffer input, - PDFColorBuffer output, - PDFRenderErrorReporter* reporter) -{ - Q_ASSERT(source); - Q_ASSERT(target); - Q_ASSERT(cms); - Q_ASSERT(target->isBlendColorSpace()); - Q_ASSERT(input.size() % source->getColorComponentCount() == 0); - - const std::size_t sourceColors = input.size() / source->getColorComponentCount(); - const std::size_t targetColors = output.size() / target->getColorComponentCount(); - - if (sourceColors != targetColors) - { - // This is bad input values. Function should not be called with these parameters. - // Assert and return false. We do not want crash. - Q_ASSERT(false); - return false; - } - - const std::size_t sourceColorsRemainder = input.size() % source->getColorComponentCount(); - const std::size_t targetColorsRemainder = output.size() % target->getColorComponentCount(); - - if (sourceColorsRemainder > 0 || targetColorsRemainder > 0) - { - // Input/output buffer size is incorrect - Q_ASSERT(false); - return false; - } - - if (source->equals(target)) - { - // Just copy input buffer to output buffer - Q_ASSERT(input.size() == output.size()); - std::copy(input.begin(), input.end(), output.begin()); - return true; - } - - // We will use following algorithm to transform colors: - // 1. Determine source color space type for cms, - // and adjust colors to this color space type - // 2. Prepare work output buffer of target size (can have - // different size than real output buffer) - // 3. Transform colors using CMS - // 4. Transform output color buffer to target color buffer - - PDFCMS::ColorSpaceTransformParams params; - std::vector transformedInputColorsVector; - std::vector transformedOutputColorsVector; - PDFColorBuffer transformedInput = input; - PDFColorBuffer transformedOutput = output; - params.intent = intent; - - switch (source->getColorSpace()) - { - case ColorSpace::DeviceGray: - params.sourceType = PDFCMS::ColorSpaceType::DeviceGray; - break; - - case ColorSpace::DeviceRGB: - params.sourceType = PDFCMS::ColorSpaceType::DeviceRGB; - break; - - case ColorSpace::DeviceCMYK: - params.sourceType = PDFCMS::ColorSpaceType::DeviceCMYK; - break; - - case ColorSpace::CalGray: - { - params.sourceType = PDFCMS::ColorSpaceType::XYZ; - - // Transform gray to XYZ - const PDFCalGrayColorSpace* calGrayColorSpace = static_cast(source); - const PDFColorComponent gamma = calGrayColorSpace->getGamma(); - - transformedInputColorsVector.resize(input.size() * 3, 0.0f); - auto it = transformedInputColorsVector.begin(); - for (PDFColorComponent gray : input) - { - const PDFColorComponent A = clip01(gray); - const PDFColorComponent xyzColor = std::powf(A, gamma); - - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = xyzColor; - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = xyzColor; - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = xyzColor; - } - Q_ASSERT(it == transformedInputColorsVector.end()); - - transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); - break; - } - - case ColorSpace::CalRGB: - { - params.sourceType = PDFCMS::ColorSpaceType::XYZ; - - const PDFCalRGBColorSpace* calRGBColorSpace = static_cast(source); - const PDFColor3 gamma = calRGBColorSpace->getGamma(); - const PDFColorComponentMatrix_3x3 matrix = calRGBColorSpace->getMatrix(); - - transformedInputColorsVector.resize(input.size(), 0.0f); - auto it = transformedInputColorsVector.begin(); - - for (auto sourceIt = input.cbegin(); sourceIt != input.cend(); sourceIt = std::next(sourceIt, 3)) - { - PDFColor3 ABC = { }; - Q_ASSERT(sourceIt != input.end()); - ABC[0] = *sourceIt; - Q_ASSERT(sourceIt + 1 != input.end()); - ABC[1] = *std::next(sourceIt, 1); - Q_ASSERT(sourceIt + 2 != input.end()); - ABC[2] = *std::next(sourceIt, 2); - ABC = colorPowerByFactors(ABC, gamma); - - const PDFColor3 XYZ = matrix * ABC; - - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = XYZ[0]; - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = XYZ[1]; - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = XYZ[2]; - } - Q_ASSERT(it == transformedInputColorsVector.end()); - - transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); - break; - } - - case ColorSpace::Lab: - { - params.sourceType = PDFCMS::ColorSpaceType::XYZ; - - const PDFLabColorSpace* labColorSpace = static_cast(source); - const PDFColorComponent aMin = labColorSpace->getAMin(); - const PDFColorComponent aMax = labColorSpace->getAMax(); - const PDFColorComponent bMin = labColorSpace->getBMin(); - const PDFColorComponent bMax = labColorSpace->getBMax(); - - transformedInputColorsVector.resize(input.size(), 0.0f); - auto it = transformedInputColorsVector.begin(); - - for (auto sourceIt = input.cbegin(); sourceIt != input.cend(); sourceIt = std::next(sourceIt, 3)) - { - Q_ASSERT(sourceIt != input.end()); - Q_ASSERT(sourceIt + 1 != input.end()); - Q_ASSERT(sourceIt + 2 != input.end()); - - PDFColorComponent LStar = qBound(0.0, interpolate(*sourceIt, 0.0, 1.0, 0.0, 100.0), 100.0); - PDFColorComponent aStar = qBound(aMin, interpolate(*std::next(sourceIt, 1), 0.0, 1.0, aMin, aMax), aMax); - PDFColorComponent bStar = qBound(bMin, interpolate(*std::next(sourceIt, 2), 0.0, 1.0, bMin, bMax), bMax); - - const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; - const PDFColorComponent param2 = aStar / 500.0f; - const PDFColorComponent param3 = bStar / 200.0f; - - const PDFColorComponent L = param1 + param2; - const PDFColorComponent M = param1; - const PDFColorComponent N = param1 - param3; - - auto g = [](PDFColorComponent x) -> PDFColorComponent - { - if (x >= 6.0f / 29.0f) - { - return x * x * x; - } - else - { - return (108.0f / 841.0f) * (x - 4.0f / 29.0f); - } - }; - - const PDFColorComponent gL = g(L); - const PDFColorComponent gM = g(M); - const PDFColorComponent gN = g(N); - - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = gL; - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = gM; - Q_ASSERT(it != transformedInputColorsVector.end()); - *it++ = gN; - } - Q_ASSERT(it == transformedInputColorsVector.end()); - - transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); - break; - } - - case ColorSpace::ICCBased: - { - const PDFICCBasedColorSpace* iccBasedColorSpace = static_cast(source); - - params.sourceType = PDFCMS::ColorSpaceType::ICC; - params.sourceIccId = iccBasedColorSpace->getIccProfileDataChecksum(); - params.sourceIccData = iccBasedColorSpace->getIccProfileData(); - - size_t colorComponentCount = iccBasedColorSpace->getColorComponentCount(); - const PDFICCBasedColorSpace::Ranges& ranges = iccBasedColorSpace->getRange(); - - transformedInputColorsVector.resize(input.size(), 0.0f); - auto outputIt = transformedInputColorsVector.begin(); - for (auto inputIt = input.cbegin(); inputIt != input.cend();) - { - for (size_t i = 0; i < colorComponentCount; ++i) - { - const size_t imin = 2 * i + 0; - const size_t imax = 2 * i + 1; - *outputIt++ = qBound(ranges[imin], *inputIt++, ranges[imax]); - } - } - - transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); - break; - } - - case ColorSpace::Indexed: - { - const PDFIndexedColorSpace* indexedColorSpace = static_cast(source); - - PDFColorSpacePointer baseColorSpace = indexedColorSpace->getBaseColorSpace(); - std::vector transformedToBaseColorSpaceInput = indexedColorSpace->transformColorsToBaseColorSpace(input); - PDFColorBuffer transformedInputBuffer(transformedToBaseColorSpaceInput.data(), transformedToBaseColorSpaceInput.size()); - return transform(baseColorSpace.data(), target, cms, intent, transformedInputBuffer, output, reporter); - } - - case ColorSpace::Separation: - { - const PDFSeparationColorSpace* separationColorSpace = static_cast(source); - - PDFColorSpacePointer alternateColorSpace = separationColorSpace->getAlternateColorSpace(); - std::vector transformedToBaseColorSpaceInput = separationColorSpace->transformColorsToBaseColorSpace(input); - PDFColorBuffer transformedInputBuffer(transformedToBaseColorSpaceInput.data(), transformedToBaseColorSpaceInput.size()); - return transform(alternateColorSpace.data(), target, cms, intent, transformedInputBuffer, output, reporter); - } - - case ColorSpace::DeviceN: - { - const PDFDeviceNColorSpace* separationColorSpace = static_cast(source); - - PDFColorSpacePointer alternateColorSpace = separationColorSpace->getAlternateColorSpace(); - std::vector transformedToBaseColorSpaceInput = separationColorSpace->transformColorsToBaseColorSpace(input); - PDFColorBuffer transformedInputBuffer(transformedToBaseColorSpaceInput.data(), transformedToBaseColorSpaceInput.size()); - return transform(alternateColorSpace.data(), target, cms, intent, transformedInputBuffer, output, reporter); - } - - case ColorSpace::Pattern: - return false; - - default: - Q_ASSERT(false); - return false; - } - - switch (target->getColorSpace()) - { - case ColorSpace::DeviceGray: - // Output buffer size is the same as target type - params.targetType = PDFCMS::ColorSpaceType::DeviceGray; - break; - - case ColorSpace::DeviceRGB: - // Output buffer size is the same as target type - params.targetType = PDFCMS::ColorSpaceType::DeviceRGB; - break; - - case ColorSpace::DeviceCMYK: - // Output buffer size is the same as target type - params.targetType = PDFCMS::ColorSpaceType::DeviceCMYK; - break; - - case pdf::PDFAbstractColorSpace::ColorSpace::CalGray: - { - params.targetType = PDFCMS::ColorSpaceType::XYZ; - transformedOutputColorsVector.resize(output.size() * 3, 0.0f); - transformedOutput = PDFColorBuffer(transformedOutputColorsVector.data(), transformedOutputColorsVector.size()); - break; - } - - case pdf::PDFAbstractColorSpace::ColorSpace::CalRGB: - { - // Output buffer size is the same as target type - params.targetType = PDFCMS::ColorSpaceType::XYZ; - transformedOutputColorsVector.resize(output.size(), 0.0f); - transformedOutput = PDFColorBuffer(transformedOutputColorsVector.data(), transformedOutputColorsVector.size()); - break; - } - - case pdf::PDFAbstractColorSpace::ColorSpace::ICCBased: - { - const PDFICCBasedColorSpace* iccBasedColorSpace = static_cast(target); - - params.targetType = PDFCMS::ColorSpaceType::ICC; - params.targetIccId = iccBasedColorSpace->getIccProfileDataChecksum(); - params.targetIccData = iccBasedColorSpace->getIccProfileData(); - break; - } - - default: - Q_ASSERT(false); - return false; - } - - params.input = transformedInput; - params.output = transformedOutput; - - cms->transformColorSpace(params); - - switch (target->getColorSpace()) - { - case pdf::PDFAbstractColorSpace::ColorSpace::CalGray: - { - const PDFCalGrayColorSpace* calGrayColorSpace = static_cast(source); - const PDFColorComponent gamma = 1.0 / calGrayColorSpace->getGamma(); - - auto outputIt = output.begin(); - for (auto transformedOutputIt = transformedOutput.cbegin(); transformedOutputIt != transformedOutput.cend(); transformedOutputIt = std::next(transformedOutputIt, 3)) - { - PDFColor3 XYZ = { }; - Q_ASSERT(transformedOutputIt != transformedOutput.end()); - XYZ[0] = *transformedOutputIt; - Q_ASSERT(transformedOutputIt + 1 != transformedOutput.end()); - XYZ[1] = *std::next(transformedOutputIt, 1); - Q_ASSERT(transformedOutputIt + 2 != transformedOutput.end()); - XYZ[2] = *std::next(transformedOutputIt, 2); - - const PDFColorComponent gray = (XYZ[0] + XYZ[1] + XYZ[2]) * 0.333333333333333; - const PDFColorComponent grayWithGamma = std::powf(gray, gamma); - Q_ASSERT(outputIt != output.cend()); - *outputIt++ = grayWithGamma; - } - Q_ASSERT(outputIt == output.cend()); - break; - } - - case pdf::PDFAbstractColorSpace::ColorSpace::CalRGB: - { - const PDFCalRGBColorSpace* calRGBColorSpace = static_cast(source); - const PDFColor3 gammaForward = calRGBColorSpace->getGamma(); - const PDFColorComponentMatrix_3x3 matrixForward = calRGBColorSpace->getMatrix(); - const PDFColor3 gammaInverse = PDFColor3{ 1.0f / gammaForward[0], 1.0f / gammaForward[1], 1.0f / gammaForward[2] }; - const PDFColorComponentMatrix_3x3 matrixInverse = getInverseMatrix(matrixForward); - - auto outputIt = output.begin(); - for (auto transformedOutputIt = transformedOutput.cbegin(); transformedOutputIt != transformedOutput.cend(); transformedOutputIt = std::next(transformedOutputIt, 3)) - { - PDFColor3 XYZ = { }; - XYZ[0] = *transformedOutputIt; - XYZ[1] = *std::next(transformedOutputIt, 1); - XYZ[2] = *std::next(transformedOutputIt, 2); - - const PDFColor3 RGB = matrixInverse * XYZ; - const PDFColor3 RGBwithGamma = colorPowerByFactors(RGB, gammaInverse); - - Q_ASSERT(outputIt != output.end()); - *outputIt++ = RGBwithGamma[0]; - Q_ASSERT(outputIt != output.end()); - *outputIt++ = RGBwithGamma[1]; - Q_ASSERT(outputIt != output.end()); - *outputIt++ = RGBwithGamma[2]; - } - Q_ASSERT(outputIt == output.cend()); - break; - } - - default: - break; - } - - return true; -} - -PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFObject& colorSpace, - int recursion, - std::set& usedNames) -{ - if (--recursion <= 0) - { - throw PDFException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex.")); - } - - if (colorSpace.isName()) - { - return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, colorSpace.getString(), recursion, usedNames); - } - else if (colorSpace.isArray()) - { - // First value of the array should be identification name, second value dictionary with parameters - const PDFArray* array = colorSpace.getArray(); - size_t count = array->getCount(); - - if (count > 0) - { - // Name of the color space - const PDFObject& colorSpaceIdentifier = document->getObject(array->getItem(0)); - if (colorSpaceIdentifier.isName()) - { - QByteArray name = colorSpaceIdentifier.getString(); - - const PDFDictionary* dictionary = nullptr; - const PDFStream* stream = nullptr; - if (count > 1) - { - const PDFObject& colorSpaceSettings = document->getObject(array->getItem(1)); - if (colorSpaceSettings.isDictionary()) - { - dictionary = colorSpaceSettings.getDictionary(); - } - if (colorSpaceSettings.isStream()) - { - stream = colorSpaceSettings.getStream(); - } - } - - if (name == COLOR_SPACE_NAME_PATTERN) - { - PDFColorSpacePointer uncoloredPatternColorSpace; - if (count == 2) - { - uncoloredPatternColorSpace = createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(1)), recursion, usedNames); - } - - return PDFColorSpacePointer(new PDFPatternColorSpace(std::make_shared(), qMove(uncoloredPatternColorSpace), PDFColor())); - } - - if (dictionary) - { - if (name == COLOR_SPACE_NAME_CAL_GRAY) - { - return PDFCalGrayColorSpace::createCalGrayColorSpace(document, dictionary); - } - else if (name == COLOR_SPACE_NAME_CAL_RGB) - { - return PDFCalRGBColorSpace::createCalRGBColorSpace(document, dictionary); - } - else if (name == COLOR_SPACE_NAME_LAB) - { - return PDFLabColorSpace::createLabColorSpace(document, dictionary); - } - } - - if (stream && name == COLOR_SPACE_NAME_ICCBASED) - { - return PDFICCBasedColorSpace::createICCBasedColorSpace(colorSpaceDictionary, document, stream, recursion, usedNames); - } - - if (name == COLOR_SPACE_NAME_INDEXED && count == 4) - { - return PDFIndexedColorSpace::createIndexedColorSpace(colorSpaceDictionary, document, array, recursion, usedNames); - } - - if (name == COLOR_SPACE_NAME_SEPARATION && count == 4) - { - return PDFSeparationColorSpace::createSeparationColorSpace(colorSpaceDictionary, document, array, recursion, usedNames); - } - - if (name == COLOR_SPACE_NAME_DEVICE_N && count >= 4) - { - return PDFDeviceNColorSpace::createDeviceNColorSpace(colorSpaceDictionary, document, array, recursion, usedNames); - } - - // Try to just load by standard way - we can have "standard" color space stored in array - return createColorSpaceImpl(colorSpaceDictionary, document, colorSpaceIdentifier, recursion, usedNames); - } - } - } - - throw PDFException(PDFTranslationContext::tr("Invalid color space.")); - return PDFColorSpacePointer(); -} - -PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const QByteArray& name, - int recursion, - std::set& usedNames) -{ - if (--recursion <= 0) - { - throw PDFException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex.")); - } - - // Jakub Melka: This flag is set when we are already parsing the name. This can occur - // for example by this way: we have DefaultRGB, which is ICC profile. In this ICC profile, - // we create alternate alternate RGB color space also from DefaultRGB name. But in this time, - // we must use generic RGB color space, not DefaultRGB from color space dictionary, because - // it will be cyclical dependency. - bool isNameAlreadyProcessed = usedNames.count(name); - usedNames.insert(name); - - if (name == COLOR_SPACE_NAME_PATTERN) - { - return PDFColorSpacePointer(new PDFPatternColorSpace(std::make_shared(), nullptr, PDFColor())); - } - - if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY) - { - if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY) && !isNameAlreadyProcessed) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion, usedNames); - } - else - { - return PDFColorSpacePointer(new PDFDeviceGrayColorSpace()); - } - } - else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB) - { - if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB) && !isNameAlreadyProcessed) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion, usedNames); - } - else - { - return PDFColorSpacePointer(new PDFDeviceRGBColorSpace()); - } - } - else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_CAL_CMYK) - { - if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK) && !isNameAlreadyProcessed) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion, usedNames); - } - else - { - return PDFColorSpacePointer(new PDFDeviceCMYKColorSpace()); - } - } - else if (colorSpaceDictionary && colorSpaceDictionary->hasKey(name)) - { - return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(name)), recursion, usedNames); - } - - throw PDFException(PDFTranslationContext::tr("Invalid color space.")); - return PDFColorSpacePointer(); -} - -/// Conversion matrix from XYZ space to RGB space. Values are taken from this article: -/// https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_.28.22gamma.22.29 -static constexpr const PDFColorComponentMatrix<3, 3> matrixXYZtoRGB( - 3.2406f, -1.5372f, -0.4986f, - -0.9689f, 1.8758f, 0.0415f, - 0.0557f, -0.2040f, 1.0570f -); - -PDFColor3 PDFAbstractColorSpace::convertXYZtoRGB(const PDFColor3& xyzColor) -{ - return matrixXYZtoRGB * xyzColor; -} - -PDFColor PDFXYZColorSpace::getDefaultColorOriginal() const -{ - PDFColor color; - const size_t componentCount = getColorComponentCount(); - for (size_t i = 0; i < componentCount; ++i) - { - color.push_back(0.0f); - } - return color; -} - -bool PDFXYZColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFAbstractColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFXYZColorSpace* typedOther = static_cast(other); - return m_whitePoint == typedOther->getWhitePoint() && m_correctionCoefficients == typedOther->getCorrectionCoefficients(); -} - -PDFXYZColorSpace::PDFXYZColorSpace(PDFColor3 whitePoint) : - m_whitePoint(whitePoint), - m_correctionCoefficients() -{ - PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); - - m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; - m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; - m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; -} - -const PDFColor3& PDFXYZColorSpace::getCorrectionCoefficients() const -{ - return m_correctionCoefficients; -} - -PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma) : - PDFXYZColorSpace(whitePoint), - m_blackPoint(blackPoint), - m_gamma(gamma) -{ - -} - -bool PDFCalGrayColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFXYZColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFCalGrayColorSpace* typedOther = static_cast(other); - return m_blackPoint == typedOther->getBlackPoint() && m_gamma == typedOther->getGamma(); -} - -QColor PDFCalGrayColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(color.size() == getColorComponentCount()); - Q_UNUSED(isRange01); - - const PDFColorComponent A = clip01(color[0]); - const PDFColorComponent xyzColor = std::powf(A, m_gamma); - - PDFColor3 xyzColorCMS = { xyzColor, xyzColor, xyzColor }; - QColor cmsColor = cms->getColorFromXYZ(m_whitePoint, xyzColorCMS, intent, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - const PDFColor3 xyzColorMultipliedByWhitePoint = colorMultiplyByFactor(m_whitePoint, xyzColor); - const PDFColor3 rgb = convertXYZtoRGB(xyzColorMultipliedByWhitePoint); - const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); - return fromRGB01(calibratedRGB); -} - -size_t PDFCalGrayColorSpace::getColorComponentCount() const -{ - return 1; -} - -void PDFCalGrayColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - std::vector xyzColors(colors.size() * 3, 0.0f); - auto it = xyzColors.begin(); - for (float gray : colors) - { - const PDFColorComponent xyzColor = std::powf(clip01(gray), m_gamma); - *it++ = xyzColor; - *it++ = xyzColor; - *it++ = xyzColor; - } - - Q_ASSERT(xyzColors.size() == colors.size() * 3); - if (!cms->fillRGBBufferFromXYZ(m_whitePoint, xyzColors, intent, outputBuffer, reporter)) - { - PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); - } -} - -PDFColorSpacePointer PDFCalGrayColorSpace::createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) -{ - // Standard D65 white point - PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; - PDFColor3 blackPoint = { 0, 0, 0 }; - PDFColorComponent gamma = 1.0f; - - PDFDocumentDataLoaderDecorator loader(document); - loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); - loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); - gamma = loader.readNumberFromDictionary(dictionary, CAL_GAMMA, gamma); - - return PDFColorSpacePointer(new PDFCalGrayColorSpace(whitePoint, blackPoint, gamma)); -} - -PDFCalRGBColorSpace::PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix) : - PDFXYZColorSpace(whitePoint), - m_blackPoint(blackPoint), - m_gamma(gamma), - m_matrix(matrix) -{ - -} - -bool PDFCalRGBColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFXYZColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFCalRGBColorSpace* typedOther = static_cast(other); - return m_blackPoint == typedOther->getBlackPoint() && m_gamma == typedOther->getGamma() && m_matrix == typedOther->getMatrix(); -} - -QColor PDFCalRGBColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(color.size() == getColorComponentCount()); - Q_UNUSED(isRange01); - - const PDFColor3 ABC = clip01(PDFColor3{ color[0], color[1], color[2] }); - const PDFColor3 ABCwithGamma = colorPowerByFactors(ABC, m_gamma); - const PDFColor3 XYZ = m_matrix * ABCwithGamma; - - QColor cmsColor = cms->getColorFromXYZ(m_whitePoint, XYZ, intent, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - const PDFColor3 rgb = convertXYZtoRGB(XYZ); - const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); - return fromRGB01(calibratedRGB); -} - -size_t PDFCalRGBColorSpace::getColorComponentCount() const -{ - return 3; -} - -void PDFCalRGBColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - std::vector xyzColors(colors.size(), 0.0f); - auto it = xyzColors.begin(); - for (size_t i = 0; i < colors.size(); i += 3) - { - Q_ASSERT(i + 2 < colors.size()); - const PDFColor3 ABC = clip01(PDFColor3{ colors[i + 0], colors[i + 1], colors[i + 2] }); - const PDFColor3 ABCwithGamma = colorPowerByFactors(ABC, m_gamma); - const PDFColor3 XYZ = m_matrix * ABCwithGamma; - - *it++ = XYZ[0]; - *it++ = XYZ[1]; - *it++ = XYZ[2]; - } - - Q_ASSERT(xyzColors.size() == colors.size()); - if (!cms->fillRGBBufferFromXYZ(m_whitePoint, xyzColors, intent, outputBuffer, reporter)) - { - PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); - } -} - -PDFColorSpacePointer PDFCalRGBColorSpace::createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) -{ - // Standard D65 white point - PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; - PDFColor3 blackPoint = { 0, 0, 0 }; - PDFColor3 gamma = { 1.0f, 1.0f, 1.0f }; - PDFColorComponentMatrix_3x3 matrix( 1, 0, 0, - 0, 1, 0, - 0, 0, 1 ); - - PDFDocumentDataLoaderDecorator loader(document); - loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); - loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); - loader.readNumberArrayFromDictionary(dictionary, CAL_GAMMA, gamma.begin(), gamma.end()); - loader.readNumberArrayFromDictionary(dictionary, CAL_MATRIX, matrix.begin(), matrix.end()); - - matrix.transpose(); - - return PDFColorSpacePointer(new PDFCalRGBColorSpace(whitePoint, blackPoint, gamma, matrix)); -} - -PDFColor3 PDFCalRGBColorSpace::getBlackPoint() const -{ - return m_blackPoint; -} - -PDFColor3 PDFCalRGBColorSpace::getGamma() const -{ - return m_gamma; -} - -const PDFColorComponentMatrix_3x3& PDFCalRGBColorSpace::getMatrix() const -{ - return m_matrix; -} - -PDFLabColorSpace::PDFLabColorSpace(PDFColor3 whitePoint, - PDFColor3 blackPoint, - PDFColorComponent aMin, - PDFColorComponent aMax, - PDFColorComponent bMin, - PDFColorComponent bMax) : - PDFXYZColorSpace(whitePoint), - m_blackPoint(blackPoint), - m_aMin(aMin), - m_aMax(aMax), - m_bMin(bMin), - m_bMax(bMax) -{ - -} - -bool PDFLabColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFXYZColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFLabColorSpace* typedOther = static_cast(other); - return m_blackPoint == typedOther->getBlackPoint() && - m_aMin == typedOther->getAMin() && m_aMax == typedOther->getAMax() && - m_bMin == typedOther->getBMin() && m_bMax == typedOther->getBMax(); -} - -QColor PDFLabColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(color.size() == getColorComponentCount()); - - PDFColorComponent LStar = 0; - PDFColorComponent aStar = 0; - PDFColorComponent bStar = 0; - - if (isRange01) - { - LStar = qBound(0.0, interpolate(color[0], 0.0, 1.0, 0.0, 100.0), 100.0); - aStar = qBound(m_aMin, interpolate(color[1], 0.0, 1.0, m_aMin, m_aMax), m_aMax); - bStar = qBound(m_bMin, interpolate(color[2], 0.0, 1.0, m_bMin, m_bMax), m_bMax); - } - else - { - LStar = qBound(0.0, color[0], 100.0); - aStar = qBound(m_aMin, color[1], m_aMax); - bStar = qBound(m_bMin, color[2], m_bMax); - } - - const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; - const PDFColorComponent param2 = aStar / 500.0f; - const PDFColorComponent param3 = bStar / 200.0f; - - const PDFColorComponent L = param1 + param2; - const PDFColorComponent M = param1; - const PDFColorComponent N = param1 - param3; - - auto g = [](PDFColorComponent x) -> PDFColorComponent - { - if (x >= 6.0f / 29.0f) - { - return x * x * x; - } - else - { - return (108.0f / 841.0f) * (x - 4.0f / 29.0f); - } - }; - - const PDFColorComponent gL = g(L); - const PDFColorComponent gM = g(M); - const PDFColorComponent gN = g(N); - const PDFColor3 gLMN = { gL, gM, gN }; - - QColor cmsColor = cms->getColorFromXYZ(m_whitePoint, gLMN, intent, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - const PDFColor3 XYZ = colorMultiplyByFactors(m_whitePoint, gLMN); - const PDFColor3 rgb = convertXYZtoRGB(XYZ); - const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); - return fromRGB01(calibratedRGB); -} - -size_t PDFLabColorSpace::getColorComponentCount() const -{ - return 3; -} - -void PDFLabColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - auto g = [](PDFColorComponent x) -> PDFColorComponent - { - if (x >= 6.0f / 29.0f) - { - return x * x * x; - } - else - { - return (108.0f / 841.0f) * (x - 4.0f / 29.0f); - } - }; - - std::vector xyzColors(colors.size(), 0.0f); - auto it = xyzColors.begin(); - for (size_t i = 0; i < colors.size(); i += 3) - { - Q_ASSERT(i + 2 < colors.size()); - - const PDFColorComponent LStar = qBound(0.0, interpolate(colors[i + 0], 0.0, 1.0, 0.0, 100.0), 100.0); - const PDFColorComponent aStar = qBound(m_aMin, interpolate(colors[i + 1], 0.0, 1.0, m_aMin, m_aMax), m_aMax); - const PDFColorComponent bStar = qBound(m_bMin, interpolate(colors[i + 2], 0.0, 1.0, m_bMin, m_bMax), m_bMax); - - const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; - const PDFColorComponent param2 = aStar / 500.0f; - const PDFColorComponent param3 = bStar / 200.0f; - - const PDFColorComponent L = param1 + param2; - const PDFColorComponent M = param1; - const PDFColorComponent N = param1 - param3; - - *it++ = g(L); - *it++ = g(M); - *it++ = g(N); - } - - Q_ASSERT(xyzColors.size() == colors.size()); - if (!cms->fillRGBBufferFromXYZ(m_whitePoint, xyzColors, intent, outputBuffer, reporter)) - { - PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); - } -} - -PDFColorSpacePointer PDFLabColorSpace::createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) -{ - // Standard D65 white point - PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; - PDFColor3 blackPoint = { 0, 0, 0 }; - - static_assert(std::numeric_limits::has_infinity, "Fix this code!"); - const PDFColorComponent infPos = std::numeric_limits::infinity(); - const PDFColorComponent infNeg = -std::numeric_limits::infinity(); - std::array minMax = { infNeg, infPos, infNeg, infPos }; - - PDFDocumentDataLoaderDecorator loader(document); - loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); - loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); - loader.readNumberArrayFromDictionary(dictionary, CAL_RANGE, minMax.begin(), minMax.end()); - - return PDFColorSpacePointer(new PDFLabColorSpace(whitePoint, blackPoint, minMax[0], minMax[1], minMax[2], minMax[3])); -} - -PDFColorComponent PDFLabColorSpace::getAMin() const -{ - return m_aMin; -} - -PDFColorComponent PDFLabColorSpace::getAMax() const -{ - return m_aMax; -} - -PDFColorComponent PDFLabColorSpace::getBMin() const -{ - return m_bMin; -} - -PDFColorComponent PDFLabColorSpace::getBMax() const -{ - return m_bMax; -} - -PDFColor3 PDFLabColorSpace::getBlackPoint() const -{ - return m_blackPoint; -} - -PDFICCBasedColorSpace::PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range, QByteArray iccProfileData, PDFObjectReference metadata) : - m_alternateColorSpace(qMove(alternateColorSpace)), - m_range(range), - m_iccProfileData(qMove(iccProfileData)), - m_metadata(metadata) -{ - // Compute checksum - m_iccProfileDataChecksum = QCryptographicHash::hash(m_iccProfileData, QCryptographicHash::Md5); -} - -PDFColor PDFICCBasedColorSpace::getDefaultColorOriginal() const -{ - PDFColor color; - const size_t componentCount = getColorComponentCount(); - for (size_t i = 0; i < componentCount; ++i) - { - color.push_back(0.0f); - } - return color; -} - -QColor PDFICCBasedColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_ASSERT(color.size() == getColorComponentCount()); - Q_UNUSED(isRange01); - - size_t colorComponentCount = getColorComponentCount(); - - // Clip color values by range - PDFColor clippedColor = color; - for (size_t i = 0; i < colorComponentCount; ++i) - { - const size_t imin = 2 * i + 0; - const size_t imax = 2 * i + 1; - clippedColor[i] = qBound(m_range[imin], clippedColor[i], m_range[imax]); - } - - QColor cmsColor = cms->getColorFromICC(clippedColor, intent, m_iccProfileDataChecksum, m_iccProfileData, reporter); - if (cmsColor.isValid()) - { - return cmsColor; - } - - return m_alternateColorSpace->getColor(clippedColor, cms, intent, reporter, true); -} - -size_t PDFICCBasedColorSpace::getColorComponentCount() const -{ - return m_alternateColorSpace->getColorComponentCount(); -} - -void PDFICCBasedColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const -{ - size_t colorComponentCount = getColorComponentCount(); - std::vector clippedColors(colors.size(), 0.0f); - for (size_t i = 0, colorCount = colors.size(); i < colorCount; ++i) - { - const size_t componentIndex = i % colorComponentCount; - const size_t imin = 2 * componentIndex + 0; - const size_t imax = 2 * componentIndex + 1; - clippedColors[i] = qBound(m_range[imin], colors[i], m_range[imax]); - } - - if (!cms->fillRGBBufferFromICC(clippedColors, intent, outputBuffer, m_iccProfileDataChecksum, m_iccProfileData, reporter)) - { - // Try to fill buffer from alternate color space - m_alternateColorSpace->fillRGBBuffer(clippedColors, outputBuffer, intent, cms, reporter); - } -} - -bool PDFICCBasedColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFAbstractColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFICCBasedColorSpace* typedOther = static_cast(other); - - const PDFAbstractColorSpace* alternateColorSpace = typedOther->getAlternateColorSpace(); - - if (static_cast(m_alternateColorSpace.data()) != static_cast(alternateColorSpace)) - { - return false; - } - - if (m_alternateColorSpace && alternateColorSpace && !m_alternateColorSpace->equals(alternateColorSpace)) - { - return false; - } - - return m_range == typedOther->getRange() && m_iccProfileDataChecksum == typedOther->getIccProfileDataChecksum(); -} - -PDFColorSpacePointer PDFICCBasedColorSpace::createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFStream* stream, - int recursion, - std::set& usedNames) -{ - // First, try to load alternate color space, if it is present - const PDFDictionary* dictionary = stream->getDictionary(); - QByteArray iccProfileData = document->getDecodedStream(stream); - - PDFDocumentDataLoaderDecorator loader(document); - PDFColorSpacePointer alternateColorSpace; - if (dictionary->hasKey(ICCBASED_ALTERNATE)) - { - alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(dictionary->get(ICCBASED_ALTERNATE)), recursion, usedNames); - } - else - { - // Determine color space from parameter N, which determines number of components - const PDFInteger N = loader.readIntegerFromDictionary(dictionary, ICCBASED_N, 0); - - switch (N) - { - case 1: - { - alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(COLOR_SPACE_NAME_DEVICE_GRAY), recursion, usedNames); - break; - } - - case 3: - { - alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(COLOR_SPACE_NAME_DEVICE_RGB), recursion, usedNames); - break; - } - - case 4: - { - alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(COLOR_SPACE_NAME_DEVICE_CMYK), recursion, usedNames); - break; - } - - default: - { - throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile. Number of components is %1.").arg(N)); - break; - } - } - } - - if (!alternateColorSpace) - { - throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile.")); - } - - Ranges ranges = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; - static_assert(ranges.size() == 8, "Fix initialization above!"); - - const size_t components = alternateColorSpace->getColorComponentCount(); - const size_t rangeSize = 2 * components; - - if (rangeSize > ranges.size()) - { - throw PDFException(PDFTranslationContext::tr("Too much color components for ICC based profile.")); - } - - auto itStart = ranges.begin(); - auto itEnd = std::next(itStart, rangeSize); - loader.readNumberArrayFromDictionary(dictionary, ICCBASED_RANGE, itStart, itEnd); - - return PDFColorSpacePointer(new PDFICCBasedColorSpace(qMove(alternateColorSpace), ranges, qMove(iccProfileData), loader.readReferenceFromDictionary(dictionary, "Metadata"))); -} - -const PDFICCBasedColorSpace::Ranges& PDFICCBasedColorSpace::getRange() const -{ - return m_range; -} - -const QByteArray& PDFICCBasedColorSpace::getIccProfileData() const -{ - return m_iccProfileData; -} - -const QByteArray& PDFICCBasedColorSpace::getIccProfileDataChecksum() const -{ - return m_iccProfileDataChecksum; -} - -const PDFAbstractColorSpace* PDFICCBasedColorSpace::getAlternateColorSpace() const -{ - return m_alternateColorSpace.data(); -} - -PDFIndexedColorSpace::PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue) : - m_baseColorSpace(qMove(baseColorSpace)), - m_colors(qMove(colors)), - m_maxValue(maxValue) -{ - -} - -bool PDFIndexedColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFAbstractColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFIndexedColorSpace* typedOther = static_cast(other); - - const PDFAbstractColorSpace* baseColorSpace = typedOther->getBaseColorSpace().data(); - - if (static_cast(m_baseColorSpace.data()) != static_cast(baseColorSpace)) - { - return false; - } - - if (m_baseColorSpace && baseColorSpace && !m_baseColorSpace->equals(baseColorSpace)) - { - return false; - } - - return m_colors == typedOther->getColors() && m_maxValue == typedOther->getMaxValue(); -} - -PDFColor PDFIndexedColorSpace::getDefaultColorOriginal() const -{ - return PDFColor(0.0f); -} - -QColor PDFIndexedColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - // Indexed color space value must have exactly one component! - Q_ASSERT(color.size() == 1); - Q_UNUSED(isRange01); - - const int colorIndex = qBound(MIN_VALUE, static_cast(color[0]), m_maxValue); - const int componentCount = static_cast(m_baseColorSpace->getColorComponentCount()); - const int byteOffset = colorIndex * componentCount; - - // We must point into the array. Check first and last component. - Q_ASSERT(byteOffset + componentCount - 1 < m_colors.size()); - - PDFColor decodedColor; - const char* bytePointer = m_colors.constData() + byteOffset; - - for (int i = 0; i < componentCount; ++i) - { - const unsigned char value = *bytePointer++; - const PDFColorComponent component = static_cast(value) / 255.0f; - decodedColor.push_back(component); - } - - return m_baseColorSpace->getColor(decodedColor, cms, intent, reporter, true); -} - -size_t PDFIndexedColorSpace::getColorComponentCount() const -{ - return 1; -} - -QImage PDFIndexedColorSpace::getImage(const PDFImageData& imageData, - const PDFImageData& softMask, - const PDFCMS* cms, - RenderingIntent intent, - PDFRenderErrorReporter* reporter) const -{ - if (imageData.isValid()) - { - switch (imageData.getMaskingType()) - { - case PDFImageData::MaskingType::None: - { - QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888); - image.fill(QColor(Qt::white)); - - unsigned int componentCount = imageData.getComponents(); - PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); - - if (componentCount != getColorComponentCount()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); - } - - Q_ASSERT(componentCount == 1); - - PDFColor color; - color.resize(1); - - for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) - { - reader.seek(i * imageData.getStride()); - unsigned char* outputLine = image.scanLine(i); - - for (unsigned int j = 0; j < imageData.getWidth(); ++j) - { - PDFBitReader::Value index = reader.read(); - color[0] = index; - - QColor transformedColor = getColor(color, cms, intent, reporter, false); - QRgb rgb = transformedColor.rgb(); - - *outputLine++ = qRed(rgb); - *outputLine++ = qGreen(rgb); - *outputLine++ = qBlue(rgb); - } - } - - return image; - } - - case PDFImageData::MaskingType::SoftMask: - { - const bool hasMatte = !softMask.getMatte().empty(); - QImage image(imageData.getWidth(), imageData.getHeight(), hasMatte ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888); - - unsigned int componentCount = imageData.getComponents(); - PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); - - if (componentCount != getColorComponentCount()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); - } - - Q_ASSERT(componentCount == 1); - - PDFColor color; - color.resize(1); - - QImage alphaMask = createAlphaMask(softMask); - if (alphaMask.size() != image.size()) - { - // Scale the alpha mask, if it is masked - alphaMask = alphaMask.scaled(image.size()); - } - - for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) - { - reader.seek(i * imageData.getStride()); - unsigned char* outputLine = image.scanLine(i); - unsigned char* alphaLine = alphaMask.scanLine(i); - - for (unsigned int j = 0; j < imageData.getWidth(); ++j) - { - PDFBitReader::Value index = reader.read(); - color[0] = index; - - QColor transformedColor = getColor(color, cms, intent, reporter, false); - QRgb rgb = transformedColor.rgb(); - - *outputLine++ = qRed(rgb); - *outputLine++ = qGreen(rgb); - *outputLine++ = qBlue(rgb); - *outputLine++ = *alphaLine++; - } - } - - return image; - } - - default: - throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image masking not implemented!")); - } - } - - return QImage(); -} - -PDFColorSpacePointer PDFIndexedColorSpace::createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFArray* array, - int recursion, - std::set& usedNames) -{ - Q_ASSERT(array->getCount() == 4); - - // Read base color space - PDFColorSpacePointer baseColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(1)), recursion, usedNames); - - if (!baseColorSpace) - { - throw PDFException(PDFTranslationContext::tr("Can't determine base color space for indexed color space.")); - } - - // Read maximum value - PDFDocumentDataLoaderDecorator loader(document); - const int maxValue = qBound(MIN_VALUE, loader.readInteger(array->getItem(2), 0), MAX_VALUE); - - // Read stream/byte string with corresponding color values - QByteArray colors; - const PDFObject& colorDataObject = document->getObject(array->getItem(3)); - - if (colorDataObject.isString()) - { - colors = colorDataObject.getString(); - } - else if (colorDataObject.isStream()) - { - colors = document->getDecodedStream(colorDataObject.getStream()); - } - - // Check, if we have enough colors - const int colorCount = maxValue - MIN_VALUE + 1; - const int componentCount = static_cast(baseColorSpace->getColorComponentCount()); - const int byteCount = colorCount * componentCount; - if (byteCount > colors.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors, %2 color components and must have %3 size. Provided size is %4.").arg(colorCount).arg(componentCount).arg(byteCount).arg(colors.size())); - } - - return PDFColorSpacePointer(new PDFIndexedColorSpace(qMove(baseColorSpace), qMove(colors), maxValue)); -} - -PDFColorSpacePointer PDFIndexedColorSpace::getBaseColorSpace() const -{ - return m_baseColorSpace; -} - -std::vector PDFIndexedColorSpace::transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const -{ - const std::size_t colorComponentCount = m_baseColorSpace->getColorComponentCount(); - std::vector result(buffer.size() * colorComponentCount, 0.0f); - - auto outputIt = result.begin(); - for (PDFColorComponent input : buffer) - { - const int colorIndex = qBound(MIN_VALUE, static_cast(input), m_maxValue); - const int byteOffset = int(colorIndex * colorComponentCount); - - // We must point into the array. Check first and last component. - Q_ASSERT(byteOffset + colorComponentCount - 1 < m_colors.size()); - - const char* bytePointer = m_colors.constData() + byteOffset; - - for (int i = 0; i < colorComponentCount; ++i) - { - const unsigned char value = *bytePointer++; - const PDFColorComponent component = static_cast(value) / 255.0f; - Q_ASSERT(outputIt != result.cend()); - *outputIt++ = component; - } - } - Q_ASSERT(outputIt == result.cend()); - - return result; -} - -int PDFIndexedColorSpace::getMaxValue() const -{ - return m_maxValue; -} - -const QByteArray& PDFIndexedColorSpace::getColors() const -{ - return m_colors; -} - -PDFSeparationColorSpace::PDFSeparationColorSpace(QByteArray&& colorName, PDFColorSpacePointer alternateColorSpace, PDFFunctionPtr tintTransform) : - m_colorName(qMove(colorName)), - m_alternateColorSpace(qMove(alternateColorSpace)), - m_tintTransform(qMove(tintTransform)), - m_isNone(m_colorName == "None"), - m_isAll(m_colorName == "All") -{ - -} - -bool PDFSeparationColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFAbstractColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFSeparationColorSpace* typedOther = static_cast(other); - - const PDFAbstractColorSpace* alternateColorSpace = typedOther->getAlternateColorSpace().data(); - - if (static_cast(m_alternateColorSpace.data()) != static_cast(alternateColorSpace)) - { - return false; - } - - if (m_alternateColorSpace && alternateColorSpace && !m_alternateColorSpace->equals(alternateColorSpace)) - { - return false; - } - - return m_colorName == typedOther->getColorName(); -} - -PDFColor PDFSeparationColorSpace::getDefaultColorOriginal() const -{ - return PDFColor(1.0f); -} - -QColor PDFSeparationColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - // Separation color space value must have exactly one component! - Q_ASSERT(color.size() == 1); - Q_UNUSED(isRange01); - - // According to the PDF 2.0 specification, separation color space, with colorant name "None" - // should not produce any visible output. - if (m_isNone) - { - return Qt::transparent; - } - - // Input value - double tint = color.back(); - - // Jakub Melka: According to the PDF 2.0 specification, separation color space, with colorant name "All" - // should apply tint value to all output colorants, alternate color space and tint function should - // be ignored, and because QColor is aditive, we must invert the tint value. - if (m_isAll) - { - const double inversedTint = qBound(0.0, 1.0 - tint, 1.0); - return QColor::fromRgbF(inversedTint, inversedTint, inversedTint); - } - - // Output values - std::vector outputColor; - outputColor.resize(m_alternateColorSpace->getColorComponentCount(), 0.0); - PDFFunction::FunctionResult result = m_tintTransform->apply(&tint, &tint + 1, outputColor.data(), outputColor.data() + outputColor.size()); - - if (result) - { - PDFColor color; - std::for_each(outputColor.cbegin(), outputColor.cend(), [&color](double value) { color.push_back(static_cast(value)); }); - return m_alternateColorSpace->getColor(color, cms, intent, reporter, false); - } - else - { - // Return invalid color - return QColor(); - } -} - -size_t PDFSeparationColorSpace::getColorComponentCount() const -{ - return 1; -} - -std::vector PDFSeparationColorSpace::transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const -{ - const std::size_t colorComponentCount = m_alternateColorSpace->getColorComponentCount(); - std::vector result(buffer.size() * colorComponentCount, 0.0f); - - std::vector outputColor; - outputColor.resize(colorComponentCount, 0.0); - - auto outputIt = result.begin(); - for (PDFColorComponent input : buffer) - { - // Input value - double tint = input; - - Q_ASSERT(outputIt + (colorComponentCount - 1) != result.cend()); - - if (m_isAll) - { - const double inversedTint = qBound(0.0, 1.0 - tint, 1.0); - std::fill(outputIt, outputIt + colorComponentCount, inversedTint); - } - else - { - m_tintTransform->apply(&tint, &tint + 1, outputColor.data(), outputColor.data() + outputColor.size()); - std::copy(outputColor.cbegin(), outputColor.cend(), outputIt); - } - - outputIt = std::next(outputIt, colorComponentCount); - } - Q_ASSERT(outputIt == result.cend()); - - return result; -} - -PDFColorSpacePointer PDFSeparationColorSpace::createSeparationColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFArray* array, - int recursion, - std::set& usedNames) -{ - Q_ASSERT(array->getCount() == 4); - - // Read color name - const PDFObject& colorNameObject = document->getObject(array->getItem(1)); - if (!colorNameObject.isName()) - { - throw PDFException(PDFTranslationContext::tr("Can't determine color name for separation color space.")); - } - QByteArray colorName = colorNameObject.getString(); - - // Read alternate color space - PDFColorSpacePointer alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(2)), recursion, usedNames); - if (!alternateColorSpace) - { - throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for separation color space.")); - } - - PDFFunctionPtr tintTransform = PDFFunction::createFunction(document, array->getItem(3)); - if (!tintTransform) - { - throw PDFException(PDFTranslationContext::tr("Can't determine tint transform for separation color space.")); - } - - return PDFColorSpacePointer(new PDFSeparationColorSpace(qMove(colorName), qMove(alternateColorSpace), qMove(tintTransform))); -} - -PDFColorSpacePointer PDFSeparationColorSpace::getAlternateColorSpace() const -{ - return m_alternateColorSpace; -} - -const QByteArray& PDFSeparationColorSpace::getColorName() const -{ - return m_colorName; -} - -const unsigned char* PDFImageData::getRow(unsigned int rowIndex) const -{ - const unsigned char* data = reinterpret_cast(m_data.constData()); - - Q_ASSERT(rowIndex < m_height); - return data + (rowIndex * m_stride); -} - -bool PDFPatternColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - // Compare pointers - return this == other; -} - -QColor PDFPatternColorSpace::getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const -{ - Q_UNUSED(cms); - Q_UNUSED(intent); - Q_UNUSED(reporter); - - return QColor(Qt::transparent); -} - -PDFColor PDFPatternColorSpace::getDefaultColorOriginal() const -{ - return PDFColor(); -} - -QColor PDFPatternColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_UNUSED(color); - Q_UNUSED(cms); - Q_UNUSED(intent); - Q_UNUSED(reporter); - Q_UNUSED(isRange01); - - throw PDFException(PDFTranslationContext::tr("Pattern doesn't have defined uniform color.")); -} - -size_t PDFPatternColorSpace::getColorComponentCount() const -{ - return 0; -} - -PDFDeviceNColorSpace::PDFDeviceNColorSpace(PDFDeviceNColorSpace::Type type, - PDFDeviceNColorSpace::Colorants&& colorants, - PDFColorSpacePointer alternateColorSpace, - PDFColorSpacePointer processColorSpace, - PDFFunctionPtr tintTransform, - std::vector&& colorantsPrintingOrder, - std::vector processColorSpaceComponents) : - m_type(type), - m_colorants(qMove(colorants)), - m_alternateColorSpace(qMove(alternateColorSpace)), - m_processColorSpace(qMove(processColorSpace)), - m_tintTransform(qMove(tintTransform)), - m_colorantsPrintingOrder(qMove(colorantsPrintingOrder)), - m_processColorSpaceComponents(qMove(processColorSpaceComponents)), - m_isNone(false) -{ - m_isNone = std::all_of(m_colorants.cbegin(), m_colorants.cend(), [](const auto& colorant) { return colorant.name == "None"; }); -} - -bool PDFDeviceNColorSpace::equals(const PDFAbstractColorSpace* other) const -{ - if (!PDFAbstractColorSpace::equals(other)) - { - return false; - } - - Q_ASSERT(dynamic_cast(other)); - const PDFDeviceNColorSpace* typedOther = static_cast(other); - - const PDFAbstractColorSpace* alternateColorSpace = typedOther->getAlternateColorSpace().data(); - - if (static_cast(m_alternateColorSpace.data()) != static_cast(alternateColorSpace)) - { - return false; - } - - if (m_alternateColorSpace && alternateColorSpace && !m_alternateColorSpace->equals(alternateColorSpace)) - { - return false; - } - - const Colorants& colorants = typedOther->getColorants(); - - if (m_colorants.size() != colorants.size()) - { - return false; - } - - for (size_t i = 0; i < m_colorants.size(); ++i) - { - if (m_colorants[i].name != colorants[i].name) - { - return false; - } - } - - return true; -} - -PDFColor PDFDeviceNColorSpace::getDefaultColorOriginal() const -{ - PDFColor color; - color.resize(getColorComponentCount()); - - // Jakub Melka: According to the PDF 2.0 specification, each channel should - // be initially set to 1.0. - for (size_t i = 0, colorComponentCount = color.size(); i < colorComponentCount; ++i) - { - color[i] = 1.0; - } - - return color; -} - -QColor PDFDeviceNColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const -{ - Q_UNUSED(isRange01); - - // According to the PDF 2.0 specification, DeviceN color space, with all colorant name "None" - // should not produce any visible output. - if (m_isNone) - { - return Qt::transparent; - } - - // Input values - std::vector inputColor(color.size(), 0.0); - for (size_t i = 0, count = inputColor.size(); i < count; ++i) - { - inputColor[i] = color[i]; - } - - // Output values - std::vector outputColor; - outputColor.resize(m_alternateColorSpace->getColorComponentCount(), 0.0); - PDFFunction::FunctionResult result = m_tintTransform->apply(inputColor.data(), inputColor.data() + inputColor.size(), outputColor.data(), outputColor.data() + outputColor.size()); - - if (result) - { - PDFColor color; - std::for_each(outputColor.cbegin(), outputColor.cend(), [&color](double value) { color.push_back(static_cast(value)); }); - return m_alternateColorSpace->getColor(color, cms, intent, reporter, false); - } - else - { - // Return invalid color - return QColor(); - } -} - -size_t PDFDeviceNColorSpace::getColorComponentCount() const -{ - return m_colorants.size(); -} - -std::vector PDFDeviceNColorSpace::transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const -{ - std::vector result; - - const std::size_t colorantCount = getColorComponentCount(); - if (colorantCount > 0) - { - const std::size_t inputColorCount = buffer.size() / colorantCount; - const std::size_t alternateColorSpaceComponentCount = m_alternateColorSpace->getColorComponentCount(); - result.resize(inputColorCount * alternateColorSpaceComponentCount, 0.0f); - - std::vector inputColor(colorantCount, 0.0); - std::vector outputColor(alternateColorSpaceComponentCount, 0.0); - - auto outputIt = result.begin(); - for (auto it = buffer.begin(); it != buffer.end(); it = std::next(it, colorantCount)) - { - std::copy(it, it + colorantCount, inputColor.begin()); - m_tintTransform->apply(inputColor.data(), inputColor.data() + inputColor.size(), outputColor.data(), outputColor.data() + outputColor.size()); - std::copy(outputColor.cbegin(), outputColor.cend(), outputIt); - outputIt = std::next(outputIt, alternateColorSpaceComponentCount); - } - Q_ASSERT(outputIt == result.cend()); - } - - return result; -} - -PDFColorSpacePointer PDFDeviceNColorSpace::createDeviceNColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFArray* array, - int recursion, - std::set& usedNames) -{ - Q_ASSERT(array->getCount() >= 4); - - PDFDocumentDataLoaderDecorator loader(document); - std::vector colorantNames = loader.readNameArray(array->getItem(1)); - - if (colorantNames.empty()) - { - throw PDFException(PDFTranslationContext::tr("Invalid colorants for DeviceN color space.")); - } - - std::vector colorants; - colorants.resize(colorantNames.size()); - for (size_t i = 0; i < colorantNames.size(); ++i) - { - colorants[i].name = qMove(colorantNames[i]); - } - - // Read alternate color space - PDFColorSpacePointer alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(2)), recursion, usedNames); - if (!alternateColorSpace) - { - throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for DeviceN color space.")); - } - - PDFFunctionPtr tintTransform = PDFFunction::createFunction(document, array->getItem(3)); - if (!tintTransform) - { - throw PDFException(PDFTranslationContext::tr("Can't determine tint transform for DeviceN color space.")); - } - - Type type = Type::DeviceN; - std::vector colorantsPrintingOrder; - PDFColorSpacePointer processColorSpace; - std::vector processColorSpaceComponents; - - // Now, check, if we have attributes, and if yes, then read them - if (array->getCount() == 5) - { - const PDFObject& object = document->getObject(array->getItem(4)); - if (object.isDictionary()) - { - const PDFDictionary* attributesDictionary = object.getDictionary(); - QByteArray subtype = loader.readNameFromDictionary(attributesDictionary, "Subtype"); - if (subtype == "NChannel") - { - type = Type::NChannel; - } - - const PDFObject& colorantsObject = document->getObject(attributesDictionary->get("Colorants")); - if (colorantsObject.isDictionary()) - { - const PDFDictionary* colorantsDictionary = colorantsObject.getDictionary(); - - // Separation color spaces for each colorant - for (ColorantInfo& colorantInfo : colorants) - { - if (colorantsDictionary->hasKey(colorantInfo.name)) - { - colorantInfo.separationColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorantsDictionary->get(colorantInfo.name)), recursion, usedNames); - } - } - } - - const PDFObject& mixingHints = document->getObject(attributesDictionary->get("MixingHints")); - if (mixingHints.isDictionary()) - { - const PDFDictionary* mixingHintsDictionary = mixingHints.getDictionary(); - - // Printing order - colorantsPrintingOrder = loader.readNameArray(mixingHintsDictionary->get("PrintingOrder")); - - // Solidities - const PDFObject& solidityObject = document->getObject(mixingHintsDictionary->get("Solidites")); - if (solidityObject.isDictionary()) - { - const PDFDictionary* solidityDictionary = solidityObject.getDictionary(); - const PDFReal defaultSolidity = loader.readNumberFromDictionary(solidityDictionary, "Default", 0.0); - for (ColorantInfo& colorantInfo : colorants) - { - colorantInfo.solidity = loader.readNumberFromDictionary(solidityDictionary, colorantInfo.name, defaultSolidity); - } - } - - // Dot gain - const PDFObject& dotGainObject = document->getObject(mixingHintsDictionary->get("DotGain")); - if (dotGainObject.isDictionary()) - { - const PDFDictionary* dotGainDictionary = dotGainObject.getDictionary(); - for (ColorantInfo& colorantInfo : colorants) - { - const PDFObject& dotGainFunctionObject = document->getObject(dotGainDictionary->get(colorantInfo.name)); - if (!dotGainFunctionObject.isNull()) - { - colorantInfo.dotGain = PDFFunction::createFunction(document, dotGainFunctionObject); - } - } - } - } - - // Process - const PDFObject& processObject = document->getObject(attributesDictionary->get("Process")); - if (processObject.isDictionary()) - { - const PDFDictionary* processDictionary = processObject.getDictionary(); - const PDFObject& processColorSpaceObject = document->getObject(processDictionary->get("ColorSpace")); - if (!processColorSpaceObject.isNull()) - { - processColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, processColorSpaceObject, recursion, usedNames); - processColorSpaceComponents = loader.readNameArrayFromDictionary(processDictionary, "Components"); - } - } - } - } - - return PDFColorSpacePointer(new PDFDeviceNColorSpace(type, qMove(colorants), qMove(alternateColorSpace), qMove(processColorSpace), qMove(tintTransform), qMove(colorantsPrintingOrder), qMove(processColorSpaceComponents))); -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfcolorspaces.h" +#include "pdfobject.h" +#include "pdfdocument.h" +#include "pdfexception.h" +#include "pdfutils.h" +#include "pdfpattern.h" +#include "pdfcms.h" +#include "pdfexecutionpolicy.h" + +#include + +#include + +namespace pdf +{ + +static PDFColorComponent getDeterminant(const PDFColorComponentMatrix_3x3& matrix) +{ + const PDFColorComponent a_11 = matrix.getValue(0, 0); + const PDFColorComponent a_12 = matrix.getValue(0, 1); + const PDFColorComponent a_13 = matrix.getValue(0, 2); + const PDFColorComponent a_21 = matrix.getValue(1, 0); + const PDFColorComponent a_22 = matrix.getValue(1, 1); + const PDFColorComponent a_23 = matrix.getValue(1, 2); + const PDFColorComponent a_31 = matrix.getValue(2, 0); + const PDFColorComponent a_32 = matrix.getValue(2, 1); + const PDFColorComponent a_33 = matrix.getValue(2, 2); + + return -a_13* a_22 * a_31 + a_12 * a_23 * a_31 + a_13 * a_21 * a_32 - a_11 * a_23 * a_32 - a_12 * a_21 * a_33 + a_11 * a_22 * a_33; +} + +PDFColorComponentMatrix_3x3 getInverseMatrix(const PDFColorComponentMatrix_3x3& matrix) +{ + const PDFColorComponent a_11 = matrix.getValue(0, 0); + const PDFColorComponent a_12 = matrix.getValue(0, 1); + const PDFColorComponent a_13 = matrix.getValue(0, 2); + const PDFColorComponent a_21 = matrix.getValue(1, 0); + const PDFColorComponent a_22 = matrix.getValue(1, 1); + const PDFColorComponent a_23 = matrix.getValue(1, 2); + const PDFColorComponent a_31 = matrix.getValue(2, 0); + const PDFColorComponent a_32 = matrix.getValue(2, 1); + const PDFColorComponent a_33 = matrix.getValue(2, 2); + + const PDFColorComponent determinant = -a_13* a_22 * a_31 + a_12 * a_23 * a_31 + a_13 * a_21 * a_32 - a_11 * a_23 * a_32 - a_12 * a_21 * a_33 + a_11 * a_22 * a_33; + const PDFColorComponent coefficient = !qIsNull(determinant) ? 1.0 / determinant : 0.0; + + PDFColorComponentMatrix_3x3 inversedMatrix { a_22 * a_33 - a_23 * a_32, a_13 * a_32 - a_12 * a_33, a_12 * a_23 - a_13 * a_22, + a_23 * a_31 - a_21 * a_33, a_11 * a_33 - a_13 * a_31, a_13 * a_21 - a_11 * a_23, + a_21 * a_32 - a_22 * a_31, a_12 * a_31 - a_11 * a_32, a_11 * a_22 - a_12 * a_21 }; + inversedMatrix.multiplyByFactor(coefficient); + return inversedMatrix; +} + +PDFColor PDFDeviceGrayColorSpace::getDefaultColorOriginal() const +{ + return PDFColor(0.0f); +} + +QColor PDFDeviceGrayColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(cms); + Q_ASSERT(color.size() == getColorComponentCount()); + Q_UNUSED(isRange01); + + PDFColorComponent component = clip01(color[0]); + + // If color management system handles the color transformation, then use it, + // otherwise fall back to the generic case. + QColor cmsColor = cms->getColorFromDeviceGray(color, intent, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + QColor result(QColor::Rgb); + result.setRgbF(component, component, component, 1.0); + return result; +} + +size_t PDFDeviceGrayColorSpace::getColorComponentCount() const +{ + return 1; +} + +void PDFDeviceGrayColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + if (!cms->fillRGBBufferFromDeviceGray(colors, intent, outputBuffer, reporter)) + { + PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); + } +} + +PDFColor PDFDeviceRGBColorSpace::getDefaultColorOriginal() const +{ + return PDFColor(0.0f, 0.0f, 0.0f); +} + +QColor PDFDeviceRGBColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + Q_UNUSED(isRange01); + + PDFColorComponent r = clip01(color[0]); + PDFColorComponent g = clip01(color[1]); + PDFColorComponent b = clip01(color[2]); + + PDFColor clippedColor(r, g, b); + QColor cmsColor = cms->getColorFromDeviceRGB(clippedColor, intent, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + QColor result(QColor::Rgb); + result.setRgbF(r, g, b, 1.0); + return result; +} + +size_t PDFDeviceRGBColorSpace::getColorComponentCount() const +{ + return 3; +} + +void PDFDeviceRGBColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + if (!cms->fillRGBBufferFromDeviceRGB(colors, intent, outputBuffer, reporter)) + { + PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); + } +} + +PDFColor PDFDeviceCMYKColorSpace::getDefaultColorOriginal() const +{ + return PDFColor(0.0f, 0.0f, 0.0f, 1.0f); +} + +QColor PDFDeviceCMYKColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + Q_UNUSED(isRange01); + + PDFColorComponent c = clip01(color[0]); + PDFColorComponent m = clip01(color[1]); + PDFColorComponent y = clip01(color[2]); + PDFColorComponent k = clip01(color[3]); + + PDFColor clippedColor(c, m, y, k); + QColor cmsColor = cms->getColorFromDeviceCMYK(clippedColor, intent, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + QColor result(QColor::Cmyk); + result.setCmykF(c, m, y, k, 1.0); + return result; +} + +size_t PDFDeviceCMYKColorSpace::getColorComponentCount() const +{ + return 4; +} + +void PDFDeviceCMYKColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + if (!cms->fillRGBBufferFromDeviceCMYK(colors, intent, outputBuffer, reporter)) + { + PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); + } +} + +bool PDFAbstractColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + return getColorSpace() == other->getColorSpace(); +} + +bool PDFAbstractColorSpace::isBlendColorSpace() const +{ + switch (getColorSpace()) + { + case pdf::PDFAbstractColorSpace::ColorSpace::DeviceGray: + case pdf::PDFAbstractColorSpace::ColorSpace::DeviceRGB: + case pdf::PDFAbstractColorSpace::ColorSpace::DeviceCMYK: + case pdf::PDFAbstractColorSpace::ColorSpace::CalGray: + case pdf::PDFAbstractColorSpace::ColorSpace::CalRGB: + case pdf::PDFAbstractColorSpace::ColorSpace::ICCBased: + return true; + + default: + return false; + } +} + +QColor PDFAbstractColorSpace::getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + return getColor(getDefaultColorOriginal(), cms, intent, reporter, true); +} + +QImage PDFAbstractColorSpace::getImage(const PDFImageData& imageData, + const PDFImageData& softMask, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter) const +{ + if (imageData.isValid()) + { + switch (imageData.getMaskingType()) + { + case PDFImageData::MaskingType::None: + { + QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888); + image.fill(QColor(Qt::white)); + + unsigned int componentCount = imageData.getComponents(); + if (componentCount != getColorComponentCount()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); + } + + const std::vector& decode = imageData.getDecode(); + if (!decode.empty() && decode.size() != componentCount * 2) + { + throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); + } + + const unsigned int imageWidth = imageData.getWidth(); + const unsigned int imageHeight = imageData.getHeight(); + + QMutex exceptionMutex; + std::optional exception; + + auto transformPixelLine = [&](unsigned int i) + { + try + { + PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); + reader.seek(i * imageData.getStride()); + + const double max = reader.max(); + const double coefficient = 1.0 / max; + unsigned char* outputLine = image.scanLine(i); + + std::vector inputColors(imageWidth * componentCount, 0.0f); + auto itInputColor = inputColors.begin(); + for (unsigned int j = 0; j < imageData.getWidth(); ++j) + { + for (unsigned int k = 0; k < componentCount; ++k) + { + PDFReal value = reader.read(); + + // Interpolate value, if it is not empty + if (!decode.empty()) + { + *itInputColor++ = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); + } + else + { + *itInputColor++ = value * coefficient; + } + } + } + + fillRGBBuffer(inputColors, outputLine, intent, cms, reporter); + } + catch (PDFException lineException) + { + QMutexLocker lock(&exceptionMutex); + if (!exception) + { + exception = lineException; + } + } + }; + + auto range = PDFIntegerRange(0, imageHeight); + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), transformPixelLine); + + if (exception) + { + throw *exception; + } + + return image; + } + + case PDFImageData::MaskingType::SoftMask: + { + const bool hasMatte = !softMask.getMatte().empty(); + QImage image(imageData.getWidth(), imageData.getHeight(), hasMatte ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888); + image.fill(QColor(Qt::white)); + + unsigned int componentCount = imageData.getComponents(); + if (componentCount != getColorComponentCount()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); + } + + const std::vector& decode = imageData.getDecode(); + if (!decode.empty() && decode.size() != componentCount * 2) + { + throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); + } + + const unsigned int imageWidth = imageData.getWidth(); + const unsigned int imageHeight = imageData.getHeight(); + + QImage alphaMask = createAlphaMask(softMask); + if (alphaMask.size() != image.size()) + { + // Scale the alpha mask, if it is masked + alphaMask = alphaMask.scaled(image.size()); + } + + QMutex exceptionMutex; + std::optional exception; + + auto transformPixelLine = [&](unsigned int i) + { + try + { + PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); + reader.seek(i * imageData.getStride()); + + const double max = reader.max(); + const double coefficient = 1.0 / max; + unsigned char* outputLine = image.scanLine(i); + unsigned char* alphaLine = alphaMask.scanLine(i); + + std::vector inputColors(imageWidth * componentCount, 0.0f); + std::vector outputColors(imageWidth * 3, 0); + + auto itInputColor = inputColors.begin(); + for (unsigned int j = 0; j < imageData.getWidth(); ++j) + { + for (unsigned int k = 0; k < componentCount; ++k) + { + PDFReal value = reader.read(); + + // Interpolate value, if it is not empty + if (!decode.empty()) + { + *itInputColor++ = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); + } + else + { + *itInputColor++ = value * coefficient; + } + } + } + + fillRGBBuffer(inputColors, outputColors.data(), intent, cms, reporter); + + const unsigned char* transformedLine = outputColors.data(); + for (unsigned int i = 0; i < imageWidth; ++i) + { + *outputLine++ = *transformedLine++; + *outputLine++ = *transformedLine++; + *outputLine++ = *transformedLine++; + *outputLine++ = *alphaLine++; + } + } + catch (PDFException lineException) + { + QMutexLocker lock(&exceptionMutex); + if (!exception) + { + exception = lineException; + } + } + }; + + auto range = PDFIntegerRange(0, imageHeight); + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Content, range.begin(), range.end(), transformPixelLine); + + if (exception) + { + throw *exception; + } + + return image; + } + + case PDFImageData::MaskingType::ColorKeyMasking: + { + QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGBA8888); + image.fill(QColor(Qt::transparent)); + + unsigned int componentCount = imageData.getComponents(); + if (componentCount != getColorComponentCount()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colors for color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); + } + + Q_ASSERT(componentCount > 0); + const std::vector& colorKeyMask = imageData.getColorKeyMask(); + if (colorKeyMask.size() / 2 != componentCount) + { + throw PDFException(PDFTranslationContext::tr("Invalid number of color components in color key mask. Expected %1, provided %2.").arg(2 * componentCount).arg(colorKeyMask.size())); + } + + const std::vector& decode = imageData.getDecode(); + if (!decode.empty() && decode.size() != componentCount * 2) + { + throw PDFException(PDFTranslationContext::tr("Invalid size of the decoded array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); + } + + PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); + + PDFColor color; + color.resize(componentCount); + + const double max = reader.max(); + const double coefficient = 1.0 / max; + for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) + { + reader.seek(i * imageData.getStride()); + unsigned char* outputLine = image.scanLine(i); + + for (unsigned int j = 0; j < imageData.getWidth(); ++j) + { + // Number of masked-out colors + unsigned int maskedColors = 0; + + for (unsigned int k = 0; k < componentCount; ++k) + { + PDFBitReader::Value value = reader.read(); + + // Interpolate value, if decode is not empty + if (!decode.empty()) + { + color[k] = interpolate(value, 0.0, max, decode[2 * k], decode[2 * k + 1]); + } + else + { + color[k] = value * coefficient; + } + + Q_ASSERT(2 * k + 1 < colorKeyMask.size()); + if (static_cast::type::value_type>(value) >= colorKeyMask[2 * k] && + static_cast::type::value_type>(value) <= colorKeyMask[2 * k + 1]) + { + ++maskedColors; + } + } + + QColor transformedColor = getColor(color, cms, intent, reporter, true); + QRgb rgb = transformedColor.rgb(); + + *outputLine++ = qRed(rgb); + *outputLine++ = qGreen(rgb); + *outputLine++ = qBlue(rgb); + *outputLine++ = (maskedColors == componentCount) ? 0x00 : 0xFF; + } + } + + return image; + } + + default: + { + throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image masking not implemented!")); + } + } + } + + return QImage(); +} + +void PDFAbstractColorSpace::fillRGBBuffer(const std::vector& colors, + unsigned char* outputBuffer, + RenderingIntent intent, + const PDFCMS* cms, + PDFRenderErrorReporter* reporter) const +{ + // Generic solution + size_t colorComponentCount = getColorComponentCount(); + size_t pixels = colors.size() / colorComponentCount; + + auto it = colors.cbegin(); + for (size_t i = 0; i < pixels; ++i) + { + PDFColor color; + color.resize(colorComponentCount); + for (size_t j = 0; j < colorComponentCount; ++j) + { + color[j] = *it++; + } + QColor transformedColor = getColor(color, cms, intent, reporter, true); + QRgb rgb = transformedColor.rgb(); + + *outputBuffer++ = qRed(rgb); + *outputBuffer++ = qGreen(rgb); + *outputBuffer++ = qBlue(rgb); + } +} + +QColor PDFAbstractColorSpace::getCheckedColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + if (getColorComponentCount() != color.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid number of color components. Expected number is %1, actual number is %2.").arg(static_cast(getColorComponentCount())).arg(static_cast(color.size()))); + } + + return getColor(color, cms, intent, reporter, true); +} + +QImage PDFAbstractColorSpace::createAlphaMask(const PDFImageData& softMask) +{ + if (softMask.getMaskingType() != PDFImageData::MaskingType::None) + { + throw PDFException(PDFTranslationContext::tr("Soft mask can't have masking.")); + } + + if (softMask.getWidth() < 1 || softMask.getHeight() < 1) + { + throw PDFException(PDFTranslationContext::tr("Invalid size of soft mask.")); + } + + QImage image(softMask.getWidth(), softMask.getHeight(), QImage::Format_Alpha8); + + unsigned int componentCount = softMask.getComponents(); + if (componentCount != 1) + { + throw PDFException(PDFTranslationContext::tr("Soft mask should have only 1 color component (alpha) instead of %1.").arg(componentCount)); + } + + const std::vector& decode = softMask.getDecode(); + if (!decode.empty() && decode.size() != componentCount * 2) + { + throw PDFException(PDFTranslationContext::tr("Invalid size of the decode array. Expected %1, actual %2.").arg(componentCount * 2).arg(decode.size())); + } + + PDFBitReader reader(&softMask.getData(), softMask.getBitsPerComponent()); + + PDFColor color; + color.resize(componentCount); + + const double max = reader.max(); + const double coefficient = 1.0 / max; + for (unsigned int i = 0, rowCount = softMask.getHeight(); i < rowCount; ++i) + { + reader.seek(i * softMask.getStride()); + unsigned char* outputLine = image.scanLine(i); + + for (unsigned int j = 0; j < softMask.getWidth(); ++j) + { + PDFReal alpha = 0.0; + + PDFReal value = reader.read(); + + // Interpolate value, if it is not empty + if (!decode.empty()) + { + alpha = interpolate(value, 0.0, max, decode[0], decode[1]); + } + else + { + alpha = value * coefficient; + } + + alpha = qBound(0.0, alpha, 1.0); + uint8_t alphaCoded = alpha * 255; + *outputLine++ = alphaCoded; + } + } + + return image; +} + +PDFColorSpacePointer PDFAbstractColorSpace::createColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace) +{ + std::set usedNames; + return createColorSpaceImpl(colorSpaceDictionary, document, colorSpace, COLOR_SPACE_MAX_LEVEL_OF_RECURSION, usedNames); +} + +PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name) +{ + std::set usedNames; + return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, name, COLOR_SPACE_MAX_LEVEL_OF_RECURSION, usedNames); +} + +PDFColor PDFAbstractColorSpace::convertToColor(const std::vector& components) +{ + PDFColor result; + + for (PDFReal component : components) + { + result.push_back(component); + } + + return result; +} + +bool PDFAbstractColorSpace::isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance) +{ + const size_t size = color1.size(); + if (size != color2.size()) + { + return false; + } + + for (size_t i = 0; i < size; ++i) + { + if (std::fabs(color1[i] - color2[i]) > tolerance) + { + return false; + } + } + + return true; +} + +PDFColor PDFAbstractColorSpace::mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio) +{ + const size_t size = color1.size(); + Q_ASSERT(size == color2.size()); + + PDFColor result; + result.resize(size); + for (size_t i = 0; i < size; ++i) + { + result[i] = color1[i] * (1.0 - ratio) + color2[i] * ratio; + } + + return result; +} + +bool PDFAbstractColorSpace::transform(const PDFAbstractColorSpace* source, + const PDFAbstractColorSpace* target, + const PDFCMS* cms, + RenderingIntent intent, + const PDFColorBuffer input, + PDFColorBuffer output, + PDFRenderErrorReporter* reporter) +{ + Q_ASSERT(source); + Q_ASSERT(target); + Q_ASSERT(cms); + Q_ASSERT(target->isBlendColorSpace()); + Q_ASSERT(input.size() % source->getColorComponentCount() == 0); + + const std::size_t sourceColors = input.size() / source->getColorComponentCount(); + const std::size_t targetColors = output.size() / target->getColorComponentCount(); + + if (sourceColors != targetColors) + { + // This is bad input values. Function should not be called with these parameters. + // Assert and return false. We do not want crash. + Q_ASSERT(false); + return false; + } + + const std::size_t sourceColorsRemainder = input.size() % source->getColorComponentCount(); + const std::size_t targetColorsRemainder = output.size() % target->getColorComponentCount(); + + if (sourceColorsRemainder > 0 || targetColorsRemainder > 0) + { + // Input/output buffer size is incorrect + Q_ASSERT(false); + return false; + } + + if (source->equals(target)) + { + // Just copy input buffer to output buffer + Q_ASSERT(input.size() == output.size()); + std::copy(input.begin(), input.end(), output.begin()); + return true; + } + + // We will use following algorithm to transform colors: + // 1. Determine source color space type for cms, + // and adjust colors to this color space type + // 2. Prepare work output buffer of target size (can have + // different size than real output buffer) + // 3. Transform colors using CMS + // 4. Transform output color buffer to target color buffer + + PDFCMS::ColorSpaceTransformParams params; + std::vector transformedInputColorsVector; + std::vector transformedOutputColorsVector; + PDFColorBuffer transformedInput = input; + PDFColorBuffer transformedOutput = output; + params.intent = intent; + + switch (source->getColorSpace()) + { + case ColorSpace::DeviceGray: + params.sourceType = PDFCMS::ColorSpaceType::DeviceGray; + break; + + case ColorSpace::DeviceRGB: + params.sourceType = PDFCMS::ColorSpaceType::DeviceRGB; + break; + + case ColorSpace::DeviceCMYK: + params.sourceType = PDFCMS::ColorSpaceType::DeviceCMYK; + break; + + case ColorSpace::CalGray: + { + params.sourceType = PDFCMS::ColorSpaceType::XYZ; + + // Transform gray to XYZ + const PDFCalGrayColorSpace* calGrayColorSpace = static_cast(source); + const PDFColorComponent gamma = calGrayColorSpace->getGamma(); + + transformedInputColorsVector.resize(input.size() * 3, 0.0f); + auto it = transformedInputColorsVector.begin(); + for (PDFColorComponent gray : input) + { + const PDFColorComponent A = clip01(gray); + const PDFColorComponent xyzColor = std::powf(A, gamma); + + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = xyzColor; + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = xyzColor; + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = xyzColor; + } + Q_ASSERT(it == transformedInputColorsVector.end()); + + transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); + break; + } + + case ColorSpace::CalRGB: + { + params.sourceType = PDFCMS::ColorSpaceType::XYZ; + + const PDFCalRGBColorSpace* calRGBColorSpace = static_cast(source); + const PDFColor3 gamma = calRGBColorSpace->getGamma(); + const PDFColorComponentMatrix_3x3 matrix = calRGBColorSpace->getMatrix(); + + transformedInputColorsVector.resize(input.size(), 0.0f); + auto it = transformedInputColorsVector.begin(); + + for (auto sourceIt = input.cbegin(); sourceIt != input.cend(); sourceIt = std::next(sourceIt, 3)) + { + PDFColor3 ABC = { }; + Q_ASSERT(sourceIt != input.end()); + ABC[0] = *sourceIt; + Q_ASSERT(sourceIt + 1 != input.end()); + ABC[1] = *std::next(sourceIt, 1); + Q_ASSERT(sourceIt + 2 != input.end()); + ABC[2] = *std::next(sourceIt, 2); + ABC = colorPowerByFactors(ABC, gamma); + + const PDFColor3 XYZ = matrix * ABC; + + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = XYZ[0]; + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = XYZ[1]; + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = XYZ[2]; + } + Q_ASSERT(it == transformedInputColorsVector.end()); + + transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); + break; + } + + case ColorSpace::Lab: + { + params.sourceType = PDFCMS::ColorSpaceType::XYZ; + + const PDFLabColorSpace* labColorSpace = static_cast(source); + const PDFColorComponent aMin = labColorSpace->getAMin(); + const PDFColorComponent aMax = labColorSpace->getAMax(); + const PDFColorComponent bMin = labColorSpace->getBMin(); + const PDFColorComponent bMax = labColorSpace->getBMax(); + + transformedInputColorsVector.resize(input.size(), 0.0f); + auto it = transformedInputColorsVector.begin(); + + for (auto sourceIt = input.cbegin(); sourceIt != input.cend(); sourceIt = std::next(sourceIt, 3)) + { + Q_ASSERT(sourceIt != input.end()); + Q_ASSERT(sourceIt + 1 != input.end()); + Q_ASSERT(sourceIt + 2 != input.end()); + + PDFColorComponent LStar = qBound(0.0, interpolate(*sourceIt, 0.0, 1.0, 0.0, 100.0), 100.0); + PDFColorComponent aStar = qBound(aMin, interpolate(*std::next(sourceIt, 1), 0.0, 1.0, aMin, aMax), aMax); + PDFColorComponent bStar = qBound(bMin, interpolate(*std::next(sourceIt, 2), 0.0, 1.0, bMin, bMax), bMax); + + const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; + const PDFColorComponent param2 = aStar / 500.0f; + const PDFColorComponent param3 = bStar / 200.0f; + + const PDFColorComponent L = param1 + param2; + const PDFColorComponent M = param1; + const PDFColorComponent N = param1 - param3; + + auto g = [](PDFColorComponent x) -> PDFColorComponent + { + if (x >= 6.0f / 29.0f) + { + return x * x * x; + } + else + { + return (108.0f / 841.0f) * (x - 4.0f / 29.0f); + } + }; + + const PDFColorComponent gL = g(L); + const PDFColorComponent gM = g(M); + const PDFColorComponent gN = g(N); + + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = gL; + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = gM; + Q_ASSERT(it != transformedInputColorsVector.end()); + *it++ = gN; + } + Q_ASSERT(it == transformedInputColorsVector.end()); + + transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); + break; + } + + case ColorSpace::ICCBased: + { + const PDFICCBasedColorSpace* iccBasedColorSpace = static_cast(source); + + params.sourceType = PDFCMS::ColorSpaceType::ICC; + params.sourceIccId = iccBasedColorSpace->getIccProfileDataChecksum(); + params.sourceIccData = iccBasedColorSpace->getIccProfileData(); + + size_t colorComponentCount = iccBasedColorSpace->getColorComponentCount(); + const PDFICCBasedColorSpace::Ranges& ranges = iccBasedColorSpace->getRange(); + + transformedInputColorsVector.resize(input.size(), 0.0f); + auto outputIt = transformedInputColorsVector.begin(); + for (auto inputIt = input.cbegin(); inputIt != input.cend();) + { + for (size_t i = 0; i < colorComponentCount; ++i) + { + const size_t imin = 2 * i + 0; + const size_t imax = 2 * i + 1; + *outputIt++ = qBound(ranges[imin], *inputIt++, ranges[imax]); + } + } + + transformedInput = PDFColorBuffer(transformedInputColorsVector.data(), transformedInputColorsVector.size()); + break; + } + + case ColorSpace::Indexed: + { + const PDFIndexedColorSpace* indexedColorSpace = static_cast(source); + + PDFColorSpacePointer baseColorSpace = indexedColorSpace->getBaseColorSpace(); + std::vector transformedToBaseColorSpaceInput = indexedColorSpace->transformColorsToBaseColorSpace(input); + PDFColorBuffer transformedInputBuffer(transformedToBaseColorSpaceInput.data(), transformedToBaseColorSpaceInput.size()); + return transform(baseColorSpace.data(), target, cms, intent, transformedInputBuffer, output, reporter); + } + + case ColorSpace::Separation: + { + const PDFSeparationColorSpace* separationColorSpace = static_cast(source); + + PDFColorSpacePointer alternateColorSpace = separationColorSpace->getAlternateColorSpace(); + std::vector transformedToBaseColorSpaceInput = separationColorSpace->transformColorsToBaseColorSpace(input); + PDFColorBuffer transformedInputBuffer(transformedToBaseColorSpaceInput.data(), transformedToBaseColorSpaceInput.size()); + return transform(alternateColorSpace.data(), target, cms, intent, transformedInputBuffer, output, reporter); + } + + case ColorSpace::DeviceN: + { + const PDFDeviceNColorSpace* separationColorSpace = static_cast(source); + + PDFColorSpacePointer alternateColorSpace = separationColorSpace->getAlternateColorSpace(); + std::vector transformedToBaseColorSpaceInput = separationColorSpace->transformColorsToBaseColorSpace(input); + PDFColorBuffer transformedInputBuffer(transformedToBaseColorSpaceInput.data(), transformedToBaseColorSpaceInput.size()); + return transform(alternateColorSpace.data(), target, cms, intent, transformedInputBuffer, output, reporter); + } + + case ColorSpace::Pattern: + return false; + + default: + Q_ASSERT(false); + return false; + } + + switch (target->getColorSpace()) + { + case ColorSpace::DeviceGray: + // Output buffer size is the same as target type + params.targetType = PDFCMS::ColorSpaceType::DeviceGray; + break; + + case ColorSpace::DeviceRGB: + // Output buffer size is the same as target type + params.targetType = PDFCMS::ColorSpaceType::DeviceRGB; + break; + + case ColorSpace::DeviceCMYK: + // Output buffer size is the same as target type + params.targetType = PDFCMS::ColorSpaceType::DeviceCMYK; + break; + + case pdf::PDFAbstractColorSpace::ColorSpace::CalGray: + { + params.targetType = PDFCMS::ColorSpaceType::XYZ; + transformedOutputColorsVector.resize(output.size() * 3, 0.0f); + transformedOutput = PDFColorBuffer(transformedOutputColorsVector.data(), transformedOutputColorsVector.size()); + break; + } + + case pdf::PDFAbstractColorSpace::ColorSpace::CalRGB: + { + // Output buffer size is the same as target type + params.targetType = PDFCMS::ColorSpaceType::XYZ; + transformedOutputColorsVector.resize(output.size(), 0.0f); + transformedOutput = PDFColorBuffer(transformedOutputColorsVector.data(), transformedOutputColorsVector.size()); + break; + } + + case pdf::PDFAbstractColorSpace::ColorSpace::ICCBased: + { + const PDFICCBasedColorSpace* iccBasedColorSpace = static_cast(target); + + params.targetType = PDFCMS::ColorSpaceType::ICC; + params.targetIccId = iccBasedColorSpace->getIccProfileDataChecksum(); + params.targetIccData = iccBasedColorSpace->getIccProfileData(); + break; + } + + default: + Q_ASSERT(false); + return false; + } + + params.input = transformedInput; + params.output = transformedOutput; + + cms->transformColorSpace(params); + + switch (target->getColorSpace()) + { + case pdf::PDFAbstractColorSpace::ColorSpace::CalGray: + { + const PDFCalGrayColorSpace* calGrayColorSpace = static_cast(source); + const PDFColorComponent gamma = 1.0 / calGrayColorSpace->getGamma(); + + auto outputIt = output.begin(); + for (auto transformedOutputIt = transformedOutput.cbegin(); transformedOutputIt != transformedOutput.cend(); transformedOutputIt = std::next(transformedOutputIt, 3)) + { + PDFColor3 XYZ = { }; + Q_ASSERT(transformedOutputIt != transformedOutput.end()); + XYZ[0] = *transformedOutputIt; + Q_ASSERT(transformedOutputIt + 1 != transformedOutput.end()); + XYZ[1] = *std::next(transformedOutputIt, 1); + Q_ASSERT(transformedOutputIt + 2 != transformedOutput.end()); + XYZ[2] = *std::next(transformedOutputIt, 2); + + const PDFColorComponent gray = (XYZ[0] + XYZ[1] + XYZ[2]) * 0.333333333333333; + const PDFColorComponent grayWithGamma = std::powf(gray, gamma); + Q_ASSERT(outputIt != output.cend()); + *outputIt++ = grayWithGamma; + } + Q_ASSERT(outputIt == output.cend()); + break; + } + + case pdf::PDFAbstractColorSpace::ColorSpace::CalRGB: + { + const PDFCalRGBColorSpace* calRGBColorSpace = static_cast(source); + const PDFColor3 gammaForward = calRGBColorSpace->getGamma(); + const PDFColorComponentMatrix_3x3 matrixForward = calRGBColorSpace->getMatrix(); + const PDFColor3 gammaInverse = PDFColor3{ 1.0f / gammaForward[0], 1.0f / gammaForward[1], 1.0f / gammaForward[2] }; + const PDFColorComponentMatrix_3x3 matrixInverse = getInverseMatrix(matrixForward); + + auto outputIt = output.begin(); + for (auto transformedOutputIt = transformedOutput.cbegin(); transformedOutputIt != transformedOutput.cend(); transformedOutputIt = std::next(transformedOutputIt, 3)) + { + PDFColor3 XYZ = { }; + XYZ[0] = *transformedOutputIt; + XYZ[1] = *std::next(transformedOutputIt, 1); + XYZ[2] = *std::next(transformedOutputIt, 2); + + const PDFColor3 RGB = matrixInverse * XYZ; + const PDFColor3 RGBwithGamma = colorPowerByFactors(RGB, gammaInverse); + + Q_ASSERT(outputIt != output.end()); + *outputIt++ = RGBwithGamma[0]; + Q_ASSERT(outputIt != output.end()); + *outputIt++ = RGBwithGamma[1]; + Q_ASSERT(outputIt != output.end()); + *outputIt++ = RGBwithGamma[2]; + } + Q_ASSERT(outputIt == output.cend()); + break; + } + + default: + break; + } + + return true; +} + +PDFColorSpacePointer PDFAbstractColorSpace::createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace, + int recursion, + std::set& usedNames) +{ + if (--recursion <= 0) + { + throw PDFException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex.")); + } + + if (colorSpace.isName()) + { + return createDeviceColorSpaceByNameImpl(colorSpaceDictionary, document, colorSpace.getString(), recursion, usedNames); + } + else if (colorSpace.isArray()) + { + // First value of the array should be identification name, second value dictionary with parameters + const PDFArray* array = colorSpace.getArray(); + size_t count = array->getCount(); + + if (count > 0) + { + // Name of the color space + const PDFObject& colorSpaceIdentifier = document->getObject(array->getItem(0)); + if (colorSpaceIdentifier.isName()) + { + QByteArray name = colorSpaceIdentifier.getString(); + + const PDFDictionary* dictionary = nullptr; + const PDFStream* stream = nullptr; + if (count > 1) + { + const PDFObject& colorSpaceSettings = document->getObject(array->getItem(1)); + if (colorSpaceSettings.isDictionary()) + { + dictionary = colorSpaceSettings.getDictionary(); + } + if (colorSpaceSettings.isStream()) + { + stream = colorSpaceSettings.getStream(); + } + } + + if (name == COLOR_SPACE_NAME_PATTERN) + { + PDFColorSpacePointer uncoloredPatternColorSpace; + if (count == 2) + { + uncoloredPatternColorSpace = createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(1)), recursion, usedNames); + } + + return PDFColorSpacePointer(new PDFPatternColorSpace(std::make_shared(), qMove(uncoloredPatternColorSpace), PDFColor())); + } + + if (dictionary) + { + if (name == COLOR_SPACE_NAME_CAL_GRAY) + { + return PDFCalGrayColorSpace::createCalGrayColorSpace(document, dictionary); + } + else if (name == COLOR_SPACE_NAME_CAL_RGB) + { + return PDFCalRGBColorSpace::createCalRGBColorSpace(document, dictionary); + } + else if (name == COLOR_SPACE_NAME_LAB) + { + return PDFLabColorSpace::createLabColorSpace(document, dictionary); + } + } + + if (stream && name == COLOR_SPACE_NAME_ICCBASED) + { + return PDFICCBasedColorSpace::createICCBasedColorSpace(colorSpaceDictionary, document, stream, recursion, usedNames); + } + + if (name == COLOR_SPACE_NAME_INDEXED && count == 4) + { + return PDFIndexedColorSpace::createIndexedColorSpace(colorSpaceDictionary, document, array, recursion, usedNames); + } + + if (name == COLOR_SPACE_NAME_SEPARATION && count == 4) + { + return PDFSeparationColorSpace::createSeparationColorSpace(colorSpaceDictionary, document, array, recursion, usedNames); + } + + if (name == COLOR_SPACE_NAME_DEVICE_N && count >= 4) + { + return PDFDeviceNColorSpace::createDeviceNColorSpace(colorSpaceDictionary, document, array, recursion, usedNames); + } + + // Try to just load by standard way - we can have "standard" color space stored in array + return createColorSpaceImpl(colorSpaceDictionary, document, colorSpaceIdentifier, recursion, usedNames); + } + } + } + + throw PDFException(PDFTranslationContext::tr("Invalid color space.")); + return PDFColorSpacePointer(); +} + +PDFColorSpacePointer PDFAbstractColorSpace::createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name, + int recursion, + std::set& usedNames) +{ + if (--recursion <= 0) + { + throw PDFException(PDFTranslationContext::tr("Can't load color space, because color space structure is too complex.")); + } + + // Jakub Melka: This flag is set when we are already parsing the name. This can occur + // for example by this way: we have DefaultRGB, which is ICC profile. In this ICC profile, + // we create alternate alternate RGB color space also from DefaultRGB name. But in this time, + // we must use generic RGB color space, not DefaultRGB from color space dictionary, because + // it will be cyclical dependency. + bool isNameAlreadyProcessed = usedNames.count(name); + usedNames.insert(name); + + if (name == COLOR_SPACE_NAME_PATTERN) + { + return PDFColorSpacePointer(new PDFPatternColorSpace(std::make_shared(), nullptr, PDFColor())); + } + + if (name == COLOR_SPACE_NAME_DEVICE_GRAY || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY) + { + if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_GRAY) && !isNameAlreadyProcessed) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_GRAY)), recursion, usedNames); + } + else + { + return PDFColorSpacePointer(new PDFDeviceGrayColorSpace()); + } + } + else if (name == COLOR_SPACE_NAME_DEVICE_RGB || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB) + { + if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_RGB) && !isNameAlreadyProcessed) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_RGB)), recursion, usedNames); + } + else + { + return PDFColorSpacePointer(new PDFDeviceRGBColorSpace()); + } + } + else if (name == COLOR_SPACE_NAME_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK || name == COLOR_SPACE_NAME_ABBREVIATION_CAL_CMYK) + { + if (colorSpaceDictionary && colorSpaceDictionary->hasKey(COLOR_SPACE_NAME_DEFAULT_CMYK) && !isNameAlreadyProcessed) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(COLOR_SPACE_NAME_DEFAULT_CMYK)), recursion, usedNames); + } + else + { + return PDFColorSpacePointer(new PDFDeviceCMYKColorSpace()); + } + } + else if (colorSpaceDictionary && colorSpaceDictionary->hasKey(name)) + { + return createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorSpaceDictionary->get(name)), recursion, usedNames); + } + + throw PDFException(PDFTranslationContext::tr("Invalid color space.")); + return PDFColorSpacePointer(); +} + +/// Conversion matrix from XYZ space to RGB space. Values are taken from this article: +/// https://en.wikipedia.org/wiki/SRGB#The_sRGB_transfer_function_.28.22gamma.22.29 +static constexpr const PDFColorComponentMatrix<3, 3> matrixXYZtoRGB( + 3.2406f, -1.5372f, -0.4986f, + -0.9689f, 1.8758f, 0.0415f, + 0.0557f, -0.2040f, 1.0570f +); + +PDFColor3 PDFAbstractColorSpace::convertXYZtoRGB(const PDFColor3& xyzColor) +{ + return matrixXYZtoRGB * xyzColor; +} + +PDFColor PDFXYZColorSpace::getDefaultColorOriginal() const +{ + PDFColor color; + const size_t componentCount = getColorComponentCount(); + for (size_t i = 0; i < componentCount; ++i) + { + color.push_back(0.0f); + } + return color; +} + +bool PDFXYZColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFAbstractColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFXYZColorSpace* typedOther = static_cast(other); + return m_whitePoint == typedOther->getWhitePoint() && m_correctionCoefficients == typedOther->getCorrectionCoefficients(); +} + +PDFXYZColorSpace::PDFXYZColorSpace(PDFColor3 whitePoint) : + m_whitePoint(whitePoint), + m_correctionCoefficients() +{ + PDFColor3 mappedWhitePoint = convertXYZtoRGB(m_whitePoint); + + m_correctionCoefficients[0] = 1.0f / mappedWhitePoint[0]; + m_correctionCoefficients[1] = 1.0f / mappedWhitePoint[1]; + m_correctionCoefficients[2] = 1.0f / mappedWhitePoint[2]; +} + +const PDFColor3& PDFXYZColorSpace::getCorrectionCoefficients() const +{ + return m_correctionCoefficients; +} + +PDFCalGrayColorSpace::PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma) : + PDFXYZColorSpace(whitePoint), + m_blackPoint(blackPoint), + m_gamma(gamma) +{ + +} + +bool PDFCalGrayColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFXYZColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFCalGrayColorSpace* typedOther = static_cast(other); + return m_blackPoint == typedOther->getBlackPoint() && m_gamma == typedOther->getGamma(); +} + +QColor PDFCalGrayColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + Q_UNUSED(isRange01); + + const PDFColorComponent A = clip01(color[0]); + const PDFColorComponent xyzColor = std::powf(A, m_gamma); + + PDFColor3 xyzColorCMS = { xyzColor, xyzColor, xyzColor }; + QColor cmsColor = cms->getColorFromXYZ(m_whitePoint, xyzColorCMS, intent, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + const PDFColor3 xyzColorMultipliedByWhitePoint = colorMultiplyByFactor(m_whitePoint, xyzColor); + const PDFColor3 rgb = convertXYZtoRGB(xyzColorMultipliedByWhitePoint); + const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); + return fromRGB01(calibratedRGB); +} + +size_t PDFCalGrayColorSpace::getColorComponentCount() const +{ + return 1; +} + +void PDFCalGrayColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + std::vector xyzColors(colors.size() * 3, 0.0f); + auto it = xyzColors.begin(); + for (float gray : colors) + { + const PDFColorComponent xyzColor = std::powf(clip01(gray), m_gamma); + *it++ = xyzColor; + *it++ = xyzColor; + *it++ = xyzColor; + } + + Q_ASSERT(xyzColors.size() == colors.size() * 3); + if (!cms->fillRGBBufferFromXYZ(m_whitePoint, xyzColors, intent, outputBuffer, reporter)) + { + PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); + } +} + +PDFColorSpacePointer PDFCalGrayColorSpace::createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) +{ + // Standard D65 white point + PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; + PDFColor3 blackPoint = { 0, 0, 0 }; + PDFColorComponent gamma = 1.0f; + + PDFDocumentDataLoaderDecorator loader(document); + loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); + gamma = loader.readNumberFromDictionary(dictionary, CAL_GAMMA, gamma); + + return PDFColorSpacePointer(new PDFCalGrayColorSpace(whitePoint, blackPoint, gamma)); +} + +PDFCalRGBColorSpace::PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix) : + PDFXYZColorSpace(whitePoint), + m_blackPoint(blackPoint), + m_gamma(gamma), + m_matrix(matrix) +{ + +} + +bool PDFCalRGBColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFXYZColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFCalRGBColorSpace* typedOther = static_cast(other); + return m_blackPoint == typedOther->getBlackPoint() && m_gamma == typedOther->getGamma() && m_matrix == typedOther->getMatrix(); +} + +QColor PDFCalRGBColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + Q_UNUSED(isRange01); + + const PDFColor3 ABC = clip01(PDFColor3{ color[0], color[1], color[2] }); + const PDFColor3 ABCwithGamma = colorPowerByFactors(ABC, m_gamma); + const PDFColor3 XYZ = m_matrix * ABCwithGamma; + + QColor cmsColor = cms->getColorFromXYZ(m_whitePoint, XYZ, intent, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + const PDFColor3 rgb = convertXYZtoRGB(XYZ); + const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); + return fromRGB01(calibratedRGB); +} + +size_t PDFCalRGBColorSpace::getColorComponentCount() const +{ + return 3; +} + +void PDFCalRGBColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + std::vector xyzColors(colors.size(), 0.0f); + auto it = xyzColors.begin(); + for (size_t i = 0; i < colors.size(); i += 3) + { + Q_ASSERT(i + 2 < colors.size()); + const PDFColor3 ABC = clip01(PDFColor3{ colors[i + 0], colors[i + 1], colors[i + 2] }); + const PDFColor3 ABCwithGamma = colorPowerByFactors(ABC, m_gamma); + const PDFColor3 XYZ = m_matrix * ABCwithGamma; + + *it++ = XYZ[0]; + *it++ = XYZ[1]; + *it++ = XYZ[2]; + } + + Q_ASSERT(xyzColors.size() == colors.size()); + if (!cms->fillRGBBufferFromXYZ(m_whitePoint, xyzColors, intent, outputBuffer, reporter)) + { + PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); + } +} + +PDFColorSpacePointer PDFCalRGBColorSpace::createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) +{ + // Standard D65 white point + PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; + PDFColor3 blackPoint = { 0, 0, 0 }; + PDFColor3 gamma = { 1.0f, 1.0f, 1.0f }; + PDFColorComponentMatrix_3x3 matrix( 1, 0, 0, + 0, 1, 0, + 0, 0, 1 ); + + PDFDocumentDataLoaderDecorator loader(document); + loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_GAMMA, gamma.begin(), gamma.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_MATRIX, matrix.begin(), matrix.end()); + + matrix.transpose(); + + return PDFColorSpacePointer(new PDFCalRGBColorSpace(whitePoint, blackPoint, gamma, matrix)); +} + +PDFColor3 PDFCalRGBColorSpace::getBlackPoint() const +{ + return m_blackPoint; +} + +PDFColor3 PDFCalRGBColorSpace::getGamma() const +{ + return m_gamma; +} + +const PDFColorComponentMatrix_3x3& PDFCalRGBColorSpace::getMatrix() const +{ + return m_matrix; +} + +PDFLabColorSpace::PDFLabColorSpace(PDFColor3 whitePoint, + PDFColor3 blackPoint, + PDFColorComponent aMin, + PDFColorComponent aMax, + PDFColorComponent bMin, + PDFColorComponent bMax) : + PDFXYZColorSpace(whitePoint), + m_blackPoint(blackPoint), + m_aMin(aMin), + m_aMax(aMax), + m_bMin(bMin), + m_bMax(bMax) +{ + +} + +bool PDFLabColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFXYZColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFLabColorSpace* typedOther = static_cast(other); + return m_blackPoint == typedOther->getBlackPoint() && + m_aMin == typedOther->getAMin() && m_aMax == typedOther->getAMax() && + m_bMin == typedOther->getBMin() && m_bMax == typedOther->getBMax(); +} + +QColor PDFLabColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + + PDFColorComponent LStar = 0; + PDFColorComponent aStar = 0; + PDFColorComponent bStar = 0; + + if (isRange01) + { + LStar = qBound(0.0, interpolate(color[0], 0.0, 1.0, 0.0, 100.0), 100.0); + aStar = qBound(m_aMin, interpolate(color[1], 0.0, 1.0, m_aMin, m_aMax), m_aMax); + bStar = qBound(m_bMin, interpolate(color[2], 0.0, 1.0, m_bMin, m_bMax), m_bMax); + } + else + { + LStar = qBound(0.0, color[0], 100.0); + aStar = qBound(m_aMin, color[1], m_aMax); + bStar = qBound(m_bMin, color[2], m_bMax); + } + + const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; + const PDFColorComponent param2 = aStar / 500.0f; + const PDFColorComponent param3 = bStar / 200.0f; + + const PDFColorComponent L = param1 + param2; + const PDFColorComponent M = param1; + const PDFColorComponent N = param1 - param3; + + auto g = [](PDFColorComponent x) -> PDFColorComponent + { + if (x >= 6.0f / 29.0f) + { + return x * x * x; + } + else + { + return (108.0f / 841.0f) * (x - 4.0f / 29.0f); + } + }; + + const PDFColorComponent gL = g(L); + const PDFColorComponent gM = g(M); + const PDFColorComponent gN = g(N); + const PDFColor3 gLMN = { gL, gM, gN }; + + QColor cmsColor = cms->getColorFromXYZ(m_whitePoint, gLMN, intent, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + const PDFColor3 XYZ = colorMultiplyByFactors(m_whitePoint, gLMN); + const PDFColor3 rgb = convertXYZtoRGB(XYZ); + const PDFColor3 calibratedRGB = colorMultiplyByFactors(rgb, m_correctionCoefficients); + return fromRGB01(calibratedRGB); +} + +size_t PDFLabColorSpace::getColorComponentCount() const +{ + return 3; +} + +void PDFLabColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + auto g = [](PDFColorComponent x) -> PDFColorComponent + { + if (x >= 6.0f / 29.0f) + { + return x * x * x; + } + else + { + return (108.0f / 841.0f) * (x - 4.0f / 29.0f); + } + }; + + std::vector xyzColors(colors.size(), 0.0f); + auto it = xyzColors.begin(); + for (size_t i = 0; i < colors.size(); i += 3) + { + Q_ASSERT(i + 2 < colors.size()); + + const PDFColorComponent LStar = qBound(0.0, interpolate(colors[i + 0], 0.0, 1.0, 0.0, 100.0), 100.0); + const PDFColorComponent aStar = qBound(m_aMin, interpolate(colors[i + 1], 0.0, 1.0, m_aMin, m_aMax), m_aMax); + const PDFColorComponent bStar = qBound(m_bMin, interpolate(colors[i + 2], 0.0, 1.0, m_bMin, m_bMax), m_bMax); + + const PDFColorComponent param1 = (LStar + 16.0f) / 116.0f; + const PDFColorComponent param2 = aStar / 500.0f; + const PDFColorComponent param3 = bStar / 200.0f; + + const PDFColorComponent L = param1 + param2; + const PDFColorComponent M = param1; + const PDFColorComponent N = param1 - param3; + + *it++ = g(L); + *it++ = g(M); + *it++ = g(N); + } + + Q_ASSERT(xyzColors.size() == colors.size()); + if (!cms->fillRGBBufferFromXYZ(m_whitePoint, xyzColors, intent, outputBuffer, reporter)) + { + PDFAbstractColorSpace::fillRGBBuffer(colors, outputBuffer, intent, cms, reporter); + } +} + +PDFColorSpacePointer PDFLabColorSpace::createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary) +{ + // Standard D65 white point + PDFColor3 whitePoint = { 0.9505f, 1.0000f, 1.0890f }; + PDFColor3 blackPoint = { 0, 0, 0 }; + + static_assert(std::numeric_limits::has_infinity, "Fix this code!"); + const PDFColorComponent infPos = std::numeric_limits::infinity(); + const PDFColorComponent infNeg = -std::numeric_limits::infinity(); + std::array minMax = { infNeg, infPos, infNeg, infPos }; + + PDFDocumentDataLoaderDecorator loader(document); + loader.readNumberArrayFromDictionary(dictionary, CAL_WHITE_POINT, whitePoint.begin(), whitePoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_BLACK_POINT, blackPoint.begin(), blackPoint.end()); + loader.readNumberArrayFromDictionary(dictionary, CAL_RANGE, minMax.begin(), minMax.end()); + + return PDFColorSpacePointer(new PDFLabColorSpace(whitePoint, blackPoint, minMax[0], minMax[1], minMax[2], minMax[3])); +} + +PDFColorComponent PDFLabColorSpace::getAMin() const +{ + return m_aMin; +} + +PDFColorComponent PDFLabColorSpace::getAMax() const +{ + return m_aMax; +} + +PDFColorComponent PDFLabColorSpace::getBMin() const +{ + return m_bMin; +} + +PDFColorComponent PDFLabColorSpace::getBMax() const +{ + return m_bMax; +} + +PDFColor3 PDFLabColorSpace::getBlackPoint() const +{ + return m_blackPoint; +} + +PDFICCBasedColorSpace::PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range, QByteArray iccProfileData, PDFObjectReference metadata) : + m_alternateColorSpace(qMove(alternateColorSpace)), + m_range(range), + m_iccProfileData(qMove(iccProfileData)), + m_metadata(metadata) +{ + // Compute checksum + m_iccProfileDataChecksum = QCryptographicHash::hash(m_iccProfileData, QCryptographicHash::Md5); +} + +PDFColor PDFICCBasedColorSpace::getDefaultColorOriginal() const +{ + PDFColor color; + const size_t componentCount = getColorComponentCount(); + for (size_t i = 0; i < componentCount; ++i) + { + color.push_back(0.0f); + } + return color; +} + +QColor PDFICCBasedColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_ASSERT(color.size() == getColorComponentCount()); + Q_UNUSED(isRange01); + + size_t colorComponentCount = getColorComponentCount(); + + // Clip color values by range + PDFColor clippedColor = color; + for (size_t i = 0; i < colorComponentCount; ++i) + { + const size_t imin = 2 * i + 0; + const size_t imax = 2 * i + 1; + clippedColor[i] = qBound(m_range[imin], clippedColor[i], m_range[imax]); + } + + QColor cmsColor = cms->getColorFromICC(clippedColor, intent, m_iccProfileDataChecksum, m_iccProfileData, reporter); + if (cmsColor.isValid()) + { + return cmsColor; + } + + return m_alternateColorSpace->getColor(clippedColor, cms, intent, reporter, true); +} + +size_t PDFICCBasedColorSpace::getColorComponentCount() const +{ + return m_alternateColorSpace->getColorComponentCount(); +} + +void PDFICCBasedColorSpace::fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const +{ + size_t colorComponentCount = getColorComponentCount(); + std::vector clippedColors(colors.size(), 0.0f); + for (size_t i = 0, colorCount = colors.size(); i < colorCount; ++i) + { + const size_t componentIndex = i % colorComponentCount; + const size_t imin = 2 * componentIndex + 0; + const size_t imax = 2 * componentIndex + 1; + clippedColors[i] = qBound(m_range[imin], colors[i], m_range[imax]); + } + + if (!cms->fillRGBBufferFromICC(clippedColors, intent, outputBuffer, m_iccProfileDataChecksum, m_iccProfileData, reporter)) + { + // Try to fill buffer from alternate color space + m_alternateColorSpace->fillRGBBuffer(clippedColors, outputBuffer, intent, cms, reporter); + } +} + +bool PDFICCBasedColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFAbstractColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFICCBasedColorSpace* typedOther = static_cast(other); + + const PDFAbstractColorSpace* alternateColorSpace = typedOther->getAlternateColorSpace(); + + if (static_cast(m_alternateColorSpace.data()) != static_cast(alternateColorSpace)) + { + return false; + } + + if (m_alternateColorSpace && alternateColorSpace && !m_alternateColorSpace->equals(alternateColorSpace)) + { + return false; + } + + return m_range == typedOther->getRange() && m_iccProfileDataChecksum == typedOther->getIccProfileDataChecksum(); +} + +PDFColorSpacePointer PDFICCBasedColorSpace::createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFStream* stream, + int recursion, + std::set& usedNames) +{ + // First, try to load alternate color space, if it is present + const PDFDictionary* dictionary = stream->getDictionary(); + QByteArray iccProfileData = document->getDecodedStream(stream); + + PDFDocumentDataLoaderDecorator loader(document); + PDFColorSpacePointer alternateColorSpace; + if (dictionary->hasKey(ICCBASED_ALTERNATE)) + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(dictionary->get(ICCBASED_ALTERNATE)), recursion, usedNames); + } + else + { + // Determine color space from parameter N, which determines number of components + const PDFInteger N = loader.readIntegerFromDictionary(dictionary, ICCBASED_N, 0); + + switch (N) + { + case 1: + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(COLOR_SPACE_NAME_DEVICE_GRAY), recursion, usedNames); + break; + } + + case 3: + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(COLOR_SPACE_NAME_DEVICE_RGB), recursion, usedNames); + break; + } + + case 4: + { + alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, PDFObject::createName(COLOR_SPACE_NAME_DEVICE_CMYK), recursion, usedNames); + break; + } + + default: + { + throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile. Number of components is %1.").arg(N)); + break; + } + } + } + + if (!alternateColorSpace) + { + throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for ICC based profile.")); + } + + Ranges ranges = { 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }; + static_assert(ranges.size() == 8, "Fix initialization above!"); + + const size_t components = alternateColorSpace->getColorComponentCount(); + const size_t rangeSize = 2 * components; + + if (rangeSize > ranges.size()) + { + throw PDFException(PDFTranslationContext::tr("Too much color components for ICC based profile.")); + } + + auto itStart = ranges.begin(); + auto itEnd = std::next(itStart, rangeSize); + loader.readNumberArrayFromDictionary(dictionary, ICCBASED_RANGE, itStart, itEnd); + + return PDFColorSpacePointer(new PDFICCBasedColorSpace(qMove(alternateColorSpace), ranges, qMove(iccProfileData), loader.readReferenceFromDictionary(dictionary, "Metadata"))); +} + +const PDFICCBasedColorSpace::Ranges& PDFICCBasedColorSpace::getRange() const +{ + return m_range; +} + +const QByteArray& PDFICCBasedColorSpace::getIccProfileData() const +{ + return m_iccProfileData; +} + +const QByteArray& PDFICCBasedColorSpace::getIccProfileDataChecksum() const +{ + return m_iccProfileDataChecksum; +} + +const PDFAbstractColorSpace* PDFICCBasedColorSpace::getAlternateColorSpace() const +{ + return m_alternateColorSpace.data(); +} + +PDFIndexedColorSpace::PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue) : + m_baseColorSpace(qMove(baseColorSpace)), + m_colors(qMove(colors)), + m_maxValue(maxValue) +{ + +} + +bool PDFIndexedColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFAbstractColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFIndexedColorSpace* typedOther = static_cast(other); + + const PDFAbstractColorSpace* baseColorSpace = typedOther->getBaseColorSpace().data(); + + if (static_cast(m_baseColorSpace.data()) != static_cast(baseColorSpace)) + { + return false; + } + + if (m_baseColorSpace && baseColorSpace && !m_baseColorSpace->equals(baseColorSpace)) + { + return false; + } + + return m_colors == typedOther->getColors() && m_maxValue == typedOther->getMaxValue(); +} + +PDFColor PDFIndexedColorSpace::getDefaultColorOriginal() const +{ + return PDFColor(0.0f); +} + +QColor PDFIndexedColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + // Indexed color space value must have exactly one component! + Q_ASSERT(color.size() == 1); + Q_UNUSED(isRange01); + + const int colorIndex = qBound(MIN_VALUE, static_cast(color[0]), m_maxValue); + const int componentCount = static_cast(m_baseColorSpace->getColorComponentCount()); + const int byteOffset = colorIndex * componentCount; + + // We must point into the array. Check first and last component. + Q_ASSERT(byteOffset + componentCount - 1 < m_colors.size()); + + PDFColor decodedColor; + const char* bytePointer = m_colors.constData() + byteOffset; + + for (int i = 0; i < componentCount; ++i) + { + const unsigned char value = *bytePointer++; + const PDFColorComponent component = static_cast(value) / 255.0f; + decodedColor.push_back(component); + } + + return m_baseColorSpace->getColor(decodedColor, cms, intent, reporter, true); +} + +size_t PDFIndexedColorSpace::getColorComponentCount() const +{ + return 1; +} + +QImage PDFIndexedColorSpace::getImage(const PDFImageData& imageData, + const PDFImageData& softMask, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter) const +{ + if (imageData.isValid()) + { + switch (imageData.getMaskingType()) + { + case PDFImageData::MaskingType::None: + { + QImage image(imageData.getWidth(), imageData.getHeight(), QImage::Format_RGB888); + image.fill(QColor(Qt::white)); + + unsigned int componentCount = imageData.getComponents(); + PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); + + if (componentCount != getColorComponentCount()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); + } + + Q_ASSERT(componentCount == 1); + + PDFColor color; + color.resize(1); + + for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) + { + reader.seek(i * imageData.getStride()); + unsigned char* outputLine = image.scanLine(i); + + for (unsigned int j = 0; j < imageData.getWidth(); ++j) + { + PDFBitReader::Value index = reader.read(); + color[0] = index; + + QColor transformedColor = getColor(color, cms, intent, reporter, false); + QRgb rgb = transformedColor.rgb(); + + *outputLine++ = qRed(rgb); + *outputLine++ = qGreen(rgb); + *outputLine++ = qBlue(rgb); + } + } + + return image; + } + + case PDFImageData::MaskingType::SoftMask: + { + const bool hasMatte = !softMask.getMatte().empty(); + QImage image(imageData.getWidth(), imageData.getHeight(), hasMatte ? QImage::Format_RGBA8888_Premultiplied : QImage::Format_RGBA8888); + + unsigned int componentCount = imageData.getComponents(); + PDFBitReader reader(&imageData.getData(), imageData.getBitsPerComponent()); + + if (componentCount != getColorComponentCount()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors. Provided color count is %4.").arg(getColorComponentCount()).arg(componentCount)); + } + + Q_ASSERT(componentCount == 1); + + PDFColor color; + color.resize(1); + + QImage alphaMask = createAlphaMask(softMask); + if (alphaMask.size() != image.size()) + { + // Scale the alpha mask, if it is masked + alphaMask = alphaMask.scaled(image.size()); + } + + for (unsigned int i = 0, rowCount = imageData.getHeight(); i < rowCount; ++i) + { + reader.seek(i * imageData.getStride()); + unsigned char* outputLine = image.scanLine(i); + unsigned char* alphaLine = alphaMask.scanLine(i); + + for (unsigned int j = 0; j < imageData.getWidth(); ++j) + { + PDFBitReader::Value index = reader.read(); + color[0] = index; + + QColor transformedColor = getColor(color, cms, intent, reporter, false); + QRgb rgb = transformedColor.rgb(); + + *outputLine++ = qRed(rgb); + *outputLine++ = qGreen(rgb); + *outputLine++ = qBlue(rgb); + *outputLine++ = *alphaLine++; + } + } + + return image; + } + + default: + throw PDFRendererException(RenderErrorType::NotImplemented, PDFTranslationContext::tr("Image masking not implemented!")); + } + } + + return QImage(); +} + +PDFColorSpacePointer PDFIndexedColorSpace::createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion, + std::set& usedNames) +{ + Q_ASSERT(array->getCount() == 4); + + // Read base color space + PDFColorSpacePointer baseColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(1)), recursion, usedNames); + + if (!baseColorSpace) + { + throw PDFException(PDFTranslationContext::tr("Can't determine base color space for indexed color space.")); + } + + // Read maximum value + PDFDocumentDataLoaderDecorator loader(document); + const int maxValue = qBound(MIN_VALUE, loader.readInteger(array->getItem(2), 0), MAX_VALUE); + + // Read stream/byte string with corresponding color values + QByteArray colors; + const PDFObject& colorDataObject = document->getObject(array->getItem(3)); + + if (colorDataObject.isString()) + { + colors = colorDataObject.getString(); + } + else if (colorDataObject.isStream()) + { + colors = document->getDecodedStream(colorDataObject.getStream()); + } + + // Check, if we have enough colors + const int colorCount = maxValue - MIN_VALUE + 1; + const int componentCount = static_cast(baseColorSpace->getColorComponentCount()); + const int byteCount = colorCount * componentCount; + if (byteCount > colors.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colors for indexed color space. Color space has %1 colors, %2 color components and must have %3 size. Provided size is %4.").arg(colorCount).arg(componentCount).arg(byteCount).arg(colors.size())); + } + + return PDFColorSpacePointer(new PDFIndexedColorSpace(qMove(baseColorSpace), qMove(colors), maxValue)); +} + +PDFColorSpacePointer PDFIndexedColorSpace::getBaseColorSpace() const +{ + return m_baseColorSpace; +} + +std::vector PDFIndexedColorSpace::transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const +{ + const std::size_t colorComponentCount = m_baseColorSpace->getColorComponentCount(); + std::vector result(buffer.size() * colorComponentCount, 0.0f); + + auto outputIt = result.begin(); + for (PDFColorComponent input : buffer) + { + const int colorIndex = qBound(MIN_VALUE, static_cast(input), m_maxValue); + const int byteOffset = int(colorIndex * colorComponentCount); + + // We must point into the array. Check first and last component. + Q_ASSERT(byteOffset + colorComponentCount - 1 < m_colors.size()); + + const char* bytePointer = m_colors.constData() + byteOffset; + + for (int i = 0; i < colorComponentCount; ++i) + { + const unsigned char value = *bytePointer++; + const PDFColorComponent component = static_cast(value) / 255.0f; + Q_ASSERT(outputIt != result.cend()); + *outputIt++ = component; + } + } + Q_ASSERT(outputIt == result.cend()); + + return result; +} + +int PDFIndexedColorSpace::getMaxValue() const +{ + return m_maxValue; +} + +const QByteArray& PDFIndexedColorSpace::getColors() const +{ + return m_colors; +} + +PDFSeparationColorSpace::PDFSeparationColorSpace(QByteArray&& colorName, PDFColorSpacePointer alternateColorSpace, PDFFunctionPtr tintTransform) : + m_colorName(qMove(colorName)), + m_alternateColorSpace(qMove(alternateColorSpace)), + m_tintTransform(qMove(tintTransform)), + m_isNone(m_colorName == "None"), + m_isAll(m_colorName == "All") +{ + +} + +bool PDFSeparationColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFAbstractColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFSeparationColorSpace* typedOther = static_cast(other); + + const PDFAbstractColorSpace* alternateColorSpace = typedOther->getAlternateColorSpace().data(); + + if (static_cast(m_alternateColorSpace.data()) != static_cast(alternateColorSpace)) + { + return false; + } + + if (m_alternateColorSpace && alternateColorSpace && !m_alternateColorSpace->equals(alternateColorSpace)) + { + return false; + } + + return m_colorName == typedOther->getColorName(); +} + +PDFColor PDFSeparationColorSpace::getDefaultColorOriginal() const +{ + return PDFColor(1.0f); +} + +QColor PDFSeparationColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + // Separation color space value must have exactly one component! + Q_ASSERT(color.size() == 1); + Q_UNUSED(isRange01); + + // According to the PDF 2.0 specification, separation color space, with colorant name "None" + // should not produce any visible output. + if (m_isNone) + { + return Qt::transparent; + } + + // Input value + double tint = color.back(); + + // Jakub Melka: According to the PDF 2.0 specification, separation color space, with colorant name "All" + // should apply tint value to all output colorants, alternate color space and tint function should + // be ignored, and because QColor is aditive, we must invert the tint value. + if (m_isAll) + { + const double inversedTint = qBound(0.0, 1.0 - tint, 1.0); + return QColor::fromRgbF(inversedTint, inversedTint, inversedTint); + } + + // Output values + std::vector outputColor; + outputColor.resize(m_alternateColorSpace->getColorComponentCount(), 0.0); + PDFFunction::FunctionResult result = m_tintTransform->apply(&tint, &tint + 1, outputColor.data(), outputColor.data() + outputColor.size()); + + if (result) + { + PDFColor color; + std::for_each(outputColor.cbegin(), outputColor.cend(), [&color](double value) { color.push_back(static_cast(value)); }); + return m_alternateColorSpace->getColor(color, cms, intent, reporter, false); + } + else + { + // Return invalid color + return QColor(); + } +} + +size_t PDFSeparationColorSpace::getColorComponentCount() const +{ + return 1; +} + +std::vector PDFSeparationColorSpace::transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const +{ + const std::size_t colorComponentCount = m_alternateColorSpace->getColorComponentCount(); + std::vector result(buffer.size() * colorComponentCount, 0.0f); + + std::vector outputColor; + outputColor.resize(colorComponentCount, 0.0); + + auto outputIt = result.begin(); + for (PDFColorComponent input : buffer) + { + // Input value + double tint = input; + + Q_ASSERT(outputIt + (colorComponentCount - 1) != result.cend()); + + if (m_isAll) + { + const double inversedTint = qBound(0.0, 1.0 - tint, 1.0); + std::fill(outputIt, outputIt + colorComponentCount, inversedTint); + } + else + { + m_tintTransform->apply(&tint, &tint + 1, outputColor.data(), outputColor.data() + outputColor.size()); + std::copy(outputColor.cbegin(), outputColor.cend(), outputIt); + } + + outputIt = std::next(outputIt, colorComponentCount); + } + Q_ASSERT(outputIt == result.cend()); + + return result; +} + +PDFColorSpacePointer PDFSeparationColorSpace::createSeparationColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion, + std::set& usedNames) +{ + Q_ASSERT(array->getCount() == 4); + + // Read color name + const PDFObject& colorNameObject = document->getObject(array->getItem(1)); + if (!colorNameObject.isName()) + { + throw PDFException(PDFTranslationContext::tr("Can't determine color name for separation color space.")); + } + QByteArray colorName = colorNameObject.getString(); + + // Read alternate color space + PDFColorSpacePointer alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(2)), recursion, usedNames); + if (!alternateColorSpace) + { + throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for separation color space.")); + } + + PDFFunctionPtr tintTransform = PDFFunction::createFunction(document, array->getItem(3)); + if (!tintTransform) + { + throw PDFException(PDFTranslationContext::tr("Can't determine tint transform for separation color space.")); + } + + return PDFColorSpacePointer(new PDFSeparationColorSpace(qMove(colorName), qMove(alternateColorSpace), qMove(tintTransform))); +} + +PDFColorSpacePointer PDFSeparationColorSpace::getAlternateColorSpace() const +{ + return m_alternateColorSpace; +} + +const QByteArray& PDFSeparationColorSpace::getColorName() const +{ + return m_colorName; +} + +const unsigned char* PDFImageData::getRow(unsigned int rowIndex) const +{ + const unsigned char* data = reinterpret_cast(m_data.constData()); + + Q_ASSERT(rowIndex < m_height); + return data + (rowIndex * m_stride); +} + +bool PDFPatternColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + // Compare pointers + return this == other; +} + +QColor PDFPatternColorSpace::getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const +{ + Q_UNUSED(cms); + Q_UNUSED(intent); + Q_UNUSED(reporter); + + return QColor(Qt::transparent); +} + +PDFColor PDFPatternColorSpace::getDefaultColorOriginal() const +{ + return PDFColor(); +} + +QColor PDFPatternColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_UNUSED(color); + Q_UNUSED(cms); + Q_UNUSED(intent); + Q_UNUSED(reporter); + Q_UNUSED(isRange01); + + throw PDFException(PDFTranslationContext::tr("Pattern doesn't have defined uniform color.")); +} + +size_t PDFPatternColorSpace::getColorComponentCount() const +{ + return 0; +} + +PDFDeviceNColorSpace::PDFDeviceNColorSpace(PDFDeviceNColorSpace::Type type, + PDFDeviceNColorSpace::Colorants&& colorants, + PDFColorSpacePointer alternateColorSpace, + PDFColorSpacePointer processColorSpace, + PDFFunctionPtr tintTransform, + std::vector&& colorantsPrintingOrder, + std::vector processColorSpaceComponents) : + m_type(type), + m_colorants(qMove(colorants)), + m_alternateColorSpace(qMove(alternateColorSpace)), + m_processColorSpace(qMove(processColorSpace)), + m_tintTransform(qMove(tintTransform)), + m_colorantsPrintingOrder(qMove(colorantsPrintingOrder)), + m_processColorSpaceComponents(qMove(processColorSpaceComponents)), + m_isNone(false) +{ + m_isNone = std::all_of(m_colorants.cbegin(), m_colorants.cend(), [](const auto& colorant) { return colorant.name == "None"; }); +} + +bool PDFDeviceNColorSpace::equals(const PDFAbstractColorSpace* other) const +{ + if (!PDFAbstractColorSpace::equals(other)) + { + return false; + } + + Q_ASSERT(dynamic_cast(other)); + const PDFDeviceNColorSpace* typedOther = static_cast(other); + + const PDFAbstractColorSpace* alternateColorSpace = typedOther->getAlternateColorSpace().data(); + + if (static_cast(m_alternateColorSpace.data()) != static_cast(alternateColorSpace)) + { + return false; + } + + if (m_alternateColorSpace && alternateColorSpace && !m_alternateColorSpace->equals(alternateColorSpace)) + { + return false; + } + + const Colorants& colorants = typedOther->getColorants(); + + if (m_colorants.size() != colorants.size()) + { + return false; + } + + for (size_t i = 0; i < m_colorants.size(); ++i) + { + if (m_colorants[i].name != colorants[i].name) + { + return false; + } + } + + return true; +} + +PDFColor PDFDeviceNColorSpace::getDefaultColorOriginal() const +{ + PDFColor color; + color.resize(getColorComponentCount()); + + // Jakub Melka: According to the PDF 2.0 specification, each channel should + // be initially set to 1.0. + for (size_t i = 0, colorComponentCount = color.size(); i < colorComponentCount; ++i) + { + color[i] = 1.0; + } + + return color; +} + +QColor PDFDeviceNColorSpace::getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const +{ + Q_UNUSED(isRange01); + + // According to the PDF 2.0 specification, DeviceN color space, with all colorant name "None" + // should not produce any visible output. + if (m_isNone) + { + return Qt::transparent; + } + + // Input values + std::vector inputColor(color.size(), 0.0); + for (size_t i = 0, count = inputColor.size(); i < count; ++i) + { + inputColor[i] = color[i]; + } + + // Output values + std::vector outputColor; + outputColor.resize(m_alternateColorSpace->getColorComponentCount(), 0.0); + PDFFunction::FunctionResult result = m_tintTransform->apply(inputColor.data(), inputColor.data() + inputColor.size(), outputColor.data(), outputColor.data() + outputColor.size()); + + if (result) + { + PDFColor color; + std::for_each(outputColor.cbegin(), outputColor.cend(), [&color](double value) { color.push_back(static_cast(value)); }); + return m_alternateColorSpace->getColor(color, cms, intent, reporter, false); + } + else + { + // Return invalid color + return QColor(); + } +} + +size_t PDFDeviceNColorSpace::getColorComponentCount() const +{ + return m_colorants.size(); +} + +std::vector PDFDeviceNColorSpace::transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const +{ + std::vector result; + + const std::size_t colorantCount = getColorComponentCount(); + if (colorantCount > 0) + { + const std::size_t inputColorCount = buffer.size() / colorantCount; + const std::size_t alternateColorSpaceComponentCount = m_alternateColorSpace->getColorComponentCount(); + result.resize(inputColorCount * alternateColorSpaceComponentCount, 0.0f); + + std::vector inputColor(colorantCount, 0.0); + std::vector outputColor(alternateColorSpaceComponentCount, 0.0); + + auto outputIt = result.begin(); + for (auto it = buffer.begin(); it != buffer.end(); it = std::next(it, colorantCount)) + { + std::copy(it, it + colorantCount, inputColor.begin()); + m_tintTransform->apply(inputColor.data(), inputColor.data() + inputColor.size(), outputColor.data(), outputColor.data() + outputColor.size()); + std::copy(outputColor.cbegin(), outputColor.cend(), outputIt); + outputIt = std::next(outputIt, alternateColorSpaceComponentCount); + } + Q_ASSERT(outputIt == result.cend()); + } + + return result; +} + +PDFColorSpacePointer PDFDeviceNColorSpace::createDeviceNColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion, + std::set& usedNames) +{ + Q_ASSERT(array->getCount() >= 4); + + PDFDocumentDataLoaderDecorator loader(document); + std::vector colorantNames = loader.readNameArray(array->getItem(1)); + + if (colorantNames.empty()) + { + throw PDFException(PDFTranslationContext::tr("Invalid colorants for DeviceN color space.")); + } + + std::vector colorants; + colorants.resize(colorantNames.size()); + for (size_t i = 0; i < colorantNames.size(); ++i) + { + colorants[i].name = qMove(colorantNames[i]); + } + + // Read alternate color space + PDFColorSpacePointer alternateColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(array->getItem(2)), recursion, usedNames); + if (!alternateColorSpace) + { + throw PDFException(PDFTranslationContext::tr("Can't determine alternate color space for DeviceN color space.")); + } + + PDFFunctionPtr tintTransform = PDFFunction::createFunction(document, array->getItem(3)); + if (!tintTransform) + { + throw PDFException(PDFTranslationContext::tr("Can't determine tint transform for DeviceN color space.")); + } + + Type type = Type::DeviceN; + std::vector colorantsPrintingOrder; + PDFColorSpacePointer processColorSpace; + std::vector processColorSpaceComponents; + + // Now, check, if we have attributes, and if yes, then read them + if (array->getCount() == 5) + { + const PDFObject& object = document->getObject(array->getItem(4)); + if (object.isDictionary()) + { + const PDFDictionary* attributesDictionary = object.getDictionary(); + QByteArray subtype = loader.readNameFromDictionary(attributesDictionary, "Subtype"); + if (subtype == "NChannel") + { + type = Type::NChannel; + } + + const PDFObject& colorantsObject = document->getObject(attributesDictionary->get("Colorants")); + if (colorantsObject.isDictionary()) + { + const PDFDictionary* colorantsDictionary = colorantsObject.getDictionary(); + + // Separation color spaces for each colorant + for (ColorantInfo& colorantInfo : colorants) + { + if (colorantsDictionary->hasKey(colorantInfo.name)) + { + colorantInfo.separationColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, document->getObject(colorantsDictionary->get(colorantInfo.name)), recursion, usedNames); + } + } + } + + const PDFObject& mixingHints = document->getObject(attributesDictionary->get("MixingHints")); + if (mixingHints.isDictionary()) + { + const PDFDictionary* mixingHintsDictionary = mixingHints.getDictionary(); + + // Printing order + colorantsPrintingOrder = loader.readNameArray(mixingHintsDictionary->get("PrintingOrder")); + + // Solidities + const PDFObject& solidityObject = document->getObject(mixingHintsDictionary->get("Solidites")); + if (solidityObject.isDictionary()) + { + const PDFDictionary* solidityDictionary = solidityObject.getDictionary(); + const PDFReal defaultSolidity = loader.readNumberFromDictionary(solidityDictionary, "Default", 0.0); + for (ColorantInfo& colorantInfo : colorants) + { + colorantInfo.solidity = loader.readNumberFromDictionary(solidityDictionary, colorantInfo.name, defaultSolidity); + } + } + + // Dot gain + const PDFObject& dotGainObject = document->getObject(mixingHintsDictionary->get("DotGain")); + if (dotGainObject.isDictionary()) + { + const PDFDictionary* dotGainDictionary = dotGainObject.getDictionary(); + for (ColorantInfo& colorantInfo : colorants) + { + const PDFObject& dotGainFunctionObject = document->getObject(dotGainDictionary->get(colorantInfo.name)); + if (!dotGainFunctionObject.isNull()) + { + colorantInfo.dotGain = PDFFunction::createFunction(document, dotGainFunctionObject); + } + } + } + } + + // Process + const PDFObject& processObject = document->getObject(attributesDictionary->get("Process")); + if (processObject.isDictionary()) + { + const PDFDictionary* processDictionary = processObject.getDictionary(); + const PDFObject& processColorSpaceObject = document->getObject(processDictionary->get("ColorSpace")); + if (!processColorSpaceObject.isNull()) + { + processColorSpace = PDFAbstractColorSpace::createColorSpaceImpl(colorSpaceDictionary, document, processColorSpaceObject, recursion, usedNames); + processColorSpaceComponents = loader.readNameArrayFromDictionary(processDictionary, "Components"); + } + } + } + } + + return PDFColorSpacePointer(new PDFDeviceNColorSpace(type, qMove(colorants), qMove(alternateColorSpace), qMove(processColorSpace), qMove(tintTransform), qMove(colorantsPrintingOrder), qMove(processColorSpaceComponents))); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfcolorspaces.h b/Pdf4QtLib/sources/pdfcolorspaces.h index 3e14231..0bc6dca 100644 --- a/Pdf4QtLib/sources/pdfcolorspaces.h +++ b/Pdf4QtLib/sources/pdfcolorspaces.h @@ -1,931 +1,931 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFCOLORSPACES_H -#define PDFCOLORSPACES_H - -#include "pdfflatarray.h" -#include "pdffunction.h" -#include "pdfutils.h" - -#include -#include -#include - -#include - -namespace pdf -{ -class PDFCMS; -class PDFArray; -class PDFObject; -class PDFStream; -class PDFPattern; -class PDFDocument; -class PDFDictionary; -class PDFAbstractColorSpace; -class PDFPatternColorSpace; -class PDFRenderErrorReporter; - -using PDFColor = PDFFlatArray; -using PDFColorSpacePointer = QSharedPointer; -using PDFColorBuffer = PDFBuffer; -using PDFConstColorBuffer = PDFBuffer; - -static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12; - -static constexpr const char* COLOR_SPACE_DICTIONARY = "ColorSpace"; - -static constexpr const char* COLOR_SPACE_NAME_DEVICE_GRAY = "DeviceGray"; -static constexpr const char* COLOR_SPACE_NAME_DEVICE_RGB = "DeviceRGB"; -static constexpr const char* COLOR_SPACE_NAME_DEVICE_CMYK = "DeviceCMYK"; - -static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY = "G"; -static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB = "RGB"; -static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK = "CMYK"; -static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_CAL_CMYK = "CalCMYK"; - -static constexpr const char* COLOR_SPACE_NAME_DEFAULT_GRAY = "DefaultGray"; -static constexpr const char* COLOR_SPACE_NAME_DEFAULT_RGB = "DefaultRGB"; -static constexpr const char* COLOR_SPACE_NAME_DEFAULT_CMYK = "DefaultCMYK"; - -static constexpr const char* COLOR_SPACE_NAME_CAL_GRAY = "CalGray"; -static constexpr const char* COLOR_SPACE_NAME_CAL_RGB = "CalRGB"; -static constexpr const char* COLOR_SPACE_NAME_LAB = "Lab"; -static constexpr const char* COLOR_SPACE_NAME_ICCBASED = "ICCBased"; -static constexpr const char* COLOR_SPACE_NAME_INDEXED = "Indexed"; -static constexpr const char* COLOR_SPACE_NAME_SEPARATION = "Separation"; -static constexpr const char* COLOR_SPACE_NAME_DEVICE_N = "DeviceN"; -static constexpr const char* COLOR_SPACE_NAME_PATTERN = "Pattern"; - -static constexpr const char* CAL_WHITE_POINT = "WhitePoint"; -static constexpr const char* CAL_BLACK_POINT = "BlackPoint"; -static constexpr const char* CAL_GAMMA = "Gamma"; -static constexpr const char* CAL_MATRIX = "Matrix"; -static constexpr const char* CAL_RANGE = "Range"; - -static constexpr const char* ICCBASED_ALTERNATE = "Alternate"; -static constexpr const char* ICCBASED_N = "N"; -static constexpr const char* ICCBASED_RANGE = "Range"; - -enum class BlackPointCompensationMode -{ - Default, - ON, - OFF -}; - -/// Image raw data - containing data for image. Image data are row-ordered, and by components. -/// So the row can be for 3-components RGB like 'RGBRGBRGB...RGB', where size of row in bytes is 3 * width of image. -class PDFImageData -{ -public: - - enum class MaskingType - { - None, - ColorKeyMasking, ///< Masking by color key - ImageMask, ///< Masking by 1-bit image (see "ImageMask" entry in image's dictionary), current color from the graphic state is used to paint an image - SoftMask, ///< Image is masked by soft mask - }; - - explicit PDFImageData() : - m_components(0), - m_bitsPerComponent(0), - m_width(0), - m_height(0), - m_stride(0), - m_maskingType(MaskingType::None) - { - - } - - explicit inline PDFImageData(unsigned int components, - unsigned int bitsPerComponent, - unsigned int width, - unsigned int height, - unsigned int stride, - MaskingType maskingType, - QByteArray data, - std::vector&& colorKeyMask, - std::vector&& decode, - std::vector&& matte) : - m_components(components), - m_bitsPerComponent(bitsPerComponent), - m_width(width), - m_height(height), - m_stride(stride), - m_maskingType(maskingType), - m_data(qMove(data)), - m_colorKeyMask(qMove(colorKeyMask)), - m_decode(qMove(decode)), - m_matte(qMove(matte)) - { - - } - - unsigned int getComponents() const { return m_components; } - unsigned int getBitsPerComponent() const { return m_bitsPerComponent; } - unsigned int getWidth() const { return m_width; } - unsigned int getHeight() const { return m_height; } - unsigned int getStride() const { return m_stride; } - MaskingType getMaskingType() const { return m_maskingType; } - const QByteArray& getData() const { return m_data; } - const std::vector& getColorKeyMask() const { return m_colorKeyMask; } - const std::vector& getDecode() const { return m_decode; } - const std::vector& getMatte() const { return m_matte; } - - void setMaskingType(MaskingType maskingType) { m_maskingType = maskingType; } - void setDecode(std::vector decode) { m_decode = qMove(decode); } - - /// Returns number of color channels - unsigned int getColorChannels() const { return m_components; } - - bool isValid() const { return m_width && m_height && m_components && m_bitsPerComponent; } - - const unsigned char* getRow(unsigned int rowIndex) const; - -private: - unsigned int m_components; - unsigned int m_bitsPerComponent; - unsigned int m_width; - unsigned int m_height; - unsigned int m_stride; - MaskingType m_maskingType; - - QByteArray m_data; - - /// Mask entry of the image. If it is empty, no color key masking is induced. - /// If it is not empty, then it should contain 2 x number of color components, - /// consisting of [ min_0, max_0, min_1, max_1, ... , min_n, max_n ]. - std::vector m_colorKeyMask; - - /// Decode array. If it is empty, then no decoding is performed. If it is nonempty, - /// then contains n pairs of numbers, where n is number of color components. If ImageMask - /// in the image dictionary is true, then decode array should be [0 1] or [1 0]. - std::vector m_decode; - - /// Matte color (color, agains which is image preblended, when using soft masking - /// image (defined for soft masks). - std::vector m_matte; -}; - -using PDFColor3 = std::array; - -/// Matrix for color component multiplication (for example, conversion between some color spaces) -template -class PDFColorComponentMatrix -{ -public: - explicit constexpr inline PDFColorComponentMatrix() : m_values() { } - - template - explicit constexpr inline PDFColorComponentMatrix(Components... components) : m_values({ static_cast(components)... }) { } - - bool operator==(const PDFColorComponentMatrix&) const = default; - bool operator!=(const PDFColorComponentMatrix&) const = default; - - std::array operator*(const std::array& color) const - { - std::array result = { }; - - for (size_t row = 0; row < Rows; ++row) - { - for (size_t column = 0; column < Cols; ++column) - { - result[row] += m_values[row * Cols + column] * color[column]; - } - } - - return result; - } - - inline PDFColorComponent getValue(size_t row, size_t column) const - { - return m_values[row * Cols + column]; - } - inline void setValue(size_t row, size_t column, PDFColorComponent value) - { - m_values[row * Cols + column] = value; - } - - void transpose() - { - Q_ASSERT(Rows == Cols); - - for (size_t row = 0; row < Rows; ++row) - { - for (size_t column = row; column < Cols; ++column) - { - const size_t index1 = row * Cols + column; - const size_t index2 = column * Cols + row; - std::swap(m_values[index1], m_values[index2]); - } - } - } - - void makeIdentity() - { - Q_ASSERT(Rows == Cols); - - m_values = { }; - - for (size_t row = 0; row < Rows; ++row) - { - const size_t index = row * Cols + row; - m_values[index] = PDFColorComponent(1.0f); - } - } - - void makeDiagonal(auto diagonalItems) - { - Q_ASSERT(Rows == Cols); - Q_ASSERT(diagonalItems.size() == Rows); - - m_values = { }; - - for (size_t row = 0; row < Rows; ++row) - { - const size_t index = row * Cols + row; - m_values[index] = diagonalItems[row]; - } - } - - void multiplyByFactor(PDFColorComponent factor) - { - for (auto it = begin(); it != end(); ++it) - { - *it *= factor; - } - } - - inline typename std::array::iterator begin() { return m_values.begin(); } - inline typename std::array::iterator end() { return m_values.end(); } - -private: - std::array m_values; -}; - -template -static inline PDFColorComponentMatrix operator*(const PDFColorComponentMatrix& left, const PDFColorComponentMatrix& right) -{ - PDFColorComponentMatrix result; - - for (size_t ci = 0; ci < i; ++ci) - { - for (size_t cj = 0; cj < j; ++cj) - { - PDFColorComponent value = 0.0f; - - for (size_t ck = 0; ck < k; ++ck) - { - value += left.getValue(ci, ck) * right.getValue(ck, cj); - } - - result.setValue(ci, cj, value); - } - } - - return result; -} - -using PDFColorComponentMatrix_3x3 = PDFColorComponentMatrix<3, 3>; - -/// Represents PDF's color space (abstract class). Contains functions for parsing -/// color spaces. -class PDF4QTLIBSHARED_EXPORT PDFAbstractColorSpace -{ -public: - explicit PDFAbstractColorSpace() = default; - virtual ~PDFAbstractColorSpace() = default; - - enum class ColorSpace - { - DeviceGray, - DeviceRGB, - DeviceCMYK, - CalGray, - CalRGB, - Lab, - ICCBased, - Indexed, - Separation, - DeviceN, - Pattern, - Invalid - }; - - /// Returns color space identification - virtual ColorSpace getColorSpace() const = 0; - - /// Returns true, if two color spaces are equal - virtual bool equals(const PDFAbstractColorSpace* other) const; - - /// Returns true, if this color space can be used for blending - bool isBlendColorSpace() const; - - /// Returns default color for the color space - virtual QColor getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const; - - /// Returns default color in original color space (not transformed to QColor) - virtual PDFColor getDefaultColorOriginal() const = 0; - - /// Returns transformed color for given input color. Color is transformed using color - /// management system (cms), if color management system fails, and returns invalid color, - /// then generic solution for color transformation is used (which is often not valid). - /// Caller can also specify rendering intent and error reporter, where color management - /// system can write errors during color transformation. - /// \param color Input color - /// \param cms Color management system - /// \param intent Rendering intent - /// \param reporter Error reporter - /// \param isRange01 Is range [0, 1], or native color component range (for Lab spaces)? - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const = 0; - - /// Returns color component count - virtual size_t getColorComponentCount() const = 0; - - /// Transforms image data to result image. - /// \param imageData Image data - /// \param softMask Soft mask (for alpha channel) - /// \param cms Color management system - /// \param intent Rendering intent - /// \param reporter Error reporter - virtual QImage getImage(const PDFImageData& imageData, - const PDFImageData& softMask, - const PDFCMS* cms, - RenderingIntent intent, - PDFRenderErrorReporter* reporter) const; - - /// Fills RGB buffer using colors from \p colors. Colors are transformed - /// by this color space (or color management system is used). Buffer - /// must be big enough to contain all 8-bit RGB data. - /// \param Colors Input color buffer - /// \param intent Rendering intent - /// \param outputBuffer 8-bit RGB output buffer - /// \param cms Color management system - /// \param reporter Render error reporter - virtual void fillRGBBuffer(const std::vector& colors, - unsigned char* outputBuffer, - RenderingIntent intent, - const PDFCMS* cms, - PDFRenderErrorReporter* reporter) const; - - /// If this class is pattern space, returns this, otherwise returns nullptr. - virtual const PDFPatternColorSpace* asPatternColorSpace() const { return nullptr; } - - /// Checks, if number of color components is OK, and if yes, converts them to the QColor value. - /// If they are not OK, exception is thrown. \sa getColor - /// \param color Input color - /// \param cms Color management system - /// \param intent Rendering intent - /// \param reporter Error reporter - QColor getCheckedColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const; - - /// Creates alpha mask from soft image data. Exception is thrown, if something fails. - /// \param softMask Soft mask - static QImage createAlphaMask(const PDFImageData& softMask); - - /// Parses the desired color space. If desired color space is not found, then exception is thrown. - /// If everything is OK, then shared pointer to the new color space is returned. - /// \param colorSpaceDictionary Dictionary containing color spaces of the page - /// \param document Document (for loading objects) - /// \param colorSpace Identification of color space (either name or array), must be a direct object - static PDFColorSpacePointer createColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFObject& colorSpace); - - /// Creates device color space by name. Color space can be created by this function only, if - /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). - /// \param colorSpaceDictionary Dictionary containing color spaces of the page - /// \param document Document (for loading objects) - /// \param name Name of the color space - static PDFColorSpacePointer createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const QByteArray& name); - - /// Converts a vector of real numbers to the PDFColor - static PDFColor convertToColor(const std::vector& components); - - /// Returns true, if two colors are equal (considering the tolerance). So, if one - /// of the color components differs more than \p tolerance from the another, then - /// false is returned. If colors have different number of components, false is returned. - /// \param color1 First color - /// \param color2 Second color - /// \param tolerance Color tolerance - static bool isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance); - - /// Mix colors according the given ratio. - /// \param color1 First color - /// \param color2 Second color - /// \param ratio Mixing ratio - static PDFColor mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio); - - /// Transforms color from source color space to target color space. Target color space - /// must be blend color space. - /// \param source Source color space - /// \param target Target color space (must be blend color space) - /// \param cms Color management system - /// \param intent Rendering intent - /// \param input Input color buffer - /// \param output Output color buffer, must match size of input color buffer - /// \param reporter Error reporter - static bool transform(const PDFAbstractColorSpace* source, - const PDFAbstractColorSpace* target, - const PDFCMS* cms, - RenderingIntent intent, - const PDFColorBuffer input, - PDFColorBuffer output, - PDFRenderErrorReporter* reporter); - -protected: - /// Clips the color component to range [0, 1] - static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound(0.0, component, 1.0); } - - /// Clips the color to range [0 1] in all components - static constexpr PDFColor3 clip01(const PDFColor3& color) - { - PDFColor3 result = color; - - for (PDFColorComponent& component : result) - { - component = clip01(component); - } - - return result; - } - - /// Parses the desired color space. If desired color space is not found, then exception is thrown. - /// If everything is OK, then shared pointer to the new color space is returned. - /// \param colorSpaceDictionary Dictionary containing color spaces of the page - /// \param document Document (for loading objects) - /// \param colorSpace Identification of color space (either name or array), must be a direct object - /// \param recursion Recursion guard - /// \param usedNames Names, which were already parsed - static PDFColorSpacePointer createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFObject& colorSpace, - int recursion, - std::set& usedNames); - - /// Creates device color space by name. Color space can be created by this function only, if - /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). - /// \param colorSpaceDictionary Dictionary containing color spaces of the page - /// \param document Document (for loading objects) - /// \param name Name of the color space - /// \param usedNames Names, which were already parsed - static PDFColorSpacePointer createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const QByteArray& name, - int recursion, - std::set& usedNames); - - /// Converts XYZ value to the standard RGB value (linear). No gamma correction is applied. - /// Default transformation matrix is applied. - /// \param xyzColor Color in XYZ space - static PDFColor3 convertXYZtoRGB(const PDFColor3& xyzColor); - - /// Multiplies color by factor - /// \param color Color to be multiplied - /// \param factor Multiplication factor - static constexpr PDFColor3 colorMultiplyByFactor(const PDFColor3& color, PDFColorComponent factor) - { - PDFColor3 result = color; - for (PDFColorComponent& component : result) - { - component *= factor; - } - return result; - } - - /// Multiplies color by factors (stored in components of the color) - /// \param color Color to be multiplied - /// \param factor Multiplication factors - static constexpr PDFColor3 colorMultiplyByFactors(const PDFColor3& color, const PDFColor3& factors) - { - PDFColor3 result = { }; - for (size_t i = 0; i < color.size(); ++i) - { - result[i] = color[i] * factors[i]; - } - return result; - } - - /// Powers color by factors (stored in components of the color) - /// \param color Color to be multiplied - /// \param factor Power factors - static constexpr PDFColor3 colorPowerByFactors(const PDFColor3& color, const PDFColor3& factors) - { - PDFColor3 result = { }; - for (size_t i = 0; i < color.size(); ++i) - { - result[i] = std::powf(color[i], factors[i]); - } - return result; - } - - /// Converts RGB values of range [0.0, 1.0] to standard QColor - /// \param color Color to be converted - static inline QColor fromRGB01(const PDFColor3& color) - { - PDFColorComponent r = clip01(color[0]); - PDFColorComponent g = clip01(color[1]); - PDFColorComponent b = clip01(color[2]); - - QColor result(QColor::Rgb); - result.setRgbF(r, g, b, 1.0); - return result; - } -}; - -class PDFDeviceGrayColorSpace : public PDFAbstractColorSpace -{ -public: - explicit PDFDeviceGrayColorSpace() = default; - virtual ~PDFDeviceGrayColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceGray; } - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; -}; - -class PDFDeviceRGBColorSpace : public PDFAbstractColorSpace -{ -public: - explicit PDFDeviceRGBColorSpace() = default; - virtual ~PDFDeviceRGBColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceRGB; } - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; -}; - -class PDFDeviceCMYKColorSpace : public PDFAbstractColorSpace -{ -public: - explicit PDFDeviceCMYKColorSpace() = default; - virtual ~PDFDeviceCMYKColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceCMYK; } - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; -}; - -class PDFXYZColorSpace : public PDFAbstractColorSpace -{ -public: - virtual PDFColor getDefaultColorOriginal() const override; - virtual bool equals(const PDFAbstractColorSpace* other) const override; - - const PDFColor3& getWhitePoint() const { return m_whitePoint; } - const PDFColor3& getCorrectionCoefficients() const; - -protected: - explicit PDFXYZColorSpace(PDFColor3 whitePoint); - virtual ~PDFXYZColorSpace() = default; - - PDFColor3 m_whitePoint; - - /// What are these coefficients? We want to map white point from XYZ space to white point - /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white - /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) - /// after multiplication by these coefficients. - PDFColor3 m_correctionCoefficients; -}; - -class PDFCalGrayColorSpace : public PDFXYZColorSpace -{ -public: - explicit PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma); - virtual ~PDFCalGrayColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::CalGray; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; - - PDFColorComponent getGamma() const { return m_gamma; } - const PDFColor3& getBlackPoint() const { return m_blackPoint; } - - /// Creates CalGray color space from provided values. - /// \param document Document - /// \param dictionary Dictionary - static PDFColorSpacePointer createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); - -private: - PDFColor3 m_blackPoint; - PDFColorComponent m_gamma; -}; - -class PDFCalRGBColorSpace : public PDFXYZColorSpace -{ -public: - explicit PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix); - virtual ~PDFCalRGBColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::CalRGB; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; - - /// Creates CalRGB color space from provided values. - /// \param document Document - /// \param dictionary Dictionary - static PDFColorSpacePointer createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); - - PDFColor3 getBlackPoint() const; - PDFColor3 getGamma() const; - const PDFColorComponentMatrix_3x3& getMatrix() const; - -private: - PDFColor3 m_blackPoint; - PDFColor3 m_gamma; - PDFColorComponentMatrix_3x3 m_matrix; -}; - -class PDFLabColorSpace : public PDFXYZColorSpace -{ -public: - explicit PDFLabColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent aMin, PDFColorComponent aMax, PDFColorComponent bMin, PDFColorComponent bMax); - virtual ~PDFLabColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::Lab; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; - - /// Creates Lab color space from provided values. - /// \param document Document - /// \param dictionary Dictionary - static PDFColorSpacePointer createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); - - PDFColorComponent getAMin() const; - PDFColorComponent getAMax() const; - PDFColorComponent getBMin() const; - PDFColorComponent getBMax() const; - - PDFColor3 getBlackPoint() const; - -private: - PDFColor3 m_blackPoint; - PDFColorComponent m_aMin; - PDFColorComponent m_aMax; - PDFColorComponent m_bMin; - PDFColorComponent m_bMax; -}; - -class PDFICCBasedColorSpace : public PDFAbstractColorSpace -{ -public: - static constexpr const size_t MAX_COLOR_COMPONENTS = 4; - using Ranges = std::array; - - explicit PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range, QByteArray iccProfileData, PDFObjectReference metadata); - virtual ~PDFICCBasedColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::ICCBased; } - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual void fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; - virtual bool equals(const PDFAbstractColorSpace* other) const override; - - PDFObjectReference getMetadata() const { return m_metadata; } - - /// Creates ICC based color space from provided values. - /// \param colorSpaceDictionary Color space dictionary - /// \param document Document - /// \param stream Stream with ICC profile - /// \param recursion Recursion guard - /// \param usedNames Names, which were already parsed - static PDFColorSpacePointer createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFStream* stream, - int recursion, - std::set& usedNames); - - const Ranges& getRange() const; - const QByteArray& getIccProfileData() const; - const QByteArray& getIccProfileDataChecksum() const; - const PDFAbstractColorSpace* getAlternateColorSpace() const; - -private: - PDFColorSpacePointer m_alternateColorSpace; - Ranges m_range; - QByteArray m_iccProfileData; - QByteArray m_iccProfileDataChecksum; - PDFObjectReference m_metadata; -}; - -class PDFIndexedColorSpace : public PDFAbstractColorSpace -{ -public: - explicit PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue); - virtual ~PDFIndexedColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::Indexed; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual QImage getImage(const PDFImageData& imageData, - const PDFImageData& softMask, - const PDFCMS* cms, - RenderingIntent intent, - PDFRenderErrorReporter* reporter) const override; - - /// Creates indexed color space from provided values. - /// \param colorSpaceDictionary Color space dictionary - /// \param document Document - /// \param array Array with indexed color space definition - /// \param recursion Recursion guard - /// \param usedNames Names, which were already parsed - static PDFColorSpacePointer createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFArray* array, - int recursion, - std::set& usedNames); - - PDFColorSpacePointer getBaseColorSpace() const; - std::vector transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const; - - int getMaxValue() const; - const QByteArray& getColors() const; - -private: - static constexpr const int MIN_VALUE = 0; - static constexpr const int MAX_VALUE = 255; - - PDFColorSpacePointer m_baseColorSpace; - QByteArray m_colors; - int m_maxValue; -}; - -class PDFSeparationColorSpace : public PDFAbstractColorSpace -{ -public: - explicit PDFSeparationColorSpace(QByteArray&& colorName, PDFColorSpacePointer alternateColorSpace, PDFFunctionPtr tintTransform); - virtual ~PDFSeparationColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::Separation; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - - bool isNone() const { return m_isNone; } - bool isAll() const { return m_isAll; } - - std::vector transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const; - - /// Creates separation color space from provided values. - /// \param colorSpaceDictionary Color space dictionary - /// \param document Document - /// \param array Array with separation color space definition - /// \param recursion Recursion guard - /// \param usedNames Names, which were already parsed - static PDFColorSpacePointer createSeparationColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFArray* array, - int recursion, - std::set& usedNames); - - PDFColorSpacePointer getAlternateColorSpace() const; - const QByteArray& getColorName() const; - -private: - QByteArray m_colorName; - PDFColorSpacePointer m_alternateColorSpace; - PDFFunctionPtr m_tintTransform; - bool m_isNone; - bool m_isAll; -}; - -class PDFDeviceNColorSpace : public PDFAbstractColorSpace -{ -public: - - enum class Type - { - DeviceN, - NChannel - }; - - struct ColorantInfo - { - QByteArray name; - PDFColorSpacePointer separationColorSpace; - PDFReal solidity = 0.0; - PDFFunctionPtr dotGain; - }; - - using Colorants = std::vector; - - explicit PDFDeviceNColorSpace(Type type, - Colorants&& colorants, - PDFColorSpacePointer alternateColorSpace, - PDFColorSpacePointer processColorSpace, - PDFFunctionPtr tintTransform, - std::vector&& colorantsPrintingOrder, - std::vector processColorSpaceComponents); - virtual ~PDFDeviceNColorSpace() = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceN; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - - /// Returns type of DeviceN color space - Type getType() const { return m_type; } - - const Colorants& getColorants() const { return m_colorants; } - const PDFColorSpacePointer& getAlternateColorSpace() const { return m_alternateColorSpace; } - const PDFColorSpacePointer& getProcessColorSpace() const { return m_processColorSpace; } - const PDFFunctionPtr& getTintTransform() const { return m_tintTransform; } - const std::vector& getPrintingOrder() const { return m_colorantsPrintingOrder; } - const std::vector& getProcessColorSpaceComponents() const { return m_processColorSpaceComponents; } - bool isNone() const { return m_isNone; } - - std::vector transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const; - - /// Creates DeviceN color space from provided values. - /// \param colorSpaceDictionary Color space dictionary - /// \param document Document - /// \param array Array with DeviceN color space definition - /// \param recursion Recursion guard - /// \param usedNames Names, which were already parsed - static PDFColorSpacePointer createDeviceNColorSpace(const PDFDictionary* colorSpaceDictionary, - const PDFDocument* document, - const PDFArray* array, - int recursion, - std::set& usedNames); - -private: - Type m_type; - Colorants m_colorants; - PDFColorSpacePointer m_alternateColorSpace; - PDFColorSpacePointer m_processColorSpace; - PDFFunctionPtr m_tintTransform; - std::vector m_colorantsPrintingOrder; - std::vector m_processColorSpaceComponents; - bool m_isNone; -}; - -class PDFPatternColorSpace : public PDFAbstractColorSpace -{ -public: - explicit PDFPatternColorSpace(std::shared_ptr&& pattern, PDFColorSpacePointer&& uncoloredPatternColorSpace, PDFColor uncoloredPatternColor) : - m_pattern(qMove(pattern)), - m_uncoloredPatternColorSpace(qMove(uncoloredPatternColorSpace)), - m_uncoloredPatternColor(qMove(uncoloredPatternColor)) - { - - } - - virtual ~PDFPatternColorSpace() override = default; - - virtual ColorSpace getColorSpace() const override { return ColorSpace::Pattern; } - virtual bool equals(const PDFAbstractColorSpace* other) const override; - virtual QColor getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; - virtual PDFColor getDefaultColorOriginal() const override; - virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; - virtual size_t getColorComponentCount() const override; - virtual const PDFPatternColorSpace* asPatternColorSpace() const override { return this; } - - const PDFPattern* getPattern() const { return m_pattern.get(); } - PDFColorSpacePointer getUncoloredPatternColorSpace() const { return m_uncoloredPatternColorSpace; } - PDFColor getUncoloredPatternColor() const { return m_uncoloredPatternColor; } - -private: - std::shared_ptr m_pattern; - PDFColorSpacePointer m_uncoloredPatternColorSpace; - PDFColor m_uncoloredPatternColor; -}; - -} // namespace pdf - -#endif // PDFCOLORSPACES_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFCOLORSPACES_H +#define PDFCOLORSPACES_H + +#include "pdfflatarray.h" +#include "pdffunction.h" +#include "pdfutils.h" + +#include +#include +#include + +#include + +namespace pdf +{ +class PDFCMS; +class PDFArray; +class PDFObject; +class PDFStream; +class PDFPattern; +class PDFDocument; +class PDFDictionary; +class PDFAbstractColorSpace; +class PDFPatternColorSpace; +class PDFRenderErrorReporter; + +using PDFColor = PDFFlatArray; +using PDFColorSpacePointer = QSharedPointer; +using PDFColorBuffer = PDFBuffer; +using PDFConstColorBuffer = PDFBuffer; + +static constexpr const int COLOR_SPACE_MAX_LEVEL_OF_RECURSION = 12; + +static constexpr const char* COLOR_SPACE_DICTIONARY = "ColorSpace"; + +static constexpr const char* COLOR_SPACE_NAME_DEVICE_GRAY = "DeviceGray"; +static constexpr const char* COLOR_SPACE_NAME_DEVICE_RGB = "DeviceRGB"; +static constexpr const char* COLOR_SPACE_NAME_DEVICE_CMYK = "DeviceCMYK"; + +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_GRAY = "G"; +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_RGB = "RGB"; +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_DEVICE_CMYK = "CMYK"; +static constexpr const char* COLOR_SPACE_NAME_ABBREVIATION_CAL_CMYK = "CalCMYK"; + +static constexpr const char* COLOR_SPACE_NAME_DEFAULT_GRAY = "DefaultGray"; +static constexpr const char* COLOR_SPACE_NAME_DEFAULT_RGB = "DefaultRGB"; +static constexpr const char* COLOR_SPACE_NAME_DEFAULT_CMYK = "DefaultCMYK"; + +static constexpr const char* COLOR_SPACE_NAME_CAL_GRAY = "CalGray"; +static constexpr const char* COLOR_SPACE_NAME_CAL_RGB = "CalRGB"; +static constexpr const char* COLOR_SPACE_NAME_LAB = "Lab"; +static constexpr const char* COLOR_SPACE_NAME_ICCBASED = "ICCBased"; +static constexpr const char* COLOR_SPACE_NAME_INDEXED = "Indexed"; +static constexpr const char* COLOR_SPACE_NAME_SEPARATION = "Separation"; +static constexpr const char* COLOR_SPACE_NAME_DEVICE_N = "DeviceN"; +static constexpr const char* COLOR_SPACE_NAME_PATTERN = "Pattern"; + +static constexpr const char* CAL_WHITE_POINT = "WhitePoint"; +static constexpr const char* CAL_BLACK_POINT = "BlackPoint"; +static constexpr const char* CAL_GAMMA = "Gamma"; +static constexpr const char* CAL_MATRIX = "Matrix"; +static constexpr const char* CAL_RANGE = "Range"; + +static constexpr const char* ICCBASED_ALTERNATE = "Alternate"; +static constexpr const char* ICCBASED_N = "N"; +static constexpr const char* ICCBASED_RANGE = "Range"; + +enum class BlackPointCompensationMode +{ + Default, + ON, + OFF +}; + +/// Image raw data - containing data for image. Image data are row-ordered, and by components. +/// So the row can be for 3-components RGB like 'RGBRGBRGB...RGB', where size of row in bytes is 3 * width of image. +class PDFImageData +{ +public: + + enum class MaskingType + { + None, + ColorKeyMasking, ///< Masking by color key + ImageMask, ///< Masking by 1-bit image (see "ImageMask" entry in image's dictionary), current color from the graphic state is used to paint an image + SoftMask, ///< Image is masked by soft mask + }; + + explicit PDFImageData() : + m_components(0), + m_bitsPerComponent(0), + m_width(0), + m_height(0), + m_stride(0), + m_maskingType(MaskingType::None) + { + + } + + explicit inline PDFImageData(unsigned int components, + unsigned int bitsPerComponent, + unsigned int width, + unsigned int height, + unsigned int stride, + MaskingType maskingType, + QByteArray data, + std::vector&& colorKeyMask, + std::vector&& decode, + std::vector&& matte) : + m_components(components), + m_bitsPerComponent(bitsPerComponent), + m_width(width), + m_height(height), + m_stride(stride), + m_maskingType(maskingType), + m_data(qMove(data)), + m_colorKeyMask(qMove(colorKeyMask)), + m_decode(qMove(decode)), + m_matte(qMove(matte)) + { + + } + + unsigned int getComponents() const { return m_components; } + unsigned int getBitsPerComponent() const { return m_bitsPerComponent; } + unsigned int getWidth() const { return m_width; } + unsigned int getHeight() const { return m_height; } + unsigned int getStride() const { return m_stride; } + MaskingType getMaskingType() const { return m_maskingType; } + const QByteArray& getData() const { return m_data; } + const std::vector& getColorKeyMask() const { return m_colorKeyMask; } + const std::vector& getDecode() const { return m_decode; } + const std::vector& getMatte() const { return m_matte; } + + void setMaskingType(MaskingType maskingType) { m_maskingType = maskingType; } + void setDecode(std::vector decode) { m_decode = qMove(decode); } + + /// Returns number of color channels + unsigned int getColorChannels() const { return m_components; } + + bool isValid() const { return m_width && m_height && m_components && m_bitsPerComponent; } + + const unsigned char* getRow(unsigned int rowIndex) const; + +private: + unsigned int m_components; + unsigned int m_bitsPerComponent; + unsigned int m_width; + unsigned int m_height; + unsigned int m_stride; + MaskingType m_maskingType; + + QByteArray m_data; + + /// Mask entry of the image. If it is empty, no color key masking is induced. + /// If it is not empty, then it should contain 2 x number of color components, + /// consisting of [ min_0, max_0, min_1, max_1, ... , min_n, max_n ]. + std::vector m_colorKeyMask; + + /// Decode array. If it is empty, then no decoding is performed. If it is nonempty, + /// then contains n pairs of numbers, where n is number of color components. If ImageMask + /// in the image dictionary is true, then decode array should be [0 1] or [1 0]. + std::vector m_decode; + + /// Matte color (color, agains which is image preblended, when using soft masking + /// image (defined for soft masks). + std::vector m_matte; +}; + +using PDFColor3 = std::array; + +/// Matrix for color component multiplication (for example, conversion between some color spaces) +template +class PDFColorComponentMatrix +{ +public: + explicit constexpr inline PDFColorComponentMatrix() : m_values() { } + + template + explicit constexpr inline PDFColorComponentMatrix(Components... components) : m_values({ static_cast(components)... }) { } + + bool operator==(const PDFColorComponentMatrix&) const = default; + bool operator!=(const PDFColorComponentMatrix&) const = default; + + std::array operator*(const std::array& color) const + { + std::array result = { }; + + for (size_t row = 0; row < Rows; ++row) + { + for (size_t column = 0; column < Cols; ++column) + { + result[row] += m_values[row * Cols + column] * color[column]; + } + } + + return result; + } + + inline PDFColorComponent getValue(size_t row, size_t column) const + { + return m_values[row * Cols + column]; + } + inline void setValue(size_t row, size_t column, PDFColorComponent value) + { + m_values[row * Cols + column] = value; + } + + void transpose() + { + Q_ASSERT(Rows == Cols); + + for (size_t row = 0; row < Rows; ++row) + { + for (size_t column = row; column < Cols; ++column) + { + const size_t index1 = row * Cols + column; + const size_t index2 = column * Cols + row; + std::swap(m_values[index1], m_values[index2]); + } + } + } + + void makeIdentity() + { + Q_ASSERT(Rows == Cols); + + m_values = { }; + + for (size_t row = 0; row < Rows; ++row) + { + const size_t index = row * Cols + row; + m_values[index] = PDFColorComponent(1.0f); + } + } + + void makeDiagonal(auto diagonalItems) + { + Q_ASSERT(Rows == Cols); + Q_ASSERT(diagonalItems.size() == Rows); + + m_values = { }; + + for (size_t row = 0; row < Rows; ++row) + { + const size_t index = row * Cols + row; + m_values[index] = diagonalItems[row]; + } + } + + void multiplyByFactor(PDFColorComponent factor) + { + for (auto it = begin(); it != end(); ++it) + { + *it *= factor; + } + } + + inline typename std::array::iterator begin() { return m_values.begin(); } + inline typename std::array::iterator end() { return m_values.end(); } + +private: + std::array m_values; +}; + +template +static inline PDFColorComponentMatrix operator*(const PDFColorComponentMatrix& left, const PDFColorComponentMatrix& right) +{ + PDFColorComponentMatrix result; + + for (size_t ci = 0; ci < i; ++ci) + { + for (size_t cj = 0; cj < j; ++cj) + { + PDFColorComponent value = 0.0f; + + for (size_t ck = 0; ck < k; ++ck) + { + value += left.getValue(ci, ck) * right.getValue(ck, cj); + } + + result.setValue(ci, cj, value); + } + } + + return result; +} + +using PDFColorComponentMatrix_3x3 = PDFColorComponentMatrix<3, 3>; + +/// Represents PDF's color space (abstract class). Contains functions for parsing +/// color spaces. +class PDF4QTLIBSHARED_EXPORT PDFAbstractColorSpace +{ +public: + explicit PDFAbstractColorSpace() = default; + virtual ~PDFAbstractColorSpace() = default; + + enum class ColorSpace + { + DeviceGray, + DeviceRGB, + DeviceCMYK, + CalGray, + CalRGB, + Lab, + ICCBased, + Indexed, + Separation, + DeviceN, + Pattern, + Invalid + }; + + /// Returns color space identification + virtual ColorSpace getColorSpace() const = 0; + + /// Returns true, if two color spaces are equal + virtual bool equals(const PDFAbstractColorSpace* other) const; + + /// Returns true, if this color space can be used for blending + bool isBlendColorSpace() const; + + /// Returns default color for the color space + virtual QColor getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const; + + /// Returns default color in original color space (not transformed to QColor) + virtual PDFColor getDefaultColorOriginal() const = 0; + + /// Returns transformed color for given input color. Color is transformed using color + /// management system (cms), if color management system fails, and returns invalid color, + /// then generic solution for color transformation is used (which is often not valid). + /// Caller can also specify rendering intent and error reporter, where color management + /// system can write errors during color transformation. + /// \param color Input color + /// \param cms Color management system + /// \param intent Rendering intent + /// \param reporter Error reporter + /// \param isRange01 Is range [0, 1], or native color component range (for Lab spaces)? + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const = 0; + + /// Returns color component count + virtual size_t getColorComponentCount() const = 0; + + /// Transforms image data to result image. + /// \param imageData Image data + /// \param softMask Soft mask (for alpha channel) + /// \param cms Color management system + /// \param intent Rendering intent + /// \param reporter Error reporter + virtual QImage getImage(const PDFImageData& imageData, + const PDFImageData& softMask, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter) const; + + /// Fills RGB buffer using colors from \p colors. Colors are transformed + /// by this color space (or color management system is used). Buffer + /// must be big enough to contain all 8-bit RGB data. + /// \param Colors Input color buffer + /// \param intent Rendering intent + /// \param outputBuffer 8-bit RGB output buffer + /// \param cms Color management system + /// \param reporter Render error reporter + virtual void fillRGBBuffer(const std::vector& colors, + unsigned char* outputBuffer, + RenderingIntent intent, + const PDFCMS* cms, + PDFRenderErrorReporter* reporter) const; + + /// If this class is pattern space, returns this, otherwise returns nullptr. + virtual const PDFPatternColorSpace* asPatternColorSpace() const { return nullptr; } + + /// Checks, if number of color components is OK, and if yes, converts them to the QColor value. + /// If they are not OK, exception is thrown. \sa getColor + /// \param color Input color + /// \param cms Color management system + /// \param intent Rendering intent + /// \param reporter Error reporter + QColor getCheckedColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const; + + /// Creates alpha mask from soft image data. Exception is thrown, if something fails. + /// \param softMask Soft mask + static QImage createAlphaMask(const PDFImageData& softMask); + + /// Parses the desired color space. If desired color space is not found, then exception is thrown. + /// If everything is OK, then shared pointer to the new color space is returned. + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param colorSpace Identification of color space (either name or array), must be a direct object + static PDFColorSpacePointer createColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace); + + /// Creates device color space by name. Color space can be created by this function only, if + /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param name Name of the color space + static PDFColorSpacePointer createDeviceColorSpaceByName(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name); + + /// Converts a vector of real numbers to the PDFColor + static PDFColor convertToColor(const std::vector& components); + + /// Returns true, if two colors are equal (considering the tolerance). So, if one + /// of the color components differs more than \p tolerance from the another, then + /// false is returned. If colors have different number of components, false is returned. + /// \param color1 First color + /// \param color2 Second color + /// \param tolerance Color tolerance + static bool isColorEqual(const PDFColor& color1, const PDFColor& color2, PDFReal tolerance); + + /// Mix colors according the given ratio. + /// \param color1 First color + /// \param color2 Second color + /// \param ratio Mixing ratio + static PDFColor mixColors(const PDFColor& color1, const PDFColor& color2, PDFReal ratio); + + /// Transforms color from source color space to target color space. Target color space + /// must be blend color space. + /// \param source Source color space + /// \param target Target color space (must be blend color space) + /// \param cms Color management system + /// \param intent Rendering intent + /// \param input Input color buffer + /// \param output Output color buffer, must match size of input color buffer + /// \param reporter Error reporter + static bool transform(const PDFAbstractColorSpace* source, + const PDFAbstractColorSpace* target, + const PDFCMS* cms, + RenderingIntent intent, + const PDFColorBuffer input, + PDFColorBuffer output, + PDFRenderErrorReporter* reporter); + +protected: + /// Clips the color component to range [0, 1] + static constexpr PDFColorComponent clip01(PDFColorComponent component) { return qBound(0.0, component, 1.0); } + + /// Clips the color to range [0 1] in all components + static constexpr PDFColor3 clip01(const PDFColor3& color) + { + PDFColor3 result = color; + + for (PDFColorComponent& component : result) + { + component = clip01(component); + } + + return result; + } + + /// Parses the desired color space. If desired color space is not found, then exception is thrown. + /// If everything is OK, then shared pointer to the new color space is returned. + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param colorSpace Identification of color space (either name or array), must be a direct object + /// \param recursion Recursion guard + /// \param usedNames Names, which were already parsed + static PDFColorSpacePointer createColorSpaceImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFObject& colorSpace, + int recursion, + std::set& usedNames); + + /// Creates device color space by name. Color space can be created by this function only, if + /// it is simple - one of the basic device color spaces (gray, RGB or CMYK). + /// \param colorSpaceDictionary Dictionary containing color spaces of the page + /// \param document Document (for loading objects) + /// \param name Name of the color space + /// \param usedNames Names, which were already parsed + static PDFColorSpacePointer createDeviceColorSpaceByNameImpl(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const QByteArray& name, + int recursion, + std::set& usedNames); + + /// Converts XYZ value to the standard RGB value (linear). No gamma correction is applied. + /// Default transformation matrix is applied. + /// \param xyzColor Color in XYZ space + static PDFColor3 convertXYZtoRGB(const PDFColor3& xyzColor); + + /// Multiplies color by factor + /// \param color Color to be multiplied + /// \param factor Multiplication factor + static constexpr PDFColor3 colorMultiplyByFactor(const PDFColor3& color, PDFColorComponent factor) + { + PDFColor3 result = color; + for (PDFColorComponent& component : result) + { + component *= factor; + } + return result; + } + + /// Multiplies color by factors (stored in components of the color) + /// \param color Color to be multiplied + /// \param factor Multiplication factors + static constexpr PDFColor3 colorMultiplyByFactors(const PDFColor3& color, const PDFColor3& factors) + { + PDFColor3 result = { }; + for (size_t i = 0; i < color.size(); ++i) + { + result[i] = color[i] * factors[i]; + } + return result; + } + + /// Powers color by factors (stored in components of the color) + /// \param color Color to be multiplied + /// \param factor Power factors + static constexpr PDFColor3 colorPowerByFactors(const PDFColor3& color, const PDFColor3& factors) + { + PDFColor3 result = { }; + for (size_t i = 0; i < color.size(); ++i) + { + result[i] = std::powf(color[i], factors[i]); + } + return result; + } + + /// Converts RGB values of range [0.0, 1.0] to standard QColor + /// \param color Color to be converted + static inline QColor fromRGB01(const PDFColor3& color) + { + PDFColorComponent r = clip01(color[0]); + PDFColorComponent g = clip01(color[1]); + PDFColorComponent b = clip01(color[2]); + + QColor result(QColor::Rgb); + result.setRgbF(r, g, b, 1.0); + return result; + } +}; + +class PDFDeviceGrayColorSpace : public PDFAbstractColorSpace +{ +public: + explicit PDFDeviceGrayColorSpace() = default; + virtual ~PDFDeviceGrayColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceGray; } + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; +}; + +class PDFDeviceRGBColorSpace : public PDFAbstractColorSpace +{ +public: + explicit PDFDeviceRGBColorSpace() = default; + virtual ~PDFDeviceRGBColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceRGB; } + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; +}; + +class PDFDeviceCMYKColorSpace : public PDFAbstractColorSpace +{ +public: + explicit PDFDeviceCMYKColorSpace() = default; + virtual ~PDFDeviceCMYKColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceCMYK; } + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; +}; + +class PDFXYZColorSpace : public PDFAbstractColorSpace +{ +public: + virtual PDFColor getDefaultColorOriginal() const override; + virtual bool equals(const PDFAbstractColorSpace* other) const override; + + const PDFColor3& getWhitePoint() const { return m_whitePoint; } + const PDFColor3& getCorrectionCoefficients() const; + +protected: + explicit PDFXYZColorSpace(PDFColor3 whitePoint); + virtual ~PDFXYZColorSpace() = default; + + PDFColor3 m_whitePoint; + + /// What are these coefficients? We want to map white point from XYZ space to white point + /// of RGB space. These coefficients are reciprocal values to the point converted from XYZ white + /// point. So, if we call getColor(m_whitePoint), then we should get vector (1.0, 1.0, 1.0) + /// after multiplication by these coefficients. + PDFColor3 m_correctionCoefficients; +}; + +class PDFCalGrayColorSpace : public PDFXYZColorSpace +{ +public: + explicit PDFCalGrayColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent gamma); + virtual ~PDFCalGrayColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::CalGray; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; + + PDFColorComponent getGamma() const { return m_gamma; } + const PDFColor3& getBlackPoint() const { return m_blackPoint; } + + /// Creates CalGray color space from provided values. + /// \param document Document + /// \param dictionary Dictionary + static PDFColorSpacePointer createCalGrayColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); + +private: + PDFColor3 m_blackPoint; + PDFColorComponent m_gamma; +}; + +class PDFCalRGBColorSpace : public PDFXYZColorSpace +{ +public: + explicit PDFCalRGBColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColor3 gamma, PDFColorComponentMatrix_3x3 matrix); + virtual ~PDFCalRGBColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::CalRGB; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; + + /// Creates CalRGB color space from provided values. + /// \param document Document + /// \param dictionary Dictionary + static PDFColorSpacePointer createCalRGBColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); + + PDFColor3 getBlackPoint() const; + PDFColor3 getGamma() const; + const PDFColorComponentMatrix_3x3& getMatrix() const; + +private: + PDFColor3 m_blackPoint; + PDFColor3 m_gamma; + PDFColorComponentMatrix_3x3 m_matrix; +}; + +class PDFLabColorSpace : public PDFXYZColorSpace +{ +public: + explicit PDFLabColorSpace(PDFColor3 whitePoint, PDFColor3 blackPoint, PDFColorComponent aMin, PDFColorComponent aMax, PDFColorComponent bMin, PDFColorComponent bMax); + virtual ~PDFLabColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::Lab; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors,unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; + + /// Creates Lab color space from provided values. + /// \param document Document + /// \param dictionary Dictionary + static PDFColorSpacePointer createLabColorSpace(const PDFDocument* document, const PDFDictionary* dictionary); + + PDFColorComponent getAMin() const; + PDFColorComponent getAMax() const; + PDFColorComponent getBMin() const; + PDFColorComponent getBMax() const; + + PDFColor3 getBlackPoint() const; + +private: + PDFColor3 m_blackPoint; + PDFColorComponent m_aMin; + PDFColorComponent m_aMax; + PDFColorComponent m_bMin; + PDFColorComponent m_bMax; +}; + +class PDFICCBasedColorSpace : public PDFAbstractColorSpace +{ +public: + static constexpr const size_t MAX_COLOR_COMPONENTS = 4; + using Ranges = std::array; + + explicit PDFICCBasedColorSpace(PDFColorSpacePointer alternateColorSpace, Ranges range, QByteArray iccProfileData, PDFObjectReference metadata); + virtual ~PDFICCBasedColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::ICCBased; } + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual void fillRGBBuffer(const std::vector& colors, unsigned char* outputBuffer, RenderingIntent intent, const PDFCMS* cms, PDFRenderErrorReporter* reporter) const override; + virtual bool equals(const PDFAbstractColorSpace* other) const override; + + PDFObjectReference getMetadata() const { return m_metadata; } + + /// Creates ICC based color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param stream Stream with ICC profile + /// \param recursion Recursion guard + /// \param usedNames Names, which were already parsed + static PDFColorSpacePointer createICCBasedColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFStream* stream, + int recursion, + std::set& usedNames); + + const Ranges& getRange() const; + const QByteArray& getIccProfileData() const; + const QByteArray& getIccProfileDataChecksum() const; + const PDFAbstractColorSpace* getAlternateColorSpace() const; + +private: + PDFColorSpacePointer m_alternateColorSpace; + Ranges m_range; + QByteArray m_iccProfileData; + QByteArray m_iccProfileDataChecksum; + PDFObjectReference m_metadata; +}; + +class PDFIndexedColorSpace : public PDFAbstractColorSpace +{ +public: + explicit PDFIndexedColorSpace(PDFColorSpacePointer baseColorSpace, QByteArray&& colors, int maxValue); + virtual ~PDFIndexedColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::Indexed; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual QImage getImage(const PDFImageData& imageData, + const PDFImageData& softMask, + const PDFCMS* cms, + RenderingIntent intent, + PDFRenderErrorReporter* reporter) const override; + + /// Creates indexed color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param array Array with indexed color space definition + /// \param recursion Recursion guard + /// \param usedNames Names, which were already parsed + static PDFColorSpacePointer createIndexedColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion, + std::set& usedNames); + + PDFColorSpacePointer getBaseColorSpace() const; + std::vector transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const; + + int getMaxValue() const; + const QByteArray& getColors() const; + +private: + static constexpr const int MIN_VALUE = 0; + static constexpr const int MAX_VALUE = 255; + + PDFColorSpacePointer m_baseColorSpace; + QByteArray m_colors; + int m_maxValue; +}; + +class PDFSeparationColorSpace : public PDFAbstractColorSpace +{ +public: + explicit PDFSeparationColorSpace(QByteArray&& colorName, PDFColorSpacePointer alternateColorSpace, PDFFunctionPtr tintTransform); + virtual ~PDFSeparationColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::Separation; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + + bool isNone() const { return m_isNone; } + bool isAll() const { return m_isAll; } + + std::vector transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const; + + /// Creates separation color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param array Array with separation color space definition + /// \param recursion Recursion guard + /// \param usedNames Names, which were already parsed + static PDFColorSpacePointer createSeparationColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion, + std::set& usedNames); + + PDFColorSpacePointer getAlternateColorSpace() const; + const QByteArray& getColorName() const; + +private: + QByteArray m_colorName; + PDFColorSpacePointer m_alternateColorSpace; + PDFFunctionPtr m_tintTransform; + bool m_isNone; + bool m_isAll; +}; + +class PDFDeviceNColorSpace : public PDFAbstractColorSpace +{ +public: + + enum class Type + { + DeviceN, + NChannel + }; + + struct ColorantInfo + { + QByteArray name; + PDFColorSpacePointer separationColorSpace; + PDFReal solidity = 0.0; + PDFFunctionPtr dotGain; + }; + + using Colorants = std::vector; + + explicit PDFDeviceNColorSpace(Type type, + Colorants&& colorants, + PDFColorSpacePointer alternateColorSpace, + PDFColorSpacePointer processColorSpace, + PDFFunctionPtr tintTransform, + std::vector&& colorantsPrintingOrder, + std::vector processColorSpaceComponents); + virtual ~PDFDeviceNColorSpace() = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::DeviceN; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + + /// Returns type of DeviceN color space + Type getType() const { return m_type; } + + const Colorants& getColorants() const { return m_colorants; } + const PDFColorSpacePointer& getAlternateColorSpace() const { return m_alternateColorSpace; } + const PDFColorSpacePointer& getProcessColorSpace() const { return m_processColorSpace; } + const PDFFunctionPtr& getTintTransform() const { return m_tintTransform; } + const std::vector& getPrintingOrder() const { return m_colorantsPrintingOrder; } + const std::vector& getProcessColorSpaceComponents() const { return m_processColorSpaceComponents; } + bool isNone() const { return m_isNone; } + + std::vector transformColorsToBaseColorSpace(const PDFColorBuffer buffer) const; + + /// Creates DeviceN color space from provided values. + /// \param colorSpaceDictionary Color space dictionary + /// \param document Document + /// \param array Array with DeviceN color space definition + /// \param recursion Recursion guard + /// \param usedNames Names, which were already parsed + static PDFColorSpacePointer createDeviceNColorSpace(const PDFDictionary* colorSpaceDictionary, + const PDFDocument* document, + const PDFArray* array, + int recursion, + std::set& usedNames); + +private: + Type m_type; + Colorants m_colorants; + PDFColorSpacePointer m_alternateColorSpace; + PDFColorSpacePointer m_processColorSpace; + PDFFunctionPtr m_tintTransform; + std::vector m_colorantsPrintingOrder; + std::vector m_processColorSpaceComponents; + bool m_isNone; +}; + +class PDFPatternColorSpace : public PDFAbstractColorSpace +{ +public: + explicit PDFPatternColorSpace(std::shared_ptr&& pattern, PDFColorSpacePointer&& uncoloredPatternColorSpace, PDFColor uncoloredPatternColor) : + m_pattern(qMove(pattern)), + m_uncoloredPatternColorSpace(qMove(uncoloredPatternColorSpace)), + m_uncoloredPatternColor(qMove(uncoloredPatternColor)) + { + + } + + virtual ~PDFPatternColorSpace() override = default; + + virtual ColorSpace getColorSpace() const override { return ColorSpace::Pattern; } + virtual bool equals(const PDFAbstractColorSpace* other) const override; + virtual QColor getDefaultColor(const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter) const override; + virtual PDFColor getDefaultColorOriginal() const override; + virtual QColor getColor(const PDFColor& color, const PDFCMS* cms, RenderingIntent intent, PDFRenderErrorReporter* reporter, bool isRange01) const override; + virtual size_t getColorComponentCount() const override; + virtual const PDFPatternColorSpace* asPatternColorSpace() const override { return this; } + + const PDFPattern* getPattern() const { return m_pattern.get(); } + PDFColorSpacePointer getUncoloredPatternColorSpace() const { return m_uncoloredPatternColorSpace; } + PDFColor getUncoloredPatternColor() const { return m_uncoloredPatternColor; } + +private: + std::shared_ptr m_pattern; + PDFColorSpacePointer m_uncoloredPatternColorSpace; + PDFColor m_uncoloredPatternColor; +}; + +} // namespace pdf + +#endif // PDFCOLORSPACES_H diff --git a/Pdf4QtLib/sources/pdfcompiler.cpp b/Pdf4QtLib/sources/pdfcompiler.cpp index 329c436..3b016cb 100644 --- a/Pdf4QtLib/sources/pdfcompiler.cpp +++ b/Pdf4QtLib/sources/pdfcompiler.cpp @@ -1,499 +1,499 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfcompiler.h" -#include "pdfcms.h" -#include "pdfdrawspacecontroller.h" -#include "pdfprogress.h" -#include "pdfexecutionpolicy.h" - -#include - -#include - -namespace pdf -{ - -PDFAsynchronousPageCompiler::PDFAsynchronousPageCompiler(PDFDrawWidgetProxy* proxy) : - BaseClass(proxy), - m_proxy(proxy) -{ - m_cache.setMaxCost(128 * 1024 * 1024); -} - -void PDFAsynchronousPageCompiler::start() -{ - switch (m_state) - { - case State::Inactive: - { - m_state = State::Active; - break; - } - - case State::Active: - break; // We have nothing to do... - - case State::Stopping: - { - // We shouldn't call this function while stopping! - Q_ASSERT(false); - break; - } - } -} - -void PDFAsynchronousPageCompiler::stop(bool clearCache) -{ - switch (m_state) - { - case State::Inactive: - break; // We have nothing to do... - - case State::Active: - { - // Stop the engine - m_state = State::Stopping; - - for (const auto& taskItem : m_tasks) - { - disconnect(taskItem.second.taskWatcher, &QFutureWatcher::finished, this, &PDFAsynchronousPageCompiler::onPageCompiled); - taskItem.second.taskWatcher->waitForFinished(); - } - m_tasks.clear(); - - if (clearCache) - { - m_cache.clear(); - } - - m_state = State::Inactive; - break; - } - - case State::Stopping: - { - // We shouldn't call this function while stopping! - Q_ASSERT(false); - break; - } - } -} - -void PDFAsynchronousPageCompiler::reset() -{ - stop(true); - start(); -} - -void PDFAsynchronousPageCompiler::setCacheLimit(int limit) -{ - m_cache.setMaxCost(limit); -} - -const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFInteger pageIndex, bool compile) -{ - if (m_state != State::Active || !m_proxy->getDocument()) - { - // Engine is not active, always return nullptr - return nullptr; - } - - const PDFPrecompiledPage* page = m_cache.object(pageIndex); - if (!page && compile && !m_tasks.count(pageIndex)) - { - // Compile the page - auto compilePage = [this, pageIndex]() -> PDFPrecompiledPage - { - PDFPrecompiledPage compiledPage; - PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); - PDFRenderer renderer(m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), m_proxy->getFeatures(), m_proxy->getMeshQualitySettings()); - renderer.compile(&compiledPage, pageIndex); - return compiledPage; - }; - - m_proxy->getFontCache()->setCacheShrinkEnabled(this, false); - CompileTask& task = m_tasks[pageIndex]; - task.taskFuture = QtConcurrent::run(compilePage); - task.taskWatcher = new QFutureWatcher(this); - connect(task.taskWatcher, &QFutureWatcher::finished, this, &PDFAsynchronousPageCompiler::onPageCompiled); - task.taskWatcher->setFuture(task.taskFuture); - } - - return page; -} - -void PDFAsynchronousPageCompiler::onPageCompiled() -{ - std::vector compiledPages; - - // Search all tasks for finished tasks - for (auto it = m_tasks.begin(); it != m_tasks.end();) - { - CompileTask& task = it->second; - if (task.taskWatcher->isFinished()) - { - if (m_state == State::Active) - { - // If we are in active state, try to store precompiled page - PDFPrecompiledPage* page = new PDFPrecompiledPage(task.taskWatcher->result()); - qint64 memoryConsumptionEstimate = page->getMemoryConsumptionEstimate(); - if (m_cache.insert(it->first, page, memoryConsumptionEstimate)) - { - compiledPages.push_back(it->first); - } - else - { - // We can't insert page to the cache, because cache size is too small. We will - // emit error string to inform the user, that cache is too small. - QString message = PDFTranslationContext::tr("Precompiled page size is too high (%1 kB). Cache size is %2 kB. Increase the cache size!").arg(memoryConsumptionEstimate / 1024).arg(m_cache.maxCost() / 1024); - emit renderingError(it->first, { PDFRenderError(RenderErrorType::Error, message) }); - } - } - - task.taskWatcher->deleteLater(); - it = m_tasks.erase(it); - } - else - { - // Just increment the counter - ++it; - } - } - - // We allow font cache shrinking, when we aren't doing something in parallel. - m_proxy->getFontCache()->setCacheShrinkEnabled(this, m_tasks.empty()); - - if (!compiledPages.empty()) - { - Q_ASSERT(std::is_sorted(compiledPages.cbegin(), compiledPages.cend())); - emit pageImageChanged(false, compiledPages); - } -} - -PDFTextLayout PDFTextLayoutGenerator::createTextLayout() -{ - m_textLayout.perform(); - m_textLayout.optimize(); - return qMove(m_textLayout); -} - -bool PDFTextLayoutGenerator::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) -{ - if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent)) - { - return false; - } - - return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd); -} - -bool PDFTextLayoutGenerator::isContentKindSuppressed(ContentKind kind) const -{ - switch (kind) - { - case ContentKind::Shapes: - case ContentKind::Text: - case ContentKind::Images: - case ContentKind::Shading: - return true; - - case ContentKind::Tiling: - return false; // Tiling can have text - - default: - { - Q_ASSERT(false); - break; - } - } - - return false; -} - -void PDFTextLayoutGenerator::performOutputCharacter(const PDFTextCharacterInfo& info) -{ - if (!isContentSuppressed() && !info.character.isSpace()) - { - m_textLayout.addCharacter(info); - } -} - -PDFAsynchronousTextLayoutCompiler::PDFAsynchronousTextLayoutCompiler(PDFDrawWidgetProxy* proxy) : - BaseClass(proxy), - m_proxy(proxy), - m_isRunning(false), - m_cache(std::bind(&PDFAsynchronousTextLayoutCompiler::createTextLayout, this, std::placeholders::_1)) -{ - connect(&m_textLayoutCompileFutureWatcher, &QFutureWatcher::finished, this, &PDFAsynchronousTextLayoutCompiler::onTextLayoutCreated); -} - -void PDFAsynchronousTextLayoutCompiler::start() -{ - switch (m_state) - { - case State::Inactive: - { - m_state = State::Active; - break; - } - - case State::Active: - break; // We have nothing to do... - - case State::Stopping: - { - // We shouldn't call this function while stopping! - Q_ASSERT(false); - break; - } - } -} - -void PDFAsynchronousTextLayoutCompiler::stop(bool clearCache) -{ - switch (m_state) - { - case State::Inactive: - break; // We have nothing to do... - - case State::Active: - { - // Stop the engine - m_state = State::Stopping; - m_textLayoutCompileFutureWatcher.waitForFinished(); - - if (clearCache) - { - m_textLayouts = std::nullopt; - m_cache.clear(); - } - - m_state = State::Inactive; - break; - } - - case State::Stopping: - { - // We shouldn't call this function while stopping! - Q_ASSERT(false); - break; - } - } -} - -void PDFAsynchronousTextLayoutCompiler::reset() -{ - stop(true); - start(); -} - -PDFTextLayout PDFAsynchronousTextLayoutCompiler::createTextLayout(PDFInteger pageIndex) -{ - PDFTextLayout result; - - if (isTextLayoutReady()) - { - result = getTextLayout(pageIndex); - } - else - { - if (m_state != State::Active || !m_proxy->getDocument()) - { - // Engine is not active, do not calculate layout - return result; - } - - const PDFCatalog* catalog = m_proxy->getDocument()->getCatalog(); - if (pageIndex < 0 || pageIndex >= PDFInteger(catalog->getPageCount())) - { - return result; - } - - if (!catalog->getPage(pageIndex)) - { - // Invalid page index - return result; - } - - const PDFPage* page = catalog->getPage(pageIndex); - Q_ASSERT(page); - - bool guard = false; - m_proxy->getFontCache()->setCacheShrinkEnabled(&guard, false); - - PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); - PDFTextLayoutGenerator generator(m_proxy->getFeatures(), page, m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), QMatrix(), m_proxy->getMeshQualitySettings()); - generator.processContents(); - result = generator.createTextLayout(); - m_proxy->getFontCache()->setCacheShrinkEnabled(&guard, true); - } - - return result; -} - -PDFTextLayout PDFAsynchronousTextLayoutCompiler::getTextLayout(PDFInteger pageIndex) -{ - if (m_state != State::Active || !m_proxy->getDocument()) - { - // Engine is not active, always return empty layout - return PDFTextLayout(); - } - - if (m_textLayouts) - { - return m_textLayouts->getTextLayout(pageIndex); - } - - return PDFTextLayout(); -} - -PDFTextLayoutGetter PDFAsynchronousTextLayoutCompiler::getTextLayoutLazy(PDFInteger pageIndex) -{ - return PDFTextLayoutGetter(&m_cache, pageIndex); -} - -PDFTextSelection PDFAsynchronousTextLayoutCompiler::getTextSelectionAll(QColor color) const -{ - PDFTextSelection result; - - if (m_textLayouts) - { - const PDFTextLayoutStorage& textLayouts = *m_textLayouts; - - QMutex mutex; - PDFIntegerRange pageRange(0, textLayouts.getCount()); - auto selectPageText = [&mutex, &textLayouts, &result, color](PDFInteger pageIndex) - { - PDFTextLayout textLayout = textLayouts.getTextLayout(pageIndex); - PDFTextSelectionItems items; - - const PDFTextBlocks& blocks = textLayout.getTextBlocks(); - for (size_t blockId = 0, blockCount = blocks.size(); blockId < blockCount; ++blockId) - { - const PDFTextBlock& block = blocks[blockId]; - const PDFTextLines& lines = block.getLines(); - - if (!lines.empty()) - { - const PDFTextLine& lastLine = lines.back(); - Q_ASSERT(!lastLine.getCharacters().empty()); - - PDFCharacterPointer ptrStart; - ptrStart.pageIndex = pageIndex; - ptrStart.blockIndex = blockId; - ptrStart.lineIndex = 0; - ptrStart.characterIndex = 0; - - PDFCharacterPointer ptrEnd; - ptrEnd.pageIndex = pageIndex; - ptrEnd.blockIndex = blockId; - ptrEnd.lineIndex = lines.size() - 1; - ptrEnd.characterIndex = lastLine.getCharacters().size() - 1; - - items.emplace_back(ptrStart, ptrEnd); - } - } - - QMutexLocker lock(&mutex); - result.addItems(qMove(items), color); - }; - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageRange.begin(), pageRange.end(), selectPageText); - } - - result.build(); - return result; -} - -void PDFAsynchronousTextLayoutCompiler::makeTextLayout() -{ - if (m_state != State::Active || !m_proxy->getDocument()) - { - // Engine is not active, do not calculate layout - return; - } - - if (m_textLayouts.has_value()) - { - // Value is computed already - return; - } - - if (m_isRunning) - { - // Text layout is already being processed - return; - } - - // Jakub Melka: Mark, that we are running (test for future is not enough, - // because future can finish before this function exits, for example) - m_isRunning = true; - - ProgressStartupInfo info; - info.showDialog = true; - info.text = tr("Indexing document contents..."); - - m_proxy->getFontCache()->setCacheShrinkEnabled(this, false); - const PDFCatalog* catalog = m_proxy->getDocument()->getCatalog(); - m_proxy->getProgress()->start(catalog->getPageCount(), qMove(info)); - - PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); - - auto createTextLayout = [this, cms, catalog]() -> PDFTextLayoutStorage - { - PDFTextLayoutStorage result(catalog->getPageCount()); - QMutex mutex; - auto generateTextLayout = [this, &result, &mutex, cms, catalog](PDFInteger pageIndex) - { - if (!catalog->getPage(pageIndex)) - { - // Invalid page index - result.setTextLayout(pageIndex, PDFTextLayout(), &mutex); - return; - } - - const PDFPage* page = catalog->getPage(pageIndex); - Q_ASSERT(page); - - PDFTextLayoutGenerator generator(m_proxy->getFeatures(), page, m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), QMatrix(), m_proxy->getMeshQualitySettings()); - generator.processContents(); - result.setTextLayout(pageIndex, generator.createTextLayout(), &mutex); - m_proxy->getProgress()->step(); - }; - - auto pageRange = PDFIntegerRange(0, catalog->getPageCount()); - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageRange.begin(), pageRange.end(), generateTextLayout); - return result; - }; - - Q_ASSERT(!m_textLayoutCompileFuture.isRunning()); - m_textLayoutCompileFuture = QtConcurrent::run(createTextLayout); - m_textLayoutCompileFutureWatcher.setFuture(m_textLayoutCompileFuture); -} - -void PDFAsynchronousTextLayoutCompiler::onTextLayoutCreated() -{ - m_proxy->getFontCache()->setCacheShrinkEnabled(this, true); - m_proxy->getProgress()->finish(); - m_cache.clear(); - - m_textLayouts = m_textLayoutCompileFuture.result(); - m_isRunning = false; - emit textLayoutChanged(); -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfcompiler.h" +#include "pdfcms.h" +#include "pdfdrawspacecontroller.h" +#include "pdfprogress.h" +#include "pdfexecutionpolicy.h" + +#include + +#include + +namespace pdf +{ + +PDFAsynchronousPageCompiler::PDFAsynchronousPageCompiler(PDFDrawWidgetProxy* proxy) : + BaseClass(proxy), + m_proxy(proxy) +{ + m_cache.setMaxCost(128 * 1024 * 1024); +} + +void PDFAsynchronousPageCompiler::start() +{ + switch (m_state) + { + case State::Inactive: + { + m_state = State::Active; + break; + } + + case State::Active: + break; // We have nothing to do... + + case State::Stopping: + { + // We shouldn't call this function while stopping! + Q_ASSERT(false); + break; + } + } +} + +void PDFAsynchronousPageCompiler::stop(bool clearCache) +{ + switch (m_state) + { + case State::Inactive: + break; // We have nothing to do... + + case State::Active: + { + // Stop the engine + m_state = State::Stopping; + + for (const auto& taskItem : m_tasks) + { + disconnect(taskItem.second.taskWatcher, &QFutureWatcher::finished, this, &PDFAsynchronousPageCompiler::onPageCompiled); + taskItem.second.taskWatcher->waitForFinished(); + } + m_tasks.clear(); + + if (clearCache) + { + m_cache.clear(); + } + + m_state = State::Inactive; + break; + } + + case State::Stopping: + { + // We shouldn't call this function while stopping! + Q_ASSERT(false); + break; + } + } +} + +void PDFAsynchronousPageCompiler::reset() +{ + stop(true); + start(); +} + +void PDFAsynchronousPageCompiler::setCacheLimit(int limit) +{ + m_cache.setMaxCost(limit); +} + +const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFInteger pageIndex, bool compile) +{ + if (m_state != State::Active || !m_proxy->getDocument()) + { + // Engine is not active, always return nullptr + return nullptr; + } + + const PDFPrecompiledPage* page = m_cache.object(pageIndex); + if (!page && compile && !m_tasks.count(pageIndex)) + { + // Compile the page + auto compilePage = [this, pageIndex]() -> PDFPrecompiledPage + { + PDFPrecompiledPage compiledPage; + PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); + PDFRenderer renderer(m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), m_proxy->getFeatures(), m_proxy->getMeshQualitySettings()); + renderer.compile(&compiledPage, pageIndex); + return compiledPage; + }; + + m_proxy->getFontCache()->setCacheShrinkEnabled(this, false); + CompileTask& task = m_tasks[pageIndex]; + task.taskFuture = QtConcurrent::run(compilePage); + task.taskWatcher = new QFutureWatcher(this); + connect(task.taskWatcher, &QFutureWatcher::finished, this, &PDFAsynchronousPageCompiler::onPageCompiled); + task.taskWatcher->setFuture(task.taskFuture); + } + + return page; +} + +void PDFAsynchronousPageCompiler::onPageCompiled() +{ + std::vector compiledPages; + + // Search all tasks for finished tasks + for (auto it = m_tasks.begin(); it != m_tasks.end();) + { + CompileTask& task = it->second; + if (task.taskWatcher->isFinished()) + { + if (m_state == State::Active) + { + // If we are in active state, try to store precompiled page + PDFPrecompiledPage* page = new PDFPrecompiledPage(task.taskWatcher->result()); + qint64 memoryConsumptionEstimate = page->getMemoryConsumptionEstimate(); + if (m_cache.insert(it->first, page, memoryConsumptionEstimate)) + { + compiledPages.push_back(it->first); + } + else + { + // We can't insert page to the cache, because cache size is too small. We will + // emit error string to inform the user, that cache is too small. + QString message = PDFTranslationContext::tr("Precompiled page size is too high (%1 kB). Cache size is %2 kB. Increase the cache size!").arg(memoryConsumptionEstimate / 1024).arg(m_cache.maxCost() / 1024); + emit renderingError(it->first, { PDFRenderError(RenderErrorType::Error, message) }); + } + } + + task.taskWatcher->deleteLater(); + it = m_tasks.erase(it); + } + else + { + // Just increment the counter + ++it; + } + } + + // We allow font cache shrinking, when we aren't doing something in parallel. + m_proxy->getFontCache()->setCacheShrinkEnabled(this, m_tasks.empty()); + + if (!compiledPages.empty()) + { + Q_ASSERT(std::is_sorted(compiledPages.cbegin(), compiledPages.cend())); + emit pageImageChanged(false, compiledPages); + } +} + +PDFTextLayout PDFTextLayoutGenerator::createTextLayout() +{ + m_textLayout.perform(); + m_textLayout.optimize(); + return qMove(m_textLayout); +} + +bool PDFTextLayoutGenerator::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) +{ + if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent)) + { + return false; + } + + return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd); +} + +bool PDFTextLayoutGenerator::isContentKindSuppressed(ContentKind kind) const +{ + switch (kind) + { + case ContentKind::Shapes: + case ContentKind::Text: + case ContentKind::Images: + case ContentKind::Shading: + return true; + + case ContentKind::Tiling: + return false; // Tiling can have text + + default: + { + Q_ASSERT(false); + break; + } + } + + return false; +} + +void PDFTextLayoutGenerator::performOutputCharacter(const PDFTextCharacterInfo& info) +{ + if (!isContentSuppressed() && !info.character.isSpace()) + { + m_textLayout.addCharacter(info); + } +} + +PDFAsynchronousTextLayoutCompiler::PDFAsynchronousTextLayoutCompiler(PDFDrawWidgetProxy* proxy) : + BaseClass(proxy), + m_proxy(proxy), + m_isRunning(false), + m_cache(std::bind(&PDFAsynchronousTextLayoutCompiler::createTextLayout, this, std::placeholders::_1)) +{ + connect(&m_textLayoutCompileFutureWatcher, &QFutureWatcher::finished, this, &PDFAsynchronousTextLayoutCompiler::onTextLayoutCreated); +} + +void PDFAsynchronousTextLayoutCompiler::start() +{ + switch (m_state) + { + case State::Inactive: + { + m_state = State::Active; + break; + } + + case State::Active: + break; // We have nothing to do... + + case State::Stopping: + { + // We shouldn't call this function while stopping! + Q_ASSERT(false); + break; + } + } +} + +void PDFAsynchronousTextLayoutCompiler::stop(bool clearCache) +{ + switch (m_state) + { + case State::Inactive: + break; // We have nothing to do... + + case State::Active: + { + // Stop the engine + m_state = State::Stopping; + m_textLayoutCompileFutureWatcher.waitForFinished(); + + if (clearCache) + { + m_textLayouts = std::nullopt; + m_cache.clear(); + } + + m_state = State::Inactive; + break; + } + + case State::Stopping: + { + // We shouldn't call this function while stopping! + Q_ASSERT(false); + break; + } + } +} + +void PDFAsynchronousTextLayoutCompiler::reset() +{ + stop(true); + start(); +} + +PDFTextLayout PDFAsynchronousTextLayoutCompiler::createTextLayout(PDFInteger pageIndex) +{ + PDFTextLayout result; + + if (isTextLayoutReady()) + { + result = getTextLayout(pageIndex); + } + else + { + if (m_state != State::Active || !m_proxy->getDocument()) + { + // Engine is not active, do not calculate layout + return result; + } + + const PDFCatalog* catalog = m_proxy->getDocument()->getCatalog(); + if (pageIndex < 0 || pageIndex >= PDFInteger(catalog->getPageCount())) + { + return result; + } + + if (!catalog->getPage(pageIndex)) + { + // Invalid page index + return result; + } + + const PDFPage* page = catalog->getPage(pageIndex); + Q_ASSERT(page); + + bool guard = false; + m_proxy->getFontCache()->setCacheShrinkEnabled(&guard, false); + + PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); + PDFTextLayoutGenerator generator(m_proxy->getFeatures(), page, m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), QMatrix(), m_proxy->getMeshQualitySettings()); + generator.processContents(); + result = generator.createTextLayout(); + m_proxy->getFontCache()->setCacheShrinkEnabled(&guard, true); + } + + return result; +} + +PDFTextLayout PDFAsynchronousTextLayoutCompiler::getTextLayout(PDFInteger pageIndex) +{ + if (m_state != State::Active || !m_proxy->getDocument()) + { + // Engine is not active, always return empty layout + return PDFTextLayout(); + } + + if (m_textLayouts) + { + return m_textLayouts->getTextLayout(pageIndex); + } + + return PDFTextLayout(); +} + +PDFTextLayoutGetter PDFAsynchronousTextLayoutCompiler::getTextLayoutLazy(PDFInteger pageIndex) +{ + return PDFTextLayoutGetter(&m_cache, pageIndex); +} + +PDFTextSelection PDFAsynchronousTextLayoutCompiler::getTextSelectionAll(QColor color) const +{ + PDFTextSelection result; + + if (m_textLayouts) + { + const PDFTextLayoutStorage& textLayouts = *m_textLayouts; + + QMutex mutex; + PDFIntegerRange pageRange(0, textLayouts.getCount()); + auto selectPageText = [&mutex, &textLayouts, &result, color](PDFInteger pageIndex) + { + PDFTextLayout textLayout = textLayouts.getTextLayout(pageIndex); + PDFTextSelectionItems items; + + const PDFTextBlocks& blocks = textLayout.getTextBlocks(); + for (size_t blockId = 0, blockCount = blocks.size(); blockId < blockCount; ++blockId) + { + const PDFTextBlock& block = blocks[blockId]; + const PDFTextLines& lines = block.getLines(); + + if (!lines.empty()) + { + const PDFTextLine& lastLine = lines.back(); + Q_ASSERT(!lastLine.getCharacters().empty()); + + PDFCharacterPointer ptrStart; + ptrStart.pageIndex = pageIndex; + ptrStart.blockIndex = blockId; + ptrStart.lineIndex = 0; + ptrStart.characterIndex = 0; + + PDFCharacterPointer ptrEnd; + ptrEnd.pageIndex = pageIndex; + ptrEnd.blockIndex = blockId; + ptrEnd.lineIndex = lines.size() - 1; + ptrEnd.characterIndex = lastLine.getCharacters().size() - 1; + + items.emplace_back(ptrStart, ptrEnd); + } + } + + QMutexLocker lock(&mutex); + result.addItems(qMove(items), color); + }; + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageRange.begin(), pageRange.end(), selectPageText); + } + + result.build(); + return result; +} + +void PDFAsynchronousTextLayoutCompiler::makeTextLayout() +{ + if (m_state != State::Active || !m_proxy->getDocument()) + { + // Engine is not active, do not calculate layout + return; + } + + if (m_textLayouts.has_value()) + { + // Value is computed already + return; + } + + if (m_isRunning) + { + // Text layout is already being processed + return; + } + + // Jakub Melka: Mark, that we are running (test for future is not enough, + // because future can finish before this function exits, for example) + m_isRunning = true; + + ProgressStartupInfo info; + info.showDialog = true; + info.text = tr("Indexing document contents..."); + + m_proxy->getFontCache()->setCacheShrinkEnabled(this, false); + const PDFCatalog* catalog = m_proxy->getDocument()->getCatalog(); + m_proxy->getProgress()->start(catalog->getPageCount(), qMove(info)); + + PDFCMSPointer cms = m_proxy->getCMSManager()->getCurrentCMS(); + + auto createTextLayout = [this, cms, catalog]() -> PDFTextLayoutStorage + { + PDFTextLayoutStorage result(catalog->getPageCount()); + QMutex mutex; + auto generateTextLayout = [this, &result, &mutex, cms, catalog](PDFInteger pageIndex) + { + if (!catalog->getPage(pageIndex)) + { + // Invalid page index + result.setTextLayout(pageIndex, PDFTextLayout(), &mutex); + return; + } + + const PDFPage* page = catalog->getPage(pageIndex); + Q_ASSERT(page); + + PDFTextLayoutGenerator generator(m_proxy->getFeatures(), page, m_proxy->getDocument(), m_proxy->getFontCache(), cms.data(), m_proxy->getOptionalContentActivity(), QMatrix(), m_proxy->getMeshQualitySettings()); + generator.processContents(); + result.setTextLayout(pageIndex, generator.createTextLayout(), &mutex); + m_proxy->getProgress()->step(); + }; + + auto pageRange = PDFIntegerRange(0, catalog->getPageCount()); + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageRange.begin(), pageRange.end(), generateTextLayout); + return result; + }; + + Q_ASSERT(!m_textLayoutCompileFuture.isRunning()); + m_textLayoutCompileFuture = QtConcurrent::run(createTextLayout); + m_textLayoutCompileFutureWatcher.setFuture(m_textLayoutCompileFuture); +} + +void PDFAsynchronousTextLayoutCompiler::onTextLayoutCreated() +{ + m_proxy->getFontCache()->setCacheShrinkEnabled(this, true); + m_proxy->getProgress()->finish(); + m_cache.clear(); + + m_textLayouts = m_textLayoutCompileFuture.result(); + m_isRunning = false; + emit textLayoutChanged(); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfcompiler.h b/Pdf4QtLib/sources/pdfcompiler.h index 7498ceb..bff21ce 100644 --- a/Pdf4QtLib/sources/pdfcompiler.h +++ b/Pdf4QtLib/sources/pdfcompiler.h @@ -1,212 +1,212 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFCOMPILER_H -#define PDFCOMPILER_H - -#include "pdfrenderer.h" -#include "pdfpainter.h" -#include "pdftextlayout.h" - -#include -#include -#include - -namespace pdf -{ -class PDFDrawWidgetProxy; - -/// Asynchronous page compiler compiles pages asynchronously, and stores them in the -/// cache. Cache size can be set. This object is designed to cooperate with -/// draw widget proxy. -class PDFAsynchronousPageCompiler : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - explicit PDFAsynchronousPageCompiler(PDFDrawWidgetProxy* proxy); - - /// Starts the engine. Call this function only if the engine - /// is stopped. - void start(); - - /// Stops the engine and all underlying asynchronous tasks. Also - /// clears the cache if needed. Call this function only if engine is active. - /// Cache is cleared only, if \p clearCache parameter is being set to true. - /// Set it to false, if "soft" document update occurs (this means change - /// to the document, which doesn't modify page content in precompiled - /// pages (graphic content / number of pages change). - /// \param clearCache Clear cache - void stop(bool clearCache); - - /// Resets the engine - calls stop and then calls start. - void reset(); - - /// Sets cache limit in bytes - /// \param limit Cache limit [bytes] - void setCacheLimit(int limit); - - enum class State - { - Inactive, - Active, - Stopping - }; - - /// Tries to retrieve precompiled page from the cache. If page is not found, - /// then nullptr is returned (no exception is thrown). If \p compile is set to true, - /// and page is not found, and compiler is active, then new asynchronous compile - /// task is performed. - /// \param pageIndex Index of page - /// \param compile Compile the page, if it is not found in the cache - const PDFPrecompiledPage* getCompiledPage(PDFInteger pageIndex, bool compile); - -signals: - void pageImageChanged(bool all, const std::vector& pages); - void renderingError(PDFInteger pageIndex, const QList& errors); - -private: - void onPageCompiled(); - - struct CompileTask - { - QFuture taskFuture; - QFutureWatcher* taskWatcher = nullptr; - }; - - PDFDrawWidgetProxy* m_proxy; - State m_state = State::Inactive; - QCache m_cache; - std::map m_tasks; -}; - -class PDF4QTLIBSHARED_EXPORT PDFAsynchronousTextLayoutCompiler : public QObject -{ - Q_OBJECT - -private: - using BaseClass = QObject; - -public: - explicit PDFAsynchronousTextLayoutCompiler(PDFDrawWidgetProxy* proxy); - - /// Starts the engine. Call this function only if the engine - /// is stopped. - void start(); - - /// Stops the engine and all underlying asynchronous tasks. Also - /// clears the cache if parameter \p clearCache. Call this function - /// only if engine is active. Clear cache should be set to false, - /// only if "soft" document update appears (no text on page is being - /// changed). - /// \param clearCache Clear cache - void stop(bool clearCache); - - /// Resets the engine - calls stop and then calls start. - void reset(); - - enum class State - { - Inactive, - Active, - Stopping - }; - - /// Creates text layout of the page synchronously. If page index is invalid, - /// then empty text layout is returned. Compiler must be active to get - /// valid text layout. - /// \param pageIndex Page index - PDFTextLayout createTextLayout(PDFInteger pageIndex); - - /// Returns text layout of the page. If page index is invalid, - /// then empty text layout is returned. - /// \param pageIndex Page index - PDFTextLayout getTextLayout(PDFInteger pageIndex); - - /// Returns getter for text layout of the page. If page index is invalid, - /// then empty text layout getter is returned. - /// \param pageIndex Page index - PDFTextLayoutGetter getTextLayoutLazy(PDFInteger pageIndex); - - /// Select all texts on all pages using \p color color. - /// \param color Color to be used for text selection - PDFTextSelection getTextSelectionAll(QColor color) const; - - /// Create text layout for the document. Function is asynchronous, - /// it returns immediately. After text layout is created, signal - /// \p textLayoutChanged is emitted. - void makeTextLayout(); - - /// Returns true, if text layout is ready - bool isTextLayoutReady() const { return m_textLayouts.has_value(); } - - /// Returns text layout storage (if it is ready), or nullptr - const PDFTextLayoutStorage* getTextLayoutStorage() const { return isTextLayoutReady() ? &m_textLayouts.value() : nullptr; } - -signals: - void textLayoutChanged(); - -private: - void onTextLayoutCreated(); - - PDFDrawWidgetProxy* m_proxy; - State m_state = State::Inactive; - bool m_isRunning; - std::optional m_textLayouts; - QFuture m_textLayoutCompileFuture; - QFutureWatcher m_textLayoutCompileFutureWatcher; - PDFTextLayoutCache m_cache; -}; - -class PDFTextLayoutGenerator : public PDFPageContentProcessor -{ - using BaseClass = PDFPageContentProcessor; - -public: - explicit PDFTextLayoutGenerator(PDFRenderer::Features features, - const PDFPage* page, - const PDFDocument* document, - const PDFFontCache* fontCache, - const PDFCMS* cms, - const PDFOptionalContentActivity* optionalContentActivity, - QMatrix pagePointToDevicePointMatrix, - const PDFMeshQualitySettings& meshQualitySettings) : - BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings), - m_features(features) - { - - } - - /// Creates text layout from the text - PDFTextLayout createTextLayout(); - -protected: - virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override; - virtual bool isContentKindSuppressed(ContentKind kind) const override; - virtual void performOutputCharacter(const PDFTextCharacterInfo& info) override; - -private: - PDFRenderer::Features m_features; - PDFTextLayout m_textLayout; -}; - -} // namespace pdf - -#endif // PDFCOMPILER_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFCOMPILER_H +#define PDFCOMPILER_H + +#include "pdfrenderer.h" +#include "pdfpainter.h" +#include "pdftextlayout.h" + +#include +#include +#include + +namespace pdf +{ +class PDFDrawWidgetProxy; + +/// Asynchronous page compiler compiles pages asynchronously, and stores them in the +/// cache. Cache size can be set. This object is designed to cooperate with +/// draw widget proxy. +class PDFAsynchronousPageCompiler : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFAsynchronousPageCompiler(PDFDrawWidgetProxy* proxy); + + /// Starts the engine. Call this function only if the engine + /// is stopped. + void start(); + + /// Stops the engine and all underlying asynchronous tasks. Also + /// clears the cache if needed. Call this function only if engine is active. + /// Cache is cleared only, if \p clearCache parameter is being set to true. + /// Set it to false, if "soft" document update occurs (this means change + /// to the document, which doesn't modify page content in precompiled + /// pages (graphic content / number of pages change). + /// \param clearCache Clear cache + void stop(bool clearCache); + + /// Resets the engine - calls stop and then calls start. + void reset(); + + /// Sets cache limit in bytes + /// \param limit Cache limit [bytes] + void setCacheLimit(int limit); + + enum class State + { + Inactive, + Active, + Stopping + }; + + /// Tries to retrieve precompiled page from the cache. If page is not found, + /// then nullptr is returned (no exception is thrown). If \p compile is set to true, + /// and page is not found, and compiler is active, then new asynchronous compile + /// task is performed. + /// \param pageIndex Index of page + /// \param compile Compile the page, if it is not found in the cache + const PDFPrecompiledPage* getCompiledPage(PDFInteger pageIndex, bool compile); + +signals: + void pageImageChanged(bool all, const std::vector& pages); + void renderingError(PDFInteger pageIndex, const QList& errors); + +private: + void onPageCompiled(); + + struct CompileTask + { + QFuture taskFuture; + QFutureWatcher* taskWatcher = nullptr; + }; + + PDFDrawWidgetProxy* m_proxy; + State m_state = State::Inactive; + QCache m_cache; + std::map m_tasks; +}; + +class PDF4QTLIBSHARED_EXPORT PDFAsynchronousTextLayoutCompiler : public QObject +{ + Q_OBJECT + +private: + using BaseClass = QObject; + +public: + explicit PDFAsynchronousTextLayoutCompiler(PDFDrawWidgetProxy* proxy); + + /// Starts the engine. Call this function only if the engine + /// is stopped. + void start(); + + /// Stops the engine and all underlying asynchronous tasks. Also + /// clears the cache if parameter \p clearCache. Call this function + /// only if engine is active. Clear cache should be set to false, + /// only if "soft" document update appears (no text on page is being + /// changed). + /// \param clearCache Clear cache + void stop(bool clearCache); + + /// Resets the engine - calls stop and then calls start. + void reset(); + + enum class State + { + Inactive, + Active, + Stopping + }; + + /// Creates text layout of the page synchronously. If page index is invalid, + /// then empty text layout is returned. Compiler must be active to get + /// valid text layout. + /// \param pageIndex Page index + PDFTextLayout createTextLayout(PDFInteger pageIndex); + + /// Returns text layout of the page. If page index is invalid, + /// then empty text layout is returned. + /// \param pageIndex Page index + PDFTextLayout getTextLayout(PDFInteger pageIndex); + + /// Returns getter for text layout of the page. If page index is invalid, + /// then empty text layout getter is returned. + /// \param pageIndex Page index + PDFTextLayoutGetter getTextLayoutLazy(PDFInteger pageIndex); + + /// Select all texts on all pages using \p color color. + /// \param color Color to be used for text selection + PDFTextSelection getTextSelectionAll(QColor color) const; + + /// Create text layout for the document. Function is asynchronous, + /// it returns immediately. After text layout is created, signal + /// \p textLayoutChanged is emitted. + void makeTextLayout(); + + /// Returns true, if text layout is ready + bool isTextLayoutReady() const { return m_textLayouts.has_value(); } + + /// Returns text layout storage (if it is ready), or nullptr + const PDFTextLayoutStorage* getTextLayoutStorage() const { return isTextLayoutReady() ? &m_textLayouts.value() : nullptr; } + +signals: + void textLayoutChanged(); + +private: + void onTextLayoutCreated(); + + PDFDrawWidgetProxy* m_proxy; + State m_state = State::Inactive; + bool m_isRunning; + std::optional m_textLayouts; + QFuture m_textLayoutCompileFuture; + QFutureWatcher m_textLayoutCompileFutureWatcher; + PDFTextLayoutCache m_cache; +}; + +class PDFTextLayoutGenerator : public PDFPageContentProcessor +{ + using BaseClass = PDFPageContentProcessor; + +public: + explicit PDFTextLayoutGenerator(PDFRenderer::Features features, + const PDFPage* page, + const PDFDocument* document, + const PDFFontCache* fontCache, + const PDFCMS* cms, + const PDFOptionalContentActivity* optionalContentActivity, + QMatrix pagePointToDevicePointMatrix, + const PDFMeshQualitySettings& meshQualitySettings) : + BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings), + m_features(features) + { + + } + + /// Creates text layout from the text + PDFTextLayout createTextLayout(); + +protected: + virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override; + virtual bool isContentKindSuppressed(ContentKind kind) const override; + virtual void performOutputCharacter(const PDFTextCharacterInfo& info) override; + +private: + PDFRenderer::Features m_features; + PDFTextLayout m_textLayout; +}; + +} // namespace pdf + +#endif // PDFCOMPILER_H diff --git a/Pdf4QtLib/sources/pdfconstants.h b/Pdf4QtLib/sources/pdfconstants.h index 2bc69fb..5ff7b68 100644 --- a/Pdf4QtLib/sources/pdfconstants.h +++ b/Pdf4QtLib/sources/pdfconstants.h @@ -1,74 +1,74 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#ifndef PDFCONSTANTS_H -#define PDFCONSTANTS_H - -namespace pdf -{ - -// Name of the library, together with version -static constexpr const char* PDF_LIBRARY_NAME = "PDF4QT 1.0.0"; -static constexpr const char* PDF_LIBRARY_VERSION = "1.0.0"; - -// Structure file constants -static constexpr const char* PDF_END_OF_FILE_MARK = "%%EOF"; -static constexpr const char* PDF_START_OF_XREF_MARK = "startxref"; - -static constexpr const char* PDF_FILE_HEADER_V1 = "%PDF-?.?"; -static constexpr const char* PDF_FILE_HEADER_V2 = "%!PS-Adobe-?.? PDF-?.?"; -static constexpr const char* PDF_FILE_HEADER_REGEXP = "%PDF-([[:digit:]]\\.[[:digit:]])|%!PS-Adobe-[[:digit:]]\\.[[:digit:]] PDF-([[:digit:]]\\.[[:digit:]])"; - -static constexpr const int PDF_HEADER_SCAN_LIMIT = 1024; -static constexpr const int PDF_FOOTER_SCAN_LIMIT = 1024; - -// Stream dictionary constants - entries common to all stream dictionaries -static constexpr const char* PDF_STREAM_DICT_LENGTH = "Length"; -static constexpr const char* PDF_STREAM_DICT_FILTER = "Filter"; -static constexpr const char* PDF_STREAM_DICT_DECODE_PARMS = "DecodeParms"; -static constexpr const char* PDF_STREAM_DICT_FILE_SPECIFICATION = "F"; -static constexpr const char* PDF_STREAM_DICT_FILE_FILTER = "FFilter"; -static constexpr const char* PDF_STREAM_DICT_FDECODE_PARMS = "FDecodeParms"; -static constexpr const char* PDF_STREAM_DICT_DECODED_LENGTH = "DL"; - -// xref table constants -static constexpr const char* PDF_XREF_HEADER = "xref"; -static constexpr const char* PDF_XREF_TRAILER = "trailer"; -static constexpr const char* PDF_XREF_TRAILER_PREVIOUS = "Prev"; -static constexpr const char* PDF_XREF_TRAILER_XREFSTM = "XRefStm"; -static constexpr const char* PDF_XREF_FREE = "f"; -static constexpr const char* PDF_XREF_OCCUPIED = "n"; - -// objects - -static constexpr const char* PDF_OBJECT_START_MARK = "obj"; -static constexpr const char* PDF_OBJECT_END_MARK = "endobj"; - -// maximum generation limit -static constexpr const int PDF_MAX_OBJECT_GENERATION = 65535; - -// Colors -static constexpr const int PDF_MAX_COLOR_COMPONENTS = 32; - -// Cache limits -static constexpr size_t DEFAULT_FONT_CACHE_LIMIT = 32; -static constexpr size_t DEFAULT_REALIZED_FONT_CACHE_LIMIT = 128; - -} // namespace pdf - -#endif // PDFCONSTANTS_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#ifndef PDFCONSTANTS_H +#define PDFCONSTANTS_H + +namespace pdf +{ + +// Name of the library, together with version +static constexpr const char* PDF_LIBRARY_NAME = "PDF4QT 1.0.0"; +static constexpr const char* PDF_LIBRARY_VERSION = "1.0.0"; + +// Structure file constants +static constexpr const char* PDF_END_OF_FILE_MARK = "%%EOF"; +static constexpr const char* PDF_START_OF_XREF_MARK = "startxref"; + +static constexpr const char* PDF_FILE_HEADER_V1 = "%PDF-?.?"; +static constexpr const char* PDF_FILE_HEADER_V2 = "%!PS-Adobe-?.? PDF-?.?"; +static constexpr const char* PDF_FILE_HEADER_REGEXP = "%PDF-([[:digit:]]\\.[[:digit:]])|%!PS-Adobe-[[:digit:]]\\.[[:digit:]] PDF-([[:digit:]]\\.[[:digit:]])"; + +static constexpr const int PDF_HEADER_SCAN_LIMIT = 1024; +static constexpr const int PDF_FOOTER_SCAN_LIMIT = 1024; + +// Stream dictionary constants - entries common to all stream dictionaries +static constexpr const char* PDF_STREAM_DICT_LENGTH = "Length"; +static constexpr const char* PDF_STREAM_DICT_FILTER = "Filter"; +static constexpr const char* PDF_STREAM_DICT_DECODE_PARMS = "DecodeParms"; +static constexpr const char* PDF_STREAM_DICT_FILE_SPECIFICATION = "F"; +static constexpr const char* PDF_STREAM_DICT_FILE_FILTER = "FFilter"; +static constexpr const char* PDF_STREAM_DICT_FDECODE_PARMS = "FDecodeParms"; +static constexpr const char* PDF_STREAM_DICT_DECODED_LENGTH = "DL"; + +// xref table constants +static constexpr const char* PDF_XREF_HEADER = "xref"; +static constexpr const char* PDF_XREF_TRAILER = "trailer"; +static constexpr const char* PDF_XREF_TRAILER_PREVIOUS = "Prev"; +static constexpr const char* PDF_XREF_TRAILER_XREFSTM = "XRefStm"; +static constexpr const char* PDF_XREF_FREE = "f"; +static constexpr const char* PDF_XREF_OCCUPIED = "n"; + +// objects + +static constexpr const char* PDF_OBJECT_START_MARK = "obj"; +static constexpr const char* PDF_OBJECT_END_MARK = "endobj"; + +// maximum generation limit +static constexpr const int PDF_MAX_OBJECT_GENERATION = 65535; + +// Colors +static constexpr const int PDF_MAX_COLOR_COMPONENTS = 32; + +// Cache limits +static constexpr size_t DEFAULT_FONT_CACHE_LIMIT = 32; +static constexpr size_t DEFAULT_REALIZED_FONT_CACHE_LIMIT = 128; + +} // namespace pdf + +#endif // PDFCONSTANTS_H diff --git a/Pdf4QtLib/sources/pdfdocument.cpp b/Pdf4QtLib/sources/pdfdocument.cpp index 94c0074..eac2037 100644 --- a/Pdf4QtLib/sources/pdfdocument.cpp +++ b/Pdf4QtLib/sources/pdfdocument.cpp @@ -1,645 +1,645 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#include "pdfdocument.h" -#include "pdfencoding.h" -#include "pdfexception.h" -#include "pdfstreamfilters.h" -#include "pdfconstants.h" - -namespace pdf -{ - -static constexpr const char* PDF_DOCUMENT_INFO_ENTRY = "Info"; - -QByteArray PDFObjectStorage::getDecodedStream(const PDFStream* stream) const -{ - return PDFStreamFilterStorage::getDecodedStream(stream, std::bind(QOverload::of(&PDFObjectStorage::getObject), this, std::placeholders::_1), getSecurityHandler()); -} - -PDFDocument::~PDFDocument() -{ - -} - -bool PDFDocument::operator==(const PDFDocument& other) const -{ - // Document is considered equal, if storage is equal - return m_pdfObjectStorage == other.m_pdfObjectStorage; -} - -QByteArray PDFDocument::getIdPart(size_t index) const -{ - QByteArray id; - const PDFObject& idArrayObject = getTrailerDictionary()->get("ID"); - if (idArrayObject.isArray()) - { - const PDFArray* idArray = idArrayObject.getArray(); - if (idArray->getCount() > index) - { - const PDFObject& idArrayItem = idArray->getItem(index); - if (idArrayItem.isString()) - { - id = idArrayItem.getString(); - } - } - } - - return id; -} - -QByteArray PDFDocument::getDecodedStream(const PDFStream* stream) const -{ - return m_pdfObjectStorage.getDecodedStream(stream); -} - -const PDFDictionary* PDFDocument::getTrailerDictionary() const -{ - const PDFObject& trailerDictionary = m_pdfObjectStorage.getTrailerDictionary(); - - // Trailer object should be dictionary/stream here. It is verified in the document reader. - Q_ASSERT(trailerDictionary.isDictionary() || trailerDictionary.isStream()); - - if (trailerDictionary.isDictionary()) - { - return trailerDictionary.getDictionary(); - } - else if (trailerDictionary.isStream()) - { - return trailerDictionary.getStream()->getDictionary(); - } - - return nullptr; -} - -QByteArray PDFDocument::getVersion() const -{ - QByteArray result = m_catalog.getVersion(); - - if (result.isEmpty() && m_info.version.isValid()) - { - result = QString("%1.%2").arg(m_info.version.major).arg(m_info.version.minor).toLatin1(); - } - - return result; -} - -void PDFDocument::init() -{ - initInfo(); - - const PDFDictionary* dictionary = getTrailerDictionary(); - Q_ASSERT(dictionary); - - m_catalog = PDFCatalog::parse(getObject(dictionary->get("Root")), this); -} - -void PDFDocument::initInfo() -{ - // Trailer object should be dictionary here. It is verified in the document reader. - const PDFDictionary* dictionary = getTrailerDictionary(); - Q_ASSERT(dictionary); - - if (dictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY)) - { - m_info = PDFDocumentInfo::parse(dictionary->get(PDF_DOCUMENT_INFO_ENTRY), &m_pdfObjectStorage); - } -} - -bool PDFObjectStorage::operator==(const PDFObjectStorage& other) const -{ - // We compare just content. Security handler just defines encryption behavior. - return m_objects == other.m_objects && - m_trailerDictionary == other.m_trailerDictionary; -} - -const PDFObject& PDFObjectStorage::getObject(PDFObjectReference reference) const -{ - if (reference.objectNumber >= 0 && - reference.objectNumber < static_cast(m_objects.size()) && - m_objects[reference.objectNumber].generation == reference.generation) - { - return m_objects[reference.objectNumber].object; - } - else - { - static const PDFObject dummy; - return dummy; - } -} - -PDFObjectReference PDFObjectStorage::addObject(PDFObject object) -{ - PDFObjectReference reference(m_objects.size(), 0); - m_objects.emplace_back(0, qMove(object)); - return reference; -} - -void PDFObjectStorage::setObject(PDFObjectReference reference, PDFObject object) -{ - m_objects[reference.objectNumber] = Entry(reference.generation, qMove(object)); -} - -void PDFObjectStorage::updateTrailerDictionary(PDFObject trailerDictionary) -{ - m_trailerDictionary = PDFObjectManipulator::merge(m_trailerDictionary, trailerDictionary, PDFObjectManipulator::RemoveNullObjects); -} - -PDFDocumentDataLoaderDecorator::PDFDocumentDataLoaderDecorator(const PDFDocument* document) - : m_storage(&document->getStorage()) -{ - -} - -QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isName()) - { - return dereferencedObject.getString(); - } - - return QByteArray(); -} - -QByteArray PDFDocumentDataLoaderDecorator::readString(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isString()) - { - return dereferencedObject.getString(); - } - - return QByteArray(); -} - -PDFInteger PDFDocumentDataLoaderDecorator::readInteger(const PDFObject& object, PDFInteger defaultValue) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isInt()) - { - return dereferencedObject.getInteger(); - } - - return defaultValue; -} - -PDFReal PDFDocumentDataLoaderDecorator::readNumber(const PDFObject& object, PDFReal defaultValue) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - - if (dereferencedObject.isReal()) - { - return dereferencedObject.getReal(); - } else if (dereferencedObject.isInt()) - { - return dereferencedObject.getInteger(); - } - - return defaultValue; -} - -bool PDFDocumentDataLoaderDecorator::readBoolean(const PDFObject& object, bool defaultValue) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - - if (dereferencedObject.isBool()) - { - return dereferencedObject.getBool(); - } - - return defaultValue; -} - -QString PDFDocumentDataLoaderDecorator::readTextString(const PDFObject& object, const QString& defaultValue) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isString()) - { - return PDFEncoding::convertTextString(dereferencedObject.getString()); - } - - return defaultValue; -} - -QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, const QRectF& defaultValue) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - if (array->getCount() == 4) - { - std::array items; - for (size_t i = 0; i < 4; ++i) - { - const PDFObject& object = m_storage->getObject(array->getItem(i)); - if (object.isReal()) - { - items[i] = object.getReal(); - } - else if (object.isInt()) - { - items[i] = object.getInteger(); - } - else - { - return defaultValue; - } - } - - const PDFReal xMin = qMin(items[0], items[2]); - const PDFReal xMax = qMax(items[0], items[2]); - const PDFReal yMin = qMin(items[1], items[3]); - const PDFReal yMax = qMax(items[1], items[3]); - - return QRectF(xMin, yMin, xMax - xMin, yMax - yMin); - } - } - - return defaultValue; -} - -QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) const -{ - if (dictionary->hasKey(key)) - { - std::vector matrixNumbers = readNumberArrayFromDictionary(dictionary, key); - if (matrixNumbers.size() != 6) - { - throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number of matrix elements. Expected 6, actual %1.").arg(matrixNumbers.size())); - } - - return QMatrix(matrixNumbers[0], matrixNumbers[1], matrixNumbers[2], matrixNumbers[3], matrixNumbers[4], matrixNumbers[5]); - } - - return defaultValue; -} - -std::vector PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary, - const char* key, - std::vector defaultValue) const -{ - if (dictionary->hasKey(key)) - { - return readNumberArray(dictionary->get(key), defaultValue); - } - - return defaultValue; -} - -std::vector PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readIntegerArray(dictionary->get(key)); - } - - return std::vector(); -} - -PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const -{ - if (dictionary->hasKey(key)) - { - return readNumber(dictionary->get(key), defaultValue); - } - - return defaultValue; -} - -PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const QByteArray& key, PDFReal defaultValue) const -{ - if (dictionary->hasKey(key)) - { - return readNumber(dictionary->get(key), defaultValue); - } - - return defaultValue; -} - -PDFInteger PDFDocumentDataLoaderDecorator::readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const -{ - if (dictionary->hasKey(key)) - { - return readInteger(dictionary->get(key), defaultValue); - } - - return defaultValue; -} - -QString PDFDocumentDataLoaderDecorator::readTextStringFromDictionary(const PDFDictionary* dictionary, const char* key, const QString& defaultValue) const -{ - if (dictionary->hasKey(key)) - { - return readTextString(dictionary->get(key), defaultValue); - } - - return defaultValue; -} - -std::vector PDFDocumentDataLoaderDecorator::readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readReferenceArray(dictionary->get(key)); - } - - return std::vector(); -} - -std::vector PDFDocumentDataLoaderDecorator::readNumberArray(const PDFObject& object, std::vector defaultValue) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - std::vector result; - const size_t count = array->getCount(); - result.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - const PDFReal number = readNumber(array->getItem(i), std::numeric_limits::quiet_NaN()); - if (std::isnan(number)) - { - return defaultValue; - } - result.push_back(number); - } - - // We assume, that RVO (return value optimization) will work - return result; - } - - return defaultValue; -} - -std::vector PDFDocumentDataLoaderDecorator::readIntegerArray(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - std::vector result; - const size_t count = array->getCount(); - result.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - // This value is not representable in the current PDF parser. So we assume we - // can't get this value. - constexpr const PDFInteger INVALID_VALUE = std::numeric_limits::max(); - const PDFInteger number = readInteger(array->getItem(i), INVALID_VALUE); - if (number == INVALID_VALUE) - { - return std::vector(); - } - result.push_back(number); - } - - // We assume, that RVO (return value optimization) will work - return result; - } - - return std::vector(); -} - -PDFObjectReference PDFDocumentDataLoaderDecorator::readReference(const PDFObject& object) const -{ - if (object.isReference()) - { - return object.getReference(); - } - - return PDFObjectReference(); -} - -PDFObjectReference PDFDocumentDataLoaderDecorator::readReferenceFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - const PDFObject& object = dictionary->get(key); - - if (object.isReference()) - { - return object.getReference(); - } - - return PDFObjectReference(); -} - -std::vector PDFDocumentDataLoaderDecorator::readReferenceArray(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - std::vector result; - const size_t count = array->getCount(); - result.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - const PDFObject& referenceObject = array->getItem(i); - if (referenceObject.isReference()) - { - result.push_back(referenceObject.getReference()); - } - else - { - result.clear(); - break; - } - } - - // We assume, that RVO (return value optimization) will work - return result; - } - - return std::vector(); -} - -std::vector PDFDocumentDataLoaderDecorator::readNameArray(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - std::vector result; - const size_t count = array->getCount(); - result.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - const PDFObject& nameObject = array->getItem(i); - if (nameObject.isName()) - { - result.push_back(nameObject.getString()); - } - else - { - result.clear(); - break; - } - } - - // We assume, that RVO (return value optimization) will work - return result; - } - - return std::vector(); -} - -std::vector PDFDocumentDataLoaderDecorator::readNameArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readNameArray(dictionary->get(key)); - } - - return std::vector(); -} - -bool PDFDocumentDataLoaderDecorator::readBooleanFromDictionary(const PDFDictionary* dictionary, const char* key, bool defaultValue) const -{ - if (dictionary->hasKey(key)) - { - return readBoolean(dictionary->get(key), defaultValue); - } - - return defaultValue; -} - -QByteArray PDFDocumentDataLoaderDecorator::readNameFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readName(dictionary->get(key)); - } - - return QByteArray(); -} - -QByteArray PDFDocumentDataLoaderDecorator::readStringFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readString(dictionary->get(key)); - } - - return QByteArray(); -} - -std::vector PDFDocumentDataLoaderDecorator::readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readStringArray(dictionary->get(key)); - } - - return std::vector(); -} - -QStringList PDFDocumentDataLoaderDecorator::readTextStringList(const PDFObject& object) -{ - QStringList result; - - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - const size_t count = array->getCount(); - result.reserve(int(count)); - - for (size_t i = 0; i < count; ++i) - { - result << readTextString(array->getItem(i), QString()); - } - } - - return result; -} - -QColor PDFDocumentDataLoaderDecorator::readRGBColorFromDictionary(const PDFDictionary* dictionary, const char* key, QColor defaultColor) -{ - std::vector colors = readNumberArrayFromDictionary(dictionary, key); - - if (colors.size() == 3) - { - const PDFReal red = qBound(0.0, colors[0], 1.0); - const PDFReal green = qBound(0.0, colors[1], 1.0); - const PDFReal blue = qBound(0.0, colors[2], 1.0); - return QColor::fromRgbF(red, green, blue); - } - - return defaultColor; -} - -std::optional PDFDocumentDataLoaderDecorator::readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - return readStringFromDictionary(dictionary, key); - } - return std::nullopt; -} - -std::optional PDFDocumentDataLoaderDecorator::readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const -{ - if (dictionary->hasKey(key)) - { - PDFInteger integer = readIntegerFromDictionary(dictionary, key, std::numeric_limits::max()); - if (integer != std::numeric_limits::max()) - { - return integer; - } - } - return std::nullopt; -} - -std::vector PDFDocumentDataLoaderDecorator::readStringArray(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - std::vector result; - const size_t count = array->getCount(); - result.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - const PDFObject& stringObject = array->getItem(i); - if (stringObject.isString()) - { - result.push_back(stringObject.getString()); - } - else - { - result.clear(); - break; - } - } - - // We assume, that RVO (return value optimization) will work - return result; - } - - return std::vector(); -} - -} // namespace pdf +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#include "pdfdocument.h" +#include "pdfencoding.h" +#include "pdfexception.h" +#include "pdfstreamfilters.h" +#include "pdfconstants.h" + +namespace pdf +{ + +static constexpr const char* PDF_DOCUMENT_INFO_ENTRY = "Info"; + +QByteArray PDFObjectStorage::getDecodedStream(const PDFStream* stream) const +{ + return PDFStreamFilterStorage::getDecodedStream(stream, std::bind(QOverload::of(&PDFObjectStorage::getObject), this, std::placeholders::_1), getSecurityHandler()); +} + +PDFDocument::~PDFDocument() +{ + +} + +bool PDFDocument::operator==(const PDFDocument& other) const +{ + // Document is considered equal, if storage is equal + return m_pdfObjectStorage == other.m_pdfObjectStorage; +} + +QByteArray PDFDocument::getIdPart(size_t index) const +{ + QByteArray id; + const PDFObject& idArrayObject = getTrailerDictionary()->get("ID"); + if (idArrayObject.isArray()) + { + const PDFArray* idArray = idArrayObject.getArray(); + if (idArray->getCount() > index) + { + const PDFObject& idArrayItem = idArray->getItem(index); + if (idArrayItem.isString()) + { + id = idArrayItem.getString(); + } + } + } + + return id; +} + +QByteArray PDFDocument::getDecodedStream(const PDFStream* stream) const +{ + return m_pdfObjectStorage.getDecodedStream(stream); +} + +const PDFDictionary* PDFDocument::getTrailerDictionary() const +{ + const PDFObject& trailerDictionary = m_pdfObjectStorage.getTrailerDictionary(); + + // Trailer object should be dictionary/stream here. It is verified in the document reader. + Q_ASSERT(trailerDictionary.isDictionary() || trailerDictionary.isStream()); + + if (trailerDictionary.isDictionary()) + { + return trailerDictionary.getDictionary(); + } + else if (trailerDictionary.isStream()) + { + return trailerDictionary.getStream()->getDictionary(); + } + + return nullptr; +} + +QByteArray PDFDocument::getVersion() const +{ + QByteArray result = m_catalog.getVersion(); + + if (result.isEmpty() && m_info.version.isValid()) + { + result = QString("%1.%2").arg(m_info.version.major).arg(m_info.version.minor).toLatin1(); + } + + return result; +} + +void PDFDocument::init() +{ + initInfo(); + + const PDFDictionary* dictionary = getTrailerDictionary(); + Q_ASSERT(dictionary); + + m_catalog = PDFCatalog::parse(getObject(dictionary->get("Root")), this); +} + +void PDFDocument::initInfo() +{ + // Trailer object should be dictionary here. It is verified in the document reader. + const PDFDictionary* dictionary = getTrailerDictionary(); + Q_ASSERT(dictionary); + + if (dictionary->hasKey(PDF_DOCUMENT_INFO_ENTRY)) + { + m_info = PDFDocumentInfo::parse(dictionary->get(PDF_DOCUMENT_INFO_ENTRY), &m_pdfObjectStorage); + } +} + +bool PDFObjectStorage::operator==(const PDFObjectStorage& other) const +{ + // We compare just content. Security handler just defines encryption behavior. + return m_objects == other.m_objects && + m_trailerDictionary == other.m_trailerDictionary; +} + +const PDFObject& PDFObjectStorage::getObject(PDFObjectReference reference) const +{ + if (reference.objectNumber >= 0 && + reference.objectNumber < static_cast(m_objects.size()) && + m_objects[reference.objectNumber].generation == reference.generation) + { + return m_objects[reference.objectNumber].object; + } + else + { + static const PDFObject dummy; + return dummy; + } +} + +PDFObjectReference PDFObjectStorage::addObject(PDFObject object) +{ + PDFObjectReference reference(m_objects.size(), 0); + m_objects.emplace_back(0, qMove(object)); + return reference; +} + +void PDFObjectStorage::setObject(PDFObjectReference reference, PDFObject object) +{ + m_objects[reference.objectNumber] = Entry(reference.generation, qMove(object)); +} + +void PDFObjectStorage::updateTrailerDictionary(PDFObject trailerDictionary) +{ + m_trailerDictionary = PDFObjectManipulator::merge(m_trailerDictionary, trailerDictionary, PDFObjectManipulator::RemoveNullObjects); +} + +PDFDocumentDataLoaderDecorator::PDFDocumentDataLoaderDecorator(const PDFDocument* document) + : m_storage(&document->getStorage()) +{ + +} + +QByteArray PDFDocumentDataLoaderDecorator::readName(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isName()) + { + return dereferencedObject.getString(); + } + + return QByteArray(); +} + +QByteArray PDFDocumentDataLoaderDecorator::readString(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isString()) + { + return dereferencedObject.getString(); + } + + return QByteArray(); +} + +PDFInteger PDFDocumentDataLoaderDecorator::readInteger(const PDFObject& object, PDFInteger defaultValue) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isInt()) + { + return dereferencedObject.getInteger(); + } + + return defaultValue; +} + +PDFReal PDFDocumentDataLoaderDecorator::readNumber(const PDFObject& object, PDFReal defaultValue) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + + if (dereferencedObject.isReal()) + { + return dereferencedObject.getReal(); + } else if (dereferencedObject.isInt()) + { + return dereferencedObject.getInteger(); + } + + return defaultValue; +} + +bool PDFDocumentDataLoaderDecorator::readBoolean(const PDFObject& object, bool defaultValue) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + + if (dereferencedObject.isBool()) + { + return dereferencedObject.getBool(); + } + + return defaultValue; +} + +QString PDFDocumentDataLoaderDecorator::readTextString(const PDFObject& object, const QString& defaultValue) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isString()) + { + return PDFEncoding::convertTextString(dereferencedObject.getString()); + } + + return defaultValue; +} + +QRectF PDFDocumentDataLoaderDecorator::readRectangle(const PDFObject& object, const QRectF& defaultValue) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + if (array->getCount() == 4) + { + std::array items; + for (size_t i = 0; i < 4; ++i) + { + const PDFObject& object = m_storage->getObject(array->getItem(i)); + if (object.isReal()) + { + items[i] = object.getReal(); + } + else if (object.isInt()) + { + items[i] = object.getInteger(); + } + else + { + return defaultValue; + } + } + + const PDFReal xMin = qMin(items[0], items[2]); + const PDFReal xMax = qMax(items[0], items[2]); + const PDFReal yMin = qMin(items[1], items[3]); + const PDFReal yMax = qMax(items[1], items[3]); + + return QRectF(xMin, yMin, xMax - xMin, yMax - yMin); + } + } + + return defaultValue; +} + +QMatrix PDFDocumentDataLoaderDecorator::readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) const +{ + if (dictionary->hasKey(key)) + { + std::vector matrixNumbers = readNumberArrayFromDictionary(dictionary, key); + if (matrixNumbers.size() != 6) + { + throw PDFRendererException(RenderErrorType::Error, PDFTranslationContext::tr("Invalid number of matrix elements. Expected 6, actual %1.").arg(matrixNumbers.size())); + } + + return QMatrix(matrixNumbers[0], matrixNumbers[1], matrixNumbers[2], matrixNumbers[3], matrixNumbers[4], matrixNumbers[5]); + } + + return defaultValue; +} + +std::vector PDFDocumentDataLoaderDecorator::readNumberArrayFromDictionary(const PDFDictionary* dictionary, + const char* key, + std::vector defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readNumberArray(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +std::vector PDFDocumentDataLoaderDecorator::readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readIntegerArray(dictionary->get(key)); + } + + return std::vector(); +} + +PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readNumber(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +PDFReal PDFDocumentDataLoaderDecorator::readNumberFromDictionary(const PDFDictionary* dictionary, const QByteArray& key, PDFReal defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readNumber(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +PDFInteger PDFDocumentDataLoaderDecorator::readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readInteger(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +QString PDFDocumentDataLoaderDecorator::readTextStringFromDictionary(const PDFDictionary* dictionary, const char* key, const QString& defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readTextString(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +std::vector PDFDocumentDataLoaderDecorator::readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readReferenceArray(dictionary->get(key)); + } + + return std::vector(); +} + +std::vector PDFDocumentDataLoaderDecorator::readNumberArray(const PDFObject& object, std::vector defaultValue) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFReal number = readNumber(array->getItem(i), std::numeric_limits::quiet_NaN()); + if (std::isnan(number)) + { + return defaultValue; + } + result.push_back(number); + } + + // We assume, that RVO (return value optimization) will work + return result; + } + + return defaultValue; +} + +std::vector PDFDocumentDataLoaderDecorator::readIntegerArray(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + // This value is not representable in the current PDF parser. So we assume we + // can't get this value. + constexpr const PDFInteger INVALID_VALUE = std::numeric_limits::max(); + const PDFInteger number = readInteger(array->getItem(i), INVALID_VALUE); + if (number == INVALID_VALUE) + { + return std::vector(); + } + result.push_back(number); + } + + // We assume, that RVO (return value optimization) will work + return result; + } + + return std::vector(); +} + +PDFObjectReference PDFDocumentDataLoaderDecorator::readReference(const PDFObject& object) const +{ + if (object.isReference()) + { + return object.getReference(); + } + + return PDFObjectReference(); +} + +PDFObjectReference PDFDocumentDataLoaderDecorator::readReferenceFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + const PDFObject& object = dictionary->get(key); + + if (object.isReference()) + { + return object.getReference(); + } + + return PDFObjectReference(); +} + +std::vector PDFDocumentDataLoaderDecorator::readReferenceArray(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFObject& referenceObject = array->getItem(i); + if (referenceObject.isReference()) + { + result.push_back(referenceObject.getReference()); + } + else + { + result.clear(); + break; + } + } + + // We assume, that RVO (return value optimization) will work + return result; + } + + return std::vector(); +} + +std::vector PDFDocumentDataLoaderDecorator::readNameArray(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFObject& nameObject = array->getItem(i); + if (nameObject.isName()) + { + result.push_back(nameObject.getString()); + } + else + { + result.clear(); + break; + } + } + + // We assume, that RVO (return value optimization) will work + return result; + } + + return std::vector(); +} + +std::vector PDFDocumentDataLoaderDecorator::readNameArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readNameArray(dictionary->get(key)); + } + + return std::vector(); +} + +bool PDFDocumentDataLoaderDecorator::readBooleanFromDictionary(const PDFDictionary* dictionary, const char* key, bool defaultValue) const +{ + if (dictionary->hasKey(key)) + { + return readBoolean(dictionary->get(key), defaultValue); + } + + return defaultValue; +} + +QByteArray PDFDocumentDataLoaderDecorator::readNameFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readName(dictionary->get(key)); + } + + return QByteArray(); +} + +QByteArray PDFDocumentDataLoaderDecorator::readStringFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readString(dictionary->get(key)); + } + + return QByteArray(); +} + +std::vector PDFDocumentDataLoaderDecorator::readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readStringArray(dictionary->get(key)); + } + + return std::vector(); +} + +QStringList PDFDocumentDataLoaderDecorator::readTextStringList(const PDFObject& object) +{ + QStringList result; + + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + const size_t count = array->getCount(); + result.reserve(int(count)); + + for (size_t i = 0; i < count; ++i) + { + result << readTextString(array->getItem(i), QString()); + } + } + + return result; +} + +QColor PDFDocumentDataLoaderDecorator::readRGBColorFromDictionary(const PDFDictionary* dictionary, const char* key, QColor defaultColor) +{ + std::vector colors = readNumberArrayFromDictionary(dictionary, key); + + if (colors.size() == 3) + { + const PDFReal red = qBound(0.0, colors[0], 1.0); + const PDFReal green = qBound(0.0, colors[1], 1.0); + const PDFReal blue = qBound(0.0, colors[2], 1.0); + return QColor::fromRgbF(red, green, blue); + } + + return defaultColor; +} + +std::optional PDFDocumentDataLoaderDecorator::readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + return readStringFromDictionary(dictionary, key); + } + return std::nullopt; +} + +std::optional PDFDocumentDataLoaderDecorator::readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const +{ + if (dictionary->hasKey(key)) + { + PDFInteger integer = readIntegerFromDictionary(dictionary, key, std::numeric_limits::max()); + if (integer != std::numeric_limits::max()) + { + return integer; + } + } + return std::nullopt; +} + +std::vector PDFDocumentDataLoaderDecorator::readStringArray(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + std::vector result; + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFObject& stringObject = array->getItem(i); + if (stringObject.isString()) + { + result.push_back(stringObject.getString()); + } + else + { + result.clear(); + break; + } + } + + // We assume, that RVO (return value optimization) will work + return result; + } + + return std::vector(); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocument.h b/Pdf4QtLib/sources/pdfdocument.h index cd7c17b..109d856 100644 --- a/Pdf4QtLib/sources/pdfdocument.h +++ b/Pdf4QtLib/sources/pdfdocument.h @@ -1,636 +1,636 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCUMENT_H -#define PDFDOCUMENT_H - -#include "pdfglobal.h" -#include "pdfobject.h" -#include "pdfcatalog.h" -#include "pdfsecurityhandler.h" - -#include -#include -#include -#include - -#include - -namespace pdf -{ -class PDFDocument; -class PDFDocumentBuilder; - -/// Storage for objects. This class is not thread safe for writing (calling non-const functions). Caller must ensure -/// locking, if this object is used from multiple threads. Calling const functions should be thread safe. -class PDF4QTLIBSHARED_EXPORT PDFObjectStorage -{ -public: - inline PDFObjectStorage() = default; - - inline PDFObjectStorage(const PDFObjectStorage&) = default; - inline PDFObjectStorage(PDFObjectStorage&&) = default; - - inline PDFObjectStorage& operator=(const PDFObjectStorage&) = default; - inline PDFObjectStorage& operator=(PDFObjectStorage&&) = default; - - bool operator==(const PDFObjectStorage& other) const; - bool operator!=(const PDFObjectStorage& other) const { return !(*this == other); } - - struct Entry - { - constexpr inline explicit Entry() = default; - inline explicit Entry(PDFInteger generation, PDFObject object) : generation(generation), object(std::move(object)) { } - - inline bool operator==(const Entry& other) const { return generation == other.generation && object == other.object; } - inline bool operator!=(const Entry& other) const { return !(*this == other); } - - PDFInteger generation = 0; - PDFObject object; - }; - - using PDFObjects = std::vector; - - explicit PDFObjectStorage(PDFObjects&& objects, PDFObject&& trailerDictionary, PDFSecurityHandlerPointer&& securityHandler) : - m_objects(std::move(objects)), - m_trailerDictionary(std::move(trailerDictionary)), - m_securityHandler(std::move(securityHandler)) - { - - } - - /// Returns object from the object storage. If invalid reference is passed, - /// then null object is returned (no exception is thrown). - const PDFObject& getObject(PDFObjectReference reference) const; - - /// If object is reference, the dereference attempt is performed - /// and object is returned. If it is not a reference, then self - /// is returned. If dereference attempt fails, then null object - /// is returned (no exception is thrown). - const PDFObject& getObject(const PDFObject& object) const; - - /// Returns dictionary from an object. If object is not a dictionary, - /// then nullptr is returned (no exception is thrown). - const PDFDictionary* getDictionaryFromObject(const PDFObject& object) const; - - /// Returns object by reference. If dereference attempt fails, then null object - /// is returned (no exception is thrown). - const PDFObject& getObjectByReference(PDFObjectReference reference) const; - - /// Returns array of objects stored in this storage - const PDFObjects& getObjects() const { return m_objects; } - - /// Returns array of objects stored in this storage - PDFObjects& getObjects() { return m_objects; } - - /// Sets array of objects - void setObjects(PDFObjects&& objects) { m_objects = qMove(objects); } - - /// Returns trailer dictionary - const PDFObject& getTrailerDictionary() const { return m_trailerDictionary; } - - /// Returns security handler associated with these objects - const PDFSecurityHandler* getSecurityHandler() const { return m_securityHandler.data(); } - - /// Sets security handler associated with these objects - void setSecurityHandler(PDFSecurityHandlerPointer handler) { m_securityHandler = qMove(handler); } - - /// Adds a new object to the object list. This function - /// is not thread safe, do not call it from multiple threads. - /// \param object Object to be added - /// \returns Reference to new object - PDFObjectReference addObject(PDFObject object); - - /// Sets object to object storage. Reference must exist. - /// \param reference Reference to object - /// \param object New value of object - void setObject(PDFObjectReference reference, PDFObject object); - - /// Updates trailer dictionary. Preserves items which are not in a new - /// dictionary \p trailerDictionary. It merges new dictionary to the - /// old one. - /// \param trailerDictionary New trailer dictionary - void updateTrailerDictionary(PDFObject trailerDictionary); - - /// Returns the decoded stream. If stream data cannot be decoded, - /// then empty byte array is returned. - /// \param stream Stream to be decoded - QByteArray getDecodedStream(const PDFStream* stream) const; - - /// Set trailer dictionary - /// \param object Object defining trailer dictionary - void setTrailerDictionary(const PDFObject& object) { m_trailerDictionary = object; } - -private: - PDFObjects m_objects; - PDFObject m_trailerDictionary; - PDFSecurityHandlerPointer m_securityHandler; -}; - -/// Loads data from the object contained in the PDF document, such as integers, -/// bools, ... This object has two sets of functions - first one with default values, -/// then if object with valid data is not found, default value is used, and second one, -/// without default value, if valid data are not found, then exception is thrown. -/// This class uses Decorator design pattern. -class PDF4QTLIBSHARED_EXPORT PDFDocumentDataLoaderDecorator -{ -public: - explicit PDFDocumentDataLoaderDecorator(const PDFDocument* document); - inline explicit PDFDocumentDataLoaderDecorator(const PDFObjectStorage* storage) : m_storage(storage) { } - inline ~PDFDocumentDataLoaderDecorator() = default; - - /// Reads a name from the object, if it is possible. If object is not a name, - /// then empty byte array is returned. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - QByteArray readName(const PDFObject& object) const; - - /// Reads a string from the object, if it is possible. If object is not a string, - /// then empty byte array is returned. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - QByteArray readString(const PDFObject& object) const; - - /// Reads an integer from the object, if it is possible. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - /// \param defaultValue Default value - PDFInteger readInteger(const PDFObject& object, PDFInteger defaultValue) const; - - /// Reads a real number from the object, if it is possible. If integer appears as object, - /// then it is converted to real number. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - /// \param defaultValue Default value - PDFReal readNumber(const PDFObject& object, PDFReal defaultValue) const; - - /// Reads a boolean from the object, if it is possible. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - /// \param defaultValue Default value - bool readBoolean(const PDFObject& object, bool defaultValue) const; - - /// Reads a text string from the object, if it is possible. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - /// \param defaultValue Default value - QString readTextString(const PDFObject& object, const QString& defaultValue) const; - - /// Reads a rectangle from the object, if it is possible. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - /// \param defaultValue Default value - QRectF readRectangle(const PDFObject& object, const QRectF& defaultValue) const; - - /// Reads enum from name object, if it is possible. - /// \param object Object, can be an indirect reference to object (it is dereferenced) - /// \param begin Begin of the enum search array - /// \param end End of the enum search array - /// \param default value Default value - template - Enum readEnumByName(const PDFObject& object, Iterator begin, Iterator end, Enum defaultValue) const - { - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isName() || dereferencedObject.isString()) - { - QByteArray name = dereferencedObject.getString(); - - for (Iterator it = begin; it != end; ++it) - { - if (name == (*it).first) - { - return (*it).second; - } - } - } - - return defaultValue; - } - - /// Tries to read array of real values. Reads as much values as possible. - /// If array size differs, then nothing happens. - /// \param object Array of integers - /// \param first First iterator - /// \param second Second iterator - template - void readNumberArray(const PDFObject& object, T first, T last) - { - const PDFObject& dereferencedObject = m_storage->getObject(object); - if (dereferencedObject.isArray()) - { - const PDFArray* array = dereferencedObject.getArray(); - - size_t distance = std::distance(first, last); - if (array->getCount() == distance) - { - T it = first; - for (size_t i = 0; i < distance; ++i) - { - *it = readNumber(array->getItem(i), *it); - ++it; - } - } - } - } - - /// Tries to read array of real values from dictionary. Reads as much values as possible. - /// If array size differs, or entry dictionary doesn't exist, then nothing happens. - /// \param dictionary Dictionary with desired values - /// \param key Entry key - /// \param first First iterator - /// \param second Second iterator - template - void readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, T first, T last) - { - if (dictionary->hasKey(key)) - { - readNumberArray(dictionary->get(key), first, last); - } - } - - /// Tries to read matrix from the dictionary. If matrix entry is not present, default value is returned. - /// If it is present and invalid, exception is thrown. - QMatrix readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) const; - - /// Tries to read array of real values from dictionary. If entry dictionary doesn't exist, - /// or error occurs, default value is returned. - std::vector readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector defaultValue = std::vector()) const; - - /// Tries to read array of integer values from dictionary. If entry dictionary doesn't exist, - /// or error occurs, empty array is returned. - std::vector readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - /// \param defaultValue Default value - PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const; - - /// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - /// \param defaultValue Default value - PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const QByteArray& key, PDFReal defaultValue) const; - - /// Reads integer from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - /// \param defaultValue Default value - PDFInteger readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const; - - /// Reads a text string from the dictionary, if it is possible. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - /// \param defaultValue Default value - QString readTextStringFromDictionary(const PDFDictionary* dictionary, const char* key, const QString& defaultValue) const; - - /// Tries to read array of references from dictionary. If entry dictionary doesn't exist, - /// or error occurs, empty array is returned. - std::vector readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads number array from dictionary. Reads all values. If some value is not - /// real number (or integer number), default value is returned. Default value is also returned, - /// if \p object is invalid. - /// \param object Object containing array of numbers - std::vector readNumberArray(const PDFObject& object, std::vector defaultValue = std::vector()) const; - - /// Reads integer array from dictionary. Reads all values. If some value is not - /// integer number, empty array is returned. Empty array is also returned, - /// if \p object is invalid. - /// \param object Object containing array of numbers - std::vector readIntegerArray(const PDFObject& object) const; - - /// Reads reference. If error occurs, then invalid reference is returned. - /// \param object Object containing reference - PDFObjectReference readReference(const PDFObject& object) const; - - /// Reads reference from dictionary. If error occurs, then invalid reference is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - PDFObjectReference readReferenceFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads reference array. Reads all values. If error occurs, - /// then empty array is returned. - /// \param object Object containing array of references - std::vector readReferenceArray(const PDFObject& object) const; - - /// Reads name array. Reads all values. If error occurs, - /// then empty array is returned. - /// \param object Object containing array of references - std::vector readNameArray(const PDFObject& object) const; - - /// Reads string array. Reads all values. If error occurs, - /// then empty array is returned. - /// \param object Object containing array of references - std::vector readStringArray(const PDFObject& object) const; - - /// Reads name array from dictionary. Reads all values. If error occurs, - /// then empty array is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - std::vector readNameArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads boolean from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - /// \param defaultValue Default value - bool readBooleanFromDictionary(const PDFDictionary* dictionary, const char* key, bool defaultValue) const; - - /// Reads a name from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - QByteArray readNameFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads a string from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - QByteArray readStringFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads string array from dictionary. Reads all values. If error occurs, - /// then empty array is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - std::vector readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads string list. If error occurs, empty list is returned. - QStringList readTextStringList(const PDFObject& object); - - /// Reads RGB color from dictionary - QColor readRGBColorFromDictionary(const PDFDictionary* dictionary, const char* key, QColor defaultColor); - - /// Reads list of object, using parse function defined in object - template - std::vector readObjectList(PDFObject object) - { - std::vector result; - object = m_storage->getObject(object); - if (object.isArray()) - { - const PDFArray* array = object.getArray(); - const size_t count = array->getCount(); - result.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - result.emplace_back(Object::parse(m_storage, array->getItem(i))); - } - } - - return result; - } - - /// Reads optional string from dictionary. If key is not in dictionary, - /// then empty optional is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - std::optional readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const; - - /// Reads optionalinteger from dictionary. If dictionary entry doesn't exist, or error occurs, empty optional is returned. - /// \param dictionary Dictionary containing desired data - /// \param key Entry key - std::optional readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const; - -private: - const PDFObjectStorage* m_storage; -}; - -/// PDF document main class. -class PDF4QTLIBSHARED_EXPORT PDFDocument -{ - Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocument) - -public: - explicit PDFDocument() = default; - ~PDFDocument(); - - bool operator==(const PDFDocument& other) const; - bool operator!=(const PDFDocument& other) const { return !(*this == other); } - - const PDFObjectStorage& getStorage() const { return m_pdfObjectStorage; } - - /// Returns info about the document (title, author, etc.) - const PDFDocumentInfo* getInfo() const { return &m_info; } - - /// Returns document id part with given index. If index is invalid, - /// then empty id is returned. - QByteArray getIdPart(size_t index) const; - - /// If object is reference, the dereference attempt is performed - /// and object is returned. If it is not a reference, then self - /// is returned. If dereference attempt fails, then null object - /// is returned (no exception is thrown). - const PDFObject& getObject(const PDFObject& object) const; - - /// Returns dictionary from an object. If object is not a dictionary, - /// then nullptr is returned (no exception is thrown). - const PDFDictionary* getDictionaryFromObject(const PDFObject& object) const; - - /// Returns object by reference. If dereference attempt fails, then null object - /// is returned (no exception is thrown). - const PDFObject& getObjectByReference(PDFObjectReference reference) const; - - /// Returns the document catalog - const PDFCatalog* getCatalog() const { return &m_catalog; } - - /// Returns the decoded stream. If stream data cannot be decoded, - /// then empty byte array is returned. - /// \param stream Stream to be decoded - QByteArray getDecodedStream(const PDFStream* stream) const; - - /// Returns the trailer dictionary - const PDFDictionary* getTrailerDictionary() const; - - /// Returns version of the PDF document. Version can be taken from catalog, - /// or from PDF file header. Version from catalog has precedence over version from - /// header. - QByteArray getVersion() const; - - explicit PDFDocument(PDFObjectStorage&& storage, PDFVersion version) : - m_pdfObjectStorage(std::move(storage)) - { - init(); - - m_info.version = version; - } - -private: - friend class PDFDocumentReader; - friend class PDFDocumentBuilder; - friend class PDFOptimizer; - - /// Initialize data based on object in the storage. - /// Can throw exception if error is detected. - void init(); - - /// Initialize the document info from the trailer dictionary. - /// If document info is not present, then default document - /// info is used. If error is detected, exception is thrown. - void initInfo(); - - /// Storage of objects - PDFObjectStorage m_pdfObjectStorage; - - /// Info about the PDF document - PDFDocumentInfo m_info; - - /// Catalog object - PDFCatalog m_catalog; -}; - -using PDFDocumentPointer = QSharedPointer; - -/// Helper class for document updates (for example, add/delete annotations, -/// fill form fields, do some other minor operations) and also for major -/// updates (document reset). It also contains modification flags, which are -/// hints for update operations (for example, if only annotations are changed, -/// then rebuilding outline tree is not needed). At least one of the flags -/// must be set. Reset flag is conservative, so it should be set, when document -/// changes are not known. -class PDFModifiedDocument -{ -public: - - enum ModificationFlag - { - None = 0x0000, ///< No flag - Reset = 0x0001, ///< Whole document content is changed (for example, new document is being set) - Annotation = 0x0002, ///< Annotations changed - FormField = 0x0004, ///< Form field content changed - Authorization = 0x0008, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access) - }; - - Q_DECLARE_FLAGS(ModificationFlags, ModificationFlag) - - explicit inline PDFModifiedDocument() = default; - explicit inline PDFModifiedDocument(PDFDocument* document, PDFOptionalContentActivity* optionalContentActivity) : - m_document(document), - m_optionalContentActivity(optionalContentActivity), - m_flags(Reset) - { - Q_ASSERT(m_document); - } - - explicit inline PDFModifiedDocument(PDFDocumentPointer document, PDFOptionalContentActivity* optionalContentActivity) : - m_documentPointer(qMove(document)), - m_document(m_documentPointer.data()), - m_optionalContentActivity(optionalContentActivity), - m_flags(Reset) - { - Q_ASSERT(m_document); - } - - explicit inline PDFModifiedDocument(PDFDocument* document, PDFOptionalContentActivity* optionalContentActivity, ModificationFlags flags) : - m_document(document), - m_optionalContentActivity(optionalContentActivity), - m_flags(flags) - { - Q_ASSERT(m_document); - } - - explicit inline PDFModifiedDocument(PDFDocumentPointer document, PDFOptionalContentActivity* optionalContentActivity, ModificationFlags flags) : - m_documentPointer(qMove(document)), - m_document(m_documentPointer.data()), - m_optionalContentActivity(optionalContentActivity), - m_flags(flags) - { - Q_ASSERT(m_document); - } - - PDFDocument* getDocument() const { return m_document; } - PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; } - void setOptionalContentActivity(PDFOptionalContentActivity* optionalContentActivity) { m_optionalContentActivity = optionalContentActivity; } - ModificationFlags getFlags() const { return m_flags; } - - bool hasReset() const { return m_flags.testFlag(Reset); } - bool hasFlag(ModificationFlag flag) const { return m_flags.testFlag(flag); } - - operator PDFDocument*() const { return m_document; } - operator PDFDocumentPointer() const { return m_documentPointer; } - -private: - PDFDocumentPointer m_documentPointer; - PDFDocument* m_document = nullptr; - PDFOptionalContentActivity* m_optionalContentActivity = nullptr; - ModificationFlags m_flags = Reset; -}; - -// Implementation - -inline -const PDFObject& PDFDocument::getObject(const PDFObject& object) const -{ - if (object.isReference()) - { - // Try to dereference the object - return m_pdfObjectStorage.getObject(object.getReference()); - } - - return object; -} - -inline -const PDFDictionary* PDFDocument::getDictionaryFromObject(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = getObject(object); - if (dereferencedObject.isDictionary()) - { - return dereferencedObject.getDictionary(); - } - else if (dereferencedObject.isStream()) - { - return dereferencedObject.getStream()->getDictionary(); - } - - return nullptr; -} - -inline -const PDFObject& PDFDocument::getObjectByReference(PDFObjectReference reference) const -{ - return m_pdfObjectStorage.getObject(reference); -} - -inline -const PDFObject& PDFObjectStorage::getObject(const PDFObject& object) const -{ - if (object.isReference()) - { - // Try to dereference the object - return getObject(object.getReference()); - } - - return object; -} - -inline -const PDFDictionary* PDFObjectStorage::getDictionaryFromObject(const PDFObject& object) const -{ - const PDFObject& dereferencedObject = getObject(object); - if (dereferencedObject.isDictionary()) - { - return dereferencedObject.getDictionary(); - } - else if (dereferencedObject.isStream()) - { - return dereferencedObject.getStream()->getDictionary(); - } - - return nullptr; -} - -inline -const PDFObject& PDFObjectStorage::getObjectByReference(PDFObjectReference reference) const -{ - return getObject(reference); -} - -} // namespace pdf - -#endif // PDFDOCUMENT_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCUMENT_H +#define PDFDOCUMENT_H + +#include "pdfglobal.h" +#include "pdfobject.h" +#include "pdfcatalog.h" +#include "pdfsecurityhandler.h" + +#include +#include +#include +#include + +#include + +namespace pdf +{ +class PDFDocument; +class PDFDocumentBuilder; + +/// Storage for objects. This class is not thread safe for writing (calling non-const functions). Caller must ensure +/// locking, if this object is used from multiple threads. Calling const functions should be thread safe. +class PDF4QTLIBSHARED_EXPORT PDFObjectStorage +{ +public: + inline PDFObjectStorage() = default; + + inline PDFObjectStorage(const PDFObjectStorage&) = default; + inline PDFObjectStorage(PDFObjectStorage&&) = default; + + inline PDFObjectStorage& operator=(const PDFObjectStorage&) = default; + inline PDFObjectStorage& operator=(PDFObjectStorage&&) = default; + + bool operator==(const PDFObjectStorage& other) const; + bool operator!=(const PDFObjectStorage& other) const { return !(*this == other); } + + struct Entry + { + constexpr inline explicit Entry() = default; + inline explicit Entry(PDFInteger generation, PDFObject object) : generation(generation), object(std::move(object)) { } + + inline bool operator==(const Entry& other) const { return generation == other.generation && object == other.object; } + inline bool operator!=(const Entry& other) const { return !(*this == other); } + + PDFInteger generation = 0; + PDFObject object; + }; + + using PDFObjects = std::vector; + + explicit PDFObjectStorage(PDFObjects&& objects, PDFObject&& trailerDictionary, PDFSecurityHandlerPointer&& securityHandler) : + m_objects(std::move(objects)), + m_trailerDictionary(std::move(trailerDictionary)), + m_securityHandler(std::move(securityHandler)) + { + + } + + /// Returns object from the object storage. If invalid reference is passed, + /// then null object is returned (no exception is thrown). + const PDFObject& getObject(PDFObjectReference reference) const; + + /// If object is reference, the dereference attempt is performed + /// and object is returned. If it is not a reference, then self + /// is returned. If dereference attempt fails, then null object + /// is returned (no exception is thrown). + const PDFObject& getObject(const PDFObject& object) const; + + /// Returns dictionary from an object. If object is not a dictionary, + /// then nullptr is returned (no exception is thrown). + const PDFDictionary* getDictionaryFromObject(const PDFObject& object) const; + + /// Returns object by reference. If dereference attempt fails, then null object + /// is returned (no exception is thrown). + const PDFObject& getObjectByReference(PDFObjectReference reference) const; + + /// Returns array of objects stored in this storage + const PDFObjects& getObjects() const { return m_objects; } + + /// Returns array of objects stored in this storage + PDFObjects& getObjects() { return m_objects; } + + /// Sets array of objects + void setObjects(PDFObjects&& objects) { m_objects = qMove(objects); } + + /// Returns trailer dictionary + const PDFObject& getTrailerDictionary() const { return m_trailerDictionary; } + + /// Returns security handler associated with these objects + const PDFSecurityHandler* getSecurityHandler() const { return m_securityHandler.data(); } + + /// Sets security handler associated with these objects + void setSecurityHandler(PDFSecurityHandlerPointer handler) { m_securityHandler = qMove(handler); } + + /// Adds a new object to the object list. This function + /// is not thread safe, do not call it from multiple threads. + /// \param object Object to be added + /// \returns Reference to new object + PDFObjectReference addObject(PDFObject object); + + /// Sets object to object storage. Reference must exist. + /// \param reference Reference to object + /// \param object New value of object + void setObject(PDFObjectReference reference, PDFObject object); + + /// Updates trailer dictionary. Preserves items which are not in a new + /// dictionary \p trailerDictionary. It merges new dictionary to the + /// old one. + /// \param trailerDictionary New trailer dictionary + void updateTrailerDictionary(PDFObject trailerDictionary); + + /// Returns the decoded stream. If stream data cannot be decoded, + /// then empty byte array is returned. + /// \param stream Stream to be decoded + QByteArray getDecodedStream(const PDFStream* stream) const; + + /// Set trailer dictionary + /// \param object Object defining trailer dictionary + void setTrailerDictionary(const PDFObject& object) { m_trailerDictionary = object; } + +private: + PDFObjects m_objects; + PDFObject m_trailerDictionary; + PDFSecurityHandlerPointer m_securityHandler; +}; + +/// Loads data from the object contained in the PDF document, such as integers, +/// bools, ... This object has two sets of functions - first one with default values, +/// then if object with valid data is not found, default value is used, and second one, +/// without default value, if valid data are not found, then exception is thrown. +/// This class uses Decorator design pattern. +class PDF4QTLIBSHARED_EXPORT PDFDocumentDataLoaderDecorator +{ +public: + explicit PDFDocumentDataLoaderDecorator(const PDFDocument* document); + inline explicit PDFDocumentDataLoaderDecorator(const PDFObjectStorage* storage) : m_storage(storage) { } + inline ~PDFDocumentDataLoaderDecorator() = default; + + /// Reads a name from the object, if it is possible. If object is not a name, + /// then empty byte array is returned. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + QByteArray readName(const PDFObject& object) const; + + /// Reads a string from the object, if it is possible. If object is not a string, + /// then empty byte array is returned. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + QByteArray readString(const PDFObject& object) const; + + /// Reads an integer from the object, if it is possible. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param defaultValue Default value + PDFInteger readInteger(const PDFObject& object, PDFInteger defaultValue) const; + + /// Reads a real number from the object, if it is possible. If integer appears as object, + /// then it is converted to real number. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param defaultValue Default value + PDFReal readNumber(const PDFObject& object, PDFReal defaultValue) const; + + /// Reads a boolean from the object, if it is possible. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param defaultValue Default value + bool readBoolean(const PDFObject& object, bool defaultValue) const; + + /// Reads a text string from the object, if it is possible. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param defaultValue Default value + QString readTextString(const PDFObject& object, const QString& defaultValue) const; + + /// Reads a rectangle from the object, if it is possible. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param defaultValue Default value + QRectF readRectangle(const PDFObject& object, const QRectF& defaultValue) const; + + /// Reads enum from name object, if it is possible. + /// \param object Object, can be an indirect reference to object (it is dereferenced) + /// \param begin Begin of the enum search array + /// \param end End of the enum search array + /// \param default value Default value + template + Enum readEnumByName(const PDFObject& object, Iterator begin, Iterator end, Enum defaultValue) const + { + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isName() || dereferencedObject.isString()) + { + QByteArray name = dereferencedObject.getString(); + + for (Iterator it = begin; it != end; ++it) + { + if (name == (*it).first) + { + return (*it).second; + } + } + } + + return defaultValue; + } + + /// Tries to read array of real values. Reads as much values as possible. + /// If array size differs, then nothing happens. + /// \param object Array of integers + /// \param first First iterator + /// \param second Second iterator + template + void readNumberArray(const PDFObject& object, T first, T last) + { + const PDFObject& dereferencedObject = m_storage->getObject(object); + if (dereferencedObject.isArray()) + { + const PDFArray* array = dereferencedObject.getArray(); + + size_t distance = std::distance(first, last); + if (array->getCount() == distance) + { + T it = first; + for (size_t i = 0; i < distance; ++i) + { + *it = readNumber(array->getItem(i), *it); + ++it; + } + } + } + } + + /// Tries to read array of real values from dictionary. Reads as much values as possible. + /// If array size differs, or entry dictionary doesn't exist, then nothing happens. + /// \param dictionary Dictionary with desired values + /// \param key Entry key + /// \param first First iterator + /// \param second Second iterator + template + void readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, T first, T last) + { + if (dictionary->hasKey(key)) + { + readNumberArray(dictionary->get(key), first, last); + } + } + + /// Tries to read matrix from the dictionary. If matrix entry is not present, default value is returned. + /// If it is present and invalid, exception is thrown. + QMatrix readMatrixFromDictionary(const PDFDictionary* dictionary, const char* key, QMatrix defaultValue) const; + + /// Tries to read array of real values from dictionary. If entry dictionary doesn't exist, + /// or error occurs, default value is returned. + std::vector readNumberArrayFromDictionary(const PDFDictionary* dictionary, const char* key, std::vector defaultValue = std::vector()) const; + + /// Tries to read array of integer values from dictionary. If entry dictionary doesn't exist, + /// or error occurs, empty array is returned. + std::vector readIntegerArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const char* key, PDFReal defaultValue) const; + + /// Reads number from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + PDFReal readNumberFromDictionary(const PDFDictionary* dictionary, const QByteArray& key, PDFReal defaultValue) const; + + /// Reads integer from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + PDFInteger readIntegerFromDictionary(const PDFDictionary* dictionary, const char* key, PDFInteger defaultValue) const; + + /// Reads a text string from the dictionary, if it is possible. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + QString readTextStringFromDictionary(const PDFDictionary* dictionary, const char* key, const QString& defaultValue) const; + + /// Tries to read array of references from dictionary. If entry dictionary doesn't exist, + /// or error occurs, empty array is returned. + std::vector readReferenceArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads number array from dictionary. Reads all values. If some value is not + /// real number (or integer number), default value is returned. Default value is also returned, + /// if \p object is invalid. + /// \param object Object containing array of numbers + std::vector readNumberArray(const PDFObject& object, std::vector defaultValue = std::vector()) const; + + /// Reads integer array from dictionary. Reads all values. If some value is not + /// integer number, empty array is returned. Empty array is also returned, + /// if \p object is invalid. + /// \param object Object containing array of numbers + std::vector readIntegerArray(const PDFObject& object) const; + + /// Reads reference. If error occurs, then invalid reference is returned. + /// \param object Object containing reference + PDFObjectReference readReference(const PDFObject& object) const; + + /// Reads reference from dictionary. If error occurs, then invalid reference is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + PDFObjectReference readReferenceFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads reference array. Reads all values. If error occurs, + /// then empty array is returned. + /// \param object Object containing array of references + std::vector readReferenceArray(const PDFObject& object) const; + + /// Reads name array. Reads all values. If error occurs, + /// then empty array is returned. + /// \param object Object containing array of references + std::vector readNameArray(const PDFObject& object) const; + + /// Reads string array. Reads all values. If error occurs, + /// then empty array is returned. + /// \param object Object containing array of references + std::vector readStringArray(const PDFObject& object) const; + + /// Reads name array from dictionary. Reads all values. If error occurs, + /// then empty array is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::vector readNameArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads boolean from dictionary. If dictionary entry doesn't exist, or error occurs, default value is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + /// \param defaultValue Default value + bool readBooleanFromDictionary(const PDFDictionary* dictionary, const char* key, bool defaultValue) const; + + /// Reads a name from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + QByteArray readNameFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads a string from dictionary. If dictionary entry doesn't exist, or error occurs, empty byte array is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + QByteArray readStringFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads string array from dictionary. Reads all values. If error occurs, + /// then empty array is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::vector readStringArrayFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads string list. If error occurs, empty list is returned. + QStringList readTextStringList(const PDFObject& object); + + /// Reads RGB color from dictionary + QColor readRGBColorFromDictionary(const PDFDictionary* dictionary, const char* key, QColor defaultColor); + + /// Reads list of object, using parse function defined in object + template + std::vector readObjectList(PDFObject object) + { + std::vector result; + object = m_storage->getObject(object); + if (object.isArray()) + { + const PDFArray* array = object.getArray(); + const size_t count = array->getCount(); + result.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + result.emplace_back(Object::parse(m_storage, array->getItem(i))); + } + } + + return result; + } + + /// Reads optional string from dictionary. If key is not in dictionary, + /// then empty optional is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::optional readOptionalStringFromDictionary(const PDFDictionary* dictionary, const char* key) const; + + /// Reads optionalinteger from dictionary. If dictionary entry doesn't exist, or error occurs, empty optional is returned. + /// \param dictionary Dictionary containing desired data + /// \param key Entry key + std::optional readOptionalIntegerFromDictionary(const PDFDictionary* dictionary, const char* key) const; + +private: + const PDFObjectStorage* m_storage; +}; + +/// PDF document main class. +class PDF4QTLIBSHARED_EXPORT PDFDocument +{ + Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocument) + +public: + explicit PDFDocument() = default; + ~PDFDocument(); + + bool operator==(const PDFDocument& other) const; + bool operator!=(const PDFDocument& other) const { return !(*this == other); } + + const PDFObjectStorage& getStorage() const { return m_pdfObjectStorage; } + + /// Returns info about the document (title, author, etc.) + const PDFDocumentInfo* getInfo() const { return &m_info; } + + /// Returns document id part with given index. If index is invalid, + /// then empty id is returned. + QByteArray getIdPart(size_t index) const; + + /// If object is reference, the dereference attempt is performed + /// and object is returned. If it is not a reference, then self + /// is returned. If dereference attempt fails, then null object + /// is returned (no exception is thrown). + const PDFObject& getObject(const PDFObject& object) const; + + /// Returns dictionary from an object. If object is not a dictionary, + /// then nullptr is returned (no exception is thrown). + const PDFDictionary* getDictionaryFromObject(const PDFObject& object) const; + + /// Returns object by reference. If dereference attempt fails, then null object + /// is returned (no exception is thrown). + const PDFObject& getObjectByReference(PDFObjectReference reference) const; + + /// Returns the document catalog + const PDFCatalog* getCatalog() const { return &m_catalog; } + + /// Returns the decoded stream. If stream data cannot be decoded, + /// then empty byte array is returned. + /// \param stream Stream to be decoded + QByteArray getDecodedStream(const PDFStream* stream) const; + + /// Returns the trailer dictionary + const PDFDictionary* getTrailerDictionary() const; + + /// Returns version of the PDF document. Version can be taken from catalog, + /// or from PDF file header. Version from catalog has precedence over version from + /// header. + QByteArray getVersion() const; + + explicit PDFDocument(PDFObjectStorage&& storage, PDFVersion version) : + m_pdfObjectStorage(std::move(storage)) + { + init(); + + m_info.version = version; + } + +private: + friend class PDFDocumentReader; + friend class PDFDocumentBuilder; + friend class PDFOptimizer; + + /// Initialize data based on object in the storage. + /// Can throw exception if error is detected. + void init(); + + /// Initialize the document info from the trailer dictionary. + /// If document info is not present, then default document + /// info is used. If error is detected, exception is thrown. + void initInfo(); + + /// Storage of objects + PDFObjectStorage m_pdfObjectStorage; + + /// Info about the PDF document + PDFDocumentInfo m_info; + + /// Catalog object + PDFCatalog m_catalog; +}; + +using PDFDocumentPointer = QSharedPointer; + +/// Helper class for document updates (for example, add/delete annotations, +/// fill form fields, do some other minor operations) and also for major +/// updates (document reset). It also contains modification flags, which are +/// hints for update operations (for example, if only annotations are changed, +/// then rebuilding outline tree is not needed). At least one of the flags +/// must be set. Reset flag is conservative, so it should be set, when document +/// changes are not known. +class PDFModifiedDocument +{ +public: + + enum ModificationFlag + { + None = 0x0000, ///< No flag + Reset = 0x0001, ///< Whole document content is changed (for example, new document is being set) + Annotation = 0x0002, ///< Annotations changed + FormField = 0x0004, ///< Form field content changed + Authorization = 0x0008, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access) + }; + + Q_DECLARE_FLAGS(ModificationFlags, ModificationFlag) + + explicit inline PDFModifiedDocument() = default; + explicit inline PDFModifiedDocument(PDFDocument* document, PDFOptionalContentActivity* optionalContentActivity) : + m_document(document), + m_optionalContentActivity(optionalContentActivity), + m_flags(Reset) + { + Q_ASSERT(m_document); + } + + explicit inline PDFModifiedDocument(PDFDocumentPointer document, PDFOptionalContentActivity* optionalContentActivity) : + m_documentPointer(qMove(document)), + m_document(m_documentPointer.data()), + m_optionalContentActivity(optionalContentActivity), + m_flags(Reset) + { + Q_ASSERT(m_document); + } + + explicit inline PDFModifiedDocument(PDFDocument* document, PDFOptionalContentActivity* optionalContentActivity, ModificationFlags flags) : + m_document(document), + m_optionalContentActivity(optionalContentActivity), + m_flags(flags) + { + Q_ASSERT(m_document); + } + + explicit inline PDFModifiedDocument(PDFDocumentPointer document, PDFOptionalContentActivity* optionalContentActivity, ModificationFlags flags) : + m_documentPointer(qMove(document)), + m_document(m_documentPointer.data()), + m_optionalContentActivity(optionalContentActivity), + m_flags(flags) + { + Q_ASSERT(m_document); + } + + PDFDocument* getDocument() const { return m_document; } + PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; } + void setOptionalContentActivity(PDFOptionalContentActivity* optionalContentActivity) { m_optionalContentActivity = optionalContentActivity; } + ModificationFlags getFlags() const { return m_flags; } + + bool hasReset() const { return m_flags.testFlag(Reset); } + bool hasFlag(ModificationFlag flag) const { return m_flags.testFlag(flag); } + + operator PDFDocument*() const { return m_document; } + operator PDFDocumentPointer() const { return m_documentPointer; } + +private: + PDFDocumentPointer m_documentPointer; + PDFDocument* m_document = nullptr; + PDFOptionalContentActivity* m_optionalContentActivity = nullptr; + ModificationFlags m_flags = Reset; +}; + +// Implementation + +inline +const PDFObject& PDFDocument::getObject(const PDFObject& object) const +{ + if (object.isReference()) + { + // Try to dereference the object + return m_pdfObjectStorage.getObject(object.getReference()); + } + + return object; +} + +inline +const PDFDictionary* PDFDocument::getDictionaryFromObject(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = getObject(object); + if (dereferencedObject.isDictionary()) + { + return dereferencedObject.getDictionary(); + } + else if (dereferencedObject.isStream()) + { + return dereferencedObject.getStream()->getDictionary(); + } + + return nullptr; +} + +inline +const PDFObject& PDFDocument::getObjectByReference(PDFObjectReference reference) const +{ + return m_pdfObjectStorage.getObject(reference); +} + +inline +const PDFObject& PDFObjectStorage::getObject(const PDFObject& object) const +{ + if (object.isReference()) + { + // Try to dereference the object + return getObject(object.getReference()); + } + + return object; +} + +inline +const PDFDictionary* PDFObjectStorage::getDictionaryFromObject(const PDFObject& object) const +{ + const PDFObject& dereferencedObject = getObject(object); + if (dereferencedObject.isDictionary()) + { + return dereferencedObject.getDictionary(); + } + else if (dereferencedObject.isStream()) + { + return dereferencedObject.getStream()->getDictionary(); + } + + return nullptr; +} + +inline +const PDFObject& PDFObjectStorage::getObjectByReference(PDFObjectReference reference) const +{ + return getObject(reference); +} + +} // namespace pdf + +#endif // PDFDOCUMENT_H diff --git a/Pdf4QtLib/sources/pdfdocumentdrawinterface.h b/Pdf4QtLib/sources/pdfdocumentdrawinterface.h index f1a18f7..d2917e7 100644 --- a/Pdf4QtLib/sources/pdfdocumentdrawinterface.h +++ b/Pdf4QtLib/sources/pdfdocumentdrawinterface.h @@ -1,145 +1,145 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCUMENTDRAWINTERFACE_H -#define PDFDOCUMENTDRAWINTERFACE_H - -#include "pdfglobal.h" -#include "pdfexception.h" - -class QPainter; -class QKeyEvent; -class QMouseEvent; -class QWheelEvent; - -namespace pdf -{ -class PDFPrecompiledPage; -class PDFTextLayoutGetter; - -class PDF4QTLIBSHARED_EXPORT IDocumentDrawInterface -{ -public: - explicit inline IDocumentDrawInterface() = default; - virtual ~IDocumentDrawInterface() = default; - - /// Performs drawing of additional graphics onto the painter using precompiled page, - /// optionally text layout and page point to device point matrix. - /// \param painter Painter - /// \param pageIndex Page index - /// \param compiledPage Compiled page - /// \param layoutGetter Layout getter - /// \param pagePointToDevicePointMatrix Matrix mapping page space to device point space - /// \param[out] errors Output parameter - rendering errors - virtual void drawPage(QPainter* painter, - pdf::PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const; - - /// Performs drawing of additional graphics after all pages are drawn onto the painter. - /// \param painter Painter - /// \param rect Draw rectangle (usually viewport rectangle of the pdf widget) - virtual void drawPostRendering(QPainter* painter, QRect rect) const; -}; - -/// Input interface for handling events. Implementations should react on these events, -/// and set it to accepted, if they were consumed by the interface. Interface, which -/// consumes mouse press event, should also consume mouse release event. -class IDrawWidgetInputInterface -{ -public: - explicit inline IDrawWidgetInputInterface() = default; - virtual ~IDrawWidgetInputInterface() = default; - - enum InputPriority - { - ToolPriority = 10, - FormPriority = 20, - AnnotationPriority = 30, - UserPriority = 40 - }; - - /// Handles shortcut override event. Accept this event, when you want given - /// key sequence to be propagated to keyPressEvent. - /// \param widget Widget - /// \param event Event - virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) = 0; - - /// Handles key press event from widget - /// \param widget Widget - /// \param event Event - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) = 0; - - /// Handles key release event from widget - /// \param widget Widget - /// \param event Event - virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) = 0; - - /// Handles mouse press event from widget - /// \param widget Widget - /// \param event Event - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) = 0; - - /// Handles mouse double click event from widget - /// \param widget Widget - /// \param event Event - virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) = 0; - - /// Handles mouse release event from widget - /// \param widget Widget - /// \param event Event - virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) = 0; - - /// Handles mouse move event from widget - /// \param widget Widget - /// \param event Event - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) = 0; - - /// Handles mouse wheel event from widget - /// \param widget Widget - /// \param event Event - virtual void wheelEvent(QWidget* widget, QWheelEvent* event) = 0; - - /// Returns tooltip - virtual QString getTooltip() const = 0; - - /// Returns current cursor - virtual const std::optional& getCursor() const = 0; - - /// Returns input priority (interfaces with higher priority - /// will get input events before interfaces with lower priority) - virtual int getInputPriority() const = 0; - - class Comparator - { - public: - explicit constexpr inline Comparator() = default; - - bool operator()(IDrawWidgetInputInterface* left, IDrawWidgetInputInterface* right) const - { - return std::make_pair(left->getInputPriority(), left) < std::make_pair(right->getInputPriority(), right); - } - }; - - static constexpr Comparator getComparator() { return Comparator(); } -}; - -} // namespace pdf - -#endif // PDFDOCUMENTDRAWINTERFACE_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCUMENTDRAWINTERFACE_H +#define PDFDOCUMENTDRAWINTERFACE_H + +#include "pdfglobal.h" +#include "pdfexception.h" + +class QPainter; +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +namespace pdf +{ +class PDFPrecompiledPage; +class PDFTextLayoutGetter; + +class PDF4QTLIBSHARED_EXPORT IDocumentDrawInterface +{ +public: + explicit inline IDocumentDrawInterface() = default; + virtual ~IDocumentDrawInterface() = default; + + /// Performs drawing of additional graphics onto the painter using precompiled page, + /// optionally text layout and page point to device point matrix. + /// \param painter Painter + /// \param pageIndex Page index + /// \param compiledPage Compiled page + /// \param layoutGetter Layout getter + /// \param pagePointToDevicePointMatrix Matrix mapping page space to device point space + /// \param[out] errors Output parameter - rendering errors + virtual void drawPage(QPainter* painter, + pdf::PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const; + + /// Performs drawing of additional graphics after all pages are drawn onto the painter. + /// \param painter Painter + /// \param rect Draw rectangle (usually viewport rectangle of the pdf widget) + virtual void drawPostRendering(QPainter* painter, QRect rect) const; +}; + +/// Input interface for handling events. Implementations should react on these events, +/// and set it to accepted, if they were consumed by the interface. Interface, which +/// consumes mouse press event, should also consume mouse release event. +class IDrawWidgetInputInterface +{ +public: + explicit inline IDrawWidgetInputInterface() = default; + virtual ~IDrawWidgetInputInterface() = default; + + enum InputPriority + { + ToolPriority = 10, + FormPriority = 20, + AnnotationPriority = 30, + UserPriority = 40 + }; + + /// Handles shortcut override event. Accept this event, when you want given + /// key sequence to be propagated to keyPressEvent. + /// \param widget Widget + /// \param event Event + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) = 0; + + /// Handles key press event from widget + /// \param widget Widget + /// \param event Event + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) = 0; + + /// Handles key release event from widget + /// \param widget Widget + /// \param event Event + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) = 0; + + /// Handles mouse press event from widget + /// \param widget Widget + /// \param event Event + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) = 0; + + /// Handles mouse double click event from widget + /// \param widget Widget + /// \param event Event + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) = 0; + + /// Handles mouse release event from widget + /// \param widget Widget + /// \param event Event + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) = 0; + + /// Handles mouse move event from widget + /// \param widget Widget + /// \param event Event + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) = 0; + + /// Handles mouse wheel event from widget + /// \param widget Widget + /// \param event Event + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) = 0; + + /// Returns tooltip + virtual QString getTooltip() const = 0; + + /// Returns current cursor + virtual const std::optional& getCursor() const = 0; + + /// Returns input priority (interfaces with higher priority + /// will get input events before interfaces with lower priority) + virtual int getInputPriority() const = 0; + + class Comparator + { + public: + explicit constexpr inline Comparator() = default; + + bool operator()(IDrawWidgetInputInterface* left, IDrawWidgetInputInterface* right) const + { + return std::make_pair(left->getInputPriority(), left) < std::make_pair(right->getInputPriority(), right); + } + }; + + static constexpr Comparator getComparator() { return Comparator(); } +}; + +} // namespace pdf + +#endif // PDFDOCUMENTDRAWINTERFACE_H diff --git a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp index 33cb113..8f89df6 100644 --- a/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp +++ b/Pdf4QtLib/sources/pdfdocumentmanipulator.cpp @@ -1,603 +1,603 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfdocumentmanipulator.h" -#include "pdfdocumentbuilder.h" -#include "pdfoptimizer.h" - -namespace pdf -{ - -PDFOperationResult PDFDocumentManipulator::assemble(const AssembledPages& pages) -{ - if (pages.empty()) - { - return tr("Empty page list."); - } - - m_flags = None; - m_mergedObjects = { }; - m_assembledDocument = PDFDocument(); - - try - { - classify(pages); - - pdf::PDFDocumentBuilder documentBuilder; - if (m_flags.testFlag(SingleDocument)) - { - PDFInteger documentIndex = -1; - - for (const AssembledPage& assembledPage : pages) - { - if (assembledPage.isDocumentPage()) - { - documentIndex = assembledPage.documentIndex; - } - } - - if (documentIndex == -1 || !m_documents.count(documentIndex)) - { - throw PDFException(tr("Invalid document.")); - } - - documentBuilder.setDocument(m_documents.at(documentIndex)); - } - else - { - documentBuilder.createDocument(); - } - - initializeMergedObjects(documentBuilder); - - ProcessedPages processedPages = processPages(documentBuilder, pages); - std::vector adjustedPages; - std::transform(processedPages.cbegin(), processedPages.cend(), std::back_inserter(adjustedPages), [](const auto& page) { return page.targetPageReference; }); - documentBuilder.setPages(adjustedPages); - - // Correct page tree (invalid parents are present) - documentBuilder.flattenPageTree(); - if (!m_flags.testFlag(SingleDocument) || m_flags.testFlag(RemovedPages)) - { - documentBuilder.removeOutline(); - documentBuilder.removeThreads(); - documentBuilder.removeDocumentActions(); - documentBuilder.removeStructureTree(); - } - - // Jakub Melka: we also create document parts for each document part (if we aren't - // manipulating a single document). - if (!m_flags.testFlag(SingleDocument)) - { - addOutlineAndDocumentParts(documentBuilder, pages, adjustedPages); - } - - pdf::PDFDocument mergedDocument = documentBuilder.build(); - - // Optimize document - remove unused objects and shrink object storage - finalizeDocument(&mergedDocument); - } - catch (PDFException exception) - { - return exception.getMessage(); - } - - return true; -} - -PDFDocumentManipulator::ProcessedPages PDFDocumentManipulator::processPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages) -{ - ProcessedPages processedPages; - - // First, decide, if we are manipulating a single document, or - // an array of documents. If the former is the case, then we do not - // want to copy objects, it is just unnecessary. If the latter is the case, - // then we must - - if (m_flags.testFlag(SingleDocument)) - { - documentBuilder.flattenPageTree(); - std::vector pageReferences = documentBuilder.getPages(); - std::set usedPages; - - processedPages.reserve(pageReferences.size()); - for (const AssembledPage& assembledPage : pages) - { - ProcessedPage processedPage; - processedPage.assembledPage = assembledPage; - - if (assembledPage.isDocumentPage()) - { - const PDFInteger pageIndex = assembledPage.pageIndex; - - if (pageIndex < 0 || pageIndex >= PDFInteger(pageReferences.size())) - { - throw PDFException(tr("Missing page (%1) in a document.").arg(pageIndex)); - } - - PDFObjectReference pageReference = pageReferences[pageIndex]; - if (!usedPages.count(pageReference)) - { - processedPage.targetPageReference = pageReference; - usedPages.insert(pageReference); - } - else - { - // Page is being cloned. So we must clone it... - std::vector references = pdf::PDFDocumentBuilder::createReferencesFromObjects(documentBuilder.copyFrom(pdf::PDFDocumentBuilder::createObjectsFromReferences({ pageReference }), *documentBuilder.getStorage(), true)); - Q_ASSERT(references.size() == 1); - processedPage.targetPageReference = references.front(); - usedPages.insert(references.front()); - } - } - - processedPages.push_back(processedPage); - } - } - else - { - processedPages = collectObjectsAndCopyPages(documentBuilder, pages); - } - - // Now, create "special" pages, such as image pages or blank pages, and rotate - // final pages (we must check, that page object exists). - for (ProcessedPage& processedPage : processedPages) - { - if (processedPage.assembledPage.isBlankPage() || processedPage.assembledPage.isImagePage()) - { - QImage image; - - if (processedPage.assembledPage.isImagePage()) - { - const PDFInteger imageIndex = processedPage.assembledPage.imageIndex; - - if (!m_images.count(imageIndex)) - { - throw PDFException(tr("Missing image.")); - } - - image = m_images.at(imageIndex); - - if (image.isNull()) - { - throw PDFException(tr("Missing image.")); - } - } - - QRectF pageRect = QRectF(QPointF(0, 0), processedPage.assembledPage.pageSize * PDF_MM_TO_POINT); - processedPage.targetPageReference = documentBuilder.appendPage(pageRect); - PDFPageContentStreamBuilder contentStreamBuilder(&documentBuilder); - - QPainter* painter = contentStreamBuilder.begin(processedPage.targetPageReference); - - if (processedPage.assembledPage.isImagePage()) - { - // Just paint the image - painter->drawImage(pageRect, image); - } - - contentStreamBuilder.end(painter); - } - - if (!processedPage.targetPageReference.isValid()) - { - throw PDFException(tr("Error occured during page creation.")); - } - - documentBuilder.setPageRotation(processedPage.targetPageReference, processedPage.assembledPage.pageRotation); - } - - return processedPages; -} - -PDFDocumentManipulator::ProcessedPages PDFDocumentManipulator::collectObjectsAndCopyPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages) -{ - ProcessedPages processedPages; - processedPages.reserve(pages.size()); - - std::map, PDFObjectReference> documentPages; - - for (const AssembledPage& assembledPage : pages) - { - ProcessedPage processedPage; - processedPage.assembledPage = assembledPage; - processedPages.push_back(processedPage); - - if (assembledPage.isDocumentPage()) - { - documentPages[std::make_pair(assembledPage.documentIndex, assembledPage.pageIndex)] = PDFObjectReference(); - } - } - - for (auto it = documentPages.begin(); it != documentPages.end();) - { - const int documentIndex = it->first.first; - - // Jakub Melka: we will find end of a single document page range - auto itEnd = it; - while (itEnd != documentPages.end() && itEnd->first.first == documentIndex) - { - ++itEnd; - } - - if (documentIndex != -1) - { - // Check we have the document - if (!m_documents.count(documentIndex)) - { - throw PDFException(tr("Invalid document.")); - } - const PDFDocument* document = m_documents.at(documentIndex); - - // Copy the pages into the target document builder - std::vector pageIndices; - for (auto currentIt = it; currentIt != itEnd; ++currentIt) - { - pageIndices.push_back(currentIt->first.second); - } - - pdf::PDFDocumentBuilder temporaryBuilder(document); - temporaryBuilder.flattenPageTree(); - - std::vector currentPages = temporaryBuilder.getPages(); - std::vector objectsToMerge; - objectsToMerge.reserve(std::distance(it, itEnd)); - - for (int pageIndex : pageIndices) - { - if (pageIndex < 0 || pageIndex >= currentPages.size()) - { - throw PDFException(tr("Missing page (%1) in a document.").arg(pageIndex)); - } - - objectsToMerge.push_back(currentPages[pageIndex]); - } - - pdf::PDFObjectReference acroFormReference; - pdf::PDFObjectReference namesReference; - pdf::PDFObjectReference ocPropertiesReference; - pdf::PDFObjectReference outlineReference; - - pdf::PDFObject formObject = document->getCatalog()->getFormObject(); - if (formObject.isReference()) - { - acroFormReference = formObject.getReference(); - } - else - { - acroFormReference = temporaryBuilder.addObject(formObject); - } - - if (const pdf::PDFDictionary* catalogDictionary = temporaryBuilder.getDictionaryFromObject(temporaryBuilder.getObjectByReference(temporaryBuilder.getCatalogReference()))) - { - pdf::PDFObject namesObject = catalogDictionary->get("Names"); - if (namesObject.isReference()) - { - namesReference = namesObject.getReference(); - } - - pdf::PDFObject ocPropertiesObject = catalogDictionary->get("OCProperties"); - if (ocPropertiesObject.isReference()) - { - ocPropertiesReference = ocPropertiesObject.getReference(); - } - - pdf::PDFObject outlineObject = catalogDictionary->get("Outlines"); - if (outlineObject.isReference()) - { - outlineReference = outlineObject.getReference(); - } - } - - if (!namesReference.isValid()) - { - namesReference = temporaryBuilder.addObject(pdf::PDFObject()); - } - - if (!ocPropertiesReference.isValid()) - { - ocPropertiesReference = temporaryBuilder.addObject(pdf::PDFObject()); - } - - if (!outlineReference.isValid()) - { - outlineReference = temporaryBuilder.addObject(pdf::PDFObject()); - } - - objectsToMerge.insert(objectsToMerge.end(), { acroFormReference, namesReference, ocPropertiesReference, outlineReference }); - - // Now, we are ready to merge objects into target document builder - std::vector references = pdf::PDFDocumentBuilder::createReferencesFromObjects(documentBuilder.copyFrom(pdf::PDFDocumentBuilder::createObjectsFromReferences(objectsToMerge), *temporaryBuilder.getStorage(), true)); - - outlineReference = references.back(); - references.pop_back(); - ocPropertiesReference = references.back(); - references.pop_back(); - namesReference = references.back(); - references.pop_back(); - acroFormReference = references.back(); - references.pop_back(); - - documentBuilder.appendTo(m_mergedObjects[MOT_OCProperties], documentBuilder.getObjectByReference(ocPropertiesReference)); - documentBuilder.appendTo(m_mergedObjects[MOT_Form], documentBuilder.getObjectByReference(acroFormReference)); - documentBuilder.mergeNames(m_mergedObjects[MOT_Names], namesReference); - m_outlines[documentIndex] = outlineReference; - - Q_ASSERT(references.size() == std::distance(it, itEnd)); - - auto referenceIt = references.begin(); - for (auto currentIt = it; currentIt != itEnd; ++currentIt, ++referenceIt) - { - currentIt->second = *referenceIt; - } - } - - // Advance the index - it = itEnd; - } - - std::set usedReferences; - for (ProcessedPage& processedPage : processedPages) - { - if (processedPage.assembledPage.isDocumentPage()) - { - auto key = std::make_pair(processedPage.assembledPage.documentIndex, processedPage.assembledPage.pageIndex); - Q_ASSERT(documentPages.count(key)); - - PDFObjectReference pageReference = documentPages.at(key); - if (!usedReferences.count(pageReference)) - { - processedPage.targetPageReference = pageReference; - usedReferences.insert(pageReference); - } - else - { - // Page is being cloned. So we must clone it... - std::vector references = pdf::PDFDocumentBuilder::createReferencesFromObjects(documentBuilder.copyFrom(pdf::PDFDocumentBuilder::createObjectsFromReferences({ pageReference }), *documentBuilder.getStorage(), true)); - Q_ASSERT(references.size() == 1); - processedPage.targetPageReference = references.front(); - usedReferences.insert(references.front()); - } - } - } - - return processedPages; -} - -void PDFDocumentManipulator::classify(const AssembledPages& pages) -{ - m_flags = None; - - std::set documentIndices; - std::set pageIndices; - for (const AssembledPage& assembledPage : pages) - { - documentIndices.insert(assembledPage.documentIndex); - pageIndices.insert(assembledPage.pageIndex); - } - - documentIndices.erase(-1); - pageIndices.erase(-1); - - m_flags.setFlag(SingleDocument, documentIndices.size() == 1); - - if (m_flags.testFlag(SingleDocument) && m_documents.count(*documentIndices.begin())) - { - const PDFDocument* document = m_documents.at(*documentIndices.begin()); - const bool pagesRemoved = pageIndices.size() < document->getCatalog()->getPageCount(); - m_flags.setFlag(RemovedPages, pagesRemoved); - } -} - -void PDFDocumentManipulator::initializeMergedObjects(PDFDocumentBuilder& documentBuilder) -{ - m_mergedObjects[MOT_OCProperties] = documentBuilder.addObject(PDFObject()); - m_mergedObjects[MOT_Form] = documentBuilder.addObject(PDFObject()); - m_mergedObjects[MOT_Names] = documentBuilder.addObject(PDFObject()); -} - -void PDFDocumentManipulator::finalizeMergedObjects(PDFDocumentBuilder& documentBuilder) -{ - if (!m_flags.testFlag(SingleDocument)) - { - if (!documentBuilder.getObjectByReference(m_mergedObjects[MOT_OCProperties]).isNull()) - { - documentBuilder.setCatalogOptionalContentProperties(m_mergedObjects[MOT_OCProperties]); - } - - if (!documentBuilder.getObjectByReference(m_mergedObjects[MOT_Names]).isNull()) - { - documentBuilder.setCatalogNames(m_mergedObjects[MOT_Names]); - } - - if (!documentBuilder.getObjectByReference(m_mergedObjects[MOT_Form]).isNull()) - { - documentBuilder.setCatalogAcroForm(m_mergedObjects[MOT_Form]); - } - } -} - -void PDFDocumentManipulator::finalizeDocument(PDFDocument* document) -{ - auto optimizationFlags = pdf::PDFOptimizer::OptimizationFlags(PDFOptimizer::RemoveUnusedObjects | - PDFOptimizer::ShrinkObjectStorage | - PDFOptimizer::DereferenceSimpleObjects | - PDFOptimizer::MergeIdenticalObjects); - PDFOptimizer optimizer(optimizationFlags, nullptr); - optimizer.setDocument(document); - optimizer.optimize(); - PDFDocument mergedDocument = optimizer.takeOptimizedDocument(); - - // We must adjust some objects - they can have merged objects - pdf::PDFDocumentBuilder finalBuilder(&mergedDocument); - if (const pdf::PDFDictionary* dictionary = finalBuilder.getDictionaryFromObject(finalBuilder.getObjectByReference(finalBuilder.getCatalogReference()))) - { - pdf::PDFDocumentDataLoaderDecorator loader(finalBuilder.getStorage()); - pdf::PDFObjectReference ocPropertiesReference = loader.readReferenceFromDictionary(dictionary, "OCProperties"); - if (ocPropertiesReference.isValid()) - { - finalBuilder.setObject(ocPropertiesReference, pdf::PDFObjectManipulator::removeDuplicitReferencesInArrays(finalBuilder.getObjectByReference(ocPropertiesReference))); - } - pdf::PDFObjectReference acroFormReference = loader.readReferenceFromDictionary(dictionary, "AcroForm"); - if (acroFormReference.isValid()) - { - finalBuilder.setObject(acroFormReference, pdf::PDFObjectManipulator::removeDuplicitReferencesInArrays(finalBuilder.getObjectByReference(acroFormReference))); - } - } - m_assembledDocument = finalBuilder.build(); -} - -void PDFDocumentManipulator::addOutlineAndDocumentParts(PDFDocumentBuilder& documentBuilder, - const AssembledPages& pages, - const std::vector& adjustedPages) -{ - PDFInteger lastDocumentIndex = pages.front().documentIndex; - - struct DocumentPartInfo - { - size_t pageCount = 0; - PDFInteger documentIndex = 0; - bool isWholeDocument = false; - QString caption; - PDFObjectReference firstPageReference; - }; - std::vector documentParts = { DocumentPartInfo() }; - - PDFClosedIntervalSet pageNumbers; - PDFInteger imageCount = 0; - PDFInteger blankPageCount = 0; - PDFInteger totalPageCount = 0; - - auto addDocumentPartCaption = [&](PDFInteger documentIndex) - { - DocumentPartInfo& info = documentParts.back(); - - QString documentTitle; - if (documentIndex != -1 && m_documents.count(documentIndex)) - { - const PDFDocument* document = m_documents.at(documentIndex); - documentTitle = document->getInfo()->title; - if (documentTitle.isEmpty()) - { - documentTitle = tr("Document %1").arg(documentIndex); - } - - if (pageNumbers.getTotalLength() < PDFInteger(document->getCatalog()->getPageCount())) - { - documentTitle = tr("%1, p. %2").arg(documentTitle, pageNumbers.toText(true)); - } - else - { - info.isWholeDocument = true; - } - } - else if (imageCount > 0 && blankPageCount == 0) - { - documentTitle = tr("%1 Images").arg(imageCount); - } - else - { - documentTitle = tr("%1 Pages").arg(imageCount + blankPageCount); - } - - info.caption = documentTitle; - info.documentIndex = documentIndex; - info.firstPageReference = (totalPageCount < PDFInteger(adjustedPages.size())) ? adjustedPages[totalPageCount] : PDFObjectReference(); - - pageNumbers = PDFClosedIntervalSet(); - imageCount = 0; - blankPageCount = 0; - totalPageCount += info.pageCount; - }; - - for (const AssembledPage& page : pages) - { - if (page.documentIndex == lastDocumentIndex) - { - ++documentParts.back().pageCount; - } - else - { - addDocumentPartCaption(lastDocumentIndex); - documentParts.push_back(DocumentPartInfo()); - ++documentParts.back().pageCount; - lastDocumentIndex = page.documentIndex; - } - - if (page.isDocumentPage()) - { - pageNumbers.addValue(page.pageIndex + 1); - } - - if (page.isImagePage()) - { - ++imageCount; - } - - if (page.isBlankPage()) - { - ++blankPageCount; - } - } - addDocumentPartCaption(lastDocumentIndex); - - std::vector documentPartPageCounts; - std::transform(documentParts.cbegin(), documentParts.cend(), std::back_inserter(documentPartPageCounts), [](const auto& part) { return part.pageCount; }); - documentBuilder.createDocumentParts(documentPartPageCounts); - - if (m_outlineMode != OutlineMode::NoOutline) - { - QSharedPointer rootItem(new PDFOutlineItem()); - - for (const DocumentPartInfo& info : documentParts) - { - QSharedPointer documentPartItem(new PDFOutlineItem); - documentPartItem->setAction(PDFActionPtr(new PDFActionGoTo(PDFDestination::createFit(info.firstPageReference), PDFDestination()))); - documentPartItem->setTitle(info.caption); - documentPartItem->setFontBold(true); - - if (m_outlineMode == OutlineMode::Join && info.isWholeDocument) - { - const PDFInteger documentIndex = info.documentIndex; - QSharedPointer outline = PDFOutlineItem::parse(documentBuilder.getStorage(), PDFObject::createReference(m_outlines.at(documentIndex))); - if (outline) - { - for (size_t i = 0; i < outline->getChildCount(); ++i) - { - documentPartItem->addChild(outline->getChildPtr(i)); - } - } - } - - rootItem->addChild(std::move(documentPartItem)); - } - - documentBuilder.setOutline(rootItem.data()); - } -} - -PDFDocumentManipulator::OutlineMode PDFDocumentManipulator::getOutlineMode() const -{ - return m_outlineMode; -} - -void PDFDocumentManipulator::setOutlineMode(OutlineMode outlineMode) -{ - m_outlineMode = outlineMode; -} - -} // namespace pdf +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfdocumentmanipulator.h" +#include "pdfdocumentbuilder.h" +#include "pdfoptimizer.h" + +namespace pdf +{ + +PDFOperationResult PDFDocumentManipulator::assemble(const AssembledPages& pages) +{ + if (pages.empty()) + { + return tr("Empty page list."); + } + + m_flags = None; + m_mergedObjects = { }; + m_assembledDocument = PDFDocument(); + + try + { + classify(pages); + + pdf::PDFDocumentBuilder documentBuilder; + if (m_flags.testFlag(SingleDocument)) + { + PDFInteger documentIndex = -1; + + for (const AssembledPage& assembledPage : pages) + { + if (assembledPage.isDocumentPage()) + { + documentIndex = assembledPage.documentIndex; + } + } + + if (documentIndex == -1 || !m_documents.count(documentIndex)) + { + throw PDFException(tr("Invalid document.")); + } + + documentBuilder.setDocument(m_documents.at(documentIndex)); + } + else + { + documentBuilder.createDocument(); + } + + initializeMergedObjects(documentBuilder); + + ProcessedPages processedPages = processPages(documentBuilder, pages); + std::vector adjustedPages; + std::transform(processedPages.cbegin(), processedPages.cend(), std::back_inserter(adjustedPages), [](const auto& page) { return page.targetPageReference; }); + documentBuilder.setPages(adjustedPages); + + // Correct page tree (invalid parents are present) + documentBuilder.flattenPageTree(); + if (!m_flags.testFlag(SingleDocument) || m_flags.testFlag(RemovedPages)) + { + documentBuilder.removeOutline(); + documentBuilder.removeThreads(); + documentBuilder.removeDocumentActions(); + documentBuilder.removeStructureTree(); + } + + // Jakub Melka: we also create document parts for each document part (if we aren't + // manipulating a single document). + if (!m_flags.testFlag(SingleDocument)) + { + addOutlineAndDocumentParts(documentBuilder, pages, adjustedPages); + } + + pdf::PDFDocument mergedDocument = documentBuilder.build(); + + // Optimize document - remove unused objects and shrink object storage + finalizeDocument(&mergedDocument); + } + catch (PDFException exception) + { + return exception.getMessage(); + } + + return true; +} + +PDFDocumentManipulator::ProcessedPages PDFDocumentManipulator::processPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages) +{ + ProcessedPages processedPages; + + // First, decide, if we are manipulating a single document, or + // an array of documents. If the former is the case, then we do not + // want to copy objects, it is just unnecessary. If the latter is the case, + // then we must + + if (m_flags.testFlag(SingleDocument)) + { + documentBuilder.flattenPageTree(); + std::vector pageReferences = documentBuilder.getPages(); + std::set usedPages; + + processedPages.reserve(pageReferences.size()); + for (const AssembledPage& assembledPage : pages) + { + ProcessedPage processedPage; + processedPage.assembledPage = assembledPage; + + if (assembledPage.isDocumentPage()) + { + const PDFInteger pageIndex = assembledPage.pageIndex; + + if (pageIndex < 0 || pageIndex >= PDFInteger(pageReferences.size())) + { + throw PDFException(tr("Missing page (%1) in a document.").arg(pageIndex)); + } + + PDFObjectReference pageReference = pageReferences[pageIndex]; + if (!usedPages.count(pageReference)) + { + processedPage.targetPageReference = pageReference; + usedPages.insert(pageReference); + } + else + { + // Page is being cloned. So we must clone it... + std::vector references = pdf::PDFDocumentBuilder::createReferencesFromObjects(documentBuilder.copyFrom(pdf::PDFDocumentBuilder::createObjectsFromReferences({ pageReference }), *documentBuilder.getStorage(), true)); + Q_ASSERT(references.size() == 1); + processedPage.targetPageReference = references.front(); + usedPages.insert(references.front()); + } + } + + processedPages.push_back(processedPage); + } + } + else + { + processedPages = collectObjectsAndCopyPages(documentBuilder, pages); + } + + // Now, create "special" pages, such as image pages or blank pages, and rotate + // final pages (we must check, that page object exists). + for (ProcessedPage& processedPage : processedPages) + { + if (processedPage.assembledPage.isBlankPage() || processedPage.assembledPage.isImagePage()) + { + QImage image; + + if (processedPage.assembledPage.isImagePage()) + { + const PDFInteger imageIndex = processedPage.assembledPage.imageIndex; + + if (!m_images.count(imageIndex)) + { + throw PDFException(tr("Missing image.")); + } + + image = m_images.at(imageIndex); + + if (image.isNull()) + { + throw PDFException(tr("Missing image.")); + } + } + + QRectF pageRect = QRectF(QPointF(0, 0), processedPage.assembledPage.pageSize * PDF_MM_TO_POINT); + processedPage.targetPageReference = documentBuilder.appendPage(pageRect); + PDFPageContentStreamBuilder contentStreamBuilder(&documentBuilder); + + QPainter* painter = contentStreamBuilder.begin(processedPage.targetPageReference); + + if (processedPage.assembledPage.isImagePage()) + { + // Just paint the image + painter->drawImage(pageRect, image); + } + + contentStreamBuilder.end(painter); + } + + if (!processedPage.targetPageReference.isValid()) + { + throw PDFException(tr("Error occured during page creation.")); + } + + documentBuilder.setPageRotation(processedPage.targetPageReference, processedPage.assembledPage.pageRotation); + } + + return processedPages; +} + +PDFDocumentManipulator::ProcessedPages PDFDocumentManipulator::collectObjectsAndCopyPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages) +{ + ProcessedPages processedPages; + processedPages.reserve(pages.size()); + + std::map, PDFObjectReference> documentPages; + + for (const AssembledPage& assembledPage : pages) + { + ProcessedPage processedPage; + processedPage.assembledPage = assembledPage; + processedPages.push_back(processedPage); + + if (assembledPage.isDocumentPage()) + { + documentPages[std::make_pair(assembledPage.documentIndex, assembledPage.pageIndex)] = PDFObjectReference(); + } + } + + for (auto it = documentPages.begin(); it != documentPages.end();) + { + const int documentIndex = it->first.first; + + // Jakub Melka: we will find end of a single document page range + auto itEnd = it; + while (itEnd != documentPages.end() && itEnd->first.first == documentIndex) + { + ++itEnd; + } + + if (documentIndex != -1) + { + // Check we have the document + if (!m_documents.count(documentIndex)) + { + throw PDFException(tr("Invalid document.")); + } + const PDFDocument* document = m_documents.at(documentIndex); + + // Copy the pages into the target document builder + std::vector pageIndices; + for (auto currentIt = it; currentIt != itEnd; ++currentIt) + { + pageIndices.push_back(currentIt->first.second); + } + + pdf::PDFDocumentBuilder temporaryBuilder(document); + temporaryBuilder.flattenPageTree(); + + std::vector currentPages = temporaryBuilder.getPages(); + std::vector objectsToMerge; + objectsToMerge.reserve(std::distance(it, itEnd)); + + for (int pageIndex : pageIndices) + { + if (pageIndex < 0 || pageIndex >= currentPages.size()) + { + throw PDFException(tr("Missing page (%1) in a document.").arg(pageIndex)); + } + + objectsToMerge.push_back(currentPages[pageIndex]); + } + + pdf::PDFObjectReference acroFormReference; + pdf::PDFObjectReference namesReference; + pdf::PDFObjectReference ocPropertiesReference; + pdf::PDFObjectReference outlineReference; + + pdf::PDFObject formObject = document->getCatalog()->getFormObject(); + if (formObject.isReference()) + { + acroFormReference = formObject.getReference(); + } + else + { + acroFormReference = temporaryBuilder.addObject(formObject); + } + + if (const pdf::PDFDictionary* catalogDictionary = temporaryBuilder.getDictionaryFromObject(temporaryBuilder.getObjectByReference(temporaryBuilder.getCatalogReference()))) + { + pdf::PDFObject namesObject = catalogDictionary->get("Names"); + if (namesObject.isReference()) + { + namesReference = namesObject.getReference(); + } + + pdf::PDFObject ocPropertiesObject = catalogDictionary->get("OCProperties"); + if (ocPropertiesObject.isReference()) + { + ocPropertiesReference = ocPropertiesObject.getReference(); + } + + pdf::PDFObject outlineObject = catalogDictionary->get("Outlines"); + if (outlineObject.isReference()) + { + outlineReference = outlineObject.getReference(); + } + } + + if (!namesReference.isValid()) + { + namesReference = temporaryBuilder.addObject(pdf::PDFObject()); + } + + if (!ocPropertiesReference.isValid()) + { + ocPropertiesReference = temporaryBuilder.addObject(pdf::PDFObject()); + } + + if (!outlineReference.isValid()) + { + outlineReference = temporaryBuilder.addObject(pdf::PDFObject()); + } + + objectsToMerge.insert(objectsToMerge.end(), { acroFormReference, namesReference, ocPropertiesReference, outlineReference }); + + // Now, we are ready to merge objects into target document builder + std::vector references = pdf::PDFDocumentBuilder::createReferencesFromObjects(documentBuilder.copyFrom(pdf::PDFDocumentBuilder::createObjectsFromReferences(objectsToMerge), *temporaryBuilder.getStorage(), true)); + + outlineReference = references.back(); + references.pop_back(); + ocPropertiesReference = references.back(); + references.pop_back(); + namesReference = references.back(); + references.pop_back(); + acroFormReference = references.back(); + references.pop_back(); + + documentBuilder.appendTo(m_mergedObjects[MOT_OCProperties], documentBuilder.getObjectByReference(ocPropertiesReference)); + documentBuilder.appendTo(m_mergedObjects[MOT_Form], documentBuilder.getObjectByReference(acroFormReference)); + documentBuilder.mergeNames(m_mergedObjects[MOT_Names], namesReference); + m_outlines[documentIndex] = outlineReference; + + Q_ASSERT(references.size() == std::distance(it, itEnd)); + + auto referenceIt = references.begin(); + for (auto currentIt = it; currentIt != itEnd; ++currentIt, ++referenceIt) + { + currentIt->second = *referenceIt; + } + } + + // Advance the index + it = itEnd; + } + + std::set usedReferences; + for (ProcessedPage& processedPage : processedPages) + { + if (processedPage.assembledPage.isDocumentPage()) + { + auto key = std::make_pair(processedPage.assembledPage.documentIndex, processedPage.assembledPage.pageIndex); + Q_ASSERT(documentPages.count(key)); + + PDFObjectReference pageReference = documentPages.at(key); + if (!usedReferences.count(pageReference)) + { + processedPage.targetPageReference = pageReference; + usedReferences.insert(pageReference); + } + else + { + // Page is being cloned. So we must clone it... + std::vector references = pdf::PDFDocumentBuilder::createReferencesFromObjects(documentBuilder.copyFrom(pdf::PDFDocumentBuilder::createObjectsFromReferences({ pageReference }), *documentBuilder.getStorage(), true)); + Q_ASSERT(references.size() == 1); + processedPage.targetPageReference = references.front(); + usedReferences.insert(references.front()); + } + } + } + + return processedPages; +} + +void PDFDocumentManipulator::classify(const AssembledPages& pages) +{ + m_flags = None; + + std::set documentIndices; + std::set pageIndices; + for (const AssembledPage& assembledPage : pages) + { + documentIndices.insert(assembledPage.documentIndex); + pageIndices.insert(assembledPage.pageIndex); + } + + documentIndices.erase(-1); + pageIndices.erase(-1); + + m_flags.setFlag(SingleDocument, documentIndices.size() == 1); + + if (m_flags.testFlag(SingleDocument) && m_documents.count(*documentIndices.begin())) + { + const PDFDocument* document = m_documents.at(*documentIndices.begin()); + const bool pagesRemoved = pageIndices.size() < document->getCatalog()->getPageCount(); + m_flags.setFlag(RemovedPages, pagesRemoved); + } +} + +void PDFDocumentManipulator::initializeMergedObjects(PDFDocumentBuilder& documentBuilder) +{ + m_mergedObjects[MOT_OCProperties] = documentBuilder.addObject(PDFObject()); + m_mergedObjects[MOT_Form] = documentBuilder.addObject(PDFObject()); + m_mergedObjects[MOT_Names] = documentBuilder.addObject(PDFObject()); +} + +void PDFDocumentManipulator::finalizeMergedObjects(PDFDocumentBuilder& documentBuilder) +{ + if (!m_flags.testFlag(SingleDocument)) + { + if (!documentBuilder.getObjectByReference(m_mergedObjects[MOT_OCProperties]).isNull()) + { + documentBuilder.setCatalogOptionalContentProperties(m_mergedObjects[MOT_OCProperties]); + } + + if (!documentBuilder.getObjectByReference(m_mergedObjects[MOT_Names]).isNull()) + { + documentBuilder.setCatalogNames(m_mergedObjects[MOT_Names]); + } + + if (!documentBuilder.getObjectByReference(m_mergedObjects[MOT_Form]).isNull()) + { + documentBuilder.setCatalogAcroForm(m_mergedObjects[MOT_Form]); + } + } +} + +void PDFDocumentManipulator::finalizeDocument(PDFDocument* document) +{ + auto optimizationFlags = pdf::PDFOptimizer::OptimizationFlags(PDFOptimizer::RemoveUnusedObjects | + PDFOptimizer::ShrinkObjectStorage | + PDFOptimizer::DereferenceSimpleObjects | + PDFOptimizer::MergeIdenticalObjects); + PDFOptimizer optimizer(optimizationFlags, nullptr); + optimizer.setDocument(document); + optimizer.optimize(); + PDFDocument mergedDocument = optimizer.takeOptimizedDocument(); + + // We must adjust some objects - they can have merged objects + pdf::PDFDocumentBuilder finalBuilder(&mergedDocument); + if (const pdf::PDFDictionary* dictionary = finalBuilder.getDictionaryFromObject(finalBuilder.getObjectByReference(finalBuilder.getCatalogReference()))) + { + pdf::PDFDocumentDataLoaderDecorator loader(finalBuilder.getStorage()); + pdf::PDFObjectReference ocPropertiesReference = loader.readReferenceFromDictionary(dictionary, "OCProperties"); + if (ocPropertiesReference.isValid()) + { + finalBuilder.setObject(ocPropertiesReference, pdf::PDFObjectManipulator::removeDuplicitReferencesInArrays(finalBuilder.getObjectByReference(ocPropertiesReference))); + } + pdf::PDFObjectReference acroFormReference = loader.readReferenceFromDictionary(dictionary, "AcroForm"); + if (acroFormReference.isValid()) + { + finalBuilder.setObject(acroFormReference, pdf::PDFObjectManipulator::removeDuplicitReferencesInArrays(finalBuilder.getObjectByReference(acroFormReference))); + } + } + m_assembledDocument = finalBuilder.build(); +} + +void PDFDocumentManipulator::addOutlineAndDocumentParts(PDFDocumentBuilder& documentBuilder, + const AssembledPages& pages, + const std::vector& adjustedPages) +{ + PDFInteger lastDocumentIndex = pages.front().documentIndex; + + struct DocumentPartInfo + { + size_t pageCount = 0; + PDFInteger documentIndex = 0; + bool isWholeDocument = false; + QString caption; + PDFObjectReference firstPageReference; + }; + std::vector documentParts = { DocumentPartInfo() }; + + PDFClosedIntervalSet pageNumbers; + PDFInteger imageCount = 0; + PDFInteger blankPageCount = 0; + PDFInteger totalPageCount = 0; + + auto addDocumentPartCaption = [&](PDFInteger documentIndex) + { + DocumentPartInfo& info = documentParts.back(); + + QString documentTitle; + if (documentIndex != -1 && m_documents.count(documentIndex)) + { + const PDFDocument* document = m_documents.at(documentIndex); + documentTitle = document->getInfo()->title; + if (documentTitle.isEmpty()) + { + documentTitle = tr("Document %1").arg(documentIndex); + } + + if (pageNumbers.getTotalLength() < PDFInteger(document->getCatalog()->getPageCount())) + { + documentTitle = tr("%1, p. %2").arg(documentTitle, pageNumbers.toText(true)); + } + else + { + info.isWholeDocument = true; + } + } + else if (imageCount > 0 && blankPageCount == 0) + { + documentTitle = tr("%1 Images").arg(imageCount); + } + else + { + documentTitle = tr("%1 Pages").arg(imageCount + blankPageCount); + } + + info.caption = documentTitle; + info.documentIndex = documentIndex; + info.firstPageReference = (totalPageCount < PDFInteger(adjustedPages.size())) ? adjustedPages[totalPageCount] : PDFObjectReference(); + + pageNumbers = PDFClosedIntervalSet(); + imageCount = 0; + blankPageCount = 0; + totalPageCount += info.pageCount; + }; + + for (const AssembledPage& page : pages) + { + if (page.documentIndex == lastDocumentIndex) + { + ++documentParts.back().pageCount; + } + else + { + addDocumentPartCaption(lastDocumentIndex); + documentParts.push_back(DocumentPartInfo()); + ++documentParts.back().pageCount; + lastDocumentIndex = page.documentIndex; + } + + if (page.isDocumentPage()) + { + pageNumbers.addValue(page.pageIndex + 1); + } + + if (page.isImagePage()) + { + ++imageCount; + } + + if (page.isBlankPage()) + { + ++blankPageCount; + } + } + addDocumentPartCaption(lastDocumentIndex); + + std::vector documentPartPageCounts; + std::transform(documentParts.cbegin(), documentParts.cend(), std::back_inserter(documentPartPageCounts), [](const auto& part) { return part.pageCount; }); + documentBuilder.createDocumentParts(documentPartPageCounts); + + if (m_outlineMode != OutlineMode::NoOutline) + { + QSharedPointer rootItem(new PDFOutlineItem()); + + for (const DocumentPartInfo& info : documentParts) + { + QSharedPointer documentPartItem(new PDFOutlineItem); + documentPartItem->setAction(PDFActionPtr(new PDFActionGoTo(PDFDestination::createFit(info.firstPageReference), PDFDestination()))); + documentPartItem->setTitle(info.caption); + documentPartItem->setFontBold(true); + + if (m_outlineMode == OutlineMode::Join && info.isWholeDocument) + { + const PDFInteger documentIndex = info.documentIndex; + QSharedPointer outline = PDFOutlineItem::parse(documentBuilder.getStorage(), PDFObject::createReference(m_outlines.at(documentIndex))); + if (outline) + { + for (size_t i = 0; i < outline->getChildCount(); ++i) + { + documentPartItem->addChild(outline->getChildPtr(i)); + } + } + } + + rootItem->addChild(std::move(documentPartItem)); + } + + documentBuilder.setOutline(rootItem.data()); + } +} + +PDFDocumentManipulator::OutlineMode PDFDocumentManipulator::getOutlineMode() const +{ + return m_outlineMode; +} + +void PDFDocumentManipulator::setOutlineMode(OutlineMode outlineMode) +{ + m_outlineMode = outlineMode; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocumentmanipulator.h b/Pdf4QtLib/sources/pdfdocumentmanipulator.h index 66d0eb1..a8deb63 100644 --- a/Pdf4QtLib/sources/pdfdocumentmanipulator.h +++ b/Pdf4QtLib/sources/pdfdocumentmanipulator.h @@ -1,158 +1,158 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCUMENTMANIPULATOR_H -#define PDFDOCUMENTMANIPULATOR_H - -#include "pdfdocument.h" -#include "pdfutils.h" - -#include - -namespace pdf -{ - -/// Document page assembler/manipulator. Can assemble document(s) pages -/// to a new document, where pages are inserted/removed/moved, or joined -/// from another documents, or blank pages/image pages inserted. Document -/// is also optimized. -class PDF4QTLIBSHARED_EXPORT PDFDocumentManipulator -{ - Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentManipulator) - -public: - explicit PDFDocumentManipulator() = default; - - /// Selects outline creation mode, when multiple documents - /// are merged into one. For single document manipulation, - /// this has no meaning. - enum class OutlineMode - { - NoOutline, - Join, - DocumentParts - }; - - struct AssembledPage - { - PDFInteger documentIndex = -1; ///< Source document index. If page is not from a document, value is -1. - PDFInteger imageIndex = -1; ///< Source image index. If page is not from a image, value is -1. - PDFInteger pageIndex = -1; ///< Source document page index. If page is not from a document, value is -1. - QSizeF pageSize; ///< Unrotated page size - PageRotation pageRotation = PageRotation::None; ///< Page rotation - - constexpr bool isDocumentPage() const { return documentIndex != -1; } - constexpr bool isImagePage() const { return imageIndex != -1; } - constexpr bool isBlankPage() const { return documentIndex == -1 && imageIndex == -1; } - }; - - using AssembledPages = std::vector; - - /// Adds document with given index to available document list - /// \param documentIndex Document index - /// \param document Document - void addDocument(int documentIndex, const PDFDocument* document) { m_documents[documentIndex] = document; } - - /// Adds image with given index to available image list - /// \param imageIndex Image index - /// \param image Image - void addImage(int imageIndex, QImage image) { m_images[imageIndex] = std::move(image); } - - /// Assembles pages into a new document. Returns true, if a new document - /// was assembled, otherwise error message is being returned. Assebmled - /// document can be accessed trough a given getters. - /// \param pages Pages - /// \returns True or error message - PDFOperationResult assemble(const AssembledPages& pages); - - /// Returns reference to an assembled document. This function should - /// be called only, if method \p assemble returns true, otherwise - /// undefined document can be returned. - /// \returns Assembled document - const PDFDocument& getAssembledDocument() const { return m_assembledDocument; } - - /// Returns rvalue reference to an assembled document. This function should - /// be called only, if method \p assemble returns true, otherwise - /// undefined document can be returned. - /// \returns Assembled document - PDFDocument&& takeAssembledDocument() { return std::move(m_assembledDocument); } - - static constexpr AssembledPage createDocumentPage(int documentIndex, int pageIndex, QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ documentIndex, -1, pageIndex, pageSize, pageRotation}; } - static constexpr AssembledPage createImagePage(int imageIndex, QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ -1, imageIndex, -1, pageSize, pageRotation}; } - static constexpr AssembledPage createBlankPage(QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ -1, -1, -1, pageSize, pageRotation}; } - - OutlineMode getOutlineMode() const; - void setOutlineMode(OutlineMode outlineMode); - -private: - - struct ProcessedPage - { - AssembledPage assembledPage; - PDFObjectReference targetPageReference; - }; - - using ProcessedPages = std::vector; - - enum AssembleFlag - { - None = 0x0000, - SingleDocument = 0x0001, ///< We are assembling a single page document (possibly with blank pages / image pages - RemovedPages = 0x0002, ///< Document contains removed pages - }; - Q_DECLARE_FLAGS(AssembleFlags, AssembleFlag) - - enum MergedObjectType - { - MOT_OCProperties, - MOT_Form, - MOT_Names, - MOT_Last - }; - - /// Processes pages given a page list and a document builder. - /// \param documentBuilder Document builder - /// \param pages Pages to be processed - /// \returns Processed pages - ProcessedPages processPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages); - - /// Collects objects and copies them into the target document builder. - /// \param documentBuilder Document builder - /// \param pages Pages to be copied - /// \returns Processed pages - ProcessedPages collectObjectsAndCopyPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages); - - void classify(const AssembledPages& pages); - void initializeMergedObjects(PDFDocumentBuilder& documentBuilder); - void finalizeMergedObjects(PDFDocumentBuilder& documentBuilder); - void finalizeDocument(PDFDocument* document); - void addOutlineAndDocumentParts(PDFDocumentBuilder& documentBuilder, - const AssembledPages& pages, - const std::vector& adjustedPages); - - std::map m_documents; - std::map m_images; - AssembleFlags m_flags = None; - std::array m_mergedObjects = { }; - PDFDocument m_assembledDocument; - OutlineMode m_outlineMode = OutlineMode::DocumentParts; - std::map m_outlines; -}; - -} // namespace pdf - -#endif // PDFDOCUMENTMANIPULATOR_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCUMENTMANIPULATOR_H +#define PDFDOCUMENTMANIPULATOR_H + +#include "pdfdocument.h" +#include "pdfutils.h" + +#include + +namespace pdf +{ + +/// Document page assembler/manipulator. Can assemble document(s) pages +/// to a new document, where pages are inserted/removed/moved, or joined +/// from another documents, or blank pages/image pages inserted. Document +/// is also optimized. +class PDF4QTLIBSHARED_EXPORT PDFDocumentManipulator +{ + Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentManipulator) + +public: + explicit PDFDocumentManipulator() = default; + + /// Selects outline creation mode, when multiple documents + /// are merged into one. For single document manipulation, + /// this has no meaning. + enum class OutlineMode + { + NoOutline, + Join, + DocumentParts + }; + + struct AssembledPage + { + PDFInteger documentIndex = -1; ///< Source document index. If page is not from a document, value is -1. + PDFInteger imageIndex = -1; ///< Source image index. If page is not from a image, value is -1. + PDFInteger pageIndex = -1; ///< Source document page index. If page is not from a document, value is -1. + QSizeF pageSize; ///< Unrotated page size + PageRotation pageRotation = PageRotation::None; ///< Page rotation + + constexpr bool isDocumentPage() const { return documentIndex != -1; } + constexpr bool isImagePage() const { return imageIndex != -1; } + constexpr bool isBlankPage() const { return documentIndex == -1 && imageIndex == -1; } + }; + + using AssembledPages = std::vector; + + /// Adds document with given index to available document list + /// \param documentIndex Document index + /// \param document Document + void addDocument(int documentIndex, const PDFDocument* document) { m_documents[documentIndex] = document; } + + /// Adds image with given index to available image list + /// \param imageIndex Image index + /// \param image Image + void addImage(int imageIndex, QImage image) { m_images[imageIndex] = std::move(image); } + + /// Assembles pages into a new document. Returns true, if a new document + /// was assembled, otherwise error message is being returned. Assebmled + /// document can be accessed trough a given getters. + /// \param pages Pages + /// \returns True or error message + PDFOperationResult assemble(const AssembledPages& pages); + + /// Returns reference to an assembled document. This function should + /// be called only, if method \p assemble returns true, otherwise + /// undefined document can be returned. + /// \returns Assembled document + const PDFDocument& getAssembledDocument() const { return m_assembledDocument; } + + /// Returns rvalue reference to an assembled document. This function should + /// be called only, if method \p assemble returns true, otherwise + /// undefined document can be returned. + /// \returns Assembled document + PDFDocument&& takeAssembledDocument() { return std::move(m_assembledDocument); } + + static constexpr AssembledPage createDocumentPage(int documentIndex, int pageIndex, QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ documentIndex, -1, pageIndex, pageSize, pageRotation}; } + static constexpr AssembledPage createImagePage(int imageIndex, QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ -1, imageIndex, -1, pageSize, pageRotation}; } + static constexpr AssembledPage createBlankPage(QSizeF pageSize, PageRotation pageRotation) { return AssembledPage{ -1, -1, -1, pageSize, pageRotation}; } + + OutlineMode getOutlineMode() const; + void setOutlineMode(OutlineMode outlineMode); + +private: + + struct ProcessedPage + { + AssembledPage assembledPage; + PDFObjectReference targetPageReference; + }; + + using ProcessedPages = std::vector; + + enum AssembleFlag + { + None = 0x0000, + SingleDocument = 0x0001, ///< We are assembling a single page document (possibly with blank pages / image pages + RemovedPages = 0x0002, ///< Document contains removed pages + }; + Q_DECLARE_FLAGS(AssembleFlags, AssembleFlag) + + enum MergedObjectType + { + MOT_OCProperties, + MOT_Form, + MOT_Names, + MOT_Last + }; + + /// Processes pages given a page list and a document builder. + /// \param documentBuilder Document builder + /// \param pages Pages to be processed + /// \returns Processed pages + ProcessedPages processPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages); + + /// Collects objects and copies them into the target document builder. + /// \param documentBuilder Document builder + /// \param pages Pages to be copied + /// \returns Processed pages + ProcessedPages collectObjectsAndCopyPages(PDFDocumentBuilder& documentBuilder, const AssembledPages& pages); + + void classify(const AssembledPages& pages); + void initializeMergedObjects(PDFDocumentBuilder& documentBuilder); + void finalizeMergedObjects(PDFDocumentBuilder& documentBuilder); + void finalizeDocument(PDFDocument* document); + void addOutlineAndDocumentParts(PDFDocumentBuilder& documentBuilder, + const AssembledPages& pages, + const std::vector& adjustedPages); + + std::map m_documents; + std::map m_images; + AssembleFlags m_flags = None; + std::array m_mergedObjects = { }; + PDFDocument m_assembledDocument; + OutlineMode m_outlineMode = OutlineMode::DocumentParts; + std::map m_outlines; +}; + +} // namespace pdf + +#endif // PDFDOCUMENTMANIPULATOR_H diff --git a/Pdf4QtLib/sources/pdfdocumentreader.cpp b/Pdf4QtLib/sources/pdfdocumentreader.cpp index 13e3795..dfcbe40 100644 --- a/Pdf4QtLib/sources/pdfdocumentreader.cpp +++ b/Pdf4QtLib/sources/pdfdocumentreader.cpp @@ -1,848 +1,848 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#include "pdfdocumentreader.h" -#include "pdfconstants.h" -#include "pdfxreftable.h" -#include "pdfexception.h" -#include "pdfparser.h" -#include "pdfstreamfilters.h" -#include "pdfexecutionpolicy.h" - -#include - -#include -#include -#include -#include - -namespace pdf -{ - -PDFDocumentReader::PDFDocumentReader(PDFProgress* progress, const std::function& getPasswordCallback, bool permissive, bool authorizeOwnerOnly) : - m_result(Result::OK), - m_getPasswordCallback(getPasswordCallback), - m_progress(progress), - m_permissive(permissive), - m_authorizeOwnerOnly(authorizeOwnerOnly) -{ - -} - -PDFDocument PDFDocumentReader::readFromFile(const QString& fileName) -{ - QFile file(fileName); - - reset(); - - if (file.exists()) - { - if (file.open(QFile::ReadOnly)) - { - PDFDocument document = readFromDevice(&file); - file.close(); - return document; - } - else - { - m_result = Result::Failed; - m_errorMessage = tr("File '%1' cannot be opened for reading. %1").arg(file.errorString()); - } - } - else - { - m_result = Result::Failed; - m_errorMessage = tr("File '%1' doesn't exist.").arg(fileName); - } - - return PDFDocument(); -} - -PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device) -{ - reset(); - - if (device->isOpen()) - { - if (device->isReadable()) - { - // Do not close the device, it was not opened by us. - return readFromBuffer(device->readAll()); - } - else - { - m_result = Result::Failed; - m_errorMessage = tr("Device is not opened for reading."); - } - } - else if (device->open(QIODevice::ReadOnly)) - { - QByteArray byteArray = device->readAll(); - device->close(); - return readFromBuffer(byteArray); - } - else - { - m_result = Result::Failed; - m_errorMessage = tr("Can't open device for reading."); - } - - return PDFDocument(); -} - -void PDFDocumentReader::checkFooter(const QByteArray& buffer) -{ - if (findFromEnd(PDF_END_OF_FILE_MARK, buffer, PDF_FOOTER_SCAN_LIMIT) == FIND_NOT_FOUND_RESULT) - { - QString message = tr("End of file marking was not found."); - if (m_permissive) - { - QMutexLocker lock(&m_mutex); - m_warnings << message; - } - else - { - throw PDFException(message); - } - } -} - -void PDFDocumentReader::checkHeader(const QByteArray& buffer) -{ - // According to PDF Reference 1.7, Appendix H, file header can have two formats: - // - %PDF-x.x - // - %!PS-Adobe-y.y PDF-x.x - // We will search for both of these formats. - std::regex headerRegExp(PDF_FILE_HEADER_REGEXP); - std::cmatch headerMatch; - - auto itBegin = buffer.cbegin(); - auto itEnd = std::next(buffer.cbegin(), qMin(buffer.size(), PDF_HEADER_SCAN_LIMIT)); - - if (std::regex_search(itBegin, itEnd, headerMatch, headerRegExp)) - { - // Size depends on regular expression, not on the text (if regular expresion is matched) - Q_ASSERT(headerMatch.size() == 3); - Q_ASSERT(headerMatch[1].matched != headerMatch[2].matched); - - for (int i : { 1, 2 }) - { - if (headerMatch[i].matched) - { - Q_ASSERT(std::distance(headerMatch[i].first, headerMatch[i].second) == 3); - m_version = PDFVersion(*headerMatch[i].first - '0', *std::prev(headerMatch[i].second) - '0'); - break; - } - } - } - else - { - throw PDFException(tr("Header of PDF file was not found.")); - } - - // Check, if version is valid - if (!m_version.isValid()) - { - throw PDFException(tr("Version of the PDF file is not valid.")); - } -} - -const PDFInteger PDFDocumentReader::findXrefTableOffset(const QByteArray& buffer) -{ - const int startXRefPosition = findFromEnd(PDF_START_OF_XREF_MARK, buffer, PDF_FOOTER_SCAN_LIMIT); - if (startXRefPosition == FIND_NOT_FOUND_RESULT) - { - throw PDFException(tr("Start of object reference table not found.")); - } - - Q_ASSERT(startXRefPosition + std::strlen(PDF_START_OF_XREF_MARK) < buffer.size()); - PDFLexicalAnalyzer analyzer(buffer.constData() + startXRefPosition + std::strlen(PDF_START_OF_XREF_MARK), buffer.constData() + buffer.size()); - const PDFLexicalAnalyzer::Token token = analyzer.fetch(); - if (token.type != PDFLexicalAnalyzer::TokenType::Integer) - { - throw PDFException(tr("Start of object reference table not found.")); - } - - const PDFInteger firstXrefTableOffset = token.data.toLongLong(); - return firstXrefTableOffset; -} - -PDFObject PDFDocumentReader::getObject(PDFParsingContext* context, PDFInteger offset, PDFObjectReference reference) const -{ - PDFParsingContext::PDFParsingContextGuard guard(context, reference); - - PDFParser parser(m_source, context, PDFParser::AllowStreams); - parser.seek(offset); - - PDFObject objectNumber = parser.getObject(); - PDFObject generation = parser.getObject(); - - if (!objectNumber.isInt() || !generation.isInt()) - { - throw PDFException(tr("Can't read object at position %1.").arg(offset)); - } - - if (!parser.fetchCommand(PDF_OBJECT_START_MARK)) - { - throw PDFException(tr("Can't read object at position %1.").arg(offset)); - } - - PDFObject object = parser.getObject(); - - if (!parser.fetchCommand(PDF_OBJECT_END_MARK)) - { - throw PDFException(tr("Can't read object at position %1.").arg(offset)); - } - - PDFObjectReference scannedReference(objectNumber.getInteger(), generation.getInteger()); - if (scannedReference != reference) - { - throw PDFException(tr("Can't read object at position %1.").arg(offset)); - } - - return object; -} - -PDFObject PDFDocumentReader::getObjectFromXrefTable(PDFXRefTable* xrefTable, PDFParsingContext* context, PDFObjectReference reference) const -{ - const PDFXRefTable::Entry& entry = xrefTable->getEntry(reference); - switch (entry.type) - { - case PDFXRefTable::EntryType::Free: - return PDFObject(); - - case PDFXRefTable::EntryType::Occupied: - { - Q_ASSERT(entry.reference == reference); - return getObject(context, entry.offset, reference); - } - - default: - { - Q_ASSERT(false); - break; - } - } - - return PDFObject(); -} - -PDFObject PDFDocumentReader::readDamagedTrailerDictionary() const -{ - PDFObject object = PDFObject::createDictionary(std::make_shared(PDFDictionary())); - PDFParsingContext context([](PDFParsingContext*, PDFObjectReference){ return PDFObject(); }); - - int offset = 0; - while (offset < m_source.size()) - { - offset = m_source.indexOf(PDF_XREF_TRAILER, offset); - - if (offset == -1) - { - break; - } - - offset += static_cast(std::strlen(PDF_XREF_TRAILER)); - - // Try to read trailer dictioanry - try - { - PDFParser parser(m_source, &context, PDFParser::None); - parser.seek(offset); - - PDFObject trailerDictionaryObject = parser.getObject(); - if (trailerDictionaryObject.isDictionary()) - { - object = PDFObjectManipulator::merge(object, trailerDictionaryObject, PDFObjectManipulator::RemoveNullObjects); - } - } - catch (PDFException) - { - // Do nothing... - } - } - - return object; -} - -PDFDocumentReader::Result PDFDocumentReader::processReferenceTableEntries(PDFXRefTable* xrefTable, const std::vector& occupiedEntries, PDFObjectStorage::PDFObjects& objects) -{ - auto objectFetcher = [this, xrefTable](PDFParsingContext* context, PDFObjectReference reference) { return getObjectFromXrefTable(xrefTable, context, reference); }; - auto processEntry = [this, &objectFetcher, &objects](const PDFXRefTable::Entry& entry) - { - Q_ASSERT(entry.type == PDFXRefTable::EntryType::Occupied); - - if (m_result == Result::OK) - { - try - { - PDFParsingContext context(objectFetcher); - PDFObject object = getObject(&context, entry.offset, entry.reference); - - progressStep(); - - QMutexLocker lock(&m_mutex); - objects[entry.reference.objectNumber] = PDFObjectStorage::Entry(entry.reference.generation, object); - } - catch (PDFException exception) - { - QMutexLocker lock(&m_mutex); - - if (m_permissive) - { - m_warnings << exception.getMessage(); - } - else - { - m_result = Result::Failed; - m_errorMessage = exception.getMessage(); - } - } - } - }; - - // Now, we are ready to scan all objects - if (!occupiedEntries.empty()) - { - progressStart(occupiedEntries.size(), PDFTranslationContext::tr("Reading contents of document...")); - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, occupiedEntries.cbegin(), occupiedEntries.cend(), processEntry); - progressFinish(); - } - - return m_result; -} - -PDFDocumentReader::Result PDFDocumentReader::processSecurityHandler(const PDFObject& trailerDictionaryObject, - const std::vector& occupiedEntries, - PDFObjectStorage::PDFObjects& objects) -{ - const PDFDictionary* trailerDictionary = nullptr; - if (trailerDictionaryObject.isDictionary()) - { - trailerDictionary = trailerDictionaryObject.getDictionary(); - } - else if (trailerDictionaryObject.isStream()) - { - const PDFStream* stream = trailerDictionaryObject.getStream(); - trailerDictionary = stream->getDictionary(); - } - else - { - throw PDFException(tr("Invalid trailer dictionary.")); - } - - // Read the document ID - QByteArray id; - const PDFObject& idArrayObject = trailerDictionary->get("ID"); - if (idArrayObject.isArray()) - { - const PDFArray* idArray = idArrayObject.getArray(); - if (idArray->getCount() > 0) - { - const PDFObject& idArrayItem = idArray->getItem(0); - if (idArrayItem.isString()) - { - id = idArrayItem.getString(); - } - } - } - - PDFObjectReference encryptObjectReference; - PDFObject encryptObject = trailerDictionary->get("Encrypt"); - if (encryptObject.isReference()) - { - encryptObjectReference = encryptObject.getReference(); - PDFObjectReference encryptObjectReference = encryptObject.getReference(); - if (static_cast(encryptObjectReference.objectNumber) < objects.size() && objects[encryptObjectReference.objectNumber].generation == encryptObjectReference.generation) - { - encryptObject = objects[encryptObjectReference.objectNumber].object; - } - } - - // Read the security handler - m_securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id); - PDFSecurityHandler::AuthorizationResult authorizationResult = m_securityHandler->authenticate(m_getPasswordCallback, m_authorizeOwnerOnly); - - if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled) - { - // User cancelled the document reading - m_result = Result::Cancelled; - return m_result; - } - - if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed) - { - throw PDFException(PDFTranslationContext::tr("Authorization failed. Bad password provided.")); - } - - // Now, decrypt the document, if we are authorized. We must also check, if we have to decrypt the object. - // According to the PDF specification, following items are ommited from encryption: - // 1) Values for ID entry in the trailer dictionary - // 2) Any strings in Encrypt dictionary - // 3) String/streams in object streams (entire object streams are encrypted) - // 4) Hexadecimal strings in Content key in signature dictionary - // - // Trailer dictionary is not decrypted, because PDF specification provides no algorithm to decrypt it, - // because it needs object number and generation for generating the decrypt key. So 1) is handled - // automatically. 2) is handled in the code below. 3) is handled also automatically, because we do not - // decipher object streams here. 4) must be handled in the security handler. - if (m_securityHandler->getMode() != EncryptionMode::None) - { - auto decryptEntry = [this, encryptObjectReference, &objects](const PDFXRefTable::Entry& entry) - { - progressStep(); - - if (encryptObjectReference.objectNumber != 0 && encryptObjectReference == entry.reference) - { - // 2) - Encrypt dictionary - return; - } - - objects[entry.reference.objectNumber].object = m_securityHandler->decryptObject(objects[entry.reference.objectNumber].object, entry.reference); - }; - - progressStart(occupiedEntries.size(), PDFTranslationContext::tr("Decrypting encrypted contents of document...")); - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, occupiedEntries.cbegin(), occupiedEntries.cend(), decryptEntry); - progressFinish(); - } - - return m_result; -} - -void PDFDocumentReader::processObjectStreams(PDFXRefTable* xrefTable, PDFObjectStorage::PDFObjects& objects) -{ - // Then process object streams - std::vector objectStreamEntries = xrefTable->getObjectStreamEntries(); - std::set objectStreams; - for (const PDFXRefTable::Entry& entry : objectStreamEntries) - { - Q_ASSERT(entry.type == PDFXRefTable::EntryType::InObjectStream); - objectStreams.insert(entry.objectStream); - } - - auto objectFetcher = [this, xrefTable](PDFParsingContext* context, PDFObjectReference reference) { return getObjectFromXrefTable(xrefTable, context, reference); }; - auto processObjectStream = [this, &objectFetcher, &objects, &objectStreamEntries] (const PDFObjectReference& objectStreamReference) - { - if (m_result != Result::OK) - { - return; - } - - try - { - PDFParsingContext context(objectFetcher); - if (objectStreamReference.objectNumber >= static_cast(objects.size())) - { - throw PDFException(PDFTranslationContext::tr("Object stream %1 not found.").arg(objectStreamReference.objectNumber)); - } - - const PDFObject& object = objects[objectStreamReference.objectNumber].object; - if (!object.isStream()) - { - throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); - } - - const PDFStream* objectStream = object.getStream(); - const PDFDictionary* objectStreamDictionary = objectStream->getDictionary(); - - const PDFObject& objectStreamType = objectStreamDictionary->get("Type"); - if (!objectStreamType.isName() || objectStreamType.getString() != "ObjStm") - { - throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); - } - - const PDFObject& nObject = objectStreamDictionary->get("N"); - const PDFObject& firstObject = objectStreamDictionary->get("First"); - if (!nObject.isInt() || !firstObject.isInt()) - { - throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); - } - - // Number of objects in object stream dictionary - const PDFInteger n = nObject.getInteger(); - const PDFInteger first = firstObject.getInteger(); - - QByteArray objectStreamData = PDFStreamFilterStorage::getDecodedStream(objectStream, m_securityHandler.data()); - - PDFParsingContext::PDFParsingContextGuard guard(&context, objectStreamReference); - PDFParser parser(objectStreamData, &context, PDFParser::AllowStreams); - - std::vector> objectNumberAndOffset; - objectNumberAndOffset.reserve(n); - for (PDFInteger i = 0; i < n; ++i) - { - PDFObject currentObjectNumber = parser.getObject(); - PDFObject currentOffset = parser.getObject(); - - if (!currentObjectNumber.isInt() || !currentOffset.isInt()) - { - throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); - } - - const PDFInteger objectNumber = currentObjectNumber.getInteger(); - const PDFInteger offset = currentOffset.getInteger() + first; - objectNumberAndOffset.emplace_back(objectNumber, offset); - } - - for (size_t i = 0; i < objectNumberAndOffset.size(); ++i) - { - const PDFInteger objectNumber = objectNumberAndOffset[i].first; - const PDFInteger offset = objectNumberAndOffset[i].second; - parser.seek(offset); - - PDFObject object = parser.getObject(); - auto predicate = [objectNumber, objectStreamReference](const PDFXRefTable::Entry& entry) -> bool { return entry.reference.objectNumber == objectNumber && entry.objectStream == objectStreamReference; }; - if (std::find_if(objectStreamEntries.cbegin(), objectStreamEntries.cend(), predicate) != objectStreamEntries.cend()) - { - QMutexLocker lock(&m_mutex); - objects[objectNumber].object = qMove(object); - } - else - { - // Silently ignore this error. It is not critical, so, maybe this object will be null. - } - } - } - catch (PDFException exception) - { - QMutexLocker lock(&m_mutex); - m_result = Result::Failed; - m_errorMessage = exception.getMessage(); - } - }; - - // Now, we are ready to scan all object streams - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, objectStreams.cbegin(), objectStreams.cend(), processObjectStream); -} - -PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) -{ - bool shouldTryPermissiveReading = true; - - try - { - m_source = buffer; - - // FOOTER CHECKING - // 1) Check, if EOF marking is present - // 2) Find start of cross reference table - checkFooter(buffer); - const PDFInteger firstXrefTableOffset = findXrefTableOffset(buffer); - - // HEADER CHECKING - // 1) Check if header is present - // 2) Scan header version - checkHeader(buffer); - - // Now, we are ready to scan xref table - PDFXRefTable xrefTable; - xrefTable.readXRefTable(nullptr, buffer, firstXrefTableOffset); - - if (xrefTable.getSize() == 0) - { - throw PDFException(tr("Empty xref table.")); - } - - PDFObjectStorage::PDFObjects objects; - objects.resize(xrefTable.getSize()); - - std::vector occupiedEntries = xrefTable.getOccupiedEntries(); - - // First, process regular objects - if (processReferenceTableEntries(&xrefTable, occupiedEntries, objects) != Result::OK) - { - // Do not proceed further, if document loading failed - return PDFDocument(); - } - - if (processSecurityHandler(xrefTable.getTrailerDictionary(), occupiedEntries, objects) == Result::Cancelled) - { - return PDFDocument(); - } - - // We are past security inicialization. Do not attempt to restore damaged document from - // this point. After this point, security is decrypted. If something fails here, - // then document can't be restored (user can't be asked multiple times for password). - shouldTryPermissiveReading = !m_securityHandler || m_securityHandler->getMode() == EncryptionMode::None; - processObjectStreams(&xrefTable, objects); - - PDFObjectStorage storage(std::move(objects), PDFObject(xrefTable.getTrailerDictionary()), qMove(m_securityHandler)); - return PDFDocument(std::move(storage), m_version); - } - catch (PDFException parserException) - { - m_result = Result::Failed; - m_errorMessage = parserException.getMessage(); - m_warnings << m_errorMessage; - } - - if (m_result == Result::Failed && m_permissive && shouldTryPermissiveReading) - { - return readDamagedDocumentFromBuffer(buffer); - } - - return PDFDocument(); -} - -std::vector> PDFDocumentReader::findObjectByteOffsets(const QByteArray& buffer) const -{ - std::vector> offsets; - int lastOffset = 0; - const int shift = static_cast(std::strlen(PDF_OBJECT_END_MARK)); - while (lastOffset < buffer.size()) - { - int offset = buffer.indexOf(PDF_OBJECT_END_MARK, lastOffset); - - // Object end mark was not found - if (offset == -1) - { - break; - } - - offset += shift; - - int startOffset = buffer.indexOf(PDF_OBJECT_START_MARK, lastOffset); - if (startOffset != -1 && startOffset < offset) - { - --startOffset; - - // Skip whitespace between obj and generation number - while (startOffset >= 0 && PDFLexicalAnalyzer::isWhitespace(buffer[startOffset])) - { - --startOffset; - } - - // Skip generation number - while (startOffset >= 0 && std::isdigit(buffer[startOffset])) - { - --startOffset; - } - - // Skip whitespace between generation number and object number - while (startOffset >= 0 && PDFLexicalAnalyzer::isWhitespace(buffer[startOffset])) - { - --startOffset; - } - - // Skip object number - while (startOffset >= 0 && std::isdigit(buffer[startOffset])) - { - --startOffset; - } - - ++startOffset; - - if (startOffset < offset) - { - offsets.emplace_back(startOffset, offset); - } - } - - lastOffset = offset; - } - - return offsets; -} - -bool PDFDocumentReader::restoreObjects(std::map& restoredObjects, const std::vector>& offsets) -{ - QMutex restoredObjectsMutex; - std::atomic_bool succesfull = true; - - auto getObject = [&restoredObjects, &restoredObjectsMutex](PDFParsingContext*, PDFObjectReference reference) - { - QMutexLocker lock(&restoredObjectsMutex); - - auto it = restoredObjects.find(reference); - if (it != restoredObjects.cend()) - { - return it->second; - } - - return PDFObject(); - }; - - auto processOffsetEntry = [&, this](const std::pair& offset) - { - PDFParsingContext context(getObject); - const int startOffset = offset.first; - const int endOffset = offset.second; - - Q_ASSERT(startOffset >= 0 && startOffset < m_source.size()); - Q_ASSERT(endOffset >= 0 && endOffset <= m_source.size()); - Q_ASSERT(startOffset <= endOffset); - - try - { - const char* begin = m_source.constData() + startOffset; - const char* end = m_source.constData() + endOffset; - - PDFParser parser(begin, end, &context, PDFParser::AllowStreams); - PDFObject objectNumberObject = parser.getObject(); - PDFObject objectGenerationObject = parser.getObject(); - parser.fetchCommand(PDF_OBJECT_START_MARK); - PDFObject object = parser.getObject(); - - if (objectNumberObject.isInt() && objectGenerationObject.isInt() && !object.isNull()) - { - PDFObjectReference reference(objectNumberObject.getInteger(), objectGenerationObject.getInteger()); - if (reference.isValid()) - { - QMutexLocker lock(&restoredObjectsMutex); - if (!restoredObjects.count(reference)) - { - restoredObjects[reference] = qMove(object); - } - } - } - } - catch (PDFException) - { - // Do nothing - succesfull = false; - } - }; - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, offsets.cbegin(), offsets.cend(), processOffsetEntry); - return succesfull; -} - -PDFDocument PDFDocumentReader::readDamagedDocumentFromBuffer(const QByteArray& buffer) -{ - try - { - m_result = Result::OK; - - // Try to reconstruct trailer dictionary - std::map restoredObjects; - - PDFObject trailerDictionaryObject = readDamagedTrailerDictionary(); - if (!trailerDictionaryObject.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Trailer dictionary is not valid.")); - } - - // Jakub Melka: Try to parse objects - read offsets of objects. We must probably - // try second pass, if some streams have referenced objects. - std::vector> offsets = findObjectByteOffsets(buffer); - if (!restoreObjects(restoredObjects, offsets)) - { - restoreObjects(restoredObjects, offsets); - } - - // We will create security handler. - PDFObjectStorage::PDFObjects objects; - std::vector occupiedEntries; - - if (!restoredObjects.empty()) - { - objects.resize(restoredObjects.rbegin()->first.objectNumber + 1); - - for (auto& objectItem : restoredObjects) - { - PDFObjectReference reference = objectItem.first; - PDFObjectStorage::Entry& entry = objects[reference.objectNumber]; - entry.generation = reference.generation; - entry.object = qMove(objectItem.second); - } - } - - if (processSecurityHandler(trailerDictionaryObject, occupiedEntries, objects) == Result::Cancelled) - { - return PDFDocument(); - } - - PDFObjectStorage storage(std::move(objects), PDFObject(trailerDictionaryObject), qMove(m_securityHandler)); - return PDFDocument(std::move(storage), m_version); - } - catch (PDFException parserException) - { - m_result = Result::Failed; - m_warnings << parserException.getMessage(); - } - - return PDFDocument(); -} - -void PDFDocumentReader::reset() -{ - m_result = Result::OK; - m_errorMessage = QString(); - m_version = PDFVersion(); - m_source = QByteArray(); - m_securityHandler = nullptr; -} - -int PDFDocumentReader::findFromEnd(const char* what, const QByteArray& byteArray, int limit) -{ - if (byteArray.isEmpty()) - { - // Byte array is empty, no value found - return FIND_NOT_FOUND_RESULT; - } - - const int size = byteArray.size(); - const int adjustedLimit = qMin(byteArray.size(), limit); - const int whatLength = static_cast(std::strlen(what)); - - if (adjustedLimit < whatLength) - { - // Buffer is smaller than scan string - return FIND_NOT_FOUND_RESULT; - } - - auto itBegin = std::next(byteArray.cbegin(), size - adjustedLimit); - auto itEnd = byteArray.cend(); - auto it = std::find_end(itBegin, itEnd, what, std::next(what, whatLength)); - - if (it != byteArray.cend()) - { - return std::distance(byteArray.cbegin(), it); - } - - return FIND_NOT_FOUND_RESULT; -} - -void PDFDocumentReader::progressStart(size_t stepCount, QString text) -{ - if (m_progress) - { - ProgressStartupInfo info; - info.showDialog = !text.isEmpty(); - info.text = qMove(text); - - m_progress->start(stepCount, qMove(info)); - } -} - -void PDFDocumentReader::progressStep() -{ - if (m_progress) - { - m_progress->step(); - } -} - -void PDFDocumentReader::progressFinish() -{ - if (m_progress) - { - m_progress->finish(); - } -} - -} // namespace pdf +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#include "pdfdocumentreader.h" +#include "pdfconstants.h" +#include "pdfxreftable.h" +#include "pdfexception.h" +#include "pdfparser.h" +#include "pdfstreamfilters.h" +#include "pdfexecutionpolicy.h" + +#include + +#include +#include +#include +#include + +namespace pdf +{ + +PDFDocumentReader::PDFDocumentReader(PDFProgress* progress, const std::function& getPasswordCallback, bool permissive, bool authorizeOwnerOnly) : + m_result(Result::OK), + m_getPasswordCallback(getPasswordCallback), + m_progress(progress), + m_permissive(permissive), + m_authorizeOwnerOnly(authorizeOwnerOnly) +{ + +} + +PDFDocument PDFDocumentReader::readFromFile(const QString& fileName) +{ + QFile file(fileName); + + reset(); + + if (file.exists()) + { + if (file.open(QFile::ReadOnly)) + { + PDFDocument document = readFromDevice(&file); + file.close(); + return document; + } + else + { + m_result = Result::Failed; + m_errorMessage = tr("File '%1' cannot be opened for reading. %1").arg(file.errorString()); + } + } + else + { + m_result = Result::Failed; + m_errorMessage = tr("File '%1' doesn't exist.").arg(fileName); + } + + return PDFDocument(); +} + +PDFDocument PDFDocumentReader::readFromDevice(QIODevice* device) +{ + reset(); + + if (device->isOpen()) + { + if (device->isReadable()) + { + // Do not close the device, it was not opened by us. + return readFromBuffer(device->readAll()); + } + else + { + m_result = Result::Failed; + m_errorMessage = tr("Device is not opened for reading."); + } + } + else if (device->open(QIODevice::ReadOnly)) + { + QByteArray byteArray = device->readAll(); + device->close(); + return readFromBuffer(byteArray); + } + else + { + m_result = Result::Failed; + m_errorMessage = tr("Can't open device for reading."); + } + + return PDFDocument(); +} + +void PDFDocumentReader::checkFooter(const QByteArray& buffer) +{ + if (findFromEnd(PDF_END_OF_FILE_MARK, buffer, PDF_FOOTER_SCAN_LIMIT) == FIND_NOT_FOUND_RESULT) + { + QString message = tr("End of file marking was not found."); + if (m_permissive) + { + QMutexLocker lock(&m_mutex); + m_warnings << message; + } + else + { + throw PDFException(message); + } + } +} + +void PDFDocumentReader::checkHeader(const QByteArray& buffer) +{ + // According to PDF Reference 1.7, Appendix H, file header can have two formats: + // - %PDF-x.x + // - %!PS-Adobe-y.y PDF-x.x + // We will search for both of these formats. + std::regex headerRegExp(PDF_FILE_HEADER_REGEXP); + std::cmatch headerMatch; + + auto itBegin = buffer.cbegin(); + auto itEnd = std::next(buffer.cbegin(), qMin(buffer.size(), PDF_HEADER_SCAN_LIMIT)); + + if (std::regex_search(itBegin, itEnd, headerMatch, headerRegExp)) + { + // Size depends on regular expression, not on the text (if regular expresion is matched) + Q_ASSERT(headerMatch.size() == 3); + Q_ASSERT(headerMatch[1].matched != headerMatch[2].matched); + + for (int i : { 1, 2 }) + { + if (headerMatch[i].matched) + { + Q_ASSERT(std::distance(headerMatch[i].first, headerMatch[i].second) == 3); + m_version = PDFVersion(*headerMatch[i].first - '0', *std::prev(headerMatch[i].second) - '0'); + break; + } + } + } + else + { + throw PDFException(tr("Header of PDF file was not found.")); + } + + // Check, if version is valid + if (!m_version.isValid()) + { + throw PDFException(tr("Version of the PDF file is not valid.")); + } +} + +const PDFInteger PDFDocumentReader::findXrefTableOffset(const QByteArray& buffer) +{ + const int startXRefPosition = findFromEnd(PDF_START_OF_XREF_MARK, buffer, PDF_FOOTER_SCAN_LIMIT); + if (startXRefPosition == FIND_NOT_FOUND_RESULT) + { + throw PDFException(tr("Start of object reference table not found.")); + } + + Q_ASSERT(startXRefPosition + std::strlen(PDF_START_OF_XREF_MARK) < buffer.size()); + PDFLexicalAnalyzer analyzer(buffer.constData() + startXRefPosition + std::strlen(PDF_START_OF_XREF_MARK), buffer.constData() + buffer.size()); + const PDFLexicalAnalyzer::Token token = analyzer.fetch(); + if (token.type != PDFLexicalAnalyzer::TokenType::Integer) + { + throw PDFException(tr("Start of object reference table not found.")); + } + + const PDFInteger firstXrefTableOffset = token.data.toLongLong(); + return firstXrefTableOffset; +} + +PDFObject PDFDocumentReader::getObject(PDFParsingContext* context, PDFInteger offset, PDFObjectReference reference) const +{ + PDFParsingContext::PDFParsingContextGuard guard(context, reference); + + PDFParser parser(m_source, context, PDFParser::AllowStreams); + parser.seek(offset); + + PDFObject objectNumber = parser.getObject(); + PDFObject generation = parser.getObject(); + + if (!objectNumber.isInt() || !generation.isInt()) + { + throw PDFException(tr("Can't read object at position %1.").arg(offset)); + } + + if (!parser.fetchCommand(PDF_OBJECT_START_MARK)) + { + throw PDFException(tr("Can't read object at position %1.").arg(offset)); + } + + PDFObject object = parser.getObject(); + + if (!parser.fetchCommand(PDF_OBJECT_END_MARK)) + { + throw PDFException(tr("Can't read object at position %1.").arg(offset)); + } + + PDFObjectReference scannedReference(objectNumber.getInteger(), generation.getInteger()); + if (scannedReference != reference) + { + throw PDFException(tr("Can't read object at position %1.").arg(offset)); + } + + return object; +} + +PDFObject PDFDocumentReader::getObjectFromXrefTable(PDFXRefTable* xrefTable, PDFParsingContext* context, PDFObjectReference reference) const +{ + const PDFXRefTable::Entry& entry = xrefTable->getEntry(reference); + switch (entry.type) + { + case PDFXRefTable::EntryType::Free: + return PDFObject(); + + case PDFXRefTable::EntryType::Occupied: + { + Q_ASSERT(entry.reference == reference); + return getObject(context, entry.offset, reference); + } + + default: + { + Q_ASSERT(false); + break; + } + } + + return PDFObject(); +} + +PDFObject PDFDocumentReader::readDamagedTrailerDictionary() const +{ + PDFObject object = PDFObject::createDictionary(std::make_shared(PDFDictionary())); + PDFParsingContext context([](PDFParsingContext*, PDFObjectReference){ return PDFObject(); }); + + int offset = 0; + while (offset < m_source.size()) + { + offset = m_source.indexOf(PDF_XREF_TRAILER, offset); + + if (offset == -1) + { + break; + } + + offset += static_cast(std::strlen(PDF_XREF_TRAILER)); + + // Try to read trailer dictioanry + try + { + PDFParser parser(m_source, &context, PDFParser::None); + parser.seek(offset); + + PDFObject trailerDictionaryObject = parser.getObject(); + if (trailerDictionaryObject.isDictionary()) + { + object = PDFObjectManipulator::merge(object, trailerDictionaryObject, PDFObjectManipulator::RemoveNullObjects); + } + } + catch (PDFException) + { + // Do nothing... + } + } + + return object; +} + +PDFDocumentReader::Result PDFDocumentReader::processReferenceTableEntries(PDFXRefTable* xrefTable, const std::vector& occupiedEntries, PDFObjectStorage::PDFObjects& objects) +{ + auto objectFetcher = [this, xrefTable](PDFParsingContext* context, PDFObjectReference reference) { return getObjectFromXrefTable(xrefTable, context, reference); }; + auto processEntry = [this, &objectFetcher, &objects](const PDFXRefTable::Entry& entry) + { + Q_ASSERT(entry.type == PDFXRefTable::EntryType::Occupied); + + if (m_result == Result::OK) + { + try + { + PDFParsingContext context(objectFetcher); + PDFObject object = getObject(&context, entry.offset, entry.reference); + + progressStep(); + + QMutexLocker lock(&m_mutex); + objects[entry.reference.objectNumber] = PDFObjectStorage::Entry(entry.reference.generation, object); + } + catch (PDFException exception) + { + QMutexLocker lock(&m_mutex); + + if (m_permissive) + { + m_warnings << exception.getMessage(); + } + else + { + m_result = Result::Failed; + m_errorMessage = exception.getMessage(); + } + } + } + }; + + // Now, we are ready to scan all objects + if (!occupiedEntries.empty()) + { + progressStart(occupiedEntries.size(), PDFTranslationContext::tr("Reading contents of document...")); + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, occupiedEntries.cbegin(), occupiedEntries.cend(), processEntry); + progressFinish(); + } + + return m_result; +} + +PDFDocumentReader::Result PDFDocumentReader::processSecurityHandler(const PDFObject& trailerDictionaryObject, + const std::vector& occupiedEntries, + PDFObjectStorage::PDFObjects& objects) +{ + const PDFDictionary* trailerDictionary = nullptr; + if (trailerDictionaryObject.isDictionary()) + { + trailerDictionary = trailerDictionaryObject.getDictionary(); + } + else if (trailerDictionaryObject.isStream()) + { + const PDFStream* stream = trailerDictionaryObject.getStream(); + trailerDictionary = stream->getDictionary(); + } + else + { + throw PDFException(tr("Invalid trailer dictionary.")); + } + + // Read the document ID + QByteArray id; + const PDFObject& idArrayObject = trailerDictionary->get("ID"); + if (idArrayObject.isArray()) + { + const PDFArray* idArray = idArrayObject.getArray(); + if (idArray->getCount() > 0) + { + const PDFObject& idArrayItem = idArray->getItem(0); + if (idArrayItem.isString()) + { + id = idArrayItem.getString(); + } + } + } + + PDFObjectReference encryptObjectReference; + PDFObject encryptObject = trailerDictionary->get("Encrypt"); + if (encryptObject.isReference()) + { + encryptObjectReference = encryptObject.getReference(); + PDFObjectReference encryptObjectReference = encryptObject.getReference(); + if (static_cast(encryptObjectReference.objectNumber) < objects.size() && objects[encryptObjectReference.objectNumber].generation == encryptObjectReference.generation) + { + encryptObject = objects[encryptObjectReference.objectNumber].object; + } + } + + // Read the security handler + m_securityHandler = PDFSecurityHandler::createSecurityHandler(encryptObject, id); + PDFSecurityHandler::AuthorizationResult authorizationResult = m_securityHandler->authenticate(m_getPasswordCallback, m_authorizeOwnerOnly); + + if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Cancelled) + { + // User cancelled the document reading + m_result = Result::Cancelled; + return m_result; + } + + if (authorizationResult == PDFSecurityHandler::AuthorizationResult::Failed) + { + throw PDFException(PDFTranslationContext::tr("Authorization failed. Bad password provided.")); + } + + // Now, decrypt the document, if we are authorized. We must also check, if we have to decrypt the object. + // According to the PDF specification, following items are ommited from encryption: + // 1) Values for ID entry in the trailer dictionary + // 2) Any strings in Encrypt dictionary + // 3) String/streams in object streams (entire object streams are encrypted) + // 4) Hexadecimal strings in Content key in signature dictionary + // + // Trailer dictionary is not decrypted, because PDF specification provides no algorithm to decrypt it, + // because it needs object number and generation for generating the decrypt key. So 1) is handled + // automatically. 2) is handled in the code below. 3) is handled also automatically, because we do not + // decipher object streams here. 4) must be handled in the security handler. + if (m_securityHandler->getMode() != EncryptionMode::None) + { + auto decryptEntry = [this, encryptObjectReference, &objects](const PDFXRefTable::Entry& entry) + { + progressStep(); + + if (encryptObjectReference.objectNumber != 0 && encryptObjectReference == entry.reference) + { + // 2) - Encrypt dictionary + return; + } + + objects[entry.reference.objectNumber].object = m_securityHandler->decryptObject(objects[entry.reference.objectNumber].object, entry.reference); + }; + + progressStart(occupiedEntries.size(), PDFTranslationContext::tr("Decrypting encrypted contents of document...")); + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, occupiedEntries.cbegin(), occupiedEntries.cend(), decryptEntry); + progressFinish(); + } + + return m_result; +} + +void PDFDocumentReader::processObjectStreams(PDFXRefTable* xrefTable, PDFObjectStorage::PDFObjects& objects) +{ + // Then process object streams + std::vector objectStreamEntries = xrefTable->getObjectStreamEntries(); + std::set objectStreams; + for (const PDFXRefTable::Entry& entry : objectStreamEntries) + { + Q_ASSERT(entry.type == PDFXRefTable::EntryType::InObjectStream); + objectStreams.insert(entry.objectStream); + } + + auto objectFetcher = [this, xrefTable](PDFParsingContext* context, PDFObjectReference reference) { return getObjectFromXrefTable(xrefTable, context, reference); }; + auto processObjectStream = [this, &objectFetcher, &objects, &objectStreamEntries] (const PDFObjectReference& objectStreamReference) + { + if (m_result != Result::OK) + { + return; + } + + try + { + PDFParsingContext context(objectFetcher); + if (objectStreamReference.objectNumber >= static_cast(objects.size())) + { + throw PDFException(PDFTranslationContext::tr("Object stream %1 not found.").arg(objectStreamReference.objectNumber)); + } + + const PDFObject& object = objects[objectStreamReference.objectNumber].object; + if (!object.isStream()) + { + throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); + } + + const PDFStream* objectStream = object.getStream(); + const PDFDictionary* objectStreamDictionary = objectStream->getDictionary(); + + const PDFObject& objectStreamType = objectStreamDictionary->get("Type"); + if (!objectStreamType.isName() || objectStreamType.getString() != "ObjStm") + { + throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); + } + + const PDFObject& nObject = objectStreamDictionary->get("N"); + const PDFObject& firstObject = objectStreamDictionary->get("First"); + if (!nObject.isInt() || !firstObject.isInt()) + { + throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); + } + + // Number of objects in object stream dictionary + const PDFInteger n = nObject.getInteger(); + const PDFInteger first = firstObject.getInteger(); + + QByteArray objectStreamData = PDFStreamFilterStorage::getDecodedStream(objectStream, m_securityHandler.data()); + + PDFParsingContext::PDFParsingContextGuard guard(&context, objectStreamReference); + PDFParser parser(objectStreamData, &context, PDFParser::AllowStreams); + + std::vector> objectNumberAndOffset; + objectNumberAndOffset.reserve(n); + for (PDFInteger i = 0; i < n; ++i) + { + PDFObject currentObjectNumber = parser.getObject(); + PDFObject currentOffset = parser.getObject(); + + if (!currentObjectNumber.isInt() || !currentOffset.isInt()) + { + throw PDFException(PDFTranslationContext::tr("Object stream %1 is invalid.").arg(objectStreamReference.objectNumber)); + } + + const PDFInteger objectNumber = currentObjectNumber.getInteger(); + const PDFInteger offset = currentOffset.getInteger() + first; + objectNumberAndOffset.emplace_back(objectNumber, offset); + } + + for (size_t i = 0; i < objectNumberAndOffset.size(); ++i) + { + const PDFInteger objectNumber = objectNumberAndOffset[i].first; + const PDFInteger offset = objectNumberAndOffset[i].second; + parser.seek(offset); + + PDFObject object = parser.getObject(); + auto predicate = [objectNumber, objectStreamReference](const PDFXRefTable::Entry& entry) -> bool { return entry.reference.objectNumber == objectNumber && entry.objectStream == objectStreamReference; }; + if (std::find_if(objectStreamEntries.cbegin(), objectStreamEntries.cend(), predicate) != objectStreamEntries.cend()) + { + QMutexLocker lock(&m_mutex); + objects[objectNumber].object = qMove(object); + } + else + { + // Silently ignore this error. It is not critical, so, maybe this object will be null. + } + } + } + catch (PDFException exception) + { + QMutexLocker lock(&m_mutex); + m_result = Result::Failed; + m_errorMessage = exception.getMessage(); + } + }; + + // Now, we are ready to scan all object streams + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, objectStreams.cbegin(), objectStreams.cend(), processObjectStream); +} + +PDFDocument PDFDocumentReader::readFromBuffer(const QByteArray& buffer) +{ + bool shouldTryPermissiveReading = true; + + try + { + m_source = buffer; + + // FOOTER CHECKING + // 1) Check, if EOF marking is present + // 2) Find start of cross reference table + checkFooter(buffer); + const PDFInteger firstXrefTableOffset = findXrefTableOffset(buffer); + + // HEADER CHECKING + // 1) Check if header is present + // 2) Scan header version + checkHeader(buffer); + + // Now, we are ready to scan xref table + PDFXRefTable xrefTable; + xrefTable.readXRefTable(nullptr, buffer, firstXrefTableOffset); + + if (xrefTable.getSize() == 0) + { + throw PDFException(tr("Empty xref table.")); + } + + PDFObjectStorage::PDFObjects objects; + objects.resize(xrefTable.getSize()); + + std::vector occupiedEntries = xrefTable.getOccupiedEntries(); + + // First, process regular objects + if (processReferenceTableEntries(&xrefTable, occupiedEntries, objects) != Result::OK) + { + // Do not proceed further, if document loading failed + return PDFDocument(); + } + + if (processSecurityHandler(xrefTable.getTrailerDictionary(), occupiedEntries, objects) == Result::Cancelled) + { + return PDFDocument(); + } + + // We are past security inicialization. Do not attempt to restore damaged document from + // this point. After this point, security is decrypted. If something fails here, + // then document can't be restored (user can't be asked multiple times for password). + shouldTryPermissiveReading = !m_securityHandler || m_securityHandler->getMode() == EncryptionMode::None; + processObjectStreams(&xrefTable, objects); + + PDFObjectStorage storage(std::move(objects), PDFObject(xrefTable.getTrailerDictionary()), qMove(m_securityHandler)); + return PDFDocument(std::move(storage), m_version); + } + catch (PDFException parserException) + { + m_result = Result::Failed; + m_errorMessage = parserException.getMessage(); + m_warnings << m_errorMessage; + } + + if (m_result == Result::Failed && m_permissive && shouldTryPermissiveReading) + { + return readDamagedDocumentFromBuffer(buffer); + } + + return PDFDocument(); +} + +std::vector> PDFDocumentReader::findObjectByteOffsets(const QByteArray& buffer) const +{ + std::vector> offsets; + int lastOffset = 0; + const int shift = static_cast(std::strlen(PDF_OBJECT_END_MARK)); + while (lastOffset < buffer.size()) + { + int offset = buffer.indexOf(PDF_OBJECT_END_MARK, lastOffset); + + // Object end mark was not found + if (offset == -1) + { + break; + } + + offset += shift; + + int startOffset = buffer.indexOf(PDF_OBJECT_START_MARK, lastOffset); + if (startOffset != -1 && startOffset < offset) + { + --startOffset; + + // Skip whitespace between obj and generation number + while (startOffset >= 0 && PDFLexicalAnalyzer::isWhitespace(buffer[startOffset])) + { + --startOffset; + } + + // Skip generation number + while (startOffset >= 0 && std::isdigit(buffer[startOffset])) + { + --startOffset; + } + + // Skip whitespace between generation number and object number + while (startOffset >= 0 && PDFLexicalAnalyzer::isWhitespace(buffer[startOffset])) + { + --startOffset; + } + + // Skip object number + while (startOffset >= 0 && std::isdigit(buffer[startOffset])) + { + --startOffset; + } + + ++startOffset; + + if (startOffset < offset) + { + offsets.emplace_back(startOffset, offset); + } + } + + lastOffset = offset; + } + + return offsets; +} + +bool PDFDocumentReader::restoreObjects(std::map& restoredObjects, const std::vector>& offsets) +{ + QMutex restoredObjectsMutex; + std::atomic_bool succesfull = true; + + auto getObject = [&restoredObjects, &restoredObjectsMutex](PDFParsingContext*, PDFObjectReference reference) + { + QMutexLocker lock(&restoredObjectsMutex); + + auto it = restoredObjects.find(reference); + if (it != restoredObjects.cend()) + { + return it->second; + } + + return PDFObject(); + }; + + auto processOffsetEntry = [&, this](const std::pair& offset) + { + PDFParsingContext context(getObject); + const int startOffset = offset.first; + const int endOffset = offset.second; + + Q_ASSERT(startOffset >= 0 && startOffset < m_source.size()); + Q_ASSERT(endOffset >= 0 && endOffset <= m_source.size()); + Q_ASSERT(startOffset <= endOffset); + + try + { + const char* begin = m_source.constData() + startOffset; + const char* end = m_source.constData() + endOffset; + + PDFParser parser(begin, end, &context, PDFParser::AllowStreams); + PDFObject objectNumberObject = parser.getObject(); + PDFObject objectGenerationObject = parser.getObject(); + parser.fetchCommand(PDF_OBJECT_START_MARK); + PDFObject object = parser.getObject(); + + if (objectNumberObject.isInt() && objectGenerationObject.isInt() && !object.isNull()) + { + PDFObjectReference reference(objectNumberObject.getInteger(), objectGenerationObject.getInteger()); + if (reference.isValid()) + { + QMutexLocker lock(&restoredObjectsMutex); + if (!restoredObjects.count(reference)) + { + restoredObjects[reference] = qMove(object); + } + } + } + } + catch (PDFException) + { + // Do nothing + succesfull = false; + } + }; + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Unknown, offsets.cbegin(), offsets.cend(), processOffsetEntry); + return succesfull; +} + +PDFDocument PDFDocumentReader::readDamagedDocumentFromBuffer(const QByteArray& buffer) +{ + try + { + m_result = Result::OK; + + // Try to reconstruct trailer dictionary + std::map restoredObjects; + + PDFObject trailerDictionaryObject = readDamagedTrailerDictionary(); + if (!trailerDictionaryObject.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Trailer dictionary is not valid.")); + } + + // Jakub Melka: Try to parse objects - read offsets of objects. We must probably + // try second pass, if some streams have referenced objects. + std::vector> offsets = findObjectByteOffsets(buffer); + if (!restoreObjects(restoredObjects, offsets)) + { + restoreObjects(restoredObjects, offsets); + } + + // We will create security handler. + PDFObjectStorage::PDFObjects objects; + std::vector occupiedEntries; + + if (!restoredObjects.empty()) + { + objects.resize(restoredObjects.rbegin()->first.objectNumber + 1); + + for (auto& objectItem : restoredObjects) + { + PDFObjectReference reference = objectItem.first; + PDFObjectStorage::Entry& entry = objects[reference.objectNumber]; + entry.generation = reference.generation; + entry.object = qMove(objectItem.second); + } + } + + if (processSecurityHandler(trailerDictionaryObject, occupiedEntries, objects) == Result::Cancelled) + { + return PDFDocument(); + } + + PDFObjectStorage storage(std::move(objects), PDFObject(trailerDictionaryObject), qMove(m_securityHandler)); + return PDFDocument(std::move(storage), m_version); + } + catch (PDFException parserException) + { + m_result = Result::Failed; + m_warnings << parserException.getMessage(); + } + + return PDFDocument(); +} + +void PDFDocumentReader::reset() +{ + m_result = Result::OK; + m_errorMessage = QString(); + m_version = PDFVersion(); + m_source = QByteArray(); + m_securityHandler = nullptr; +} + +int PDFDocumentReader::findFromEnd(const char* what, const QByteArray& byteArray, int limit) +{ + if (byteArray.isEmpty()) + { + // Byte array is empty, no value found + return FIND_NOT_FOUND_RESULT; + } + + const int size = byteArray.size(); + const int adjustedLimit = qMin(byteArray.size(), limit); + const int whatLength = static_cast(std::strlen(what)); + + if (adjustedLimit < whatLength) + { + // Buffer is smaller than scan string + return FIND_NOT_FOUND_RESULT; + } + + auto itBegin = std::next(byteArray.cbegin(), size - adjustedLimit); + auto itEnd = byteArray.cend(); + auto it = std::find_end(itBegin, itEnd, what, std::next(what, whatLength)); + + if (it != byteArray.cend()) + { + return std::distance(byteArray.cbegin(), it); + } + + return FIND_NOT_FOUND_RESULT; +} + +void PDFDocumentReader::progressStart(size_t stepCount, QString text) +{ + if (m_progress) + { + ProgressStartupInfo info; + info.showDialog = !text.isEmpty(); + info.text = qMove(text); + + m_progress->start(stepCount, qMove(info)); + } +} + +void PDFDocumentReader::progressStep() +{ + if (m_progress) + { + m_progress->step(); + } +} + +void PDFDocumentReader::progressFinish() +{ + if (m_progress) + { + m_progress->finish(); + } +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocumentreader.h b/Pdf4QtLib/sources/pdfdocumentreader.h index bbec82c..cee4b44 100644 --- a/Pdf4QtLib/sources/pdfdocumentreader.h +++ b/Pdf4QtLib/sources/pdfdocumentreader.h @@ -1,179 +1,179 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#ifndef PDFDOCUMENTREADER_H -#define PDFDOCUMENTREADER_H - -#include "pdfglobal.h" -#include "pdfdocument.h" -#include "pdfprogress.h" -#include "pdfxreftable.h" - -#include -#include - -namespace pdf -{ -class PDFXRefTable; -class PDFParsingContext; - -/// This class is a reader of PDF document from various devices (file, io device, -/// byte buffer). This class doesn't throw exceptions, to check errors, use -/// appropriate functions. -class PDF4QTLIBSHARED_EXPORT PDFDocumentReader -{ - Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentReader) - -public: - explicit PDFDocumentReader(PDFProgress* progress, const std::function& getPasswordCallback, bool permissive, bool authorizeOwnerOnly); - - constexpr inline PDFDocumentReader(const PDFDocumentReader&) = delete; - constexpr inline PDFDocumentReader(PDFDocumentReader&&) = delete; - constexpr inline PDFDocumentReader& operator=(const PDFDocumentReader&) = delete; - constexpr inline PDFDocumentReader& operator=(PDFDocumentReader&&) = delete; - - enum class Result - { - OK, ///< Document was successfully loaded - Failed, ///< Error occured during document reading - Cancelled ///< User cancelled document reading - }; - - /// Reads a PDF document from the specified file. If file doesn't exist, - /// cannot be opened or contain invalid pdf, empty PDF file is returned. - /// No exception is thrown. - PDFDocument readFromFile(const QString& fileName); - - /// Reads a PDF document from the specified device. If device is not opened - /// for reading, then function tries it to open for reading. If it is opened, - /// but not for reading, empty PDF document is returned. This also occurs - /// when incorrect PDF is read. No exception is thrown. - PDFDocument readFromDevice(QIODevice* device); - - /// Reads a PDF document from the specified buffer (byte array). If incorrect - /// PDF is read, then empty PDF document is returned. No exception is thrown. - PDFDocument readFromBuffer(const QByteArray& buffer); - - /// Returns result code for reading document from the device - Result getReadingResult() const { return m_result; } - - /// Returns error message, if document reading was unsuccessfull - const QString& getErrorMessage() const { return m_errorMessage; } - - /// Get source data of the document - const QByteArray& getSource() const { return m_source; } - - /// Returns warning messages - const QStringList& getWarnings() const { return m_warnings; } - -private: - static constexpr const int FIND_NOT_FOUND_RESULT = -1; - - /// Resets the internal state and prepares it for new reading cycle - void reset(); - - /// Find a last string in the byte array, scan only \p limit bytes. If string - /// is not found, then FIND_NOT_FOUND_RESULT is returned, if it is found, then - /// it position from the beginning of byte array is returned. - /// \param what String to be found - /// \param byteArray Byte array to be scanned from the end - /// \param limit Scan up to this value bytes from the end - /// \returns Position of string, or FIND_NOT_FOUND_RESULT - int findFromEnd(const char* what, const QByteArray& byteArray, int limit); - - void checkFooter(const QByteArray& buffer); - void checkHeader(const QByteArray& buffer); - const PDFInteger findXrefTableOffset(const QByteArray& buffer); - Result processReferenceTableEntries(PDFXRefTable* xrefTable, const std::vector& occupiedEntries, PDFObjectStorage::PDFObjects& objects); - Result processSecurityHandler(const PDFObject& trailerDictionaryObject, const std::vector& occupiedEntries, PDFObjectStorage::PDFObjects& objects); - void processObjectStreams(PDFXRefTable* xrefTable, PDFObjectStorage::PDFObjects& objects); - - /// This function fetches object from the buffer from the specified offset. - /// Can throw exception, returns a pair of scanned reference and object content. - /// \param context Context - /// \param offset Offset - /// \param reference Reference to parsed object - PDFObject getObject(PDFParsingContext* context, PDFInteger offset, PDFObjectReference reference) const; - - /// Tries to restore objects from object list. This function can be used in multiple pass, because - /// for example streams, can have length defined in referred object. If such is the case, then - /// second pass is needed. Returns true, if all object were correctly read. - /// \param restoredObjects Map of restored objects - /// \param offsets Offsets, from which are objects being read - bool restoreObjects(std::map& restoredObjects, const std::vector>& offsets); - - /// Fetch object from reference table - PDFObject getObjectFromXrefTable(PDFXRefTable* xrefTable, PDFParsingContext* context, PDFObjectReference reference) const; - - /// Tries to read damaged trailer dictionary - PDFObject readDamagedTrailerDictionary() const; - - /// Attempts to read a damaged PDF document from the specified buffer (byte array). If incorrect - /// PDF is read, then empty PDF document is returned. No exception is thrown. - PDFDocument readDamagedDocumentFromBuffer(const QByteArray& buffer); - - /// This function is used, when damaged pdf document is being restored. It returns - /// array of hints, where objects should appear. It constists of pair of start offset, - /// and end offset. Start offset is always a valid index to the buffer, end offset - /// can be one index after the buffers end (it is end iterator). - /// \param buffer Buffer - std::vector> findObjectByteOffsets(const QByteArray& buffer) const; - - void progressStart(size_t stepCount, QString text); - void progressStep(); - void progressFinish(); - - /// Mutex for access to variables of this reader from more threads - /// (providing thread safety) - QMutex m_mutex; - - /// Result of document reading from the device - std::atomic m_result; - - /// In case if error occurs, it is stored here - QString m_errorMessage; - - /// Version of the scanned file - PDFVersion m_version; - - /// Callback to obtain password from the user - std::function m_getPasswordCallback; - - /// Progress indicator - PDFProgress* m_progress; - - /// Raw document data (byte array containing source data for created document) - QByteArray m_source; - - /// Security handler - PDFSecurityHandlerPointer m_securityHandler; - - /// Be permissive when reading, tolerate errors and try to fix broken document - bool m_permissive; - - /// Authorize as owner only (if owner authorization fails, then whole document - /// reading fails) - bool m_authorizeOwnerOnly; - - /// Warnings - QStringList m_warnings; -}; - -} // namespace pdf - -#endif // PDFDOCUMENTREADER_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#ifndef PDFDOCUMENTREADER_H +#define PDFDOCUMENTREADER_H + +#include "pdfglobal.h" +#include "pdfdocument.h" +#include "pdfprogress.h" +#include "pdfxreftable.h" + +#include +#include + +namespace pdf +{ +class PDFXRefTable; +class PDFParsingContext; + +/// This class is a reader of PDF document from various devices (file, io device, +/// byte buffer). This class doesn't throw exceptions, to check errors, use +/// appropriate functions. +class PDF4QTLIBSHARED_EXPORT PDFDocumentReader +{ + Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentReader) + +public: + explicit PDFDocumentReader(PDFProgress* progress, const std::function& getPasswordCallback, bool permissive, bool authorizeOwnerOnly); + + constexpr inline PDFDocumentReader(const PDFDocumentReader&) = delete; + constexpr inline PDFDocumentReader(PDFDocumentReader&&) = delete; + constexpr inline PDFDocumentReader& operator=(const PDFDocumentReader&) = delete; + constexpr inline PDFDocumentReader& operator=(PDFDocumentReader&&) = delete; + + enum class Result + { + OK, ///< Document was successfully loaded + Failed, ///< Error occured during document reading + Cancelled ///< User cancelled document reading + }; + + /// Reads a PDF document from the specified file. If file doesn't exist, + /// cannot be opened or contain invalid pdf, empty PDF file is returned. + /// No exception is thrown. + PDFDocument readFromFile(const QString& fileName); + + /// Reads a PDF document from the specified device. If device is not opened + /// for reading, then function tries it to open for reading. If it is opened, + /// but not for reading, empty PDF document is returned. This also occurs + /// when incorrect PDF is read. No exception is thrown. + PDFDocument readFromDevice(QIODevice* device); + + /// Reads a PDF document from the specified buffer (byte array). If incorrect + /// PDF is read, then empty PDF document is returned. No exception is thrown. + PDFDocument readFromBuffer(const QByteArray& buffer); + + /// Returns result code for reading document from the device + Result getReadingResult() const { return m_result; } + + /// Returns error message, if document reading was unsuccessfull + const QString& getErrorMessage() const { return m_errorMessage; } + + /// Get source data of the document + const QByteArray& getSource() const { return m_source; } + + /// Returns warning messages + const QStringList& getWarnings() const { return m_warnings; } + +private: + static constexpr const int FIND_NOT_FOUND_RESULT = -1; + + /// Resets the internal state and prepares it for new reading cycle + void reset(); + + /// Find a last string in the byte array, scan only \p limit bytes. If string + /// is not found, then FIND_NOT_FOUND_RESULT is returned, if it is found, then + /// it position from the beginning of byte array is returned. + /// \param what String to be found + /// \param byteArray Byte array to be scanned from the end + /// \param limit Scan up to this value bytes from the end + /// \returns Position of string, or FIND_NOT_FOUND_RESULT + int findFromEnd(const char* what, const QByteArray& byteArray, int limit); + + void checkFooter(const QByteArray& buffer); + void checkHeader(const QByteArray& buffer); + const PDFInteger findXrefTableOffset(const QByteArray& buffer); + Result processReferenceTableEntries(PDFXRefTable* xrefTable, const std::vector& occupiedEntries, PDFObjectStorage::PDFObjects& objects); + Result processSecurityHandler(const PDFObject& trailerDictionaryObject, const std::vector& occupiedEntries, PDFObjectStorage::PDFObjects& objects); + void processObjectStreams(PDFXRefTable* xrefTable, PDFObjectStorage::PDFObjects& objects); + + /// This function fetches object from the buffer from the specified offset. + /// Can throw exception, returns a pair of scanned reference and object content. + /// \param context Context + /// \param offset Offset + /// \param reference Reference to parsed object + PDFObject getObject(PDFParsingContext* context, PDFInteger offset, PDFObjectReference reference) const; + + /// Tries to restore objects from object list. This function can be used in multiple pass, because + /// for example streams, can have length defined in referred object. If such is the case, then + /// second pass is needed. Returns true, if all object were correctly read. + /// \param restoredObjects Map of restored objects + /// \param offsets Offsets, from which are objects being read + bool restoreObjects(std::map& restoredObjects, const std::vector>& offsets); + + /// Fetch object from reference table + PDFObject getObjectFromXrefTable(PDFXRefTable* xrefTable, PDFParsingContext* context, PDFObjectReference reference) const; + + /// Tries to read damaged trailer dictionary + PDFObject readDamagedTrailerDictionary() const; + + /// Attempts to read a damaged PDF document from the specified buffer (byte array). If incorrect + /// PDF is read, then empty PDF document is returned. No exception is thrown. + PDFDocument readDamagedDocumentFromBuffer(const QByteArray& buffer); + + /// This function is used, when damaged pdf document is being restored. It returns + /// array of hints, where objects should appear. It constists of pair of start offset, + /// and end offset. Start offset is always a valid index to the buffer, end offset + /// can be one index after the buffers end (it is end iterator). + /// \param buffer Buffer + std::vector> findObjectByteOffsets(const QByteArray& buffer) const; + + void progressStart(size_t stepCount, QString text); + void progressStep(); + void progressFinish(); + + /// Mutex for access to variables of this reader from more threads + /// (providing thread safety) + QMutex m_mutex; + + /// Result of document reading from the device + std::atomic m_result; + + /// In case if error occurs, it is stored here + QString m_errorMessage; + + /// Version of the scanned file + PDFVersion m_version; + + /// Callback to obtain password from the user + std::function m_getPasswordCallback; + + /// Progress indicator + PDFProgress* m_progress; + + /// Raw document data (byte array containing source data for created document) + QByteArray m_source; + + /// Security handler + PDFSecurityHandlerPointer m_securityHandler; + + /// Be permissive when reading, tolerate errors and try to fix broken document + bool m_permissive; + + /// Authorize as owner only (if owner authorization fails, then whole document + /// reading fails) + bool m_authorizeOwnerOnly; + + /// Warnings + QStringList m_warnings; +}; + +} // namespace pdf + +#endif // PDFDOCUMENTREADER_H diff --git a/Pdf4QtLib/sources/pdfdocumenttextflow.cpp b/Pdf4QtLib/sources/pdfdocumenttextflow.cpp index 01e2ce5..995f0d9 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextflow.cpp +++ b/Pdf4QtLib/sources/pdfdocumenttextflow.cpp @@ -1,1043 +1,1043 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfdocumenttextflow.h" -#include "pdfdocument.h" -#include "pdfstructuretree.h" -#include "pdfcompiler.h" -#include "pdfexecutionpolicy.h" -#include "pdfconstants.h" -#include "pdfcms.h" - -namespace pdf -{ - - -class PDFStructureTreeReferenceCollector : public PDFStructureTreeAbstractVisitor -{ -public: - explicit inline PDFStructureTreeReferenceCollector(std::map* mapping) : - m_mapping(mapping) - { - - } - - virtual void visitStructureTree(const PDFStructureTree* structureTree) override; - virtual void visitStructureElement(const PDFStructureElement* structureElement) override; - virtual void visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) override; - virtual void visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) override; - -private: - void addReference(const PDFStructureItem* structureObjectReference); - - std::map* m_mapping; -}; - -void PDFStructureTreeReferenceCollector::visitStructureTree(const PDFStructureTree* structureTree) -{ - addReference(structureTree); - acceptChildren(structureTree); -} - -void PDFStructureTreeReferenceCollector::visitStructureElement(const PDFStructureElement* structureElement) -{ - addReference(structureElement); - acceptChildren(structureElement); -} - -void PDFStructureTreeReferenceCollector::visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) -{ - addReference(structureMarkedContentReference); - acceptChildren(structureMarkedContentReference); -} - -void PDFStructureTreeReferenceCollector::visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) -{ - addReference(structureObjectReference); - acceptChildren(structureObjectReference); -} - -void PDFStructureTreeReferenceCollector::addReference(const PDFStructureItem* structureItem) -{ - if (structureItem->getSelfReference().isValid()) - { - (*m_mapping)[structureItem->getSelfReference()] = structureItem; - } -} - -struct PDFStructureTreeTextItem -{ - enum class Type - { - StartTag, - EndTag, - Text - }; - - PDFStructureTreeTextItem() = default; - PDFStructureTreeTextItem(Type type, const PDFStructureItem* item, QString text, PDFInteger pageIndex, QRectF boundingRect) : - type(type), item(item), text(qMove(text)), pageIndex(pageIndex), boundingRect(boundingRect) - { - - } - - static PDFStructureTreeTextItem createText(QString text, PDFInteger pageIndex, QRectF boundingRect) { return PDFStructureTreeTextItem(Type::Text, nullptr, qMove(text), pageIndex, boundingRect); } - static PDFStructureTreeTextItem createStartTag(const PDFStructureItem* item) { return PDFStructureTreeTextItem(Type::StartTag, item, QString(), -1, QRectF()); } - static PDFStructureTreeTextItem createEndTag(const PDFStructureItem* item) { return PDFStructureTreeTextItem(Type::EndTag, item, QString(), -1, QRectF()); } - - Type type = Type::Text; - const PDFStructureItem* item = nullptr; - QString text; - PDFInteger pageIndex = -1; - QRectF boundingRect; -}; - -using PDFStructureTreeTextSequence = std::vector; - -/// Text extractor for structure tree. Extracts sequences of structure items, -/// page sequences are stored in \p textSequences. They can be accessed using -/// getters. -class PDFStructureTreeTextExtractor -{ -public: - enum Option - { - None = 0x0000, - SkipArtifact = 0x0001, ///< Skip content marked as 'Artifact' - AdjustReversedText = 0x0002, ///< Adjust reversed text - CreateTreeMapping = 0x0004, ///< Create text mapping to structure tree item - BoundingBoxes = 0x0008, ///< Compute bounding boxes of the texts - }; - Q_DECLARE_FLAGS(Options, Option) - - explicit PDFStructureTreeTextExtractor(const PDFDocument* document, const PDFStructureTree* tree, Options options); - - /// Performs text extracting algorithm. Only \p pageIndices - /// pages are processed for text extraction. - /// \param pageIndices Page indices - void perform(const std::vector& pageIndices); - - /// Returns a list of errors/warnings - const QList& getErrors() const { return m_errors; } - - /// Returns a list of unmatched text - const QStringList& getUnmatchedText() const { return m_unmatchedText; } - - /// Returns text sequence for given page. If page number is invalid, - /// then empty text sequence is returned. - /// \param pageNumber Page number - const PDFStructureTreeTextSequence& getTextSequence(PDFInteger pageNumber) const; - - struct TextItem - { - QRectF boundingRect; - PDFInteger pageIndex = -1; - QString text; - }; - - using TextItems = std::vector; - - /// Returns text for given structure tree item. If structure tree item - /// is not found, then empty list is returned. This functionality - /// requires, that \p CreateTreeMapping flag is being set. - /// \param item Item - const TextItems& getText(const PDFStructureItem* item) const; - -private: - QList m_errors; - const PDFDocument* m_document; - const PDFStructureTree* m_tree; - QStringList m_unmatchedText; - std::map m_textSequences; - std::map m_textForItems; - Options m_options; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(PDFStructureTreeTextExtractor::Options) - -class PDFStructureTreeTextContentProcessor : public PDFPageContentProcessor -{ - using BaseClass = PDFPageContentProcessor; - -public: - explicit PDFStructureTreeTextContentProcessor(PDFRenderer::Features features, - const PDFPage* page, - const PDFDocument* document, - const PDFFontCache* fontCache, - const PDFCMS* cms, - const PDFOptionalContentActivity* optionalContentActivity, - QMatrix pagePointToDevicePointMatrix, - const PDFMeshQualitySettings& meshQualitySettings, - const PDFStructureTree* tree, - const std::map* mapping, - PDFStructureTreeTextExtractor::Options extractorOptions) : - BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings), - m_features(features), - m_tree(tree), - m_mapping(mapping), - m_extractorOptions(extractorOptions), - m_pageIndex(document->getCatalog()->getPageIndexFromPageReference(page->getPageReference())) - { - - } - - PDFStructureTreeTextSequence& takeSequence() { return m_textSequence; } - QStringList& takeUnmatchedTexts() { return m_unmatchedText; } - -protected: - virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override; - virtual bool isContentKindSuppressed(ContentKind kind) const override; - virtual void performOutputCharacter(const PDFTextCharacterInfo& info) override; - virtual void performMarkedContentBegin(const QByteArray& tag, const PDFObject& properties) override; - virtual void performMarkedContentEnd() override; - virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) override; - -private: - const PDFStructureItem* getStructureTreeItemFromMCID(PDFInteger mcid) const; - void finishText(); - - bool isArtifact() const; - bool isReversedText() const; - - struct MarkedContentInfo - { - QByteArray tag; - PDFInteger mcid = -1; - const PDFStructureItem* structureTreeItem = nullptr; - bool isArtifact = false; - bool isReversedText = false; - }; - - PDFRenderer::Features m_features; - const PDFStructureTree* m_tree; - const std::map* m_mapping; - std::vector m_markedContentInfoStack; - QString m_currentText; - QRectF m_currentBoundingBox; - PDFStructureTreeTextSequence m_textSequence; - QStringList m_unmatchedText; - PDFStructureTreeTextExtractor::Options m_extractorOptions; - PDFInteger m_pageIndex; -}; - -void PDFStructureTreeTextContentProcessor::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) -{ - if (!text) - { - // Jakub Melka: This should not occur - return; - } - - if (!m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::BoundingBoxes)) - { - return; - } - - Q_UNUSED(stroke); - Q_UNUSED(fill); - Q_UNUSED(fillRule); - - QMatrix matrix = getCurrentWorldMatrix(); - QPainterPath worldPath = matrix.map(path); - m_currentBoundingBox = m_currentBoundingBox.united(worldPath.controlPointRect()); -} - -void PDFStructureTreeTextContentProcessor::finishText() -{ - m_currentText = m_currentText.trimmed(); - if (!m_currentText.isEmpty() && (!m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::SkipArtifact) || !isArtifact())) - { - if (m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::AdjustReversedText) && isReversedText()) - { - QString reversed; - reversed.reserve(m_currentText.size()); - for (auto it = m_currentText.rbegin(); it != m_currentText.rend(); ++it) - { - reversed.push_back(*it); - } - m_currentText = qMove(reversed); - } - m_textSequence.emplace_back(PDFStructureTreeTextItem::createText(qMove(m_currentText), m_pageIndex, m_currentBoundingBox)); - } - m_currentText = QString(); - m_currentBoundingBox = QRectF(); -} - -bool PDFStructureTreeTextContentProcessor::isArtifact() const -{ - return std::any_of(m_markedContentInfoStack.cbegin(), m_markedContentInfoStack.cend(), [](const auto& item) { return item.isArtifact; }); -} - -bool PDFStructureTreeTextContentProcessor::isReversedText() const -{ - return std::any_of(m_markedContentInfoStack.cbegin(), m_markedContentInfoStack.cend(), [](const auto& item) { return item.isReversedText; }); -} - -void PDFStructureTreeTextContentProcessor::performMarkedContentBegin(const QByteArray& tag, const PDFObject& properties) -{ - MarkedContentInfo info; - info.tag = tag; - - if (properties.isDictionary()) - { - const PDFDictionary* dictionary = properties.getDictionary(); - PDFObject mcid = dictionary->get("MCID"); - if (mcid.isInt()) - { - // We must finish text, because we can have a sequence of text, - // then subitem, then text, and followed by another subitem. They - // can be interleaved. - finishText(); - - info.mcid = mcid.getInteger(); - info.structureTreeItem = getStructureTreeItemFromMCID(info.mcid); - info.isArtifact = tag == "Artifact"; - info.isReversedText = tag == "ReversedChars"; - - if (!info.structureTreeItem) - { - reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Structure tree item for MCID %1 not found.").arg(info.mcid)); - } - - if (info.structureTreeItem) - { - m_textSequence.emplace_back(PDFStructureTreeTextItem::createStartTag(info.structureTreeItem)); - } - } - } - - m_markedContentInfoStack.emplace_back(qMove(info)); -} - -void PDFStructureTreeTextContentProcessor::performMarkedContentEnd() -{ - MarkedContentInfo info = qMove(m_markedContentInfoStack.back()); - m_markedContentInfoStack.pop_back(); - - if (info.mcid != -1) - { - finishText(); - if (info.structureTreeItem) - { - m_textSequence.emplace_back(PDFStructureTreeTextItem::createEndTag(info.structureTreeItem)); - } - } - - // Check for text, which doesn't belong to any structure tree item - if (m_markedContentInfoStack.empty()) - { - m_currentText = m_currentText.trimmed(); - if (!m_currentText.isEmpty()) - { - m_unmatchedText << qMove(m_currentText); - } - m_currentBoundingBox = QRectF(); - } -} - -const PDFStructureItem* PDFStructureTreeTextContentProcessor::getStructureTreeItemFromMCID(PDFInteger mcid) const -{ - auto it = m_mapping->find(m_tree->getParent(getStructuralParentKey(), mcid)); - if (it != m_mapping->cend()) - { - return it->second; - } - return nullptr; -} - -bool PDFStructureTreeTextContentProcessor::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) -{ - if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent)) - { - return false; - } - - return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd); -} - -bool PDFStructureTreeTextContentProcessor::isContentKindSuppressed(ContentKind kind) const -{ - switch (kind) - { - case ContentKind::Text: - return !m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::BoundingBoxes); - - case ContentKind::Shapes: - case ContentKind::Images: - case ContentKind::Shading: - return true; - - case ContentKind::Tiling: - return false; // Tiling can have text - - default: - { - Q_ASSERT(false); - break; - } - } - - return false; -} - -void PDFStructureTreeTextContentProcessor::performOutputCharacter(const PDFTextCharacterInfo& info) -{ - if (!isContentSuppressed()) - { - if (!info.character.isNull() && info.character != QChar(QChar::SoftHyphen)) - { - m_currentText.push_back(info.character); - } - } -} - -PDFStructureTreeTextExtractor::PDFStructureTreeTextExtractor(const PDFDocument* document, const PDFStructureTree* tree, Options options) : - m_document(document), - m_tree(tree), - m_options(options) -{ - -} - -void PDFStructureTreeTextExtractor::perform(const std::vector& pageIndices) -{ - std::map mapping; - PDFStructureTreeReferenceCollector referenceCollector(&mapping); - m_tree->accept(&referenceCollector); - - PDFFontCache fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT); - - QMutex mutex; - PDFCMSGeneric cms; - PDFMeshQualitySettings mqs; - PDFOptionalContentActivity oca(m_document, OCUsage::Export, nullptr); - pdf::PDFModifiedDocument md(const_cast(m_document), &oca); - fontCache.setDocument(md); - fontCache.setCacheShrinkEnabled(nullptr, false); - - auto generateTextLayout = [&, this](PDFInteger pageIndex) - { - const PDFCatalog* catalog = m_document->getCatalog(); - if (!catalog->getPage(pageIndex)) - { - // Invalid page index - return; - } - - const PDFPage* page = catalog->getPage(pageIndex); - Q_ASSERT(page); - - PDFStructureTreeTextContentProcessor processor(PDFRenderer::IgnoreOptionalContent, page, m_document, &fontCache, &cms, &oca, QMatrix(), mqs, m_tree, &mapping, m_options); - QList errors = processor.processContents(); - - QMutexLocker lock(&mutex); - m_textSequences[pageIndex] = qMove(processor.takeSequence()); - m_unmatchedText << qMove(processor.takeUnmatchedTexts()); - m_errors.append(qMove(errors)); - }; - - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageIndices.begin(), pageIndices.end(), generateTextLayout); - - fontCache.setCacheShrinkEnabled(nullptr, true); - - if (m_options.testFlag(CreateTreeMapping)) - { - for (const auto& sequence : m_textSequences) - { - std::stack stack; - for (const PDFStructureTreeTextItem& sequenceItem : sequence.second) - { - switch (sequenceItem.type) - { - case PDFStructureTreeTextItem::Type::StartTag: - stack.push(sequenceItem.item); - break; - case PDFStructureTreeTextItem::Type::EndTag: - stack.pop(); - break; - case PDFStructureTreeTextItem::Type::Text: - if (!stack.empty()) - { - m_textForItems[stack.top()].emplace_back(TextItem{ sequenceItem.boundingRect, sequenceItem.pageIndex, sequenceItem.text }); - } - break; - } - } - } - } -} - -const PDFStructureTreeTextSequence& PDFStructureTreeTextExtractor::getTextSequence(PDFInteger pageIndex) const -{ - auto it = m_textSequences.find(pageIndex); - if (it != m_textSequences.cend()) - { - return it->second; - } - - static PDFStructureTreeTextSequence dummy; - return dummy; -} - -const PDFStructureTreeTextExtractor::TextItems& PDFStructureTreeTextExtractor::getText(const PDFStructureItem* item) const -{ - auto it = m_textForItems.find(item); - if (it != m_textForItems.cend()) - { - return it->second; - } - - static const TextItems dummy; - return dummy; -} - - -class PDFStructureTreeTextFlowCollector : public PDFStructureTreeAbstractVisitor -{ -public: - explicit PDFStructureTreeTextFlowCollector(PDFDocumentTextFlow::Items* items, const PDFStructureTreeTextExtractor* extractor) : - m_items(items), - m_extractor(extractor) - { - - } - - virtual void visitStructureTree(const PDFStructureTree* structureTree) override; - virtual void visitStructureElement(const PDFStructureElement* structureElement) override; - virtual void visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) override; - virtual void visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) override; - -private: - void markHasContent(); - - PDFDocumentTextFlow::Items* m_items; - const PDFStructureTreeTextExtractor* m_extractor; - std::vector m_hasContentStack; -}; - -void PDFStructureTreeTextFlowCollector::visitStructureTree(const PDFStructureTree* structureTree) -{ - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemStart}); - acceptChildren(structureTree); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemEnd}); -} - -void PDFStructureTreeTextFlowCollector::markHasContent() -{ - for (size_t i = 0; i < m_hasContentStack.size(); ++i) - { - m_hasContentStack[i] = true; - } -} - -void PDFStructureTreeTextFlowCollector::visitStructureElement(const PDFStructureElement* structureElement) -{ - size_t index = m_items->size(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemStart}); - - // Mark stack so we can delete unused items - m_hasContentStack.push_back(false); - - QString title = structureElement->getText(PDFStructureElement::Title); - QString language = structureElement->getText(PDFStructureElement::Language); - QString alternativeDescription = structureElement->getText(PDFStructureElement::AlternativeDescription); - QString expandedForm = structureElement->getText(PDFStructureElement::ExpandedForm); - QString actualText = structureElement->getText(PDFStructureElement::ActualText); - QString phoneme = structureElement->getText(PDFStructureElement::Phoneme); - - if (!title.isEmpty()) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureTitle}); - } - - if (!language.isEmpty()) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, language, PDFDocumentTextFlow::StructureLanguage }); - } - - if (!alternativeDescription.isEmpty()) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, alternativeDescription, PDFDocumentTextFlow::StructureAlternativeDescription }); - } - - if (!expandedForm.isEmpty()) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, expandedForm, PDFDocumentTextFlow::StructureExpandedForm }); - } - - if (!actualText.isEmpty()) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, actualText, PDFDocumentTextFlow::StructureActualText }); - } - - if (!phoneme.isEmpty()) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, phoneme, PDFDocumentTextFlow::StructurePhoneme }); - } - - for (const auto& textItem : m_extractor->getText(structureElement)) - { - markHasContent(); - m_items->push_back(PDFDocumentTextFlow::Item{ textItem.boundingRect, textItem.pageIndex, textItem.text, PDFDocumentTextFlow::Text }); - } - - acceptChildren(structureElement); - - const bool hasContent = m_hasContentStack.back(); - m_hasContentStack.pop_back(); - - m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemEnd }); - - if (!hasContent) - { - // Delete unused content - m_items->erase(std::next(m_items->begin(), index), m_items->end()); - } -} - -void PDFStructureTreeTextFlowCollector::visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) -{ - acceptChildren(structureMarkedContentReference); -} - -void PDFStructureTreeTextFlowCollector::visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) -{ - acceptChildren(structureObjectReference); -} - -PDFDocumentTextFlow PDFDocumentTextFlowFactory::create(const PDFDocument* document, const std::vector& pageIndices, Algorithm algorithm) -{ - PDFDocumentTextFlow result; - PDFStructureTree structureTree; - - const PDFCatalog* catalog = document->getCatalog(); - if (algorithm != Algorithm::Layout) - { - structureTree = PDFStructureTree::parse(&document->getStorage(), catalog->getStructureTreeRoot()); - } - - if (algorithm == Algorithm::Auto) - { - // Determine algorithm - if (catalog->isLogicalStructureMarked() && structureTree.isValid()) - { - algorithm = Algorithm::Structure; - } - else - { - algorithm = Algorithm::Layout; - } - } - - Q_ASSERT(algorithm != Algorithm::Auto); - - // Perform algorithm to retrieve document text - switch (algorithm) - { - case Algorithm::Layout: - { - PDFFontCache fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT); - - std::map items; - - QMutex mutex; - PDFCMSGeneric cms; - PDFMeshQualitySettings mqs; - PDFOptionalContentActivity oca(document, OCUsage::Export, nullptr); - pdf::PDFModifiedDocument md(const_cast(document), &oca); - fontCache.setDocument(md); - fontCache.setCacheShrinkEnabled(nullptr, false); - - auto generateTextLayout = [this, &items, &mutex, &fontCache, &cms, &mqs, &oca, document, catalog](PDFInteger pageIndex) - { - if (!catalog->getPage(pageIndex)) - { - // Invalid page index - return; - } - - const PDFPage* page = catalog->getPage(pageIndex); - Q_ASSERT(page); - - PDFTextLayoutGenerator generator(PDFRenderer::IgnoreOptionalContent, page, document, &fontCache, &cms, &oca, QMatrix(), mqs); - QList errors = generator.processContents(); - PDFTextLayout textLayout = generator.createTextLayout(); - PDFTextFlows textFlows = PDFTextFlow::createTextFlows(textLayout, PDFTextFlow::FlowFlags(PDFTextFlow::SeparateBlocks) | PDFTextFlow::RemoveSoftHyphen, pageIndex); - - PDFDocumentTextFlow::Items flowItems; - flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, PDFTranslationContext::tr("Page %1").arg(pageIndex + 1), PDFDocumentTextFlow::PageStart }); - for (const PDFTextFlow& textFlow : textFlows) - { - flowItems.emplace_back(PDFDocumentTextFlow::Item{ textFlow.getBoundingBox(), pageIndex, textFlow.getText(), PDFDocumentTextFlow::Text }); - } - flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, QString(), PDFDocumentTextFlow::PageEnd }); - - QMutexLocker lock(&mutex); - items[pageIndex] = qMove(flowItems); - m_errors.append(qMove(errors)); - }; - - PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageIndices.begin(), pageIndices.end(), generateTextLayout); - - fontCache.setCacheShrinkEnabled(nullptr, true); - - PDFDocumentTextFlow::Items flowItems; - for (const auto& item : items) - { - flowItems.insert(flowItems.end(), std::make_move_iterator(item.second.begin()), std::make_move_iterator(item.second.end())); - } - - result = PDFDocumentTextFlow(qMove(flowItems)); - break; - } - - case Algorithm::Structure: - { - if (!structureTree.isValid()) - { - m_errors << PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Valid tagged document required.")); - break; - } - - PDFStructureTreeTextExtractor::Options options = PDFStructureTreeTextExtractor::SkipArtifact | PDFStructureTreeTextExtractor::AdjustReversedText | PDFStructureTreeTextExtractor::CreateTreeMapping; - options.setFlag(PDFStructureTreeTextExtractor::BoundingBoxes, m_calculateBoundingBoxes); - PDFStructureTreeTextExtractor extractor(document, &structureTree, options); - extractor.perform(pageIndices); - - PDFDocumentTextFlow::Items flowItems; - PDFStructureTreeTextFlowCollector collector(&flowItems, &extractor); - structureTree.accept(&collector); - - result = PDFDocumentTextFlow(qMove(flowItems)); - m_errors.append(extractor.getErrors()); - break; - } - - case Algorithm::Content: - { - PDFStructureTreeTextExtractor::Options options = PDFStructureTreeTextExtractor::None; - options.setFlag(PDFStructureTreeTextExtractor::BoundingBoxes, m_calculateBoundingBoxes); - PDFStructureTreeTextExtractor extractor(document, &structureTree, options); - extractor.perform(pageIndices); - - PDFDocumentTextFlow::Items flowItems; - for (PDFInteger pageIndex : pageIndices) - { - flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, PDFTranslationContext::tr("Page %1").arg(pageIndex + 1), PDFDocumentTextFlow::PageStart }); - for (const PDFStructureTreeTextItem& sequenceItem : extractor.getTextSequence(pageIndex)) - { - if (sequenceItem.type == PDFStructureTreeTextItem::Type::Text) - { - flowItems.emplace_back(PDFDocumentTextFlow::Item{ sequenceItem.boundingRect, pageIndex, sequenceItem.text, PDFDocumentTextFlow::Text }); - } - } - flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, QString(), PDFDocumentTextFlow::PageEnd }); - } - - result = PDFDocumentTextFlow(qMove(flowItems)); - m_errors.append(extractor.getErrors()); - break; - } - - default: - Q_ASSERT(false); - break; - } - - return result; -} - -PDFDocumentTextFlow PDFDocumentTextFlowFactory::create(const PDFDocument* document, Algorithm algorithm) -{ - std::vector pageIndices; - pageIndices.resize(document->getCatalog()->getPageCount(), 0); - std::iota(pageIndices.begin(), pageIndices.end(), 0); - - return create(document, pageIndices, algorithm); -} - -void PDFDocumentTextFlowFactory::setCalculateBoundingBoxes(bool calculateBoundingBoxes) -{ - m_calculateBoundingBoxes = calculateBoundingBoxes; -} - -void PDFDocumentTextFlowEditor::setTextFlow(PDFDocumentTextFlow textFlow) -{ - m_originalTextFlow = std::move(textFlow); - createEditedFromOriginalTextFlow(); -} - -void PDFDocumentTextFlowEditor::setSelectionActive(bool active) -{ - for (auto& item : m_editedTextFlow) - { - if (item.editedItemFlags.testFlag(Selected)) - { - item.editedItemFlags.setFlag(Removed, !active); - } - } -} - -void PDFDocumentTextFlowEditor::select(size_t index, bool select) -{ - getEditedItem(index)->editedItemFlags.setFlag(Selected, select); -} - -void PDFDocumentTextFlowEditor::deselect() -{ - for (auto& item : m_editedTextFlow) - { - item.editedItemFlags.setFlag(Selected, false); - } -} - -void PDFDocumentTextFlowEditor::removeItem(size_t index) -{ - getEditedItem(index)->editedItemFlags.setFlag(Removed, true); -} - -void PDFDocumentTextFlowEditor::addItem(size_t index) -{ - getEditedItem(index)->editedItemFlags.setFlag(Removed, false); -} - -void PDFDocumentTextFlowEditor::clear() -{ - m_originalTextFlow = PDFDocumentTextFlow(); - m_editedTextFlow.clear(); - m_pageIndicesMapping.clear(); -} - -void PDFDocumentTextFlowEditor::setText(const QString& text, size_t index) -{ - EditedItem* item = getEditedItem(index); - item->text = text; - updateModifiedFlag(index); -} - -bool PDFDocumentTextFlowEditor::isSelectionEmpty() const -{ - return std::all_of(m_editedTextFlow.cbegin(), m_editedTextFlow.cend(), [](const auto& item) { return !item.editedItemFlags.testFlag(Selected); }); -} - -bool PDFDocumentTextFlowEditor::isSelectionModified() const -{ - return std::any_of(m_editedTextFlow.cbegin(), m_editedTextFlow.cend(), [](const auto& item) { return item.editedItemFlags.testFlag(Selected) && item.editedItemFlags.testFlag(Modified); }); -} - -void PDFDocumentTextFlowEditor::selectByRectangle(QRectF rectangle) -{ - for (auto& item : m_editedTextFlow) - { - const QRectF& boundingRectangle = item.boundingRect; - - if (boundingRectangle.isEmpty()) - { - item.editedItemFlags.setFlag(Selected, false); - continue; - } - - const bool isContained = rectangle.contains(boundingRectangle); - item.editedItemFlags.setFlag(Selected, isContained); - } -} - -void PDFDocumentTextFlowEditor::selectByContainedText(QString text) -{ - for (auto& item : m_editedTextFlow) - { - const bool isContained = item.text.contains(text, Qt::CaseSensitive); - item.editedItemFlags.setFlag(Selected, isContained); - } -} - -void PDFDocumentTextFlowEditor::selectByRegularExpression(const QRegularExpression& expression) -{ - for (auto& item : m_editedTextFlow) - { - QRegularExpressionMatch match = expression.match(item.text, 0, QRegularExpression::NormalMatch, QRegularExpression::NoMatchOption); - const bool hasMatch = match.hasMatch(); - item.editedItemFlags.setFlag(Selected, hasMatch); - } -} - -void PDFDocumentTextFlowEditor::selectByPageIndices(const pdf::PDFClosedIntervalSet& indices) -{ - std::vector pageIndices = indices.unfold(); - std::sort(pageIndices.begin(), pageIndices.end()); - - for (auto& item : m_editedTextFlow) - { - const bool isPageValid = std::binary_search(pageIndices.begin(), pageIndices.end(), item.pageIndex + 1); - item.editedItemFlags.setFlag(Selected, isPageValid); - } -} - -void PDFDocumentTextFlowEditor::restoreOriginalTexts() -{ - for (auto& item : m_editedTextFlow) - { - if (item.editedItemFlags.testFlag(Selected)) - { - item.text = m_originalTextFlow.getItem(item.originalIndex)->text; - item.editedItemFlags.setFlag(Modified, false); - } - } -} - -void PDFDocumentTextFlowEditor::moveSelectionUp() -{ - size_t originalSize = m_editedTextFlow.size(); - size_t firstSelectedIndex = originalSize; - - EditedItems selectedItems; - for (auto it = m_editedTextFlow.begin(); it != m_editedTextFlow.end();) - { - if (it->editedItemFlags.testFlag(Selected)) - { - if (firstSelectedIndex == originalSize) - { - firstSelectedIndex = std::distance(m_editedTextFlow.begin(), it); - } - - selectedItems.emplace_back(std::move(*it)); - it = m_editedTextFlow.erase(it); - } - else - { - ++it; - } - } - - if (firstSelectedIndex > 0) - { - --firstSelectedIndex; - } - - m_editedTextFlow.insert(std::next(m_editedTextFlow.begin(), firstSelectedIndex), std::make_move_iterator(selectedItems.begin()), std::make_move_iterator(selectedItems.end())); -} - -void PDFDocumentTextFlowEditor::moveSelectionDown() -{ - size_t originalSize = m_editedTextFlow.size(); - size_t lastSelectedIndex = originalSize; - - EditedItems selectedItems; - for (auto it = m_editedTextFlow.begin(); it != m_editedTextFlow.end();) - { - if (it->editedItemFlags.testFlag(Selected)) - { - lastSelectedIndex = std::distance(m_editedTextFlow.begin(), it); - selectedItems.emplace_back(std::move(*it)); - it = m_editedTextFlow.erase(it); - } - else - { - ++it; - } - } - - if (lastSelectedIndex < m_editedTextFlow.size()) - { - ++lastSelectedIndex; - } - - m_editedTextFlow.insert(std::next(m_editedTextFlow.begin(), lastSelectedIndex), std::make_move_iterator(selectedItems.begin()), std::make_move_iterator(selectedItems.end())); -} - -PDFDocumentTextFlowEditor::PageIndicesMappingRange PDFDocumentTextFlowEditor::getItemsForPageIndex(PDFInteger pageIndex) const -{ - auto comparator = [](const auto& l, const auto& r) - { - return l.first < r.first; - }; - return std::equal_range(m_pageIndicesMapping.cbegin(), m_pageIndicesMapping.cend(), std::make_pair(pageIndex, size_t(0)), comparator); -} - -PDFDocumentTextFlow PDFDocumentTextFlowEditor::createEditedTextFlow() const -{ - PDFDocumentTextFlow::Items items; - items.reserve(getItemCount()); - - const size_t size = getItemCount(); - for (size_t i = 0; i < size; ++i) - { - if (isRemoved(i)) - { - continue; - } - - PDFDocumentTextFlow::Item item = *getOriginalItem(i); - item.text = getText(i); - items.emplace_back(std::move(item)); - } - - return PDFDocumentTextFlow(std::move(items)); -} - -void PDFDocumentTextFlowEditor::createPageMapping() -{ - m_pageIndicesMapping.clear(); - m_pageIndicesMapping.reserve(m_editedTextFlow.size()); - - for (size_t i = 0; i < m_editedTextFlow.size(); ++i) - { - m_pageIndicesMapping.emplace_back(m_editedTextFlow[i].pageIndex, i); - } - - std::sort(m_pageIndicesMapping.begin(), m_pageIndicesMapping.end()); -} - -void PDFDocumentTextFlowEditor::createEditedFromOriginalTextFlow() -{ - const size_t count = m_originalTextFlow.getSize(); - m_editedTextFlow.reserve(count); - - for (size_t i = 0; i < count; ++i) - { - const PDFDocumentTextFlow::Item* originalItem = getOriginalItem(i); - - if (originalItem->text.trimmed().isEmpty()) - { - continue; - } - - EditedItem editedItem; - static_cast(editedItem) = *originalItem; - editedItem.originalIndex = i; - editedItem.editedItemFlags = originalItem->isText() ? None : Removed; - m_editedTextFlow.emplace_back(std::move(editedItem)); - } - - createPageMapping(); -} - -void PDFDocumentTextFlowEditor::updateModifiedFlag(size_t index) -{ - const bool isModified = getText(index) != getOriginalItem(index)->text; - - EditedItem* item = getEditedItem(index); - item->editedItemFlags.setFlag(Modified, isModified); -} - -} // namespace pdf +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfdocumenttextflow.h" +#include "pdfdocument.h" +#include "pdfstructuretree.h" +#include "pdfcompiler.h" +#include "pdfexecutionpolicy.h" +#include "pdfconstants.h" +#include "pdfcms.h" + +namespace pdf +{ + + +class PDFStructureTreeReferenceCollector : public PDFStructureTreeAbstractVisitor +{ +public: + explicit inline PDFStructureTreeReferenceCollector(std::map* mapping) : + m_mapping(mapping) + { + + } + + virtual void visitStructureTree(const PDFStructureTree* structureTree) override; + virtual void visitStructureElement(const PDFStructureElement* structureElement) override; + virtual void visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) override; + virtual void visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) override; + +private: + void addReference(const PDFStructureItem* structureObjectReference); + + std::map* m_mapping; +}; + +void PDFStructureTreeReferenceCollector::visitStructureTree(const PDFStructureTree* structureTree) +{ + addReference(structureTree); + acceptChildren(structureTree); +} + +void PDFStructureTreeReferenceCollector::visitStructureElement(const PDFStructureElement* structureElement) +{ + addReference(structureElement); + acceptChildren(structureElement); +} + +void PDFStructureTreeReferenceCollector::visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) +{ + addReference(structureMarkedContentReference); + acceptChildren(structureMarkedContentReference); +} + +void PDFStructureTreeReferenceCollector::visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) +{ + addReference(structureObjectReference); + acceptChildren(structureObjectReference); +} + +void PDFStructureTreeReferenceCollector::addReference(const PDFStructureItem* structureItem) +{ + if (structureItem->getSelfReference().isValid()) + { + (*m_mapping)[structureItem->getSelfReference()] = structureItem; + } +} + +struct PDFStructureTreeTextItem +{ + enum class Type + { + StartTag, + EndTag, + Text + }; + + PDFStructureTreeTextItem() = default; + PDFStructureTreeTextItem(Type type, const PDFStructureItem* item, QString text, PDFInteger pageIndex, QRectF boundingRect) : + type(type), item(item), text(qMove(text)), pageIndex(pageIndex), boundingRect(boundingRect) + { + + } + + static PDFStructureTreeTextItem createText(QString text, PDFInteger pageIndex, QRectF boundingRect) { return PDFStructureTreeTextItem(Type::Text, nullptr, qMove(text), pageIndex, boundingRect); } + static PDFStructureTreeTextItem createStartTag(const PDFStructureItem* item) { return PDFStructureTreeTextItem(Type::StartTag, item, QString(), -1, QRectF()); } + static PDFStructureTreeTextItem createEndTag(const PDFStructureItem* item) { return PDFStructureTreeTextItem(Type::EndTag, item, QString(), -1, QRectF()); } + + Type type = Type::Text; + const PDFStructureItem* item = nullptr; + QString text; + PDFInteger pageIndex = -1; + QRectF boundingRect; +}; + +using PDFStructureTreeTextSequence = std::vector; + +/// Text extractor for structure tree. Extracts sequences of structure items, +/// page sequences are stored in \p textSequences. They can be accessed using +/// getters. +class PDFStructureTreeTextExtractor +{ +public: + enum Option + { + None = 0x0000, + SkipArtifact = 0x0001, ///< Skip content marked as 'Artifact' + AdjustReversedText = 0x0002, ///< Adjust reversed text + CreateTreeMapping = 0x0004, ///< Create text mapping to structure tree item + BoundingBoxes = 0x0008, ///< Compute bounding boxes of the texts + }; + Q_DECLARE_FLAGS(Options, Option) + + explicit PDFStructureTreeTextExtractor(const PDFDocument* document, const PDFStructureTree* tree, Options options); + + /// Performs text extracting algorithm. Only \p pageIndices + /// pages are processed for text extraction. + /// \param pageIndices Page indices + void perform(const std::vector& pageIndices); + + /// Returns a list of errors/warnings + const QList& getErrors() const { return m_errors; } + + /// Returns a list of unmatched text + const QStringList& getUnmatchedText() const { return m_unmatchedText; } + + /// Returns text sequence for given page. If page number is invalid, + /// then empty text sequence is returned. + /// \param pageNumber Page number + const PDFStructureTreeTextSequence& getTextSequence(PDFInteger pageNumber) const; + + struct TextItem + { + QRectF boundingRect; + PDFInteger pageIndex = -1; + QString text; + }; + + using TextItems = std::vector; + + /// Returns text for given structure tree item. If structure tree item + /// is not found, then empty list is returned. This functionality + /// requires, that \p CreateTreeMapping flag is being set. + /// \param item Item + const TextItems& getText(const PDFStructureItem* item) const; + +private: + QList m_errors; + const PDFDocument* m_document; + const PDFStructureTree* m_tree; + QStringList m_unmatchedText; + std::map m_textSequences; + std::map m_textForItems; + Options m_options; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(PDFStructureTreeTextExtractor::Options) + +class PDFStructureTreeTextContentProcessor : public PDFPageContentProcessor +{ + using BaseClass = PDFPageContentProcessor; + +public: + explicit PDFStructureTreeTextContentProcessor(PDFRenderer::Features features, + const PDFPage* page, + const PDFDocument* document, + const PDFFontCache* fontCache, + const PDFCMS* cms, + const PDFOptionalContentActivity* optionalContentActivity, + QMatrix pagePointToDevicePointMatrix, + const PDFMeshQualitySettings& meshQualitySettings, + const PDFStructureTree* tree, + const std::map* mapping, + PDFStructureTreeTextExtractor::Options extractorOptions) : + BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings), + m_features(features), + m_tree(tree), + m_mapping(mapping), + m_extractorOptions(extractorOptions), + m_pageIndex(document->getCatalog()->getPageIndexFromPageReference(page->getPageReference())) + { + + } + + PDFStructureTreeTextSequence& takeSequence() { return m_textSequence; } + QStringList& takeUnmatchedTexts() { return m_unmatchedText; } + +protected: + virtual bool isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) override; + virtual bool isContentKindSuppressed(ContentKind kind) const override; + virtual void performOutputCharacter(const PDFTextCharacterInfo& info) override; + virtual void performMarkedContentBegin(const QByteArray& tag, const PDFObject& properties) override; + virtual void performMarkedContentEnd() override; + virtual void performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) override; + +private: + const PDFStructureItem* getStructureTreeItemFromMCID(PDFInteger mcid) const; + void finishText(); + + bool isArtifact() const; + bool isReversedText() const; + + struct MarkedContentInfo + { + QByteArray tag; + PDFInteger mcid = -1; + const PDFStructureItem* structureTreeItem = nullptr; + bool isArtifact = false; + bool isReversedText = false; + }; + + PDFRenderer::Features m_features; + const PDFStructureTree* m_tree; + const std::map* m_mapping; + std::vector m_markedContentInfoStack; + QString m_currentText; + QRectF m_currentBoundingBox; + PDFStructureTreeTextSequence m_textSequence; + QStringList m_unmatchedText; + PDFStructureTreeTextExtractor::Options m_extractorOptions; + PDFInteger m_pageIndex; +}; + +void PDFStructureTreeTextContentProcessor::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule) +{ + if (!text) + { + // Jakub Melka: This should not occur + return; + } + + if (!m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::BoundingBoxes)) + { + return; + } + + Q_UNUSED(stroke); + Q_UNUSED(fill); + Q_UNUSED(fillRule); + + QMatrix matrix = getCurrentWorldMatrix(); + QPainterPath worldPath = matrix.map(path); + m_currentBoundingBox = m_currentBoundingBox.united(worldPath.controlPointRect()); +} + +void PDFStructureTreeTextContentProcessor::finishText() +{ + m_currentText = m_currentText.trimmed(); + if (!m_currentText.isEmpty() && (!m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::SkipArtifact) || !isArtifact())) + { + if (m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::AdjustReversedText) && isReversedText()) + { + QString reversed; + reversed.reserve(m_currentText.size()); + for (auto it = m_currentText.rbegin(); it != m_currentText.rend(); ++it) + { + reversed.push_back(*it); + } + m_currentText = qMove(reversed); + } + m_textSequence.emplace_back(PDFStructureTreeTextItem::createText(qMove(m_currentText), m_pageIndex, m_currentBoundingBox)); + } + m_currentText = QString(); + m_currentBoundingBox = QRectF(); +} + +bool PDFStructureTreeTextContentProcessor::isArtifact() const +{ + return std::any_of(m_markedContentInfoStack.cbegin(), m_markedContentInfoStack.cend(), [](const auto& item) { return item.isArtifact; }); +} + +bool PDFStructureTreeTextContentProcessor::isReversedText() const +{ + return std::any_of(m_markedContentInfoStack.cbegin(), m_markedContentInfoStack.cend(), [](const auto& item) { return item.isReversedText; }); +} + +void PDFStructureTreeTextContentProcessor::performMarkedContentBegin(const QByteArray& tag, const PDFObject& properties) +{ + MarkedContentInfo info; + info.tag = tag; + + if (properties.isDictionary()) + { + const PDFDictionary* dictionary = properties.getDictionary(); + PDFObject mcid = dictionary->get("MCID"); + if (mcid.isInt()) + { + // We must finish text, because we can have a sequence of text, + // then subitem, then text, and followed by another subitem. They + // can be interleaved. + finishText(); + + info.mcid = mcid.getInteger(); + info.structureTreeItem = getStructureTreeItemFromMCID(info.mcid); + info.isArtifact = tag == "Artifact"; + info.isReversedText = tag == "ReversedChars"; + + if (!info.structureTreeItem) + { + reportRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Structure tree item for MCID %1 not found.").arg(info.mcid)); + } + + if (info.structureTreeItem) + { + m_textSequence.emplace_back(PDFStructureTreeTextItem::createStartTag(info.structureTreeItem)); + } + } + } + + m_markedContentInfoStack.emplace_back(qMove(info)); +} + +void PDFStructureTreeTextContentProcessor::performMarkedContentEnd() +{ + MarkedContentInfo info = qMove(m_markedContentInfoStack.back()); + m_markedContentInfoStack.pop_back(); + + if (info.mcid != -1) + { + finishText(); + if (info.structureTreeItem) + { + m_textSequence.emplace_back(PDFStructureTreeTextItem::createEndTag(info.structureTreeItem)); + } + } + + // Check for text, which doesn't belong to any structure tree item + if (m_markedContentInfoStack.empty()) + { + m_currentText = m_currentText.trimmed(); + if (!m_currentText.isEmpty()) + { + m_unmatchedText << qMove(m_currentText); + } + m_currentBoundingBox = QRectF(); + } +} + +const PDFStructureItem* PDFStructureTreeTextContentProcessor::getStructureTreeItemFromMCID(PDFInteger mcid) const +{ + auto it = m_mapping->find(m_tree->getParent(getStructuralParentKey(), mcid)); + if (it != m_mapping->cend()) + { + return it->second; + } + return nullptr; +} + +bool PDFStructureTreeTextContentProcessor::isContentSuppressedByOC(PDFObjectReference ocgOrOcmd) +{ + if (m_features.testFlag(PDFRenderer::IgnoreOptionalContent)) + { + return false; + } + + return PDFPageContentProcessor::isContentSuppressedByOC(ocgOrOcmd); +} + +bool PDFStructureTreeTextContentProcessor::isContentKindSuppressed(ContentKind kind) const +{ + switch (kind) + { + case ContentKind::Text: + return !m_extractorOptions.testFlag(PDFStructureTreeTextExtractor::BoundingBoxes); + + case ContentKind::Shapes: + case ContentKind::Images: + case ContentKind::Shading: + return true; + + case ContentKind::Tiling: + return false; // Tiling can have text + + default: + { + Q_ASSERT(false); + break; + } + } + + return false; +} + +void PDFStructureTreeTextContentProcessor::performOutputCharacter(const PDFTextCharacterInfo& info) +{ + if (!isContentSuppressed()) + { + if (!info.character.isNull() && info.character != QChar(QChar::SoftHyphen)) + { + m_currentText.push_back(info.character); + } + } +} + +PDFStructureTreeTextExtractor::PDFStructureTreeTextExtractor(const PDFDocument* document, const PDFStructureTree* tree, Options options) : + m_document(document), + m_tree(tree), + m_options(options) +{ + +} + +void PDFStructureTreeTextExtractor::perform(const std::vector& pageIndices) +{ + std::map mapping; + PDFStructureTreeReferenceCollector referenceCollector(&mapping); + m_tree->accept(&referenceCollector); + + PDFFontCache fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT); + + QMutex mutex; + PDFCMSGeneric cms; + PDFMeshQualitySettings mqs; + PDFOptionalContentActivity oca(m_document, OCUsage::Export, nullptr); + pdf::PDFModifiedDocument md(const_cast(m_document), &oca); + fontCache.setDocument(md); + fontCache.setCacheShrinkEnabled(nullptr, false); + + auto generateTextLayout = [&, this](PDFInteger pageIndex) + { + const PDFCatalog* catalog = m_document->getCatalog(); + if (!catalog->getPage(pageIndex)) + { + // Invalid page index + return; + } + + const PDFPage* page = catalog->getPage(pageIndex); + Q_ASSERT(page); + + PDFStructureTreeTextContentProcessor processor(PDFRenderer::IgnoreOptionalContent, page, m_document, &fontCache, &cms, &oca, QMatrix(), mqs, m_tree, &mapping, m_options); + QList errors = processor.processContents(); + + QMutexLocker lock(&mutex); + m_textSequences[pageIndex] = qMove(processor.takeSequence()); + m_unmatchedText << qMove(processor.takeUnmatchedTexts()); + m_errors.append(qMove(errors)); + }; + + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageIndices.begin(), pageIndices.end(), generateTextLayout); + + fontCache.setCacheShrinkEnabled(nullptr, true); + + if (m_options.testFlag(CreateTreeMapping)) + { + for (const auto& sequence : m_textSequences) + { + std::stack stack; + for (const PDFStructureTreeTextItem& sequenceItem : sequence.second) + { + switch (sequenceItem.type) + { + case PDFStructureTreeTextItem::Type::StartTag: + stack.push(sequenceItem.item); + break; + case PDFStructureTreeTextItem::Type::EndTag: + stack.pop(); + break; + case PDFStructureTreeTextItem::Type::Text: + if (!stack.empty()) + { + m_textForItems[stack.top()].emplace_back(TextItem{ sequenceItem.boundingRect, sequenceItem.pageIndex, sequenceItem.text }); + } + break; + } + } + } + } +} + +const PDFStructureTreeTextSequence& PDFStructureTreeTextExtractor::getTextSequence(PDFInteger pageIndex) const +{ + auto it = m_textSequences.find(pageIndex); + if (it != m_textSequences.cend()) + { + return it->second; + } + + static PDFStructureTreeTextSequence dummy; + return dummy; +} + +const PDFStructureTreeTextExtractor::TextItems& PDFStructureTreeTextExtractor::getText(const PDFStructureItem* item) const +{ + auto it = m_textForItems.find(item); + if (it != m_textForItems.cend()) + { + return it->second; + } + + static const TextItems dummy; + return dummy; +} + + +class PDFStructureTreeTextFlowCollector : public PDFStructureTreeAbstractVisitor +{ +public: + explicit PDFStructureTreeTextFlowCollector(PDFDocumentTextFlow::Items* items, const PDFStructureTreeTextExtractor* extractor) : + m_items(items), + m_extractor(extractor) + { + + } + + virtual void visitStructureTree(const PDFStructureTree* structureTree) override; + virtual void visitStructureElement(const PDFStructureElement* structureElement) override; + virtual void visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) override; + virtual void visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) override; + +private: + void markHasContent(); + + PDFDocumentTextFlow::Items* m_items; + const PDFStructureTreeTextExtractor* m_extractor; + std::vector m_hasContentStack; +}; + +void PDFStructureTreeTextFlowCollector::visitStructureTree(const PDFStructureTree* structureTree) +{ + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemStart}); + acceptChildren(structureTree); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemEnd}); +} + +void PDFStructureTreeTextFlowCollector::markHasContent() +{ + for (size_t i = 0; i < m_hasContentStack.size(); ++i) + { + m_hasContentStack[i] = true; + } +} + +void PDFStructureTreeTextFlowCollector::visitStructureElement(const PDFStructureElement* structureElement) +{ + size_t index = m_items->size(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemStart}); + + // Mark stack so we can delete unused items + m_hasContentStack.push_back(false); + + QString title = structureElement->getText(PDFStructureElement::Title); + QString language = structureElement->getText(PDFStructureElement::Language); + QString alternativeDescription = structureElement->getText(PDFStructureElement::AlternativeDescription); + QString expandedForm = structureElement->getText(PDFStructureElement::ExpandedForm); + QString actualText = structureElement->getText(PDFStructureElement::ActualText); + QString phoneme = structureElement->getText(PDFStructureElement::Phoneme); + + if (!title.isEmpty()) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureTitle}); + } + + if (!language.isEmpty()) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, language, PDFDocumentTextFlow::StructureLanguage }); + } + + if (!alternativeDescription.isEmpty()) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, alternativeDescription, PDFDocumentTextFlow::StructureAlternativeDescription }); + } + + if (!expandedForm.isEmpty()) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, expandedForm, PDFDocumentTextFlow::StructureExpandedForm }); + } + + if (!actualText.isEmpty()) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, actualText, PDFDocumentTextFlow::StructureActualText }); + } + + if (!phoneme.isEmpty()) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, phoneme, PDFDocumentTextFlow::StructurePhoneme }); + } + + for (const auto& textItem : m_extractor->getText(structureElement)) + { + markHasContent(); + m_items->push_back(PDFDocumentTextFlow::Item{ textItem.boundingRect, textItem.pageIndex, textItem.text, PDFDocumentTextFlow::Text }); + } + + acceptChildren(structureElement); + + const bool hasContent = m_hasContentStack.back(); + m_hasContentStack.pop_back(); + + m_items->push_back(PDFDocumentTextFlow::Item{ QRectF(), -1, QString(), PDFDocumentTextFlow::StructureItemEnd }); + + if (!hasContent) + { + // Delete unused content + m_items->erase(std::next(m_items->begin(), index), m_items->end()); + } +} + +void PDFStructureTreeTextFlowCollector::visitStructureMarkedContentReference(const PDFStructureMarkedContentReference* structureMarkedContentReference) +{ + acceptChildren(structureMarkedContentReference); +} + +void PDFStructureTreeTextFlowCollector::visitStructureObjectReference(const PDFStructureObjectReference* structureObjectReference) +{ + acceptChildren(structureObjectReference); +} + +PDFDocumentTextFlow PDFDocumentTextFlowFactory::create(const PDFDocument* document, const std::vector& pageIndices, Algorithm algorithm) +{ + PDFDocumentTextFlow result; + PDFStructureTree structureTree; + + const PDFCatalog* catalog = document->getCatalog(); + if (algorithm != Algorithm::Layout) + { + structureTree = PDFStructureTree::parse(&document->getStorage(), catalog->getStructureTreeRoot()); + } + + if (algorithm == Algorithm::Auto) + { + // Determine algorithm + if (catalog->isLogicalStructureMarked() && structureTree.isValid()) + { + algorithm = Algorithm::Structure; + } + else + { + algorithm = Algorithm::Layout; + } + } + + Q_ASSERT(algorithm != Algorithm::Auto); + + // Perform algorithm to retrieve document text + switch (algorithm) + { + case Algorithm::Layout: + { + PDFFontCache fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT); + + std::map items; + + QMutex mutex; + PDFCMSGeneric cms; + PDFMeshQualitySettings mqs; + PDFOptionalContentActivity oca(document, OCUsage::Export, nullptr); + pdf::PDFModifiedDocument md(const_cast(document), &oca); + fontCache.setDocument(md); + fontCache.setCacheShrinkEnabled(nullptr, false); + + auto generateTextLayout = [this, &items, &mutex, &fontCache, &cms, &mqs, &oca, document, catalog](PDFInteger pageIndex) + { + if (!catalog->getPage(pageIndex)) + { + // Invalid page index + return; + } + + const PDFPage* page = catalog->getPage(pageIndex); + Q_ASSERT(page); + + PDFTextLayoutGenerator generator(PDFRenderer::IgnoreOptionalContent, page, document, &fontCache, &cms, &oca, QMatrix(), mqs); + QList errors = generator.processContents(); + PDFTextLayout textLayout = generator.createTextLayout(); + PDFTextFlows textFlows = PDFTextFlow::createTextFlows(textLayout, PDFTextFlow::FlowFlags(PDFTextFlow::SeparateBlocks) | PDFTextFlow::RemoveSoftHyphen, pageIndex); + + PDFDocumentTextFlow::Items flowItems; + flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, PDFTranslationContext::tr("Page %1").arg(pageIndex + 1), PDFDocumentTextFlow::PageStart }); + for (const PDFTextFlow& textFlow : textFlows) + { + flowItems.emplace_back(PDFDocumentTextFlow::Item{ textFlow.getBoundingBox(), pageIndex, textFlow.getText(), PDFDocumentTextFlow::Text }); + } + flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, QString(), PDFDocumentTextFlow::PageEnd }); + + QMutexLocker lock(&mutex); + items[pageIndex] = qMove(flowItems); + m_errors.append(qMove(errors)); + }; + + PDFExecutionPolicy::execute(PDFExecutionPolicy::Scope::Page, pageIndices.begin(), pageIndices.end(), generateTextLayout); + + fontCache.setCacheShrinkEnabled(nullptr, true); + + PDFDocumentTextFlow::Items flowItems; + for (const auto& item : items) + { + flowItems.insert(flowItems.end(), std::make_move_iterator(item.second.begin()), std::make_move_iterator(item.second.end())); + } + + result = PDFDocumentTextFlow(qMove(flowItems)); + break; + } + + case Algorithm::Structure: + { + if (!structureTree.isValid()) + { + m_errors << PDFRenderError(RenderErrorType::Error, PDFTranslationContext::tr("Valid tagged document required.")); + break; + } + + PDFStructureTreeTextExtractor::Options options = PDFStructureTreeTextExtractor::SkipArtifact | PDFStructureTreeTextExtractor::AdjustReversedText | PDFStructureTreeTextExtractor::CreateTreeMapping; + options.setFlag(PDFStructureTreeTextExtractor::BoundingBoxes, m_calculateBoundingBoxes); + PDFStructureTreeTextExtractor extractor(document, &structureTree, options); + extractor.perform(pageIndices); + + PDFDocumentTextFlow::Items flowItems; + PDFStructureTreeTextFlowCollector collector(&flowItems, &extractor); + structureTree.accept(&collector); + + result = PDFDocumentTextFlow(qMove(flowItems)); + m_errors.append(extractor.getErrors()); + break; + } + + case Algorithm::Content: + { + PDFStructureTreeTextExtractor::Options options = PDFStructureTreeTextExtractor::None; + options.setFlag(PDFStructureTreeTextExtractor::BoundingBoxes, m_calculateBoundingBoxes); + PDFStructureTreeTextExtractor extractor(document, &structureTree, options); + extractor.perform(pageIndices); + + PDFDocumentTextFlow::Items flowItems; + for (PDFInteger pageIndex : pageIndices) + { + flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, PDFTranslationContext::tr("Page %1").arg(pageIndex + 1), PDFDocumentTextFlow::PageStart }); + for (const PDFStructureTreeTextItem& sequenceItem : extractor.getTextSequence(pageIndex)) + { + if (sequenceItem.type == PDFStructureTreeTextItem::Type::Text) + { + flowItems.emplace_back(PDFDocumentTextFlow::Item{ sequenceItem.boundingRect, pageIndex, sequenceItem.text, PDFDocumentTextFlow::Text }); + } + } + flowItems.emplace_back(PDFDocumentTextFlow::Item{ QRectF(), pageIndex, QString(), PDFDocumentTextFlow::PageEnd }); + } + + result = PDFDocumentTextFlow(qMove(flowItems)); + m_errors.append(extractor.getErrors()); + break; + } + + default: + Q_ASSERT(false); + break; + } + + return result; +} + +PDFDocumentTextFlow PDFDocumentTextFlowFactory::create(const PDFDocument* document, Algorithm algorithm) +{ + std::vector pageIndices; + pageIndices.resize(document->getCatalog()->getPageCount(), 0); + std::iota(pageIndices.begin(), pageIndices.end(), 0); + + return create(document, pageIndices, algorithm); +} + +void PDFDocumentTextFlowFactory::setCalculateBoundingBoxes(bool calculateBoundingBoxes) +{ + m_calculateBoundingBoxes = calculateBoundingBoxes; +} + +void PDFDocumentTextFlowEditor::setTextFlow(PDFDocumentTextFlow textFlow) +{ + m_originalTextFlow = std::move(textFlow); + createEditedFromOriginalTextFlow(); +} + +void PDFDocumentTextFlowEditor::setSelectionActive(bool active) +{ + for (auto& item : m_editedTextFlow) + { + if (item.editedItemFlags.testFlag(Selected)) + { + item.editedItemFlags.setFlag(Removed, !active); + } + } +} + +void PDFDocumentTextFlowEditor::select(size_t index, bool select) +{ + getEditedItem(index)->editedItemFlags.setFlag(Selected, select); +} + +void PDFDocumentTextFlowEditor::deselect() +{ + for (auto& item : m_editedTextFlow) + { + item.editedItemFlags.setFlag(Selected, false); + } +} + +void PDFDocumentTextFlowEditor::removeItem(size_t index) +{ + getEditedItem(index)->editedItemFlags.setFlag(Removed, true); +} + +void PDFDocumentTextFlowEditor::addItem(size_t index) +{ + getEditedItem(index)->editedItemFlags.setFlag(Removed, false); +} + +void PDFDocumentTextFlowEditor::clear() +{ + m_originalTextFlow = PDFDocumentTextFlow(); + m_editedTextFlow.clear(); + m_pageIndicesMapping.clear(); +} + +void PDFDocumentTextFlowEditor::setText(const QString& text, size_t index) +{ + EditedItem* item = getEditedItem(index); + item->text = text; + updateModifiedFlag(index); +} + +bool PDFDocumentTextFlowEditor::isSelectionEmpty() const +{ + return std::all_of(m_editedTextFlow.cbegin(), m_editedTextFlow.cend(), [](const auto& item) { return !item.editedItemFlags.testFlag(Selected); }); +} + +bool PDFDocumentTextFlowEditor::isSelectionModified() const +{ + return std::any_of(m_editedTextFlow.cbegin(), m_editedTextFlow.cend(), [](const auto& item) { return item.editedItemFlags.testFlag(Selected) && item.editedItemFlags.testFlag(Modified); }); +} + +void PDFDocumentTextFlowEditor::selectByRectangle(QRectF rectangle) +{ + for (auto& item : m_editedTextFlow) + { + const QRectF& boundingRectangle = item.boundingRect; + + if (boundingRectangle.isEmpty()) + { + item.editedItemFlags.setFlag(Selected, false); + continue; + } + + const bool isContained = rectangle.contains(boundingRectangle); + item.editedItemFlags.setFlag(Selected, isContained); + } +} + +void PDFDocumentTextFlowEditor::selectByContainedText(QString text) +{ + for (auto& item : m_editedTextFlow) + { + const bool isContained = item.text.contains(text, Qt::CaseSensitive); + item.editedItemFlags.setFlag(Selected, isContained); + } +} + +void PDFDocumentTextFlowEditor::selectByRegularExpression(const QRegularExpression& expression) +{ + for (auto& item : m_editedTextFlow) + { + QRegularExpressionMatch match = expression.match(item.text, 0, QRegularExpression::NormalMatch, QRegularExpression::NoMatchOption); + const bool hasMatch = match.hasMatch(); + item.editedItemFlags.setFlag(Selected, hasMatch); + } +} + +void PDFDocumentTextFlowEditor::selectByPageIndices(const pdf::PDFClosedIntervalSet& indices) +{ + std::vector pageIndices = indices.unfold(); + std::sort(pageIndices.begin(), pageIndices.end()); + + for (auto& item : m_editedTextFlow) + { + const bool isPageValid = std::binary_search(pageIndices.begin(), pageIndices.end(), item.pageIndex + 1); + item.editedItemFlags.setFlag(Selected, isPageValid); + } +} + +void PDFDocumentTextFlowEditor::restoreOriginalTexts() +{ + for (auto& item : m_editedTextFlow) + { + if (item.editedItemFlags.testFlag(Selected)) + { + item.text = m_originalTextFlow.getItem(item.originalIndex)->text; + item.editedItemFlags.setFlag(Modified, false); + } + } +} + +void PDFDocumentTextFlowEditor::moveSelectionUp() +{ + size_t originalSize = m_editedTextFlow.size(); + size_t firstSelectedIndex = originalSize; + + EditedItems selectedItems; + for (auto it = m_editedTextFlow.begin(); it != m_editedTextFlow.end();) + { + if (it->editedItemFlags.testFlag(Selected)) + { + if (firstSelectedIndex == originalSize) + { + firstSelectedIndex = std::distance(m_editedTextFlow.begin(), it); + } + + selectedItems.emplace_back(std::move(*it)); + it = m_editedTextFlow.erase(it); + } + else + { + ++it; + } + } + + if (firstSelectedIndex > 0) + { + --firstSelectedIndex; + } + + m_editedTextFlow.insert(std::next(m_editedTextFlow.begin(), firstSelectedIndex), std::make_move_iterator(selectedItems.begin()), std::make_move_iterator(selectedItems.end())); +} + +void PDFDocumentTextFlowEditor::moveSelectionDown() +{ + size_t originalSize = m_editedTextFlow.size(); + size_t lastSelectedIndex = originalSize; + + EditedItems selectedItems; + for (auto it = m_editedTextFlow.begin(); it != m_editedTextFlow.end();) + { + if (it->editedItemFlags.testFlag(Selected)) + { + lastSelectedIndex = std::distance(m_editedTextFlow.begin(), it); + selectedItems.emplace_back(std::move(*it)); + it = m_editedTextFlow.erase(it); + } + else + { + ++it; + } + } + + if (lastSelectedIndex < m_editedTextFlow.size()) + { + ++lastSelectedIndex; + } + + m_editedTextFlow.insert(std::next(m_editedTextFlow.begin(), lastSelectedIndex), std::make_move_iterator(selectedItems.begin()), std::make_move_iterator(selectedItems.end())); +} + +PDFDocumentTextFlowEditor::PageIndicesMappingRange PDFDocumentTextFlowEditor::getItemsForPageIndex(PDFInteger pageIndex) const +{ + auto comparator = [](const auto& l, const auto& r) + { + return l.first < r.first; + }; + return std::equal_range(m_pageIndicesMapping.cbegin(), m_pageIndicesMapping.cend(), std::make_pair(pageIndex, size_t(0)), comparator); +} + +PDFDocumentTextFlow PDFDocumentTextFlowEditor::createEditedTextFlow() const +{ + PDFDocumentTextFlow::Items items; + items.reserve(getItemCount()); + + const size_t size = getItemCount(); + for (size_t i = 0; i < size; ++i) + { + if (isRemoved(i)) + { + continue; + } + + PDFDocumentTextFlow::Item item = *getOriginalItem(i); + item.text = getText(i); + items.emplace_back(std::move(item)); + } + + return PDFDocumentTextFlow(std::move(items)); +} + +void PDFDocumentTextFlowEditor::createPageMapping() +{ + m_pageIndicesMapping.clear(); + m_pageIndicesMapping.reserve(m_editedTextFlow.size()); + + for (size_t i = 0; i < m_editedTextFlow.size(); ++i) + { + m_pageIndicesMapping.emplace_back(m_editedTextFlow[i].pageIndex, i); + } + + std::sort(m_pageIndicesMapping.begin(), m_pageIndicesMapping.end()); +} + +void PDFDocumentTextFlowEditor::createEditedFromOriginalTextFlow() +{ + const size_t count = m_originalTextFlow.getSize(); + m_editedTextFlow.reserve(count); + + for (size_t i = 0; i < count; ++i) + { + const PDFDocumentTextFlow::Item* originalItem = getOriginalItem(i); + + if (originalItem->text.trimmed().isEmpty()) + { + continue; + } + + EditedItem editedItem; + static_cast(editedItem) = *originalItem; + editedItem.originalIndex = i; + editedItem.editedItemFlags = originalItem->isText() ? None : Removed; + m_editedTextFlow.emplace_back(std::move(editedItem)); + } + + createPageMapping(); +} + +void PDFDocumentTextFlowEditor::updateModifiedFlag(size_t index) +{ + const bool isModified = getText(index) != getOriginalItem(index)->text; + + EditedItem* item = getEditedItem(index); + item->editedItemFlags.setFlag(Modified, isModified); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocumenttextflow.h b/Pdf4QtLib/sources/pdfdocumenttextflow.h index 98be760..8e82332 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextflow.h +++ b/Pdf4QtLib/sources/pdfdocumenttextflow.h @@ -1,284 +1,284 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCUMENTTEXTFLOW_H -#define PDFDOCUMENTTEXTFLOW_H - -#include "pdfglobal.h" -#include "pdfexception.h" -#include "pdfutils.h" - -namespace pdf -{ -class PDFDocument; - -/// Text flow extracted from document. Text flow can be created \p PDFDocumentTextFlowFactory. -/// Flow can contain various items, not just text ones. Also, some manipulation functions -/// are available, they can modify text flow by various content. -class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlow -{ -public: - - enum Flag - { - None = 0x0000, ///< No text flag - Text = 0x0001, ///< Ordinary text - PageStart = 0x0002, ///< Page start marker - PageEnd = 0x0004, ///< Page end marker - StructureTitle = 0x0008, ///< Structure tree item title - StructureLanguage = 0x0010, ///< Structure tree item language - StructureAlternativeDescription = 0x0020, ///< Structure tree item alternative description - StructureExpandedForm = 0x0040, ///< Structure tree item expanded form of text - StructureActualText = 0x0080, ///< Structure tree item actual text - StructurePhoneme = 0x0100, ///< Structure tree item phoneme - StructureItemStart = 0x0200, ///< Start of structure tree item - StructureItemEnd = 0x0400, ///< End of structure tree item - }; - Q_DECLARE_FLAGS(Flags, Flag) - - struct Item - { - QRectF boundingRect; ///< Bounding rect in page coordinates - PDFInteger pageIndex = 0; - QString text; - Flags flags = None; - - bool isText() const { return flags.testFlag(Text); } - bool isSpecial() const { return !isText(); } - bool isTitle() const { return flags.testFlag(StructureTitle); } - bool isLanguage() const { return flags.testFlag(StructureLanguage); } - }; - using Items = std::vector; - - explicit PDFDocumentTextFlow() = default; - explicit PDFDocumentTextFlow(Items&& items) : - m_items(qMove(items)) - { - - } - - const Items& getItems() const { return m_items; } - - /// Returns item at a given index - /// \param index Index - const Item* getItem(size_t index) const { return &m_items.at(index); } - - /// Returns text flow item count - size_t getSize() const { return m_items.size(); } - - /// Returns true, if text flow is empty - bool isEmpty() const { return m_items.empty(); } - -private: - Items m_items; -}; - -/// This factory creates text flow for whole document -class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlowFactory -{ -public: - explicit PDFDocumentTextFlowFactory() = default; - - enum class Algorithm - { - Auto, ///< Determine best text layout algorithm automatically - Layout, ///< Use text layout recognition using docstrum algorithm - Content, ///< Use content-stream text layout recognition (usually unreliable), but fast - Structure, ///< Use structure oriented text layout recognition (requires tagged document) - }; - - /// Performs document text flow analysis using given algorithm. Text flow - /// can be performed only for given subset of pages, if required. - /// \param document Document - /// \param pageIndices Analyzed page indices - /// \param algorithm Algorithm - PDFDocumentTextFlow create(const PDFDocument* document, - const std::vector& pageIndices, - Algorithm algorithm); - - /// Performs document text flow analysis using given algorithm. Text flow - /// is created for all pages. - /// \param document Document - /// \param algorithm Algorithm - PDFDocumentTextFlow create(const PDFDocument* document, Algorithm algorithm); - - /// Has some error/warning occured during text layout creation? - bool hasError() const { return !m_errors.isEmpty(); } - - /// Returns a list of errors/warnings - const QList& getErrors() const { return m_errors; } - - /// Sets if bounding boxes for text blocks should be calculated - /// \param calculateBoundingBoxes Perform bounding box calculation? - void setCalculateBoundingBoxes(bool calculateBoundingBoxes); - -private: - QList m_errors; - bool m_calculateBoundingBoxes = false; -}; - -/// Editor which can edit document text flow, modify user text, -/// change order of text items, restore original state of a text flow, -/// and many other features. -class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlowEditor -{ -public: - inline PDFDocumentTextFlowEditor() = default; - - /// Sets a text flow and initializes edited text flow - /// \param textFlow Text flow - void setTextFlow(PDFDocumentTextFlow textFlow); - - /// Marks selected item as active or inactive - /// \param active Active - void setSelectionActive(bool active); - - /// Selects or deselects item - /// \param index Index - /// \param select Select (true) or deselect (false) - void select(size_t index, bool select); - - /// Deselects all selected items - void deselect(); - - void removeItem(size_t index); - void addItem(size_t index); - - void clear(); - - enum EditedItemFlag - { - None = 0x0000, - Removed = 0x0001, - Modified = 0x0002, - Selected = 0x0004 - }; - Q_DECLARE_FLAGS(EditedItemFlags, EditedItemFlag) - - struct EditedItem : public PDFDocumentTextFlow::Item - { - size_t originalIndex = 0; ///< Index of original item - EditedItemFlags editedItemFlags = None; - }; - - using EditedItems = std::vector; - using PageIndicesMapping = std::vector>; - using PageIndicesMappingIterator = PageIndicesMapping::const_iterator; - using PageIndicesMappingRange = std::pair; - - /// Returns true, if item is active - /// \param index Index - bool isActive(size_t index) const { return !getEditedItem(index)->editedItemFlags.testFlag(Removed); } - - /// Returns true, if item is removed - /// \param index Index - bool isRemoved(size_t index) const { return !isActive(index); } - - /// Returns true, if item is modified - /// \param index Index - bool isModified(size_t index) const { return getEditedItem(index)->editedItemFlags.testFlag(Modified); } - - /// Returns true, if item is selected - /// \param index Index - bool isSelected(size_t index) const { return getEditedItem(index)->editedItemFlags.testFlag(Selected); } - - /// Returns edited text (or original, if edited text is not modified) - /// for a given index. - /// \param index Index - const QString& getText(size_t index) const { return getEditedItem(index)->text; } - - /// Sets edited text for a given index - void setText(const QString& text, size_t index); - - /// Returns true, if text flow is empty - bool isEmpty() const { return m_originalTextFlow.isEmpty(); } - - /// Returns true, if text selection is empty - bool isSelectionEmpty() const; - - /// Returns true, if selection contains modified items - bool isSelectionModified() const; - - /// Returns item count in edited text flow - size_t getItemCount() const { return m_editedTextFlow.size(); } - - /// Returns page index for given item - /// \param index Index - PDFInteger getPageIndex(size_t index) const { return getEditedItem(index)->pageIndex; } - - bool isItemTypeText(size_t index) const { return getEditedItem(index)->isText(); } - bool isItemTypeSpecial(size_t index) const { return getEditedItem(index)->isSpecial(); } - bool isItemTypeTitle(size_t index) const { return getEditedItem(index)->isTitle(); } - bool isItemTypeLanguage(size_t index) const { return getEditedItem(index)->isLanguage(); } - - /// Selects items contained in a rectangle - /// \param rectangle Selection rectangle - void selectByRectangle(QRectF rectangle); - - /// Select items which contains text - /// \param text Text - void selectByContainedText(QString text); - - /// Select items which matches regular expression - /// \param expression Regular expression - void selectByRegularExpression(const QRegularExpression& expression); - - /// Select all items on a given page indices - /// \param indices Indices - void selectByPageIndices(const PDFClosedIntervalSet& indices); - - /// Restores original texts in selected items - void restoreOriginalTexts(); - - /// Move all selected items one position up. If multiple non-consecutive - /// items are selected, they are grouped into one group and move one item - /// up above first selected item. - void moveSelectionUp(); - - /// Move all selected items one position down. If multiple non-consecutive - /// items are selected, they are grouped into one group and move one item - /// down above first selected item. - void moveSelectionDown(); - - /// Returns item indices for a given page index, i.e. - /// index of items which are lying on a page. - /// \param pageIndex Page index - PageIndicesMappingRange getItemsForPageIndex(PDFInteger pageIndex) const; - - const EditedItem* getEditedItem(size_t index) const { return &m_editedTextFlow.at(index); } - - /// Creates text flow from active edited items. If item is removed, - /// then it is not added into this text flow. User text modification - /// is applied to a text flow. - PDFDocumentTextFlow createEditedTextFlow() const; - -private: - void createPageMapping(); - void createEditedFromOriginalTextFlow(); - void updateModifiedFlag(size_t index); - - const PDFDocumentTextFlow::Item* getOriginalItem(size_t index) const { return m_originalTextFlow.getItem(index); } - EditedItem* getEditedItem(size_t index) { return &m_editedTextFlow.at(index); } - - PDFDocumentTextFlow m_originalTextFlow; - EditedItems m_editedTextFlow; - PageIndicesMapping m_pageIndicesMapping; -}; - -} // namespace pdf - -#endif // PDFDOCUMENTTEXTFLOW_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCUMENTTEXTFLOW_H +#define PDFDOCUMENTTEXTFLOW_H + +#include "pdfglobal.h" +#include "pdfexception.h" +#include "pdfutils.h" + +namespace pdf +{ +class PDFDocument; + +/// Text flow extracted from document. Text flow can be created \p PDFDocumentTextFlowFactory. +/// Flow can contain various items, not just text ones. Also, some manipulation functions +/// are available, they can modify text flow by various content. +class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlow +{ +public: + + enum Flag + { + None = 0x0000, ///< No text flag + Text = 0x0001, ///< Ordinary text + PageStart = 0x0002, ///< Page start marker + PageEnd = 0x0004, ///< Page end marker + StructureTitle = 0x0008, ///< Structure tree item title + StructureLanguage = 0x0010, ///< Structure tree item language + StructureAlternativeDescription = 0x0020, ///< Structure tree item alternative description + StructureExpandedForm = 0x0040, ///< Structure tree item expanded form of text + StructureActualText = 0x0080, ///< Structure tree item actual text + StructurePhoneme = 0x0100, ///< Structure tree item phoneme + StructureItemStart = 0x0200, ///< Start of structure tree item + StructureItemEnd = 0x0400, ///< End of structure tree item + }; + Q_DECLARE_FLAGS(Flags, Flag) + + struct Item + { + QRectF boundingRect; ///< Bounding rect in page coordinates + PDFInteger pageIndex = 0; + QString text; + Flags flags = None; + + bool isText() const { return flags.testFlag(Text); } + bool isSpecial() const { return !isText(); } + bool isTitle() const { return flags.testFlag(StructureTitle); } + bool isLanguage() const { return flags.testFlag(StructureLanguage); } + }; + using Items = std::vector; + + explicit PDFDocumentTextFlow() = default; + explicit PDFDocumentTextFlow(Items&& items) : + m_items(qMove(items)) + { + + } + + const Items& getItems() const { return m_items; } + + /// Returns item at a given index + /// \param index Index + const Item* getItem(size_t index) const { return &m_items.at(index); } + + /// Returns text flow item count + size_t getSize() const { return m_items.size(); } + + /// Returns true, if text flow is empty + bool isEmpty() const { return m_items.empty(); } + +private: + Items m_items; +}; + +/// This factory creates text flow for whole document +class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlowFactory +{ +public: + explicit PDFDocumentTextFlowFactory() = default; + + enum class Algorithm + { + Auto, ///< Determine best text layout algorithm automatically + Layout, ///< Use text layout recognition using docstrum algorithm + Content, ///< Use content-stream text layout recognition (usually unreliable), but fast + Structure, ///< Use structure oriented text layout recognition (requires tagged document) + }; + + /// Performs document text flow analysis using given algorithm. Text flow + /// can be performed only for given subset of pages, if required. + /// \param document Document + /// \param pageIndices Analyzed page indices + /// \param algorithm Algorithm + PDFDocumentTextFlow create(const PDFDocument* document, + const std::vector& pageIndices, + Algorithm algorithm); + + /// Performs document text flow analysis using given algorithm. Text flow + /// is created for all pages. + /// \param document Document + /// \param algorithm Algorithm + PDFDocumentTextFlow create(const PDFDocument* document, Algorithm algorithm); + + /// Has some error/warning occured during text layout creation? + bool hasError() const { return !m_errors.isEmpty(); } + + /// Returns a list of errors/warnings + const QList& getErrors() const { return m_errors; } + + /// Sets if bounding boxes for text blocks should be calculated + /// \param calculateBoundingBoxes Perform bounding box calculation? + void setCalculateBoundingBoxes(bool calculateBoundingBoxes); + +private: + QList m_errors; + bool m_calculateBoundingBoxes = false; +}; + +/// Editor which can edit document text flow, modify user text, +/// change order of text items, restore original state of a text flow, +/// and many other features. +class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlowEditor +{ +public: + inline PDFDocumentTextFlowEditor() = default; + + /// Sets a text flow and initializes edited text flow + /// \param textFlow Text flow + void setTextFlow(PDFDocumentTextFlow textFlow); + + /// Marks selected item as active or inactive + /// \param active Active + void setSelectionActive(bool active); + + /// Selects or deselects item + /// \param index Index + /// \param select Select (true) or deselect (false) + void select(size_t index, bool select); + + /// Deselects all selected items + void deselect(); + + void removeItem(size_t index); + void addItem(size_t index); + + void clear(); + + enum EditedItemFlag + { + None = 0x0000, + Removed = 0x0001, + Modified = 0x0002, + Selected = 0x0004 + }; + Q_DECLARE_FLAGS(EditedItemFlags, EditedItemFlag) + + struct EditedItem : public PDFDocumentTextFlow::Item + { + size_t originalIndex = 0; ///< Index of original item + EditedItemFlags editedItemFlags = None; + }; + + using EditedItems = std::vector; + using PageIndicesMapping = std::vector>; + using PageIndicesMappingIterator = PageIndicesMapping::const_iterator; + using PageIndicesMappingRange = std::pair; + + /// Returns true, if item is active + /// \param index Index + bool isActive(size_t index) const { return !getEditedItem(index)->editedItemFlags.testFlag(Removed); } + + /// Returns true, if item is removed + /// \param index Index + bool isRemoved(size_t index) const { return !isActive(index); } + + /// Returns true, if item is modified + /// \param index Index + bool isModified(size_t index) const { return getEditedItem(index)->editedItemFlags.testFlag(Modified); } + + /// Returns true, if item is selected + /// \param index Index + bool isSelected(size_t index) const { return getEditedItem(index)->editedItemFlags.testFlag(Selected); } + + /// Returns edited text (or original, if edited text is not modified) + /// for a given index. + /// \param index Index + const QString& getText(size_t index) const { return getEditedItem(index)->text; } + + /// Sets edited text for a given index + void setText(const QString& text, size_t index); + + /// Returns true, if text flow is empty + bool isEmpty() const { return m_originalTextFlow.isEmpty(); } + + /// Returns true, if text selection is empty + bool isSelectionEmpty() const; + + /// Returns true, if selection contains modified items + bool isSelectionModified() const; + + /// Returns item count in edited text flow + size_t getItemCount() const { return m_editedTextFlow.size(); } + + /// Returns page index for given item + /// \param index Index + PDFInteger getPageIndex(size_t index) const { return getEditedItem(index)->pageIndex; } + + bool isItemTypeText(size_t index) const { return getEditedItem(index)->isText(); } + bool isItemTypeSpecial(size_t index) const { return getEditedItem(index)->isSpecial(); } + bool isItemTypeTitle(size_t index) const { return getEditedItem(index)->isTitle(); } + bool isItemTypeLanguage(size_t index) const { return getEditedItem(index)->isLanguage(); } + + /// Selects items contained in a rectangle + /// \param rectangle Selection rectangle + void selectByRectangle(QRectF rectangle); + + /// Select items which contains text + /// \param text Text + void selectByContainedText(QString text); + + /// Select items which matches regular expression + /// \param expression Regular expression + void selectByRegularExpression(const QRegularExpression& expression); + + /// Select all items on a given page indices + /// \param indices Indices + void selectByPageIndices(const PDFClosedIntervalSet& indices); + + /// Restores original texts in selected items + void restoreOriginalTexts(); + + /// Move all selected items one position up. If multiple non-consecutive + /// items are selected, they are grouped into one group and move one item + /// up above first selected item. + void moveSelectionUp(); + + /// Move all selected items one position down. If multiple non-consecutive + /// items are selected, they are grouped into one group and move one item + /// down above first selected item. + void moveSelectionDown(); + + /// Returns item indices for a given page index, i.e. + /// index of items which are lying on a page. + /// \param pageIndex Page index + PageIndicesMappingRange getItemsForPageIndex(PDFInteger pageIndex) const; + + const EditedItem* getEditedItem(size_t index) const { return &m_editedTextFlow.at(index); } + + /// Creates text flow from active edited items. If item is removed, + /// then it is not added into this text flow. User text modification + /// is applied to a text flow. + PDFDocumentTextFlow createEditedTextFlow() const; + +private: + void createPageMapping(); + void createEditedFromOriginalTextFlow(); + void updateModifiedFlag(size_t index); + + const PDFDocumentTextFlow::Item* getOriginalItem(size_t index) const { return m_originalTextFlow.getItem(index); } + EditedItem* getEditedItem(size_t index) { return &m_editedTextFlow.at(index); } + + PDFDocumentTextFlow m_originalTextFlow; + EditedItems m_editedTextFlow; + PageIndicesMapping m_pageIndicesMapping; +}; + +} // namespace pdf + +#endif // PDFDOCUMENTTEXTFLOW_H diff --git a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp index dc6a543..efeeab3 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp +++ b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.cpp @@ -1,344 +1,344 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfdocumenttextfloweditormodel.h" -#include "pdfdocumenttextflow.h" - -#include -#include - -namespace pdf -{ - -PDFDocumentTextFlowEditorModel::PDFDocumentTextFlowEditorModel(QObject* parent) : - BaseClass(parent), - m_editor(nullptr) -{ - -} - -PDFDocumentTextFlowEditorModel::~PDFDocumentTextFlowEditorModel() -{ - -} - -QVariant PDFDocumentTextFlowEditorModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Vertical) - { - return BaseClass::headerData(section, orientation, role); - } - - if (role == Qt::DisplayRole) - { - switch (section) - { - case ColumnPageNo: - return tr("Page No."); - - case ColumnType: - return tr("Type"); - - case ColumnState: - return tr("State"); - - case ColumnText: - return tr("Text"); - - default: - Q_ASSERT(false); - break; - } - } - - return BaseClass::headerData(section, orientation, role); -} - -int PDFDocumentTextFlowEditorModel::rowCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - { - return 0; - } - - if (m_editor) - { - return int(m_editor->getItemCount()); - } - - return 0; -} - -int PDFDocumentTextFlowEditorModel::columnCount(const QModelIndex& parent) const -{ - if (parent.isValid()) - { - return 0; - } - - return ColumnLast; -} - -QVariant PDFDocumentTextFlowEditorModel::data(const QModelIndex& index, int role) const -{ - if (!index.isValid() || !m_editor) - { - return QVariant(); - } - - if (role == Qt::BackgroundRole) - { - if (m_editor->isSelected(index.row())) - { - return QBrush(QColor(255, 255, 200)); - } - } - - if (role == Qt::DisplayRole || role == Qt::EditRole) - { - switch (index.column()) - { - case ColumnPageNo: - { - PDFInteger pageIndex = m_editor->getPageIndex(index.row()); - if (pageIndex >= 0) - { - return QString::number(pageIndex + 1); - } - - return QVariant(); - } - - case ColumnType: - { - if (m_editor->isItemTypeTitle(index.row())) - { - return tr("Title"); - } - if (m_editor->isItemTypeLanguage(index.row())) - { - return tr("Language"); - } - if (m_editor->isItemTypeSpecial(index.row())) - { - return tr("Special"); - } - - return tr("Text"); - } - - case ColumnState: - { - const bool isModified = m_editor->isModified(index.row()); - const bool isRemoved = m_editor->isRemoved(index.row()); - - if (isRemoved) - { - return tr("Removed"); - } - - if (isModified) - { - return tr("Modified"); - } - - return tr("Active"); - } - - case ColumnText: - return m_editor->getText(index.row()); - - default: - Q_ASSERT(false); - break; - } - } - - return QVariant(); -} - -bool PDFDocumentTextFlowEditorModel::setData(const QModelIndex& index, const QVariant& value, int role) -{ - if (role == Qt::EditRole && index.column() == ColumnText) - { - m_editor->setText(value.toString(), index.row()); - return true; - } - - return false; -} - -Qt::DropActions PDFDocumentTextFlowEditorModel::supportedDropActions() const -{ - return Qt::DropAction(); -} - -Qt::DropActions PDFDocumentTextFlowEditorModel::supportedDragActions() const -{ - return Qt::DropActions(); -} - -Qt::ItemFlags PDFDocumentTextFlowEditorModel::flags(const QModelIndex& index) const -{ - Qt::ItemFlags flags = BaseClass::flags(index); - - if (index.column() == ColumnText) - { - flags.setFlag(Qt::ItemIsEditable); - } - - return flags; -} - -PDFDocumentTextFlowEditor* PDFDocumentTextFlowEditorModel::getEditor() const -{ - return m_editor; -} - -void PDFDocumentTextFlowEditorModel::setEditor(PDFDocumentTextFlowEditor* editor) -{ - if (m_editor != editor) - { - beginResetModel(); - m_editor = editor; - endResetModel(); - } -} - -void PDFDocumentTextFlowEditorModel::beginFlowChange() -{ - beginResetModel(); -} - -void PDFDocumentTextFlowEditorModel::endFlowChange() -{ - endResetModel(); -} - -void PDFDocumentTextFlowEditorModel::clear() -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - beginFlowChange(); - m_editor->clear(); - endFlowChange(); -} - -void PDFDocumentTextFlowEditorModel::setSelectionActivated(bool activate) -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->setSelectionActive(activate); - m_editor->deselect(); - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -void PDFDocumentTextFlowEditorModel::selectByRectangle(QRectF rectangle) -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->selectByRectangle(rectangle); - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -void PDFDocumentTextFlowEditorModel::selectByContainedText(QString text) -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->selectByContainedText(text); - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -void PDFDocumentTextFlowEditorModel::selectByRegularExpression(const QRegularExpression& expression) -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->selectByRegularExpression(expression); - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -void PDFDocumentTextFlowEditorModel::selectByPageIndices(const PDFClosedIntervalSet& indices) -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->selectByPageIndices(indices); - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -void PDFDocumentTextFlowEditorModel::restoreOriginalTexts() -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->restoreOriginalTexts(); - m_editor->deselect(); - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -void PDFDocumentTextFlowEditorModel::moveSelectionUp() -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->moveSelectionUp(); - notifyDataChanged(); -} - -void PDFDocumentTextFlowEditorModel::moveSelectionDown() -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - m_editor->moveSelectionDown(); - notifyDataChanged(); -} - -void PDFDocumentTextFlowEditorModel::notifyDataChanged() -{ - if (!m_editor || m_editor->isEmpty()) - { - return; - } - - emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); -} - -} // namespace pdf +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfdocumenttextfloweditormodel.h" +#include "pdfdocumenttextflow.h" + +#include +#include + +namespace pdf +{ + +PDFDocumentTextFlowEditorModel::PDFDocumentTextFlowEditorModel(QObject* parent) : + BaseClass(parent), + m_editor(nullptr) +{ + +} + +PDFDocumentTextFlowEditorModel::~PDFDocumentTextFlowEditorModel() +{ + +} + +QVariant PDFDocumentTextFlowEditorModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) + { + return BaseClass::headerData(section, orientation, role); + } + + if (role == Qt::DisplayRole) + { + switch (section) + { + case ColumnPageNo: + return tr("Page No."); + + case ColumnType: + return tr("Type"); + + case ColumnState: + return tr("State"); + + case ColumnText: + return tr("Text"); + + default: + Q_ASSERT(false); + break; + } + } + + return BaseClass::headerData(section, orientation, role); +} + +int PDFDocumentTextFlowEditorModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + { + return 0; + } + + if (m_editor) + { + return int(m_editor->getItemCount()); + } + + return 0; +} + +int PDFDocumentTextFlowEditorModel::columnCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + { + return 0; + } + + return ColumnLast; +} + +QVariant PDFDocumentTextFlowEditorModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || !m_editor) + { + return QVariant(); + } + + if (role == Qt::BackgroundRole) + { + if (m_editor->isSelected(index.row())) + { + return QBrush(QColor(255, 255, 200)); + } + } + + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + switch (index.column()) + { + case ColumnPageNo: + { + PDFInteger pageIndex = m_editor->getPageIndex(index.row()); + if (pageIndex >= 0) + { + return QString::number(pageIndex + 1); + } + + return QVariant(); + } + + case ColumnType: + { + if (m_editor->isItemTypeTitle(index.row())) + { + return tr("Title"); + } + if (m_editor->isItemTypeLanguage(index.row())) + { + return tr("Language"); + } + if (m_editor->isItemTypeSpecial(index.row())) + { + return tr("Special"); + } + + return tr("Text"); + } + + case ColumnState: + { + const bool isModified = m_editor->isModified(index.row()); + const bool isRemoved = m_editor->isRemoved(index.row()); + + if (isRemoved) + { + return tr("Removed"); + } + + if (isModified) + { + return tr("Modified"); + } + + return tr("Active"); + } + + case ColumnText: + return m_editor->getText(index.row()); + + default: + Q_ASSERT(false); + break; + } + } + + return QVariant(); +} + +bool PDFDocumentTextFlowEditorModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (role == Qt::EditRole && index.column() == ColumnText) + { + m_editor->setText(value.toString(), index.row()); + return true; + } + + return false; +} + +Qt::DropActions PDFDocumentTextFlowEditorModel::supportedDropActions() const +{ + return Qt::DropAction(); +} + +Qt::DropActions PDFDocumentTextFlowEditorModel::supportedDragActions() const +{ + return Qt::DropActions(); +} + +Qt::ItemFlags PDFDocumentTextFlowEditorModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags flags = BaseClass::flags(index); + + if (index.column() == ColumnText) + { + flags.setFlag(Qt::ItemIsEditable); + } + + return flags; +} + +PDFDocumentTextFlowEditor* PDFDocumentTextFlowEditorModel::getEditor() const +{ + return m_editor; +} + +void PDFDocumentTextFlowEditorModel::setEditor(PDFDocumentTextFlowEditor* editor) +{ + if (m_editor != editor) + { + beginResetModel(); + m_editor = editor; + endResetModel(); + } +} + +void PDFDocumentTextFlowEditorModel::beginFlowChange() +{ + beginResetModel(); +} + +void PDFDocumentTextFlowEditorModel::endFlowChange() +{ + endResetModel(); +} + +void PDFDocumentTextFlowEditorModel::clear() +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + beginFlowChange(); + m_editor->clear(); + endFlowChange(); +} + +void PDFDocumentTextFlowEditorModel::setSelectionActivated(bool activate) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->setSelectionActive(activate); + m_editor->deselect(); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::selectByRectangle(QRectF rectangle) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByRectangle(rectangle); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::selectByContainedText(QString text) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByContainedText(text); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::selectByRegularExpression(const QRegularExpression& expression) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByRegularExpression(expression); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::selectByPageIndices(const PDFClosedIntervalSet& indices) +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->selectByPageIndices(indices); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::restoreOriginalTexts() +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->restoreOriginalTexts(); + m_editor->deselect(); + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +void PDFDocumentTextFlowEditorModel::moveSelectionUp() +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->moveSelectionUp(); + notifyDataChanged(); +} + +void PDFDocumentTextFlowEditorModel::moveSelectionDown() +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + m_editor->moveSelectionDown(); + notifyDataChanged(); +} + +void PDFDocumentTextFlowEditorModel::notifyDataChanged() +{ + if (!m_editor || m_editor->isEmpty()) + { + return; + } + + emit dataChanged(index(0, 0), index(rowCount(QModelIndex()) - 1, ColumnLast)); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h index ce68c27..ccc4096 100644 --- a/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h +++ b/Pdf4QtLib/sources/pdfdocumenttextfloweditormodel.h @@ -1,82 +1,82 @@ -// Copyright (C) 2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCUMENTTEXTFLOWEDITORMODEL_H -#define PDFDOCUMENTTEXTFLOWEDITORMODEL_H - -#include "pdfglobal.h" -#include "pdfutils.h" - -#include - -namespace pdf -{ -class PDFDocumentTextFlowEditor; - -class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlowEditorModel : public QAbstractTableModel -{ - Q_OBJECT - -private: - using BaseClass = QAbstractTableModel; - -public: - PDFDocumentTextFlowEditorModel(QObject* parent); - virtual ~PDFDocumentTextFlowEditorModel() override; - - enum Column - { - ColumnPageNo, - ColumnType, - ColumnState, - ColumnText, - ColumnLast - }; - - virtual int rowCount(const QModelIndex& parent) const override; - virtual int columnCount(const QModelIndex& parent) const override; - virtual QVariant data(const QModelIndex& index, int role) const override; - virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; - virtual Qt::DropActions supportedDropActions() const override; - virtual Qt::DropActions supportedDragActions() const override; - virtual Qt::ItemFlags flags(const QModelIndex& index) const override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - - PDFDocumentTextFlowEditor* getEditor() const; - void setEditor(PDFDocumentTextFlowEditor* editor); - - void beginFlowChange(); - void endFlowChange(); - - void clear(); - void setSelectionActivated(bool activate); - void selectByRectangle(QRectF rectangle); - void selectByContainedText(QString text); - void selectByRegularExpression(const QRegularExpression& expression); - void selectByPageIndices(const pdf::PDFClosedIntervalSet& indices); - void restoreOriginalTexts(); - void moveSelectionUp(); - void moveSelectionDown(); - void notifyDataChanged(); - -private: - PDFDocumentTextFlowEditor* m_editor; -}; - -} // namespace pdf - -#endif // PDFDOCUMENTTEXTFLOWEDITORMODEL_H +// Copyright (C) 2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCUMENTTEXTFLOWEDITORMODEL_H +#define PDFDOCUMENTTEXTFLOWEDITORMODEL_H + +#include "pdfglobal.h" +#include "pdfutils.h" + +#include + +namespace pdf +{ +class PDFDocumentTextFlowEditor; + +class PDF4QTLIBSHARED_EXPORT PDFDocumentTextFlowEditorModel : public QAbstractTableModel +{ + Q_OBJECT + +private: + using BaseClass = QAbstractTableModel; + +public: + PDFDocumentTextFlowEditorModel(QObject* parent); + virtual ~PDFDocumentTextFlowEditorModel() override; + + enum Column + { + ColumnPageNo, + ColumnType, + ColumnState, + ColumnText, + ColumnLast + }; + + virtual int rowCount(const QModelIndex& parent) const override; + virtual int columnCount(const QModelIndex& parent) const override; + virtual QVariant data(const QModelIndex& index, int role) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; + virtual Qt::DropActions supportedDropActions() const override; + virtual Qt::DropActions supportedDragActions() const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + PDFDocumentTextFlowEditor* getEditor() const; + void setEditor(PDFDocumentTextFlowEditor* editor); + + void beginFlowChange(); + void endFlowChange(); + + void clear(); + void setSelectionActivated(bool activate); + void selectByRectangle(QRectF rectangle); + void selectByContainedText(QString text); + void selectByRegularExpression(const QRegularExpression& expression); + void selectByPageIndices(const pdf::PDFClosedIntervalSet& indices); + void restoreOriginalTexts(); + void moveSelectionUp(); + void moveSelectionDown(); + void notifyDataChanged(); + +private: + PDFDocumentTextFlowEditor* m_editor; +}; + +} // namespace pdf + +#endif // PDFDOCUMENTTEXTFLOWEDITORMODEL_H diff --git a/Pdf4QtLib/sources/pdfdocumentwriter.cpp b/Pdf4QtLib/sources/pdfdocumentwriter.cpp index 14558f6..6f14a50 100644 --- a/Pdf4QtLib/sources/pdfdocumentwriter.cpp +++ b/Pdf4QtLib/sources/pdfdocumentwriter.cpp @@ -1,574 +1,574 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfdocumentwriter.h" -#include "pdfconstants.h" -#include "pdfvisitor.h" -#include "pdfparser.h" - -#include -#include - -namespace pdf -{ - -class PDFWriteObjectVisitor : public PDFAbstractVisitor -{ -public: - explicit PDFWriteObjectVisitor(QIODevice* device) : - m_device(device) - { - - } - - virtual void visitNull() override; - virtual void visitBool(bool value) override; - virtual void visitInt(PDFInteger value) override; - virtual void visitReal(PDFReal value) override; - virtual void visitString(PDFStringRef string) override; - virtual void visitName(PDFStringRef name) override; - virtual void visitArray(const PDFArray* array) override; - virtual void visitDictionary(const PDFDictionary* dictionary) override; - virtual void visitStream(const PDFStream* stream) override; - virtual void visitReference(const PDFObjectReference reference) override; - - PDFObject getDecryptedObject(); - -private: - void writeName(const QByteArray& string); - - QIODevice* m_device; -}; - -void PDFWriteObjectVisitor::visitNull() -{ - m_device->write("null "); -} - -void PDFWriteObjectVisitor::visitBool(bool value) -{ - if (value) - { - m_device->write("true "); - } - else - { - m_device->write("false "); - } -} - -void PDFWriteObjectVisitor::visitInt(PDFInteger value) -{ - m_device->write(QString::number(value).toLatin1()); - m_device->write(" "); -} - -void PDFWriteObjectVisitor::visitReal(PDFReal value) -{ - // Jakub Melka: we use 5 digits, because they are specified - // in PDF 1.7 specification, appendix C, Table C.1, where it is defined, - // that number of significant digits of precision is 5. - m_device->write(QString::number(value, 'f', 5).toLatin1()); - m_device->write(" "); -} - -void PDFWriteObjectVisitor::visitString(PDFStringRef string) -{ - QByteArray data = string.getString(); - if (data.indexOf('(') != -1 || - data.indexOf(')') != -1 || - data.indexOf('\\') != -1) - { - m_device->write("<"); - m_device->write(data.toHex()); - m_device->write(">"); - } - else - { - m_device->write("("); - m_device->write(data); - m_device->write(")"); - } - - m_device->write(" "); -} - -void PDFWriteObjectVisitor::writeName(const QByteArray& string) -{ - m_device->write("/"); - - for (const char character : string) - { - if (PDFLexicalAnalyzer::isRegular(character)) - { - m_device->write(&character, 1); - } - else - { - m_device->write("#"); - m_device->write(QByteArray(&character, 1).toHex()); - } - } - - m_device->write(" "); -} - -void PDFWriteObjectVisitor::visitName(PDFStringRef name) -{ - writeName(name.getString()); -} - -void PDFWriteObjectVisitor::visitArray(const PDFArray* array) -{ - m_device->write("[ "); - acceptArray(array); - m_device->write("] "); -} - -void PDFWriteObjectVisitor::visitDictionary(const PDFDictionary* dictionary) -{ - m_device->write("<< "); - - for (size_t i = 0, count = dictionary->getCount(); i < count; ++i) - { - writeName(dictionary->getKey(i).getString()); - dictionary->getValue(i).accept(this); - } - - m_device->write(">> "); -} - -void PDFWriteObjectVisitor::visitStream(const PDFStream* stream) -{ - visitDictionary(stream->getDictionary()); - - m_device->write("stream"); - m_device->write("\x0D\x0A"); - m_device->write(*stream->getContent()); - m_device->write("\x0D\x0A"); - m_device->write("endstream"); - m_device->write("\x0D\x0A"); -} - -void PDFWriteObjectVisitor::visitReference(const PDFObjectReference reference) -{ - visitInt(reference.objectNumber); - visitInt(reference.generation); - m_device->write("R "); -} - -PDFOperationResult PDFDocumentWriter::write(const QString& fileName, const PDFDocument* document, bool safeWrite) -{ - Q_ASSERT(document); - - const PDFObjectStorage& storage = document->getStorage(); - if (!storage.getSecurityHandler()->isEncryptionAllowed()) - { - return tr("Writing of encrypted documents is not supported."); - } - - if (safeWrite) - { - QSaveFile file(fileName); - file.setDirectWriteFallback(true); - - if (file.open(QFile::WriteOnly | QFile::Truncate)) - { - PDFOperationResult result = write(&file, document); - if (result) - { - file.commit(); - } - else - { - file.cancelWriting(); - } - return result; - } - else - { - return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString()); - } - } - else - { - QFile file(fileName); - - if (file.open(QFile::WriteOnly | QFile::Truncate)) - { - PDFOperationResult result = write(&file, document); - file.close(); - - if (!result) - { - // If some error occured, then remove invalid file - file.remove(); - } - - return result; - } - else - { - return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString()); - } - } -} - -PDFOperationResult PDFDocumentWriter::write(QIODevice* device, const PDFDocument* document) -{ - if (!device->isWritable()) - { - return tr("Device is not writable."); - } - - const PDFObjectStorage& storage = document->getStorage(); - const PDFObjectStorage::PDFObjects& objects = storage.getObjects(); - const size_t objectCount = objects.size(); - const bool isEncrypted = storage.getSecurityHandler()->getMode() != EncryptionMode::None; - if (!storage.getSecurityHandler()->isEncryptionAllowed()) - { - return tr("Writing of encrypted documents is not supported."); - } - - // Write header - PDFVersion version = document->getInfo()->version; - device->write(QString("%PDF-%1.%2").arg(version.major).arg(version.minor).toLatin1()); - writeCRLF(device); - device->write("% PDF producer: "); - device->write(PDF_LIBRARY_NAME); - writeCRLF(device); - writeCRLF(device); - writeCRLF(device); - - PDFObjectReference encryptObjectReference; - PDFObject encryptObject = document->getTrailerDictionary()->get("Encrypt"); - if (encryptObject.isReference()) - { - encryptObjectReference = encryptObject.getReference(); - } - - // Write objects - std::vector offsets(objectCount, -1); - for (size_t i = 0; i < objectCount; ++i) - { - const PDFObjectStorage::Entry& entry = objects[i]; - if (entry.object.isNull()) - { - continue; - } - - // Jakub Melka: we must mark actual position of object - offsets[i] = device->pos(); - - if (isEncrypted) - { - PDFObjectReference reference(i, entry.generation); - PDFObject objectToWrite = entry.object; - - if (reference != encryptObjectReference) - { - objectToWrite = storage.getSecurityHandler()->encryptObject(objectToWrite, reference); - } - - PDFWriteObjectVisitor visitor(device); - writeObjectHeader(device, reference); - objectToWrite.accept(&visitor); - writeObjectFooter(device); - } - else - { - PDFWriteObjectVisitor visitor(device); - writeObjectHeader(device, PDFObjectReference(i, entry.generation)); - entry.object.accept(&visitor); - writeObjectFooter(device); - } - } - - // Write cross-reference table - PDFInteger xrefOffset = device->pos(); - device->write("xref"); - writeCRLF(device); - device->write(QString("0 %1").arg(objectCount).toLatin1()); - writeCRLF(device); - - for (size_t i = 0; i < objectCount; ++i) - { - const PDFObjectStorage::Entry& entry = objects[i]; - PDFInteger generation = entry.generation; - - if (i == 0) - { - generation = 65535; - } - - PDFInteger offset = offsets[i]; - if (offset == -1) - { - offset = 0; - } - - QString offsetString = QString::number(offset).rightJustified(10, QChar('0'), true); - QString generationString = QString::number(generation).rightJustified(5, QChar('0'), true); - - device->write(offsetString.toLatin1()); - device->write(" "); - device->write(generationString.toLatin1()); - device->write(" "); - device->write(entry.object.isNull() ? "f" : "n"); - writeCRLF(device); - } - - // Jakub Melka: Adjust trailer dictionary, to be really dictionary, not a stream - PDFDictionary trailerDictionary = *document->getTrailerDictionary(); - PDFDictionary newTrailerDictionary; - - for (const char* entry : { "Size", "Root", "Encrypt", "Info", "ID"}) - { - PDFObject object = trailerDictionary.get(entry); - if (!object.isNull()) - { - newTrailerDictionary.addEntry(PDFInplaceOrMemoryString(entry), qMove(object)); - } - } - - PDFObject trailerDictionaryObject = PDFObject::createDictionary(std::make_shared(qMove(newTrailerDictionary))); - - device->write("trailer"); - writeCRLF(device); - PDFWriteObjectVisitor trailerVisitor(device); - trailerDictionaryObject.accept(&trailerVisitor); - writeCRLF(device); - device->write("startxref"); - writeCRLF(device); - device->write(QString::number(xrefOffset).toLatin1()); - writeCRLF(device); - - // Write footer - device->write("%%EOF"); - - return true; -} - -void PDFDocumentWriter::writeCRLF(QIODevice* device) -{ - device->write("\x0D\x0A"); -} - -void PDFDocumentWriter::writeObjectHeader(QIODevice* device, PDFObjectReference reference) -{ - QString objectHeader = QString("%1 %2 obj").arg(QString::number(reference.objectNumber), QString::number(reference.generation)); - device->write(objectHeader.toLatin1()); - writeCRLF(device); -} - -void PDFDocumentWriter::writeObjectFooter(QIODevice* device) -{ - device->write("endobj"); - writeCRLF(device); -} - -class PDFSizeCounterIODevice : public QIODevice -{ -public: - explicit PDFSizeCounterIODevice(QObject* parent) : - QIODevice(parent) - { - - } - - virtual bool isSequential() const override; - virtual bool open(OpenMode mode) override; - virtual void close() override; - virtual qint64 pos() const override; - virtual qint64 size() const override; - virtual bool seek(qint64 pos) override; - virtual bool atEnd() const override; - virtual bool reset() override; - virtual qint64 bytesAvailable() const override; - virtual qint64 bytesToWrite() const override; - virtual bool canReadLine() const override; - virtual bool waitForReadyRead(int msecs) override; - virtual bool waitForBytesWritten(int msecs) override; - -protected: - virtual qint64 readData(char* data, qint64 maxlen) override; - virtual qint64 readLineData(char* data, qint64 maxlen) override; - virtual qint64 writeData(const char* data, qint64 len) override; - -private: - OpenMode m_openMode = NotOpen; - qint64 m_fileSize = 0; -}; - -bool PDFSizeCounterIODevice::isSequential() const -{ - return true; -} - -bool PDFSizeCounterIODevice::open(OpenMode mode) -{ - if (m_openMode == NotOpen) - { - setOpenMode(mode); - return true; - } - else - { - return false; - } -} - -void PDFSizeCounterIODevice::close() -{ - setOpenMode(NotOpen); -} - -qint64 PDFSizeCounterIODevice::pos() const -{ - return m_fileSize; -} - -qint64 PDFSizeCounterIODevice::size() const -{ - return m_fileSize; -} - -bool PDFSizeCounterIODevice::seek(qint64 pos) -{ - Q_UNUSED(pos); - - return false; -} - -bool PDFSizeCounterIODevice::atEnd() const -{ - return true; -} - -bool PDFSizeCounterIODevice::reset() -{ - return false; -} - -qint64 PDFSizeCounterIODevice::bytesAvailable() const -{ - return 0; -} - -qint64 PDFSizeCounterIODevice::bytesToWrite() const -{ - return 0; -} - -bool PDFSizeCounterIODevice::canReadLine() const -{ - return false; -} - -bool PDFSizeCounterIODevice::waitForReadyRead(int msecs) -{ - Q_UNUSED(msecs); - - return false; -} - -bool PDFSizeCounterIODevice::waitForBytesWritten(int msecs) -{ - Q_UNUSED(msecs); - - return false; -} - -qint64 PDFSizeCounterIODevice::readData(char* data, qint64 maxlen) -{ - Q_UNUSED(data); - Q_UNUSED(maxlen); - - return 0; -} - -qint64 PDFSizeCounterIODevice::readLineData(char* data, qint64 maxlen) -{ - Q_UNUSED(data); - Q_UNUSED(maxlen); - - return 0; -} - -qint64 PDFSizeCounterIODevice::writeData(const char* data, qint64 len) -{ - Q_UNUSED(data); - - m_fileSize += len; - return len; -} - -qint64 PDFDocumentWriter::getDocumentFileSize(const PDFDocument* document) -{ - PDFSizeCounterIODevice device(nullptr); - PDFDocumentWriter writer(nullptr); - - device.open(QIODevice::WriteOnly); - - if (writer.write(&device, document)) - { - device.close(); - return device.pos(); - } - - device.close(); - return -1; -} - -qint64 PDFDocumentWriter::getObjectSize(const PDFDocument* document, PDFObjectReference reference) -{ - const PDFObject& object = document->getObjectByReference(reference); - - if (object.isNull()) - { - return 0; - } - - PDFSizeCounterIODevice device(nullptr); - - device.open(QIODevice::WriteOnly); - - PDFWriteObjectVisitor visitor(&device); - writeObjectHeader(&device, reference); - object.accept(&visitor); - writeObjectFooter(&device); - - device.close(); - return device.pos(); -} - -QByteArray PDFDocumentWriter::getSerializedObject(const PDFObject& object) -{ - QBuffer buffer; - - if (buffer.open(QBuffer::WriteOnly)) - { - PDFWriteObjectVisitor visitor(&buffer); - object.accept(&visitor); - - buffer.close(); - } - - return buffer.data(); -} - -} // namespace pdf +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfdocumentwriter.h" +#include "pdfconstants.h" +#include "pdfvisitor.h" +#include "pdfparser.h" + +#include +#include + +namespace pdf +{ + +class PDFWriteObjectVisitor : public PDFAbstractVisitor +{ +public: + explicit PDFWriteObjectVisitor(QIODevice* device) : + m_device(device) + { + + } + + virtual void visitNull() override; + virtual void visitBool(bool value) override; + virtual void visitInt(PDFInteger value) override; + virtual void visitReal(PDFReal value) override; + virtual void visitString(PDFStringRef string) override; + virtual void visitName(PDFStringRef name) override; + virtual void visitArray(const PDFArray* array) override; + virtual void visitDictionary(const PDFDictionary* dictionary) override; + virtual void visitStream(const PDFStream* stream) override; + virtual void visitReference(const PDFObjectReference reference) override; + + PDFObject getDecryptedObject(); + +private: + void writeName(const QByteArray& string); + + QIODevice* m_device; +}; + +void PDFWriteObjectVisitor::visitNull() +{ + m_device->write("null "); +} + +void PDFWriteObjectVisitor::visitBool(bool value) +{ + if (value) + { + m_device->write("true "); + } + else + { + m_device->write("false "); + } +} + +void PDFWriteObjectVisitor::visitInt(PDFInteger value) +{ + m_device->write(QString::number(value).toLatin1()); + m_device->write(" "); +} + +void PDFWriteObjectVisitor::visitReal(PDFReal value) +{ + // Jakub Melka: we use 5 digits, because they are specified + // in PDF 1.7 specification, appendix C, Table C.1, where it is defined, + // that number of significant digits of precision is 5. + m_device->write(QString::number(value, 'f', 5).toLatin1()); + m_device->write(" "); +} + +void PDFWriteObjectVisitor::visitString(PDFStringRef string) +{ + QByteArray data = string.getString(); + if (data.indexOf('(') != -1 || + data.indexOf(')') != -1 || + data.indexOf('\\') != -1) + { + m_device->write("<"); + m_device->write(data.toHex()); + m_device->write(">"); + } + else + { + m_device->write("("); + m_device->write(data); + m_device->write(")"); + } + + m_device->write(" "); +} + +void PDFWriteObjectVisitor::writeName(const QByteArray& string) +{ + m_device->write("/"); + + for (const char character : string) + { + if (PDFLexicalAnalyzer::isRegular(character)) + { + m_device->write(&character, 1); + } + else + { + m_device->write("#"); + m_device->write(QByteArray(&character, 1).toHex()); + } + } + + m_device->write(" "); +} + +void PDFWriteObjectVisitor::visitName(PDFStringRef name) +{ + writeName(name.getString()); +} + +void PDFWriteObjectVisitor::visitArray(const PDFArray* array) +{ + m_device->write("[ "); + acceptArray(array); + m_device->write("] "); +} + +void PDFWriteObjectVisitor::visitDictionary(const PDFDictionary* dictionary) +{ + m_device->write("<< "); + + for (size_t i = 0, count = dictionary->getCount(); i < count; ++i) + { + writeName(dictionary->getKey(i).getString()); + dictionary->getValue(i).accept(this); + } + + m_device->write(">> "); +} + +void PDFWriteObjectVisitor::visitStream(const PDFStream* stream) +{ + visitDictionary(stream->getDictionary()); + + m_device->write("stream"); + m_device->write("\x0D\x0A"); + m_device->write(*stream->getContent()); + m_device->write("\x0D\x0A"); + m_device->write("endstream"); + m_device->write("\x0D\x0A"); +} + +void PDFWriteObjectVisitor::visitReference(const PDFObjectReference reference) +{ + visitInt(reference.objectNumber); + visitInt(reference.generation); + m_device->write("R "); +} + +PDFOperationResult PDFDocumentWriter::write(const QString& fileName, const PDFDocument* document, bool safeWrite) +{ + Q_ASSERT(document); + + const PDFObjectStorage& storage = document->getStorage(); + if (!storage.getSecurityHandler()->isEncryptionAllowed()) + { + return tr("Writing of encrypted documents is not supported."); + } + + if (safeWrite) + { + QSaveFile file(fileName); + file.setDirectWriteFallback(true); + + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + PDFOperationResult result = write(&file, document); + if (result) + { + file.commit(); + } + else + { + file.cancelWriting(); + } + return result; + } + else + { + return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString()); + } + } + else + { + QFile file(fileName); + + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + PDFOperationResult result = write(&file, document); + file.close(); + + if (!result) + { + // If some error occured, then remove invalid file + file.remove(); + } + + return result; + } + else + { + return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString()); + } + } +} + +PDFOperationResult PDFDocumentWriter::write(QIODevice* device, const PDFDocument* document) +{ + if (!device->isWritable()) + { + return tr("Device is not writable."); + } + + const PDFObjectStorage& storage = document->getStorage(); + const PDFObjectStorage::PDFObjects& objects = storage.getObjects(); + const size_t objectCount = objects.size(); + const bool isEncrypted = storage.getSecurityHandler()->getMode() != EncryptionMode::None; + if (!storage.getSecurityHandler()->isEncryptionAllowed()) + { + return tr("Writing of encrypted documents is not supported."); + } + + // Write header + PDFVersion version = document->getInfo()->version; + device->write(QString("%PDF-%1.%2").arg(version.major).arg(version.minor).toLatin1()); + writeCRLF(device); + device->write("% PDF producer: "); + device->write(PDF_LIBRARY_NAME); + writeCRLF(device); + writeCRLF(device); + writeCRLF(device); + + PDFObjectReference encryptObjectReference; + PDFObject encryptObject = document->getTrailerDictionary()->get("Encrypt"); + if (encryptObject.isReference()) + { + encryptObjectReference = encryptObject.getReference(); + } + + // Write objects + std::vector offsets(objectCount, -1); + for (size_t i = 0; i < objectCount; ++i) + { + const PDFObjectStorage::Entry& entry = objects[i]; + if (entry.object.isNull()) + { + continue; + } + + // Jakub Melka: we must mark actual position of object + offsets[i] = device->pos(); + + if (isEncrypted) + { + PDFObjectReference reference(i, entry.generation); + PDFObject objectToWrite = entry.object; + + if (reference != encryptObjectReference) + { + objectToWrite = storage.getSecurityHandler()->encryptObject(objectToWrite, reference); + } + + PDFWriteObjectVisitor visitor(device); + writeObjectHeader(device, reference); + objectToWrite.accept(&visitor); + writeObjectFooter(device); + } + else + { + PDFWriteObjectVisitor visitor(device); + writeObjectHeader(device, PDFObjectReference(i, entry.generation)); + entry.object.accept(&visitor); + writeObjectFooter(device); + } + } + + // Write cross-reference table + PDFInteger xrefOffset = device->pos(); + device->write("xref"); + writeCRLF(device); + device->write(QString("0 %1").arg(objectCount).toLatin1()); + writeCRLF(device); + + for (size_t i = 0; i < objectCount; ++i) + { + const PDFObjectStorage::Entry& entry = objects[i]; + PDFInteger generation = entry.generation; + + if (i == 0) + { + generation = 65535; + } + + PDFInteger offset = offsets[i]; + if (offset == -1) + { + offset = 0; + } + + QString offsetString = QString::number(offset).rightJustified(10, QChar('0'), true); + QString generationString = QString::number(generation).rightJustified(5, QChar('0'), true); + + device->write(offsetString.toLatin1()); + device->write(" "); + device->write(generationString.toLatin1()); + device->write(" "); + device->write(entry.object.isNull() ? "f" : "n"); + writeCRLF(device); + } + + // Jakub Melka: Adjust trailer dictionary, to be really dictionary, not a stream + PDFDictionary trailerDictionary = *document->getTrailerDictionary(); + PDFDictionary newTrailerDictionary; + + for (const char* entry : { "Size", "Root", "Encrypt", "Info", "ID"}) + { + PDFObject object = trailerDictionary.get(entry); + if (!object.isNull()) + { + newTrailerDictionary.addEntry(PDFInplaceOrMemoryString(entry), qMove(object)); + } + } + + PDFObject trailerDictionaryObject = PDFObject::createDictionary(std::make_shared(qMove(newTrailerDictionary))); + + device->write("trailer"); + writeCRLF(device); + PDFWriteObjectVisitor trailerVisitor(device); + trailerDictionaryObject.accept(&trailerVisitor); + writeCRLF(device); + device->write("startxref"); + writeCRLF(device); + device->write(QString::number(xrefOffset).toLatin1()); + writeCRLF(device); + + // Write footer + device->write("%%EOF"); + + return true; +} + +void PDFDocumentWriter::writeCRLF(QIODevice* device) +{ + device->write("\x0D\x0A"); +} + +void PDFDocumentWriter::writeObjectHeader(QIODevice* device, PDFObjectReference reference) +{ + QString objectHeader = QString("%1 %2 obj").arg(QString::number(reference.objectNumber), QString::number(reference.generation)); + device->write(objectHeader.toLatin1()); + writeCRLF(device); +} + +void PDFDocumentWriter::writeObjectFooter(QIODevice* device) +{ + device->write("endobj"); + writeCRLF(device); +} + +class PDFSizeCounterIODevice : public QIODevice +{ +public: + explicit PDFSizeCounterIODevice(QObject* parent) : + QIODevice(parent) + { + + } + + virtual bool isSequential() const override; + virtual bool open(OpenMode mode) override; + virtual void close() override; + virtual qint64 pos() const override; + virtual qint64 size() const override; + virtual bool seek(qint64 pos) override; + virtual bool atEnd() const override; + virtual bool reset() override; + virtual qint64 bytesAvailable() const override; + virtual qint64 bytesToWrite() const override; + virtual bool canReadLine() const override; + virtual bool waitForReadyRead(int msecs) override; + virtual bool waitForBytesWritten(int msecs) override; + +protected: + virtual qint64 readData(char* data, qint64 maxlen) override; + virtual qint64 readLineData(char* data, qint64 maxlen) override; + virtual qint64 writeData(const char* data, qint64 len) override; + +private: + OpenMode m_openMode = NotOpen; + qint64 m_fileSize = 0; +}; + +bool PDFSizeCounterIODevice::isSequential() const +{ + return true; +} + +bool PDFSizeCounterIODevice::open(OpenMode mode) +{ + if (m_openMode == NotOpen) + { + setOpenMode(mode); + return true; + } + else + { + return false; + } +} + +void PDFSizeCounterIODevice::close() +{ + setOpenMode(NotOpen); +} + +qint64 PDFSizeCounterIODevice::pos() const +{ + return m_fileSize; +} + +qint64 PDFSizeCounterIODevice::size() const +{ + return m_fileSize; +} + +bool PDFSizeCounterIODevice::seek(qint64 pos) +{ + Q_UNUSED(pos); + + return false; +} + +bool PDFSizeCounterIODevice::atEnd() const +{ + return true; +} + +bool PDFSizeCounterIODevice::reset() +{ + return false; +} + +qint64 PDFSizeCounterIODevice::bytesAvailable() const +{ + return 0; +} + +qint64 PDFSizeCounterIODevice::bytesToWrite() const +{ + return 0; +} + +bool PDFSizeCounterIODevice::canReadLine() const +{ + return false; +} + +bool PDFSizeCounterIODevice::waitForReadyRead(int msecs) +{ + Q_UNUSED(msecs); + + return false; +} + +bool PDFSizeCounterIODevice::waitForBytesWritten(int msecs) +{ + Q_UNUSED(msecs); + + return false; +} + +qint64 PDFSizeCounterIODevice::readData(char* data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + + return 0; +} + +qint64 PDFSizeCounterIODevice::readLineData(char* data, qint64 maxlen) +{ + Q_UNUSED(data); + Q_UNUSED(maxlen); + + return 0; +} + +qint64 PDFSizeCounterIODevice::writeData(const char* data, qint64 len) +{ + Q_UNUSED(data); + + m_fileSize += len; + return len; +} + +qint64 PDFDocumentWriter::getDocumentFileSize(const PDFDocument* document) +{ + PDFSizeCounterIODevice device(nullptr); + PDFDocumentWriter writer(nullptr); + + device.open(QIODevice::WriteOnly); + + if (writer.write(&device, document)) + { + device.close(); + return device.pos(); + } + + device.close(); + return -1; +} + +qint64 PDFDocumentWriter::getObjectSize(const PDFDocument* document, PDFObjectReference reference) +{ + const PDFObject& object = document->getObjectByReference(reference); + + if (object.isNull()) + { + return 0; + } + + PDFSizeCounterIODevice device(nullptr); + + device.open(QIODevice::WriteOnly); + + PDFWriteObjectVisitor visitor(&device); + writeObjectHeader(&device, reference); + object.accept(&visitor); + writeObjectFooter(&device); + + device.close(); + return device.pos(); +} + +QByteArray PDFDocumentWriter::getSerializedObject(const PDFObject& object) +{ + QBuffer buffer; + + if (buffer.open(QBuffer::WriteOnly)) + { + PDFWriteObjectVisitor visitor(&buffer); + object.accept(&visitor); + + buffer.close(); + } + + return buffer.data(); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdocumentwriter.h b/Pdf4QtLib/sources/pdfdocumentwriter.h index f3cb382..7524da9 100644 --- a/Pdf4QtLib/sources/pdfdocumentwriter.h +++ b/Pdf4QtLib/sources/pdfdocumentwriter.h @@ -1,86 +1,86 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDOCUMENTWRITER_H -#define PDFDOCUMENTWRITER_H - -#include "pdfdocument.h" -#include "pdfprogress.h" -#include "pdfutils.h" - -#include - -namespace pdf -{ - -/// Class used for writing PDF documents to the desired target device (or file, -/// buffer, etc.). If writing is not successful, then error message is returned. -class PDF4QTLIBSHARED_EXPORT PDFDocumentWriter -{ - Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentWriter) - -public: - explicit inline PDFDocumentWriter(PDFProgress* progress) : - m_progress(progress) - { - - } - - /// Writes document to the file. If \p safeWrite is true, then document is first - /// written to the temporary file, and then renamed to original file name atomically, - /// so no data can be lost on, for example, power failure. If it is not possible to - /// create temporary file, then writing operation will attempt to write to the file - /// directly. - /// \param fileName File name - /// \param document Document - /// \param safeWrite Write document to the temporary file and then rename - PDFOperationResult write(const QString& fileName, const PDFDocument* document, bool safeWrite); - - /// Write document to the output device. Device must be writable (i.e. opened - /// for writing). - /// \param device Output device - /// \param document Document - PDFOperationResult write(QIODevice* device, const PDFDocument* document); - - /// Calculates document file size, as if it is written to the disk. - /// No file is accessed by this function; document is written - /// to fake stream, which counts operations. If error occurs, and - /// size can't be determined, then -1 is returned. - /// \param document Document - static qint64 getDocumentFileSize(const PDFDocument* document); - - /// Calculates size estimate of an object. If object is null, then zero is returned. - /// \param document Document - /// \param reference Reference - static qint64 getObjectSize(const PDFDocument* document, PDFObjectReference reference); - - /// Writes an object to byte array, without object header/footer - /// \param object Object to be written - static QByteArray getSerializedObject(const PDFObject& object); - -private: - static void writeCRLF(QIODevice* device); - static void writeObjectHeader(QIODevice* device, PDFObjectReference reference); - static void writeObjectFooter(QIODevice* device); - - /// Progress indicator - PDFProgress* m_progress; -}; - -} // namespace pdf - -#endif // PDFDOCUMENTWRITER_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDOCUMENTWRITER_H +#define PDFDOCUMENTWRITER_H + +#include "pdfdocument.h" +#include "pdfprogress.h" +#include "pdfutils.h" + +#include + +namespace pdf +{ + +/// Class used for writing PDF documents to the desired target device (or file, +/// buffer, etc.). If writing is not successful, then error message is returned. +class PDF4QTLIBSHARED_EXPORT PDFDocumentWriter +{ + Q_DECLARE_TR_FUNCTIONS(pdf::PDFDocumentWriter) + +public: + explicit inline PDFDocumentWriter(PDFProgress* progress) : + m_progress(progress) + { + + } + + /// Writes document to the file. If \p safeWrite is true, then document is first + /// written to the temporary file, and then renamed to original file name atomically, + /// so no data can be lost on, for example, power failure. If it is not possible to + /// create temporary file, then writing operation will attempt to write to the file + /// directly. + /// \param fileName File name + /// \param document Document + /// \param safeWrite Write document to the temporary file and then rename + PDFOperationResult write(const QString& fileName, const PDFDocument* document, bool safeWrite); + + /// Write document to the output device. Device must be writable (i.e. opened + /// for writing). + /// \param device Output device + /// \param document Document + PDFOperationResult write(QIODevice* device, const PDFDocument* document); + + /// Calculates document file size, as if it is written to the disk. + /// No file is accessed by this function; document is written + /// to fake stream, which counts operations. If error occurs, and + /// size can't be determined, then -1 is returned. + /// \param document Document + static qint64 getDocumentFileSize(const PDFDocument* document); + + /// Calculates size estimate of an object. If object is null, then zero is returned. + /// \param document Document + /// \param reference Reference + static qint64 getObjectSize(const PDFDocument* document, PDFObjectReference reference); + + /// Writes an object to byte array, without object header/footer + /// \param object Object to be written + static QByteArray getSerializedObject(const PDFObject& object); + +private: + static void writeCRLF(QIODevice* device); + static void writeObjectHeader(QIODevice* device, PDFObjectReference reference); + static void writeObjectFooter(QIODevice* device); + + /// Progress indicator + PDFProgress* m_progress; +}; + +} // namespace pdf + +#endif // PDFDOCUMENTWRITER_H diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp index 4c7c47f..51d26a7 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp @@ -1,1416 +1,1416 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#include "pdfdrawspacecontroller.h" -#include "pdfdrawwidget.h" -#include "pdfrenderer.h" -#include "pdfpainter.h" -#include "pdfcompiler.h" -#include "pdfconstants.h" -#include "pdfcms.h" -#include "pdfannotation.h" - -#include -#include - -namespace pdf -{ - -PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) : - QObject(parent), - m_document(nullptr), - m_optionalContentActivity(nullptr), - m_pageLayoutMode(PageLayout::OneColumn), - m_verticalSpacingMM(5.0), - m_horizontalSpacingMM(1.0), - m_pageRotation(PageRotation::None), - m_fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT) -{ - -} - -PDFDrawSpaceController::~PDFDrawSpaceController() -{ - -} - -void PDFDrawSpaceController::setDocument(const PDFModifiedDocument& document) -{ - if (document != m_document) - { - m_document = document; - m_fontCache.setDocument(document); - m_optionalContentActivity = document.getOptionalContentActivity(); - - // If document is not being reset, then recalculation is not needed, - // pages should remain the same. - if (document.hasReset()) - { - recalculate(); - } - } -} - -void PDFDrawSpaceController::setPageLayout(PageLayout pageLayout) -{ - if (m_pageLayoutMode != pageLayout) - { - m_pageLayoutMode = pageLayout; - recalculate(); - } -} - -QRectF PDFDrawSpaceController::getBlockBoundingRectangle(size_t blockIndex) const -{ - if (blockIndex < m_blockItems.size()) - { - return m_blockItems[blockIndex].blockRectMM; - } - - return QRectF(); -} - -PDFDrawSpaceController::LayoutItems PDFDrawSpaceController::getLayoutItems(size_t blockIndex) const -{ - LayoutItems result; - - auto comparator = [](const LayoutItem& l, const LayoutItem& r) - { - return l.blockIndex < r.blockIndex; - }; - Q_ASSERT(std::is_sorted(m_layoutItems.cbegin(), m_layoutItems.cend(), comparator)); - - LayoutItem templateItem; - templateItem.blockIndex = blockIndex; - - auto range = std::equal_range(m_layoutItems.cbegin(), m_layoutItems.cend(), templateItem, comparator); - result.reserve(std::distance(range.first, range.second)); - std::copy(range.first, range.second, std::back_inserter(result)); - - return result; -} - -PDFDrawSpaceController::LayoutItem PDFDrawSpaceController::getLayoutItemForPage(PDFInteger pageIndex) const -{ - LayoutItem result; - - if (pageIndex >= 0 && pageIndex < static_cast(m_layoutItems.size()) && m_layoutItems[pageIndex].pageIndex == pageIndex) - { - result = m_layoutItems[pageIndex]; - } - - if (!result.isValid()) - { - auto it = std::find_if(m_layoutItems.cbegin(), m_layoutItems.cend(), [pageIndex](const LayoutItem& item) { return item.pageIndex == pageIndex; }); - if (it != m_layoutItems.cend()) - { - result = *it; - } - } - - return result; -} - -QSizeF PDFDrawSpaceController::getReferenceBoundingBox() const -{ - QRectF rect; - - for (const LayoutItem& item : m_layoutItems) - { - QRectF pageRect = item.pageRectMM; - pageRect.translate(0, -pageRect.top()); - rect = rect.united(pageRect); - } - - if (rect.isValid()) - { - rect.adjust(0, 0, m_horizontalSpacingMM, m_verticalSpacingMM); - } - - return rect.size(); -} - -void PDFDrawSpaceController::setPageRotation(PageRotation pageRotation) -{ - if (m_pageRotation != pageRotation) - { - m_pageRotation = pageRotation; - recalculate(); - } -} - -void PDFDrawSpaceController::recalculate() -{ - if (!m_document) - { - clear(true); - return; - } - - const PDFCatalog* catalog = m_document->getCatalog(); - size_t pageCount = catalog->getPageCount(); - - // Clear the old draw space - clear(false); - - static constexpr size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); - - // Places the pages on the left/right sides. Pages can be nullptr, but not both of them. - // Updates bounding rectangle. - auto placePagesLeftRight = [this, catalog](PDFInteger blockIndex, size_t leftIndex, size_t rightIndex, PDFReal& yPos, QRectF& boundingRect) - { - PDFReal yPosAdvance = 0.0; - - if (leftIndex != INVALID_PAGE_INDEX) - { - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(leftIndex)->getRotatedMediaBoxMM(), m_pageRotation).size(); - PDFReal xPos = -pageSize.width() - m_horizontalSpacingMM * 0.5; - QRectF rect(xPos, yPos, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(blockIndex, leftIndex, rect); - yPosAdvance = qMax(yPosAdvance, pageSize.height()); - boundingRect = boundingRect.united(rect); - } - - if (rightIndex != INVALID_PAGE_INDEX) - { - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(rightIndex)->getRotatedMediaBoxMM(), m_pageRotation).size(); - PDFReal xPos = m_horizontalSpacingMM * 0.5; - QRectF rect(xPos, yPos, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(blockIndex, rightIndex, rect); - yPosAdvance = qMax(yPosAdvance, pageSize.height()); - boundingRect = boundingRect.united(rect); - } - - if (yPosAdvance > 0.0) - { - yPos += yPosAdvance + m_verticalSpacingMM; - } - }; - - // Generates block with pages using page indices. If generateBlocks is true, then - // for each pair of pages, single block is generated, otherwise block containing all - // pages is generated. - auto placePagesLeftRightByIndices = [this, &placePagesLeftRight](const std::vector& indices, bool generateBlocks) - { - Q_ASSERT(indices.size() % 2 == 0); - - PDFReal yPos = 0.0; - PDFInteger blockIndex = 0; - QRectF boundingRectangle; - - size_t count = indices.size() / 2; - for (size_t i = 0; i < count; ++i) - { - const size_t leftPageIndex = indices[2 * i]; - const size_t rightPageIndex = indices[2 * i + 1]; - placePagesLeftRight(blockIndex, leftPageIndex, rightPageIndex, yPos, boundingRectangle); - - if (generateBlocks) - { - m_blockItems.emplace_back(boundingRectangle); - - // Clear the old data - yPos = 0.0; - ++blockIndex; - boundingRectangle = QRectF(); - } - } - - if (!generateBlocks) - { - // Generate single block for all layed out pages - m_blockItems.emplace_back(boundingRectangle); - } - }; - - switch (m_pageLayoutMode) - { - case PageLayout::SinglePage: - { - // Each block contains single page - m_layoutItems.reserve(pageCount); - m_blockItems.reserve(pageCount); - - // Pages can have different size, so we center them around the center. - // Block size will equal to the page size. - - for (size_t i = 0; i < pageCount; ++i) - { - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getRotatedMediaBoxMM(), m_pageRotation).size(); - QRectF rect(-pageSize.width() * 0.5, -pageSize.height() * 0.5, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(i, i, rect); - m_blockItems.emplace_back(rect); - } - - break; - } - - case PageLayout::OneColumn: - { - // Single block, one column - m_layoutItems.reserve(pageCount); - m_blockItems.reserve(1); - - PDFReal yPos = 0.0; - QRectF boundingRectangle; - - for (size_t i = 0; i < pageCount; ++i) - { - // Top of current page is at yPos. - QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getRotatedMediaBoxMM(), m_pageRotation).size(); - QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); - m_layoutItems.emplace_back(0, i, rect); - yPos += pageSize.height() + m_verticalSpacingMM; - boundingRectangle = boundingRectangle.united(rect); - } - - // Insert the single block with union of bounding rectangles - m_blockItems.emplace_back(boundingRectangle); - - break; - } - - case PageLayout::TwoColumnLeft: - { - // Pages with number 1, 3, 5, ... are on the left, 2, 4, 6 are on the right. - // Page indices are numbered from 0, so pages 0, 2, 4 will be on the left, - // 1, 3, 5 will be on the right. - // For purposes or paging, "left" pages will be on the left side of y axis (negative x axis), - // the "right" pages will be on the right side of y axis (positive x axis). - - m_layoutItems.reserve(pageCount); - m_blockItems.reserve(1); - - std::vector pageIndices(pageCount, INVALID_PAGE_INDEX); - std::iota(pageIndices.begin(), pageIndices.end(), static_cast(0)); - - if (pageIndices.size() % 2 == 1) - { - pageIndices.push_back(INVALID_PAGE_INDEX); - } - - placePagesLeftRightByIndices(pageIndices, false); - break; - } - - case PageLayout::TwoColumnRight: - { - // Similar to previous case, but page sequence start on the right. - - m_layoutItems.reserve(pageCount); - m_blockItems.reserve(1); - - std::vector pageIndices(pageCount + 1, INVALID_PAGE_INDEX); - std::iota(std::next(pageIndices.begin()), pageIndices.end(), static_cast(0)); - - if (pageIndices.size() % 2 == 1) - { - pageIndices.push_back(INVALID_PAGE_INDEX); - } - - placePagesLeftRightByIndices(pageIndices, false); - break; - } - - case PageLayout::TwoPagesLeft: - { - m_layoutItems.reserve(pageCount); - m_blockItems.reserve((pageCount / 2) + (pageCount % 2)); - - std::vector pageIndices(pageCount, INVALID_PAGE_INDEX); - std::iota(pageIndices.begin(), pageIndices.end(), static_cast(0)); - - if (pageIndices.size() % 2 == 1) - { - pageIndices.push_back(INVALID_PAGE_INDEX); - } - - placePagesLeftRightByIndices(pageIndices, true); - break; - } - - case PageLayout::TwoPagesRight: - { - m_layoutItems.reserve(pageCount); - m_blockItems.reserve((pageCount / 2) + (pageCount % 2)); - - std::vector pageIndices(pageCount + 1, INVALID_PAGE_INDEX); - std::iota(std::next(pageIndices.begin()), pageIndices.end(), static_cast(0)); - - if (pageIndices.size() % 2 == 1) - { - pageIndices.push_back(INVALID_PAGE_INDEX); - } - - placePagesLeftRightByIndices(pageIndices, true); - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } - - emit drawSpaceChanged(); -} - -void PDFDrawSpaceController::clear(bool emitSignal) -{ - m_layoutItems.clear(); - m_blockItems.clear(); - - if (emitSignal) - { - emit drawSpaceChanged(); - } -} - -PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) : - QObject(parent), - m_updateDisabled(false), - m_currentBlock(INVALID_BLOCK_INDEX), - m_pixelPerMM(PDF_DEFAULT_DPMM), - m_zoom(1.0), - m_pixelToDeviceSpaceUnit(0.0), - m_deviceSpaceUnitToPixel(0.0), - m_verticalOffset(0), - m_horizontalOffset(0), - m_controller(nullptr), - m_widget(nullptr), - m_verticalScrollbar(nullptr), - m_horizontalScrollbar(nullptr), - m_features(PDFRenderer::getDefaultFeatures()), - m_compiler(new PDFAsynchronousPageCompiler(this)), - m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)), - m_rasterizer(new PDFRasterizer(this)), - m_progress(nullptr), - m_useOpenGL(false) -{ - m_controller = new PDFDrawSpaceController(this); - connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update); - connect(m_controller, &PDFDrawSpaceController::repaintNeeded, this, &PDFDrawWidgetProxy::repaintNeeded); - connect(m_controller, &PDFDrawSpaceController::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged); - connect(m_compiler, &PDFAsynchronousPageCompiler::renderingError, this, &PDFDrawWidgetProxy::renderingError); - connect(m_compiler, &PDFAsynchronousPageCompiler::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged); - connect(m_textLayoutCompiler, &PDFAsynchronousTextLayoutCompiler::textLayoutChanged, this, &PDFDrawWidgetProxy::onTextLayoutChanged); -} - -PDFDrawWidgetProxy::~PDFDrawWidgetProxy() -{ - -} - -void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document) -{ - if (getDocument() != document) - { - m_compiler->stop(document.hasReset()); - m_textLayoutCompiler->stop(document.hasReset()); - m_controller->setDocument(document); - - if (PDFOptionalContentActivity* optionalContentActivity = document.getOptionalContentActivity()) - { - connect(optionalContentActivity, &PDFOptionalContentActivity::optionalContentGroupStateChanged, this, &PDFDrawWidgetProxy::onOptionalContentGroupStateChanged, Qt::UniqueConnection); - } - - m_compiler->start(); - m_textLayoutCompiler->start(); - } -} - -void PDFDrawWidgetProxy::init(PDFWidget* widget) -{ - m_widget = widget; - m_horizontalScrollbar = widget->getHorizontalScrollbar(); - m_verticalScrollbar = widget->getVerticalScrollbar(); - - connect(m_horizontalScrollbar, &QScrollBar::valueChanged, this, &PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged); - connect(m_verticalScrollbar, &QScrollBar::valueChanged, this, &PDFDrawWidgetProxy::onVerticalScrollbarValueChanged); - connect(this, &PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFDrawWidgetProxy::repaintNeeded); - connect(getCMSManager(), &PDFCMSManager::colorManagementSystemChanged, this, &PDFDrawWidgetProxy::onColorManagementSystemChanged); - - // We must update the draw space - widget has been set - update(); -} - -void PDFDrawWidgetProxy::update() -{ - if (m_updateDisabled) - { - return; - } - PDFBoolGuard guard(m_updateDisabled); - - Q_ASSERT(m_widget); - Q_ASSERT(m_horizontalScrollbar); - Q_ASSERT(m_verticalScrollbar); - - QWidget* widget = m_widget->getDrawWidget()->getWidget(); - - // First, we must calculate pixel per mm ratio to obtain DPMM (device pixel per mm), - // we also assume, that zoom is correctly set. - m_pixelPerMM = static_cast(widget->width()) / static_cast(widget->widthMM()); - - Q_ASSERT(m_zoom > 0.0); - Q_ASSERT(m_pixelPerMM > 0.0); - - m_deviceSpaceUnitToPixel = m_pixelPerMM * m_zoom; - m_pixelToDeviceSpaceUnit = 1.0 / m_deviceSpaceUnitToPixel; - - m_layout.clear(); - - // Switch to the first block, if we haven't selected any, otherwise fix active - // block item (select first block available). - if (m_controller->getBlockCount() > 0) - { - if (m_currentBlock == INVALID_BLOCK_INDEX) - { - m_currentBlock = 0; - } - else - { - m_currentBlock = qBound(0, m_currentBlock, m_controller->getBlockCount() - 1); - } - } - else - { - m_currentBlock = INVALID_BLOCK_INDEX; - } - - // Then, create pixel size layout of the pages using the draw space controller - QRectF rectangle = m_controller->getBlockBoundingRectangle(m_currentBlock); - if (rectangle.isValid()) - { - // We must have a valid block - PDFDrawSpaceController::LayoutItems items = m_controller->getLayoutItems(m_currentBlock); - - m_layout.items.reserve(items.size()); - for (const PDFDrawSpaceController::LayoutItem& item : items) - { - m_layout.items.emplace_back(item.pageIndex, fromDeviceSpace(item.pageRectMM).toRect()); - } - - m_layout.blockRect = fromDeviceSpace(rectangle).toRect(); - } - - QSize blockSize = m_layout.blockRect.size(); - QSize widgetSize = widget->size(); - - // Horizontal scrollbar - const int horizontalDifference = blockSize.width() - widgetSize.width(); - if (horizontalDifference > 0) - { - m_horizontalScrollbar->setVisible(true); - m_horizontalScrollbar->setMinimum(0); - m_horizontalScrollbar->setMaximum(horizontalDifference); - - m_horizontalOffsetRange = Range(-horizontalDifference, 0); - m_horizontalOffset = m_horizontalOffsetRange.bound(m_horizontalOffset); - m_horizontalScrollbar->setValue(-m_horizontalOffset); - } - else - { - // We do not need the horizontal scrollbar, because block can be draw onto widget entirely. - // We set the offset to the half of available empty space. - m_horizontalScrollbar->setVisible(false); - m_horizontalOffset = -horizontalDifference / 2; - m_horizontalOffsetRange = Range(m_horizontalOffset); - } - - // Vertical scrollbar - has two meanings, in block mode, it switches between blocks, - // in continuous mode, it controls the vertical offset. - if (isBlockMode()) - { - size_t blockCount = m_controller->getBlockCount(); - if (blockCount > 0) - { - Q_ASSERT(m_currentBlock != INVALID_BLOCK_INDEX); - - m_verticalScrollbar->setVisible(blockCount > 1); - m_verticalScrollbar->setMinimum(0); - m_verticalScrollbar->setMaximum(static_cast(blockCount - 1)); - m_verticalScrollbar->setValue(static_cast(m_currentBlock)); - m_verticalScrollbar->setSingleStep(1); - m_verticalScrollbar->setPageStep(1); - } - else - { - Q_ASSERT(m_currentBlock == INVALID_BLOCK_INDEX); - m_verticalScrollbar->setVisible(false); - } - - // We must fix case, when we can display everything on the widget (we have - // enough space). Then we will center the page on the widget. - const int verticalDifference = blockSize.height() - widgetSize.height(); - if (verticalDifference < 0) - { - m_verticalOffset = -verticalDifference / 2; - m_verticalOffsetRange = Range(m_verticalOffset); - } - else - { - m_verticalOffsetRange = Range(-verticalDifference, 0); - m_verticalOffset = m_verticalOffsetRange.bound(m_verticalOffset); - } - } - else - { - const int verticalDifference = blockSize.height() - widgetSize.height(); - if (verticalDifference > 0) - { - m_verticalScrollbar->setVisible(true); - m_verticalScrollbar->setMinimum(0); - m_verticalScrollbar->setMaximum(verticalDifference); - - // We must also calculate single step/page step. Because pages can have different size, - // we use first page to compute page step. - if (!m_layout.items.empty()) - { - const LayoutItem& item = m_layout.items.front(); - - const int pageStep = qMax(item.pageRect.height(), 1); - const int singleStep = qMax(pageStep / 10, 1); - - m_verticalScrollbar->setPageStep(pageStep); - m_verticalScrollbar->setSingleStep(singleStep); - } - - m_verticalOffsetRange = Range(-verticalDifference, 0); - m_verticalOffset = m_verticalOffsetRange.bound(m_verticalOffset); - m_verticalScrollbar->setValue(-m_verticalOffset); - } - else - { - m_verticalScrollbar->setVisible(false); - m_verticalOffset = -verticalDifference / 2; - m_verticalOffsetRange = Range(m_verticalOffset); - } - } - - emit drawSpaceChanged(); -} - -QMatrix PDFDrawWidgetProxy::createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const -{ - QMatrix matrix; - - // We want to create transformation from unrotated rectangle - // to rotated page rectangle. - - QRectF unrotatedRectangle = rectangle; - switch (m_controller->getPageRotation()) - { - case PageRotation::None: - break; - - case PageRotation::Rotate180: - { - matrix.translate(0, rectangle.top() + rectangle.bottom()); - matrix.scale(1.0, -1.0); - Q_ASSERT(qFuzzyCompare(matrix.map(rectangle.topLeft()).y(), rectangle.bottom())); - Q_ASSERT(qFuzzyCompare(matrix.map(rectangle.bottomLeft()).y(), rectangle.top())); - break; - } - - case PageRotation::Rotate90: - { - unrotatedRectangle = rectangle.transposed(); - matrix.translate(rectangle.left(), rectangle.top()); - matrix.rotate(90); - matrix.translate(-rectangle.left(), -rectangle.top()); - matrix.translate(0, -unrotatedRectangle.height()); - break; - } - - case PageRotation::Rotate270: - { - unrotatedRectangle = rectangle.transposed(); - matrix.translate(rectangle.left(), rectangle.top()); - matrix.rotate(90); - matrix.translate(-rectangle.left(), -rectangle.top()); - matrix.translate(0, -unrotatedRectangle.height()); - matrix.translate(0.0, unrotatedRectangle.top() + unrotatedRectangle.bottom()); - matrix.scale(1.0, -1.0); - matrix.translate(unrotatedRectangle.left() + unrotatedRectangle.right(), 0.0); - matrix.scale(-1.0, 1.0); - break; - } - - default: - break; - } - - return PDFRenderer::createPagePointToDevicePointMatrix(page, unrotatedRectangle) * matrix; -} - -void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect) -{ - drawPages(painter, rect, m_features); - - for (IDocumentDrawInterface* drawInterface : m_drawInterfaces) - { - painter->save(); - drawInterface->drawPostRendering(painter, rect); - painter->restore(); - } -} - -QColor PDFDrawWidgetProxy::getPaperColor() -{ - QColor paperColor = getCMSManager()->getCurrentCMS()->getPaperColor(); - if (m_features.testFlag(PDFRenderer::InvertColors)) - { - paperColor = invertColor(paperColor); - } - - return paperColor; -} - -void PDFDrawWidgetProxy::drawPages(QPainter* painter, QRect rect, PDFRenderer::Features features) -{ - painter->fillRect(rect, Qt::lightGray); - QMatrix baseMatrix = painter->worldMatrix(); - - // Use current paper color (it can be a bit different from white) - QColor paperColor = getPaperColor(); - - // Iterate trough pages and display them on the painter device - for (const LayoutItem& item : m_layout.items) - { - // The offsets m_horizontalOffset and m_verticalOffset are offsets to the - // topleft point of the block. But block maybe doesn't start at (0, 0), - // so we must also use translation from the block beginning. - QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); - if (placedRect.intersects(rect)) - { - // Clear the page space by paper color - painter->fillRect(placedRect, paperColor); - - const PDFPrecompiledPage* compiledPage = m_compiler->getCompiledPage(item.pageIndex, true); - if (compiledPage && compiledPage->isValid()) - { - QElapsedTimer timer; - timer.start(); - - const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); - QMatrix matrix = createPagePointToDevicePointMatrix(page, placedRect) * baseMatrix; - compiledPage->draw(painter, page->getCropBox(), matrix, features); - PDFTextLayoutGetter layoutGetter = m_textLayoutCompiler->getTextLayoutLazy(item.pageIndex); - - // Draw text blocks/text lines, if it is enabled - if (features.testFlag(PDFRenderer::DebugTextBlocks)) - { - m_textLayoutCompiler->makeTextLayout(); - const PDFTextLayout& layout = layoutGetter; - const PDFTextBlocks& textBlocks = layout.getTextBlocks(); - - painter->save(); - painter->setFont(m_widget->font()); - painter->setPen(Qt::red); - painter->setBrush(QColor(255, 0, 0, 128)); - - QFontMetricsF fontMetrics(painter->font(), painter->device()); - int blockIndex = 1; - for (const PDFTextBlock& block : textBlocks) - { - QString blockNumber = QString::number(blockIndex++); - - painter->drawPath(matrix.map(block.getBoundingBox())); - painter->drawText(matrix.map(block.getTopLeft()) - QPointF(fontMetrics.width(blockNumber), 0), blockNumber, Qt::TextSingleLine, 0); - } - - painter->restore(); - } - if (features.testFlag(PDFRenderer::DebugTextLines)) - { - m_textLayoutCompiler->makeTextLayout(); - const PDFTextLayout& layout = layoutGetter; - const PDFTextBlocks& textBlocks = layout.getTextBlocks(); - - painter->save(); - painter->setFont(m_widget->font()); - painter->setPen(Qt::green); - painter->setBrush(QColor(0, 255, 0, 128)); - - QFontMetricsF fontMetrics(painter->font(), painter->device()); - int lineIndex = 1; - for (const PDFTextBlock& block : textBlocks) - { - for (const PDFTextLine& line : block.getLines()) - { - QString lineNumber = QString::number(lineIndex++); - - painter->drawPath(matrix.map(line.getBoundingBox())); - painter->drawText(matrix.map(line.getTopLeft()) - QPointF(fontMetrics.width(lineNumber), 0), lineNumber, Qt::TextSingleLine, 0); - } - } - - painter->restore(); - } - - QList drawInterfaceErrors; - if (!features.testFlag(PDFRenderer::DenyExtraGraphics)) - { - for (IDocumentDrawInterface* drawInterface : m_drawInterfaces) - { - painter->save(); - drawInterface->drawPage(painter, item.pageIndex, compiledPage, layoutGetter, matrix, drawInterfaceErrors); - painter->restore(); - } - } - - const qint64 drawTimeNS = timer.nsecsElapsed(); - - // Draw rendering times - if (features.testFlag(PDFRenderer::DisplayTimes)) - { - QFont font = m_widget->font(); - font.setPointSize(12); - - auto formatDrawTime = [](qint64 nanoseconds) - { - PDFReal miliseconds = nanoseconds / 1000000.0; - return QString::number(miliseconds, 'f', 3); - }; - - QFontMetrics fontMetrics(font); - const int lineSpacing = fontMetrics.lineSpacing(); - - painter->save(); - painter->setPen(Qt::red); - painter->setFont(font); - painter->translate(placedRect.topLeft()); - painter->translate(placedRect.width() / 20.0, placedRect.height() / 20.0); // Offset - - painter->setBackground(QBrush(Qt::white)); - painter->setBackgroundMode(Qt::OpaqueMode); - painter->drawText(0, 0, PDFTranslationContext::tr("Compile time: %1 [ms]").arg(formatDrawTime(compiledPage->getCompilingTimeNS()))); - painter->translate(0, lineSpacing); - painter->drawText(0, 0, PDFTranslationContext::tr("Draw time: %1 [ms]").arg(formatDrawTime(drawTimeNS))); - - painter->restore(); - } - - const QList& pageErrors = compiledPage->getErrors(); - if (!pageErrors.empty() || !drawInterfaceErrors.empty()) - { - QList errors = pageErrors; - if (!drawInterfaceErrors.isEmpty()) - { - errors.append(drawInterfaceErrors); - } - emit renderingError(item.pageIndex, qMove(errors)); - } - } - } - } -} - -QImage PDFDrawWidgetProxy::drawThumbnailImage(PDFInteger pageIndex, int pixelSize) const -{ - QImage image; - if (!m_controller->getDocument()) - { - // No thumbnail - return empty image - return image; - } - - if (const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(pageIndex)) - { - QRectF pageRect = page->getRotatedMediaBox(); - QSizeF pageSize = pageRect.size(); - pageSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio); - QSize imageSize = pageSize.toSize(); - - if (imageSize.isValid()) - { - const PDFPrecompiledPage* compiledPage = m_compiler->getCompiledPage(pageIndex, true); - if (compiledPage && compiledPage->isValid()) - { - // Rasterize the image. - image = m_rasterizer->render(pageIndex, page, compiledPage, imageSize, m_features, m_widget->getAnnotationManager(), PageRotation::None); - } - - if (image.isNull()) - { - image = QImage(imageSize, QImage::Format_RGBA8888_Premultiplied); - image.fill(Qt::white); - } - } - } - - return image; -} - -std::vector PDFDrawWidgetProxy::getPagesIntersectingRect(QRect rect) const -{ - std::vector pages; - - // We assume, that no more, than 32 pages will be displayed in the rectangle - pages.reserve(32); - - // Iterate trough pages, place them and test, if they intersects with rectangle - for (const LayoutItem& item : m_layout.items) - { - // The offsets m_horizontalOffset and m_verticalOffset are offsets to the - // topleft point of the block. But block maybe doesn't start at (0, 0), - // so we must also use translation from the block beginning. - QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); - if (placedRect.intersects(rect)) - { - pages.push_back(item.pageIndex); - } - } - std::sort(pages.begin(), pages.end()); - - return pages; -} - -PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const -{ - // Iterate trough pages, place them and test, if they intersects with rectangle - for (const LayoutItem& item : m_layout.items) - { - // The offsets m_horizontalOffset and m_verticalOffset are offsets to the - // topleft point of the block. But block maybe doesn't start at (0, 0), - // so we must also use translation from the block beginning. - QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); - placedRect.adjust(0, 0, 1, 1); - if (placedRect.contains(point)) - { - if (pagePoint) - { - const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); - QMatrix matrix = createPagePointToDevicePointMatrix(page, placedRect).inverted(); - *pagePoint = matrix.map(point); - } - - return item.pageIndex; - } - } - - return -1; -} - -PDFWidgetSnapshot PDFDrawWidgetProxy::getSnapshot() const -{ - PDFWidgetSnapshot snapshot; - - // Get the viewport - QRect viewport = getWidget()->rect(); - - // Iterate trough pages, place them and test, if they intersects with rectangle - for (const LayoutItem& item : m_layout.items) - { - // The offsets m_horizontalOffset and m_verticalOffset are offsets to the - // topleft point of the block. But block maybe doesn't start at (0, 0), - // so we must also use translation from the block beginning. - QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); - if (placedRect.intersects(viewport)) - { - const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); - - PDFWidgetSnapshot::SnapshotItem snapshotItem; - snapshotItem.rect = placedRect; - snapshotItem.pageIndex = item.pageIndex; - snapshotItem.compiledPage = m_compiler->getCompiledPage(item.pageIndex, false); - snapshotItem.pageToDeviceMatrix = createPagePointToDevicePointMatrix(page, placedRect); - snapshot.items.emplace_back(qMove(snapshotItem)); - } - } - - return snapshot; -} - -QRect PDFDrawWidgetProxy::getPagesIntersectingRectBoundingBox(QRect rect) const -{ - QRect resultRect; - - // Iterate trough pages, place them and test, if they intersects with rectangle - for (const LayoutItem& item : m_layout.items) - { - // The offsets m_horizontalOffset and m_verticalOffset are offsets to the - // topleft point of the block. But block maybe doesn't start at (0, 0), - // so we must also use translation from the block beginning. - QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); - if (placedRect.intersects(rect)) - { - resultRect = resultRect.united(placedRect); - } - } - - return resultRect; -} - -void PDFDrawWidgetProxy::performOperation(Operation operation) -{ - switch (operation) - { - case NavigateDocumentStart: - { - if (m_verticalScrollbar->isVisible()) - { - m_verticalScrollbar->setValue(0); - } - break; - } - - case NavigateDocumentEnd: - { - if (m_verticalScrollbar->isVisible()) - { - m_verticalScrollbar->setValue(m_verticalScrollbar->maximum()); - } - break; - } - - case NavigateNextPage: - { - if (m_verticalScrollbar->isVisible()) - { - m_verticalScrollbar->setValue(m_verticalScrollbar->value() + m_verticalScrollbar->pageStep()); - } - break; - } - - case NavigatePreviousPage: - { - if (m_verticalScrollbar->isVisible()) - { - m_verticalScrollbar->setValue(m_verticalScrollbar->value() - m_verticalScrollbar->pageStep()); - } - break; - } - - case NavigateNextStep: - { - if (m_verticalScrollbar->isVisible()) - { - m_verticalScrollbar->setValue(m_verticalScrollbar->value() + m_verticalScrollbar->singleStep()); - } - break; - } - - case NavigatePreviousStep: - { - if (m_verticalScrollbar->isVisible()) - { - m_verticalScrollbar->setValue(m_verticalScrollbar->value() - m_verticalScrollbar->singleStep()); - } - break; - } - - case ZoomIn: - { - zoom(m_zoom * ZOOM_STEP); - break; - } - - case ZoomOut: - { - zoom(m_zoom / ZOOM_STEP); - break; - } - - case ZoomFit: - { - zoom(getZoomHint(ZoomHint::Fit)); - break; - } - - case ZoomFitWidth: - { - zoom(getZoomHint(ZoomHint::FitWidth)); - break; - } - - case ZoomFitHeight: - { - zoom(getZoomHint(ZoomHint::FitHeight)); - break; - } - - case RotateRight: - { - m_controller->setPageRotation(getPageRotationRotatedRight(m_controller->getPageRotation())); - break; - } - - case RotateLeft: - { - m_controller->setPageRotation(getPageRotationRotatedLeft(m_controller->getPageRotation())); - break; - } - - default: - { - Q_ASSERT(false); - break; - } - } -} - -QPoint PDFDrawWidgetProxy::scrollByPixels(QPoint offset) -{ - PDFInteger oldHorizontalOffset = m_horizontalOffset; - PDFInteger oldVerticalOffset = m_verticalOffset; - - setHorizontalOffset(m_horizontalOffset + offset.x()); - setVerticalOffset(m_verticalOffset + offset.y()); - - return QPoint(m_horizontalOffset - oldHorizontalOffset, m_verticalOffset - oldVerticalOffset); -} - -void PDFDrawWidgetProxy::zoom(PDFReal zoom) -{ - const PDFReal clampedZoom = qBound(MIN_ZOOM, zoom, MAX_ZOOM); - if (m_zoom != clampedZoom) - { - const PDFReal oldHorizontalOffsetMM = m_horizontalOffset * m_pixelToDeviceSpaceUnit; - const PDFReal oldVerticalOffsetMM = m_verticalOffset * m_pixelToDeviceSpaceUnit; - - m_zoom = clampedZoom; - - update(); - - // Try to restore offsets, so we are in the same place - setHorizontalOffset(oldHorizontalOffsetMM * m_deviceSpaceUnitToPixel); - setVerticalOffset(oldVerticalOffsetMM * m_deviceSpaceUnitToPixel); - } -} - -PDFReal PDFDrawWidgetProxy::getZoomHint(ZoomHint hint) const -{ - QSizeF referenceSize = m_controller->getReferenceBoundingBox(); - if (referenceSize.isValid()) - { - const PDFReal ratio = 0.95; - const PDFReal widthMM = m_widget->widthMM() * ratio; - const PDFReal heightMM = m_widget->heightMM() * ratio; - - const PDFReal widthHint = widthMM / referenceSize.width(); - const PDFReal heightHint = heightMM / referenceSize.height(); - - switch (hint) - { - case ZoomHint::Fit: - return qMin(widthHint, heightHint); - - case ZoomHint::FitWidth: - return widthHint; - - case ZoomHint::FitHeight: - return heightHint; - - default: - break; - } - } - - // Return default 100% zoom - return 1.0; -} - -void PDFDrawWidgetProxy::goToPage(PDFInteger pageIndex) -{ - PDFDrawSpaceController::LayoutItem layoutItem = m_controller->getLayoutItemForPage(pageIndex); - - if (layoutItem.isValid()) - { - // We have found our page, navigate onto it - if (isBlockMode()) - { - setBlockIndex(layoutItem.blockIndex); - } - else - { - QRect rect = fromDeviceSpace(layoutItem.pageRectMM).toRect(); - setVerticalOffset(-rect.top() - m_layout.blockRect.top()); - } - } -} - -void PDFDrawWidgetProxy::setPageLayout(PageLayout pageLayout) -{ - if (getPageLayout() != pageLayout) - { - m_controller->setPageLayout(pageLayout); - emit pageLayoutChanged(); - } -} - -QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const -{ - Q_ASSERT(rect.isValid()); - - return QRectF(rect.left() * m_deviceSpaceUnitToPixel, - rect.top() * m_deviceSpaceUnitToPixel, - rect.width() * m_deviceSpaceUnitToPixel, - rect.height() * m_deviceSpaceUnitToPixel); -} - -void PDFDrawWidgetProxy::onTextLayoutChanged() -{ - emit repaintNeeded(); - emit textLayoutChanged(); -} - -bool PDFDrawWidgetProxy::isBlockMode() const -{ - switch (m_controller->getPageLayout()) - { - case PageLayout::OneColumn: - case PageLayout::TwoColumnLeft: - case PageLayout::TwoColumnRight: - return false; - - case PageLayout::SinglePage: - case PageLayout::TwoPagesLeft: - case PageLayout::TwoPagesRight: - return true; - } - - Q_ASSERT(false); - return false; -} - -void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat) -{ - m_useOpenGL = useOpenGL; - m_surfaceFormat = surfaceFormat; - m_rasterizer->reset(useOpenGL, surfaceFormat); -} - -void PDFDrawWidgetProxy::prefetchPages(PDFInteger pageIndex) -{ - // Determine number of pages, which should be prefetched. In case of two or more pages, - // we need to prefetch more pages (for example, two for two columns/two pages display mode). - int prefetchCount = 0; - switch (m_controller->getPageLayout()) - { - case PageLayout::OneColumn: - case PageLayout::SinglePage: - prefetchCount = 1; - break; - - case PageLayout::TwoPagesLeft: - case PageLayout::TwoPagesRight: - case PageLayout::TwoColumnLeft: - case PageLayout::TwoColumnRight: - prefetchCount = 2; - break; - - default: - Q_ASSERT(false); - break; - } - - if (const PDFDocument* document = getDocument()) - { - const PDFInteger pageCount = document->getCatalog()->getPageCount(); - const PDFInteger pageEnd = qMin(pageCount, pageIndex + prefetchCount + 1); - for (PDFInteger i = pageIndex + 1; i < pageEnd; ++i) - { - m_compiler->getCompiledPage(i, true); - } - } -} - -void PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged(int value) -{ - if (!m_updateDisabled && !m_horizontalScrollbar->isHidden()) - { - setHorizontalOffset(-value); - } -} - -void PDFDrawWidgetProxy::onVerticalScrollbarValueChanged(int value) -{ - if (!m_updateDisabled && !m_verticalScrollbar->isHidden()) - { - if (isBlockMode()) - { - setBlockIndex(value); - } - else - { - setVerticalOffset(-value); - } - } -} - -void PDFDrawWidgetProxy::setHorizontalOffset(int value) -{ - const PDFInteger horizontalOffset = m_horizontalOffsetRange.bound(value); - - if (m_horizontalOffset != horizontalOffset) - { - m_horizontalOffset = horizontalOffset; - updateHorizontalScrollbarFromOffset(); - emit drawSpaceChanged(); - } -} - -void PDFDrawWidgetProxy::setVerticalOffset(int value) -{ - const PDFInteger verticalOffset = m_verticalOffsetRange.bound(value); - - if (m_verticalOffset != verticalOffset) - { - m_verticalOffset = verticalOffset; - updateVerticalScrollbarFromOffset(); - emit drawSpaceChanged(); - } -} - -void PDFDrawWidgetProxy::setBlockIndex(int index) -{ - if (m_currentBlock != index) - { - m_currentBlock = static_cast(index); - update(); - } -} - -void PDFDrawWidgetProxy::updateHorizontalScrollbarFromOffset() -{ - if (!m_horizontalScrollbar->isHidden()) - { - PDFBoolGuard guard(m_updateDisabled); - m_horizontalScrollbar->setValue(-m_horizontalOffset); - } -} - -void PDFDrawWidgetProxy::updateVerticalScrollbarFromOffset() -{ - if (!m_verticalScrollbar->isHidden() && !isBlockMode()) - { - PDFBoolGuard guard(m_updateDisabled); - m_verticalScrollbar->setValue(-m_verticalOffset); - } -} - -PDFWidgetAnnotationManager* PDFDrawWidgetProxy::getAnnotationManager() const -{ - return m_widget->getAnnotationManager(); -} - -PDFRenderer::Features PDFDrawWidgetProxy::getFeatures() const -{ - return m_features; -} - -const PDFCMSManager* PDFDrawWidgetProxy::getCMSManager() const -{ - return m_widget->getCMSManager(); -} - -void PDFDrawWidgetProxy::setFeatures(PDFRenderer::Features features) -{ - if (m_features != features) - { - m_compiler->stop(true); - m_textLayoutCompiler->stop(true); - m_features = features; - m_compiler->start(); - m_textLayoutCompiler->start(); - emit pageImageChanged(true, { }); - } -} - -void PDFDrawWidgetProxy::setPreferredMeshResolutionRatio(PDFReal ratio) -{ - if (m_meshQualitySettings.preferredMeshResolutionRatio != ratio) - { - m_compiler->stop(true); - m_meshQualitySettings.preferredMeshResolutionRatio = ratio; - m_compiler->start(); - emit pageImageChanged(true, { }); - } -} - -void PDFDrawWidgetProxy::setMinimalMeshResolutionRatio(PDFReal ratio) -{ - if (m_meshQualitySettings.minimalMeshResolutionRatio != ratio) - { - m_compiler->stop(true); - m_meshQualitySettings.minimalMeshResolutionRatio = ratio; - m_compiler->start(); - emit pageImageChanged(true, { }); - } -} - -void PDFDrawWidgetProxy::setColorTolerance(PDFReal colorTolerance) -{ - if (m_meshQualitySettings.tolerance != colorTolerance) - { - m_compiler->stop(true); - m_meshQualitySettings.tolerance = colorTolerance; - m_compiler->start(); - emit pageImageChanged(true, { }); - } -} - -void PDFDrawWidgetProxy::onColorManagementSystemChanged() -{ - m_compiler->reset(); - emit pageImageChanged(true, { }); -} - -void PDFDrawWidgetProxy::onOptionalContentGroupStateChanged() -{ - m_compiler->reset(); - m_textLayoutCompiler->reset(); - emit pageImageChanged(true, { }); -} - -void IDocumentDrawInterface::drawPage(QPainter* painter, - PDFInteger pageIndex, - const PDFPrecompiledPage* compiledPage, - PDFTextLayoutGetter& layoutGetter, - const QMatrix& pagePointToDevicePointMatrix, - QList& errors) const -{ - Q_UNUSED(painter); - Q_UNUSED(pageIndex); - Q_UNUSED(compiledPage); - Q_UNUSED(layoutGetter); - Q_UNUSED(pagePointToDevicePointMatrix); - Q_UNUSED(errors); -} - -void IDocumentDrawInterface::drawPostRendering(QPainter* painter, QRect rect) const -{ - Q_UNUSED(painter); - Q_UNUSED(rect); -} - -const PDFWidgetSnapshot::SnapshotItem* PDFWidgetSnapshot::getPageSnapshot(PDFInteger pageIndex) const -{ - auto it = std::find_if(items.cbegin(), items.cend(), [pageIndex](const auto& item) { return item.pageIndex == pageIndex; }); - if (it != items.cend()) - { - return &*it; - } - - return nullptr; -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#include "pdfdrawspacecontroller.h" +#include "pdfdrawwidget.h" +#include "pdfrenderer.h" +#include "pdfpainter.h" +#include "pdfcompiler.h" +#include "pdfconstants.h" +#include "pdfcms.h" +#include "pdfannotation.h" + +#include +#include + +namespace pdf +{ + +PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) : + QObject(parent), + m_document(nullptr), + m_optionalContentActivity(nullptr), + m_pageLayoutMode(PageLayout::OneColumn), + m_verticalSpacingMM(5.0), + m_horizontalSpacingMM(1.0), + m_pageRotation(PageRotation::None), + m_fontCache(DEFAULT_FONT_CACHE_LIMIT, DEFAULT_REALIZED_FONT_CACHE_LIMIT) +{ + +} + +PDFDrawSpaceController::~PDFDrawSpaceController() +{ + +} + +void PDFDrawSpaceController::setDocument(const PDFModifiedDocument& document) +{ + if (document != m_document) + { + m_document = document; + m_fontCache.setDocument(document); + m_optionalContentActivity = document.getOptionalContentActivity(); + + // If document is not being reset, then recalculation is not needed, + // pages should remain the same. + if (document.hasReset()) + { + recalculate(); + } + } +} + +void PDFDrawSpaceController::setPageLayout(PageLayout pageLayout) +{ + if (m_pageLayoutMode != pageLayout) + { + m_pageLayoutMode = pageLayout; + recalculate(); + } +} + +QRectF PDFDrawSpaceController::getBlockBoundingRectangle(size_t blockIndex) const +{ + if (blockIndex < m_blockItems.size()) + { + return m_blockItems[blockIndex].blockRectMM; + } + + return QRectF(); +} + +PDFDrawSpaceController::LayoutItems PDFDrawSpaceController::getLayoutItems(size_t blockIndex) const +{ + LayoutItems result; + + auto comparator = [](const LayoutItem& l, const LayoutItem& r) + { + return l.blockIndex < r.blockIndex; + }; + Q_ASSERT(std::is_sorted(m_layoutItems.cbegin(), m_layoutItems.cend(), comparator)); + + LayoutItem templateItem; + templateItem.blockIndex = blockIndex; + + auto range = std::equal_range(m_layoutItems.cbegin(), m_layoutItems.cend(), templateItem, comparator); + result.reserve(std::distance(range.first, range.second)); + std::copy(range.first, range.second, std::back_inserter(result)); + + return result; +} + +PDFDrawSpaceController::LayoutItem PDFDrawSpaceController::getLayoutItemForPage(PDFInteger pageIndex) const +{ + LayoutItem result; + + if (pageIndex >= 0 && pageIndex < static_cast(m_layoutItems.size()) && m_layoutItems[pageIndex].pageIndex == pageIndex) + { + result = m_layoutItems[pageIndex]; + } + + if (!result.isValid()) + { + auto it = std::find_if(m_layoutItems.cbegin(), m_layoutItems.cend(), [pageIndex](const LayoutItem& item) { return item.pageIndex == pageIndex; }); + if (it != m_layoutItems.cend()) + { + result = *it; + } + } + + return result; +} + +QSizeF PDFDrawSpaceController::getReferenceBoundingBox() const +{ + QRectF rect; + + for (const LayoutItem& item : m_layoutItems) + { + QRectF pageRect = item.pageRectMM; + pageRect.translate(0, -pageRect.top()); + rect = rect.united(pageRect); + } + + if (rect.isValid()) + { + rect.adjust(0, 0, m_horizontalSpacingMM, m_verticalSpacingMM); + } + + return rect.size(); +} + +void PDFDrawSpaceController::setPageRotation(PageRotation pageRotation) +{ + if (m_pageRotation != pageRotation) + { + m_pageRotation = pageRotation; + recalculate(); + } +} + +void PDFDrawSpaceController::recalculate() +{ + if (!m_document) + { + clear(true); + return; + } + + const PDFCatalog* catalog = m_document->getCatalog(); + size_t pageCount = catalog->getPageCount(); + + // Clear the old draw space + clear(false); + + static constexpr size_t INVALID_PAGE_INDEX = std::numeric_limits::max(); + + // Places the pages on the left/right sides. Pages can be nullptr, but not both of them. + // Updates bounding rectangle. + auto placePagesLeftRight = [this, catalog](PDFInteger blockIndex, size_t leftIndex, size_t rightIndex, PDFReal& yPos, QRectF& boundingRect) + { + PDFReal yPosAdvance = 0.0; + + if (leftIndex != INVALID_PAGE_INDEX) + { + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(leftIndex)->getRotatedMediaBoxMM(), m_pageRotation).size(); + PDFReal xPos = -pageSize.width() - m_horizontalSpacingMM * 0.5; + QRectF rect(xPos, yPos, pageSize.width(), pageSize.height()); + m_layoutItems.emplace_back(blockIndex, leftIndex, rect); + yPosAdvance = qMax(yPosAdvance, pageSize.height()); + boundingRect = boundingRect.united(rect); + } + + if (rightIndex != INVALID_PAGE_INDEX) + { + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(rightIndex)->getRotatedMediaBoxMM(), m_pageRotation).size(); + PDFReal xPos = m_horizontalSpacingMM * 0.5; + QRectF rect(xPos, yPos, pageSize.width(), pageSize.height()); + m_layoutItems.emplace_back(blockIndex, rightIndex, rect); + yPosAdvance = qMax(yPosAdvance, pageSize.height()); + boundingRect = boundingRect.united(rect); + } + + if (yPosAdvance > 0.0) + { + yPos += yPosAdvance + m_verticalSpacingMM; + } + }; + + // Generates block with pages using page indices. If generateBlocks is true, then + // for each pair of pages, single block is generated, otherwise block containing all + // pages is generated. + auto placePagesLeftRightByIndices = [this, &placePagesLeftRight](const std::vector& indices, bool generateBlocks) + { + Q_ASSERT(indices.size() % 2 == 0); + + PDFReal yPos = 0.0; + PDFInteger blockIndex = 0; + QRectF boundingRectangle; + + size_t count = indices.size() / 2; + for (size_t i = 0; i < count; ++i) + { + const size_t leftPageIndex = indices[2 * i]; + const size_t rightPageIndex = indices[2 * i + 1]; + placePagesLeftRight(blockIndex, leftPageIndex, rightPageIndex, yPos, boundingRectangle); + + if (generateBlocks) + { + m_blockItems.emplace_back(boundingRectangle); + + // Clear the old data + yPos = 0.0; + ++blockIndex; + boundingRectangle = QRectF(); + } + } + + if (!generateBlocks) + { + // Generate single block for all layed out pages + m_blockItems.emplace_back(boundingRectangle); + } + }; + + switch (m_pageLayoutMode) + { + case PageLayout::SinglePage: + { + // Each block contains single page + m_layoutItems.reserve(pageCount); + m_blockItems.reserve(pageCount); + + // Pages can have different size, so we center them around the center. + // Block size will equal to the page size. + + for (size_t i = 0; i < pageCount; ++i) + { + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getRotatedMediaBoxMM(), m_pageRotation).size(); + QRectF rect(-pageSize.width() * 0.5, -pageSize.height() * 0.5, pageSize.width(), pageSize.height()); + m_layoutItems.emplace_back(i, i, rect); + m_blockItems.emplace_back(rect); + } + + break; + } + + case PageLayout::OneColumn: + { + // Single block, one column + m_layoutItems.reserve(pageCount); + m_blockItems.reserve(1); + + PDFReal yPos = 0.0; + QRectF boundingRectangle; + + for (size_t i = 0; i < pageCount; ++i) + { + // Top of current page is at yPos. + QSizeF pageSize = PDFPage::getRotatedBox(catalog->getPage(i)->getRotatedMediaBoxMM(), m_pageRotation).size(); + QRectF rect(-pageSize.width() * 0.5, yPos, pageSize.width(), pageSize.height()); + m_layoutItems.emplace_back(0, i, rect); + yPos += pageSize.height() + m_verticalSpacingMM; + boundingRectangle = boundingRectangle.united(rect); + } + + // Insert the single block with union of bounding rectangles + m_blockItems.emplace_back(boundingRectangle); + + break; + } + + case PageLayout::TwoColumnLeft: + { + // Pages with number 1, 3, 5, ... are on the left, 2, 4, 6 are on the right. + // Page indices are numbered from 0, so pages 0, 2, 4 will be on the left, + // 1, 3, 5 will be on the right. + // For purposes or paging, "left" pages will be on the left side of y axis (negative x axis), + // the "right" pages will be on the right side of y axis (positive x axis). + + m_layoutItems.reserve(pageCount); + m_blockItems.reserve(1); + + std::vector pageIndices(pageCount, INVALID_PAGE_INDEX); + std::iota(pageIndices.begin(), pageIndices.end(), static_cast(0)); + + if (pageIndices.size() % 2 == 1) + { + pageIndices.push_back(INVALID_PAGE_INDEX); + } + + placePagesLeftRightByIndices(pageIndices, false); + break; + } + + case PageLayout::TwoColumnRight: + { + // Similar to previous case, but page sequence start on the right. + + m_layoutItems.reserve(pageCount); + m_blockItems.reserve(1); + + std::vector pageIndices(pageCount + 1, INVALID_PAGE_INDEX); + std::iota(std::next(pageIndices.begin()), pageIndices.end(), static_cast(0)); + + if (pageIndices.size() % 2 == 1) + { + pageIndices.push_back(INVALID_PAGE_INDEX); + } + + placePagesLeftRightByIndices(pageIndices, false); + break; + } + + case PageLayout::TwoPagesLeft: + { + m_layoutItems.reserve(pageCount); + m_blockItems.reserve((pageCount / 2) + (pageCount % 2)); + + std::vector pageIndices(pageCount, INVALID_PAGE_INDEX); + std::iota(pageIndices.begin(), pageIndices.end(), static_cast(0)); + + if (pageIndices.size() % 2 == 1) + { + pageIndices.push_back(INVALID_PAGE_INDEX); + } + + placePagesLeftRightByIndices(pageIndices, true); + break; + } + + case PageLayout::TwoPagesRight: + { + m_layoutItems.reserve(pageCount); + m_blockItems.reserve((pageCount / 2) + (pageCount % 2)); + + std::vector pageIndices(pageCount + 1, INVALID_PAGE_INDEX); + std::iota(std::next(pageIndices.begin()), pageIndices.end(), static_cast(0)); + + if (pageIndices.size() % 2 == 1) + { + pageIndices.push_back(INVALID_PAGE_INDEX); + } + + placePagesLeftRightByIndices(pageIndices, true); + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } + + emit drawSpaceChanged(); +} + +void PDFDrawSpaceController::clear(bool emitSignal) +{ + m_layoutItems.clear(); + m_blockItems.clear(); + + if (emitSignal) + { + emit drawSpaceChanged(); + } +} + +PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) : + QObject(parent), + m_updateDisabled(false), + m_currentBlock(INVALID_BLOCK_INDEX), + m_pixelPerMM(PDF_DEFAULT_DPMM), + m_zoom(1.0), + m_pixelToDeviceSpaceUnit(0.0), + m_deviceSpaceUnitToPixel(0.0), + m_verticalOffset(0), + m_horizontalOffset(0), + m_controller(nullptr), + m_widget(nullptr), + m_verticalScrollbar(nullptr), + m_horizontalScrollbar(nullptr), + m_features(PDFRenderer::getDefaultFeatures()), + m_compiler(new PDFAsynchronousPageCompiler(this)), + m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)), + m_rasterizer(new PDFRasterizer(this)), + m_progress(nullptr), + m_useOpenGL(false) +{ + m_controller = new PDFDrawSpaceController(this); + connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update); + connect(m_controller, &PDFDrawSpaceController::repaintNeeded, this, &PDFDrawWidgetProxy::repaintNeeded); + connect(m_controller, &PDFDrawSpaceController::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged); + connect(m_compiler, &PDFAsynchronousPageCompiler::renderingError, this, &PDFDrawWidgetProxy::renderingError); + connect(m_compiler, &PDFAsynchronousPageCompiler::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged); + connect(m_textLayoutCompiler, &PDFAsynchronousTextLayoutCompiler::textLayoutChanged, this, &PDFDrawWidgetProxy::onTextLayoutChanged); +} + +PDFDrawWidgetProxy::~PDFDrawWidgetProxy() +{ + +} + +void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document) +{ + if (getDocument() != document) + { + m_compiler->stop(document.hasReset()); + m_textLayoutCompiler->stop(document.hasReset()); + m_controller->setDocument(document); + + if (PDFOptionalContentActivity* optionalContentActivity = document.getOptionalContentActivity()) + { + connect(optionalContentActivity, &PDFOptionalContentActivity::optionalContentGroupStateChanged, this, &PDFDrawWidgetProxy::onOptionalContentGroupStateChanged, Qt::UniqueConnection); + } + + m_compiler->start(); + m_textLayoutCompiler->start(); + } +} + +void PDFDrawWidgetProxy::init(PDFWidget* widget) +{ + m_widget = widget; + m_horizontalScrollbar = widget->getHorizontalScrollbar(); + m_verticalScrollbar = widget->getVerticalScrollbar(); + + connect(m_horizontalScrollbar, &QScrollBar::valueChanged, this, &PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged); + connect(m_verticalScrollbar, &QScrollBar::valueChanged, this, &PDFDrawWidgetProxy::onVerticalScrollbarValueChanged); + connect(this, &PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFDrawWidgetProxy::repaintNeeded); + connect(getCMSManager(), &PDFCMSManager::colorManagementSystemChanged, this, &PDFDrawWidgetProxy::onColorManagementSystemChanged); + + // We must update the draw space - widget has been set + update(); +} + +void PDFDrawWidgetProxy::update() +{ + if (m_updateDisabled) + { + return; + } + PDFBoolGuard guard(m_updateDisabled); + + Q_ASSERT(m_widget); + Q_ASSERT(m_horizontalScrollbar); + Q_ASSERT(m_verticalScrollbar); + + QWidget* widget = m_widget->getDrawWidget()->getWidget(); + + // First, we must calculate pixel per mm ratio to obtain DPMM (device pixel per mm), + // we also assume, that zoom is correctly set. + m_pixelPerMM = static_cast(widget->width()) / static_cast(widget->widthMM()); + + Q_ASSERT(m_zoom > 0.0); + Q_ASSERT(m_pixelPerMM > 0.0); + + m_deviceSpaceUnitToPixel = m_pixelPerMM * m_zoom; + m_pixelToDeviceSpaceUnit = 1.0 / m_deviceSpaceUnitToPixel; + + m_layout.clear(); + + // Switch to the first block, if we haven't selected any, otherwise fix active + // block item (select first block available). + if (m_controller->getBlockCount() > 0) + { + if (m_currentBlock == INVALID_BLOCK_INDEX) + { + m_currentBlock = 0; + } + else + { + m_currentBlock = qBound(0, m_currentBlock, m_controller->getBlockCount() - 1); + } + } + else + { + m_currentBlock = INVALID_BLOCK_INDEX; + } + + // Then, create pixel size layout of the pages using the draw space controller + QRectF rectangle = m_controller->getBlockBoundingRectangle(m_currentBlock); + if (rectangle.isValid()) + { + // We must have a valid block + PDFDrawSpaceController::LayoutItems items = m_controller->getLayoutItems(m_currentBlock); + + m_layout.items.reserve(items.size()); + for (const PDFDrawSpaceController::LayoutItem& item : items) + { + m_layout.items.emplace_back(item.pageIndex, fromDeviceSpace(item.pageRectMM).toRect()); + } + + m_layout.blockRect = fromDeviceSpace(rectangle).toRect(); + } + + QSize blockSize = m_layout.blockRect.size(); + QSize widgetSize = widget->size(); + + // Horizontal scrollbar + const int horizontalDifference = blockSize.width() - widgetSize.width(); + if (horizontalDifference > 0) + { + m_horizontalScrollbar->setVisible(true); + m_horizontalScrollbar->setMinimum(0); + m_horizontalScrollbar->setMaximum(horizontalDifference); + + m_horizontalOffsetRange = Range(-horizontalDifference, 0); + m_horizontalOffset = m_horizontalOffsetRange.bound(m_horizontalOffset); + m_horizontalScrollbar->setValue(-m_horizontalOffset); + } + else + { + // We do not need the horizontal scrollbar, because block can be draw onto widget entirely. + // We set the offset to the half of available empty space. + m_horizontalScrollbar->setVisible(false); + m_horizontalOffset = -horizontalDifference / 2; + m_horizontalOffsetRange = Range(m_horizontalOffset); + } + + // Vertical scrollbar - has two meanings, in block mode, it switches between blocks, + // in continuous mode, it controls the vertical offset. + if (isBlockMode()) + { + size_t blockCount = m_controller->getBlockCount(); + if (blockCount > 0) + { + Q_ASSERT(m_currentBlock != INVALID_BLOCK_INDEX); + + m_verticalScrollbar->setVisible(blockCount > 1); + m_verticalScrollbar->setMinimum(0); + m_verticalScrollbar->setMaximum(static_cast(blockCount - 1)); + m_verticalScrollbar->setValue(static_cast(m_currentBlock)); + m_verticalScrollbar->setSingleStep(1); + m_verticalScrollbar->setPageStep(1); + } + else + { + Q_ASSERT(m_currentBlock == INVALID_BLOCK_INDEX); + m_verticalScrollbar->setVisible(false); + } + + // We must fix case, when we can display everything on the widget (we have + // enough space). Then we will center the page on the widget. + const int verticalDifference = blockSize.height() - widgetSize.height(); + if (verticalDifference < 0) + { + m_verticalOffset = -verticalDifference / 2; + m_verticalOffsetRange = Range(m_verticalOffset); + } + else + { + m_verticalOffsetRange = Range(-verticalDifference, 0); + m_verticalOffset = m_verticalOffsetRange.bound(m_verticalOffset); + } + } + else + { + const int verticalDifference = blockSize.height() - widgetSize.height(); + if (verticalDifference > 0) + { + m_verticalScrollbar->setVisible(true); + m_verticalScrollbar->setMinimum(0); + m_verticalScrollbar->setMaximum(verticalDifference); + + // We must also calculate single step/page step. Because pages can have different size, + // we use first page to compute page step. + if (!m_layout.items.empty()) + { + const LayoutItem& item = m_layout.items.front(); + + const int pageStep = qMax(item.pageRect.height(), 1); + const int singleStep = qMax(pageStep / 10, 1); + + m_verticalScrollbar->setPageStep(pageStep); + m_verticalScrollbar->setSingleStep(singleStep); + } + + m_verticalOffsetRange = Range(-verticalDifference, 0); + m_verticalOffset = m_verticalOffsetRange.bound(m_verticalOffset); + m_verticalScrollbar->setValue(-m_verticalOffset); + } + else + { + m_verticalScrollbar->setVisible(false); + m_verticalOffset = -verticalDifference / 2; + m_verticalOffsetRange = Range(m_verticalOffset); + } + } + + emit drawSpaceChanged(); +} + +QMatrix PDFDrawWidgetProxy::createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const +{ + QMatrix matrix; + + // We want to create transformation from unrotated rectangle + // to rotated page rectangle. + + QRectF unrotatedRectangle = rectangle; + switch (m_controller->getPageRotation()) + { + case PageRotation::None: + break; + + case PageRotation::Rotate180: + { + matrix.translate(0, rectangle.top() + rectangle.bottom()); + matrix.scale(1.0, -1.0); + Q_ASSERT(qFuzzyCompare(matrix.map(rectangle.topLeft()).y(), rectangle.bottom())); + Q_ASSERT(qFuzzyCompare(matrix.map(rectangle.bottomLeft()).y(), rectangle.top())); + break; + } + + case PageRotation::Rotate90: + { + unrotatedRectangle = rectangle.transposed(); + matrix.translate(rectangle.left(), rectangle.top()); + matrix.rotate(90); + matrix.translate(-rectangle.left(), -rectangle.top()); + matrix.translate(0, -unrotatedRectangle.height()); + break; + } + + case PageRotation::Rotate270: + { + unrotatedRectangle = rectangle.transposed(); + matrix.translate(rectangle.left(), rectangle.top()); + matrix.rotate(90); + matrix.translate(-rectangle.left(), -rectangle.top()); + matrix.translate(0, -unrotatedRectangle.height()); + matrix.translate(0.0, unrotatedRectangle.top() + unrotatedRectangle.bottom()); + matrix.scale(1.0, -1.0); + matrix.translate(unrotatedRectangle.left() + unrotatedRectangle.right(), 0.0); + matrix.scale(-1.0, 1.0); + break; + } + + default: + break; + } + + return PDFRenderer::createPagePointToDevicePointMatrix(page, unrotatedRectangle) * matrix; +} + +void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect) +{ + drawPages(painter, rect, m_features); + + for (IDocumentDrawInterface* drawInterface : m_drawInterfaces) + { + painter->save(); + drawInterface->drawPostRendering(painter, rect); + painter->restore(); + } +} + +QColor PDFDrawWidgetProxy::getPaperColor() +{ + QColor paperColor = getCMSManager()->getCurrentCMS()->getPaperColor(); + if (m_features.testFlag(PDFRenderer::InvertColors)) + { + paperColor = invertColor(paperColor); + } + + return paperColor; +} + +void PDFDrawWidgetProxy::drawPages(QPainter* painter, QRect rect, PDFRenderer::Features features) +{ + painter->fillRect(rect, Qt::lightGray); + QMatrix baseMatrix = painter->worldMatrix(); + + // Use current paper color (it can be a bit different from white) + QColor paperColor = getPaperColor(); + + // Iterate trough pages and display them on the painter device + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + if (placedRect.intersects(rect)) + { + // Clear the page space by paper color + painter->fillRect(placedRect, paperColor); + + const PDFPrecompiledPage* compiledPage = m_compiler->getCompiledPage(item.pageIndex, true); + if (compiledPage && compiledPage->isValid()) + { + QElapsedTimer timer; + timer.start(); + + const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); + QMatrix matrix = createPagePointToDevicePointMatrix(page, placedRect) * baseMatrix; + compiledPage->draw(painter, page->getCropBox(), matrix, features); + PDFTextLayoutGetter layoutGetter = m_textLayoutCompiler->getTextLayoutLazy(item.pageIndex); + + // Draw text blocks/text lines, if it is enabled + if (features.testFlag(PDFRenderer::DebugTextBlocks)) + { + m_textLayoutCompiler->makeTextLayout(); + const PDFTextLayout& layout = layoutGetter; + const PDFTextBlocks& textBlocks = layout.getTextBlocks(); + + painter->save(); + painter->setFont(m_widget->font()); + painter->setPen(Qt::red); + painter->setBrush(QColor(255, 0, 0, 128)); + + QFontMetricsF fontMetrics(painter->font(), painter->device()); + int blockIndex = 1; + for (const PDFTextBlock& block : textBlocks) + { + QString blockNumber = QString::number(blockIndex++); + + painter->drawPath(matrix.map(block.getBoundingBox())); + painter->drawText(matrix.map(block.getTopLeft()) - QPointF(fontMetrics.width(blockNumber), 0), blockNumber, Qt::TextSingleLine, 0); + } + + painter->restore(); + } + if (features.testFlag(PDFRenderer::DebugTextLines)) + { + m_textLayoutCompiler->makeTextLayout(); + const PDFTextLayout& layout = layoutGetter; + const PDFTextBlocks& textBlocks = layout.getTextBlocks(); + + painter->save(); + painter->setFont(m_widget->font()); + painter->setPen(Qt::green); + painter->setBrush(QColor(0, 255, 0, 128)); + + QFontMetricsF fontMetrics(painter->font(), painter->device()); + int lineIndex = 1; + for (const PDFTextBlock& block : textBlocks) + { + for (const PDFTextLine& line : block.getLines()) + { + QString lineNumber = QString::number(lineIndex++); + + painter->drawPath(matrix.map(line.getBoundingBox())); + painter->drawText(matrix.map(line.getTopLeft()) - QPointF(fontMetrics.width(lineNumber), 0), lineNumber, Qt::TextSingleLine, 0); + } + } + + painter->restore(); + } + + QList drawInterfaceErrors; + if (!features.testFlag(PDFRenderer::DenyExtraGraphics)) + { + for (IDocumentDrawInterface* drawInterface : m_drawInterfaces) + { + painter->save(); + drawInterface->drawPage(painter, item.pageIndex, compiledPage, layoutGetter, matrix, drawInterfaceErrors); + painter->restore(); + } + } + + const qint64 drawTimeNS = timer.nsecsElapsed(); + + // Draw rendering times + if (features.testFlag(PDFRenderer::DisplayTimes)) + { + QFont font = m_widget->font(); + font.setPointSize(12); + + auto formatDrawTime = [](qint64 nanoseconds) + { + PDFReal miliseconds = nanoseconds / 1000000.0; + return QString::number(miliseconds, 'f', 3); + }; + + QFontMetrics fontMetrics(font); + const int lineSpacing = fontMetrics.lineSpacing(); + + painter->save(); + painter->setPen(Qt::red); + painter->setFont(font); + painter->translate(placedRect.topLeft()); + painter->translate(placedRect.width() / 20.0, placedRect.height() / 20.0); // Offset + + painter->setBackground(QBrush(Qt::white)); + painter->setBackgroundMode(Qt::OpaqueMode); + painter->drawText(0, 0, PDFTranslationContext::tr("Compile time: %1 [ms]").arg(formatDrawTime(compiledPage->getCompilingTimeNS()))); + painter->translate(0, lineSpacing); + painter->drawText(0, 0, PDFTranslationContext::tr("Draw time: %1 [ms]").arg(formatDrawTime(drawTimeNS))); + + painter->restore(); + } + + const QList& pageErrors = compiledPage->getErrors(); + if (!pageErrors.empty() || !drawInterfaceErrors.empty()) + { + QList errors = pageErrors; + if (!drawInterfaceErrors.isEmpty()) + { + errors.append(drawInterfaceErrors); + } + emit renderingError(item.pageIndex, qMove(errors)); + } + } + } + } +} + +QImage PDFDrawWidgetProxy::drawThumbnailImage(PDFInteger pageIndex, int pixelSize) const +{ + QImage image; + if (!m_controller->getDocument()) + { + // No thumbnail - return empty image + return image; + } + + if (const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(pageIndex)) + { + QRectF pageRect = page->getRotatedMediaBox(); + QSizeF pageSize = pageRect.size(); + pageSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio); + QSize imageSize = pageSize.toSize(); + + if (imageSize.isValid()) + { + const PDFPrecompiledPage* compiledPage = m_compiler->getCompiledPage(pageIndex, true); + if (compiledPage && compiledPage->isValid()) + { + // Rasterize the image. + image = m_rasterizer->render(pageIndex, page, compiledPage, imageSize, m_features, m_widget->getAnnotationManager(), PageRotation::None); + } + + if (image.isNull()) + { + image = QImage(imageSize, QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::white); + } + } + } + + return image; +} + +std::vector PDFDrawWidgetProxy::getPagesIntersectingRect(QRect rect) const +{ + std::vector pages; + + // We assume, that no more, than 32 pages will be displayed in the rectangle + pages.reserve(32); + + // Iterate trough pages, place them and test, if they intersects with rectangle + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + if (placedRect.intersects(rect)) + { + pages.push_back(item.pageIndex); + } + } + std::sort(pages.begin(), pages.end()); + + return pages; +} + +PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const +{ + // Iterate trough pages, place them and test, if they intersects with rectangle + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + placedRect.adjust(0, 0, 1, 1); + if (placedRect.contains(point)) + { + if (pagePoint) + { + const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); + QMatrix matrix = createPagePointToDevicePointMatrix(page, placedRect).inverted(); + *pagePoint = matrix.map(point); + } + + return item.pageIndex; + } + } + + return -1; +} + +PDFWidgetSnapshot PDFDrawWidgetProxy::getSnapshot() const +{ + PDFWidgetSnapshot snapshot; + + // Get the viewport + QRect viewport = getWidget()->rect(); + + // Iterate trough pages, place them and test, if they intersects with rectangle + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + if (placedRect.intersects(viewport)) + { + const PDFPage* page = m_controller->getDocument()->getCatalog()->getPage(item.pageIndex); + + PDFWidgetSnapshot::SnapshotItem snapshotItem; + snapshotItem.rect = placedRect; + snapshotItem.pageIndex = item.pageIndex; + snapshotItem.compiledPage = m_compiler->getCompiledPage(item.pageIndex, false); + snapshotItem.pageToDeviceMatrix = createPagePointToDevicePointMatrix(page, placedRect); + snapshot.items.emplace_back(qMove(snapshotItem)); + } + } + + return snapshot; +} + +QRect PDFDrawWidgetProxy::getPagesIntersectingRectBoundingBox(QRect rect) const +{ + QRect resultRect; + + // Iterate trough pages, place them and test, if they intersects with rectangle + for (const LayoutItem& item : m_layout.items) + { + // The offsets m_horizontalOffset and m_verticalOffset are offsets to the + // topleft point of the block. But block maybe doesn't start at (0, 0), + // so we must also use translation from the block beginning. + QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top()); + if (placedRect.intersects(rect)) + { + resultRect = resultRect.united(placedRect); + } + } + + return resultRect; +} + +void PDFDrawWidgetProxy::performOperation(Operation operation) +{ + switch (operation) + { + case NavigateDocumentStart: + { + if (m_verticalScrollbar->isVisible()) + { + m_verticalScrollbar->setValue(0); + } + break; + } + + case NavigateDocumentEnd: + { + if (m_verticalScrollbar->isVisible()) + { + m_verticalScrollbar->setValue(m_verticalScrollbar->maximum()); + } + break; + } + + case NavigateNextPage: + { + if (m_verticalScrollbar->isVisible()) + { + m_verticalScrollbar->setValue(m_verticalScrollbar->value() + m_verticalScrollbar->pageStep()); + } + break; + } + + case NavigatePreviousPage: + { + if (m_verticalScrollbar->isVisible()) + { + m_verticalScrollbar->setValue(m_verticalScrollbar->value() - m_verticalScrollbar->pageStep()); + } + break; + } + + case NavigateNextStep: + { + if (m_verticalScrollbar->isVisible()) + { + m_verticalScrollbar->setValue(m_verticalScrollbar->value() + m_verticalScrollbar->singleStep()); + } + break; + } + + case NavigatePreviousStep: + { + if (m_verticalScrollbar->isVisible()) + { + m_verticalScrollbar->setValue(m_verticalScrollbar->value() - m_verticalScrollbar->singleStep()); + } + break; + } + + case ZoomIn: + { + zoom(m_zoom * ZOOM_STEP); + break; + } + + case ZoomOut: + { + zoom(m_zoom / ZOOM_STEP); + break; + } + + case ZoomFit: + { + zoom(getZoomHint(ZoomHint::Fit)); + break; + } + + case ZoomFitWidth: + { + zoom(getZoomHint(ZoomHint::FitWidth)); + break; + } + + case ZoomFitHeight: + { + zoom(getZoomHint(ZoomHint::FitHeight)); + break; + } + + case RotateRight: + { + m_controller->setPageRotation(getPageRotationRotatedRight(m_controller->getPageRotation())); + break; + } + + case RotateLeft: + { + m_controller->setPageRotation(getPageRotationRotatedLeft(m_controller->getPageRotation())); + break; + } + + default: + { + Q_ASSERT(false); + break; + } + } +} + +QPoint PDFDrawWidgetProxy::scrollByPixels(QPoint offset) +{ + PDFInteger oldHorizontalOffset = m_horizontalOffset; + PDFInteger oldVerticalOffset = m_verticalOffset; + + setHorizontalOffset(m_horizontalOffset + offset.x()); + setVerticalOffset(m_verticalOffset + offset.y()); + + return QPoint(m_horizontalOffset - oldHorizontalOffset, m_verticalOffset - oldVerticalOffset); +} + +void PDFDrawWidgetProxy::zoom(PDFReal zoom) +{ + const PDFReal clampedZoom = qBound(MIN_ZOOM, zoom, MAX_ZOOM); + if (m_zoom != clampedZoom) + { + const PDFReal oldHorizontalOffsetMM = m_horizontalOffset * m_pixelToDeviceSpaceUnit; + const PDFReal oldVerticalOffsetMM = m_verticalOffset * m_pixelToDeviceSpaceUnit; + + m_zoom = clampedZoom; + + update(); + + // Try to restore offsets, so we are in the same place + setHorizontalOffset(oldHorizontalOffsetMM * m_deviceSpaceUnitToPixel); + setVerticalOffset(oldVerticalOffsetMM * m_deviceSpaceUnitToPixel); + } +} + +PDFReal PDFDrawWidgetProxy::getZoomHint(ZoomHint hint) const +{ + QSizeF referenceSize = m_controller->getReferenceBoundingBox(); + if (referenceSize.isValid()) + { + const PDFReal ratio = 0.95; + const PDFReal widthMM = m_widget->widthMM() * ratio; + const PDFReal heightMM = m_widget->heightMM() * ratio; + + const PDFReal widthHint = widthMM / referenceSize.width(); + const PDFReal heightHint = heightMM / referenceSize.height(); + + switch (hint) + { + case ZoomHint::Fit: + return qMin(widthHint, heightHint); + + case ZoomHint::FitWidth: + return widthHint; + + case ZoomHint::FitHeight: + return heightHint; + + default: + break; + } + } + + // Return default 100% zoom + return 1.0; +} + +void PDFDrawWidgetProxy::goToPage(PDFInteger pageIndex) +{ + PDFDrawSpaceController::LayoutItem layoutItem = m_controller->getLayoutItemForPage(pageIndex); + + if (layoutItem.isValid()) + { + // We have found our page, navigate onto it + if (isBlockMode()) + { + setBlockIndex(layoutItem.blockIndex); + } + else + { + QRect rect = fromDeviceSpace(layoutItem.pageRectMM).toRect(); + setVerticalOffset(-rect.top() - m_layout.blockRect.top()); + } + } +} + +void PDFDrawWidgetProxy::setPageLayout(PageLayout pageLayout) +{ + if (getPageLayout() != pageLayout) + { + m_controller->setPageLayout(pageLayout); + emit pageLayoutChanged(); + } +} + +QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const +{ + Q_ASSERT(rect.isValid()); + + return QRectF(rect.left() * m_deviceSpaceUnitToPixel, + rect.top() * m_deviceSpaceUnitToPixel, + rect.width() * m_deviceSpaceUnitToPixel, + rect.height() * m_deviceSpaceUnitToPixel); +} + +void PDFDrawWidgetProxy::onTextLayoutChanged() +{ + emit repaintNeeded(); + emit textLayoutChanged(); +} + +bool PDFDrawWidgetProxy::isBlockMode() const +{ + switch (m_controller->getPageLayout()) + { + case PageLayout::OneColumn: + case PageLayout::TwoColumnLeft: + case PageLayout::TwoColumnRight: + return false; + + case PageLayout::SinglePage: + case PageLayout::TwoPagesLeft: + case PageLayout::TwoPagesRight: + return true; + } + + Q_ASSERT(false); + return false; +} + +void PDFDrawWidgetProxy::updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat) +{ + m_useOpenGL = useOpenGL; + m_surfaceFormat = surfaceFormat; + m_rasterizer->reset(useOpenGL, surfaceFormat); +} + +void PDFDrawWidgetProxy::prefetchPages(PDFInteger pageIndex) +{ + // Determine number of pages, which should be prefetched. In case of two or more pages, + // we need to prefetch more pages (for example, two for two columns/two pages display mode). + int prefetchCount = 0; + switch (m_controller->getPageLayout()) + { + case PageLayout::OneColumn: + case PageLayout::SinglePage: + prefetchCount = 1; + break; + + case PageLayout::TwoPagesLeft: + case PageLayout::TwoPagesRight: + case PageLayout::TwoColumnLeft: + case PageLayout::TwoColumnRight: + prefetchCount = 2; + break; + + default: + Q_ASSERT(false); + break; + } + + if (const PDFDocument* document = getDocument()) + { + const PDFInteger pageCount = document->getCatalog()->getPageCount(); + const PDFInteger pageEnd = qMin(pageCount, pageIndex + prefetchCount + 1); + for (PDFInteger i = pageIndex + 1; i < pageEnd; ++i) + { + m_compiler->getCompiledPage(i, true); + } + } +} + +void PDFDrawWidgetProxy::onHorizontalScrollbarValueChanged(int value) +{ + if (!m_updateDisabled && !m_horizontalScrollbar->isHidden()) + { + setHorizontalOffset(-value); + } +} + +void PDFDrawWidgetProxy::onVerticalScrollbarValueChanged(int value) +{ + if (!m_updateDisabled && !m_verticalScrollbar->isHidden()) + { + if (isBlockMode()) + { + setBlockIndex(value); + } + else + { + setVerticalOffset(-value); + } + } +} + +void PDFDrawWidgetProxy::setHorizontalOffset(int value) +{ + const PDFInteger horizontalOffset = m_horizontalOffsetRange.bound(value); + + if (m_horizontalOffset != horizontalOffset) + { + m_horizontalOffset = horizontalOffset; + updateHorizontalScrollbarFromOffset(); + emit drawSpaceChanged(); + } +} + +void PDFDrawWidgetProxy::setVerticalOffset(int value) +{ + const PDFInteger verticalOffset = m_verticalOffsetRange.bound(value); + + if (m_verticalOffset != verticalOffset) + { + m_verticalOffset = verticalOffset; + updateVerticalScrollbarFromOffset(); + emit drawSpaceChanged(); + } +} + +void PDFDrawWidgetProxy::setBlockIndex(int index) +{ + if (m_currentBlock != index) + { + m_currentBlock = static_cast(index); + update(); + } +} + +void PDFDrawWidgetProxy::updateHorizontalScrollbarFromOffset() +{ + if (!m_horizontalScrollbar->isHidden()) + { + PDFBoolGuard guard(m_updateDisabled); + m_horizontalScrollbar->setValue(-m_horizontalOffset); + } +} + +void PDFDrawWidgetProxy::updateVerticalScrollbarFromOffset() +{ + if (!m_verticalScrollbar->isHidden() && !isBlockMode()) + { + PDFBoolGuard guard(m_updateDisabled); + m_verticalScrollbar->setValue(-m_verticalOffset); + } +} + +PDFWidgetAnnotationManager* PDFDrawWidgetProxy::getAnnotationManager() const +{ + return m_widget->getAnnotationManager(); +} + +PDFRenderer::Features PDFDrawWidgetProxy::getFeatures() const +{ + return m_features; +} + +const PDFCMSManager* PDFDrawWidgetProxy::getCMSManager() const +{ + return m_widget->getCMSManager(); +} + +void PDFDrawWidgetProxy::setFeatures(PDFRenderer::Features features) +{ + if (m_features != features) + { + m_compiler->stop(true); + m_textLayoutCompiler->stop(true); + m_features = features; + m_compiler->start(); + m_textLayoutCompiler->start(); + emit pageImageChanged(true, { }); + } +} + +void PDFDrawWidgetProxy::setPreferredMeshResolutionRatio(PDFReal ratio) +{ + if (m_meshQualitySettings.preferredMeshResolutionRatio != ratio) + { + m_compiler->stop(true); + m_meshQualitySettings.preferredMeshResolutionRatio = ratio; + m_compiler->start(); + emit pageImageChanged(true, { }); + } +} + +void PDFDrawWidgetProxy::setMinimalMeshResolutionRatio(PDFReal ratio) +{ + if (m_meshQualitySettings.minimalMeshResolutionRatio != ratio) + { + m_compiler->stop(true); + m_meshQualitySettings.minimalMeshResolutionRatio = ratio; + m_compiler->start(); + emit pageImageChanged(true, { }); + } +} + +void PDFDrawWidgetProxy::setColorTolerance(PDFReal colorTolerance) +{ + if (m_meshQualitySettings.tolerance != colorTolerance) + { + m_compiler->stop(true); + m_meshQualitySettings.tolerance = colorTolerance; + m_compiler->start(); + emit pageImageChanged(true, { }); + } +} + +void PDFDrawWidgetProxy::onColorManagementSystemChanged() +{ + m_compiler->reset(); + emit pageImageChanged(true, { }); +} + +void PDFDrawWidgetProxy::onOptionalContentGroupStateChanged() +{ + m_compiler->reset(); + m_textLayoutCompiler->reset(); + emit pageImageChanged(true, { }); +} + +void IDocumentDrawInterface::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(painter); + Q_UNUSED(pageIndex); + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(pagePointToDevicePointMatrix); + Q_UNUSED(errors); +} + +void IDocumentDrawInterface::drawPostRendering(QPainter* painter, QRect rect) const +{ + Q_UNUSED(painter); + Q_UNUSED(rect); +} + +const PDFWidgetSnapshot::SnapshotItem* PDFWidgetSnapshot::getPageSnapshot(PDFInteger pageIndex) const +{ + auto it = std::find_if(items.cbegin(), items.cend(), [pageIndex](const auto& item) { return item.pageIndex == pageIndex; }); + if (it != items.cend()) + { + return &*it; + } + + return nullptr; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.h b/Pdf4QtLib/sources/pdfdrawspacecontroller.h index f44b5dd..14eed72 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.h +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.h @@ -1,508 +1,508 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDRAWSPACECONTROLLER_H -#define PDFDRAWSPACECONTROLLER_H - -#include "pdfglobal.h" -#include "pdfdocument.h" -#include "pdfrenderer.h" -#include "pdffont.h" -#include "pdfdocumentdrawinterface.h" - -#include -#include -#include - -class QPainter; -class QScrollBar; - -namespace pdf -{ -class PDFProgress; -class PDFWidget; -class PDFCMSManager; -class PDFTextLayoutGetter; -class PDFWidgetAnnotationManager; -class PDFAsynchronousPageCompiler; -class PDFAsynchronousTextLayoutCompiler; - -/// This class controls draw space - page layout. Pages are divided into blocks -/// each block can contain one or multiple pages. Units are in milimeters. -/// Pages are layouted in zoom-independent mode. -class PDFDrawSpaceController : public QObject -{ - Q_OBJECT - -public: - explicit PDFDrawSpaceController(QObject* parent); - virtual ~PDFDrawSpaceController() override; - - /// Sets the document and recalculates the draw space. Document can be nullptr, - /// in that case, draw space is cleared. Optional content activity can be nullptr, - /// in that case, no content is suppressed. - /// \param document Document - void setDocument(const PDFModifiedDocument& document); - - /// Sets the page layout. Page layout can be one of the PDF's page layouts. - /// \param pageLayout Page layout - void setPageLayout(PageLayout pageLayout); - - /// Returns the page layout - PageLayout getPageLayout() const { return m_pageLayoutMode; } - - /// Returns the block count - size_t getBlockCount() const { return m_blockItems.size(); } - - /// Return the bounding rectangle of the block. If block doesn't exist, - /// then invalid rectangle is returned (no exception is thrown). - /// \param blockIndex Index of the block - QRectF getBlockBoundingRectangle(size_t blockIndex) const; - - /// Represents layouted page. This structure contains index of the block, index of the - /// page and page rectangle, in which the page is contained. - struct LayoutItem - { - constexpr inline explicit LayoutItem() : blockIndex(-1), pageIndex(-1) { } - constexpr inline explicit LayoutItem(PDFInteger blockIndex, PDFInteger pageIndex, const QRectF& pageRectMM) : - blockIndex(blockIndex), pageIndex(pageIndex), pageRectMM(pageRectMM) { } - - bool isValid() const { return pageIndex != -1; } - - PDFInteger blockIndex; - PDFInteger pageIndex; - QRectF pageRectMM; - }; - - using LayoutItems = std::vector; - - /// Returns the layout items for desired block. If block doesn't exist, - /// then empty array is returned. - /// \param blockIndex Index of the block - LayoutItems getLayoutItems(size_t blockIndex) const; - - /// Returns layout for single page. If page index is invalid, - /// or page layout cannot be found, then invalid layout item is returned. - /// \param pageIndex Page index - LayoutItem getLayoutItemForPage(PDFInteger pageIndex) const; - - /// Returns the document - const PDFDocument* getDocument() const { return m_document; } - - /// Returns the font cache - const PDFFontCache* getFontCache() const { return &m_fontCache; } - - /// Returns the font cache - PDFFontCache* getFontCache() { return &m_fontCache; } - - /// Returns optional content activity - const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; } - - /// Returns reference bounding box for correct calculation of zoom fit/fit vertical/fit horizontal. - /// If zoom is set in a way to display this bounding box on a screen, then it is assured that - /// any page on the screen will fit this bounding box, regardless of mode (single/two columns, etc.). - QSizeF getReferenceBoundingBox() const; - - /// Returns page rotation - PageRotation getPageRotation() const { return m_pageRotation; } - - /// Sets page rotation - void setPageRotation(PageRotation pageRotation); - -signals: - void drawSpaceChanged(); - void repaintNeeded(); - void pageImageChanged(bool all, const std::vector& pages); - -private: - /// Recalculates the draw space. Preserves setted page rotation. - void recalculate(); - - /// Clears the draw space. Emits signal if desired. - void clear(bool emitSignal); - - /// Represents data for the single block. Contains block size in milimeters. - struct LayoutBlock - { - constexpr inline explicit LayoutBlock() = default; - constexpr inline explicit LayoutBlock(const QRectF& blockRectMM) : blockRectMM(blockRectMM) { } - - QRectF blockRectMM; - }; - - using BlockItems = std::vector; - - const PDFDocument* m_document; - const PDFOptionalContentActivity* m_optionalContentActivity; - - PageLayout m_pageLayoutMode; - LayoutItems m_layoutItems; - BlockItems m_blockItems; - PDFReal m_verticalSpacingMM; - PDFReal m_horizontalSpacingMM; - PageRotation m_pageRotation; - - /// Font cache - PDFFontCache m_fontCache; -}; - -/// Snapshot for current widget viewable items. -struct PDFWidgetSnapshot -{ - struct SnapshotItem - { - PDFInteger pageIndex = -1; ///< Index of page - QRectF rect; ///< Page rectangle on viewport - QMatrix pageToDeviceMatrix; ///< Transforms page coordinates to widget coordinates - const PDFPrecompiledPage* compiledPage = nullptr; ///< Compiled page (can be nullptr) - }; - - bool hasPage(PDFInteger pageIndex) const { return getPageSnapshot(pageIndex) != nullptr; } - const SnapshotItem* getPageSnapshot(PDFInteger pageIndex) const; - - using SnapshotItems = std::vector; - SnapshotItems items; -}; - -/// This is a proxy class to draw space controller using widget. We have two spaces, pixel space -/// (on the controlled widget) and device space (device is draw space controller). -class PDF4QTLIBSHARED_EXPORT PDFDrawWidgetProxy : public QObject -{ - Q_OBJECT - -public: - explicit PDFDrawWidgetProxy(QObject* parent); - virtual ~PDFDrawWidgetProxy() override; - - /// Sets the document and updates the draw space. Document can be nullptr, - /// in that case, draw space is cleared. Optional content activity can be nullptr, - /// in that case, no content is suppressed. - /// \param document Document - void setDocument(const PDFModifiedDocument& document); - - void init(PDFWidget* widget); - - /// Updates the draw space area - void update(); - - /// Creates page point to device point matrix for the given rectangle. It creates transformation - /// from page's media box to the target rectangle. - /// \param page Page, for which we want to create matrix - /// \param rectangle Page rectangle, to which is page media box transformed - QMatrix createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const; - - /// Draws the actually visible pages on the painter using the rectangle. - /// Rectangle is space in the widget, which is used for painting the PDF. - /// This function is using drawPages function to draw all pages. After that, - /// custom drawing is performed. - /// \sa drawPages - /// \param painter Painter to paint the PDF pages - /// \param rect Rectangle in which the content is painted - void draw(QPainter* painter, QRect rect); - - /// Draws the actually visible pages on the painter using the rectangle. - /// Rectangle is space in the widget, which is used for painting the PDF. - /// \param painter Painter to paint the PDF pages - /// \param rect Rectangle in which the content is painted - /// \param features Rendering features - void drawPages(QPainter* painter, QRect rect, PDFRenderer::Features features); - - /// Draws thumbnail image of the given size (so larger of the page size - /// width or height equals to pixel size and the latter size is rescaled - /// using the aspect ratio) - /// \param pixelSize Pixel size - QImage drawThumbnailImage(PDFInteger pageIndex, int pixelSize) const; - - enum Operation - { - ZoomIn, - ZoomOut, - ZoomFit, - ZoomFitWidth, - ZoomFitHeight, - NavigateDocumentStart, - NavigateDocumentEnd, - NavigateNextPage, - NavigatePreviousPage, - NavigateNextStep, - NavigatePreviousStep, - RotateRight, - RotateLeft - }; - - /// Performs the desired operation (for example navigation). - /// \param operation Operation to be performed - void performOperation(Operation operation); - - /// Scrolls by pixels, if it is possible. If it is not possible to scroll, - /// then nothing happens. Returns pixel offset, by which view camera was moved. - /// \param offset Offset in pixels - QPoint scrollByPixels(QPoint offset); - - /// Sets the zoom. Tries to preserve current offsets (so the current visible - /// area will be visible after the zoom). - /// \param zoom New zoom - void zoom(PDFReal zoom); - - enum class ZoomHint - { - Fit, - FitWidth, - FitHeight - }; - - /// Calculates zoom using given hint (i.e. to fill whole space, fill vertical, - /// or fill horizontal). - /// \param hint Zoom hint type - PDFReal getZoomHint(ZoomHint hint) const; - - /// Go to the specified page - /// \param pageIndex Page to scroll to - void goToPage(PDFInteger pageIndex); - - /// Returns current zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom, - /// and each 1 cm of widget area corresponds to 0.5 cm of the device space area. - PDFReal getZoom() const { return m_zoom; } - - /// Sets the page layout. Page layout can be one of the PDF's page layouts. - /// \param pageLayout Page layout - void setPageLayout(PageLayout pageLayout); - - /// Returns the page layout - PageLayout getPageLayout() const { return m_controller->getPageLayout(); } - - /// Returns pages, which are intersecting rectangle (even partially) - /// \param rect Rectangle to test - std::vector getPagesIntersectingRect(QRect rect) const; - - /// Returns page, under which is point. If no page is under the point, - /// then -1 is returned. Point is in widget coordinates. If \p pagePoint - /// is not nullptr, then point in page coordinate space is set here. - /// \param point Point - /// \param pagePoint Point in page coordinate system - PDFInteger getPageUnderPoint(QPoint point, QPointF* pagePoint) const; - - /// Returns bounding box of pages, which are intersecting rectangle (even partially) - /// \param rect Rectangle to test - QRect getPagesIntersectingRectBoundingBox(QRect rect) const; - - /// Returns true, if we are in the block mode (multiple blocks with separate pages), - /// or continuous mode (single block with continuous list of separated pages). - bool isBlockMode() const; - - /// Updates renderer (in current implementation, renderer for page thumbnails) - /// using given parameters. - /// \param useOpenGL Use OpenGL for rendering? - /// \param surfaceFormat Surface format for OpenGL rendering - void updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat); - - /// Prefetches (prerenders) pages after page with pageIndex, i.e., prepares - /// for non-flickering scroll operation. - void prefetchPages(PDFInteger pageIndex); - - static constexpr PDFReal ZOOM_STEP = 1.2; - - const PDFDocument* getDocument() const { return m_controller->getDocument(); } - PDFFontCache* getFontCache() const { return m_controller->getFontCache(); } - const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_controller->getOptionalContentActivity(); } - PDFRenderer::Features getFeatures() const; - const PDFMeshQualitySettings& getMeshQualitySettings() const { return m_meshQualitySettings; } - PDFAsynchronousPageCompiler* getCompiler() const { return m_compiler; } - const PDFCMSManager* getCMSManager() const; - PDFProgress* getProgress() const { return m_progress; } - void setProgress(PDFProgress* progress) { m_progress = progress; } - PDFAsynchronousTextLayoutCompiler* getTextLayoutCompiler() const { return m_textLayoutCompiler; } - PDFWidget* getWidget() const { return m_widget; } - bool isUsingOpenGL() const { return m_useOpenGL; } - const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; } - - void setFeatures(PDFRenderer::Features features); - void setPreferredMeshResolutionRatio(PDFReal ratio); - void setMinimalMeshResolutionRatio(PDFReal ratio); - void setColorTolerance(PDFReal colorTolerance); - - static constexpr PDFReal getMinZoom() { return MIN_ZOOM; } - static constexpr PDFReal getMaxZoom() { return MAX_ZOOM; } - - void registerDrawInterface(IDocumentDrawInterface* drawInterface) { m_drawInterfaces.insert(drawInterface); } - void unregisterDrawInterface(IDocumentDrawInterface* drawInterface) { m_drawInterfaces.erase(drawInterface); } - - /// Returns current paper color - QColor getPaperColor(); - - /// Transforms pixels to device space - /// \param pixel Value in pixels - PDFReal transformPixelToDeviceSpace(PDFReal pixel) const { return pixel * m_pixelToDeviceSpaceUnit; } - - /// Transforms value in device space to pixel value - /// \param deviceSpaceValue Value in device space - PDFReal transformDeviceSpaceToPixel(PDFReal deviceSpaceValue) const { return deviceSpaceValue * m_deviceSpaceUnitToPixel; } - - /// Returns snapshot of current view area - PDFWidgetSnapshot getSnapshot() const; - - PDFWidgetAnnotationManager* getAnnotationManager() const; - -signals: - void drawSpaceChanged(); - void pageLayoutChanged(); - void renderingError(PDFInteger pageIndex, const QList& errors); - void repaintNeeded(); - void pageImageChanged(bool all, const std::vector& pages); - void textLayoutChanged(); - -private: - struct LayoutItem - { - constexpr inline explicit LayoutItem() : pageIndex(-1) { } - constexpr inline explicit LayoutItem(PDFInteger pageIndex, const QRect& pageRect) : - pageIndex(pageIndex), pageRect(pageRect) { } - - - PDFInteger pageIndex; - QRect pageRect; - }; - - struct Layout - { - inline void clear() - { - items.clear(); - blockRect = QRect(); - } - - std::vector items; - QRect blockRect; - }; - - static constexpr size_t INVALID_BLOCK_INDEX = std::numeric_limits::max(); - - // Minimal/maximal zoom is from 8% to 6400 %, according to the PDF 1.7 Reference, - // Appendix C. - - static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0; - static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0; - - /// Converts rectangle from device space to the pixel space - QRectF fromDeviceSpace(const QRectF& rect) const; - - void onTextLayoutChanged(); - void onOptionalContentGroupStateChanged(); - void onColorManagementSystemChanged(); - void onHorizontalScrollbarValueChanged(int value); - void onVerticalScrollbarValueChanged(int value); - - void setHorizontalOffset(int value); - void setVerticalOffset(int value); - void setBlockIndex(int index); - - void updateHorizontalScrollbarFromOffset(); - void updateVerticalScrollbarFromOffset(); - - template - struct Range - { - constexpr inline Range() : min(T()), max(T()) { } - constexpr inline Range(T value) : min(value), max(value) { } - constexpr inline Range(T min, T max) : min(min), max(max) { } - - T min; - T max; - - constexpr inline T bound(T value) { return qBound(min, value, max); } - }; - - /// Flag, disables the update - bool m_updateDisabled; - - /// Current block (in the draw space controller) - size_t m_currentBlock; - - /// Number of pixels (fractional) per milimeter (unit is pixel/mm) of the screen, - /// so, size of the area in milimeters can be computed as pixelCount * m_pixelPerMM [mm]. - PDFReal m_pixelPerMM; - - /// Zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom, - /// and each 1 cm of widget area corresponds to 0.5 cm of the device space area. - PDFReal m_zoom; - - /// Converts pixel to device space units (mm) using zoom - PDFReal m_pixelToDeviceSpaceUnit; - - /// Converts device space units (mm) to real pixels using zoom - PDFReal m_deviceSpaceUnitToPixel; - - /// Actual vertical offset of the draw space area in the widget (so block will be drawn - /// with this vertical offset) - PDFInteger m_verticalOffset; - - /// Range of vertical offset - Range m_verticalOffsetRange; - - /// Actual horizontal offset of the draw space area in the widget (so block will be drawn - /// with this horizontal offset) - PDFInteger m_horizontalOffset; - - /// Range for horizontal offset - Range m_horizontalOffsetRange; - - /// Draw space controller - PDFDrawSpaceController* m_controller; - - /// Controlled draw widget (proxy is for this widget) - PDFWidget* m_widget; - - /// Vertical scrollbar - QScrollBar* m_verticalScrollbar; - - /// Horizontal scrollbar - QScrollBar* m_horizontalScrollbar; - - /// Current page layout - Layout m_layout; - - /// Renderer features - PDFRenderer::Features m_features; - - /// Mesh quality settings - PDFMeshQualitySettings m_meshQualitySettings; - - /// Page compiler - PDFAsynchronousPageCompiler* m_compiler; - - /// Text layout compiler - PDFAsynchronousTextLayoutCompiler* m_textLayoutCompiler; - - /// Page image rasterizer for thumbnails - PDFRasterizer* m_rasterizer; - - /// Progress - PDFProgress* m_progress; - - /// Additional drawing interfaces - std::set m_drawInterfaces; - - /// Use OpenGL for rendering? - bool m_useOpenGL; - - /// Surface format for OpenGL - QSurfaceFormat m_surfaceFormat; -}; - -} // namespace pdf - -#endif // PDFDRAWSPACECONTROLLER_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDRAWSPACECONTROLLER_H +#define PDFDRAWSPACECONTROLLER_H + +#include "pdfglobal.h" +#include "pdfdocument.h" +#include "pdfrenderer.h" +#include "pdffont.h" +#include "pdfdocumentdrawinterface.h" + +#include +#include +#include + +class QPainter; +class QScrollBar; + +namespace pdf +{ +class PDFProgress; +class PDFWidget; +class PDFCMSManager; +class PDFTextLayoutGetter; +class PDFWidgetAnnotationManager; +class PDFAsynchronousPageCompiler; +class PDFAsynchronousTextLayoutCompiler; + +/// This class controls draw space - page layout. Pages are divided into blocks +/// each block can contain one or multiple pages. Units are in milimeters. +/// Pages are layouted in zoom-independent mode. +class PDFDrawSpaceController : public QObject +{ + Q_OBJECT + +public: + explicit PDFDrawSpaceController(QObject* parent); + virtual ~PDFDrawSpaceController() override; + + /// Sets the document and recalculates the draw space. Document can be nullptr, + /// in that case, draw space is cleared. Optional content activity can be nullptr, + /// in that case, no content is suppressed. + /// \param document Document + void setDocument(const PDFModifiedDocument& document); + + /// Sets the page layout. Page layout can be one of the PDF's page layouts. + /// \param pageLayout Page layout + void setPageLayout(PageLayout pageLayout); + + /// Returns the page layout + PageLayout getPageLayout() const { return m_pageLayoutMode; } + + /// Returns the block count + size_t getBlockCount() const { return m_blockItems.size(); } + + /// Return the bounding rectangle of the block. If block doesn't exist, + /// then invalid rectangle is returned (no exception is thrown). + /// \param blockIndex Index of the block + QRectF getBlockBoundingRectangle(size_t blockIndex) const; + + /// Represents layouted page. This structure contains index of the block, index of the + /// page and page rectangle, in which the page is contained. + struct LayoutItem + { + constexpr inline explicit LayoutItem() : blockIndex(-1), pageIndex(-1) { } + constexpr inline explicit LayoutItem(PDFInteger blockIndex, PDFInteger pageIndex, const QRectF& pageRectMM) : + blockIndex(blockIndex), pageIndex(pageIndex), pageRectMM(pageRectMM) { } + + bool isValid() const { return pageIndex != -1; } + + PDFInteger blockIndex; + PDFInteger pageIndex; + QRectF pageRectMM; + }; + + using LayoutItems = std::vector; + + /// Returns the layout items for desired block. If block doesn't exist, + /// then empty array is returned. + /// \param blockIndex Index of the block + LayoutItems getLayoutItems(size_t blockIndex) const; + + /// Returns layout for single page. If page index is invalid, + /// or page layout cannot be found, then invalid layout item is returned. + /// \param pageIndex Page index + LayoutItem getLayoutItemForPage(PDFInteger pageIndex) const; + + /// Returns the document + const PDFDocument* getDocument() const { return m_document; } + + /// Returns the font cache + const PDFFontCache* getFontCache() const { return &m_fontCache; } + + /// Returns the font cache + PDFFontCache* getFontCache() { return &m_fontCache; } + + /// Returns optional content activity + const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_optionalContentActivity; } + + /// Returns reference bounding box for correct calculation of zoom fit/fit vertical/fit horizontal. + /// If zoom is set in a way to display this bounding box on a screen, then it is assured that + /// any page on the screen will fit this bounding box, regardless of mode (single/two columns, etc.). + QSizeF getReferenceBoundingBox() const; + + /// Returns page rotation + PageRotation getPageRotation() const { return m_pageRotation; } + + /// Sets page rotation + void setPageRotation(PageRotation pageRotation); + +signals: + void drawSpaceChanged(); + void repaintNeeded(); + void pageImageChanged(bool all, const std::vector& pages); + +private: + /// Recalculates the draw space. Preserves setted page rotation. + void recalculate(); + + /// Clears the draw space. Emits signal if desired. + void clear(bool emitSignal); + + /// Represents data for the single block. Contains block size in milimeters. + struct LayoutBlock + { + constexpr inline explicit LayoutBlock() = default; + constexpr inline explicit LayoutBlock(const QRectF& blockRectMM) : blockRectMM(blockRectMM) { } + + QRectF blockRectMM; + }; + + using BlockItems = std::vector; + + const PDFDocument* m_document; + const PDFOptionalContentActivity* m_optionalContentActivity; + + PageLayout m_pageLayoutMode; + LayoutItems m_layoutItems; + BlockItems m_blockItems; + PDFReal m_verticalSpacingMM; + PDFReal m_horizontalSpacingMM; + PageRotation m_pageRotation; + + /// Font cache + PDFFontCache m_fontCache; +}; + +/// Snapshot for current widget viewable items. +struct PDFWidgetSnapshot +{ + struct SnapshotItem + { + PDFInteger pageIndex = -1; ///< Index of page + QRectF rect; ///< Page rectangle on viewport + QMatrix pageToDeviceMatrix; ///< Transforms page coordinates to widget coordinates + const PDFPrecompiledPage* compiledPage = nullptr; ///< Compiled page (can be nullptr) + }; + + bool hasPage(PDFInteger pageIndex) const { return getPageSnapshot(pageIndex) != nullptr; } + const SnapshotItem* getPageSnapshot(PDFInteger pageIndex) const; + + using SnapshotItems = std::vector; + SnapshotItems items; +}; + +/// This is a proxy class to draw space controller using widget. We have two spaces, pixel space +/// (on the controlled widget) and device space (device is draw space controller). +class PDF4QTLIBSHARED_EXPORT PDFDrawWidgetProxy : public QObject +{ + Q_OBJECT + +public: + explicit PDFDrawWidgetProxy(QObject* parent); + virtual ~PDFDrawWidgetProxy() override; + + /// Sets the document and updates the draw space. Document can be nullptr, + /// in that case, draw space is cleared. Optional content activity can be nullptr, + /// in that case, no content is suppressed. + /// \param document Document + void setDocument(const PDFModifiedDocument& document); + + void init(PDFWidget* widget); + + /// Updates the draw space area + void update(); + + /// Creates page point to device point matrix for the given rectangle. It creates transformation + /// from page's media box to the target rectangle. + /// \param page Page, for which we want to create matrix + /// \param rectangle Page rectangle, to which is page media box transformed + QMatrix createPagePointToDevicePointMatrix(const PDFPage* page, const QRectF& rectangle) const; + + /// Draws the actually visible pages on the painter using the rectangle. + /// Rectangle is space in the widget, which is used for painting the PDF. + /// This function is using drawPages function to draw all pages. After that, + /// custom drawing is performed. + /// \sa drawPages + /// \param painter Painter to paint the PDF pages + /// \param rect Rectangle in which the content is painted + void draw(QPainter* painter, QRect rect); + + /// Draws the actually visible pages on the painter using the rectangle. + /// Rectangle is space in the widget, which is used for painting the PDF. + /// \param painter Painter to paint the PDF pages + /// \param rect Rectangle in which the content is painted + /// \param features Rendering features + void drawPages(QPainter* painter, QRect rect, PDFRenderer::Features features); + + /// Draws thumbnail image of the given size (so larger of the page size + /// width or height equals to pixel size and the latter size is rescaled + /// using the aspect ratio) + /// \param pixelSize Pixel size + QImage drawThumbnailImage(PDFInteger pageIndex, int pixelSize) const; + + enum Operation + { + ZoomIn, + ZoomOut, + ZoomFit, + ZoomFitWidth, + ZoomFitHeight, + NavigateDocumentStart, + NavigateDocumentEnd, + NavigateNextPage, + NavigatePreviousPage, + NavigateNextStep, + NavigatePreviousStep, + RotateRight, + RotateLeft + }; + + /// Performs the desired operation (for example navigation). + /// \param operation Operation to be performed + void performOperation(Operation operation); + + /// Scrolls by pixels, if it is possible. If it is not possible to scroll, + /// then nothing happens. Returns pixel offset, by which view camera was moved. + /// \param offset Offset in pixels + QPoint scrollByPixels(QPoint offset); + + /// Sets the zoom. Tries to preserve current offsets (so the current visible + /// area will be visible after the zoom). + /// \param zoom New zoom + void zoom(PDFReal zoom); + + enum class ZoomHint + { + Fit, + FitWidth, + FitHeight + }; + + /// Calculates zoom using given hint (i.e. to fill whole space, fill vertical, + /// or fill horizontal). + /// \param hint Zoom hint type + PDFReal getZoomHint(ZoomHint hint) const; + + /// Go to the specified page + /// \param pageIndex Page to scroll to + void goToPage(PDFInteger pageIndex); + + /// Returns current zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom, + /// and each 1 cm of widget area corresponds to 0.5 cm of the device space area. + PDFReal getZoom() const { return m_zoom; } + + /// Sets the page layout. Page layout can be one of the PDF's page layouts. + /// \param pageLayout Page layout + void setPageLayout(PageLayout pageLayout); + + /// Returns the page layout + PageLayout getPageLayout() const { return m_controller->getPageLayout(); } + + /// Returns pages, which are intersecting rectangle (even partially) + /// \param rect Rectangle to test + std::vector getPagesIntersectingRect(QRect rect) const; + + /// Returns page, under which is point. If no page is under the point, + /// then -1 is returned. Point is in widget coordinates. If \p pagePoint + /// is not nullptr, then point in page coordinate space is set here. + /// \param point Point + /// \param pagePoint Point in page coordinate system + PDFInteger getPageUnderPoint(QPoint point, QPointF* pagePoint) const; + + /// Returns bounding box of pages, which are intersecting rectangle (even partially) + /// \param rect Rectangle to test + QRect getPagesIntersectingRectBoundingBox(QRect rect) const; + + /// Returns true, if we are in the block mode (multiple blocks with separate pages), + /// or continuous mode (single block with continuous list of separated pages). + bool isBlockMode() const; + + /// Updates renderer (in current implementation, renderer for page thumbnails) + /// using given parameters. + /// \param useOpenGL Use OpenGL for rendering? + /// \param surfaceFormat Surface format for OpenGL rendering + void updateRenderer(bool useOpenGL, const QSurfaceFormat& surfaceFormat); + + /// Prefetches (prerenders) pages after page with pageIndex, i.e., prepares + /// for non-flickering scroll operation. + void prefetchPages(PDFInteger pageIndex); + + static constexpr PDFReal ZOOM_STEP = 1.2; + + const PDFDocument* getDocument() const { return m_controller->getDocument(); } + PDFFontCache* getFontCache() const { return m_controller->getFontCache(); } + const PDFOptionalContentActivity* getOptionalContentActivity() const { return m_controller->getOptionalContentActivity(); } + PDFRenderer::Features getFeatures() const; + const PDFMeshQualitySettings& getMeshQualitySettings() const { return m_meshQualitySettings; } + PDFAsynchronousPageCompiler* getCompiler() const { return m_compiler; } + const PDFCMSManager* getCMSManager() const; + PDFProgress* getProgress() const { return m_progress; } + void setProgress(PDFProgress* progress) { m_progress = progress; } + PDFAsynchronousTextLayoutCompiler* getTextLayoutCompiler() const { return m_textLayoutCompiler; } + PDFWidget* getWidget() const { return m_widget; } + bool isUsingOpenGL() const { return m_useOpenGL; } + const QSurfaceFormat& getSurfaceFormat() const { return m_surfaceFormat; } + + void setFeatures(PDFRenderer::Features features); + void setPreferredMeshResolutionRatio(PDFReal ratio); + void setMinimalMeshResolutionRatio(PDFReal ratio); + void setColorTolerance(PDFReal colorTolerance); + + static constexpr PDFReal getMinZoom() { return MIN_ZOOM; } + static constexpr PDFReal getMaxZoom() { return MAX_ZOOM; } + + void registerDrawInterface(IDocumentDrawInterface* drawInterface) { m_drawInterfaces.insert(drawInterface); } + void unregisterDrawInterface(IDocumentDrawInterface* drawInterface) { m_drawInterfaces.erase(drawInterface); } + + /// Returns current paper color + QColor getPaperColor(); + + /// Transforms pixels to device space + /// \param pixel Value in pixels + PDFReal transformPixelToDeviceSpace(PDFReal pixel) const { return pixel * m_pixelToDeviceSpaceUnit; } + + /// Transforms value in device space to pixel value + /// \param deviceSpaceValue Value in device space + PDFReal transformDeviceSpaceToPixel(PDFReal deviceSpaceValue) const { return deviceSpaceValue * m_deviceSpaceUnitToPixel; } + + /// Returns snapshot of current view area + PDFWidgetSnapshot getSnapshot() const; + + PDFWidgetAnnotationManager* getAnnotationManager() const; + +signals: + void drawSpaceChanged(); + void pageLayoutChanged(); + void renderingError(PDFInteger pageIndex, const QList& errors); + void repaintNeeded(); + void pageImageChanged(bool all, const std::vector& pages); + void textLayoutChanged(); + +private: + struct LayoutItem + { + constexpr inline explicit LayoutItem() : pageIndex(-1) { } + constexpr inline explicit LayoutItem(PDFInteger pageIndex, const QRect& pageRect) : + pageIndex(pageIndex), pageRect(pageRect) { } + + + PDFInteger pageIndex; + QRect pageRect; + }; + + struct Layout + { + inline void clear() + { + items.clear(); + blockRect = QRect(); + } + + std::vector items; + QRect blockRect; + }; + + static constexpr size_t INVALID_BLOCK_INDEX = std::numeric_limits::max(); + + // Minimal/maximal zoom is from 8% to 6400 %, according to the PDF 1.7 Reference, + // Appendix C. + + static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0; + static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0; + + /// Converts rectangle from device space to the pixel space + QRectF fromDeviceSpace(const QRectF& rect) const; + + void onTextLayoutChanged(); + void onOptionalContentGroupStateChanged(); + void onColorManagementSystemChanged(); + void onHorizontalScrollbarValueChanged(int value); + void onVerticalScrollbarValueChanged(int value); + + void setHorizontalOffset(int value); + void setVerticalOffset(int value); + void setBlockIndex(int index); + + void updateHorizontalScrollbarFromOffset(); + void updateVerticalScrollbarFromOffset(); + + template + struct Range + { + constexpr inline Range() : min(T()), max(T()) { } + constexpr inline Range(T value) : min(value), max(value) { } + constexpr inline Range(T min, T max) : min(min), max(max) { } + + T min; + T max; + + constexpr inline T bound(T value) { return qBound(min, value, max); } + }; + + /// Flag, disables the update + bool m_updateDisabled; + + /// Current block (in the draw space controller) + size_t m_currentBlock; + + /// Number of pixels (fractional) per milimeter (unit is pixel/mm) of the screen, + /// so, size of the area in milimeters can be computed as pixelCount * m_pixelPerMM [mm]. + PDFReal m_pixelPerMM; + + /// Zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom, + /// and each 1 cm of widget area corresponds to 0.5 cm of the device space area. + PDFReal m_zoom; + + /// Converts pixel to device space units (mm) using zoom + PDFReal m_pixelToDeviceSpaceUnit; + + /// Converts device space units (mm) to real pixels using zoom + PDFReal m_deviceSpaceUnitToPixel; + + /// Actual vertical offset of the draw space area in the widget (so block will be drawn + /// with this vertical offset) + PDFInteger m_verticalOffset; + + /// Range of vertical offset + Range m_verticalOffsetRange; + + /// Actual horizontal offset of the draw space area in the widget (so block will be drawn + /// with this horizontal offset) + PDFInteger m_horizontalOffset; + + /// Range for horizontal offset + Range m_horizontalOffsetRange; + + /// Draw space controller + PDFDrawSpaceController* m_controller; + + /// Controlled draw widget (proxy is for this widget) + PDFWidget* m_widget; + + /// Vertical scrollbar + QScrollBar* m_verticalScrollbar; + + /// Horizontal scrollbar + QScrollBar* m_horizontalScrollbar; + + /// Current page layout + Layout m_layout; + + /// Renderer features + PDFRenderer::Features m_features; + + /// Mesh quality settings + PDFMeshQualitySettings m_meshQualitySettings; + + /// Page compiler + PDFAsynchronousPageCompiler* m_compiler; + + /// Text layout compiler + PDFAsynchronousTextLayoutCompiler* m_textLayoutCompiler; + + /// Page image rasterizer for thumbnails + PDFRasterizer* m_rasterizer; + + /// Progress + PDFProgress* m_progress; + + /// Additional drawing interfaces + std::set m_drawInterfaces; + + /// Use OpenGL for rendering? + bool m_useOpenGL; + + /// Surface format for OpenGL + QSurfaceFormat m_surfaceFormat; +}; + +} // namespace pdf + +#endif // PDFDRAWSPACECONTROLLER_H diff --git a/Pdf4QtLib/sources/pdfdrawwidget.cpp b/Pdf4QtLib/sources/pdfdrawwidget.cpp index 417f096..e41ff5b 100644 --- a/Pdf4QtLib/sources/pdfdrawwidget.cpp +++ b/Pdf4QtLib/sources/pdfdrawwidget.cpp @@ -1,625 +1,625 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfdrawwidget.h" -#include "pdfdrawspacecontroller.h" -#include "pdfcompiler.h" -#include "pdfwidgettool.h" -#include "pdfannotation.h" -#include "pdfform.h" - -#include -#include -#include -#include -#include - -namespace pdf -{ - -PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent) : - QWidget(parent), - m_cmsManager(cmsManager), - m_toolManager(nullptr), - m_annotationManager(nullptr), - m_formManager(nullptr), - m_drawWidget(nullptr), - m_horizontalScrollBar(nullptr), - m_verticalScrollBar(nullptr), - m_proxy(nullptr) -{ - m_drawWidget = createDrawWidget(engine, samplesCount); - m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this); - m_verticalScrollBar = new QScrollBar(Qt::Vertical, this); - - QGridLayout* layout = new QGridLayout(this); - layout->setSpacing(0); - layout->addWidget(m_drawWidget->getWidget(), 0, 0); - layout->addWidget(m_horizontalScrollBar, 1, 0); - layout->addWidget(m_verticalScrollBar, 0, 1); - layout->setMargin(0); - - setLayout(layout); - setFocusProxy(m_drawWidget->getWidget()); - - m_proxy = new PDFDrawWidgetProxy(this); - m_proxy->init(this); - connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError); - connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update)); - connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged); - updateRendererImpl(); -} - -PDFWidget::~PDFWidget() -{ - -} - -bool PDFWidget::focusNextPrevChild(bool next) -{ - if (m_formManager && m_formManager->focusNextPrevFormField(next)) - { - return true; - } - - return QWidget::focusNextPrevChild(next); -} - -void PDFWidget::setDocument(const PDFModifiedDocument& document) -{ - m_proxy->setDocument(document); - m_pageRenderingErrors.clear(); - m_drawWidget->getWidget()->update(); -} - -void PDFWidget::updateRenderer(RendererEngine engine, int samplesCount) -{ - PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast(m_drawWidget->getWidget()); - PDFDrawWidget* softwareDrawWidget = qobject_cast(m_drawWidget->getWidget()); - - // Do we need to change renderer? - if ((openglDrawWidget && engine != RendererEngine::OpenGL) || (softwareDrawWidget && engine != RendererEngine::Software)) - { - QGridLayout* layout = qobject_cast(this->layout()); - layout->removeWidget(m_drawWidget->getWidget()); - delete m_drawWidget->getWidget(); - - m_drawWidget = createDrawWidget(engine, samplesCount); - layout->addWidget(m_drawWidget->getWidget(), 0, 0); - setFocusProxy(m_drawWidget->getWidget()); - connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update)); - } - else if (openglDrawWidget) - { - // Just check the samples count - QSurfaceFormat format = openglDrawWidget->format(); - if (format.samples() != samplesCount) - { - format.setSamples(samplesCount); - openglDrawWidget->setFormat(format); - } - } - updateRendererImpl(); -} - -void PDFWidget::updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit) -{ - m_proxy->getCompiler()->setCacheLimit(compiledPageCacheLimit); - QPixmapCache::setCacheLimit(thumbnailsCacheLimit); - m_proxy->getFontCache()->setCacheLimits(fontCacheLimit, instancedFontCacheLimit); -} - -int PDFWidget::getPageRenderingErrorCount() const -{ - int count = 0; - for (const auto& item : m_pageRenderingErrors) - { - count += item.second.size(); - } - return count; -} - -void PDFWidget::updateRendererImpl() -{ - PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast(m_drawWidget->getWidget()); - m_proxy->updateRenderer(openglDrawWidget != nullptr, openglDrawWidget ? openglDrawWidget->format() : QSurfaceFormat::defaultFormat()); -} - -void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList& errors) -{ - // Empty list of error should not be reported! - Q_ASSERT(!errors.empty()); - m_pageRenderingErrors[pageIndex] = errors; - emit pageRenderingErrorsChanged(pageIndex, errors.size()); -} - -void PDFWidget::onPageImageChanged(bool all, const std::vector& pages) -{ - if (all) - { - m_drawWidget->getWidget()->update(); - } - else - { - std::vector currentPages = m_drawWidget->getCurrentPages(); - - Q_ASSERT(std::is_sorted(pages.cbegin(), pages.cend())); - for (PDFInteger pageIndex : currentPages) - { - if (std::binary_search(pages.cbegin(), pages.cend(), pageIndex)) - { - m_drawWidget->getWidget()->update(); - return; - } - } - } -} - -IDrawWidget* PDFWidget::createDrawWidget(RendererEngine rendererEngine, int samplesCount) -{ - switch (rendererEngine) - { - case RendererEngine::Software: - return new PDFDrawWidget(this, this); - - case RendererEngine::OpenGL: - return new PDFOpenGLDrawWidget(this, samplesCount, this); - - default: - Q_ASSERT(false); - break; - } - - return nullptr; -} - -void PDFWidget::removeInputInterface(IDrawWidgetInputInterface* inputInterface) -{ - auto it = std::find(m_inputInterfaces.begin(), m_inputInterfaces.end(), inputInterface); - if (it != m_inputInterfaces.end()) - { - m_inputInterfaces.erase(it); - } -} - -void PDFWidget::addInputInterface(IDrawWidgetInputInterface* inputInterface) -{ - if (inputInterface) - { - m_inputInterfaces.push_back(inputInterface); - std::sort(m_inputInterfaces.begin(), m_inputInterfaces.end(), IDrawWidgetInputInterface::Comparator()); - } -} - -PDFFormManager* PDFWidget::getFormManager() const -{ - return m_formManager; -} - -void PDFWidget::setFormManager(PDFFormManager* formManager) -{ - removeInputInterface(m_formManager); - m_formManager = formManager; - addInputInterface(m_formManager); -} - -void PDFWidget::setToolManager(PDFToolManager* toolManager) -{ - removeInputInterface(m_toolManager); - m_toolManager = toolManager; - addInputInterface(m_toolManager); -} - -void PDFWidget::setAnnotationManager(PDFWidgetAnnotationManager* annotationManager) -{ - removeInputInterface(m_annotationManager); - m_annotationManager = annotationManager; - addInputInterface(m_annotationManager); -} - -template -PDFDrawWidgetBase::PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent) : - BaseWidget(parent), - m_widget(widget), - m_mouseOperation(MouseOperation::None) -{ - this->setFocusPolicy(Qt::StrongFocus); - this->setMouseTracking(true); -} - -template -std::vector PDFDrawWidgetBase::getCurrentPages() const -{ - return this->m_widget->getDrawWidgetProxy()->getPagesIntersectingRect(this->rect()); -} - -template -QSize PDFDrawWidgetBase::minimumSizeHint() const -{ - return QSize(200, 200); -} - -template -bool PDFDrawWidgetBase::event(QEvent* event) -{ - if (event->type() == QEvent::ShortcutOverride) - { - return processEvent(static_cast(event)); - } - - return BaseWidget::event(event); -} - -template -void PDFDrawWidgetBase::performMouseOperation(QPoint currentMousePosition) -{ - switch (m_mouseOperation) - { - case MouseOperation::None: - // No operation performed - break; - - case MouseOperation::Translate: - { - QPoint difference = currentMousePosition - m_lastMousePosition; - m_widget->getDrawWidgetProxy()->scrollByPixels(difference); - m_lastMousePosition = currentMousePosition; - break; - } - - default: - Q_ASSERT(false); - } -} - -template -template -bool PDFDrawWidgetBase::processEvent(Event* event) -{ - QString tooltip; - for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces()) - { - (inputInterface->*Function)(this, event); - - // Update tooltip - if (tooltip.isEmpty()) - { - tooltip = inputInterface->getTooltip(); - } - - // If event is accepted, then update cursor/tooltip and return - if (event->isAccepted()) - { - this->setToolTip(tooltip); - this->updateCursor(); - return true; - } - } - this->setToolTip(tooltip); - - return false; -} - -template -void PDFDrawWidgetBase::keyPressEvent(QKeyEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } - - // Vertical navigation - QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); - if (verticalScrollbar->isVisible()) - { - constexpr std::pair keyToOperations[] = - { - { QKeySequence::MoveToStartOfDocument, PDFDrawWidgetProxy::NavigateDocumentStart }, - { QKeySequence::MoveToEndOfDocument, PDFDrawWidgetProxy::NavigateDocumentEnd }, - { QKeySequence::MoveToNextPage, PDFDrawWidgetProxy::NavigateNextPage }, - { QKeySequence::MoveToPreviousPage, PDFDrawWidgetProxy::NavigatePreviousPage }, - { QKeySequence::MoveToNextLine, PDFDrawWidgetProxy::NavigateNextStep }, - { QKeySequence::MoveToPreviousLine, PDFDrawWidgetProxy::NavigatePreviousStep } - }; - - for (const std::pair& keyToOperation : keyToOperations) - { - if (event->matches(keyToOperation.first)) - { - m_widget->getDrawWidgetProxy()->performOperation(keyToOperation.second); - event->accept(); - } - } - } - - updateCursor(); -} - -template -void PDFDrawWidgetBase::keyReleaseEvent(QKeyEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } - - event->accept(); -} - -template -void PDFDrawWidgetBase::mousePressEvent(QMouseEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } - - if (event->button() == Qt::LeftButton) - { - m_mouseOperation = MouseOperation::Translate; - m_lastMousePosition = event->pos(); - } - - updateCursor(); - event->accept(); -} - -template -void PDFDrawWidgetBase::mouseDoubleClickEvent(QMouseEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } -} - -template -void PDFDrawWidgetBase::mouseReleaseEvent(QMouseEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } - - performMouseOperation(event->pos()); - - switch (m_mouseOperation) - { - case MouseOperation::None: - break; - - case MouseOperation::Translate: - { - m_mouseOperation = MouseOperation::None; - break; - } - - default: - Q_ASSERT(false); - } - - updateCursor(); - event->accept(); -} - -template -void PDFDrawWidgetBase::mouseMoveEvent(QMouseEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } - - performMouseOperation(event->pos()); - updateCursor(); - event->accept(); -} - -template -void PDFDrawWidgetBase::updateCursor() -{ - std::optional cursor; - - for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces()) - { - cursor = inputInterface->getCursor(); - - if (cursor) - { - // We have found cursor - break; - } - } - - if (!cursor) - { - switch (m_mouseOperation) - { - case MouseOperation::None: - cursor = QCursor(Qt::OpenHandCursor); - break; - - case MouseOperation::Translate: - cursor = QCursor(Qt::ClosedHandCursor); - break; - - default: - Q_ASSERT(false); - break; - } - } - - if (cursor) - { - this->setCursor(*cursor); - } - else - { - this->unsetCursor(); - } -} - -template -void PDFDrawWidgetBase::wheelEvent(QWheelEvent* event) -{ - event->ignore(); - - if (processEvent(event)) - { - return; - } - - Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); - - PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy(); - if (keyboardModifiers.testFlag(Qt::ControlModifier)) - { - // Zoom in/Zoom out - const int angleDeltaY = event->angleDelta().y(); - const PDFReal zoom = m_widget->getDrawWidgetProxy()->getZoom(); - const PDFReal zoomStep = std::pow(PDFDrawWidgetProxy::ZOOM_STEP, static_cast(angleDeltaY) / static_cast(QWheelEvent::DefaultDeltasPerStep)); - const PDFReal newZoom = zoom * zoomStep; - proxy->zoom(newZoom); - } - else - { - // Move Up/Down. Angle is negative, if wheel is scrolled down. First we try to scroll by pixel delta. - // Otherwise we compute scroll using angle. - QPoint scrollByPixels = event->pixelDelta(); - if (scrollByPixels.isNull()) - { - const QPoint angleDelta = event->angleDelta(); - const bool shiftModifier = keyboardModifiers.testFlag(Qt::ShiftModifier); - int stepVertical = 0; - int stepHorizontal = shiftModifier ? m_widget->getHorizontalScrollbar()->pageStep() : m_widget->getHorizontalScrollbar()->singleStep(); - - if (proxy->isBlockMode()) - { - // In block mode, we must calculate pixel offsets differently - scrollbars corresponds to indices of blocks, - // not to the pixels. - QRect boundingBox = proxy->getPagesIntersectingRectBoundingBox(this->rect()); - - if (boundingBox.isEmpty()) - { - // This occurs, when we have not opened a document - boundingBox = this->rect(); - } - - stepVertical = shiftModifier ? boundingBox.height() : boundingBox.height() / 10; - } - else - { - stepVertical = shiftModifier ? m_widget->getVerticalScrollbar()->pageStep() : m_widget->getVerticalScrollbar()->singleStep(); - } - - const int scrollVertical = stepVertical * static_cast(angleDelta.y()) / static_cast(QWheelEvent::DefaultDeltasPerStep); - const int scrollHorizontal = stepHorizontal * static_cast(angleDelta.x()) / static_cast(QWheelEvent::DefaultDeltasPerStep); - - scrollByPixels = QPoint(scrollHorizontal, scrollVertical); - } - - QPoint offset = proxy->scrollByPixels(scrollByPixels); - - if (offset.y() == 0 && scrollByPixels.y() != 0 && proxy->isBlockMode()) - { - // We must move to another block (we are in block mode) - bool up = scrollByPixels.y() > 0; - - m_widget->getVerticalScrollbar()->setValue(m_widget->getVerticalScrollbar()->value() + (up ? -1 : 1)); - proxy->scrollByPixels(QPoint(0, up ? std::numeric_limits::min() : std::numeric_limits::max())); - } - } - - event->accept(); -} - -PDFOpenGLDrawWidget::PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent) : - BaseClass(widget, parent) -{ - QSurfaceFormat format = this->format(); - format.setProfile(QSurfaceFormat::CoreProfile); - format.setSamples(samplesCount); - format.setColorSpace(QSurfaceFormat::sRGBColorSpace); - format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); - setFormat(format); -} - -PDFOpenGLDrawWidget::~PDFOpenGLDrawWidget() -{ - -} - -void PDFOpenGLDrawWidget::resizeGL(int w, int h) -{ - QOpenGLWidget::resizeGL(w, h); - - getPDFWidget()->getDrawWidgetProxy()->update(); -} - -void PDFOpenGLDrawWidget::initializeGL() -{ - QOpenGLWidget::initializeGL(); -} - -void PDFOpenGLDrawWidget::paintGL() -{ - QPainter painter(this); - getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect()); -} - -PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) : - BaseClass(widget, parent) -{ - -} - -PDFDrawWidget::~PDFDrawWidget() -{ - -} - -void PDFDrawWidget::paintEvent(QPaintEvent* event) -{ - Q_UNUSED(event); - - QPainter painter(this); - getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect()); -} - -void PDFDrawWidget::resizeEvent(QResizeEvent* event) -{ - BaseClass::resizeEvent(event); - - getPDFWidget()->getDrawWidgetProxy()->update(); -} - -template class PDFDrawWidgetBase; -template class PDFDrawWidgetBase; - -} // namespace pdf +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfdrawwidget.h" +#include "pdfdrawspacecontroller.h" +#include "pdfcompiler.h" +#include "pdfwidgettool.h" +#include "pdfannotation.h" +#include "pdfform.h" + +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFWidget::PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent) : + QWidget(parent), + m_cmsManager(cmsManager), + m_toolManager(nullptr), + m_annotationManager(nullptr), + m_formManager(nullptr), + m_drawWidget(nullptr), + m_horizontalScrollBar(nullptr), + m_verticalScrollBar(nullptr), + m_proxy(nullptr) +{ + m_drawWidget = createDrawWidget(engine, samplesCount); + m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this); + m_verticalScrollBar = new QScrollBar(Qt::Vertical, this); + + QGridLayout* layout = new QGridLayout(this); + layout->setSpacing(0); + layout->addWidget(m_drawWidget->getWidget(), 0, 0); + layout->addWidget(m_horizontalScrollBar, 1, 0); + layout->addWidget(m_verticalScrollBar, 0, 1); + layout->setMargin(0); + + setLayout(layout); + setFocusProxy(m_drawWidget->getWidget()); + + m_proxy = new PDFDrawWidgetProxy(this); + m_proxy->init(this); + connect(m_proxy, &PDFDrawWidgetProxy::renderingError, this, &PDFWidget::onRenderingError); + connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update)); + connect(m_proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFWidget::onPageImageChanged); + updateRendererImpl(); +} + +PDFWidget::~PDFWidget() +{ + +} + +bool PDFWidget::focusNextPrevChild(bool next) +{ + if (m_formManager && m_formManager->focusNextPrevFormField(next)) + { + return true; + } + + return QWidget::focusNextPrevChild(next); +} + +void PDFWidget::setDocument(const PDFModifiedDocument& document) +{ + m_proxy->setDocument(document); + m_pageRenderingErrors.clear(); + m_drawWidget->getWidget()->update(); +} + +void PDFWidget::updateRenderer(RendererEngine engine, int samplesCount) +{ + PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast(m_drawWidget->getWidget()); + PDFDrawWidget* softwareDrawWidget = qobject_cast(m_drawWidget->getWidget()); + + // Do we need to change renderer? + if ((openglDrawWidget && engine != RendererEngine::OpenGL) || (softwareDrawWidget && engine != RendererEngine::Software)) + { + QGridLayout* layout = qobject_cast(this->layout()); + layout->removeWidget(m_drawWidget->getWidget()); + delete m_drawWidget->getWidget(); + + m_drawWidget = createDrawWidget(engine, samplesCount); + layout->addWidget(m_drawWidget->getWidget(), 0, 0); + setFocusProxy(m_drawWidget->getWidget()); + connect(m_proxy, &PDFDrawWidgetProxy::repaintNeeded, m_drawWidget->getWidget(), QOverload<>::of(&QWidget::update)); + } + else if (openglDrawWidget) + { + // Just check the samples count + QSurfaceFormat format = openglDrawWidget->format(); + if (format.samples() != samplesCount) + { + format.setSamples(samplesCount); + openglDrawWidget->setFormat(format); + } + } + updateRendererImpl(); +} + +void PDFWidget::updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit) +{ + m_proxy->getCompiler()->setCacheLimit(compiledPageCacheLimit); + QPixmapCache::setCacheLimit(thumbnailsCacheLimit); + m_proxy->getFontCache()->setCacheLimits(fontCacheLimit, instancedFontCacheLimit); +} + +int PDFWidget::getPageRenderingErrorCount() const +{ + int count = 0; + for (const auto& item : m_pageRenderingErrors) + { + count += item.second.size(); + } + return count; +} + +void PDFWidget::updateRendererImpl() +{ + PDFOpenGLDrawWidget* openglDrawWidget = qobject_cast(m_drawWidget->getWidget()); + m_proxy->updateRenderer(openglDrawWidget != nullptr, openglDrawWidget ? openglDrawWidget->format() : QSurfaceFormat::defaultFormat()); +} + +void PDFWidget::onRenderingError(PDFInteger pageIndex, const QList& errors) +{ + // Empty list of error should not be reported! + Q_ASSERT(!errors.empty()); + m_pageRenderingErrors[pageIndex] = errors; + emit pageRenderingErrorsChanged(pageIndex, errors.size()); +} + +void PDFWidget::onPageImageChanged(bool all, const std::vector& pages) +{ + if (all) + { + m_drawWidget->getWidget()->update(); + } + else + { + std::vector currentPages = m_drawWidget->getCurrentPages(); + + Q_ASSERT(std::is_sorted(pages.cbegin(), pages.cend())); + for (PDFInteger pageIndex : currentPages) + { + if (std::binary_search(pages.cbegin(), pages.cend(), pageIndex)) + { + m_drawWidget->getWidget()->update(); + return; + } + } + } +} + +IDrawWidget* PDFWidget::createDrawWidget(RendererEngine rendererEngine, int samplesCount) +{ + switch (rendererEngine) + { + case RendererEngine::Software: + return new PDFDrawWidget(this, this); + + case RendererEngine::OpenGL: + return new PDFOpenGLDrawWidget(this, samplesCount, this); + + default: + Q_ASSERT(false); + break; + } + + return nullptr; +} + +void PDFWidget::removeInputInterface(IDrawWidgetInputInterface* inputInterface) +{ + auto it = std::find(m_inputInterfaces.begin(), m_inputInterfaces.end(), inputInterface); + if (it != m_inputInterfaces.end()) + { + m_inputInterfaces.erase(it); + } +} + +void PDFWidget::addInputInterface(IDrawWidgetInputInterface* inputInterface) +{ + if (inputInterface) + { + m_inputInterfaces.push_back(inputInterface); + std::sort(m_inputInterfaces.begin(), m_inputInterfaces.end(), IDrawWidgetInputInterface::Comparator()); + } +} + +PDFFormManager* PDFWidget::getFormManager() const +{ + return m_formManager; +} + +void PDFWidget::setFormManager(PDFFormManager* formManager) +{ + removeInputInterface(m_formManager); + m_formManager = formManager; + addInputInterface(m_formManager); +} + +void PDFWidget::setToolManager(PDFToolManager* toolManager) +{ + removeInputInterface(m_toolManager); + m_toolManager = toolManager; + addInputInterface(m_toolManager); +} + +void PDFWidget::setAnnotationManager(PDFWidgetAnnotationManager* annotationManager) +{ + removeInputInterface(m_annotationManager); + m_annotationManager = annotationManager; + addInputInterface(m_annotationManager); +} + +template +PDFDrawWidgetBase::PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent) : + BaseWidget(parent), + m_widget(widget), + m_mouseOperation(MouseOperation::None) +{ + this->setFocusPolicy(Qt::StrongFocus); + this->setMouseTracking(true); +} + +template +std::vector PDFDrawWidgetBase::getCurrentPages() const +{ + return this->m_widget->getDrawWidgetProxy()->getPagesIntersectingRect(this->rect()); +} + +template +QSize PDFDrawWidgetBase::minimumSizeHint() const +{ + return QSize(200, 200); +} + +template +bool PDFDrawWidgetBase::event(QEvent* event) +{ + if (event->type() == QEvent::ShortcutOverride) + { + return processEvent(static_cast(event)); + } + + return BaseWidget::event(event); +} + +template +void PDFDrawWidgetBase::performMouseOperation(QPoint currentMousePosition) +{ + switch (m_mouseOperation) + { + case MouseOperation::None: + // No operation performed + break; + + case MouseOperation::Translate: + { + QPoint difference = currentMousePosition - m_lastMousePosition; + m_widget->getDrawWidgetProxy()->scrollByPixels(difference); + m_lastMousePosition = currentMousePosition; + break; + } + + default: + Q_ASSERT(false); + } +} + +template +template +bool PDFDrawWidgetBase::processEvent(Event* event) +{ + QString tooltip; + for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces()) + { + (inputInterface->*Function)(this, event); + + // Update tooltip + if (tooltip.isEmpty()) + { + tooltip = inputInterface->getTooltip(); + } + + // If event is accepted, then update cursor/tooltip and return + if (event->isAccepted()) + { + this->setToolTip(tooltip); + this->updateCursor(); + return true; + } + } + this->setToolTip(tooltip); + + return false; +} + +template +void PDFDrawWidgetBase::keyPressEvent(QKeyEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } + + // Vertical navigation + QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar(); + if (verticalScrollbar->isVisible()) + { + constexpr std::pair keyToOperations[] = + { + { QKeySequence::MoveToStartOfDocument, PDFDrawWidgetProxy::NavigateDocumentStart }, + { QKeySequence::MoveToEndOfDocument, PDFDrawWidgetProxy::NavigateDocumentEnd }, + { QKeySequence::MoveToNextPage, PDFDrawWidgetProxy::NavigateNextPage }, + { QKeySequence::MoveToPreviousPage, PDFDrawWidgetProxy::NavigatePreviousPage }, + { QKeySequence::MoveToNextLine, PDFDrawWidgetProxy::NavigateNextStep }, + { QKeySequence::MoveToPreviousLine, PDFDrawWidgetProxy::NavigatePreviousStep } + }; + + for (const std::pair& keyToOperation : keyToOperations) + { + if (event->matches(keyToOperation.first)) + { + m_widget->getDrawWidgetProxy()->performOperation(keyToOperation.second); + event->accept(); + } + } + } + + updateCursor(); +} + +template +void PDFDrawWidgetBase::keyReleaseEvent(QKeyEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } + + event->accept(); +} + +template +void PDFDrawWidgetBase::mousePressEvent(QMouseEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } + + if (event->button() == Qt::LeftButton) + { + m_mouseOperation = MouseOperation::Translate; + m_lastMousePosition = event->pos(); + } + + updateCursor(); + event->accept(); +} + +template +void PDFDrawWidgetBase::mouseDoubleClickEvent(QMouseEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } +} + +template +void PDFDrawWidgetBase::mouseReleaseEvent(QMouseEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } + + performMouseOperation(event->pos()); + + switch (m_mouseOperation) + { + case MouseOperation::None: + break; + + case MouseOperation::Translate: + { + m_mouseOperation = MouseOperation::None; + break; + } + + default: + Q_ASSERT(false); + } + + updateCursor(); + event->accept(); +} + +template +void PDFDrawWidgetBase::mouseMoveEvent(QMouseEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } + + performMouseOperation(event->pos()); + updateCursor(); + event->accept(); +} + +template +void PDFDrawWidgetBase::updateCursor() +{ + std::optional cursor; + + for (IDrawWidgetInputInterface* inputInterface : m_widget->getInputInterfaces()) + { + cursor = inputInterface->getCursor(); + + if (cursor) + { + // We have found cursor + break; + } + } + + if (!cursor) + { + switch (m_mouseOperation) + { + case MouseOperation::None: + cursor = QCursor(Qt::OpenHandCursor); + break; + + case MouseOperation::Translate: + cursor = QCursor(Qt::ClosedHandCursor); + break; + + default: + Q_ASSERT(false); + break; + } + } + + if (cursor) + { + this->setCursor(*cursor); + } + else + { + this->unsetCursor(); + } +} + +template +void PDFDrawWidgetBase::wheelEvent(QWheelEvent* event) +{ + event->ignore(); + + if (processEvent(event)) + { + return; + } + + Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); + + PDFDrawWidgetProxy* proxy = m_widget->getDrawWidgetProxy(); + if (keyboardModifiers.testFlag(Qt::ControlModifier)) + { + // Zoom in/Zoom out + const int angleDeltaY = event->angleDelta().y(); + const PDFReal zoom = m_widget->getDrawWidgetProxy()->getZoom(); + const PDFReal zoomStep = std::pow(PDFDrawWidgetProxy::ZOOM_STEP, static_cast(angleDeltaY) / static_cast(QWheelEvent::DefaultDeltasPerStep)); + const PDFReal newZoom = zoom * zoomStep; + proxy->zoom(newZoom); + } + else + { + // Move Up/Down. Angle is negative, if wheel is scrolled down. First we try to scroll by pixel delta. + // Otherwise we compute scroll using angle. + QPoint scrollByPixels = event->pixelDelta(); + if (scrollByPixels.isNull()) + { + const QPoint angleDelta = event->angleDelta(); + const bool shiftModifier = keyboardModifiers.testFlag(Qt::ShiftModifier); + int stepVertical = 0; + int stepHorizontal = shiftModifier ? m_widget->getHorizontalScrollbar()->pageStep() : m_widget->getHorizontalScrollbar()->singleStep(); + + if (proxy->isBlockMode()) + { + // In block mode, we must calculate pixel offsets differently - scrollbars corresponds to indices of blocks, + // not to the pixels. + QRect boundingBox = proxy->getPagesIntersectingRectBoundingBox(this->rect()); + + if (boundingBox.isEmpty()) + { + // This occurs, when we have not opened a document + boundingBox = this->rect(); + } + + stepVertical = shiftModifier ? boundingBox.height() : boundingBox.height() / 10; + } + else + { + stepVertical = shiftModifier ? m_widget->getVerticalScrollbar()->pageStep() : m_widget->getVerticalScrollbar()->singleStep(); + } + + const int scrollVertical = stepVertical * static_cast(angleDelta.y()) / static_cast(QWheelEvent::DefaultDeltasPerStep); + const int scrollHorizontal = stepHorizontal * static_cast(angleDelta.x()) / static_cast(QWheelEvent::DefaultDeltasPerStep); + + scrollByPixels = QPoint(scrollHorizontal, scrollVertical); + } + + QPoint offset = proxy->scrollByPixels(scrollByPixels); + + if (offset.y() == 0 && scrollByPixels.y() != 0 && proxy->isBlockMode()) + { + // We must move to another block (we are in block mode) + bool up = scrollByPixels.y() > 0; + + m_widget->getVerticalScrollbar()->setValue(m_widget->getVerticalScrollbar()->value() + (up ? -1 : 1)); + proxy->scrollByPixels(QPoint(0, up ? std::numeric_limits::min() : std::numeric_limits::max())); + } + } + + event->accept(); +} + +PDFOpenGLDrawWidget::PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent) : + BaseClass(widget, parent) +{ + QSurfaceFormat format = this->format(); + format.setProfile(QSurfaceFormat::CoreProfile); + format.setSamples(samplesCount); + format.setColorSpace(QSurfaceFormat::sRGBColorSpace); + format.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + setFormat(format); +} + +PDFOpenGLDrawWidget::~PDFOpenGLDrawWidget() +{ + +} + +void PDFOpenGLDrawWidget::resizeGL(int w, int h) +{ + QOpenGLWidget::resizeGL(w, h); + + getPDFWidget()->getDrawWidgetProxy()->update(); +} + +void PDFOpenGLDrawWidget::initializeGL() +{ + QOpenGLWidget::initializeGL(); +} + +void PDFOpenGLDrawWidget::paintGL() +{ + QPainter painter(this); + getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect()); +} + +PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) : + BaseClass(widget, parent) +{ + +} + +PDFDrawWidget::~PDFDrawWidget() +{ + +} + +void PDFDrawWidget::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + QPainter painter(this); + getPDFWidget()->getDrawWidgetProxy()->draw(&painter, this->rect()); +} + +void PDFDrawWidget::resizeEvent(QResizeEvent* event) +{ + BaseClass::resizeEvent(event); + + getPDFWidget()->getDrawWidgetProxy()->update(); +} + +template class PDFDrawWidgetBase; +template class PDFDrawWidgetBase; + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfdrawwidget.h b/Pdf4QtLib/sources/pdfdrawwidget.h index ccb94a9..3c8e6f2 100644 --- a/Pdf4QtLib/sources/pdfdrawwidget.h +++ b/Pdf4QtLib/sources/pdfdrawwidget.h @@ -1,211 +1,211 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFDRAWWIDGET_H -#define PDFDRAWWIDGET_H - -#include "pdfglobal.h" -#include "pdfrenderer.h" - -#include -#include -#include - -namespace pdf -{ -class PDFDocument; -class PDFCMSManager; -class PDFToolManager; -class PDFDrawWidget; -class PDFFormManager; -class PDFDrawWidgetProxy; -class PDFModifiedDocument; -class PDFWidgetAnnotationManager; -class IDrawWidgetInputInterface; - -class IDrawWidget -{ -public: - virtual ~IDrawWidget() = default; - - virtual QWidget* getWidget() = 0; - - /// Returns page indices, which are currently displayed in the widget - virtual std::vector getCurrentPages() const = 0; -}; - -class PDF4QTLIBSHARED_EXPORT PDFWidget : public QWidget -{ - Q_OBJECT - -public: - /// Constructs new PDFWidget. - /// \param cmsManager Color management system manager - /// \param engine Rendering engine type - /// \param samplesCount Samples count for rendering engine MSAA antialiasing - explicit PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent); - virtual ~PDFWidget() override; - - virtual bool focusNextPrevChild(bool next) override; - - using PageRenderingErrors = std::map>; - - /// Sets the document to be viewed in this widget. Document can be nullptr, - /// in that case, widget contents are cleared. Optional content activity can be nullptr, - /// if this occurs, no content is suppressed. - /// \param document Document - void setDocument(const PDFModifiedDocument& document); - - /// Update rendering engine according the settings - /// \param engine Engine type - /// \param samplesCount Samples count for rendering engine MSAA antialiasing - void updateRenderer(RendererEngine engine, int samplesCount); - - /// Updates cache limits - /// \param compiledPageCacheLimit Compiled page cache limit [bytes] - /// \param thumbnailsCacheLimit Thumbnail image cache limit [kB] - /// \param fontCacheLimit Font cache limit [-] - /// \param instancedFontCacheLimit Instanced font cache limit [-] - void updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit); - - const PDFCMSManager* getCMSManager() const { return m_cmsManager; } - PDFToolManager* getToolManager() const { return m_toolManager; } - PDFWidgetAnnotationManager* getAnnotationManager() const { return m_annotationManager; } - IDrawWidget* getDrawWidget() const { return m_drawWidget; } - QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; } - QScrollBar* getVerticalScrollbar() const { return m_verticalScrollBar; } - PDFDrawWidgetProxy* getDrawWidgetProxy() const { return m_proxy; } - const PageRenderingErrors* getPageRenderingErrors() const { return &m_pageRenderingErrors; } - int getPageRenderingErrorCount() const; - const std::vector& getInputInterfaces() const { return m_inputInterfaces; } - - void setToolManager(PDFToolManager* toolManager); - void setAnnotationManager(PDFWidgetAnnotationManager* annotationManager); - - PDFFormManager* getFormManager() const; - void setFormManager(PDFFormManager* formManager); - - void removeInputInterface(IDrawWidgetInputInterface* inputInterface); - void addInputInterface(IDrawWidgetInputInterface* inputInterface); - -signals: - void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount); - -private: - void updateRendererImpl(); - void onRenderingError(PDFInteger pageIndex, const QList& errors); - void onPageImageChanged(bool all, const std::vector& pages); - - IDrawWidget* createDrawWidget(RendererEngine rendererEngine, int samplesCount); - - const PDFCMSManager* m_cmsManager; - PDFToolManager* m_toolManager; - PDFWidgetAnnotationManager* m_annotationManager; - PDFFormManager* m_formManager; - IDrawWidget* m_drawWidget; - QScrollBar* m_horizontalScrollBar; - QScrollBar* m_verticalScrollBar; - PDFDrawWidgetProxy* m_proxy; - PageRenderingErrors m_pageRenderingErrors; - std::vector m_inputInterfaces; -}; - -template -class PDFDrawWidgetBase : public BaseWidget, public IDrawWidget -{ -public: - explicit PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent); - virtual ~PDFDrawWidgetBase() override = default; - - /// Returns page indices, which are currently displayed in the widget - virtual std::vector getCurrentPages() const override; - - virtual QSize minimumSizeHint() const override; - virtual QWidget* getWidget() override { return this; } - -protected: - virtual bool event(QEvent* event) override; - virtual void keyPressEvent(QKeyEvent* event) override; - virtual void keyReleaseEvent(QKeyEvent* event) override; - virtual void mousePressEvent(QMouseEvent* event) override; - virtual void mouseDoubleClickEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent* event) override; - virtual void mouseMoveEvent(QMouseEvent* event) override; - virtual void wheelEvent(QWheelEvent* event) override; - - PDFWidget* getPDFWidget() const { return m_widget; } - -private: - void updateCursor(); - - template - bool processEvent(Event* event); - - enum class MouseOperation - { - None, - Translate - }; - - /// Performs the mouse operation (under the current mouse position) - /// \param currentMousePosition Current position of the mouse - void performMouseOperation(QPoint currentMousePosition); - - PDFWidget* m_widget; - QPoint m_lastMousePosition; - MouseOperation m_mouseOperation; -}; - -class PDFOpenGLDrawWidget : public PDFDrawWidgetBase -{ - Q_OBJECT - -private: - using BaseClass = PDFDrawWidgetBase; - -public: - explicit PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent); - virtual ~PDFOpenGLDrawWidget() override; - -protected: - virtual void resizeGL(int w, int h) override; - virtual void initializeGL() override; - virtual void paintGL() override; -}; - -class PDFDrawWidget : public PDFDrawWidgetBase -{ - Q_OBJECT - -private: - using BaseClass = PDFDrawWidgetBase; - -public: - explicit PDFDrawWidget(PDFWidget* widget, QWidget* parent); - virtual ~PDFDrawWidget() override; - -protected: - virtual void paintEvent(QPaintEvent* event) override; - virtual void resizeEvent(QResizeEvent* event) override; -}; - -extern template class PDFDrawWidgetBase; -extern template class PDFDrawWidgetBase; - -} // namespace pdf - -#endif // PDFDRAWWIDGET_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFDRAWWIDGET_H +#define PDFDRAWWIDGET_H + +#include "pdfglobal.h" +#include "pdfrenderer.h" + +#include +#include +#include + +namespace pdf +{ +class PDFDocument; +class PDFCMSManager; +class PDFToolManager; +class PDFDrawWidget; +class PDFFormManager; +class PDFDrawWidgetProxy; +class PDFModifiedDocument; +class PDFWidgetAnnotationManager; +class IDrawWidgetInputInterface; + +class IDrawWidget +{ +public: + virtual ~IDrawWidget() = default; + + virtual QWidget* getWidget() = 0; + + /// Returns page indices, which are currently displayed in the widget + virtual std::vector getCurrentPages() const = 0; +}; + +class PDF4QTLIBSHARED_EXPORT PDFWidget : public QWidget +{ + Q_OBJECT + +public: + /// Constructs new PDFWidget. + /// \param cmsManager Color management system manager + /// \param engine Rendering engine type + /// \param samplesCount Samples count for rendering engine MSAA antialiasing + explicit PDFWidget(const PDFCMSManager* cmsManager, RendererEngine engine, int samplesCount, QWidget* parent); + virtual ~PDFWidget() override; + + virtual bool focusNextPrevChild(bool next) override; + + using PageRenderingErrors = std::map>; + + /// Sets the document to be viewed in this widget. Document can be nullptr, + /// in that case, widget contents are cleared. Optional content activity can be nullptr, + /// if this occurs, no content is suppressed. + /// \param document Document + void setDocument(const PDFModifiedDocument& document); + + /// Update rendering engine according the settings + /// \param engine Engine type + /// \param samplesCount Samples count for rendering engine MSAA antialiasing + void updateRenderer(RendererEngine engine, int samplesCount); + + /// Updates cache limits + /// \param compiledPageCacheLimit Compiled page cache limit [bytes] + /// \param thumbnailsCacheLimit Thumbnail image cache limit [kB] + /// \param fontCacheLimit Font cache limit [-] + /// \param instancedFontCacheLimit Instanced font cache limit [-] + void updateCacheLimits(int compiledPageCacheLimit, int thumbnailsCacheLimit, int fontCacheLimit, int instancedFontCacheLimit); + + const PDFCMSManager* getCMSManager() const { return m_cmsManager; } + PDFToolManager* getToolManager() const { return m_toolManager; } + PDFWidgetAnnotationManager* getAnnotationManager() const { return m_annotationManager; } + IDrawWidget* getDrawWidget() const { return m_drawWidget; } + QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; } + QScrollBar* getVerticalScrollbar() const { return m_verticalScrollBar; } + PDFDrawWidgetProxy* getDrawWidgetProxy() const { return m_proxy; } + const PageRenderingErrors* getPageRenderingErrors() const { return &m_pageRenderingErrors; } + int getPageRenderingErrorCount() const; + const std::vector& getInputInterfaces() const { return m_inputInterfaces; } + + void setToolManager(PDFToolManager* toolManager); + void setAnnotationManager(PDFWidgetAnnotationManager* annotationManager); + + PDFFormManager* getFormManager() const; + void setFormManager(PDFFormManager* formManager); + + void removeInputInterface(IDrawWidgetInputInterface* inputInterface); + void addInputInterface(IDrawWidgetInputInterface* inputInterface); + +signals: + void pageRenderingErrorsChanged(PDFInteger pageIndex, int errorsCount); + +private: + void updateRendererImpl(); + void onRenderingError(PDFInteger pageIndex, const QList& errors); + void onPageImageChanged(bool all, const std::vector& pages); + + IDrawWidget* createDrawWidget(RendererEngine rendererEngine, int samplesCount); + + const PDFCMSManager* m_cmsManager; + PDFToolManager* m_toolManager; + PDFWidgetAnnotationManager* m_annotationManager; + PDFFormManager* m_formManager; + IDrawWidget* m_drawWidget; + QScrollBar* m_horizontalScrollBar; + QScrollBar* m_verticalScrollBar; + PDFDrawWidgetProxy* m_proxy; + PageRenderingErrors m_pageRenderingErrors; + std::vector m_inputInterfaces; +}; + +template +class PDFDrawWidgetBase : public BaseWidget, public IDrawWidget +{ +public: + explicit PDFDrawWidgetBase(PDFWidget* widget, QWidget* parent); + virtual ~PDFDrawWidgetBase() override = default; + + /// Returns page indices, which are currently displayed in the widget + virtual std::vector getCurrentPages() const override; + + virtual QSize minimumSizeHint() const override; + virtual QWidget* getWidget() override { return this; } + +protected: + virtual bool event(QEvent* event) override; + virtual void keyPressEvent(QKeyEvent* event) override; + virtual void keyReleaseEvent(QKeyEvent* event) override; + virtual void mousePressEvent(QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseMoveEvent(QMouseEvent* event) override; + virtual void wheelEvent(QWheelEvent* event) override; + + PDFWidget* getPDFWidget() const { return m_widget; } + +private: + void updateCursor(); + + template + bool processEvent(Event* event); + + enum class MouseOperation + { + None, + Translate + }; + + /// Performs the mouse operation (under the current mouse position) + /// \param currentMousePosition Current position of the mouse + void performMouseOperation(QPoint currentMousePosition); + + PDFWidget* m_widget; + QPoint m_lastMousePosition; + MouseOperation m_mouseOperation; +}; + +class PDFOpenGLDrawWidget : public PDFDrawWidgetBase +{ + Q_OBJECT + +private: + using BaseClass = PDFDrawWidgetBase; + +public: + explicit PDFOpenGLDrawWidget(PDFWidget* widget, int samplesCount, QWidget* parent); + virtual ~PDFOpenGLDrawWidget() override; + +protected: + virtual void resizeGL(int w, int h) override; + virtual void initializeGL() override; + virtual void paintGL() override; +}; + +class PDFDrawWidget : public PDFDrawWidgetBase +{ + Q_OBJECT + +private: + using BaseClass = PDFDrawWidgetBase; + +public: + explicit PDFDrawWidget(PDFWidget* widget, QWidget* parent); + virtual ~PDFDrawWidget() override; + +protected: + virtual void paintEvent(QPaintEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; +}; + +extern template class PDFDrawWidgetBase; +extern template class PDFDrawWidgetBase; + +} // namespace pdf + +#endif // PDFDRAWWIDGET_H diff --git a/Pdf4QtLib/sources/pdfencoding.cpp b/Pdf4QtLib/sources/pdfencoding.cpp index 35f5460..a255eb1 100644 --- a/Pdf4QtLib/sources/pdfencoding.cpp +++ b/Pdf4QtLib/sources/pdfencoding.cpp @@ -1,2508 +1,2508 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfencoding.h" - -#include -#include - -#include - -namespace pdf -{ - -namespace encoding -{ - -// PDF Reference 1.7, Appendix D, Section D.1, StandardEncoding -static const EncodingTable STANDARD_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation - QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation - QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation - QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol - QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation - QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation - QChar(0x2019), // Hex No. 27 (Dec 039) Character '’' Punctuation - QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation - QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation - QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation - QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation - QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit - QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit - QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit - QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit - QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit - QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit - QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit - QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit - QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit - QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol - QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol - QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol - QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation - QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation - QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase - QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase - QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase - QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase - QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase - QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase - QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase - QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase - QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase - QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase - QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase - QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase - QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase - QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase - QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase - QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase - QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase - QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase - QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase - QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase - QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase - QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase - QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase - QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase - QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase - QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase - QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation - QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation - QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation - QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol - QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation - QChar(0x2018), // Hex No. 60 (Dec 096) Character '‘' Punctuation - QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase - QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase - QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase - QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase - QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase - QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase - QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase - QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase - QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase - QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase - QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase - QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase - QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase - QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase - QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase - QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase - QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase - QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase - QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase - QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase - QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase - QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase - QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase - QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase - QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase - QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase - QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation - QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol - QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation - QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 81 (Dec 129) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 82 (Dec 130) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 87 (Dec 135) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 88 (Dec 136) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 89 (Dec 137) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8A (Dec 138) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8B (Dec 139) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8C (Dec 140) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8D (Dec 141) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8E (Dec 142) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8F (Dec 143) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 90 (Dec 144) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 91 (Dec 145) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 92 (Dec 146) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 93 (Dec 147) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 94 (Dec 148) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 95 (Dec 149) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 96 (Dec 150) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 97 (Dec 151) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 98 (Dec 152) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 99 (Dec 153) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9A (Dec 154) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9B (Dec 155) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9C (Dec 156) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9D (Dec 157) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9E (Dec 158) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00a1), // Hex No. A1 (Dec 161) Character '¡' Punctuation - QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol - QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol - QChar(0x2044), // Hex No. A4 (Dec 164) Character '⁄' Symbol - QChar(0x00a5), // Hex No. A5 (Dec 165) Character '¥' Symbol - QChar(0x0192), // Hex No. A6 (Dec 166) Character 'ƒ' Letter, Lowercase - QChar(0x00a7), // Hex No. A7 (Dec 167) Character '§' Punctuation - QChar(0x00a4), // Hex No. A8 (Dec 168) Character '¤' Symbol - QChar(0x0027), // Hex No. A9 (Dec 169) Character ''' Punctuation - QChar(0x201c), // Hex No. AA (Dec 170) Character '“' Punctuation - QChar(0x00ab), // Hex No. AB (Dec 171) Character '«' Punctuation - QChar(0x2039), // Hex No. AC (Dec 172) Character '‹' Punctuation - QChar(0x203a), // Hex No. AD (Dec 173) Character '›' Punctuation - QChar(0xfb01), // Hex No. AE (Dec 174) Character 'fi' Letter, Lowercase - QChar(0xfb02), // Hex No. AF (Dec 175) Character 'fl' Letter, Lowercase - QChar(0xfffd), // Hex No. B0 (Dec 176) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x2013), // Hex No. B1 (Dec 177) Character '–' Punctuation - QChar(0x2020), // Hex No. B2 (Dec 178) Character '†' Punctuation - QChar(0x2021), // Hex No. B3 (Dec 179) Character '‡' Punctuation - QChar(0x00b7), // Hex No. B4 (Dec 180) Character '·' Punctuation - QChar(0xfffd), // Hex No. B5 (Dec 181) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00b6), // Hex No. B6 (Dec 182) Character '¶' Punctuation - QChar(0x2022), // Hex No. B7 (Dec 183) Character '•' Punctuation - QChar(0x201a), // Hex No. B8 (Dec 184) Character '‚' Punctuation - QChar(0x201e), // Hex No. B9 (Dec 185) Character '„' Punctuation - QChar(0x201d), // Hex No. BA (Dec 186) Character '”' Punctuation - QChar(0x00bb), // Hex No. BB (Dec 187) Character '»' Punctuation - QChar(0x2026), // Hex No. BC (Dec 188) Character '…' Punctuation - QChar(0x2030), // Hex No. BD (Dec 189) Character '‰' Punctuation - QChar(0xfffd), // Hex No. BE (Dec 190) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00bf), // Hex No. BF (Dec 191) Character '¿' Punctuation - QChar(0xfffd), // Hex No. C0 (Dec 192) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0060), // Hex No. C1 (Dec 193) Character '`' Symbol - QChar(0x00b4), // Hex No. C2 (Dec 194) Character '´' Symbol - QChar(0x02c6), // Hex No. C3 (Dec 195) Character 'ˆ' Letter - QChar(0x02dc), // Hex No. C4 (Dec 196) Character '˜' Symbol - QChar(0x00af), // Hex No. C5 (Dec 197) Character '¯' Symbol - QChar(0x02d8), // Hex No. C6 (Dec 198) Character '˘' Symbol - QChar(0x02d9), // Hex No. C7 (Dec 199) Character '˙' Symbol - QChar(0x00a8), // Hex No. C8 (Dec 200) Character '¨' Symbol - QChar(0xfffd), // Hex No. C9 (Dec 201) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x02da), // Hex No. CA (Dec 202) Character '˚' Symbol - QChar(0x00b8), // Hex No. CB (Dec 203) Character '¸' Symbol - QChar(0xfffd), // Hex No. CC (Dec 204) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x02dd), // Hex No. CD (Dec 205) Character '˝' Symbol - QChar(0x02db), // Hex No. CE (Dec 206) Character '˛' Symbol - QChar(0x02c7), // Hex No. CF (Dec 207) Character 'ˇ' Letter - QChar(0x2014), // Hex No. D0 (Dec 208) Character '—' Punctuation - QChar(0xfffd), // Hex No. D1 (Dec 209) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D2 (Dec 210) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D3 (Dec 211) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D4 (Dec 212) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D5 (Dec 213) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D6 (Dec 214) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D7 (Dec 215) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D8 (Dec 216) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D9 (Dec 217) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. DA (Dec 218) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. DB (Dec 219) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. DC (Dec 220) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. DD (Dec 221) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. DE (Dec 222) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. DF (Dec 223) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. E0 (Dec 224) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00c6), // Hex No. E1 (Dec 225) Character 'Æ' Letter, Uppercase - QChar(0xfffd), // Hex No. E2 (Dec 226) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00aa), // Hex No. E3 (Dec 227) Character 'ª' Letter - QChar(0xfffd), // Hex No. E4 (Dec 228) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. E5 (Dec 229) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. E6 (Dec 230) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. E7 (Dec 231) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0141), // Hex No. E8 (Dec 232) Character 'Ł' Letter, Uppercase - QChar(0x00d8), // Hex No. E9 (Dec 233) Character 'Ø' Letter, Uppercase - QChar(0x0152), // Hex No. EA (Dec 234) Character 'Œ' Letter, Uppercase - QChar(0x00ba), // Hex No. EB (Dec 235) Character 'º' Letter - QChar(0xfffd), // Hex No. EC (Dec 236) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. ED (Dec 237) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. EE (Dec 238) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. EF (Dec 239) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00e6), // Hex No. F1 (Dec 241) Character 'æ' Letter, Lowercase - QChar(0xfffd), // Hex No. F2 (Dec 242) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. F3 (Dec 243) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. F4 (Dec 244) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0131), // Hex No. F5 (Dec 245) Character 'ı' Letter, Lowercase - QChar(0xfffd), // Hex No. F6 (Dec 246) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. F7 (Dec 247) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0142), // Hex No. F8 (Dec 248) Character 'ł' Letter, Lowercase - QChar(0x00f8), // Hex No. F9 (Dec 249) Character 'ø' Letter, Lowercase - QChar(0x0153), // Hex No. FA (Dec 250) Character 'œ' Letter, Lowercase - QChar(0x00df), // Hex No. FB (Dec 251) Character 'ß' Letter, Lowercase - QChar(0xfffd), // Hex No. FC (Dec 252) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. FD (Dec 253) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. FE (Dec 254) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set -}; - -// PDF Reference 1.7, Appendix D, Section D.1, MacRomanEncoding -static const EncodingTable MAC_ROMAN_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation - QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation - QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation - QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol - QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation - QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation - QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation - QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation - QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation - QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation - QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation - QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit - QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit - QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit - QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit - QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit - QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit - QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit - QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit - QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit - QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol - QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol - QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol - QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation - QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation - QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase - QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase - QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase - QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase - QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase - QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase - QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase - QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase - QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase - QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase - QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase - QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase - QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase - QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase - QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase - QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase - QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase - QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase - QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase - QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase - QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase - QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase - QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase - QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase - QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase - QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase - QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation - QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation - QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation - QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol - QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation - QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol - QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase - QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase - QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase - QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase - QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase - QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase - QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase - QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase - QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase - QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase - QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase - QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase - QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase - QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase - QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase - QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase - QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase - QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase - QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase - QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase - QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase - QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase - QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase - QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase - QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase - QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase - QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation - QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol - QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation - QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00c4), // Hex No. 80 (Dec 128) Character 'Ä' Letter, Uppercase - QChar(0x00c5), // Hex No. 81 (Dec 129) Character 'Å' Letter, Uppercase - QChar(0x00c7), // Hex No. 82 (Dec 130) Character 'Ç' Letter, Uppercase - QChar(0x00c9), // Hex No. 83 (Dec 131) Character 'É' Letter, Uppercase - QChar(0x00d1), // Hex No. 84 (Dec 132) Character 'Ñ' Letter, Uppercase - QChar(0x00d6), // Hex No. 85 (Dec 133) Character 'Ö' Letter, Uppercase - QChar(0x00dc), // Hex No. 86 (Dec 134) Character 'Ü' Letter, Uppercase - QChar(0x00e1), // Hex No. 87 (Dec 135) Character 'á' Letter, Lowercase - QChar(0x00e0), // Hex No. 88 (Dec 136) Character 'à' Letter, Lowercase - QChar(0x00e2), // Hex No. 89 (Dec 137) Character 'â' Letter, Lowercase - QChar(0x00e4), // Hex No. 8A (Dec 138) Character 'ä' Letter, Lowercase - QChar(0x00e3), // Hex No. 8B (Dec 139) Character 'ã' Letter, Lowercase - QChar(0x00e5), // Hex No. 8C (Dec 140) Character 'å' Letter, Lowercase - QChar(0x00e7), // Hex No. 8D (Dec 141) Character 'ç' Letter, Lowercase - QChar(0x00e9), // Hex No. 8E (Dec 142) Character 'é' Letter, Lowercase - QChar(0x00e8), // Hex No. 8F (Dec 143) Character 'è' Letter, Lowercase - QChar(0x00ea), // Hex No. 90 (Dec 144) Character 'ê' Letter, Lowercase - QChar(0x00eb), // Hex No. 91 (Dec 145) Character 'ë' Letter, Lowercase - QChar(0x00ed), // Hex No. 92 (Dec 146) Character 'í' Letter, Lowercase - QChar(0x00ec), // Hex No. 93 (Dec 147) Character 'ì' Letter, Lowercase - QChar(0x00ee), // Hex No. 94 (Dec 148) Character 'î' Letter, Lowercase - QChar(0x00ef), // Hex No. 95 (Dec 149) Character 'ï' Letter, Lowercase - QChar(0x00f1), // Hex No. 96 (Dec 150) Character 'ñ' Letter, Lowercase - QChar(0x00f3), // Hex No. 97 (Dec 151) Character 'ó' Letter, Lowercase - QChar(0x00f2), // Hex No. 98 (Dec 152) Character 'ò' Letter, Lowercase - QChar(0x00f4), // Hex No. 99 (Dec 153) Character 'ô' Letter, Lowercase - QChar(0x00f6), // Hex No. 9A (Dec 154) Character 'ö' Letter, Lowercase - QChar(0x00f5), // Hex No. 9B (Dec 155) Character 'õ' Letter, Lowercase - QChar(0x00fa), // Hex No. 9C (Dec 156) Character 'ú' Letter, Lowercase - QChar(0x00f9), // Hex No. 9D (Dec 157) Character 'ù' Letter, Lowercase - QChar(0x00fb), // Hex No. 9E (Dec 158) Character 'û' Letter, Lowercase - QChar(0x00fc), // Hex No. 9F (Dec 159) Character 'ü' Letter, Lowercase - QChar(0x2020), // Hex No. A0 (Dec 160) Character '†' Punctuation - QChar(0x00b0), // Hex No. A1 (Dec 161) Character '°' Symbol - QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol - QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol - QChar(0x00a7), // Hex No. A4 (Dec 164) Character '§' Punctuation - QChar(0x2022), // Hex No. A5 (Dec 165) Character '•' Punctuation - QChar(0x00b6), // Hex No. A6 (Dec 166) Character '¶' Punctuation - QChar(0x00df), // Hex No. A7 (Dec 167) Character 'ß' Letter, Lowercase - QChar(0x00ae), // Hex No. A8 (Dec 168) Character '®' Symbol - QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol - QChar(0x2122), // Hex No. AA (Dec 170) Character '™' Symbol - QChar(0x00b4), // Hex No. AB (Dec 171) Character '´' Symbol - QChar(0x00a8), // Hex No. AC (Dec 172) Character '¨' Symbol - QChar(0x2260), // Hex No. AD (Dec 173) Character '≠' Symbol - QChar(0x00c6), // Hex No. AE (Dec 174) Character 'Æ' Letter, Uppercase - QChar(0x00d8), // Hex No. AF (Dec 175) Character 'Ø' Letter, Uppercase - QChar(0x221e), // Hex No. B0 (Dec 176) Character '∞' Symbol - QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol - QChar(0x2264), // Hex No. B2 (Dec 178) Character '≤' Symbol - QChar(0x2265), // Hex No. B3 (Dec 179) Character '≥' Symbol - QChar(0x00a5), // Hex No. B4 (Dec 180) Character '¥' Symbol - QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase - QChar(0x2202), // Hex No. B6 (Dec 182) Character '∂' Symbol - QChar(0x2211), // Hex No. B7 (Dec 183) Character '∑' Symbol - QChar(0x220f), // Hex No. B8 (Dec 184) Character '∏' Symbol - QChar(0x03c0), // Hex No. B9 (Dec 185) Character 'π' Letter, Lowercase - QChar(0x222b), // Hex No. BA (Dec 186) Character '∫' Symbol - QChar(0x00aa), // Hex No. BB (Dec 187) Character 'ª' Letter - QChar(0x00ba), // Hex No. BC (Dec 188) Character 'º' Letter - QChar(0x2126), // Hex No. BD (Dec 189) Character 'Ω' Letter, Uppercase - QChar(0x00e6), // Hex No. BE (Dec 190) Character 'æ' Letter, Lowercase - QChar(0x00f8), // Hex No. BF (Dec 191) Character 'ø' Letter, Lowercase - QChar(0x00bf), // Hex No. C0 (Dec 192) Character '¿' Punctuation - QChar(0x00a1), // Hex No. C1 (Dec 193) Character '¡' Punctuation - QChar(0x00ac), // Hex No. C2 (Dec 194) Character '¬' Symbol - QChar(0x221a), // Hex No. C3 (Dec 195) Character '√' Symbol - QChar(0x0192), // Hex No. C4 (Dec 196) Character 'ƒ' Letter, Lowercase - QChar(0x2248), // Hex No. C5 (Dec 197) Character '≈' Symbol - QChar(0x2206), // Hex No. C6 (Dec 198) Character '∆' Symbol - QChar(0x00ab), // Hex No. C7 (Dec 199) Character '«' Punctuation - QChar(0x00bb), // Hex No. C8 (Dec 200) Character '»' Punctuation - QChar(0x2026), // Hex No. C9 (Dec 201) Character '…' Punctuation - QChar(0x0020), // Hex No. CA (Dec 202) Character ' ' Whitespace - QChar(0x00c0), // Hex No. CB (Dec 203) Character 'À' Letter, Uppercase - QChar(0x00c3), // Hex No. CC (Dec 204) Character 'Ã' Letter, Uppercase - QChar(0x00d5), // Hex No. CD (Dec 205) Character 'Õ' Letter, Uppercase - QChar(0x0152), // Hex No. CE (Dec 206) Character 'Œ' Letter, Uppercase - QChar(0x0153), // Hex No. CF (Dec 207) Character 'œ' Letter, Lowercase - QChar(0x2013), // Hex No. D0 (Dec 208) Character '–' Punctuation - QChar(0x2014), // Hex No. D1 (Dec 209) Character '—' Punctuation - QChar(0x201c), // Hex No. D2 (Dec 210) Character '“' Punctuation - QChar(0x201d), // Hex No. D3 (Dec 211) Character '”' Punctuation - QChar(0x2018), // Hex No. D4 (Dec 212) Character '‘' Punctuation - QChar(0x2019), // Hex No. D5 (Dec 213) Character '’' Punctuation - QChar(0x00f7), // Hex No. D6 (Dec 214) Character '÷' Symbol - QChar(0x25ca), // Hex No. D7 (Dec 215) Character '◊' Symbol - QChar(0x00ff), // Hex No. D8 (Dec 216) Character 'ÿ' Letter, Lowercase - QChar(0x0178), // Hex No. D9 (Dec 217) Character 'Ÿ' Letter, Uppercase - QChar(0x2044), // Hex No. DA (Dec 218) Character '⁄' Symbol - QChar(0x00a4), // Hex No. DB (Dec 219) Character '¤' Symbol - QChar(0x2039), // Hex No. DC (Dec 220) Character '‹' Punctuation - QChar(0x203a), // Hex No. DD (Dec 221) Character '›' Punctuation - QChar(0xfb01), // Hex No. DE (Dec 222) Character 'fi' Letter, Lowercase - QChar(0xfb02), // Hex No. DF (Dec 223) Character 'fl' Letter, Lowercase - QChar(0x2021), // Hex No. E0 (Dec 224) Character '‡' Punctuation - QChar(0x00b7), // Hex No. E1 (Dec 225) Character '·' Punctuation - QChar(0x201a), // Hex No. E2 (Dec 226) Character '‚' Punctuation - QChar(0x201e), // Hex No. E3 (Dec 227) Character '„' Punctuation - QChar(0x2030), // Hex No. E4 (Dec 228) Character '‰' Punctuation - QChar(0x00c2), // Hex No. E5 (Dec 229) Character 'Â' Letter, Uppercase - QChar(0x00ca), // Hex No. E6 (Dec 230) Character 'Ê' Letter, Uppercase - QChar(0x00c1), // Hex No. E7 (Dec 231) Character 'Á' Letter, Uppercase - QChar(0x00cb), // Hex No. E8 (Dec 232) Character 'Ë' Letter, Uppercase - QChar(0x00c8), // Hex No. E9 (Dec 233) Character 'È' Letter, Uppercase - QChar(0x00cd), // Hex No. EA (Dec 234) Character 'Í' Letter, Uppercase - QChar(0x00ce), // Hex No. EB (Dec 235) Character 'Î' Letter, Uppercase - QChar(0x00cf), // Hex No. EC (Dec 236) Character 'Ï' Letter, Uppercase - QChar(0x00cc), // Hex No. ED (Dec 237) Character 'Ì' Letter, Uppercase - QChar(0x00d3), // Hex No. EE (Dec 238) Character 'Ó' Letter, Uppercase - QChar(0x00d4), // Hex No. EF (Dec 239) Character 'Ô' Letter, Uppercase - QChar(0xf8ff), // Hex No. F0 (Dec 240) - QChar(0x00d2), // Hex No. F1 (Dec 241) Character 'Ò' Letter, Uppercase - QChar(0x00da), // Hex No. F2 (Dec 242) Character 'Ú' Letter, Uppercase - QChar(0x00db), // Hex No. F3 (Dec 243) Character 'Û' Letter, Uppercase - QChar(0x00d9), // Hex No. F4 (Dec 244) Character 'Ù' Letter, Uppercase - QChar(0x0131), // Hex No. F5 (Dec 245) Character 'ı' Letter, Lowercase - QChar(0x02c6), // Hex No. F6 (Dec 246) Character 'ˆ' Letter - QChar(0x02dc), // Hex No. F7 (Dec 247) Character '˜' Symbol - QChar(0x00af), // Hex No. F8 (Dec 248) Character '¯' Symbol - QChar(0x02d8), // Hex No. F9 (Dec 249) Character '˘' Symbol - QChar(0x02d9), // Hex No. FA (Dec 250) Character '˙' Symbol - QChar(0x02da), // Hex No. FB (Dec 251) Character '˚' Symbol - QChar(0x00b8), // Hex No. FC (Dec 252) Character '¸' Symbol - QChar(0x02dd), // Hex No. FD (Dec 253) Character '˝' Symbol - QChar(0x02db), // Hex No. FE (Dec 254) Character '˛' Symbol - QChar(0x02c7), // Hex No. FF (Dec 255) Character 'ˇ' Letter -}; - -// PDF Reference 1.7, Appendix D, Section D.1, WinAnsiEncoding -static const EncodingTable WIN_ANSI_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation - QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation - QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation - QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol - QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation - QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation - QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation - QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation - QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation - QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation - QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation - QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit - QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit - QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit - QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit - QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit - QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit - QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit - QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit - QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit - QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol - QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol - QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol - QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation - QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation - QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase - QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase - QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase - QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase - QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase - QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase - QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase - QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase - QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase - QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase - QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase - QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase - QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase - QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase - QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase - QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase - QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase - QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase - QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase - QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase - QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase - QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase - QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase - QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase - QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase - QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase - QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation - QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation - QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation - QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol - QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation - QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol - QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase - QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase - QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase - QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase - QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase - QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase - QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase - QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase - QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase - QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase - QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase - QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase - QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase - QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase - QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase - QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase - QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase - QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase - QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase - QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase - QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase - QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase - QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase - QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase - QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase - QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase - QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation - QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol - QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation - QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol - QChar(0x2022), // Hex No. 7F (Dec 127) Character '•' Punctuation - QChar(0x20ac), // Hex No. 80 (Dec 128) Character '€' Symbol - QChar(0x2022), // Hex No. 81 (Dec 129) Character '•' Punctuation - QChar(0x201a), // Hex No. 82 (Dec 130) Character '‚' Punctuation - QChar(0x0192), // Hex No. 83 (Dec 131) Character 'ƒ' Letter, Lowercase - QChar(0x201e), // Hex No. 84 (Dec 132) Character '„' Punctuation - QChar(0x2026), // Hex No. 85 (Dec 133) Character '…' Punctuation - QChar(0x2020), // Hex No. 86 (Dec 134) Character '†' Punctuation - QChar(0x2021), // Hex No. 87 (Dec 135) Character '‡' Punctuation - QChar(0x02c6), // Hex No. 88 (Dec 136) Character 'ˆ' Letter - QChar(0x2030), // Hex No. 89 (Dec 137) Character '‰' Punctuation - QChar(0x0160), // Hex No. 8A (Dec 138) Character 'Š' Letter, Uppercase - QChar(0x2039), // Hex No. 8B (Dec 139) Character '‹' Punctuation - QChar(0x0152), // Hex No. 8C (Dec 140) Character 'Œ' Letter, Uppercase - QChar(0x2022), // Hex No. 8D (Dec 141) Character '•' Punctuation - QChar(0x017d), // Hex No. 8E (Dec 142) Character 'Ž' Letter, Uppercase - QChar(0x2022), // Hex No. 8F (Dec 143) Character '•' Punctuation - QChar(0x2022), // Hex No. 90 (Dec 144) Character '•' Punctuation - QChar(0x2018), // Hex No. 91 (Dec 145) Character '‘' Punctuation - QChar(0x2019), // Hex No. 92 (Dec 146) Character '’' Punctuation - QChar(0x201c), // Hex No. 93 (Dec 147) Character '“' Punctuation - QChar(0x201d), // Hex No. 94 (Dec 148) Character '”' Punctuation - QChar(0x2022), // Hex No. 95 (Dec 149) Character '•' Punctuation - QChar(0x2013), // Hex No. 96 (Dec 150) Character '–' Punctuation - QChar(0x2014), // Hex No. 97 (Dec 151) Character '—' Punctuation - QChar(0x02dc), // Hex No. 98 (Dec 152) Character '˜' Symbol - QChar(0x2122), // Hex No. 99 (Dec 153) Character '™' Symbol - QChar(0x0161), // Hex No. 9A (Dec 154) Character 'š' Letter, Lowercase - QChar(0x203a), // Hex No. 9B (Dec 155) Character '›' Punctuation - QChar(0x0153), // Hex No. 9C (Dec 156) Character 'œ' Letter, Lowercase - QChar(0x2022), // Hex No. 9D (Dec 157) Character '•' Punctuation - QChar(0x017e), // Hex No. 9E (Dec 158) Character 'ž' Letter, Lowercase - QChar(0x0178), // Hex No. 9F (Dec 159) Character 'Ÿ' Letter, Uppercase - QChar(0x0020), // Hex No. A0 (Dec 160) Character ' ' Whitespace - QChar(0x00a1), // Hex No. A1 (Dec 161) Character '¡' Punctuation - QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol - QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol - QChar(0x00a4), // Hex No. A4 (Dec 164) Character '¤' Symbol - QChar(0x00a5), // Hex No. A5 (Dec 165) Character '¥' Symbol - QChar(0x00a6), // Hex No. A6 (Dec 166) Character '¦' Symbol - QChar(0x00a7), // Hex No. A7 (Dec 167) Character '§' Punctuation - QChar(0x00a8), // Hex No. A8 (Dec 168) Character '¨' Symbol - QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol - QChar(0x00aa), // Hex No. AA (Dec 170) Character 'ª' Letter - QChar(0x00ab), // Hex No. AB (Dec 171) Character '«' Punctuation - QChar(0x00ac), // Hex No. AC (Dec 172) Character '¬' Symbol - QChar(0x002d), // Hex No. AD (Dec 173) Character '-' Punctuation - QChar(0x00ae), // Hex No. AE (Dec 174) Character '®' Symbol - QChar(0x00af), // Hex No. AF (Dec 175) Character '¯' Symbol - QChar(0x00b0), // Hex No. B0 (Dec 176) Character '°' Symbol - QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol - QChar(0x00b2), // Hex No. B2 (Dec 178) Character '²' - QChar(0x00b3), // Hex No. B3 (Dec 179) Character '³' - QChar(0x00b4), // Hex No. B4 (Dec 180) Character '´' Symbol - QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase - QChar(0x00b6), // Hex No. B6 (Dec 182) Character '¶' Punctuation - QChar(0x00b7), // Hex No. B7 (Dec 183) Character '·' Punctuation - QChar(0x00b8), // Hex No. B8 (Dec 184) Character '¸' Symbol - QChar(0x00b9), // Hex No. B9 (Dec 185) Character '¹' - QChar(0x00ba), // Hex No. BA (Dec 186) Character 'º' Letter - QChar(0x00bb), // Hex No. BB (Dec 187) Character '»' Punctuation - QChar(0x00bc), // Hex No. BC (Dec 188) Character '¼' - QChar(0x00bd), // Hex No. BD (Dec 189) Character '½' - QChar(0x00be), // Hex No. BE (Dec 190) Character '¾' - QChar(0x00bf), // Hex No. BF (Dec 191) Character '¿' Punctuation - QChar(0x00c0), // Hex No. C0 (Dec 192) Character 'À' Letter, Uppercase - QChar(0x00c1), // Hex No. C1 (Dec 193) Character 'Á' Letter, Uppercase - QChar(0x00c2), // Hex No. C2 (Dec 194) Character 'Â' Letter, Uppercase - QChar(0x00c3), // Hex No. C3 (Dec 195) Character 'Ã' Letter, Uppercase - QChar(0x00c4), // Hex No. C4 (Dec 196) Character 'Ä' Letter, Uppercase - QChar(0x00c5), // Hex No. C5 (Dec 197) Character 'Å' Letter, Uppercase - QChar(0x00c6), // Hex No. C6 (Dec 198) Character 'Æ' Letter, Uppercase - QChar(0x00c7), // Hex No. C7 (Dec 199) Character 'Ç' Letter, Uppercase - QChar(0x00c8), // Hex No. C8 (Dec 200) Character 'È' Letter, Uppercase - QChar(0x00c9), // Hex No. C9 (Dec 201) Character 'É' Letter, Uppercase - QChar(0x00ca), // Hex No. CA (Dec 202) Character 'Ê' Letter, Uppercase - QChar(0x00cb), // Hex No. CB (Dec 203) Character 'Ë' Letter, Uppercase - QChar(0x00cc), // Hex No. CC (Dec 204) Character 'Ì' Letter, Uppercase - QChar(0x00cd), // Hex No. CD (Dec 205) Character 'Í' Letter, Uppercase - QChar(0x00ce), // Hex No. CE (Dec 206) Character 'Î' Letter, Uppercase - QChar(0x00cf), // Hex No. CF (Dec 207) Character 'Ï' Letter, Uppercase - QChar(0x00d0), // Hex No. D0 (Dec 208) Character 'Ð' Letter, Uppercase - QChar(0x00d1), // Hex No. D1 (Dec 209) Character 'Ñ' Letter, Uppercase - QChar(0x00d2), // Hex No. D2 (Dec 210) Character 'Ò' Letter, Uppercase - QChar(0x00d3), // Hex No. D3 (Dec 211) Character 'Ó' Letter, Uppercase - QChar(0x00d4), // Hex No. D4 (Dec 212) Character 'Ô' Letter, Uppercase - QChar(0x00d5), // Hex No. D5 (Dec 213) Character 'Õ' Letter, Uppercase - QChar(0x00d6), // Hex No. D6 (Dec 214) Character 'Ö' Letter, Uppercase - QChar(0x00d7), // Hex No. D7 (Dec 215) Character '×' Symbol - QChar(0x00d8), // Hex No. D8 (Dec 216) Character 'Ø' Letter, Uppercase - QChar(0x00d9), // Hex No. D9 (Dec 217) Character 'Ù' Letter, Uppercase - QChar(0x00da), // Hex No. DA (Dec 218) Character 'Ú' Letter, Uppercase - QChar(0x00db), // Hex No. DB (Dec 219) Character 'Û' Letter, Uppercase - QChar(0x00dc), // Hex No. DC (Dec 220) Character 'Ü' Letter, Uppercase - QChar(0x00dd), // Hex No. DD (Dec 221) Character 'Ý' Letter, Uppercase - QChar(0x00de), // Hex No. DE (Dec 222) Character 'Þ' Letter, Uppercase - QChar(0x00df), // Hex No. DF (Dec 223) Character 'ß' Letter, Lowercase - QChar(0x00e0), // Hex No. E0 (Dec 224) Character 'à' Letter, Lowercase - QChar(0x00e1), // Hex No. E1 (Dec 225) Character 'á' Letter, Lowercase - QChar(0x00e2), // Hex No. E2 (Dec 226) Character 'â' Letter, Lowercase - QChar(0x00e3), // Hex No. E3 (Dec 227) Character 'ã' Letter, Lowercase - QChar(0x00e4), // Hex No. E4 (Dec 228) Character 'ä' Letter, Lowercase - QChar(0x00e5), // Hex No. E5 (Dec 229) Character 'å' Letter, Lowercase - QChar(0x00e6), // Hex No. E6 (Dec 230) Character 'æ' Letter, Lowercase - QChar(0x00e7), // Hex No. E7 (Dec 231) Character 'ç' Letter, Lowercase - QChar(0x00e8), // Hex No. E8 (Dec 232) Character 'è' Letter, Lowercase - QChar(0x00e9), // Hex No. E9 (Dec 233) Character 'é' Letter, Lowercase - QChar(0x00ea), // Hex No. EA (Dec 234) Character 'ê' Letter, Lowercase - QChar(0x00eb), // Hex No. EB (Dec 235) Character 'ë' Letter, Lowercase - QChar(0x00ec), // Hex No. EC (Dec 236) Character 'ì' Letter, Lowercase - QChar(0x00ed), // Hex No. ED (Dec 237) Character 'í' Letter, Lowercase - QChar(0x00ee), // Hex No. EE (Dec 238) Character 'î' Letter, Lowercase - QChar(0x00ef), // Hex No. EF (Dec 239) Character 'ï' Letter, Lowercase - QChar(0x00f0), // Hex No. F0 (Dec 240) Character 'ð' Letter, Lowercase - QChar(0x00f1), // Hex No. F1 (Dec 241) Character 'ñ' Letter, Lowercase - QChar(0x00f2), // Hex No. F2 (Dec 242) Character 'ò' Letter, Lowercase - QChar(0x00f3), // Hex No. F3 (Dec 243) Character 'ó' Letter, Lowercase - QChar(0x00f4), // Hex No. F4 (Dec 244) Character 'ô' Letter, Lowercase - QChar(0x00f5), // Hex No. F5 (Dec 245) Character 'õ' Letter, Lowercase - QChar(0x00f6), // Hex No. F6 (Dec 246) Character 'ö' Letter, Lowercase - QChar(0x00f7), // Hex No. F7 (Dec 247) Character '÷' Symbol - QChar(0x00f8), // Hex No. F8 (Dec 248) Character 'ø' Letter, Lowercase - QChar(0x00f9), // Hex No. F9 (Dec 249) Character 'ù' Letter, Lowercase - QChar(0x00fa), // Hex No. FA (Dec 250) Character 'ú' Letter, Lowercase - QChar(0x00fb), // Hex No. FB (Dec 251) Character 'û' Letter, Lowercase - QChar(0x00fc), // Hex No. FC (Dec 252) Character 'ü' Letter, Lowercase - QChar(0x00fd), // Hex No. FD (Dec 253) Character 'ý' Letter, Lowercase - QChar(0x00fe), // Hex No. FE (Dec 254) Character 'þ' Letter, Lowercase - QChar(0x00ff), // Hex No. FF (Dec 255) Character 'ÿ' Letter, Lowercase -}; - -// PDF Reference 1.7, Appendix D, Section D.1/D.2, PDFDocEncoding -static const EncodingTable PDF_DOC_ENCODING_CONVERSION_TABLE = { - QChar(0x0000), // Hex No. 00 (Dec 000) Null character - QChar(0x0001), // Hex No. 01 (Dec 001) - QChar(0x0002), // Hex No. 02 (Dec 002) - QChar(0x0003), // Hex No. 03 (Dec 003) - QChar(0x0004), // Hex No. 04 (Dec 004) - QChar(0x0005), // Hex No. 05 (Dec 005) - QChar(0x0006), // Hex No. 06 (Dec 006) - QChar(0x0007), // Hex No. 07 (Dec 007) - QChar(0x0008), // Hex No. 08 (Dec 008) - QChar(0x0009), // Hex No. 09 (Dec 009) Whitespace - QChar(0x000a), // Hex No. 0A (Dec 010) Whitespace - QChar(0x000b), // Hex No. 0B (Dec 011) Whitespace - QChar(0x000c), // Hex No. 0C (Dec 012) Whitespace - QChar(0x000d), // Hex No. 0D (Dec 013) Whitespace - QChar(0x000e), // Hex No. 0E (Dec 014) - QChar(0x000f), // Hex No. 0F (Dec 015) - QChar(0x0010), // Hex No. 10 (Dec 016) - QChar(0x0011), // Hex No. 11 (Dec 017) - QChar(0x0012), // Hex No. 12 (Dec 018) - QChar(0x0013), // Hex No. 13 (Dec 019) - QChar(0x0014), // Hex No. 14 (Dec 020) - QChar(0x0015), // Hex No. 15 (Dec 021) - QChar(0x0016), // Hex No. 16 (Dec 022) - QChar(0x0017), // Hex No. 17 (Dec 023) - QChar(0x02d8), // Hex No. 18 (Dec 024) Character '˘' Symbol - QChar(0x02c7), // Hex No. 19 (Dec 025) Character 'ˇ' Letter - QChar(0x02c6), // Hex No. 1A (Dec 026) Character 'ˆ' Letter - QChar(0x02d9), // Hex No. 1B (Dec 027) Character '˙' Symbol - QChar(0x02dd), // Hex No. 1C (Dec 028) Character '˝' Symbol - QChar(0x02db), // Hex No. 1D (Dec 029) Character '˛' Symbol - QChar(0x02da), // Hex No. 1E (Dec 030) Character '˚' Symbol - QChar(0x02dc), // Hex No. 1F (Dec 031) Character '˜' Symbol - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation - QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation - QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation - QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol - QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation - QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation - QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation - QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation - QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation - QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation - QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation - QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit - QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit - QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit - QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit - QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit - QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit - QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit - QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit - QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit - QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol - QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol - QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol - QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation - QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation - QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase - QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase - QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase - QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase - QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase - QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase - QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase - QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase - QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase - QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase - QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase - QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase - QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase - QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase - QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase - QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase - QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase - QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase - QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase - QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase - QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase - QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase - QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase - QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase - QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase - QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase - QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation - QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation - QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation - QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol - QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation - QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol - QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase - QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase - QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase - QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase - QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase - QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase - QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase - QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase - QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase - QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase - QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase - QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase - QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase - QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase - QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase - QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase - QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase - QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase - QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase - QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase - QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase - QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase - QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase - QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase - QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase - QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase - QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation - QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol - QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation - QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x2022), // Hex No. 80 (Dec 128) Character '•' Punctuation - QChar(0x2020), // Hex No. 81 (Dec 129) Character '†' Punctuation - QChar(0x2021), // Hex No. 82 (Dec 130) Character '‡' Punctuation - QChar(0x2026), // Hex No. 83 (Dec 131) Character '…' Punctuation - QChar(0x2014), // Hex No. 84 (Dec 132) Character '—' Punctuation - QChar(0x2013), // Hex No. 85 (Dec 133) Character '–' Punctuation - QChar(0x0192), // Hex No. 86 (Dec 134) Character 'ƒ' Letter, Lowercase - QChar(0x2044), // Hex No. 87 (Dec 135) Character '⁄' Symbol - QChar(0x2039), // Hex No. 88 (Dec 136) Character '‹' Punctuation - QChar(0x203a), // Hex No. 89 (Dec 137) Character '›' Punctuation - QChar(0x2212), // Hex No. 8A (Dec 138) Character '−' Symbol - QChar(0x2030), // Hex No. 8B (Dec 139) Character '‰' Punctuation - QChar(0x201e), // Hex No. 8C (Dec 140) Character '„' Punctuation - QChar(0x201c), // Hex No. 8D (Dec 141) Character '“' Punctuation - QChar(0x201d), // Hex No. 8E (Dec 142) Character '”' Punctuation - QChar(0x2018), // Hex No. 8F (Dec 143) Character '‘' Punctuation - QChar(0x2019), // Hex No. 90 (Dec 144) Character '’' Punctuation - QChar(0x201a), // Hex No. 91 (Dec 145) Character '‚' Punctuation - QChar(0x2122), // Hex No. 92 (Dec 146) Character '™' Symbol - QChar(0xfb01), // Hex No. 93 (Dec 147) Character 'fi' Letter, Lowercase - QChar(0xfb02), // Hex No. 94 (Dec 148) Character 'fl' Letter, Lowercase - QChar(0x0141), // Hex No. 95 (Dec 149) Character 'Ł' Letter, Uppercase - QChar(0x0152), // Hex No. 96 (Dec 150) Character 'Œ' Letter, Uppercase - QChar(0x0160), // Hex No. 97 (Dec 151) Character 'Š' Letter, Uppercase - QChar(0x0178), // Hex No. 98 (Dec 152) Character 'Ÿ' Letter, Uppercase - QChar(0x017d), // Hex No. 99 (Dec 153) Character 'Ž' Letter, Uppercase - QChar(0x0131), // Hex No. 9A (Dec 154) Character 'ı' Letter, Lowercase - QChar(0x0142), // Hex No. 9B (Dec 155) Character 'ł' Letter, Lowercase - QChar(0x0153), // Hex No. 9C (Dec 156) Character 'œ' Letter, Lowercase - QChar(0x0161), // Hex No. 9D (Dec 157) Character 'š' Letter, Lowercase - QChar(0x017e), // Hex No. 9E (Dec 158) Character 'ž' Letter, Lowercase - QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x20ac), // Hex No. A0 (Dec 160) Character '€' Symbol - QChar(0x00a1), // Hex No. A1 (Dec 161) Character '¡' Punctuation - QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol - QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol - QChar(0x00a4), // Hex No. A4 (Dec 164) Character '¤' Symbol - QChar(0x00a5), // Hex No. A5 (Dec 165) Character '¥' Symbol - QChar(0x00a6), // Hex No. A6 (Dec 166) Character '¦' Symbol - QChar(0x00a7), // Hex No. A7 (Dec 167) Character '§' Punctuation - QChar(0x00a8), // Hex No. A8 (Dec 168) Character '¨' Symbol - QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol - QChar(0x00aa), // Hex No. AA (Dec 170) Character 'ª' Letter - QChar(0x00ab), // Hex No. AB (Dec 171) Character '«' Punctuation - QChar(0x00ac), // Hex No. AC (Dec 172) Character '¬' Symbol - QChar(0xfffd), // Hex No. AD (Dec 173) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00ae), // Hex No. AE (Dec 174) Character '®' Symbol - QChar(0x00af), // Hex No. AF (Dec 175) Character '¯' Symbol - QChar(0x00b0), // Hex No. B0 (Dec 176) Character '°' Symbol - QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol - QChar(0x00b2), // Hex No. B2 (Dec 178) Character '²' - QChar(0x00b3), // Hex No. B3 (Dec 179) Character '³' - QChar(0x00b4), // Hex No. B4 (Dec 180) Character '´' Symbol - QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase - QChar(0x00b6), // Hex No. B6 (Dec 182) Character '¶' Punctuation - QChar(0x00b7), // Hex No. B7 (Dec 183) Character '·' Punctuation - QChar(0x00b8), // Hex No. B8 (Dec 184) Character '¸' Symbol - QChar(0x00b9), // Hex No. B9 (Dec 185) Character '¹' - QChar(0x00ba), // Hex No. BA (Dec 186) Character 'º' Letter - QChar(0x00bb), // Hex No. BB (Dec 187) Character '»' Punctuation - QChar(0x00bc), // Hex No. BC (Dec 188) Character '¼' - QChar(0x00bd), // Hex No. BD (Dec 189) Character '½' - QChar(0x00be), // Hex No. BE (Dec 190) Character '¾' - QChar(0x00bf), // Hex No. BF (Dec 191) Character '¿' Punctuation - QChar(0x00c0), // Hex No. C0 (Dec 192) Character 'À' Letter, Uppercase - QChar(0x00c1), // Hex No. C1 (Dec 193) Character 'Á' Letter, Uppercase - QChar(0x00c2), // Hex No. C2 (Dec 194) Character 'Â' Letter, Uppercase - QChar(0x00c3), // Hex No. C3 (Dec 195) Character 'Ã' Letter, Uppercase - QChar(0x00c4), // Hex No. C4 (Dec 196) Character 'Ä' Letter, Uppercase - QChar(0x00c5), // Hex No. C5 (Dec 197) Character 'Å' Letter, Uppercase - QChar(0x00c6), // Hex No. C6 (Dec 198) Character 'Æ' Letter, Uppercase - QChar(0x00c7), // Hex No. C7 (Dec 199) Character 'Ç' Letter, Uppercase - QChar(0x00c8), // Hex No. C8 (Dec 200) Character 'È' Letter, Uppercase - QChar(0x00c9), // Hex No. C9 (Dec 201) Character 'É' Letter, Uppercase - QChar(0x00ca), // Hex No. CA (Dec 202) Character 'Ê' Letter, Uppercase - QChar(0x00cb), // Hex No. CB (Dec 203) Character 'Ë' Letter, Uppercase - QChar(0x00cc), // Hex No. CC (Dec 204) Character 'Ì' Letter, Uppercase - QChar(0x00cd), // Hex No. CD (Dec 205) Character 'Í' Letter, Uppercase - QChar(0x00ce), // Hex No. CE (Dec 206) Character 'Î' Letter, Uppercase - QChar(0x00cf), // Hex No. CF (Dec 207) Character 'Ï' Letter, Uppercase - QChar(0x00d0), // Hex No. D0 (Dec 208) Character 'Ð' Letter, Uppercase - QChar(0x00d1), // Hex No. D1 (Dec 209) Character 'Ñ' Letter, Uppercase - QChar(0x00d2), // Hex No. D2 (Dec 210) Character 'Ò' Letter, Uppercase - QChar(0x00d3), // Hex No. D3 (Dec 211) Character 'Ó' Letter, Uppercase - QChar(0x00d4), // Hex No. D4 (Dec 212) Character 'Ô' Letter, Uppercase - QChar(0x00d5), // Hex No. D5 (Dec 213) Character 'Õ' Letter, Uppercase - QChar(0x00d6), // Hex No. D6 (Dec 214) Character 'Ö' Letter, Uppercase - QChar(0x00d7), // Hex No. D7 (Dec 215) Character '×' Symbol - QChar(0x00d8), // Hex No. D8 (Dec 216) Character 'Ø' Letter, Uppercase - QChar(0x00d9), // Hex No. D9 (Dec 217) Character 'Ù' Letter, Uppercase - QChar(0x00da), // Hex No. DA (Dec 218) Character 'Ú' Letter, Uppercase - QChar(0x00db), // Hex No. DB (Dec 219) Character 'Û' Letter, Uppercase - QChar(0x00dc), // Hex No. DC (Dec 220) Character 'Ü' Letter, Uppercase - QChar(0x00dd), // Hex No. DD (Dec 221) Character 'Ý' Letter, Uppercase - QChar(0x00de), // Hex No. DE (Dec 222) Character 'Þ' Letter, Uppercase - QChar(0x00df), // Hex No. DF (Dec 223) Character 'ß' Letter, Lowercase - QChar(0x00e0), // Hex No. E0 (Dec 224) Character 'à' Letter, Lowercase - QChar(0x00e1), // Hex No. E1 (Dec 225) Character 'á' Letter, Lowercase - QChar(0x00e2), // Hex No. E2 (Dec 226) Character 'â' Letter, Lowercase - QChar(0x00e3), // Hex No. E3 (Dec 227) Character 'ã' Letter, Lowercase - QChar(0x00e4), // Hex No. E4 (Dec 228) Character 'ä' Letter, Lowercase - QChar(0x00e5), // Hex No. E5 (Dec 229) Character 'å' Letter, Lowercase - QChar(0x00e6), // Hex No. E6 (Dec 230) Character 'æ' Letter, Lowercase - QChar(0x00e7), // Hex No. E7 (Dec 231) Character 'ç' Letter, Lowercase - QChar(0x00e8), // Hex No. E8 (Dec 232) Character 'è' Letter, Lowercase - QChar(0x00e9), // Hex No. E9 (Dec 233) Character 'é' Letter, Lowercase - QChar(0x00ea), // Hex No. EA (Dec 234) Character 'ê' Letter, Lowercase - QChar(0x00eb), // Hex No. EB (Dec 235) Character 'ë' Letter, Lowercase - QChar(0x00ec), // Hex No. EC (Dec 236) Character 'ì' Letter, Lowercase - QChar(0x00ed), // Hex No. ED (Dec 237) Character 'í' Letter, Lowercase - QChar(0x00ee), // Hex No. EE (Dec 238) Character 'î' Letter, Lowercase - QChar(0x00ef), // Hex No. EF (Dec 239) Character 'ï' Letter, Lowercase - QChar(0x00f0), // Hex No. F0 (Dec 240) Character 'ð' Letter, Lowercase - QChar(0x00f1), // Hex No. F1 (Dec 241) Character 'ñ' Letter, Lowercase - QChar(0x00f2), // Hex No. F2 (Dec 242) Character 'ò' Letter, Lowercase - QChar(0x00f3), // Hex No. F3 (Dec 243) Character 'ó' Letter, Lowercase - QChar(0x00f4), // Hex No. F4 (Dec 244) Character 'ô' Letter, Lowercase - QChar(0x00f5), // Hex No. F5 (Dec 245) Character 'õ' Letter, Lowercase - QChar(0x00f6), // Hex No. F6 (Dec 246) Character 'ö' Letter, Lowercase - QChar(0x00f7), // Hex No. F7 (Dec 247) Character '÷' Symbol - QChar(0x00f8), // Hex No. F8 (Dec 248) Character 'ø' Letter, Lowercase - QChar(0x00f9), // Hex No. F9 (Dec 249) Character 'ù' Letter, Lowercase - QChar(0x00fa), // Hex No. FA (Dec 250) Character 'ú' Letter, Lowercase - QChar(0x00fb), // Hex No. FB (Dec 251) Character 'û' Letter, Lowercase - QChar(0x00fc), // Hex No. FC (Dec 252) Character 'ü' Letter, Lowercase - QChar(0x00fd), // Hex No. FD (Dec 253) Character 'ý' Letter, Lowercase - QChar(0x00fe), // Hex No. FE (Dec 254) Character 'þ' Letter, Lowercase - QChar(0x00ff), // Hex No. FF (Dec 255) Character 'ÿ' Letter, Lowercase -}; - -// PDF Reference 1.7, Appendix D, Section D.3, MacExpertEncoding -static const EncodingTable MAC_EXPERT_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0xf721), // Hex No. 21 (Dec 033) - QChar(0xf6f8), // Hex No. 22 (Dec 034) - QChar(0xf7a2), // Hex No. 23 (Dec 035) - QChar(0xf724), // Hex No. 24 (Dec 036) - QChar(0xf6e4), // Hex No. 25 (Dec 037) - QChar(0xf726), // Hex No. 26 (Dec 038) - QChar(0xf7b4), // Hex No. 27 (Dec 039) - QChar(0x207d), // Hex No. 28 (Dec 040) Character '⁽' Punctuation - QChar(0x207e), // Hex No. 29 (Dec 041) Character '⁾' Punctuation - QChar(0x2025), // Hex No. 2A (Dec 042) Character '‥' Punctuation - QChar(0x2024), // Hex No. 2B (Dec 043) Character '․' Punctuation - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x2044), // Hex No. 2F (Dec 047) Character '⁄' Symbol - QChar(0xf730), // Hex No. 30 (Dec 048) - QChar(0xf731), // Hex No. 31 (Dec 049) - QChar(0xf732), // Hex No. 32 (Dec 050) - QChar(0xf733), // Hex No. 33 (Dec 051) - QChar(0xf734), // Hex No. 34 (Dec 052) - QChar(0xf735), // Hex No. 35 (Dec 053) - QChar(0xf736), // Hex No. 36 (Dec 054) - QChar(0xf737), // Hex No. 37 (Dec 055) - QChar(0xf738), // Hex No. 38 (Dec 056) - QChar(0xf739), // Hex No. 39 (Dec 057) - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0xfffd), // Hex No. 3C (Dec 060) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6de), // Hex No. 3D (Dec 061) - QChar(0xfffd), // Hex No. 3E (Dec 062) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf73f), // Hex No. 3F (Dec 063) - QChar(0xfffd), // Hex No. 40 (Dec 064) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 41 (Dec 065) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 42 (Dec 066) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 43 (Dec 067) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7f0), // Hex No. 44 (Dec 068) - QChar(0xfffd), // Hex No. 45 (Dec 069) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 46 (Dec 070) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00bc), // Hex No. 47 (Dec 071) Character '¼' - QChar(0x00bd), // Hex No. 48 (Dec 072) Character '½' - QChar(0x00be), // Hex No. 49 (Dec 073) Character '¾' - QChar(0x215b), // Hex No. 4A (Dec 074) Character '⅛' - QChar(0x215c), // Hex No. 4B (Dec 075) Character '⅜' - QChar(0x215d), // Hex No. 4C (Dec 076) Character '⅝' - QChar(0x215e), // Hex No. 4D (Dec 077) Character '⅞' - QChar(0x2153), // Hex No. 4E (Dec 078) Character '⅓' - QChar(0x2154), // Hex No. 4F (Dec 079) Character '⅔' - QChar(0xfffd), // Hex No. 50 (Dec 080) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 51 (Dec 081) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 52 (Dec 082) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 53 (Dec 083) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 54 (Dec 084) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 55 (Dec 085) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfb00), // Hex No. 56 (Dec 086) Character 'ff' Letter, Lowercase - QChar(0xfb01), // Hex No. 57 (Dec 087) Character 'fi' Letter, Lowercase - QChar(0xfb02), // Hex No. 58 (Dec 088) Character 'fl' Letter, Lowercase - QChar(0xfb03), // Hex No. 59 (Dec 089) Character 'ffi' Letter, Lowercase - QChar(0xfb04), // Hex No. 5A (Dec 090) Character 'ffl' Letter, Lowercase - QChar(0x208d), // Hex No. 5B (Dec 091) Character '₍' Punctuation - QChar(0xfffd), // Hex No. 5C (Dec 092) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x208e), // Hex No. 5D (Dec 093) Character '₎' Punctuation - QChar(0xf6f6), // Hex No. 5E (Dec 094) - QChar(0xf6e5), // Hex No. 5F (Dec 095) - QChar(0xf760), // Hex No. 60 (Dec 096) - QChar(0xf761), // Hex No. 61 (Dec 097) - QChar(0xf762), // Hex No. 62 (Dec 098) - QChar(0xf763), // Hex No. 63 (Dec 099) - QChar(0xf764), // Hex No. 64 (Dec 100) - QChar(0xf765), // Hex No. 65 (Dec 101) - QChar(0xf766), // Hex No. 66 (Dec 102) - QChar(0xf767), // Hex No. 67 (Dec 103) - QChar(0xf768), // Hex No. 68 (Dec 104) - QChar(0xf769), // Hex No. 69 (Dec 105) - QChar(0xf76a), // Hex No. 6A (Dec 106) - QChar(0xf76b), // Hex No. 6B (Dec 107) - QChar(0xf76c), // Hex No. 6C (Dec 108) - QChar(0xf76d), // Hex No. 6D (Dec 109) - QChar(0xf76e), // Hex No. 6E (Dec 110) - QChar(0xf76f), // Hex No. 6F (Dec 111) - QChar(0xf770), // Hex No. 70 (Dec 112) - QChar(0xf771), // Hex No. 71 (Dec 113) - QChar(0xf772), // Hex No. 72 (Dec 114) - QChar(0xf773), // Hex No. 73 (Dec 115) - QChar(0xf774), // Hex No. 74 (Dec 116) - QChar(0xf775), // Hex No. 75 (Dec 117) - QChar(0xf776), // Hex No. 76 (Dec 118) - QChar(0xf777), // Hex No. 77 (Dec 119) - QChar(0xf778), // Hex No. 78 (Dec 120) - QChar(0xf779), // Hex No. 79 (Dec 121) - QChar(0xf77a), // Hex No. 7A (Dec 122) - QChar(0x20a1), // Hex No. 7B (Dec 123) Character '₡' Symbol - QChar(0xf6dc), // Hex No. 7C (Dec 124) - QChar(0xf6dd), // Hex No. 7D (Dec 125) - QChar(0xf6fe), // Hex No. 7E (Dec 126) - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6e9), // Hex No. 81 (Dec 129) - QChar(0xf6e0), // Hex No. 82 (Dec 130) - QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7e1), // Hex No. 87 (Dec 135) - QChar(0xf7e0), // Hex No. 88 (Dec 136) - QChar(0xf7e2), // Hex No. 89 (Dec 137) - QChar(0xf7e4), // Hex No. 8A (Dec 138) - QChar(0xf7e3), // Hex No. 8B (Dec 139) - QChar(0xf7e5), // Hex No. 8C (Dec 140) - QChar(0xf7e7), // Hex No. 8D (Dec 141) - QChar(0xf7e9), // Hex No. 8E (Dec 142) - QChar(0xf7e8), // Hex No. 8F (Dec 143) - QChar(0xf7ea), // Hex No. 90 (Dec 144) - QChar(0xf7eb), // Hex No. 91 (Dec 145) - QChar(0xf7ed), // Hex No. 92 (Dec 146) - QChar(0xf7ec), // Hex No. 93 (Dec 147) - QChar(0xf7ee), // Hex No. 94 (Dec 148) - QChar(0xf7ef), // Hex No. 95 (Dec 149) - QChar(0xf7f1), // Hex No. 96 (Dec 150) - QChar(0xf7f3), // Hex No. 97 (Dec 151) - QChar(0xf7f2), // Hex No. 98 (Dec 152) - QChar(0xf7f4), // Hex No. 99 (Dec 153) - QChar(0xf7f6), // Hex No. 9A (Dec 154) - QChar(0xf7f5), // Hex No. 9B (Dec 155) - QChar(0xf7fa), // Hex No. 9C (Dec 156) - QChar(0xf7f9), // Hex No. 9D (Dec 157) - QChar(0xf7fb), // Hex No. 9E (Dec 158) - QChar(0xf7fc), // Hex No. 9F (Dec 159) - QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x2078), // Hex No. A1 (Dec 161) Character '⁸' - QChar(0x2084), // Hex No. A2 (Dec 162) Character '₄' - QChar(0x2083), // Hex No. A3 (Dec 163) Character '₃' - QChar(0x2086), // Hex No. A4 (Dec 164) Character '₆' - QChar(0x2088), // Hex No. A5 (Dec 165) Character '₈' - QChar(0x2087), // Hex No. A6 (Dec 166) Character '₇' - QChar(0xf6fd), // Hex No. A7 (Dec 167) - QChar(0xfffd), // Hex No. A8 (Dec 168) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6df), // Hex No. A9 (Dec 169) - QChar(0x2082), // Hex No. AA (Dec 170) Character '₂' - QChar(0xfffd), // Hex No. AB (Dec 171) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7a8), // Hex No. AC (Dec 172) - QChar(0xfffd), // Hex No. AD (Dec 173) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6f5), // Hex No. AE (Dec 174) - QChar(0xf6f0), // Hex No. AF (Dec 175) - QChar(0x2085), // Hex No. B0 (Dec 176) Character '₅' - QChar(0xfffd), // Hex No. B1 (Dec 177) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6e1), // Hex No. B2 (Dec 178) - QChar(0xf6e7), // Hex No. B3 (Dec 179) - QChar(0xf7fd), // Hex No. B4 (Dec 180) - QChar(0xfffd), // Hex No. B5 (Dec 181) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6e3), // Hex No. B6 (Dec 182) - QChar(0xfffd), // Hex No. B7 (Dec 183) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. B8 (Dec 184) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7fe), // Hex No. B9 (Dec 185) - QChar(0xfffd), // Hex No. BA (Dec 186) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x2089), // Hex No. BB (Dec 187) Character '₉' - QChar(0x2080), // Hex No. BC (Dec 188) Character '₀' - QChar(0xf6ff), // Hex No. BD (Dec 189) - QChar(0xf7e6), // Hex No. BE (Dec 190) - QChar(0xf7f8), // Hex No. BF (Dec 191) - QChar(0xf7bf), // Hex No. C0 (Dec 192) - QChar(0x2081), // Hex No. C1 (Dec 193) Character '₁' - QChar(0xf6f9), // Hex No. C2 (Dec 194) - QChar(0xfffd), // Hex No. C3 (Dec 195) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. C4 (Dec 196) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. C5 (Dec 197) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. C6 (Dec 198) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. C7 (Dec 199) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. C8 (Dec 200) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7b8), // Hex No. C9 (Dec 201) - QChar(0xfffd), // Hex No. CA (Dec 202) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. CB (Dec 203) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. CC (Dec 204) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. CD (Dec 205) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. CE (Dec 206) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6fa), // Hex No. CF (Dec 207) - QChar(0x2012), // Hex No. D0 (Dec 208) Character '‒' Punctuation - QChar(0xf6e6), // Hex No. D1 (Dec 209) - QChar(0xfffd), // Hex No. D2 (Dec 210) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D3 (Dec 211) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D4 (Dec 212) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. D5 (Dec 213) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7a1), // Hex No. D6 (Dec 214) - QChar(0xfffd), // Hex No. D7 (Dec 215) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf7ff), // Hex No. D8 (Dec 216) - QChar(0xfffd), // Hex No. D9 (Dec 217) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00b9), // Hex No. DA (Dec 218) Character '¹' - QChar(0x00b2), // Hex No. DB (Dec 219) Character '²' - QChar(0x00b3), // Hex No. DC (Dec 220) Character '³' - QChar(0x2074), // Hex No. DD (Dec 221) Character '⁴' - QChar(0x2075), // Hex No. DE (Dec 222) Character '⁵' - QChar(0x2076), // Hex No. DF (Dec 223) Character '⁶' - QChar(0x2077), // Hex No. E0 (Dec 224) Character '⁷' - QChar(0x2079), // Hex No. E1 (Dec 225) Character '⁹' - QChar(0x2070), // Hex No. E2 (Dec 226) Character '⁰' - QChar(0xfffd), // Hex No. E3 (Dec 227) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6ec), // Hex No. E4 (Dec 228) - QChar(0xf6f1), // Hex No. E5 (Dec 229) - QChar(0xf6f3), // Hex No. E6 (Dec 230) - QChar(0xfffd), // Hex No. E7 (Dec 231) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. E8 (Dec 232) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6ed), // Hex No. E9 (Dec 233) - QChar(0xf6f2), // Hex No. EA (Dec 234) - QChar(0xf6eb), // Hex No. EB (Dec 235) - QChar(0xfffd), // Hex No. EC (Dec 236) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. ED (Dec 237) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. EE (Dec 238) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. EF (Dec 239) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xf6ee), // Hex No. F1 (Dec 241) - QChar(0xf6fb), // Hex No. F2 (Dec 242) - QChar(0xf6f4), // Hex No. F3 (Dec 243) - QChar(0xf7af), // Hex No. F4 (Dec 244) - QChar(0xf6ea), // Hex No. F5 (Dec 245) - QChar(0x207f), // Hex No. F6 (Dec 246) Character 'ⁿ' Letter - QChar(0xf6ef), // Hex No. F7 (Dec 247) - QChar(0xf6e2), // Hex No. F8 (Dec 248) - QChar(0xf6e8), // Hex No. F9 (Dec 249) - QChar(0xf6f7), // Hex No. FA (Dec 250) - QChar(0xf6fc), // Hex No. FB (Dec 251) - QChar(0xfffd), // Hex No. FC (Dec 252) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. FD (Dec 253) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. FE (Dec 254) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set -}; - -// PDF Reference 1.7, Appendix D, Section D.4, Symbol Set and Encoding -static const EncodingTable SYMBOL_SET_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation - QChar(0x2200), // Hex No. 22 (Dec 034) Character '∀' Symbol - QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation - QChar(0x2203), // Hex No. 24 (Dec 036) Character '∃' Symbol - QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation - QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation - QChar(0x220b), // Hex No. 27 (Dec 039) Character '∋' Symbol - QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation - QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation - QChar(0x2217), // Hex No. 2A (Dec 042) Character '∗' Symbol - QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x2212), // Hex No. 2D (Dec 045) Character '−' Symbol - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation - QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit - QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit - QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit - QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit - QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit - QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit - QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit - QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit - QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit - QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol - QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol - QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol - QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation - QChar(0x2245), // Hex No. 40 (Dec 064) Character '≅' Symbol - QChar(0x0391), // Hex No. 41 (Dec 065) Character 'Α' Letter, Uppercase - QChar(0x0392), // Hex No. 42 (Dec 066) Character 'Β' Letter, Uppercase - QChar(0x03a7), // Hex No. 43 (Dec 067) Character 'Χ' Letter, Uppercase - QChar(0x2206), // Hex No. 44 (Dec 068) Character '∆' Symbol - QChar(0x0395), // Hex No. 45 (Dec 069) Character 'Ε' Letter, Uppercase - QChar(0x03a6), // Hex No. 46 (Dec 070) Character 'Φ' Letter, Uppercase - QChar(0x0393), // Hex No. 47 (Dec 071) Character 'Γ' Letter, Uppercase - QChar(0x0397), // Hex No. 48 (Dec 072) Character 'Η' Letter, Uppercase - QChar(0x0399), // Hex No. 49 (Dec 073) Character 'Ι' Letter, Uppercase - QChar(0x03d1), // Hex No. 4A (Dec 074) Character 'ϑ' Letter, Lowercase - QChar(0x039a), // Hex No. 4B (Dec 075) Character 'Κ' Letter, Uppercase - QChar(0x039b), // Hex No. 4C (Dec 076) Character 'Λ' Letter, Uppercase - QChar(0x039c), // Hex No. 4D (Dec 077) Character 'Μ' Letter, Uppercase - QChar(0x039d), // Hex No. 4E (Dec 078) Character 'Ν' Letter, Uppercase - QChar(0x039f), // Hex No. 4F (Dec 079) Character 'Ο' Letter, Uppercase - QChar(0x03a0), // Hex No. 50 (Dec 080) Character 'Π' Letter, Uppercase - QChar(0x0398), // Hex No. 51 (Dec 081) Character 'Θ' Letter, Uppercase - QChar(0x03a1), // Hex No. 52 (Dec 082) Character 'Ρ' Letter, Uppercase - QChar(0x03a3), // Hex No. 53 (Dec 083) Character 'Σ' Letter, Uppercase - QChar(0x03a4), // Hex No. 54 (Dec 084) Character 'Τ' Letter, Uppercase - QChar(0x03a5), // Hex No. 55 (Dec 085) Character 'Υ' Letter, Uppercase - QChar(0x03c2), // Hex No. 56 (Dec 086) Character 'ς' Letter, Lowercase - QChar(0x2126), // Hex No. 57 (Dec 087) Character 'Ω' Letter, Uppercase - QChar(0x039e), // Hex No. 58 (Dec 088) Character 'Ξ' Letter, Uppercase - QChar(0x03a8), // Hex No. 59 (Dec 089) Character 'Ψ' Letter, Uppercase - QChar(0x0396), // Hex No. 5A (Dec 090) Character 'Ζ' Letter, Uppercase - QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation - QChar(0x2234), // Hex No. 5C (Dec 092) Character '∴' Symbol - QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation - QChar(0x22a5), // Hex No. 5E (Dec 094) Character '⊥' Symbol - QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation - QChar(0xf8e5), // Hex No. 60 (Dec 096) - QChar(0x03b1), // Hex No. 61 (Dec 097) Character 'α' Letter, Lowercase - QChar(0x03b2), // Hex No. 62 (Dec 098) Character 'β' Letter, Lowercase - QChar(0x03c7), // Hex No. 63 (Dec 099) Character 'χ' Letter, Lowercase - QChar(0x03b4), // Hex No. 64 (Dec 100) Character 'δ' Letter, Lowercase - QChar(0x03b5), // Hex No. 65 (Dec 101) Character 'ε' Letter, Lowercase - QChar(0x03c6), // Hex No. 66 (Dec 102) Character 'φ' Letter, Lowercase - QChar(0x03b3), // Hex No. 67 (Dec 103) Character 'γ' Letter, Lowercase - QChar(0x03b7), // Hex No. 68 (Dec 104) Character 'η' Letter, Lowercase - QChar(0x03b9), // Hex No. 69 (Dec 105) Character 'ι' Letter, Lowercase - QChar(0x03d5), // Hex No. 6A (Dec 106) Character 'ϕ' Letter, Lowercase - QChar(0x03ba), // Hex No. 6B (Dec 107) Character 'κ' Letter, Lowercase - QChar(0x03bb), // Hex No. 6C (Dec 108) Character 'λ' Letter, Lowercase - QChar(0x00b5), // Hex No. 6D (Dec 109) Character 'µ' Letter, Lowercase - QChar(0x03bd), // Hex No. 6E (Dec 110) Character 'ν' Letter, Lowercase - QChar(0x03bf), // Hex No. 6F (Dec 111) Character 'ο' Letter, Lowercase - QChar(0x03c0), // Hex No. 70 (Dec 112) Character 'π' Letter, Lowercase - QChar(0x03b8), // Hex No. 71 (Dec 113) Character 'θ' Letter, Lowercase - QChar(0x03c1), // Hex No. 72 (Dec 114) Character 'ρ' Letter, Lowercase - QChar(0x03c3), // Hex No. 73 (Dec 115) Character 'σ' Letter, Lowercase - QChar(0x03c4), // Hex No. 74 (Dec 116) Character 'τ' Letter, Lowercase - QChar(0x03c5), // Hex No. 75 (Dec 117) Character 'υ' Letter, Lowercase - QChar(0x03d6), // Hex No. 76 (Dec 118) Character 'ϖ' Letter, Lowercase - QChar(0x03c9), // Hex No. 77 (Dec 119) Character 'ω' Letter, Lowercase - QChar(0x03be), // Hex No. 78 (Dec 120) Character 'ξ' Letter, Lowercase - QChar(0x03c8), // Hex No. 79 (Dec 121) Character 'ψ' Letter, Lowercase - QChar(0x03b6), // Hex No. 7A (Dec 122) Character 'ζ' Letter, Lowercase - QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation - QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol - QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation - QChar(0x223c), // Hex No. 7E (Dec 126) Character '∼' Symbol - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 81 (Dec 129) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 82 (Dec 130) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 87 (Dec 135) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 88 (Dec 136) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 89 (Dec 137) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8A (Dec 138) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8B (Dec 139) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8C (Dec 140) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8D (Dec 141) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8E (Dec 142) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8F (Dec 143) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 90 (Dec 144) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 91 (Dec 145) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 92 (Dec 146) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 93 (Dec 147) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 94 (Dec 148) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 95 (Dec 149) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 96 (Dec 150) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 97 (Dec 151) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 98 (Dec 152) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 99 (Dec 153) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9A (Dec 154) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9B (Dec 155) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9C (Dec 156) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9D (Dec 157) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9E (Dec 158) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x03d2), // Hex No. A1 (Dec 161) Character 'ϒ' Letter, Uppercase - QChar(0x2032), // Hex No. A2 (Dec 162) Character '′' Punctuation - QChar(0x2264), // Hex No. A3 (Dec 163) Character '≤' Symbol - QChar(0x2044), // Hex No. A4 (Dec 164) Character '⁄' Symbol - QChar(0x221e), // Hex No. A5 (Dec 165) Character '∞' Symbol - QChar(0x0192), // Hex No. A6 (Dec 166) Character 'ƒ' Letter, Lowercase - QChar(0x2663), // Hex No. A7 (Dec 167) Character '♣' Symbol - QChar(0x2666), // Hex No. A8 (Dec 168) Character '♦' Symbol - QChar(0x2665), // Hex No. A9 (Dec 169) Character '♥' Symbol - QChar(0x2660), // Hex No. AA (Dec 170) Character '♠' Symbol - QChar(0x2194), // Hex No. AB (Dec 171) Character '↔' Symbol - QChar(0x2190), // Hex No. AC (Dec 172) Character '←' Symbol - QChar(0x2191), // Hex No. AD (Dec 173) Character '↑' Symbol - QChar(0x2192), // Hex No. AE (Dec 174) Character '→' Symbol - QChar(0x2193), // Hex No. AF (Dec 175) Character '↓' Symbol - QChar(0x00b0), // Hex No. B0 (Dec 176) Character '°' Symbol - QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol - QChar(0x2033), // Hex No. B2 (Dec 178) Character '″' Punctuation - QChar(0x2265), // Hex No. B3 (Dec 179) Character '≥' Symbol - QChar(0x00d7), // Hex No. B4 (Dec 180) Character '×' Symbol - QChar(0x221d), // Hex No. B5 (Dec 181) Character '∝' Symbol - QChar(0x2202), // Hex No. B6 (Dec 182) Character '∂' Symbol - QChar(0x2022), // Hex No. B7 (Dec 183) Character '•' Punctuation - QChar(0x00f7), // Hex No. B8 (Dec 184) Character '÷' Symbol - QChar(0x2260), // Hex No. B9 (Dec 185) Character '≠' Symbol - QChar(0x2261), // Hex No. BA (Dec 186) Character '≡' Symbol - QChar(0x2248), // Hex No. BB (Dec 187) Character '≈' Symbol - QChar(0x2026), // Hex No. BC (Dec 188) Character '…' Punctuation - QChar(0xf8e6), // Hex No. BD (Dec 189) - QChar(0xf8e7), // Hex No. BE (Dec 190) - QChar(0x21b5), // Hex No. BF (Dec 191) Character '↵' Symbol - QChar(0x2135), // Hex No. C0 (Dec 192) Character 'ℵ' Letter - QChar(0x2111), // Hex No. C1 (Dec 193) Character 'ℑ' Letter, Uppercase - QChar(0x211c), // Hex No. C2 (Dec 194) Character 'ℜ' Letter, Uppercase - QChar(0x2118), // Hex No. C3 (Dec 195) Character '℘' Symbol - QChar(0x2297), // Hex No. C4 (Dec 196) Character '⊗' Symbol - QChar(0x2295), // Hex No. C5 (Dec 197) Character '⊕' Symbol - QChar(0x2205), // Hex No. C6 (Dec 198) Character '∅' Symbol - QChar(0x2229), // Hex No. C7 (Dec 199) Character '∩' Symbol - QChar(0x222a), // Hex No. C8 (Dec 200) Character '∪' Symbol - QChar(0x2283), // Hex No. C9 (Dec 201) Character '⊃' Symbol - QChar(0x2287), // Hex No. CA (Dec 202) Character '⊇' Symbol - QChar(0x2284), // Hex No. CB (Dec 203) Character '⊄' Symbol - QChar(0x2282), // Hex No. CC (Dec 204) Character '⊂' Symbol - QChar(0x2286), // Hex No. CD (Dec 205) Character '⊆' Symbol - QChar(0x2208), // Hex No. CE (Dec 206) Character '∈' Symbol - QChar(0x2209), // Hex No. CF (Dec 207) Character '∉' Symbol - QChar(0x2220), // Hex No. D0 (Dec 208) Character '∠' Symbol - QChar(0x2207), // Hex No. D1 (Dec 209) Character '∇' Symbol - QChar(0xf6da), // Hex No. D2 (Dec 210) - QChar(0xf6d9), // Hex No. D3 (Dec 211) - QChar(0xf6db), // Hex No. D4 (Dec 212) - QChar(0x220f), // Hex No. D5 (Dec 213) Character '∏' Symbol - QChar(0x221a), // Hex No. D6 (Dec 214) Character '√' Symbol - QChar(0x22c5), // Hex No. D7 (Dec 215) Character '⋅' Symbol - QChar(0x00ac), // Hex No. D8 (Dec 216) Character '¬' Symbol - QChar(0x2227), // Hex No. D9 (Dec 217) Character '∧' Symbol - QChar(0x2228), // Hex No. DA (Dec 218) Character '∨' Symbol - QChar(0x21d4), // Hex No. DB (Dec 219) Character '⇔' Symbol - QChar(0x21d0), // Hex No. DC (Dec 220) Character '⇐' Symbol - QChar(0x21d1), // Hex No. DD (Dec 221) Character '⇑' Symbol - QChar(0x21d2), // Hex No. DE (Dec 222) Character '⇒' Symbol - QChar(0x21d3), // Hex No. DF (Dec 223) Character '⇓' Symbol - QChar(0x25ca), // Hex No. E0 (Dec 224) Character '◊' Symbol - QChar(0x2329), // Hex No. E1 (Dec 225) Character '〈' Punctuation - QChar(0xf8e8), // Hex No. E2 (Dec 226) - QChar(0xf8e9), // Hex No. E3 (Dec 227) - QChar(0xf8ea), // Hex No. E4 (Dec 228) - QChar(0x2211), // Hex No. E5 (Dec 229) Character '∑' Symbol - QChar(0xf8eb), // Hex No. E6 (Dec 230) - QChar(0xf8ec), // Hex No. E7 (Dec 231) - QChar(0xf8ed), // Hex No. E8 (Dec 232) - QChar(0xf8ee), // Hex No. E9 (Dec 233) - QChar(0xf8ef), // Hex No. EA (Dec 234) - QChar(0xf8f0), // Hex No. EB (Dec 235) - QChar(0xf8f1), // Hex No. EC (Dec 236) - QChar(0xf8f2), // Hex No. ED (Dec 237) - QChar(0xf8f3), // Hex No. EE (Dec 238) - QChar(0xf8f4), // Hex No. EF (Dec 239) - QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x232a), // Hex No. F1 (Dec 241) Character '〉' Punctuation - QChar(0x222b), // Hex No. F2 (Dec 242) Character '∫' Symbol - QChar(0x2320), // Hex No. F3 (Dec 243) Character '⌠' Symbol - QChar(0xf8f5), // Hex No. F4 (Dec 244) - QChar(0x2321), // Hex No. F5 (Dec 245) Character '⌡' Symbol - QChar(0xf8f6), // Hex No. F6 (Dec 246) - QChar(0xf8f7), // Hex No. F7 (Dec 247) - QChar(0xf8f8), // Hex No. F8 (Dec 248) - QChar(0xf8f9), // Hex No. F9 (Dec 249) - QChar(0xf8fa), // Hex No. FA (Dec 250) - QChar(0xf8fb), // Hex No. FB (Dec 251) - QChar(0xf8fc), // Hex No. FC (Dec 252) - QChar(0xf8fd), // Hex No. FD (Dec 253) - QChar(0xf8fe), // Hex No. FE (Dec 254) - QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set -}; - -// PDF Reference 1.7, Appendix D, Section D.5, Zapf Dingbats Set and Encoding -static const EncodingTable ZAPF_DINGBATS_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x2701), // Hex No. 21 (Dec 033) Character '✁' Symbol - QChar(0x2702), // Hex No. 22 (Dec 034) Character '✂' Symbol - QChar(0x2703), // Hex No. 23 (Dec 035) Character '✃' Symbol - QChar(0x2704), // Hex No. 24 (Dec 036) Character '✄' Symbol - QChar(0x260e), // Hex No. 25 (Dec 037) Character '☎' Symbol - QChar(0x2706), // Hex No. 26 (Dec 038) Character '✆' Symbol - QChar(0x2707), // Hex No. 27 (Dec 039) Character '✇' Symbol - QChar(0x2708), // Hex No. 28 (Dec 040) Character '✈' Symbol - QChar(0x2709), // Hex No. 29 (Dec 041) Character '✉' Symbol - QChar(0x261b), // Hex No. 2A (Dec 042) Character '☛' Symbol - QChar(0x261e), // Hex No. 2B (Dec 043) Character '☞' Symbol - QChar(0x270c), // Hex No. 2C (Dec 044) Character '✌' Symbol - QChar(0x270d), // Hex No. 2D (Dec 045) Character '✍' Symbol - QChar(0x270e), // Hex No. 2E (Dec 046) Character '✎' Symbol - QChar(0x270f), // Hex No. 2F (Dec 047) Character '✏' Symbol - QChar(0x2710), // Hex No. 30 (Dec 048) Character '✐' Symbol - QChar(0x2711), // Hex No. 31 (Dec 049) Character '✑' Symbol - QChar(0x2712), // Hex No. 32 (Dec 050) Character '✒' Symbol - QChar(0x2713), // Hex No. 33 (Dec 051) Character '✓' Symbol - QChar(0x2714), // Hex No. 34 (Dec 052) Character '✔' Symbol - QChar(0x2715), // Hex No. 35 (Dec 053) Character '✕' Symbol - QChar(0x2716), // Hex No. 36 (Dec 054) Character '✖' Symbol - QChar(0x2717), // Hex No. 37 (Dec 055) Character '✗' Symbol - QChar(0x2718), // Hex No. 38 (Dec 056) Character '✘' Symbol - QChar(0x2719), // Hex No. 39 (Dec 057) Character '✙' Symbol - QChar(0x271a), // Hex No. 3A (Dec 058) Character '✚' Symbol - QChar(0x271b), // Hex No. 3B (Dec 059) Character '✛' Symbol - QChar(0x271c), // Hex No. 3C (Dec 060) Character '✜' Symbol - QChar(0x271d), // Hex No. 3D (Dec 061) Character '✝' Symbol - QChar(0x271e), // Hex No. 3E (Dec 062) Character '✞' Symbol - QChar(0x271f), // Hex No. 3F (Dec 063) Character '✟' Symbol - QChar(0x2720), // Hex No. 40 (Dec 064) Character '✠' Symbol - QChar(0x2721), // Hex No. 41 (Dec 065) Character '✡' Symbol - QChar(0x2722), // Hex No. 42 (Dec 066) Character '✢' Symbol - QChar(0x2723), // Hex No. 43 (Dec 067) Character '✣' Symbol - QChar(0x2724), // Hex No. 44 (Dec 068) Character '✤' Symbol - QChar(0x2725), // Hex No. 45 (Dec 069) Character '✥' Symbol - QChar(0x2726), // Hex No. 46 (Dec 070) Character '✦' Symbol - QChar(0x2727), // Hex No. 47 (Dec 071) Character '✧' Symbol - QChar(0x2605), // Hex No. 48 (Dec 072) Character '★' Symbol - QChar(0x2729), // Hex No. 49 (Dec 073) Character '✩' Symbol - QChar(0x272a), // Hex No. 4A (Dec 074) Character '✪' Symbol - QChar(0x272b), // Hex No. 4B (Dec 075) Character '✫' Symbol - QChar(0x272c), // Hex No. 4C (Dec 076) Character '✬' Symbol - QChar(0x272d), // Hex No. 4D (Dec 077) Character '✭' Symbol - QChar(0x272e), // Hex No. 4E (Dec 078) Character '✮' Symbol - QChar(0x272f), // Hex No. 4F (Dec 079) Character '✯' Symbol - QChar(0x2730), // Hex No. 50 (Dec 080) Character '✰' Symbol - QChar(0x2731), // Hex No. 51 (Dec 081) Character '✱' Symbol - QChar(0x2732), // Hex No. 52 (Dec 082) Character '✲' Symbol - QChar(0x2733), // Hex No. 53 (Dec 083) Character '✳' Symbol - QChar(0x2734), // Hex No. 54 (Dec 084) Character '✴' Symbol - QChar(0x2735), // Hex No. 55 (Dec 085) Character '✵' Symbol - QChar(0x2736), // Hex No. 56 (Dec 086) Character '✶' Symbol - QChar(0x2737), // Hex No. 57 (Dec 087) Character '✷' Symbol - QChar(0x2738), // Hex No. 58 (Dec 088) Character '✸' Symbol - QChar(0x2739), // Hex No. 59 (Dec 089) Character '✹' Symbol - QChar(0x273a), // Hex No. 5A (Dec 090) Character '✺' Symbol - QChar(0x273b), // Hex No. 5B (Dec 091) Character '✻' Symbol - QChar(0x273c), // Hex No. 5C (Dec 092) Character '✼' Symbol - QChar(0x273d), // Hex No. 5D (Dec 093) Character '✽' Symbol - QChar(0x273e), // Hex No. 5E (Dec 094) Character '✾' Symbol - QChar(0x273f), // Hex No. 5F (Dec 095) Character '✿' Symbol - QChar(0x2740), // Hex No. 60 (Dec 096) Character '❀' Symbol - QChar(0x2741), // Hex No. 61 (Dec 097) Character '❁' Symbol - QChar(0x2742), // Hex No. 62 (Dec 098) Character '❂' Symbol - QChar(0x2743), // Hex No. 63 (Dec 099) Character '❃' Symbol - QChar(0x2744), // Hex No. 64 (Dec 100) Character '❄' Symbol - QChar(0x2745), // Hex No. 65 (Dec 101) Character '❅' Symbol - QChar(0x2746), // Hex No. 66 (Dec 102) Character '❆' Symbol - QChar(0x2747), // Hex No. 67 (Dec 103) Character '❇' Symbol - QChar(0x2748), // Hex No. 68 (Dec 104) Character '❈' Symbol - QChar(0x2749), // Hex No. 69 (Dec 105) Character '❉' Symbol - QChar(0x274a), // Hex No. 6A (Dec 106) Character '❊' Symbol - QChar(0x274b), // Hex No. 6B (Dec 107) Character '❋' Symbol - QChar(0x25cf), // Hex No. 6C (Dec 108) Character '●' Symbol - QChar(0x274d), // Hex No. 6D (Dec 109) Character '❍' Symbol - QChar(0x25a0), // Hex No. 6E (Dec 110) Character '■' Symbol - QChar(0x274f), // Hex No. 6F (Dec 111) Character '❏' Symbol - QChar(0x2750), // Hex No. 70 (Dec 112) Character '❐' Symbol - QChar(0x2751), // Hex No. 71 (Dec 113) Character '❑' Symbol - QChar(0x2752), // Hex No. 72 (Dec 114) Character '❒' Symbol - QChar(0x25b2), // Hex No. 73 (Dec 115) Character '▲' Symbol - QChar(0x25bc), // Hex No. 74 (Dec 116) Character '▼' Symbol - QChar(0x25c6), // Hex No. 75 (Dec 117) Character '◆' Symbol - QChar(0x2756), // Hex No. 76 (Dec 118) Character '❖' Symbol - QChar(0x25d7), // Hex No. 77 (Dec 119) Character '◗' Symbol - QChar(0x2758), // Hex No. 78 (Dec 120) Character '❘' Symbol - QChar(0x2759), // Hex No. 79 (Dec 121) Character '❙' Symbol - QChar(0x275a), // Hex No. 7A (Dec 122) Character '❚' Symbol - QChar(0x275b), // Hex No. 7B (Dec 123) Character '❛' Symbol - QChar(0x275c), // Hex No. 7C (Dec 124) Character '❜' Symbol - QChar(0x275d), // Hex No. 7D (Dec 125) Character '❝' Symbol - QChar(0x275e), // Hex No. 7E (Dec 126) Character '❞' Symbol - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 81 (Dec 129) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 82 (Dec 130) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 87 (Dec 135) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 88 (Dec 136) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 89 (Dec 137) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8A (Dec 138) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8B (Dec 139) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8C (Dec 140) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8D (Dec 141) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8E (Dec 142) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 8F (Dec 143) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 90 (Dec 144) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 91 (Dec 145) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 92 (Dec 146) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 93 (Dec 147) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 94 (Dec 148) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 95 (Dec 149) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 96 (Dec 150) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 97 (Dec 151) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 98 (Dec 152) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 99 (Dec 153) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9A (Dec 154) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9B (Dec 155) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9C (Dec 156) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9D (Dec 157) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9E (Dec 158) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x2761), // Hex No. A1 (Dec 161) Character '❡' Symbol - QChar(0x2762), // Hex No. A2 (Dec 162) Character '❢' Symbol - QChar(0x2763), // Hex No. A3 (Dec 163) Character '❣' Symbol - QChar(0x2764), // Hex No. A4 (Dec 164) Character '❤' Symbol - QChar(0x2765), // Hex No. A5 (Dec 165) Character '❥' Symbol - QChar(0x2766), // Hex No. A6 (Dec 166) Character '❦' Symbol - QChar(0x2767), // Hex No. A7 (Dec 167) Character '❧' Symbol - QChar(0x2663), // Hex No. A8 (Dec 168) Character '♣' Symbol - QChar(0x2666), // Hex No. A9 (Dec 169) Character '♦' Symbol - QChar(0x2665), // Hex No. AA (Dec 170) Character '♥' Symbol - QChar(0x2660), // Hex No. AB (Dec 171) Character '♠' Symbol - QChar(0x2460), // Hex No. AC (Dec 172) Character '①' - QChar(0x2461), // Hex No. AD (Dec 173) Character '②' - QChar(0x2462), // Hex No. AE (Dec 174) Character '③' - QChar(0x2463), // Hex No. AF (Dec 175) Character '④' - QChar(0x2464), // Hex No. B0 (Dec 176) Character '⑤' - QChar(0x2465), // Hex No. B1 (Dec 177) Character '⑥' - QChar(0x2466), // Hex No. B2 (Dec 178) Character '⑦' - QChar(0x2467), // Hex No. B3 (Dec 179) Character '⑧' - QChar(0x2468), // Hex No. B4 (Dec 180) Character '⑨' - QChar(0x2469), // Hex No. B5 (Dec 181) Character '⑩' - QChar(0x2776), // Hex No. B6 (Dec 182) Character '❶' - QChar(0x2777), // Hex No. B7 (Dec 183) Character '❷' - QChar(0x2778), // Hex No. B8 (Dec 184) Character '❸' - QChar(0x2779), // Hex No. B9 (Dec 185) Character '❹' - QChar(0x277a), // Hex No. BA (Dec 186) Character '❺' - QChar(0x277b), // Hex No. BB (Dec 187) Character '❻' - QChar(0x277c), // Hex No. BC (Dec 188) Character '❼' - QChar(0x277d), // Hex No. BD (Dec 189) Character '❽' - QChar(0x277e), // Hex No. BE (Dec 190) Character '❾' - QChar(0x277f), // Hex No. BF (Dec 191) Character '❿' - QChar(0x2780), // Hex No. C0 (Dec 192) Character '➀' - QChar(0x2781), // Hex No. C1 (Dec 193) Character '➁' - QChar(0x2782), // Hex No. C2 (Dec 194) Character '➂' - QChar(0x2783), // Hex No. C3 (Dec 195) Character '➃' - QChar(0x2784), // Hex No. C4 (Dec 196) Character '➄' - QChar(0x2785), // Hex No. C5 (Dec 197) Character '➅' - QChar(0x2786), // Hex No. C6 (Dec 198) Character '➆' - QChar(0x2787), // Hex No. C7 (Dec 199) Character '➇' - QChar(0x2788), // Hex No. C8 (Dec 200) Character '➈' - QChar(0x2789), // Hex No. C9 (Dec 201) Character '➉' - QChar(0x278a), // Hex No. CA (Dec 202) Character '➊' - QChar(0x278b), // Hex No. CB (Dec 203) Character '➋' - QChar(0x278c), // Hex No. CC (Dec 204) Character '➌' - QChar(0x278d), // Hex No. CD (Dec 205) Character '➍' - QChar(0x278e), // Hex No. CE (Dec 206) Character '➎' - QChar(0x278f), // Hex No. CF (Dec 207) Character '➏' - QChar(0x2790), // Hex No. D0 (Dec 208) Character '➐' - QChar(0x2791), // Hex No. D1 (Dec 209) Character '➑' - QChar(0x2792), // Hex No. D2 (Dec 210) Character '➒' - QChar(0x2793), // Hex No. D3 (Dec 211) Character '➓' - QChar(0x2794), // Hex No. D4 (Dec 212) Character '➔' Symbol - QChar(0x2192), // Hex No. D5 (Dec 213) Character '→' Symbol - QChar(0x2194), // Hex No. D6 (Dec 214) Character '↔' Symbol - QChar(0x2195), // Hex No. D7 (Dec 215) Character '↕' Symbol - QChar(0x2798), // Hex No. D8 (Dec 216) Character '➘' Symbol - QChar(0x2799), // Hex No. D9 (Dec 217) Character '➙' Symbol - QChar(0x279a), // Hex No. DA (Dec 218) Character '➚' Symbol - QChar(0x279b), // Hex No. DB (Dec 219) Character '➛' Symbol - QChar(0x279c), // Hex No. DC (Dec 220) Character '➜' Symbol - QChar(0x279d), // Hex No. DD (Dec 221) Character '➝' Symbol - QChar(0x279e), // Hex No. DE (Dec 222) Character '➞' Symbol - QChar(0x279f), // Hex No. DF (Dec 223) Character '➟' Symbol - QChar(0x27a0), // Hex No. E0 (Dec 224) Character '➠' Symbol - QChar(0x27a1), // Hex No. E1 (Dec 225) Character '➡' Symbol - QChar(0x27a2), // Hex No. E2 (Dec 226) Character '➢' Symbol - QChar(0x27a3), // Hex No. E3 (Dec 227) Character '➣' Symbol - QChar(0x27a4), // Hex No. E4 (Dec 228) Character '➤' Symbol - QChar(0x27a5), // Hex No. E5 (Dec 229) Character '➥' Symbol - QChar(0x27a6), // Hex No. E6 (Dec 230) Character '➦' Symbol - QChar(0x27a7), // Hex No. E7 (Dec 231) Character '➧' Symbol - QChar(0x27a8), // Hex No. E8 (Dec 232) Character '➨' Symbol - QChar(0x27a9), // Hex No. E9 (Dec 233) Character '➩' Symbol - QChar(0x27aa), // Hex No. EA (Dec 234) Character '➪' Symbol - QChar(0x27ab), // Hex No. EB (Dec 235) Character '➫' Symbol - QChar(0x27ac), // Hex No. EC (Dec 236) Character '➬' Symbol - QChar(0x27ad), // Hex No. ED (Dec 237) Character '➭' Symbol - QChar(0x27ae), // Hex No. EE (Dec 238) Character '➮' Symbol - QChar(0x27af), // Hex No. EF (Dec 239) Character '➯' Symbol - QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x27b1), // Hex No. F1 (Dec 241) Character '➱' Symbol - QChar(0x27b2), // Hex No. F2 (Dec 242) Character '➲' Symbol - QChar(0x27b3), // Hex No. F3 (Dec 243) Character '➳' Symbol - QChar(0x27b4), // Hex No. F4 (Dec 244) Character '➴' Symbol - QChar(0x27b5), // Hex No. F5 (Dec 245) Character '➵' Symbol - QChar(0x27b6), // Hex No. F6 (Dec 246) Character '➶' Symbol - QChar(0x27b7), // Hex No. F7 (Dec 247) Character '➷' Symbol - QChar(0x27b8), // Hex No. F8 (Dec 248) Character '➸' Symbol - QChar(0x27b9), // Hex No. F9 (Dec 249) Character '➹' Symbol - QChar(0x27ba), // Hex No. FA (Dec 250) Character '➺' Symbol - QChar(0x27bb), // Hex No. FB (Dec 251) Character '➻' Symbol - QChar(0x27bc), // Hex No. FC (Dec 252) Character '➼' Symbol - QChar(0x27bd), // Hex No. FD (Dec 253) Character '➽' Symbol - QChar(0x27be), // Hex No. FE (Dec 254) Character '➾' Symbol - QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set -}; - -// Mac OS encoding -static const EncodingTable MAC_OS_ENCODING_CONVERSION_TABLE = { - QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace - QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation - QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation - QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation - QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol - QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation - QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation - QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation - QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation - QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation - QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation - QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol - QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation - QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation - QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation - QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation - QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit - QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit - QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit - QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit - QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit - QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit - QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit - QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit - QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit - QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit - QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation - QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation - QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol - QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol - QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol - QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation - QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation - QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase - QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase - QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase - QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase - QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase - QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase - QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase - QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase - QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase - QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase - QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase - QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase - QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase - QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase - QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase - QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase - QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase - QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase - QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase - QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase - QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase - QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase - QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase - QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase - QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase - QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase - QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation - QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation - QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation - QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol - QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation - QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol - QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase - QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase - QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase - QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase - QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase - QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase - QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase - QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase - QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase - QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase - QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase - QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase - QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase - QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase - QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase - QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase - QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase - QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase - QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase - QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase - QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase - QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase - QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase - QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase - QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase - QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase - QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation - QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol - QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation - QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol - QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set - QChar(0x00c4), // Hex No. 80 (Dec 128) Character 'Ä' Letter, Uppercase - QChar(0x00c5), // Hex No. 81 (Dec 129) Character 'Å' Letter, Uppercase - QChar(0x00c7), // Hex No. 82 (Dec 130) Character 'Ç' Letter, Uppercase - QChar(0x00c9), // Hex No. 83 (Dec 131) Character 'É' Letter, Uppercase - QChar(0x00d1), // Hex No. 84 (Dec 132) Character 'Ñ' Letter, Uppercase - QChar(0x00d6), // Hex No. 85 (Dec 133) Character 'Ö' Letter, Uppercase - QChar(0x00dc), // Hex No. 86 (Dec 134) Character 'Ü' Letter, Uppercase - QChar(0x00e1), // Hex No. 87 (Dec 135) Character 'á' Letter, Lowercase - QChar(0x00e0), // Hex No. 88 (Dec 136) Character 'à' Letter, Lowercase - QChar(0x00e2), // Hex No. 89 (Dec 137) Character 'â' Letter, Lowercase - QChar(0x00e4), // Hex No. 8A (Dec 138) Character 'ä' Letter, Lowercase - QChar(0x00e3), // Hex No. 8B (Dec 139) Character 'ã' Letter, Lowercase - QChar(0x00e5), // Hex No. 8C (Dec 140) Character 'å' Letter, Lowercase - QChar(0x00e7), // Hex No. 8D (Dec 141) Character 'ç' Letter, Lowercase - QChar(0x00e9), // Hex No. 8E (Dec 142) Character 'é' Letter, Lowercase - QChar(0x00e8), // Hex No. 8F (Dec 143) Character 'è' Letter, Lowercase - QChar(0x00ea), // Hex No. 90 (Dec 144) Character 'ê' Letter, Lowercase - QChar(0x00eb), // Hex No. 91 (Dec 145) Character 'ë' Letter, Lowercase - QChar(0x00ed), // Hex No. 92 (Dec 146) Character 'í' Letter, Lowercase - QChar(0x00ec), // Hex No. 93 (Dec 147) Character 'ì' Letter, Lowercase - QChar(0x00ee), // Hex No. 94 (Dec 148) Character 'î' Letter, Lowercase - QChar(0x00ef), // Hex No. 95 (Dec 149) Character 'ï' Letter, Lowercase - QChar(0x00f1), // Hex No. 96 (Dec 150) Character 'ñ' Letter, Lowercase - QChar(0x00f3), // Hex No. 97 (Dec 151) Character 'ó' Letter, Lowercase - QChar(0x00f2), // Hex No. 98 (Dec 152) Character 'ò' Letter, Lowercase - QChar(0x00f4), // Hex No. 99 (Dec 153) Character 'ô' Letter, Lowercase - QChar(0x00f6), // Hex No. 9A (Dec 154) Character 'ö' Letter, Lowercase - QChar(0x00f5), // Hex No. 9B (Dec 155) Character 'õ' Letter, Lowercase - QChar(0x00fa), // Hex No. 9C (Dec 156) Character 'ú' Letter, Lowercase - QChar(0x00f9), // Hex No. 9D (Dec 157) Character 'ù' Letter, Lowercase - QChar(0x00fb), // Hex No. 9E (Dec 158) Character 'û' Letter, Lowercase - QChar(0x00fc), // Hex No. 9F (Dec 159) Character 'ü' Letter, Lowercase - QChar(0x2020), // Hex No. A0 (Dec 160) Character '†' Punctuation - QChar(0x00b0), // Hex No. A1 (Dec 161) Character '°' Symbol - QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol - QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol - QChar(0x00a7), // Hex No. A4 (Dec 164) Character '§' Punctuation - QChar(0x2022), // Hex No. A5 (Dec 165) Character '•' Punctuation - QChar(0x00b6), // Hex No. A6 (Dec 166) Character '¶' Punctuation - QChar(0x00df), // Hex No. A7 (Dec 167) Character 'ß' Letter, Lowercase - QChar(0x00ae), // Hex No. A8 (Dec 168) Character '®' Symbol - QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol - QChar(0x2122), // Hex No. AA (Dec 170) Character '™' Symbol - QChar(0x00b4), // Hex No. AB (Dec 171) Character '´' Symbol - QChar(0x00a8), // Hex No. AC (Dec 172) Character '¨' Symbol - QChar(0x2260), // Hex No. AD (Dec 173) Character '≠' Symbol - QChar(0x00c6), // Hex No. AE (Dec 174) Character 'Æ' Letter, Uppercase - QChar(0x00d8), // Hex No. AF (Dec 175) Character 'Ø' Letter, Uppercase - QChar(0x221e), // Hex No. B0 (Dec 176) Character '∞' Symbol - QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol - QChar(0x2264), // Hex No. B2 (Dec 178) Character '≤' Symbol - QChar(0x2265), // Hex No. B3 (Dec 179) Character '≥' Symbol - QChar(0x00a5), // Hex No. B4 (Dec 180) Character '¥' Symbol - QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase - QChar(0x2202), // Hex No. B6 (Dec 182) Character '∂' Symbol - QChar(0x2211), // Hex No. B7 (Dec 183) Character '∑' Symbol - QChar(0x220f), // Hex No. B8 (Dec 184) Character '∏' Symbol - QChar(0x03c0), // Hex No. B9 (Dec 185) Character 'π' Letter, Lowercase - QChar(0x222b), // Hex No. BA (Dec 186) Character '∫' Symbol - QChar(0x00aa), // Hex No. BB (Dec 187) Character 'ª' Letter - QChar(0x00ba), // Hex No. BC (Dec 188) Character 'º' Letter - QChar(0x2126), // Hex No. BD (Dec 189) Character 'Ω' Letter, Uppercase - QChar(0x00e6), // Hex No. BE (Dec 190) Character 'æ' Letter, Lowercase - QChar(0x00f8), // Hex No. BF (Dec 191) Character 'ø' Letter, Lowercase - QChar(0x00bf), // Hex No. C0 (Dec 192) Character '¿' Punctuation - QChar(0x00a1), // Hex No. C1 (Dec 193) Character '¡' Punctuation - QChar(0x00ac), // Hex No. C2 (Dec 194) Character '¬' Symbol - QChar(0x221a), // Hex No. C3 (Dec 195) Character '√' Symbol - QChar(0x0192), // Hex No. C4 (Dec 196) Character 'ƒ' Letter, Lowercase - QChar(0x2248), // Hex No. C5 (Dec 197) Character '≈' Symbol - QChar(0x2206), // Hex No. C6 (Dec 198) Character '∆' Symbol - QChar(0x00ab), // Hex No. C7 (Dec 199) Character '«' Punctuation - QChar(0x00bb), // Hex No. C8 (Dec 200) Character '»' Punctuation - QChar(0x2026), // Hex No. C9 (Dec 201) Character '…' Punctuation - QChar(0x0020), // Hex No. CA (Dec 202) Character ' ' Whitespace - QChar(0x00c0), // Hex No. CB (Dec 203) Character 'À' Letter, Uppercase - QChar(0x00c3), // Hex No. CC (Dec 204) Character 'Ã' Letter, Uppercase - QChar(0x00d5), // Hex No. CD (Dec 205) Character 'Õ' Letter, Uppercase - QChar(0x0152), // Hex No. CE (Dec 206) Character 'Œ' Letter, Uppercase - QChar(0x0153), // Hex No. CF (Dec 207) Character 'œ' Letter, Lowercase - QChar(0x2013), // Hex No. D0 (Dec 208) Character '–' Punctuation - QChar(0x2014), // Hex No. D1 (Dec 209) Character '—' Punctuation - QChar(0x201c), // Hex No. D2 (Dec 210) Character '“' Punctuation - QChar(0x201d), // Hex No. D3 (Dec 211) Character '”' Punctuation - QChar(0x2018), // Hex No. D4 (Dec 212) Character '‘' Punctuation - QChar(0x2019), // Hex No. D5 (Dec 213) Character '’' Punctuation - QChar(0x00f7), // Hex No. D6 (Dec 214) Character '÷' Symbol - QChar(0x25ca), // Hex No. D7 (Dec 215) Character '◊' Symbol - QChar(0x00ff), // Hex No. D8 (Dec 216) Character 'ÿ' Letter, Lowercase - QChar(0x0178), // Hex No. D9 (Dec 217) Character 'Ÿ' Letter, Uppercase - QChar(0x2044), // Hex No. DA (Dec 218) Character '⁄' Symbol - QChar(0x20ac), // Hex No. DB (Dec 219) Character '€' Symbol !!! REPLACED FOR MAC OS - QChar(0x2039), // Hex No. DC (Dec 220) Character '‹' Punctuation - QChar(0x203a), // Hex No. DD (Dec 221) Character '›' Punctuation - QChar(0xfb01), // Hex No. DE (Dec 222) Character 'fi' Letter, Lowercase - QChar(0xfb02), // Hex No. DF (Dec 223) Character 'fl' Letter, Lowercase - QChar(0x2021), // Hex No. E0 (Dec 224) Character '‡' Punctuation - QChar(0x00b7), // Hex No. E1 (Dec 225) Character '·' Punctuation - QChar(0x201a), // Hex No. E2 (Dec 226) Character '‚' Punctuation - QChar(0x201e), // Hex No. E3 (Dec 227) Character '„' Punctuation - QChar(0x2030), // Hex No. E4 (Dec 228) Character '‰' Punctuation - QChar(0x00c2), // Hex No. E5 (Dec 229) Character 'Â' Letter, Uppercase - QChar(0x00ca), // Hex No. E6 (Dec 230) Character 'Ê' Letter, Uppercase - QChar(0x00c1), // Hex No. E7 (Dec 231) Character 'Á' Letter, Uppercase - QChar(0x00cb), // Hex No. E8 (Dec 232) Character 'Ë' Letter, Uppercase - QChar(0x00c8), // Hex No. E9 (Dec 233) Character 'È' Letter, Uppercase - QChar(0x00cd), // Hex No. EA (Dec 234) Character 'Í' Letter, Uppercase - QChar(0x00ce), // Hex No. EB (Dec 235) Character 'Î' Letter, Uppercase - QChar(0x00cf), // Hex No. EC (Dec 236) Character 'Ï' Letter, Uppercase - QChar(0x00cc), // Hex No. ED (Dec 237) Character 'Ì' Letter, Uppercase - QChar(0x00d3), // Hex No. EE (Dec 238) Character 'Ó' Letter, Uppercase - QChar(0x00d4), // Hex No. EF (Dec 239) Character 'Ô' Letter, Uppercase - QChar(0xf8ff), // Hex No. F0 (Dec 240) - QChar(0x00d2), // Hex No. F1 (Dec 241) Character 'Ò' Letter, Uppercase - QChar(0x00da), // Hex No. F2 (Dec 242) Character 'Ú' Letter, Uppercase - QChar(0x00db), // Hex No. F3 (Dec 243) Character 'Û' Letter, Uppercase - QChar(0x00d9), // Hex No. F4 (Dec 244) Character 'Ù' Letter, Uppercase - QChar(0x0131), // Hex No. F5 (Dec 245) Character 'ı' Letter, Lowercase - QChar(0x02c6), // Hex No. F6 (Dec 246) Character 'ˆ' Letter - QChar(0x02dc), // Hex No. F7 (Dec 247) Character '˜' Symbol - QChar(0x00af), // Hex No. F8 (Dec 248) Character '¯' Symbol - QChar(0x02d8), // Hex No. F9 (Dec 249) Character '˘' Symbol - QChar(0x02d9), // Hex No. FA (Dec 250) Character '˙' Symbol - QChar(0x02da), // Hex No. FB (Dec 251) Character '˚' Symbol - QChar(0x00b8), // Hex No. FC (Dec 252) Character '¸' Symbol - QChar(0x02dd), // Hex No. FD (Dec 253) Character '˝' Symbol - QChar(0x02db), // Hex No. FE (Dec 254) Character '˛' Symbol - QChar(0x02c7), // Hex No. FF (Dec 255) Character 'ˇ' Letter -}; - -} // namespace encoding - -QString PDFEncoding::convert(const QByteArray& stream, PDFEncoding::Encoding encoding) -{ - const encoding::EncodingTable* table = getTableForEncoding(encoding); - Q_ASSERT(table); - - // Test by assert, than table has enough items for encoded byte stream - Q_ASSERT(table->size() == std::numeric_limits::max() + 1); - - const int size = stream.size(); - const char* data = stream.constData(); - - QString result; - result.resize(size, QChar()); - - for (int i = 0; i < size; ++i) - { - result[i] = (*table)[static_cast(data[i])]; - } - - return result; -} - -QByteArray PDFEncoding::convertToEncoding(const QString& string, Encoding encoding) -{ - QByteArray result; - - const encoding::EncodingTable* table = getTableForEncoding(encoding); - Q_ASSERT(table); - - result.reserve(string.size()); - for (QChar character : string) - { - ushort unicode = character.unicode(); - unsigned char converted = 0; - - for (int i = 0; i < table->size(); ++i) - { - if (unicode == (*table)[static_cast(i)]) - { - converted = i; - break; - } - } - - result.push_back(converted); - } - - return result; -} - -bool PDFEncoding::canConvertToEncoding(const QString& string, Encoding encoding, QString* invalidCharacters) -{ - const encoding::EncodingTable* table = getTableForEncoding(encoding); - Q_ASSERT(table); - - bool isConvertible = true; - for (QChar character : string) - { - ushort unicode = character.unicode(); - bool converted = false; - - for (int i = 0; i < table->size(); ++i) - { - if (unicode == (*table)[static_cast(i)]) - { - converted = true; - break; - } - } - - if (!converted) - { - isConvertible = false; - - if (!invalidCharacters) - { - // We are not storing invalid characters - we can break on first not convertible - // character. - break; - } - - *invalidCharacters += character; - } - } - - return isConvertible; -} - -bool PDFEncoding::canConvertFromEncoding(const QByteArray& stream, Encoding encoding) -{ - const encoding::EncodingTable* table = getTableForEncoding(encoding); - for (const unsigned char index : stream) - { - QChar character = (*table)[index]; - if (character == QChar(0xfffd)) - { - return false; - } - } - - return true; -} - -QString PDFEncoding::convertTextString(const QByteArray& stream) -{ - if (hasUnicodeLeadMarkings(stream)) - { - return convertFromUnicode(stream); - } - else if (hasUTF8LeadMarkings(stream)) - { - return QString::fromUtf8(stream); - } - else - { - return convert(stream, Encoding::PDFDoc); - } -} - -QString PDFEncoding::convertFromUnicode(const QByteArray& stream) -{ - const ushort* bytes = reinterpret_cast(stream.data()); - const int sizeInChars = stream.size(); - const int sizeSizeInUShorts = sizeInChars / sizeof(const ushort) * sizeof(char); - - return QString::fromUtf16(bytes, sizeSizeInUShorts); -} - -QDateTime PDFEncoding::convertToDateTime(const QByteArray& stream) -{ - // According to the specification, string has form: - // (D:YYYYMMDDHHmmSSOHH'mm'), where - // YYYY - year (0000-9999) - // MM - month (1-12) - // DD - day (01-31) - // HH - hour (00-23) - // mm - minute (00-59) - // SS - second (00-59) - // O - 'Z' or '+' or '-' means zero offset / positive offset / negative offset - // HH' - hour offset - // mm' - minute offset - - auto it = stream.cbegin(); - auto itEnd = stream.cend(); - - constexpr const char* PREFIX = "D:"; - if (stream.startsWith(PREFIX)) - { - std::advance(it, std::strlen(PREFIX)); - } - - auto readInteger = [&it, &itEnd](int size, int defaultValue) -> int - { - const int remaining = std::distance(it, itEnd); - if (size <= remaining) - { - int value = 0; - - for (int i = 0; i < size; ++i) - { - const char currentChar = *it++; - if (std::isdigit(static_cast(currentChar))) - { - value = value * 10 + currentChar - '0'; - } - else - { - // This means error - digit is supposed to be here - return -1; - } - } - - return value; - } - - // No remaining part, use default value - return defaultValue; - }; - - int year = readInteger(4, 0); - int month = readInteger(2, 1); - int day = readInteger(2, 1); - int hour = readInteger(2, 0); - int minute = readInteger(2, 0); - int second = readInteger(2, 0); - bool negative = it != itEnd && *it++ == '-'; - int hourOffset = readInteger(2, 0); - if (it != itEnd) - { - // Skip ' character - ++it; - } - int minuteOffset = readInteger(2, 0); - - const int offset = hourOffset * 3600 + minuteOffset * 60; - - QDate parsedDate(year, month, day); - QTime parsedTime(hour, minute, second); - QTimeZone parsedTimeZone(negative ? -offset : offset); - - if (parsedDate.isValid() && parsedTime.isValid() && parsedTimeZone.isValid()) - { - return QDateTime(parsedDate, parsedTime, parsedTimeZone); - } - - return QDateTime(); -} - -QByteArray PDFEncoding::convertDateTimeToString(QDateTime dateTime) -{ - QDateTime utcDateTime = dateTime.toUTC(); - QString convertedDateTime = QString("D:%1").arg(utcDateTime.toString("yyyyMMddhhmmss")); - return convertedDateTime.toLatin1(); -} - -const encoding::EncodingTable* PDFEncoding::getTableForEncoding(Encoding encoding) -{ - switch (encoding) - { - case Encoding::Standard: - return &pdf::encoding::STANDARD_ENCODING_CONVERSION_TABLE; - - case Encoding::MacRoman: - return &pdf::encoding::MAC_ROMAN_ENCODING_CONVERSION_TABLE; - - case Encoding::WinAnsi: - return &pdf::encoding::WIN_ANSI_ENCODING_CONVERSION_TABLE; - - case Encoding::PDFDoc: - return &pdf::encoding::PDF_DOC_ENCODING_CONVERSION_TABLE; - - case Encoding::MacExpert: - return &pdf::encoding::MAC_EXPERT_ENCODING_CONVERSION_TABLE; - - case Encoding::Symbol: - return &pdf::encoding::SYMBOL_SET_ENCODING_CONVERSION_TABLE; - - case Encoding::ZapfDingbats: - return &pdf::encoding::ZAPF_DINGBATS_ENCODING_CONVERSION_TABLE; - - case Encoding::MacOsRoman: - return &pdf::encoding::MAC_OS_ENCODING_CONVERSION_TABLE; - } - - // Unknown encoding? - Q_ASSERT(false); - return nullptr; -} - -QString PDFEncoding::convertSmartFromByteStringToUnicode(const QByteArray& stream, bool* isBinary) -{ - if (isBinary) - { - *isBinary = false; - } - - if (hasUnicodeLeadMarkings(stream)) - { - QTextCodec::ConverterState state = { }; - - { - QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); - QString text = codec->toUnicode(stream.constData(), stream.length(), &state); - if (state.invalidChars == 0) - { - return text; - } - } - - { - QTextCodec* codec = QTextCodec::codecForName("UTF-16LE"); - QString text = codec->toUnicode(stream.constData(), stream.length(), &state); - if (state.invalidChars == 0) - { - return text; - } - } - } - - if (hasUTF8LeadMarkings(stream)) - { - QTextCodec::ConverterState state = { }; - - QTextCodec* codec = QTextCodec::codecForName("UTF-8"); - QString text = codec->toUnicode(stream.constData(), stream.length(), &state); - if (state.invalidChars == 0) - { - return text; - } - } - - if (canConvertFromEncoding(stream, Encoding::PDFDoc)) - { - return convert(stream, Encoding::PDFDoc); - } - - if (isBinary) - { - *isBinary = true; - } - return QString::fromLatin1(stream.toHex()).toUpper(); -} - -QString PDFEncoding::convertSmartFromByteStringToRepresentableQString(const QByteArray& stream) -{ - if (stream.startsWith("D:")) - { - QDateTime dateTime = convertToDateTime(stream); - if (dateTime.isValid()) - { - return dateTime.toString(Qt::TextDate); - } - } - - bool isBinary = false; - QString text = convertSmartFromByteStringToUnicode(stream, &isBinary); - - if (!isBinary) - { - return text; - } - - return stream.toPercentEncoding(" ", QByteArray(), '%'); -} - -QString PDFEncoding::getEncodingCharacters(Encoding encoding) -{ - QString string; - - if (const encoding::EncodingTable* table = getTableForEncoding(encoding)) - { - for (const QChar& character : *table) - { - if (character != QChar(0xFFFD)) - { - string += character; - } - } - } - - return string; -} - -QByteArray PDFEncoding::getPrintableCharacters() -{ - QByteArray result; - - const char min = std::numeric_limits::min(); - const char max = std::numeric_limits::max(); - for (char i = min; i < max; ++i) - { - if (std::isprint(static_cast(i))) - { - result.push_back(i); - } - } - - return result; -} - -bool PDFEncoding::hasUnicodeLeadMarkings(const QByteArray& stream) -{ - if (stream.size() >= 2) - { - if (static_cast(stream[0]) == 0xFE && static_cast(stream[1]) == 0xFF) - { - // UTF 16-BE - return true; - } - if (static_cast(stream[0]) == 0xFF && static_cast(stream[1]) == 0xFE) - { - // UTF 16-LE, forbidden in PDF 2.0 standard, but used in some PDF producers (wrongly) - return true; - } - } - - return false; -} - -bool PDFEncoding::hasUTF8LeadMarkings(const QByteArray& stream) -{ - if (stream.size() >= 3) - { - if (static_cast(stream[0]) == 239 && - static_cast(stream[1]) == 187 && - static_cast(stream[2]) == 191) - { - // UTF-8 - return true; - } - } - - return false; -} - -} // namespace pdf +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfencoding.h" + +#include +#include + +#include + +namespace pdf +{ + +namespace encoding +{ + +// PDF Reference 1.7, Appendix D, Section D.1, StandardEncoding +static const EncodingTable STANDARD_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation + QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation + QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation + QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol + QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation + QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation + QChar(0x2019), // Hex No. 27 (Dec 039) Character '’' Punctuation + QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation + QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation + QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation + QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation + QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit + QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit + QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit + QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit + QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit + QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit + QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit + QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit + QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit + QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol + QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol + QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol + QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation + QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation + QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase + QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase + QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase + QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase + QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase + QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase + QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase + QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase + QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase + QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase + QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase + QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase + QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase + QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase + QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase + QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase + QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase + QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase + QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase + QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase + QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase + QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase + QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase + QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase + QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase + QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase + QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation + QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation + QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation + QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol + QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation + QChar(0x2018), // Hex No. 60 (Dec 096) Character '‘' Punctuation + QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase + QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase + QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase + QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase + QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase + QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase + QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase + QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase + QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase + QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase + QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase + QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase + QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase + QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase + QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase + QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase + QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase + QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase + QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase + QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase + QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase + QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase + QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase + QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase + QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase + QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase + QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation + QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol + QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation + QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 81 (Dec 129) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 82 (Dec 130) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 87 (Dec 135) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 88 (Dec 136) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 89 (Dec 137) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8A (Dec 138) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8B (Dec 139) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8C (Dec 140) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8D (Dec 141) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8E (Dec 142) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8F (Dec 143) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 90 (Dec 144) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 91 (Dec 145) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 92 (Dec 146) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 93 (Dec 147) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 94 (Dec 148) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 95 (Dec 149) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 96 (Dec 150) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 97 (Dec 151) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 98 (Dec 152) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 99 (Dec 153) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9A (Dec 154) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9B (Dec 155) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9C (Dec 156) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9D (Dec 157) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9E (Dec 158) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00a1), // Hex No. A1 (Dec 161) Character '¡' Punctuation + QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol + QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol + QChar(0x2044), // Hex No. A4 (Dec 164) Character '⁄' Symbol + QChar(0x00a5), // Hex No. A5 (Dec 165) Character '¥' Symbol + QChar(0x0192), // Hex No. A6 (Dec 166) Character 'ƒ' Letter, Lowercase + QChar(0x00a7), // Hex No. A7 (Dec 167) Character '§' Punctuation + QChar(0x00a4), // Hex No. A8 (Dec 168) Character '¤' Symbol + QChar(0x0027), // Hex No. A9 (Dec 169) Character ''' Punctuation + QChar(0x201c), // Hex No. AA (Dec 170) Character '“' Punctuation + QChar(0x00ab), // Hex No. AB (Dec 171) Character '«' Punctuation + QChar(0x2039), // Hex No. AC (Dec 172) Character '‹' Punctuation + QChar(0x203a), // Hex No. AD (Dec 173) Character '›' Punctuation + QChar(0xfb01), // Hex No. AE (Dec 174) Character 'fi' Letter, Lowercase + QChar(0xfb02), // Hex No. AF (Dec 175) Character 'fl' Letter, Lowercase + QChar(0xfffd), // Hex No. B0 (Dec 176) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x2013), // Hex No. B1 (Dec 177) Character '–' Punctuation + QChar(0x2020), // Hex No. B2 (Dec 178) Character '†' Punctuation + QChar(0x2021), // Hex No. B3 (Dec 179) Character '‡' Punctuation + QChar(0x00b7), // Hex No. B4 (Dec 180) Character '·' Punctuation + QChar(0xfffd), // Hex No. B5 (Dec 181) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00b6), // Hex No. B6 (Dec 182) Character '¶' Punctuation + QChar(0x2022), // Hex No. B7 (Dec 183) Character '•' Punctuation + QChar(0x201a), // Hex No. B8 (Dec 184) Character '‚' Punctuation + QChar(0x201e), // Hex No. B9 (Dec 185) Character '„' Punctuation + QChar(0x201d), // Hex No. BA (Dec 186) Character '”' Punctuation + QChar(0x00bb), // Hex No. BB (Dec 187) Character '»' Punctuation + QChar(0x2026), // Hex No. BC (Dec 188) Character '…' Punctuation + QChar(0x2030), // Hex No. BD (Dec 189) Character '‰' Punctuation + QChar(0xfffd), // Hex No. BE (Dec 190) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00bf), // Hex No. BF (Dec 191) Character '¿' Punctuation + QChar(0xfffd), // Hex No. C0 (Dec 192) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0060), // Hex No. C1 (Dec 193) Character '`' Symbol + QChar(0x00b4), // Hex No. C2 (Dec 194) Character '´' Symbol + QChar(0x02c6), // Hex No. C3 (Dec 195) Character 'ˆ' Letter + QChar(0x02dc), // Hex No. C4 (Dec 196) Character '˜' Symbol + QChar(0x00af), // Hex No. C5 (Dec 197) Character '¯' Symbol + QChar(0x02d8), // Hex No. C6 (Dec 198) Character '˘' Symbol + QChar(0x02d9), // Hex No. C7 (Dec 199) Character '˙' Symbol + QChar(0x00a8), // Hex No. C8 (Dec 200) Character '¨' Symbol + QChar(0xfffd), // Hex No. C9 (Dec 201) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x02da), // Hex No. CA (Dec 202) Character '˚' Symbol + QChar(0x00b8), // Hex No. CB (Dec 203) Character '¸' Symbol + QChar(0xfffd), // Hex No. CC (Dec 204) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x02dd), // Hex No. CD (Dec 205) Character '˝' Symbol + QChar(0x02db), // Hex No. CE (Dec 206) Character '˛' Symbol + QChar(0x02c7), // Hex No. CF (Dec 207) Character 'ˇ' Letter + QChar(0x2014), // Hex No. D0 (Dec 208) Character '—' Punctuation + QChar(0xfffd), // Hex No. D1 (Dec 209) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D2 (Dec 210) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D3 (Dec 211) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D4 (Dec 212) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D5 (Dec 213) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D6 (Dec 214) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D7 (Dec 215) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D8 (Dec 216) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D9 (Dec 217) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. DA (Dec 218) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. DB (Dec 219) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. DC (Dec 220) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. DD (Dec 221) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. DE (Dec 222) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. DF (Dec 223) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. E0 (Dec 224) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00c6), // Hex No. E1 (Dec 225) Character 'Æ' Letter, Uppercase + QChar(0xfffd), // Hex No. E2 (Dec 226) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00aa), // Hex No. E3 (Dec 227) Character 'ª' Letter + QChar(0xfffd), // Hex No. E4 (Dec 228) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. E5 (Dec 229) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. E6 (Dec 230) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. E7 (Dec 231) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0141), // Hex No. E8 (Dec 232) Character 'Ł' Letter, Uppercase + QChar(0x00d8), // Hex No. E9 (Dec 233) Character 'Ø' Letter, Uppercase + QChar(0x0152), // Hex No. EA (Dec 234) Character 'Œ' Letter, Uppercase + QChar(0x00ba), // Hex No. EB (Dec 235) Character 'º' Letter + QChar(0xfffd), // Hex No. EC (Dec 236) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. ED (Dec 237) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. EE (Dec 238) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. EF (Dec 239) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00e6), // Hex No. F1 (Dec 241) Character 'æ' Letter, Lowercase + QChar(0xfffd), // Hex No. F2 (Dec 242) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. F3 (Dec 243) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. F4 (Dec 244) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0131), // Hex No. F5 (Dec 245) Character 'ı' Letter, Lowercase + QChar(0xfffd), // Hex No. F6 (Dec 246) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. F7 (Dec 247) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0142), // Hex No. F8 (Dec 248) Character 'ł' Letter, Lowercase + QChar(0x00f8), // Hex No. F9 (Dec 249) Character 'ø' Letter, Lowercase + QChar(0x0153), // Hex No. FA (Dec 250) Character 'œ' Letter, Lowercase + QChar(0x00df), // Hex No. FB (Dec 251) Character 'ß' Letter, Lowercase + QChar(0xfffd), // Hex No. FC (Dec 252) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. FD (Dec 253) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. FE (Dec 254) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set +}; + +// PDF Reference 1.7, Appendix D, Section D.1, MacRomanEncoding +static const EncodingTable MAC_ROMAN_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation + QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation + QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation + QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol + QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation + QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation + QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation + QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation + QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation + QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation + QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation + QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit + QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit + QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit + QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit + QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit + QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit + QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit + QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit + QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit + QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol + QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol + QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol + QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation + QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation + QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase + QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase + QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase + QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase + QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase + QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase + QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase + QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase + QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase + QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase + QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase + QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase + QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase + QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase + QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase + QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase + QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase + QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase + QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase + QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase + QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase + QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase + QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase + QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase + QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase + QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase + QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation + QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation + QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation + QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol + QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation + QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol + QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase + QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase + QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase + QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase + QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase + QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase + QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase + QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase + QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase + QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase + QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase + QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase + QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase + QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase + QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase + QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase + QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase + QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase + QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase + QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase + QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase + QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase + QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase + QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase + QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase + QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase + QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation + QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol + QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation + QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00c4), // Hex No. 80 (Dec 128) Character 'Ä' Letter, Uppercase + QChar(0x00c5), // Hex No. 81 (Dec 129) Character 'Å' Letter, Uppercase + QChar(0x00c7), // Hex No. 82 (Dec 130) Character 'Ç' Letter, Uppercase + QChar(0x00c9), // Hex No. 83 (Dec 131) Character 'É' Letter, Uppercase + QChar(0x00d1), // Hex No. 84 (Dec 132) Character 'Ñ' Letter, Uppercase + QChar(0x00d6), // Hex No. 85 (Dec 133) Character 'Ö' Letter, Uppercase + QChar(0x00dc), // Hex No. 86 (Dec 134) Character 'Ü' Letter, Uppercase + QChar(0x00e1), // Hex No. 87 (Dec 135) Character 'á' Letter, Lowercase + QChar(0x00e0), // Hex No. 88 (Dec 136) Character 'à' Letter, Lowercase + QChar(0x00e2), // Hex No. 89 (Dec 137) Character 'â' Letter, Lowercase + QChar(0x00e4), // Hex No. 8A (Dec 138) Character 'ä' Letter, Lowercase + QChar(0x00e3), // Hex No. 8B (Dec 139) Character 'ã' Letter, Lowercase + QChar(0x00e5), // Hex No. 8C (Dec 140) Character 'å' Letter, Lowercase + QChar(0x00e7), // Hex No. 8D (Dec 141) Character 'ç' Letter, Lowercase + QChar(0x00e9), // Hex No. 8E (Dec 142) Character 'é' Letter, Lowercase + QChar(0x00e8), // Hex No. 8F (Dec 143) Character 'è' Letter, Lowercase + QChar(0x00ea), // Hex No. 90 (Dec 144) Character 'ê' Letter, Lowercase + QChar(0x00eb), // Hex No. 91 (Dec 145) Character 'ë' Letter, Lowercase + QChar(0x00ed), // Hex No. 92 (Dec 146) Character 'í' Letter, Lowercase + QChar(0x00ec), // Hex No. 93 (Dec 147) Character 'ì' Letter, Lowercase + QChar(0x00ee), // Hex No. 94 (Dec 148) Character 'î' Letter, Lowercase + QChar(0x00ef), // Hex No. 95 (Dec 149) Character 'ï' Letter, Lowercase + QChar(0x00f1), // Hex No. 96 (Dec 150) Character 'ñ' Letter, Lowercase + QChar(0x00f3), // Hex No. 97 (Dec 151) Character 'ó' Letter, Lowercase + QChar(0x00f2), // Hex No. 98 (Dec 152) Character 'ò' Letter, Lowercase + QChar(0x00f4), // Hex No. 99 (Dec 153) Character 'ô' Letter, Lowercase + QChar(0x00f6), // Hex No. 9A (Dec 154) Character 'ö' Letter, Lowercase + QChar(0x00f5), // Hex No. 9B (Dec 155) Character 'õ' Letter, Lowercase + QChar(0x00fa), // Hex No. 9C (Dec 156) Character 'ú' Letter, Lowercase + QChar(0x00f9), // Hex No. 9D (Dec 157) Character 'ù' Letter, Lowercase + QChar(0x00fb), // Hex No. 9E (Dec 158) Character 'û' Letter, Lowercase + QChar(0x00fc), // Hex No. 9F (Dec 159) Character 'ü' Letter, Lowercase + QChar(0x2020), // Hex No. A0 (Dec 160) Character '†' Punctuation + QChar(0x00b0), // Hex No. A1 (Dec 161) Character '°' Symbol + QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol + QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol + QChar(0x00a7), // Hex No. A4 (Dec 164) Character '§' Punctuation + QChar(0x2022), // Hex No. A5 (Dec 165) Character '•' Punctuation + QChar(0x00b6), // Hex No. A6 (Dec 166) Character '¶' Punctuation + QChar(0x00df), // Hex No. A7 (Dec 167) Character 'ß' Letter, Lowercase + QChar(0x00ae), // Hex No. A8 (Dec 168) Character '®' Symbol + QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol + QChar(0x2122), // Hex No. AA (Dec 170) Character '™' Symbol + QChar(0x00b4), // Hex No. AB (Dec 171) Character '´' Symbol + QChar(0x00a8), // Hex No. AC (Dec 172) Character '¨' Symbol + QChar(0x2260), // Hex No. AD (Dec 173) Character '≠' Symbol + QChar(0x00c6), // Hex No. AE (Dec 174) Character 'Æ' Letter, Uppercase + QChar(0x00d8), // Hex No. AF (Dec 175) Character 'Ø' Letter, Uppercase + QChar(0x221e), // Hex No. B0 (Dec 176) Character '∞' Symbol + QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol + QChar(0x2264), // Hex No. B2 (Dec 178) Character '≤' Symbol + QChar(0x2265), // Hex No. B3 (Dec 179) Character '≥' Symbol + QChar(0x00a5), // Hex No. B4 (Dec 180) Character '¥' Symbol + QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase + QChar(0x2202), // Hex No. B6 (Dec 182) Character '∂' Symbol + QChar(0x2211), // Hex No. B7 (Dec 183) Character '∑' Symbol + QChar(0x220f), // Hex No. B8 (Dec 184) Character '∏' Symbol + QChar(0x03c0), // Hex No. B9 (Dec 185) Character 'π' Letter, Lowercase + QChar(0x222b), // Hex No. BA (Dec 186) Character '∫' Symbol + QChar(0x00aa), // Hex No. BB (Dec 187) Character 'ª' Letter + QChar(0x00ba), // Hex No. BC (Dec 188) Character 'º' Letter + QChar(0x2126), // Hex No. BD (Dec 189) Character 'Ω' Letter, Uppercase + QChar(0x00e6), // Hex No. BE (Dec 190) Character 'æ' Letter, Lowercase + QChar(0x00f8), // Hex No. BF (Dec 191) Character 'ø' Letter, Lowercase + QChar(0x00bf), // Hex No. C0 (Dec 192) Character '¿' Punctuation + QChar(0x00a1), // Hex No. C1 (Dec 193) Character '¡' Punctuation + QChar(0x00ac), // Hex No. C2 (Dec 194) Character '¬' Symbol + QChar(0x221a), // Hex No. C3 (Dec 195) Character '√' Symbol + QChar(0x0192), // Hex No. C4 (Dec 196) Character 'ƒ' Letter, Lowercase + QChar(0x2248), // Hex No. C5 (Dec 197) Character '≈' Symbol + QChar(0x2206), // Hex No. C6 (Dec 198) Character '∆' Symbol + QChar(0x00ab), // Hex No. C7 (Dec 199) Character '«' Punctuation + QChar(0x00bb), // Hex No. C8 (Dec 200) Character '»' Punctuation + QChar(0x2026), // Hex No. C9 (Dec 201) Character '…' Punctuation + QChar(0x0020), // Hex No. CA (Dec 202) Character ' ' Whitespace + QChar(0x00c0), // Hex No. CB (Dec 203) Character 'À' Letter, Uppercase + QChar(0x00c3), // Hex No. CC (Dec 204) Character 'Ã' Letter, Uppercase + QChar(0x00d5), // Hex No. CD (Dec 205) Character 'Õ' Letter, Uppercase + QChar(0x0152), // Hex No. CE (Dec 206) Character 'Œ' Letter, Uppercase + QChar(0x0153), // Hex No. CF (Dec 207) Character 'œ' Letter, Lowercase + QChar(0x2013), // Hex No. D0 (Dec 208) Character '–' Punctuation + QChar(0x2014), // Hex No. D1 (Dec 209) Character '—' Punctuation + QChar(0x201c), // Hex No. D2 (Dec 210) Character '“' Punctuation + QChar(0x201d), // Hex No. D3 (Dec 211) Character '”' Punctuation + QChar(0x2018), // Hex No. D4 (Dec 212) Character '‘' Punctuation + QChar(0x2019), // Hex No. D5 (Dec 213) Character '’' Punctuation + QChar(0x00f7), // Hex No. D6 (Dec 214) Character '÷' Symbol + QChar(0x25ca), // Hex No. D7 (Dec 215) Character '◊' Symbol + QChar(0x00ff), // Hex No. D8 (Dec 216) Character 'ÿ' Letter, Lowercase + QChar(0x0178), // Hex No. D9 (Dec 217) Character 'Ÿ' Letter, Uppercase + QChar(0x2044), // Hex No. DA (Dec 218) Character '⁄' Symbol + QChar(0x00a4), // Hex No. DB (Dec 219) Character '¤' Symbol + QChar(0x2039), // Hex No. DC (Dec 220) Character '‹' Punctuation + QChar(0x203a), // Hex No. DD (Dec 221) Character '›' Punctuation + QChar(0xfb01), // Hex No. DE (Dec 222) Character 'fi' Letter, Lowercase + QChar(0xfb02), // Hex No. DF (Dec 223) Character 'fl' Letter, Lowercase + QChar(0x2021), // Hex No. E0 (Dec 224) Character '‡' Punctuation + QChar(0x00b7), // Hex No. E1 (Dec 225) Character '·' Punctuation + QChar(0x201a), // Hex No. E2 (Dec 226) Character '‚' Punctuation + QChar(0x201e), // Hex No. E3 (Dec 227) Character '„' Punctuation + QChar(0x2030), // Hex No. E4 (Dec 228) Character '‰' Punctuation + QChar(0x00c2), // Hex No. E5 (Dec 229) Character 'Â' Letter, Uppercase + QChar(0x00ca), // Hex No. E6 (Dec 230) Character 'Ê' Letter, Uppercase + QChar(0x00c1), // Hex No. E7 (Dec 231) Character 'Á' Letter, Uppercase + QChar(0x00cb), // Hex No. E8 (Dec 232) Character 'Ë' Letter, Uppercase + QChar(0x00c8), // Hex No. E9 (Dec 233) Character 'È' Letter, Uppercase + QChar(0x00cd), // Hex No. EA (Dec 234) Character 'Í' Letter, Uppercase + QChar(0x00ce), // Hex No. EB (Dec 235) Character 'Î' Letter, Uppercase + QChar(0x00cf), // Hex No. EC (Dec 236) Character 'Ï' Letter, Uppercase + QChar(0x00cc), // Hex No. ED (Dec 237) Character 'Ì' Letter, Uppercase + QChar(0x00d3), // Hex No. EE (Dec 238) Character 'Ó' Letter, Uppercase + QChar(0x00d4), // Hex No. EF (Dec 239) Character 'Ô' Letter, Uppercase + QChar(0xf8ff), // Hex No. F0 (Dec 240) + QChar(0x00d2), // Hex No. F1 (Dec 241) Character 'Ò' Letter, Uppercase + QChar(0x00da), // Hex No. F2 (Dec 242) Character 'Ú' Letter, Uppercase + QChar(0x00db), // Hex No. F3 (Dec 243) Character 'Û' Letter, Uppercase + QChar(0x00d9), // Hex No. F4 (Dec 244) Character 'Ù' Letter, Uppercase + QChar(0x0131), // Hex No. F5 (Dec 245) Character 'ı' Letter, Lowercase + QChar(0x02c6), // Hex No. F6 (Dec 246) Character 'ˆ' Letter + QChar(0x02dc), // Hex No. F7 (Dec 247) Character '˜' Symbol + QChar(0x00af), // Hex No. F8 (Dec 248) Character '¯' Symbol + QChar(0x02d8), // Hex No. F9 (Dec 249) Character '˘' Symbol + QChar(0x02d9), // Hex No. FA (Dec 250) Character '˙' Symbol + QChar(0x02da), // Hex No. FB (Dec 251) Character '˚' Symbol + QChar(0x00b8), // Hex No. FC (Dec 252) Character '¸' Symbol + QChar(0x02dd), // Hex No. FD (Dec 253) Character '˝' Symbol + QChar(0x02db), // Hex No. FE (Dec 254) Character '˛' Symbol + QChar(0x02c7), // Hex No. FF (Dec 255) Character 'ˇ' Letter +}; + +// PDF Reference 1.7, Appendix D, Section D.1, WinAnsiEncoding +static const EncodingTable WIN_ANSI_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation + QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation + QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation + QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol + QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation + QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation + QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation + QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation + QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation + QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation + QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation + QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit + QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit + QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit + QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit + QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit + QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit + QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit + QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit + QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit + QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol + QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol + QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol + QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation + QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation + QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase + QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase + QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase + QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase + QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase + QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase + QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase + QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase + QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase + QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase + QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase + QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase + QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase + QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase + QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase + QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase + QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase + QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase + QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase + QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase + QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase + QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase + QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase + QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase + QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase + QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase + QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation + QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation + QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation + QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol + QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation + QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol + QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase + QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase + QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase + QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase + QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase + QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase + QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase + QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase + QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase + QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase + QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase + QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase + QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase + QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase + QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase + QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase + QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase + QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase + QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase + QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase + QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase + QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase + QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase + QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase + QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase + QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase + QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation + QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol + QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation + QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol + QChar(0x2022), // Hex No. 7F (Dec 127) Character '•' Punctuation + QChar(0x20ac), // Hex No. 80 (Dec 128) Character '€' Symbol + QChar(0x2022), // Hex No. 81 (Dec 129) Character '•' Punctuation + QChar(0x201a), // Hex No. 82 (Dec 130) Character '‚' Punctuation + QChar(0x0192), // Hex No. 83 (Dec 131) Character 'ƒ' Letter, Lowercase + QChar(0x201e), // Hex No. 84 (Dec 132) Character '„' Punctuation + QChar(0x2026), // Hex No. 85 (Dec 133) Character '…' Punctuation + QChar(0x2020), // Hex No. 86 (Dec 134) Character '†' Punctuation + QChar(0x2021), // Hex No. 87 (Dec 135) Character '‡' Punctuation + QChar(0x02c6), // Hex No. 88 (Dec 136) Character 'ˆ' Letter + QChar(0x2030), // Hex No. 89 (Dec 137) Character '‰' Punctuation + QChar(0x0160), // Hex No. 8A (Dec 138) Character 'Š' Letter, Uppercase + QChar(0x2039), // Hex No. 8B (Dec 139) Character '‹' Punctuation + QChar(0x0152), // Hex No. 8C (Dec 140) Character 'Œ' Letter, Uppercase + QChar(0x2022), // Hex No. 8D (Dec 141) Character '•' Punctuation + QChar(0x017d), // Hex No. 8E (Dec 142) Character 'Ž' Letter, Uppercase + QChar(0x2022), // Hex No. 8F (Dec 143) Character '•' Punctuation + QChar(0x2022), // Hex No. 90 (Dec 144) Character '•' Punctuation + QChar(0x2018), // Hex No. 91 (Dec 145) Character '‘' Punctuation + QChar(0x2019), // Hex No. 92 (Dec 146) Character '’' Punctuation + QChar(0x201c), // Hex No. 93 (Dec 147) Character '“' Punctuation + QChar(0x201d), // Hex No. 94 (Dec 148) Character '”' Punctuation + QChar(0x2022), // Hex No. 95 (Dec 149) Character '•' Punctuation + QChar(0x2013), // Hex No. 96 (Dec 150) Character '–' Punctuation + QChar(0x2014), // Hex No. 97 (Dec 151) Character '—' Punctuation + QChar(0x02dc), // Hex No. 98 (Dec 152) Character '˜' Symbol + QChar(0x2122), // Hex No. 99 (Dec 153) Character '™' Symbol + QChar(0x0161), // Hex No. 9A (Dec 154) Character 'š' Letter, Lowercase + QChar(0x203a), // Hex No. 9B (Dec 155) Character '›' Punctuation + QChar(0x0153), // Hex No. 9C (Dec 156) Character 'œ' Letter, Lowercase + QChar(0x2022), // Hex No. 9D (Dec 157) Character '•' Punctuation + QChar(0x017e), // Hex No. 9E (Dec 158) Character 'ž' Letter, Lowercase + QChar(0x0178), // Hex No. 9F (Dec 159) Character 'Ÿ' Letter, Uppercase + QChar(0x0020), // Hex No. A0 (Dec 160) Character ' ' Whitespace + QChar(0x00a1), // Hex No. A1 (Dec 161) Character '¡' Punctuation + QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol + QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol + QChar(0x00a4), // Hex No. A4 (Dec 164) Character '¤' Symbol + QChar(0x00a5), // Hex No. A5 (Dec 165) Character '¥' Symbol + QChar(0x00a6), // Hex No. A6 (Dec 166) Character '¦' Symbol + QChar(0x00a7), // Hex No. A7 (Dec 167) Character '§' Punctuation + QChar(0x00a8), // Hex No. A8 (Dec 168) Character '¨' Symbol + QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol + QChar(0x00aa), // Hex No. AA (Dec 170) Character 'ª' Letter + QChar(0x00ab), // Hex No. AB (Dec 171) Character '«' Punctuation + QChar(0x00ac), // Hex No. AC (Dec 172) Character '¬' Symbol + QChar(0x002d), // Hex No. AD (Dec 173) Character '-' Punctuation + QChar(0x00ae), // Hex No. AE (Dec 174) Character '®' Symbol + QChar(0x00af), // Hex No. AF (Dec 175) Character '¯' Symbol + QChar(0x00b0), // Hex No. B0 (Dec 176) Character '°' Symbol + QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol + QChar(0x00b2), // Hex No. B2 (Dec 178) Character '²' + QChar(0x00b3), // Hex No. B3 (Dec 179) Character '³' + QChar(0x00b4), // Hex No. B4 (Dec 180) Character '´' Symbol + QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase + QChar(0x00b6), // Hex No. B6 (Dec 182) Character '¶' Punctuation + QChar(0x00b7), // Hex No. B7 (Dec 183) Character '·' Punctuation + QChar(0x00b8), // Hex No. B8 (Dec 184) Character '¸' Symbol + QChar(0x00b9), // Hex No. B9 (Dec 185) Character '¹' + QChar(0x00ba), // Hex No. BA (Dec 186) Character 'º' Letter + QChar(0x00bb), // Hex No. BB (Dec 187) Character '»' Punctuation + QChar(0x00bc), // Hex No. BC (Dec 188) Character '¼' + QChar(0x00bd), // Hex No. BD (Dec 189) Character '½' + QChar(0x00be), // Hex No. BE (Dec 190) Character '¾' + QChar(0x00bf), // Hex No. BF (Dec 191) Character '¿' Punctuation + QChar(0x00c0), // Hex No. C0 (Dec 192) Character 'À' Letter, Uppercase + QChar(0x00c1), // Hex No. C1 (Dec 193) Character 'Á' Letter, Uppercase + QChar(0x00c2), // Hex No. C2 (Dec 194) Character 'Â' Letter, Uppercase + QChar(0x00c3), // Hex No. C3 (Dec 195) Character 'Ã' Letter, Uppercase + QChar(0x00c4), // Hex No. C4 (Dec 196) Character 'Ä' Letter, Uppercase + QChar(0x00c5), // Hex No. C5 (Dec 197) Character 'Å' Letter, Uppercase + QChar(0x00c6), // Hex No. C6 (Dec 198) Character 'Æ' Letter, Uppercase + QChar(0x00c7), // Hex No. C7 (Dec 199) Character 'Ç' Letter, Uppercase + QChar(0x00c8), // Hex No. C8 (Dec 200) Character 'È' Letter, Uppercase + QChar(0x00c9), // Hex No. C9 (Dec 201) Character 'É' Letter, Uppercase + QChar(0x00ca), // Hex No. CA (Dec 202) Character 'Ê' Letter, Uppercase + QChar(0x00cb), // Hex No. CB (Dec 203) Character 'Ë' Letter, Uppercase + QChar(0x00cc), // Hex No. CC (Dec 204) Character 'Ì' Letter, Uppercase + QChar(0x00cd), // Hex No. CD (Dec 205) Character 'Í' Letter, Uppercase + QChar(0x00ce), // Hex No. CE (Dec 206) Character 'Î' Letter, Uppercase + QChar(0x00cf), // Hex No. CF (Dec 207) Character 'Ï' Letter, Uppercase + QChar(0x00d0), // Hex No. D0 (Dec 208) Character 'Ð' Letter, Uppercase + QChar(0x00d1), // Hex No. D1 (Dec 209) Character 'Ñ' Letter, Uppercase + QChar(0x00d2), // Hex No. D2 (Dec 210) Character 'Ò' Letter, Uppercase + QChar(0x00d3), // Hex No. D3 (Dec 211) Character 'Ó' Letter, Uppercase + QChar(0x00d4), // Hex No. D4 (Dec 212) Character 'Ô' Letter, Uppercase + QChar(0x00d5), // Hex No. D5 (Dec 213) Character 'Õ' Letter, Uppercase + QChar(0x00d6), // Hex No. D6 (Dec 214) Character 'Ö' Letter, Uppercase + QChar(0x00d7), // Hex No. D7 (Dec 215) Character '×' Symbol + QChar(0x00d8), // Hex No. D8 (Dec 216) Character 'Ø' Letter, Uppercase + QChar(0x00d9), // Hex No. D9 (Dec 217) Character 'Ù' Letter, Uppercase + QChar(0x00da), // Hex No. DA (Dec 218) Character 'Ú' Letter, Uppercase + QChar(0x00db), // Hex No. DB (Dec 219) Character 'Û' Letter, Uppercase + QChar(0x00dc), // Hex No. DC (Dec 220) Character 'Ü' Letter, Uppercase + QChar(0x00dd), // Hex No. DD (Dec 221) Character 'Ý' Letter, Uppercase + QChar(0x00de), // Hex No. DE (Dec 222) Character 'Þ' Letter, Uppercase + QChar(0x00df), // Hex No. DF (Dec 223) Character 'ß' Letter, Lowercase + QChar(0x00e0), // Hex No. E0 (Dec 224) Character 'à' Letter, Lowercase + QChar(0x00e1), // Hex No. E1 (Dec 225) Character 'á' Letter, Lowercase + QChar(0x00e2), // Hex No. E2 (Dec 226) Character 'â' Letter, Lowercase + QChar(0x00e3), // Hex No. E3 (Dec 227) Character 'ã' Letter, Lowercase + QChar(0x00e4), // Hex No. E4 (Dec 228) Character 'ä' Letter, Lowercase + QChar(0x00e5), // Hex No. E5 (Dec 229) Character 'å' Letter, Lowercase + QChar(0x00e6), // Hex No. E6 (Dec 230) Character 'æ' Letter, Lowercase + QChar(0x00e7), // Hex No. E7 (Dec 231) Character 'ç' Letter, Lowercase + QChar(0x00e8), // Hex No. E8 (Dec 232) Character 'è' Letter, Lowercase + QChar(0x00e9), // Hex No. E9 (Dec 233) Character 'é' Letter, Lowercase + QChar(0x00ea), // Hex No. EA (Dec 234) Character 'ê' Letter, Lowercase + QChar(0x00eb), // Hex No. EB (Dec 235) Character 'ë' Letter, Lowercase + QChar(0x00ec), // Hex No. EC (Dec 236) Character 'ì' Letter, Lowercase + QChar(0x00ed), // Hex No. ED (Dec 237) Character 'í' Letter, Lowercase + QChar(0x00ee), // Hex No. EE (Dec 238) Character 'î' Letter, Lowercase + QChar(0x00ef), // Hex No. EF (Dec 239) Character 'ï' Letter, Lowercase + QChar(0x00f0), // Hex No. F0 (Dec 240) Character 'ð' Letter, Lowercase + QChar(0x00f1), // Hex No. F1 (Dec 241) Character 'ñ' Letter, Lowercase + QChar(0x00f2), // Hex No. F2 (Dec 242) Character 'ò' Letter, Lowercase + QChar(0x00f3), // Hex No. F3 (Dec 243) Character 'ó' Letter, Lowercase + QChar(0x00f4), // Hex No. F4 (Dec 244) Character 'ô' Letter, Lowercase + QChar(0x00f5), // Hex No. F5 (Dec 245) Character 'õ' Letter, Lowercase + QChar(0x00f6), // Hex No. F6 (Dec 246) Character 'ö' Letter, Lowercase + QChar(0x00f7), // Hex No. F7 (Dec 247) Character '÷' Symbol + QChar(0x00f8), // Hex No. F8 (Dec 248) Character 'ø' Letter, Lowercase + QChar(0x00f9), // Hex No. F9 (Dec 249) Character 'ù' Letter, Lowercase + QChar(0x00fa), // Hex No. FA (Dec 250) Character 'ú' Letter, Lowercase + QChar(0x00fb), // Hex No. FB (Dec 251) Character 'û' Letter, Lowercase + QChar(0x00fc), // Hex No. FC (Dec 252) Character 'ü' Letter, Lowercase + QChar(0x00fd), // Hex No. FD (Dec 253) Character 'ý' Letter, Lowercase + QChar(0x00fe), // Hex No. FE (Dec 254) Character 'þ' Letter, Lowercase + QChar(0x00ff), // Hex No. FF (Dec 255) Character 'ÿ' Letter, Lowercase +}; + +// PDF Reference 1.7, Appendix D, Section D.1/D.2, PDFDocEncoding +static const EncodingTable PDF_DOC_ENCODING_CONVERSION_TABLE = { + QChar(0x0000), // Hex No. 00 (Dec 000) Null character + QChar(0x0001), // Hex No. 01 (Dec 001) + QChar(0x0002), // Hex No. 02 (Dec 002) + QChar(0x0003), // Hex No. 03 (Dec 003) + QChar(0x0004), // Hex No. 04 (Dec 004) + QChar(0x0005), // Hex No. 05 (Dec 005) + QChar(0x0006), // Hex No. 06 (Dec 006) + QChar(0x0007), // Hex No. 07 (Dec 007) + QChar(0x0008), // Hex No. 08 (Dec 008) + QChar(0x0009), // Hex No. 09 (Dec 009) Whitespace + QChar(0x000a), // Hex No. 0A (Dec 010) Whitespace + QChar(0x000b), // Hex No. 0B (Dec 011) Whitespace + QChar(0x000c), // Hex No. 0C (Dec 012) Whitespace + QChar(0x000d), // Hex No. 0D (Dec 013) Whitespace + QChar(0x000e), // Hex No. 0E (Dec 014) + QChar(0x000f), // Hex No. 0F (Dec 015) + QChar(0x0010), // Hex No. 10 (Dec 016) + QChar(0x0011), // Hex No. 11 (Dec 017) + QChar(0x0012), // Hex No. 12 (Dec 018) + QChar(0x0013), // Hex No. 13 (Dec 019) + QChar(0x0014), // Hex No. 14 (Dec 020) + QChar(0x0015), // Hex No. 15 (Dec 021) + QChar(0x0016), // Hex No. 16 (Dec 022) + QChar(0x0017), // Hex No. 17 (Dec 023) + QChar(0x02d8), // Hex No. 18 (Dec 024) Character '˘' Symbol + QChar(0x02c7), // Hex No. 19 (Dec 025) Character 'ˇ' Letter + QChar(0x02c6), // Hex No. 1A (Dec 026) Character 'ˆ' Letter + QChar(0x02d9), // Hex No. 1B (Dec 027) Character '˙' Symbol + QChar(0x02dd), // Hex No. 1C (Dec 028) Character '˝' Symbol + QChar(0x02db), // Hex No. 1D (Dec 029) Character '˛' Symbol + QChar(0x02da), // Hex No. 1E (Dec 030) Character '˚' Symbol + QChar(0x02dc), // Hex No. 1F (Dec 031) Character '˜' Symbol + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation + QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation + QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation + QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol + QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation + QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation + QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation + QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation + QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation + QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation + QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation + QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit + QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit + QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit + QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit + QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit + QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit + QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit + QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit + QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit + QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol + QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol + QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol + QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation + QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation + QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase + QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase + QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase + QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase + QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase + QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase + QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase + QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase + QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase + QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase + QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase + QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase + QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase + QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase + QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase + QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase + QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase + QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase + QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase + QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase + QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase + QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase + QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase + QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase + QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase + QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase + QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation + QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation + QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation + QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol + QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation + QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol + QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase + QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase + QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase + QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase + QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase + QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase + QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase + QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase + QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase + QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase + QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase + QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase + QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase + QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase + QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase + QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase + QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase + QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase + QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase + QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase + QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase + QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase + QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase + QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase + QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase + QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase + QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation + QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol + QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation + QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x2022), // Hex No. 80 (Dec 128) Character '•' Punctuation + QChar(0x2020), // Hex No. 81 (Dec 129) Character '†' Punctuation + QChar(0x2021), // Hex No. 82 (Dec 130) Character '‡' Punctuation + QChar(0x2026), // Hex No. 83 (Dec 131) Character '…' Punctuation + QChar(0x2014), // Hex No. 84 (Dec 132) Character '—' Punctuation + QChar(0x2013), // Hex No. 85 (Dec 133) Character '–' Punctuation + QChar(0x0192), // Hex No. 86 (Dec 134) Character 'ƒ' Letter, Lowercase + QChar(0x2044), // Hex No. 87 (Dec 135) Character '⁄' Symbol + QChar(0x2039), // Hex No. 88 (Dec 136) Character '‹' Punctuation + QChar(0x203a), // Hex No. 89 (Dec 137) Character '›' Punctuation + QChar(0x2212), // Hex No. 8A (Dec 138) Character '−' Symbol + QChar(0x2030), // Hex No. 8B (Dec 139) Character '‰' Punctuation + QChar(0x201e), // Hex No. 8C (Dec 140) Character '„' Punctuation + QChar(0x201c), // Hex No. 8D (Dec 141) Character '“' Punctuation + QChar(0x201d), // Hex No. 8E (Dec 142) Character '”' Punctuation + QChar(0x2018), // Hex No. 8F (Dec 143) Character '‘' Punctuation + QChar(0x2019), // Hex No. 90 (Dec 144) Character '’' Punctuation + QChar(0x201a), // Hex No. 91 (Dec 145) Character '‚' Punctuation + QChar(0x2122), // Hex No. 92 (Dec 146) Character '™' Symbol + QChar(0xfb01), // Hex No. 93 (Dec 147) Character 'fi' Letter, Lowercase + QChar(0xfb02), // Hex No. 94 (Dec 148) Character 'fl' Letter, Lowercase + QChar(0x0141), // Hex No. 95 (Dec 149) Character 'Ł' Letter, Uppercase + QChar(0x0152), // Hex No. 96 (Dec 150) Character 'Œ' Letter, Uppercase + QChar(0x0160), // Hex No. 97 (Dec 151) Character 'Š' Letter, Uppercase + QChar(0x0178), // Hex No. 98 (Dec 152) Character 'Ÿ' Letter, Uppercase + QChar(0x017d), // Hex No. 99 (Dec 153) Character 'Ž' Letter, Uppercase + QChar(0x0131), // Hex No. 9A (Dec 154) Character 'ı' Letter, Lowercase + QChar(0x0142), // Hex No. 9B (Dec 155) Character 'ł' Letter, Lowercase + QChar(0x0153), // Hex No. 9C (Dec 156) Character 'œ' Letter, Lowercase + QChar(0x0161), // Hex No. 9D (Dec 157) Character 'š' Letter, Lowercase + QChar(0x017e), // Hex No. 9E (Dec 158) Character 'ž' Letter, Lowercase + QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x20ac), // Hex No. A0 (Dec 160) Character '€' Symbol + QChar(0x00a1), // Hex No. A1 (Dec 161) Character '¡' Punctuation + QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol + QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol + QChar(0x00a4), // Hex No. A4 (Dec 164) Character '¤' Symbol + QChar(0x00a5), // Hex No. A5 (Dec 165) Character '¥' Symbol + QChar(0x00a6), // Hex No. A6 (Dec 166) Character '¦' Symbol + QChar(0x00a7), // Hex No. A7 (Dec 167) Character '§' Punctuation + QChar(0x00a8), // Hex No. A8 (Dec 168) Character '¨' Symbol + QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol + QChar(0x00aa), // Hex No. AA (Dec 170) Character 'ª' Letter + QChar(0x00ab), // Hex No. AB (Dec 171) Character '«' Punctuation + QChar(0x00ac), // Hex No. AC (Dec 172) Character '¬' Symbol + QChar(0xfffd), // Hex No. AD (Dec 173) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00ae), // Hex No. AE (Dec 174) Character '®' Symbol + QChar(0x00af), // Hex No. AF (Dec 175) Character '¯' Symbol + QChar(0x00b0), // Hex No. B0 (Dec 176) Character '°' Symbol + QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol + QChar(0x00b2), // Hex No. B2 (Dec 178) Character '²' + QChar(0x00b3), // Hex No. B3 (Dec 179) Character '³' + QChar(0x00b4), // Hex No. B4 (Dec 180) Character '´' Symbol + QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase + QChar(0x00b6), // Hex No. B6 (Dec 182) Character '¶' Punctuation + QChar(0x00b7), // Hex No. B7 (Dec 183) Character '·' Punctuation + QChar(0x00b8), // Hex No. B8 (Dec 184) Character '¸' Symbol + QChar(0x00b9), // Hex No. B9 (Dec 185) Character '¹' + QChar(0x00ba), // Hex No. BA (Dec 186) Character 'º' Letter + QChar(0x00bb), // Hex No. BB (Dec 187) Character '»' Punctuation + QChar(0x00bc), // Hex No. BC (Dec 188) Character '¼' + QChar(0x00bd), // Hex No. BD (Dec 189) Character '½' + QChar(0x00be), // Hex No. BE (Dec 190) Character '¾' + QChar(0x00bf), // Hex No. BF (Dec 191) Character '¿' Punctuation + QChar(0x00c0), // Hex No. C0 (Dec 192) Character 'À' Letter, Uppercase + QChar(0x00c1), // Hex No. C1 (Dec 193) Character 'Á' Letter, Uppercase + QChar(0x00c2), // Hex No. C2 (Dec 194) Character 'Â' Letter, Uppercase + QChar(0x00c3), // Hex No. C3 (Dec 195) Character 'Ã' Letter, Uppercase + QChar(0x00c4), // Hex No. C4 (Dec 196) Character 'Ä' Letter, Uppercase + QChar(0x00c5), // Hex No. C5 (Dec 197) Character 'Å' Letter, Uppercase + QChar(0x00c6), // Hex No. C6 (Dec 198) Character 'Æ' Letter, Uppercase + QChar(0x00c7), // Hex No. C7 (Dec 199) Character 'Ç' Letter, Uppercase + QChar(0x00c8), // Hex No. C8 (Dec 200) Character 'È' Letter, Uppercase + QChar(0x00c9), // Hex No. C9 (Dec 201) Character 'É' Letter, Uppercase + QChar(0x00ca), // Hex No. CA (Dec 202) Character 'Ê' Letter, Uppercase + QChar(0x00cb), // Hex No. CB (Dec 203) Character 'Ë' Letter, Uppercase + QChar(0x00cc), // Hex No. CC (Dec 204) Character 'Ì' Letter, Uppercase + QChar(0x00cd), // Hex No. CD (Dec 205) Character 'Í' Letter, Uppercase + QChar(0x00ce), // Hex No. CE (Dec 206) Character 'Î' Letter, Uppercase + QChar(0x00cf), // Hex No. CF (Dec 207) Character 'Ï' Letter, Uppercase + QChar(0x00d0), // Hex No. D0 (Dec 208) Character 'Ð' Letter, Uppercase + QChar(0x00d1), // Hex No. D1 (Dec 209) Character 'Ñ' Letter, Uppercase + QChar(0x00d2), // Hex No. D2 (Dec 210) Character 'Ò' Letter, Uppercase + QChar(0x00d3), // Hex No. D3 (Dec 211) Character 'Ó' Letter, Uppercase + QChar(0x00d4), // Hex No. D4 (Dec 212) Character 'Ô' Letter, Uppercase + QChar(0x00d5), // Hex No. D5 (Dec 213) Character 'Õ' Letter, Uppercase + QChar(0x00d6), // Hex No. D6 (Dec 214) Character 'Ö' Letter, Uppercase + QChar(0x00d7), // Hex No. D7 (Dec 215) Character '×' Symbol + QChar(0x00d8), // Hex No. D8 (Dec 216) Character 'Ø' Letter, Uppercase + QChar(0x00d9), // Hex No. D9 (Dec 217) Character 'Ù' Letter, Uppercase + QChar(0x00da), // Hex No. DA (Dec 218) Character 'Ú' Letter, Uppercase + QChar(0x00db), // Hex No. DB (Dec 219) Character 'Û' Letter, Uppercase + QChar(0x00dc), // Hex No. DC (Dec 220) Character 'Ü' Letter, Uppercase + QChar(0x00dd), // Hex No. DD (Dec 221) Character 'Ý' Letter, Uppercase + QChar(0x00de), // Hex No. DE (Dec 222) Character 'Þ' Letter, Uppercase + QChar(0x00df), // Hex No. DF (Dec 223) Character 'ß' Letter, Lowercase + QChar(0x00e0), // Hex No. E0 (Dec 224) Character 'à' Letter, Lowercase + QChar(0x00e1), // Hex No. E1 (Dec 225) Character 'á' Letter, Lowercase + QChar(0x00e2), // Hex No. E2 (Dec 226) Character 'â' Letter, Lowercase + QChar(0x00e3), // Hex No. E3 (Dec 227) Character 'ã' Letter, Lowercase + QChar(0x00e4), // Hex No. E4 (Dec 228) Character 'ä' Letter, Lowercase + QChar(0x00e5), // Hex No. E5 (Dec 229) Character 'å' Letter, Lowercase + QChar(0x00e6), // Hex No. E6 (Dec 230) Character 'æ' Letter, Lowercase + QChar(0x00e7), // Hex No. E7 (Dec 231) Character 'ç' Letter, Lowercase + QChar(0x00e8), // Hex No. E8 (Dec 232) Character 'è' Letter, Lowercase + QChar(0x00e9), // Hex No. E9 (Dec 233) Character 'é' Letter, Lowercase + QChar(0x00ea), // Hex No. EA (Dec 234) Character 'ê' Letter, Lowercase + QChar(0x00eb), // Hex No. EB (Dec 235) Character 'ë' Letter, Lowercase + QChar(0x00ec), // Hex No. EC (Dec 236) Character 'ì' Letter, Lowercase + QChar(0x00ed), // Hex No. ED (Dec 237) Character 'í' Letter, Lowercase + QChar(0x00ee), // Hex No. EE (Dec 238) Character 'î' Letter, Lowercase + QChar(0x00ef), // Hex No. EF (Dec 239) Character 'ï' Letter, Lowercase + QChar(0x00f0), // Hex No. F0 (Dec 240) Character 'ð' Letter, Lowercase + QChar(0x00f1), // Hex No. F1 (Dec 241) Character 'ñ' Letter, Lowercase + QChar(0x00f2), // Hex No. F2 (Dec 242) Character 'ò' Letter, Lowercase + QChar(0x00f3), // Hex No. F3 (Dec 243) Character 'ó' Letter, Lowercase + QChar(0x00f4), // Hex No. F4 (Dec 244) Character 'ô' Letter, Lowercase + QChar(0x00f5), // Hex No. F5 (Dec 245) Character 'õ' Letter, Lowercase + QChar(0x00f6), // Hex No. F6 (Dec 246) Character 'ö' Letter, Lowercase + QChar(0x00f7), // Hex No. F7 (Dec 247) Character '÷' Symbol + QChar(0x00f8), // Hex No. F8 (Dec 248) Character 'ø' Letter, Lowercase + QChar(0x00f9), // Hex No. F9 (Dec 249) Character 'ù' Letter, Lowercase + QChar(0x00fa), // Hex No. FA (Dec 250) Character 'ú' Letter, Lowercase + QChar(0x00fb), // Hex No. FB (Dec 251) Character 'û' Letter, Lowercase + QChar(0x00fc), // Hex No. FC (Dec 252) Character 'ü' Letter, Lowercase + QChar(0x00fd), // Hex No. FD (Dec 253) Character 'ý' Letter, Lowercase + QChar(0x00fe), // Hex No. FE (Dec 254) Character 'þ' Letter, Lowercase + QChar(0x00ff), // Hex No. FF (Dec 255) Character 'ÿ' Letter, Lowercase +}; + +// PDF Reference 1.7, Appendix D, Section D.3, MacExpertEncoding +static const EncodingTable MAC_EXPERT_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0xf721), // Hex No. 21 (Dec 033) + QChar(0xf6f8), // Hex No. 22 (Dec 034) + QChar(0xf7a2), // Hex No. 23 (Dec 035) + QChar(0xf724), // Hex No. 24 (Dec 036) + QChar(0xf6e4), // Hex No. 25 (Dec 037) + QChar(0xf726), // Hex No. 26 (Dec 038) + QChar(0xf7b4), // Hex No. 27 (Dec 039) + QChar(0x207d), // Hex No. 28 (Dec 040) Character '⁽' Punctuation + QChar(0x207e), // Hex No. 29 (Dec 041) Character '⁾' Punctuation + QChar(0x2025), // Hex No. 2A (Dec 042) Character '‥' Punctuation + QChar(0x2024), // Hex No. 2B (Dec 043) Character '․' Punctuation + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x2044), // Hex No. 2F (Dec 047) Character '⁄' Symbol + QChar(0xf730), // Hex No. 30 (Dec 048) + QChar(0xf731), // Hex No. 31 (Dec 049) + QChar(0xf732), // Hex No. 32 (Dec 050) + QChar(0xf733), // Hex No. 33 (Dec 051) + QChar(0xf734), // Hex No. 34 (Dec 052) + QChar(0xf735), // Hex No. 35 (Dec 053) + QChar(0xf736), // Hex No. 36 (Dec 054) + QChar(0xf737), // Hex No. 37 (Dec 055) + QChar(0xf738), // Hex No. 38 (Dec 056) + QChar(0xf739), // Hex No. 39 (Dec 057) + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0xfffd), // Hex No. 3C (Dec 060) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6de), // Hex No. 3D (Dec 061) + QChar(0xfffd), // Hex No. 3E (Dec 062) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf73f), // Hex No. 3F (Dec 063) + QChar(0xfffd), // Hex No. 40 (Dec 064) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 41 (Dec 065) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 42 (Dec 066) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 43 (Dec 067) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7f0), // Hex No. 44 (Dec 068) + QChar(0xfffd), // Hex No. 45 (Dec 069) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 46 (Dec 070) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00bc), // Hex No. 47 (Dec 071) Character '¼' + QChar(0x00bd), // Hex No. 48 (Dec 072) Character '½' + QChar(0x00be), // Hex No. 49 (Dec 073) Character '¾' + QChar(0x215b), // Hex No. 4A (Dec 074) Character '⅛' + QChar(0x215c), // Hex No. 4B (Dec 075) Character '⅜' + QChar(0x215d), // Hex No. 4C (Dec 076) Character '⅝' + QChar(0x215e), // Hex No. 4D (Dec 077) Character '⅞' + QChar(0x2153), // Hex No. 4E (Dec 078) Character '⅓' + QChar(0x2154), // Hex No. 4F (Dec 079) Character '⅔' + QChar(0xfffd), // Hex No. 50 (Dec 080) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 51 (Dec 081) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 52 (Dec 082) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 53 (Dec 083) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 54 (Dec 084) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 55 (Dec 085) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfb00), // Hex No. 56 (Dec 086) Character 'ff' Letter, Lowercase + QChar(0xfb01), // Hex No. 57 (Dec 087) Character 'fi' Letter, Lowercase + QChar(0xfb02), // Hex No. 58 (Dec 088) Character 'fl' Letter, Lowercase + QChar(0xfb03), // Hex No. 59 (Dec 089) Character 'ffi' Letter, Lowercase + QChar(0xfb04), // Hex No. 5A (Dec 090) Character 'ffl' Letter, Lowercase + QChar(0x208d), // Hex No. 5B (Dec 091) Character '₍' Punctuation + QChar(0xfffd), // Hex No. 5C (Dec 092) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x208e), // Hex No. 5D (Dec 093) Character '₎' Punctuation + QChar(0xf6f6), // Hex No. 5E (Dec 094) + QChar(0xf6e5), // Hex No. 5F (Dec 095) + QChar(0xf760), // Hex No. 60 (Dec 096) + QChar(0xf761), // Hex No. 61 (Dec 097) + QChar(0xf762), // Hex No. 62 (Dec 098) + QChar(0xf763), // Hex No. 63 (Dec 099) + QChar(0xf764), // Hex No. 64 (Dec 100) + QChar(0xf765), // Hex No. 65 (Dec 101) + QChar(0xf766), // Hex No. 66 (Dec 102) + QChar(0xf767), // Hex No. 67 (Dec 103) + QChar(0xf768), // Hex No. 68 (Dec 104) + QChar(0xf769), // Hex No. 69 (Dec 105) + QChar(0xf76a), // Hex No. 6A (Dec 106) + QChar(0xf76b), // Hex No. 6B (Dec 107) + QChar(0xf76c), // Hex No. 6C (Dec 108) + QChar(0xf76d), // Hex No. 6D (Dec 109) + QChar(0xf76e), // Hex No. 6E (Dec 110) + QChar(0xf76f), // Hex No. 6F (Dec 111) + QChar(0xf770), // Hex No. 70 (Dec 112) + QChar(0xf771), // Hex No. 71 (Dec 113) + QChar(0xf772), // Hex No. 72 (Dec 114) + QChar(0xf773), // Hex No. 73 (Dec 115) + QChar(0xf774), // Hex No. 74 (Dec 116) + QChar(0xf775), // Hex No. 75 (Dec 117) + QChar(0xf776), // Hex No. 76 (Dec 118) + QChar(0xf777), // Hex No. 77 (Dec 119) + QChar(0xf778), // Hex No. 78 (Dec 120) + QChar(0xf779), // Hex No. 79 (Dec 121) + QChar(0xf77a), // Hex No. 7A (Dec 122) + QChar(0x20a1), // Hex No. 7B (Dec 123) Character '₡' Symbol + QChar(0xf6dc), // Hex No. 7C (Dec 124) + QChar(0xf6dd), // Hex No. 7D (Dec 125) + QChar(0xf6fe), // Hex No. 7E (Dec 126) + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6e9), // Hex No. 81 (Dec 129) + QChar(0xf6e0), // Hex No. 82 (Dec 130) + QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7e1), // Hex No. 87 (Dec 135) + QChar(0xf7e0), // Hex No. 88 (Dec 136) + QChar(0xf7e2), // Hex No. 89 (Dec 137) + QChar(0xf7e4), // Hex No. 8A (Dec 138) + QChar(0xf7e3), // Hex No. 8B (Dec 139) + QChar(0xf7e5), // Hex No. 8C (Dec 140) + QChar(0xf7e7), // Hex No. 8D (Dec 141) + QChar(0xf7e9), // Hex No. 8E (Dec 142) + QChar(0xf7e8), // Hex No. 8F (Dec 143) + QChar(0xf7ea), // Hex No. 90 (Dec 144) + QChar(0xf7eb), // Hex No. 91 (Dec 145) + QChar(0xf7ed), // Hex No. 92 (Dec 146) + QChar(0xf7ec), // Hex No. 93 (Dec 147) + QChar(0xf7ee), // Hex No. 94 (Dec 148) + QChar(0xf7ef), // Hex No. 95 (Dec 149) + QChar(0xf7f1), // Hex No. 96 (Dec 150) + QChar(0xf7f3), // Hex No. 97 (Dec 151) + QChar(0xf7f2), // Hex No. 98 (Dec 152) + QChar(0xf7f4), // Hex No. 99 (Dec 153) + QChar(0xf7f6), // Hex No. 9A (Dec 154) + QChar(0xf7f5), // Hex No. 9B (Dec 155) + QChar(0xf7fa), // Hex No. 9C (Dec 156) + QChar(0xf7f9), // Hex No. 9D (Dec 157) + QChar(0xf7fb), // Hex No. 9E (Dec 158) + QChar(0xf7fc), // Hex No. 9F (Dec 159) + QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x2078), // Hex No. A1 (Dec 161) Character '⁸' + QChar(0x2084), // Hex No. A2 (Dec 162) Character '₄' + QChar(0x2083), // Hex No. A3 (Dec 163) Character '₃' + QChar(0x2086), // Hex No. A4 (Dec 164) Character '₆' + QChar(0x2088), // Hex No. A5 (Dec 165) Character '₈' + QChar(0x2087), // Hex No. A6 (Dec 166) Character '₇' + QChar(0xf6fd), // Hex No. A7 (Dec 167) + QChar(0xfffd), // Hex No. A8 (Dec 168) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6df), // Hex No. A9 (Dec 169) + QChar(0x2082), // Hex No. AA (Dec 170) Character '₂' + QChar(0xfffd), // Hex No. AB (Dec 171) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7a8), // Hex No. AC (Dec 172) + QChar(0xfffd), // Hex No. AD (Dec 173) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6f5), // Hex No. AE (Dec 174) + QChar(0xf6f0), // Hex No. AF (Dec 175) + QChar(0x2085), // Hex No. B0 (Dec 176) Character '₅' + QChar(0xfffd), // Hex No. B1 (Dec 177) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6e1), // Hex No. B2 (Dec 178) + QChar(0xf6e7), // Hex No. B3 (Dec 179) + QChar(0xf7fd), // Hex No. B4 (Dec 180) + QChar(0xfffd), // Hex No. B5 (Dec 181) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6e3), // Hex No. B6 (Dec 182) + QChar(0xfffd), // Hex No. B7 (Dec 183) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. B8 (Dec 184) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7fe), // Hex No. B9 (Dec 185) + QChar(0xfffd), // Hex No. BA (Dec 186) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x2089), // Hex No. BB (Dec 187) Character '₉' + QChar(0x2080), // Hex No. BC (Dec 188) Character '₀' + QChar(0xf6ff), // Hex No. BD (Dec 189) + QChar(0xf7e6), // Hex No. BE (Dec 190) + QChar(0xf7f8), // Hex No. BF (Dec 191) + QChar(0xf7bf), // Hex No. C0 (Dec 192) + QChar(0x2081), // Hex No. C1 (Dec 193) Character '₁' + QChar(0xf6f9), // Hex No. C2 (Dec 194) + QChar(0xfffd), // Hex No. C3 (Dec 195) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. C4 (Dec 196) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. C5 (Dec 197) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. C6 (Dec 198) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. C7 (Dec 199) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. C8 (Dec 200) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7b8), // Hex No. C9 (Dec 201) + QChar(0xfffd), // Hex No. CA (Dec 202) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. CB (Dec 203) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. CC (Dec 204) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. CD (Dec 205) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. CE (Dec 206) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6fa), // Hex No. CF (Dec 207) + QChar(0x2012), // Hex No. D0 (Dec 208) Character '‒' Punctuation + QChar(0xf6e6), // Hex No. D1 (Dec 209) + QChar(0xfffd), // Hex No. D2 (Dec 210) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D3 (Dec 211) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D4 (Dec 212) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. D5 (Dec 213) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7a1), // Hex No. D6 (Dec 214) + QChar(0xfffd), // Hex No. D7 (Dec 215) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf7ff), // Hex No. D8 (Dec 216) + QChar(0xfffd), // Hex No. D9 (Dec 217) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00b9), // Hex No. DA (Dec 218) Character '¹' + QChar(0x00b2), // Hex No. DB (Dec 219) Character '²' + QChar(0x00b3), // Hex No. DC (Dec 220) Character '³' + QChar(0x2074), // Hex No. DD (Dec 221) Character '⁴' + QChar(0x2075), // Hex No. DE (Dec 222) Character '⁵' + QChar(0x2076), // Hex No. DF (Dec 223) Character '⁶' + QChar(0x2077), // Hex No. E0 (Dec 224) Character '⁷' + QChar(0x2079), // Hex No. E1 (Dec 225) Character '⁹' + QChar(0x2070), // Hex No. E2 (Dec 226) Character '⁰' + QChar(0xfffd), // Hex No. E3 (Dec 227) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6ec), // Hex No. E4 (Dec 228) + QChar(0xf6f1), // Hex No. E5 (Dec 229) + QChar(0xf6f3), // Hex No. E6 (Dec 230) + QChar(0xfffd), // Hex No. E7 (Dec 231) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. E8 (Dec 232) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6ed), // Hex No. E9 (Dec 233) + QChar(0xf6f2), // Hex No. EA (Dec 234) + QChar(0xf6eb), // Hex No. EB (Dec 235) + QChar(0xfffd), // Hex No. EC (Dec 236) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. ED (Dec 237) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. EE (Dec 238) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. EF (Dec 239) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xf6ee), // Hex No. F1 (Dec 241) + QChar(0xf6fb), // Hex No. F2 (Dec 242) + QChar(0xf6f4), // Hex No. F3 (Dec 243) + QChar(0xf7af), // Hex No. F4 (Dec 244) + QChar(0xf6ea), // Hex No. F5 (Dec 245) + QChar(0x207f), // Hex No. F6 (Dec 246) Character 'ⁿ' Letter + QChar(0xf6ef), // Hex No. F7 (Dec 247) + QChar(0xf6e2), // Hex No. F8 (Dec 248) + QChar(0xf6e8), // Hex No. F9 (Dec 249) + QChar(0xf6f7), // Hex No. FA (Dec 250) + QChar(0xf6fc), // Hex No. FB (Dec 251) + QChar(0xfffd), // Hex No. FC (Dec 252) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. FD (Dec 253) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. FE (Dec 254) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set +}; + +// PDF Reference 1.7, Appendix D, Section D.4, Symbol Set and Encoding +static const EncodingTable SYMBOL_SET_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation + QChar(0x2200), // Hex No. 22 (Dec 034) Character '∀' Symbol + QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation + QChar(0x2203), // Hex No. 24 (Dec 036) Character '∃' Symbol + QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation + QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation + QChar(0x220b), // Hex No. 27 (Dec 039) Character '∋' Symbol + QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation + QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation + QChar(0x2217), // Hex No. 2A (Dec 042) Character '∗' Symbol + QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x2212), // Hex No. 2D (Dec 045) Character '−' Symbol + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation + QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit + QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit + QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit + QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit + QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit + QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit + QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit + QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit + QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit + QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol + QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol + QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol + QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation + QChar(0x2245), // Hex No. 40 (Dec 064) Character '≅' Symbol + QChar(0x0391), // Hex No. 41 (Dec 065) Character 'Α' Letter, Uppercase + QChar(0x0392), // Hex No. 42 (Dec 066) Character 'Β' Letter, Uppercase + QChar(0x03a7), // Hex No. 43 (Dec 067) Character 'Χ' Letter, Uppercase + QChar(0x2206), // Hex No. 44 (Dec 068) Character '∆' Symbol + QChar(0x0395), // Hex No. 45 (Dec 069) Character 'Ε' Letter, Uppercase + QChar(0x03a6), // Hex No. 46 (Dec 070) Character 'Φ' Letter, Uppercase + QChar(0x0393), // Hex No. 47 (Dec 071) Character 'Γ' Letter, Uppercase + QChar(0x0397), // Hex No. 48 (Dec 072) Character 'Η' Letter, Uppercase + QChar(0x0399), // Hex No. 49 (Dec 073) Character 'Ι' Letter, Uppercase + QChar(0x03d1), // Hex No. 4A (Dec 074) Character 'ϑ' Letter, Lowercase + QChar(0x039a), // Hex No. 4B (Dec 075) Character 'Κ' Letter, Uppercase + QChar(0x039b), // Hex No. 4C (Dec 076) Character 'Λ' Letter, Uppercase + QChar(0x039c), // Hex No. 4D (Dec 077) Character 'Μ' Letter, Uppercase + QChar(0x039d), // Hex No. 4E (Dec 078) Character 'Ν' Letter, Uppercase + QChar(0x039f), // Hex No. 4F (Dec 079) Character 'Ο' Letter, Uppercase + QChar(0x03a0), // Hex No. 50 (Dec 080) Character 'Π' Letter, Uppercase + QChar(0x0398), // Hex No. 51 (Dec 081) Character 'Θ' Letter, Uppercase + QChar(0x03a1), // Hex No. 52 (Dec 082) Character 'Ρ' Letter, Uppercase + QChar(0x03a3), // Hex No. 53 (Dec 083) Character 'Σ' Letter, Uppercase + QChar(0x03a4), // Hex No. 54 (Dec 084) Character 'Τ' Letter, Uppercase + QChar(0x03a5), // Hex No. 55 (Dec 085) Character 'Υ' Letter, Uppercase + QChar(0x03c2), // Hex No. 56 (Dec 086) Character 'ς' Letter, Lowercase + QChar(0x2126), // Hex No. 57 (Dec 087) Character 'Ω' Letter, Uppercase + QChar(0x039e), // Hex No. 58 (Dec 088) Character 'Ξ' Letter, Uppercase + QChar(0x03a8), // Hex No. 59 (Dec 089) Character 'Ψ' Letter, Uppercase + QChar(0x0396), // Hex No. 5A (Dec 090) Character 'Ζ' Letter, Uppercase + QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation + QChar(0x2234), // Hex No. 5C (Dec 092) Character '∴' Symbol + QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation + QChar(0x22a5), // Hex No. 5E (Dec 094) Character '⊥' Symbol + QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation + QChar(0xf8e5), // Hex No. 60 (Dec 096) + QChar(0x03b1), // Hex No. 61 (Dec 097) Character 'α' Letter, Lowercase + QChar(0x03b2), // Hex No. 62 (Dec 098) Character 'β' Letter, Lowercase + QChar(0x03c7), // Hex No. 63 (Dec 099) Character 'χ' Letter, Lowercase + QChar(0x03b4), // Hex No. 64 (Dec 100) Character 'δ' Letter, Lowercase + QChar(0x03b5), // Hex No. 65 (Dec 101) Character 'ε' Letter, Lowercase + QChar(0x03c6), // Hex No. 66 (Dec 102) Character 'φ' Letter, Lowercase + QChar(0x03b3), // Hex No. 67 (Dec 103) Character 'γ' Letter, Lowercase + QChar(0x03b7), // Hex No. 68 (Dec 104) Character 'η' Letter, Lowercase + QChar(0x03b9), // Hex No. 69 (Dec 105) Character 'ι' Letter, Lowercase + QChar(0x03d5), // Hex No. 6A (Dec 106) Character 'ϕ' Letter, Lowercase + QChar(0x03ba), // Hex No. 6B (Dec 107) Character 'κ' Letter, Lowercase + QChar(0x03bb), // Hex No. 6C (Dec 108) Character 'λ' Letter, Lowercase + QChar(0x00b5), // Hex No. 6D (Dec 109) Character 'µ' Letter, Lowercase + QChar(0x03bd), // Hex No. 6E (Dec 110) Character 'ν' Letter, Lowercase + QChar(0x03bf), // Hex No. 6F (Dec 111) Character 'ο' Letter, Lowercase + QChar(0x03c0), // Hex No. 70 (Dec 112) Character 'π' Letter, Lowercase + QChar(0x03b8), // Hex No. 71 (Dec 113) Character 'θ' Letter, Lowercase + QChar(0x03c1), // Hex No. 72 (Dec 114) Character 'ρ' Letter, Lowercase + QChar(0x03c3), // Hex No. 73 (Dec 115) Character 'σ' Letter, Lowercase + QChar(0x03c4), // Hex No. 74 (Dec 116) Character 'τ' Letter, Lowercase + QChar(0x03c5), // Hex No. 75 (Dec 117) Character 'υ' Letter, Lowercase + QChar(0x03d6), // Hex No. 76 (Dec 118) Character 'ϖ' Letter, Lowercase + QChar(0x03c9), // Hex No. 77 (Dec 119) Character 'ω' Letter, Lowercase + QChar(0x03be), // Hex No. 78 (Dec 120) Character 'ξ' Letter, Lowercase + QChar(0x03c8), // Hex No. 79 (Dec 121) Character 'ψ' Letter, Lowercase + QChar(0x03b6), // Hex No. 7A (Dec 122) Character 'ζ' Letter, Lowercase + QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation + QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol + QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation + QChar(0x223c), // Hex No. 7E (Dec 126) Character '∼' Symbol + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 81 (Dec 129) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 82 (Dec 130) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 87 (Dec 135) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 88 (Dec 136) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 89 (Dec 137) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8A (Dec 138) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8B (Dec 139) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8C (Dec 140) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8D (Dec 141) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8E (Dec 142) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8F (Dec 143) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 90 (Dec 144) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 91 (Dec 145) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 92 (Dec 146) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 93 (Dec 147) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 94 (Dec 148) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 95 (Dec 149) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 96 (Dec 150) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 97 (Dec 151) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 98 (Dec 152) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 99 (Dec 153) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9A (Dec 154) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9B (Dec 155) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9C (Dec 156) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9D (Dec 157) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9E (Dec 158) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x03d2), // Hex No. A1 (Dec 161) Character 'ϒ' Letter, Uppercase + QChar(0x2032), // Hex No. A2 (Dec 162) Character '′' Punctuation + QChar(0x2264), // Hex No. A3 (Dec 163) Character '≤' Symbol + QChar(0x2044), // Hex No. A4 (Dec 164) Character '⁄' Symbol + QChar(0x221e), // Hex No. A5 (Dec 165) Character '∞' Symbol + QChar(0x0192), // Hex No. A6 (Dec 166) Character 'ƒ' Letter, Lowercase + QChar(0x2663), // Hex No. A7 (Dec 167) Character '♣' Symbol + QChar(0x2666), // Hex No. A8 (Dec 168) Character '♦' Symbol + QChar(0x2665), // Hex No. A9 (Dec 169) Character '♥' Symbol + QChar(0x2660), // Hex No. AA (Dec 170) Character '♠' Symbol + QChar(0x2194), // Hex No. AB (Dec 171) Character '↔' Symbol + QChar(0x2190), // Hex No. AC (Dec 172) Character '←' Symbol + QChar(0x2191), // Hex No. AD (Dec 173) Character '↑' Symbol + QChar(0x2192), // Hex No. AE (Dec 174) Character '→' Symbol + QChar(0x2193), // Hex No. AF (Dec 175) Character '↓' Symbol + QChar(0x00b0), // Hex No. B0 (Dec 176) Character '°' Symbol + QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol + QChar(0x2033), // Hex No. B2 (Dec 178) Character '″' Punctuation + QChar(0x2265), // Hex No. B3 (Dec 179) Character '≥' Symbol + QChar(0x00d7), // Hex No. B4 (Dec 180) Character '×' Symbol + QChar(0x221d), // Hex No. B5 (Dec 181) Character '∝' Symbol + QChar(0x2202), // Hex No. B6 (Dec 182) Character '∂' Symbol + QChar(0x2022), // Hex No. B7 (Dec 183) Character '•' Punctuation + QChar(0x00f7), // Hex No. B8 (Dec 184) Character '÷' Symbol + QChar(0x2260), // Hex No. B9 (Dec 185) Character '≠' Symbol + QChar(0x2261), // Hex No. BA (Dec 186) Character '≡' Symbol + QChar(0x2248), // Hex No. BB (Dec 187) Character '≈' Symbol + QChar(0x2026), // Hex No. BC (Dec 188) Character '…' Punctuation + QChar(0xf8e6), // Hex No. BD (Dec 189) + QChar(0xf8e7), // Hex No. BE (Dec 190) + QChar(0x21b5), // Hex No. BF (Dec 191) Character '↵' Symbol + QChar(0x2135), // Hex No. C0 (Dec 192) Character 'ℵ' Letter + QChar(0x2111), // Hex No. C1 (Dec 193) Character 'ℑ' Letter, Uppercase + QChar(0x211c), // Hex No. C2 (Dec 194) Character 'ℜ' Letter, Uppercase + QChar(0x2118), // Hex No. C3 (Dec 195) Character '℘' Symbol + QChar(0x2297), // Hex No. C4 (Dec 196) Character '⊗' Symbol + QChar(0x2295), // Hex No. C5 (Dec 197) Character '⊕' Symbol + QChar(0x2205), // Hex No. C6 (Dec 198) Character '∅' Symbol + QChar(0x2229), // Hex No. C7 (Dec 199) Character '∩' Symbol + QChar(0x222a), // Hex No. C8 (Dec 200) Character '∪' Symbol + QChar(0x2283), // Hex No. C9 (Dec 201) Character '⊃' Symbol + QChar(0x2287), // Hex No. CA (Dec 202) Character '⊇' Symbol + QChar(0x2284), // Hex No. CB (Dec 203) Character '⊄' Symbol + QChar(0x2282), // Hex No. CC (Dec 204) Character '⊂' Symbol + QChar(0x2286), // Hex No. CD (Dec 205) Character '⊆' Symbol + QChar(0x2208), // Hex No. CE (Dec 206) Character '∈' Symbol + QChar(0x2209), // Hex No. CF (Dec 207) Character '∉' Symbol + QChar(0x2220), // Hex No. D0 (Dec 208) Character '∠' Symbol + QChar(0x2207), // Hex No. D1 (Dec 209) Character '∇' Symbol + QChar(0xf6da), // Hex No. D2 (Dec 210) + QChar(0xf6d9), // Hex No. D3 (Dec 211) + QChar(0xf6db), // Hex No. D4 (Dec 212) + QChar(0x220f), // Hex No. D5 (Dec 213) Character '∏' Symbol + QChar(0x221a), // Hex No. D6 (Dec 214) Character '√' Symbol + QChar(0x22c5), // Hex No. D7 (Dec 215) Character '⋅' Symbol + QChar(0x00ac), // Hex No. D8 (Dec 216) Character '¬' Symbol + QChar(0x2227), // Hex No. D9 (Dec 217) Character '∧' Symbol + QChar(0x2228), // Hex No. DA (Dec 218) Character '∨' Symbol + QChar(0x21d4), // Hex No. DB (Dec 219) Character '⇔' Symbol + QChar(0x21d0), // Hex No. DC (Dec 220) Character '⇐' Symbol + QChar(0x21d1), // Hex No. DD (Dec 221) Character '⇑' Symbol + QChar(0x21d2), // Hex No. DE (Dec 222) Character '⇒' Symbol + QChar(0x21d3), // Hex No. DF (Dec 223) Character '⇓' Symbol + QChar(0x25ca), // Hex No. E0 (Dec 224) Character '◊' Symbol + QChar(0x2329), // Hex No. E1 (Dec 225) Character '〈' Punctuation + QChar(0xf8e8), // Hex No. E2 (Dec 226) + QChar(0xf8e9), // Hex No. E3 (Dec 227) + QChar(0xf8ea), // Hex No. E4 (Dec 228) + QChar(0x2211), // Hex No. E5 (Dec 229) Character '∑' Symbol + QChar(0xf8eb), // Hex No. E6 (Dec 230) + QChar(0xf8ec), // Hex No. E7 (Dec 231) + QChar(0xf8ed), // Hex No. E8 (Dec 232) + QChar(0xf8ee), // Hex No. E9 (Dec 233) + QChar(0xf8ef), // Hex No. EA (Dec 234) + QChar(0xf8f0), // Hex No. EB (Dec 235) + QChar(0xf8f1), // Hex No. EC (Dec 236) + QChar(0xf8f2), // Hex No. ED (Dec 237) + QChar(0xf8f3), // Hex No. EE (Dec 238) + QChar(0xf8f4), // Hex No. EF (Dec 239) + QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x232a), // Hex No. F1 (Dec 241) Character '〉' Punctuation + QChar(0x222b), // Hex No. F2 (Dec 242) Character '∫' Symbol + QChar(0x2320), // Hex No. F3 (Dec 243) Character '⌠' Symbol + QChar(0xf8f5), // Hex No. F4 (Dec 244) + QChar(0x2321), // Hex No. F5 (Dec 245) Character '⌡' Symbol + QChar(0xf8f6), // Hex No. F6 (Dec 246) + QChar(0xf8f7), // Hex No. F7 (Dec 247) + QChar(0xf8f8), // Hex No. F8 (Dec 248) + QChar(0xf8f9), // Hex No. F9 (Dec 249) + QChar(0xf8fa), // Hex No. FA (Dec 250) + QChar(0xf8fb), // Hex No. FB (Dec 251) + QChar(0xf8fc), // Hex No. FC (Dec 252) + QChar(0xf8fd), // Hex No. FD (Dec 253) + QChar(0xf8fe), // Hex No. FE (Dec 254) + QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set +}; + +// PDF Reference 1.7, Appendix D, Section D.5, Zapf Dingbats Set and Encoding +static const EncodingTable ZAPF_DINGBATS_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x2701), // Hex No. 21 (Dec 033) Character '✁' Symbol + QChar(0x2702), // Hex No. 22 (Dec 034) Character '✂' Symbol + QChar(0x2703), // Hex No. 23 (Dec 035) Character '✃' Symbol + QChar(0x2704), // Hex No. 24 (Dec 036) Character '✄' Symbol + QChar(0x260e), // Hex No. 25 (Dec 037) Character '☎' Symbol + QChar(0x2706), // Hex No. 26 (Dec 038) Character '✆' Symbol + QChar(0x2707), // Hex No. 27 (Dec 039) Character '✇' Symbol + QChar(0x2708), // Hex No. 28 (Dec 040) Character '✈' Symbol + QChar(0x2709), // Hex No. 29 (Dec 041) Character '✉' Symbol + QChar(0x261b), // Hex No. 2A (Dec 042) Character '☛' Symbol + QChar(0x261e), // Hex No. 2B (Dec 043) Character '☞' Symbol + QChar(0x270c), // Hex No. 2C (Dec 044) Character '✌' Symbol + QChar(0x270d), // Hex No. 2D (Dec 045) Character '✍' Symbol + QChar(0x270e), // Hex No. 2E (Dec 046) Character '✎' Symbol + QChar(0x270f), // Hex No. 2F (Dec 047) Character '✏' Symbol + QChar(0x2710), // Hex No. 30 (Dec 048) Character '✐' Symbol + QChar(0x2711), // Hex No. 31 (Dec 049) Character '✑' Symbol + QChar(0x2712), // Hex No. 32 (Dec 050) Character '✒' Symbol + QChar(0x2713), // Hex No. 33 (Dec 051) Character '✓' Symbol + QChar(0x2714), // Hex No. 34 (Dec 052) Character '✔' Symbol + QChar(0x2715), // Hex No. 35 (Dec 053) Character '✕' Symbol + QChar(0x2716), // Hex No. 36 (Dec 054) Character '✖' Symbol + QChar(0x2717), // Hex No. 37 (Dec 055) Character '✗' Symbol + QChar(0x2718), // Hex No. 38 (Dec 056) Character '✘' Symbol + QChar(0x2719), // Hex No. 39 (Dec 057) Character '✙' Symbol + QChar(0x271a), // Hex No. 3A (Dec 058) Character '✚' Symbol + QChar(0x271b), // Hex No. 3B (Dec 059) Character '✛' Symbol + QChar(0x271c), // Hex No. 3C (Dec 060) Character '✜' Symbol + QChar(0x271d), // Hex No. 3D (Dec 061) Character '✝' Symbol + QChar(0x271e), // Hex No. 3E (Dec 062) Character '✞' Symbol + QChar(0x271f), // Hex No. 3F (Dec 063) Character '✟' Symbol + QChar(0x2720), // Hex No. 40 (Dec 064) Character '✠' Symbol + QChar(0x2721), // Hex No. 41 (Dec 065) Character '✡' Symbol + QChar(0x2722), // Hex No. 42 (Dec 066) Character '✢' Symbol + QChar(0x2723), // Hex No. 43 (Dec 067) Character '✣' Symbol + QChar(0x2724), // Hex No. 44 (Dec 068) Character '✤' Symbol + QChar(0x2725), // Hex No. 45 (Dec 069) Character '✥' Symbol + QChar(0x2726), // Hex No. 46 (Dec 070) Character '✦' Symbol + QChar(0x2727), // Hex No. 47 (Dec 071) Character '✧' Symbol + QChar(0x2605), // Hex No. 48 (Dec 072) Character '★' Symbol + QChar(0x2729), // Hex No. 49 (Dec 073) Character '✩' Symbol + QChar(0x272a), // Hex No. 4A (Dec 074) Character '✪' Symbol + QChar(0x272b), // Hex No. 4B (Dec 075) Character '✫' Symbol + QChar(0x272c), // Hex No. 4C (Dec 076) Character '✬' Symbol + QChar(0x272d), // Hex No. 4D (Dec 077) Character '✭' Symbol + QChar(0x272e), // Hex No. 4E (Dec 078) Character '✮' Symbol + QChar(0x272f), // Hex No. 4F (Dec 079) Character '✯' Symbol + QChar(0x2730), // Hex No. 50 (Dec 080) Character '✰' Symbol + QChar(0x2731), // Hex No. 51 (Dec 081) Character '✱' Symbol + QChar(0x2732), // Hex No. 52 (Dec 082) Character '✲' Symbol + QChar(0x2733), // Hex No. 53 (Dec 083) Character '✳' Symbol + QChar(0x2734), // Hex No. 54 (Dec 084) Character '✴' Symbol + QChar(0x2735), // Hex No. 55 (Dec 085) Character '✵' Symbol + QChar(0x2736), // Hex No. 56 (Dec 086) Character '✶' Symbol + QChar(0x2737), // Hex No. 57 (Dec 087) Character '✷' Symbol + QChar(0x2738), // Hex No. 58 (Dec 088) Character '✸' Symbol + QChar(0x2739), // Hex No. 59 (Dec 089) Character '✹' Symbol + QChar(0x273a), // Hex No. 5A (Dec 090) Character '✺' Symbol + QChar(0x273b), // Hex No. 5B (Dec 091) Character '✻' Symbol + QChar(0x273c), // Hex No. 5C (Dec 092) Character '✼' Symbol + QChar(0x273d), // Hex No. 5D (Dec 093) Character '✽' Symbol + QChar(0x273e), // Hex No. 5E (Dec 094) Character '✾' Symbol + QChar(0x273f), // Hex No. 5F (Dec 095) Character '✿' Symbol + QChar(0x2740), // Hex No. 60 (Dec 096) Character '❀' Symbol + QChar(0x2741), // Hex No. 61 (Dec 097) Character '❁' Symbol + QChar(0x2742), // Hex No. 62 (Dec 098) Character '❂' Symbol + QChar(0x2743), // Hex No. 63 (Dec 099) Character '❃' Symbol + QChar(0x2744), // Hex No. 64 (Dec 100) Character '❄' Symbol + QChar(0x2745), // Hex No. 65 (Dec 101) Character '❅' Symbol + QChar(0x2746), // Hex No. 66 (Dec 102) Character '❆' Symbol + QChar(0x2747), // Hex No. 67 (Dec 103) Character '❇' Symbol + QChar(0x2748), // Hex No. 68 (Dec 104) Character '❈' Symbol + QChar(0x2749), // Hex No. 69 (Dec 105) Character '❉' Symbol + QChar(0x274a), // Hex No. 6A (Dec 106) Character '❊' Symbol + QChar(0x274b), // Hex No. 6B (Dec 107) Character '❋' Symbol + QChar(0x25cf), // Hex No. 6C (Dec 108) Character '●' Symbol + QChar(0x274d), // Hex No. 6D (Dec 109) Character '❍' Symbol + QChar(0x25a0), // Hex No. 6E (Dec 110) Character '■' Symbol + QChar(0x274f), // Hex No. 6F (Dec 111) Character '❏' Symbol + QChar(0x2750), // Hex No. 70 (Dec 112) Character '❐' Symbol + QChar(0x2751), // Hex No. 71 (Dec 113) Character '❑' Symbol + QChar(0x2752), // Hex No. 72 (Dec 114) Character '❒' Symbol + QChar(0x25b2), // Hex No. 73 (Dec 115) Character '▲' Symbol + QChar(0x25bc), // Hex No. 74 (Dec 116) Character '▼' Symbol + QChar(0x25c6), // Hex No. 75 (Dec 117) Character '◆' Symbol + QChar(0x2756), // Hex No. 76 (Dec 118) Character '❖' Symbol + QChar(0x25d7), // Hex No. 77 (Dec 119) Character '◗' Symbol + QChar(0x2758), // Hex No. 78 (Dec 120) Character '❘' Symbol + QChar(0x2759), // Hex No. 79 (Dec 121) Character '❙' Symbol + QChar(0x275a), // Hex No. 7A (Dec 122) Character '❚' Symbol + QChar(0x275b), // Hex No. 7B (Dec 123) Character '❛' Symbol + QChar(0x275c), // Hex No. 7C (Dec 124) Character '❜' Symbol + QChar(0x275d), // Hex No. 7D (Dec 125) Character '❝' Symbol + QChar(0x275e), // Hex No. 7E (Dec 126) Character '❞' Symbol + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 80 (Dec 128) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 81 (Dec 129) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 82 (Dec 130) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 83 (Dec 131) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 84 (Dec 132) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 85 (Dec 133) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 86 (Dec 134) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 87 (Dec 135) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 88 (Dec 136) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 89 (Dec 137) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8A (Dec 138) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8B (Dec 139) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8C (Dec 140) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8D (Dec 141) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8E (Dec 142) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 8F (Dec 143) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 90 (Dec 144) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 91 (Dec 145) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 92 (Dec 146) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 93 (Dec 147) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 94 (Dec 148) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 95 (Dec 149) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 96 (Dec 150) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 97 (Dec 151) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 98 (Dec 152) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 99 (Dec 153) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9A (Dec 154) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9B (Dec 155) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9C (Dec 156) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9D (Dec 157) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9E (Dec 158) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 9F (Dec 159) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. A0 (Dec 160) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x2761), // Hex No. A1 (Dec 161) Character '❡' Symbol + QChar(0x2762), // Hex No. A2 (Dec 162) Character '❢' Symbol + QChar(0x2763), // Hex No. A3 (Dec 163) Character '❣' Symbol + QChar(0x2764), // Hex No. A4 (Dec 164) Character '❤' Symbol + QChar(0x2765), // Hex No. A5 (Dec 165) Character '❥' Symbol + QChar(0x2766), // Hex No. A6 (Dec 166) Character '❦' Symbol + QChar(0x2767), // Hex No. A7 (Dec 167) Character '❧' Symbol + QChar(0x2663), // Hex No. A8 (Dec 168) Character '♣' Symbol + QChar(0x2666), // Hex No. A9 (Dec 169) Character '♦' Symbol + QChar(0x2665), // Hex No. AA (Dec 170) Character '♥' Symbol + QChar(0x2660), // Hex No. AB (Dec 171) Character '♠' Symbol + QChar(0x2460), // Hex No. AC (Dec 172) Character '①' + QChar(0x2461), // Hex No. AD (Dec 173) Character '②' + QChar(0x2462), // Hex No. AE (Dec 174) Character '③' + QChar(0x2463), // Hex No. AF (Dec 175) Character '④' + QChar(0x2464), // Hex No. B0 (Dec 176) Character '⑤' + QChar(0x2465), // Hex No. B1 (Dec 177) Character '⑥' + QChar(0x2466), // Hex No. B2 (Dec 178) Character '⑦' + QChar(0x2467), // Hex No. B3 (Dec 179) Character '⑧' + QChar(0x2468), // Hex No. B4 (Dec 180) Character '⑨' + QChar(0x2469), // Hex No. B5 (Dec 181) Character '⑩' + QChar(0x2776), // Hex No. B6 (Dec 182) Character '❶' + QChar(0x2777), // Hex No. B7 (Dec 183) Character '❷' + QChar(0x2778), // Hex No. B8 (Dec 184) Character '❸' + QChar(0x2779), // Hex No. B9 (Dec 185) Character '❹' + QChar(0x277a), // Hex No. BA (Dec 186) Character '❺' + QChar(0x277b), // Hex No. BB (Dec 187) Character '❻' + QChar(0x277c), // Hex No. BC (Dec 188) Character '❼' + QChar(0x277d), // Hex No. BD (Dec 189) Character '❽' + QChar(0x277e), // Hex No. BE (Dec 190) Character '❾' + QChar(0x277f), // Hex No. BF (Dec 191) Character '❿' + QChar(0x2780), // Hex No. C0 (Dec 192) Character '➀' + QChar(0x2781), // Hex No. C1 (Dec 193) Character '➁' + QChar(0x2782), // Hex No. C2 (Dec 194) Character '➂' + QChar(0x2783), // Hex No. C3 (Dec 195) Character '➃' + QChar(0x2784), // Hex No. C4 (Dec 196) Character '➄' + QChar(0x2785), // Hex No. C5 (Dec 197) Character '➅' + QChar(0x2786), // Hex No. C6 (Dec 198) Character '➆' + QChar(0x2787), // Hex No. C7 (Dec 199) Character '➇' + QChar(0x2788), // Hex No. C8 (Dec 200) Character '➈' + QChar(0x2789), // Hex No. C9 (Dec 201) Character '➉' + QChar(0x278a), // Hex No. CA (Dec 202) Character '➊' + QChar(0x278b), // Hex No. CB (Dec 203) Character '➋' + QChar(0x278c), // Hex No. CC (Dec 204) Character '➌' + QChar(0x278d), // Hex No. CD (Dec 205) Character '➍' + QChar(0x278e), // Hex No. CE (Dec 206) Character '➎' + QChar(0x278f), // Hex No. CF (Dec 207) Character '➏' + QChar(0x2790), // Hex No. D0 (Dec 208) Character '➐' + QChar(0x2791), // Hex No. D1 (Dec 209) Character '➑' + QChar(0x2792), // Hex No. D2 (Dec 210) Character '➒' + QChar(0x2793), // Hex No. D3 (Dec 211) Character '➓' + QChar(0x2794), // Hex No. D4 (Dec 212) Character '➔' Symbol + QChar(0x2192), // Hex No. D5 (Dec 213) Character '→' Symbol + QChar(0x2194), // Hex No. D6 (Dec 214) Character '↔' Symbol + QChar(0x2195), // Hex No. D7 (Dec 215) Character '↕' Symbol + QChar(0x2798), // Hex No. D8 (Dec 216) Character '➘' Symbol + QChar(0x2799), // Hex No. D9 (Dec 217) Character '➙' Symbol + QChar(0x279a), // Hex No. DA (Dec 218) Character '➚' Symbol + QChar(0x279b), // Hex No. DB (Dec 219) Character '➛' Symbol + QChar(0x279c), // Hex No. DC (Dec 220) Character '➜' Symbol + QChar(0x279d), // Hex No. DD (Dec 221) Character '➝' Symbol + QChar(0x279e), // Hex No. DE (Dec 222) Character '➞' Symbol + QChar(0x279f), // Hex No. DF (Dec 223) Character '➟' Symbol + QChar(0x27a0), // Hex No. E0 (Dec 224) Character '➠' Symbol + QChar(0x27a1), // Hex No. E1 (Dec 225) Character '➡' Symbol + QChar(0x27a2), // Hex No. E2 (Dec 226) Character '➢' Symbol + QChar(0x27a3), // Hex No. E3 (Dec 227) Character '➣' Symbol + QChar(0x27a4), // Hex No. E4 (Dec 228) Character '➤' Symbol + QChar(0x27a5), // Hex No. E5 (Dec 229) Character '➥' Symbol + QChar(0x27a6), // Hex No. E6 (Dec 230) Character '➦' Symbol + QChar(0x27a7), // Hex No. E7 (Dec 231) Character '➧' Symbol + QChar(0x27a8), // Hex No. E8 (Dec 232) Character '➨' Symbol + QChar(0x27a9), // Hex No. E9 (Dec 233) Character '➩' Symbol + QChar(0x27aa), // Hex No. EA (Dec 234) Character '➪' Symbol + QChar(0x27ab), // Hex No. EB (Dec 235) Character '➫' Symbol + QChar(0x27ac), // Hex No. EC (Dec 236) Character '➬' Symbol + QChar(0x27ad), // Hex No. ED (Dec 237) Character '➭' Symbol + QChar(0x27ae), // Hex No. EE (Dec 238) Character '➮' Symbol + QChar(0x27af), // Hex No. EF (Dec 239) Character '➯' Symbol + QChar(0xfffd), // Hex No. F0 (Dec 240) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x27b1), // Hex No. F1 (Dec 241) Character '➱' Symbol + QChar(0x27b2), // Hex No. F2 (Dec 242) Character '➲' Symbol + QChar(0x27b3), // Hex No. F3 (Dec 243) Character '➳' Symbol + QChar(0x27b4), // Hex No. F4 (Dec 244) Character '➴' Symbol + QChar(0x27b5), // Hex No. F5 (Dec 245) Character '➵' Symbol + QChar(0x27b6), // Hex No. F6 (Dec 246) Character '➶' Symbol + QChar(0x27b7), // Hex No. F7 (Dec 247) Character '➷' Symbol + QChar(0x27b8), // Hex No. F8 (Dec 248) Character '➸' Symbol + QChar(0x27b9), // Hex No. F9 (Dec 249) Character '➹' Symbol + QChar(0x27ba), // Hex No. FA (Dec 250) Character '➺' Symbol + QChar(0x27bb), // Hex No. FB (Dec 251) Character '➻' Symbol + QChar(0x27bc), // Hex No. FC (Dec 252) Character '➼' Symbol + QChar(0x27bd), // Hex No. FD (Dec 253) Character '➽' Symbol + QChar(0x27be), // Hex No. FE (Dec 254) Character '➾' Symbol + QChar(0xfffd), // Hex No. FF (Dec 255) REPLACEMENT CHARACTER 0xFFFD - not present in character set +}; + +// Mac OS encoding +static const EncodingTable MAC_OS_ENCODING_CONVERSION_TABLE = { + QChar(0xfffd), // Hex No. 00 (Dec 000) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 01 (Dec 001) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 02 (Dec 002) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 03 (Dec 003) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 04 (Dec 004) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 05 (Dec 005) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 06 (Dec 006) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 07 (Dec 007) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 08 (Dec 008) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 09 (Dec 009) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0A (Dec 010) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0B (Dec 011) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0C (Dec 012) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0D (Dec 013) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0E (Dec 014) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 0F (Dec 015) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 10 (Dec 016) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 11 (Dec 017) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 12 (Dec 018) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 13 (Dec 019) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 14 (Dec 020) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 15 (Dec 021) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 16 (Dec 022) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 17 (Dec 023) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 18 (Dec 024) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 19 (Dec 025) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1A (Dec 026) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1B (Dec 027) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1C (Dec 028) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1D (Dec 029) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1E (Dec 030) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0xfffd), // Hex No. 1F (Dec 031) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x0020), // Hex No. 20 (Dec 032) Character ' ' Whitespace + QChar(0x0021), // Hex No. 21 (Dec 033) Character '!' Punctuation + QChar(0x0022), // Hex No. 22 (Dec 034) Character '"' Punctuation + QChar(0x0023), // Hex No. 23 (Dec 035) Character '#' Punctuation + QChar(0x0024), // Hex No. 24 (Dec 036) Character '$' Symbol + QChar(0x0025), // Hex No. 25 (Dec 037) Character '%' Punctuation + QChar(0x0026), // Hex No. 26 (Dec 038) Character '&' Punctuation + QChar(0x0027), // Hex No. 27 (Dec 039) Character ''' Punctuation + QChar(0x0028), // Hex No. 28 (Dec 040) Character '(' Punctuation + QChar(0x0029), // Hex No. 29 (Dec 041) Character ')' Punctuation + QChar(0x002a), // Hex No. 2A (Dec 042) Character '*' Punctuation + QChar(0x002b), // Hex No. 2B (Dec 043) Character '+' Symbol + QChar(0x002c), // Hex No. 2C (Dec 044) Character ',' Punctuation + QChar(0x002d), // Hex No. 2D (Dec 045) Character '-' Punctuation + QChar(0x002e), // Hex No. 2E (Dec 046) Character '.' Punctuation + QChar(0x002f), // Hex No. 2F (Dec 047) Character '/' Punctuation + QChar(0x0030), // Hex No. 30 (Dec 048) Character '0' Digit + QChar(0x0031), // Hex No. 31 (Dec 049) Character '1' Digit + QChar(0x0032), // Hex No. 32 (Dec 050) Character '2' Digit + QChar(0x0033), // Hex No. 33 (Dec 051) Character '3' Digit + QChar(0x0034), // Hex No. 34 (Dec 052) Character '4' Digit + QChar(0x0035), // Hex No. 35 (Dec 053) Character '5' Digit + QChar(0x0036), // Hex No. 36 (Dec 054) Character '6' Digit + QChar(0x0037), // Hex No. 37 (Dec 055) Character '7' Digit + QChar(0x0038), // Hex No. 38 (Dec 056) Character '8' Digit + QChar(0x0039), // Hex No. 39 (Dec 057) Character '9' Digit + QChar(0x003a), // Hex No. 3A (Dec 058) Character ':' Punctuation + QChar(0x003b), // Hex No. 3B (Dec 059) Character ';' Punctuation + QChar(0x003c), // Hex No. 3C (Dec 060) Character '<' Symbol + QChar(0x003d), // Hex No. 3D (Dec 061) Character '=' Symbol + QChar(0x003e), // Hex No. 3E (Dec 062) Character '>' Symbol + QChar(0x003f), // Hex No. 3F (Dec 063) Character '?' Punctuation + QChar(0x0040), // Hex No. 40 (Dec 064) Character '@' Punctuation + QChar(0x0041), // Hex No. 41 (Dec 065) Character 'A' Letter, Uppercase + QChar(0x0042), // Hex No. 42 (Dec 066) Character 'B' Letter, Uppercase + QChar(0x0043), // Hex No. 43 (Dec 067) Character 'C' Letter, Uppercase + QChar(0x0044), // Hex No. 44 (Dec 068) Character 'D' Letter, Uppercase + QChar(0x0045), // Hex No. 45 (Dec 069) Character 'E' Letter, Uppercase + QChar(0x0046), // Hex No. 46 (Dec 070) Character 'F' Letter, Uppercase + QChar(0x0047), // Hex No. 47 (Dec 071) Character 'G' Letter, Uppercase + QChar(0x0048), // Hex No. 48 (Dec 072) Character 'H' Letter, Uppercase + QChar(0x0049), // Hex No. 49 (Dec 073) Character 'I' Letter, Uppercase + QChar(0x004a), // Hex No. 4A (Dec 074) Character 'J' Letter, Uppercase + QChar(0x004b), // Hex No. 4B (Dec 075) Character 'K' Letter, Uppercase + QChar(0x004c), // Hex No. 4C (Dec 076) Character 'L' Letter, Uppercase + QChar(0x004d), // Hex No. 4D (Dec 077) Character 'M' Letter, Uppercase + QChar(0x004e), // Hex No. 4E (Dec 078) Character 'N' Letter, Uppercase + QChar(0x004f), // Hex No. 4F (Dec 079) Character 'O' Letter, Uppercase + QChar(0x0050), // Hex No. 50 (Dec 080) Character 'P' Letter, Uppercase + QChar(0x0051), // Hex No. 51 (Dec 081) Character 'Q' Letter, Uppercase + QChar(0x0052), // Hex No. 52 (Dec 082) Character 'R' Letter, Uppercase + QChar(0x0053), // Hex No. 53 (Dec 083) Character 'S' Letter, Uppercase + QChar(0x0054), // Hex No. 54 (Dec 084) Character 'T' Letter, Uppercase + QChar(0x0055), // Hex No. 55 (Dec 085) Character 'U' Letter, Uppercase + QChar(0x0056), // Hex No. 56 (Dec 086) Character 'V' Letter, Uppercase + QChar(0x0057), // Hex No. 57 (Dec 087) Character 'W' Letter, Uppercase + QChar(0x0058), // Hex No. 58 (Dec 088) Character 'X' Letter, Uppercase + QChar(0x0059), // Hex No. 59 (Dec 089) Character 'Y' Letter, Uppercase + QChar(0x005a), // Hex No. 5A (Dec 090) Character 'Z' Letter, Uppercase + QChar(0x005b), // Hex No. 5B (Dec 091) Character '[' Punctuation + QChar(0x005c), // Hex No. 5C (Dec 092) Character '\' Punctuation + QChar(0x005d), // Hex No. 5D (Dec 093) Character ']' Punctuation + QChar(0x005e), // Hex No. 5E (Dec 094) Character '^' Symbol + QChar(0x005f), // Hex No. 5F (Dec 095) Character '_' Punctuation + QChar(0x0060), // Hex No. 60 (Dec 096) Character '`' Symbol + QChar(0x0061), // Hex No. 61 (Dec 097) Character 'a' Letter, Lowercase + QChar(0x0062), // Hex No. 62 (Dec 098) Character 'b' Letter, Lowercase + QChar(0x0063), // Hex No. 63 (Dec 099) Character 'c' Letter, Lowercase + QChar(0x0064), // Hex No. 64 (Dec 100) Character 'd' Letter, Lowercase + QChar(0x0065), // Hex No. 65 (Dec 101) Character 'e' Letter, Lowercase + QChar(0x0066), // Hex No. 66 (Dec 102) Character 'f' Letter, Lowercase + QChar(0x0067), // Hex No. 67 (Dec 103) Character 'g' Letter, Lowercase + QChar(0x0068), // Hex No. 68 (Dec 104) Character 'h' Letter, Lowercase + QChar(0x0069), // Hex No. 69 (Dec 105) Character 'i' Letter, Lowercase + QChar(0x006a), // Hex No. 6A (Dec 106) Character 'j' Letter, Lowercase + QChar(0x006b), // Hex No. 6B (Dec 107) Character 'k' Letter, Lowercase + QChar(0x006c), // Hex No. 6C (Dec 108) Character 'l' Letter, Lowercase + QChar(0x006d), // Hex No. 6D (Dec 109) Character 'm' Letter, Lowercase + QChar(0x006e), // Hex No. 6E (Dec 110) Character 'n' Letter, Lowercase + QChar(0x006f), // Hex No. 6F (Dec 111) Character 'o' Letter, Lowercase + QChar(0x0070), // Hex No. 70 (Dec 112) Character 'p' Letter, Lowercase + QChar(0x0071), // Hex No. 71 (Dec 113) Character 'q' Letter, Lowercase + QChar(0x0072), // Hex No. 72 (Dec 114) Character 'r' Letter, Lowercase + QChar(0x0073), // Hex No. 73 (Dec 115) Character 's' Letter, Lowercase + QChar(0x0074), // Hex No. 74 (Dec 116) Character 't' Letter, Lowercase + QChar(0x0075), // Hex No. 75 (Dec 117) Character 'u' Letter, Lowercase + QChar(0x0076), // Hex No. 76 (Dec 118) Character 'v' Letter, Lowercase + QChar(0x0077), // Hex No. 77 (Dec 119) Character 'w' Letter, Lowercase + QChar(0x0078), // Hex No. 78 (Dec 120) Character 'x' Letter, Lowercase + QChar(0x0079), // Hex No. 79 (Dec 121) Character 'y' Letter, Lowercase + QChar(0x007a), // Hex No. 7A (Dec 122) Character 'z' Letter, Lowercase + QChar(0x007b), // Hex No. 7B (Dec 123) Character '{' Punctuation + QChar(0x007c), // Hex No. 7C (Dec 124) Character '|' Symbol + QChar(0x007d), // Hex No. 7D (Dec 125) Character '}' Punctuation + QChar(0x007e), // Hex No. 7E (Dec 126) Character '~' Symbol + QChar(0xfffd), // Hex No. 7F (Dec 127) REPLACEMENT CHARACTER 0xFFFD - not present in character set + QChar(0x00c4), // Hex No. 80 (Dec 128) Character 'Ä' Letter, Uppercase + QChar(0x00c5), // Hex No. 81 (Dec 129) Character 'Å' Letter, Uppercase + QChar(0x00c7), // Hex No. 82 (Dec 130) Character 'Ç' Letter, Uppercase + QChar(0x00c9), // Hex No. 83 (Dec 131) Character 'É' Letter, Uppercase + QChar(0x00d1), // Hex No. 84 (Dec 132) Character 'Ñ' Letter, Uppercase + QChar(0x00d6), // Hex No. 85 (Dec 133) Character 'Ö' Letter, Uppercase + QChar(0x00dc), // Hex No. 86 (Dec 134) Character 'Ü' Letter, Uppercase + QChar(0x00e1), // Hex No. 87 (Dec 135) Character 'á' Letter, Lowercase + QChar(0x00e0), // Hex No. 88 (Dec 136) Character 'à' Letter, Lowercase + QChar(0x00e2), // Hex No. 89 (Dec 137) Character 'â' Letter, Lowercase + QChar(0x00e4), // Hex No. 8A (Dec 138) Character 'ä' Letter, Lowercase + QChar(0x00e3), // Hex No. 8B (Dec 139) Character 'ã' Letter, Lowercase + QChar(0x00e5), // Hex No. 8C (Dec 140) Character 'å' Letter, Lowercase + QChar(0x00e7), // Hex No. 8D (Dec 141) Character 'ç' Letter, Lowercase + QChar(0x00e9), // Hex No. 8E (Dec 142) Character 'é' Letter, Lowercase + QChar(0x00e8), // Hex No. 8F (Dec 143) Character 'è' Letter, Lowercase + QChar(0x00ea), // Hex No. 90 (Dec 144) Character 'ê' Letter, Lowercase + QChar(0x00eb), // Hex No. 91 (Dec 145) Character 'ë' Letter, Lowercase + QChar(0x00ed), // Hex No. 92 (Dec 146) Character 'í' Letter, Lowercase + QChar(0x00ec), // Hex No. 93 (Dec 147) Character 'ì' Letter, Lowercase + QChar(0x00ee), // Hex No. 94 (Dec 148) Character 'î' Letter, Lowercase + QChar(0x00ef), // Hex No. 95 (Dec 149) Character 'ï' Letter, Lowercase + QChar(0x00f1), // Hex No. 96 (Dec 150) Character 'ñ' Letter, Lowercase + QChar(0x00f3), // Hex No. 97 (Dec 151) Character 'ó' Letter, Lowercase + QChar(0x00f2), // Hex No. 98 (Dec 152) Character 'ò' Letter, Lowercase + QChar(0x00f4), // Hex No. 99 (Dec 153) Character 'ô' Letter, Lowercase + QChar(0x00f6), // Hex No. 9A (Dec 154) Character 'ö' Letter, Lowercase + QChar(0x00f5), // Hex No. 9B (Dec 155) Character 'õ' Letter, Lowercase + QChar(0x00fa), // Hex No. 9C (Dec 156) Character 'ú' Letter, Lowercase + QChar(0x00f9), // Hex No. 9D (Dec 157) Character 'ù' Letter, Lowercase + QChar(0x00fb), // Hex No. 9E (Dec 158) Character 'û' Letter, Lowercase + QChar(0x00fc), // Hex No. 9F (Dec 159) Character 'ü' Letter, Lowercase + QChar(0x2020), // Hex No. A0 (Dec 160) Character '†' Punctuation + QChar(0x00b0), // Hex No. A1 (Dec 161) Character '°' Symbol + QChar(0x00a2), // Hex No. A2 (Dec 162) Character '¢' Symbol + QChar(0x00a3), // Hex No. A3 (Dec 163) Character '£' Symbol + QChar(0x00a7), // Hex No. A4 (Dec 164) Character '§' Punctuation + QChar(0x2022), // Hex No. A5 (Dec 165) Character '•' Punctuation + QChar(0x00b6), // Hex No. A6 (Dec 166) Character '¶' Punctuation + QChar(0x00df), // Hex No. A7 (Dec 167) Character 'ß' Letter, Lowercase + QChar(0x00ae), // Hex No. A8 (Dec 168) Character '®' Symbol + QChar(0x00a9), // Hex No. A9 (Dec 169) Character '©' Symbol + QChar(0x2122), // Hex No. AA (Dec 170) Character '™' Symbol + QChar(0x00b4), // Hex No. AB (Dec 171) Character '´' Symbol + QChar(0x00a8), // Hex No. AC (Dec 172) Character '¨' Symbol + QChar(0x2260), // Hex No. AD (Dec 173) Character '≠' Symbol + QChar(0x00c6), // Hex No. AE (Dec 174) Character 'Æ' Letter, Uppercase + QChar(0x00d8), // Hex No. AF (Dec 175) Character 'Ø' Letter, Uppercase + QChar(0x221e), // Hex No. B0 (Dec 176) Character '∞' Symbol + QChar(0x00b1), // Hex No. B1 (Dec 177) Character '±' Symbol + QChar(0x2264), // Hex No. B2 (Dec 178) Character '≤' Symbol + QChar(0x2265), // Hex No. B3 (Dec 179) Character '≥' Symbol + QChar(0x00a5), // Hex No. B4 (Dec 180) Character '¥' Symbol + QChar(0x00b5), // Hex No. B5 (Dec 181) Character 'µ' Letter, Lowercase + QChar(0x2202), // Hex No. B6 (Dec 182) Character '∂' Symbol + QChar(0x2211), // Hex No. B7 (Dec 183) Character '∑' Symbol + QChar(0x220f), // Hex No. B8 (Dec 184) Character '∏' Symbol + QChar(0x03c0), // Hex No. B9 (Dec 185) Character 'π' Letter, Lowercase + QChar(0x222b), // Hex No. BA (Dec 186) Character '∫' Symbol + QChar(0x00aa), // Hex No. BB (Dec 187) Character 'ª' Letter + QChar(0x00ba), // Hex No. BC (Dec 188) Character 'º' Letter + QChar(0x2126), // Hex No. BD (Dec 189) Character 'Ω' Letter, Uppercase + QChar(0x00e6), // Hex No. BE (Dec 190) Character 'æ' Letter, Lowercase + QChar(0x00f8), // Hex No. BF (Dec 191) Character 'ø' Letter, Lowercase + QChar(0x00bf), // Hex No. C0 (Dec 192) Character '¿' Punctuation + QChar(0x00a1), // Hex No. C1 (Dec 193) Character '¡' Punctuation + QChar(0x00ac), // Hex No. C2 (Dec 194) Character '¬' Symbol + QChar(0x221a), // Hex No. C3 (Dec 195) Character '√' Symbol + QChar(0x0192), // Hex No. C4 (Dec 196) Character 'ƒ' Letter, Lowercase + QChar(0x2248), // Hex No. C5 (Dec 197) Character '≈' Symbol + QChar(0x2206), // Hex No. C6 (Dec 198) Character '∆' Symbol + QChar(0x00ab), // Hex No. C7 (Dec 199) Character '«' Punctuation + QChar(0x00bb), // Hex No. C8 (Dec 200) Character '»' Punctuation + QChar(0x2026), // Hex No. C9 (Dec 201) Character '…' Punctuation + QChar(0x0020), // Hex No. CA (Dec 202) Character ' ' Whitespace + QChar(0x00c0), // Hex No. CB (Dec 203) Character 'À' Letter, Uppercase + QChar(0x00c3), // Hex No. CC (Dec 204) Character 'Ã' Letter, Uppercase + QChar(0x00d5), // Hex No. CD (Dec 205) Character 'Õ' Letter, Uppercase + QChar(0x0152), // Hex No. CE (Dec 206) Character 'Œ' Letter, Uppercase + QChar(0x0153), // Hex No. CF (Dec 207) Character 'œ' Letter, Lowercase + QChar(0x2013), // Hex No. D0 (Dec 208) Character '–' Punctuation + QChar(0x2014), // Hex No. D1 (Dec 209) Character '—' Punctuation + QChar(0x201c), // Hex No. D2 (Dec 210) Character '“' Punctuation + QChar(0x201d), // Hex No. D3 (Dec 211) Character '”' Punctuation + QChar(0x2018), // Hex No. D4 (Dec 212) Character '‘' Punctuation + QChar(0x2019), // Hex No. D5 (Dec 213) Character '’' Punctuation + QChar(0x00f7), // Hex No. D6 (Dec 214) Character '÷' Symbol + QChar(0x25ca), // Hex No. D7 (Dec 215) Character '◊' Symbol + QChar(0x00ff), // Hex No. D8 (Dec 216) Character 'ÿ' Letter, Lowercase + QChar(0x0178), // Hex No. D9 (Dec 217) Character 'Ÿ' Letter, Uppercase + QChar(0x2044), // Hex No. DA (Dec 218) Character '⁄' Symbol + QChar(0x20ac), // Hex No. DB (Dec 219) Character '€' Symbol !!! REPLACED FOR MAC OS + QChar(0x2039), // Hex No. DC (Dec 220) Character '‹' Punctuation + QChar(0x203a), // Hex No. DD (Dec 221) Character '›' Punctuation + QChar(0xfb01), // Hex No. DE (Dec 222) Character 'fi' Letter, Lowercase + QChar(0xfb02), // Hex No. DF (Dec 223) Character 'fl' Letter, Lowercase + QChar(0x2021), // Hex No. E0 (Dec 224) Character '‡' Punctuation + QChar(0x00b7), // Hex No. E1 (Dec 225) Character '·' Punctuation + QChar(0x201a), // Hex No. E2 (Dec 226) Character '‚' Punctuation + QChar(0x201e), // Hex No. E3 (Dec 227) Character '„' Punctuation + QChar(0x2030), // Hex No. E4 (Dec 228) Character '‰' Punctuation + QChar(0x00c2), // Hex No. E5 (Dec 229) Character 'Â' Letter, Uppercase + QChar(0x00ca), // Hex No. E6 (Dec 230) Character 'Ê' Letter, Uppercase + QChar(0x00c1), // Hex No. E7 (Dec 231) Character 'Á' Letter, Uppercase + QChar(0x00cb), // Hex No. E8 (Dec 232) Character 'Ë' Letter, Uppercase + QChar(0x00c8), // Hex No. E9 (Dec 233) Character 'È' Letter, Uppercase + QChar(0x00cd), // Hex No. EA (Dec 234) Character 'Í' Letter, Uppercase + QChar(0x00ce), // Hex No. EB (Dec 235) Character 'Î' Letter, Uppercase + QChar(0x00cf), // Hex No. EC (Dec 236) Character 'Ï' Letter, Uppercase + QChar(0x00cc), // Hex No. ED (Dec 237) Character 'Ì' Letter, Uppercase + QChar(0x00d3), // Hex No. EE (Dec 238) Character 'Ó' Letter, Uppercase + QChar(0x00d4), // Hex No. EF (Dec 239) Character 'Ô' Letter, Uppercase + QChar(0xf8ff), // Hex No. F0 (Dec 240) + QChar(0x00d2), // Hex No. F1 (Dec 241) Character 'Ò' Letter, Uppercase + QChar(0x00da), // Hex No. F2 (Dec 242) Character 'Ú' Letter, Uppercase + QChar(0x00db), // Hex No. F3 (Dec 243) Character 'Û' Letter, Uppercase + QChar(0x00d9), // Hex No. F4 (Dec 244) Character 'Ù' Letter, Uppercase + QChar(0x0131), // Hex No. F5 (Dec 245) Character 'ı' Letter, Lowercase + QChar(0x02c6), // Hex No. F6 (Dec 246) Character 'ˆ' Letter + QChar(0x02dc), // Hex No. F7 (Dec 247) Character '˜' Symbol + QChar(0x00af), // Hex No. F8 (Dec 248) Character '¯' Symbol + QChar(0x02d8), // Hex No. F9 (Dec 249) Character '˘' Symbol + QChar(0x02d9), // Hex No. FA (Dec 250) Character '˙' Symbol + QChar(0x02da), // Hex No. FB (Dec 251) Character '˚' Symbol + QChar(0x00b8), // Hex No. FC (Dec 252) Character '¸' Symbol + QChar(0x02dd), // Hex No. FD (Dec 253) Character '˝' Symbol + QChar(0x02db), // Hex No. FE (Dec 254) Character '˛' Symbol + QChar(0x02c7), // Hex No. FF (Dec 255) Character 'ˇ' Letter +}; + +} // namespace encoding + +QString PDFEncoding::convert(const QByteArray& stream, PDFEncoding::Encoding encoding) +{ + const encoding::EncodingTable* table = getTableForEncoding(encoding); + Q_ASSERT(table); + + // Test by assert, than table has enough items for encoded byte stream + Q_ASSERT(table->size() == std::numeric_limits::max() + 1); + + const int size = stream.size(); + const char* data = stream.constData(); + + QString result; + result.resize(size, QChar()); + + for (int i = 0; i < size; ++i) + { + result[i] = (*table)[static_cast(data[i])]; + } + + return result; +} + +QByteArray PDFEncoding::convertToEncoding(const QString& string, Encoding encoding) +{ + QByteArray result; + + const encoding::EncodingTable* table = getTableForEncoding(encoding); + Q_ASSERT(table); + + result.reserve(string.size()); + for (QChar character : string) + { + ushort unicode = character.unicode(); + unsigned char converted = 0; + + for (int i = 0; i < table->size(); ++i) + { + if (unicode == (*table)[static_cast(i)]) + { + converted = i; + break; + } + } + + result.push_back(converted); + } + + return result; +} + +bool PDFEncoding::canConvertToEncoding(const QString& string, Encoding encoding, QString* invalidCharacters) +{ + const encoding::EncodingTable* table = getTableForEncoding(encoding); + Q_ASSERT(table); + + bool isConvertible = true; + for (QChar character : string) + { + ushort unicode = character.unicode(); + bool converted = false; + + for (int i = 0; i < table->size(); ++i) + { + if (unicode == (*table)[static_cast(i)]) + { + converted = true; + break; + } + } + + if (!converted) + { + isConvertible = false; + + if (!invalidCharacters) + { + // We are not storing invalid characters - we can break on first not convertible + // character. + break; + } + + *invalidCharacters += character; + } + } + + return isConvertible; +} + +bool PDFEncoding::canConvertFromEncoding(const QByteArray& stream, Encoding encoding) +{ + const encoding::EncodingTable* table = getTableForEncoding(encoding); + for (const unsigned char index : stream) + { + QChar character = (*table)[index]; + if (character == QChar(0xfffd)) + { + return false; + } + } + + return true; +} + +QString PDFEncoding::convertTextString(const QByteArray& stream) +{ + if (hasUnicodeLeadMarkings(stream)) + { + return convertFromUnicode(stream); + } + else if (hasUTF8LeadMarkings(stream)) + { + return QString::fromUtf8(stream); + } + else + { + return convert(stream, Encoding::PDFDoc); + } +} + +QString PDFEncoding::convertFromUnicode(const QByteArray& stream) +{ + const ushort* bytes = reinterpret_cast(stream.data()); + const int sizeInChars = stream.size(); + const int sizeSizeInUShorts = sizeInChars / sizeof(const ushort) * sizeof(char); + + return QString::fromUtf16(bytes, sizeSizeInUShorts); +} + +QDateTime PDFEncoding::convertToDateTime(const QByteArray& stream) +{ + // According to the specification, string has form: + // (D:YYYYMMDDHHmmSSOHH'mm'), where + // YYYY - year (0000-9999) + // MM - month (1-12) + // DD - day (01-31) + // HH - hour (00-23) + // mm - minute (00-59) + // SS - second (00-59) + // O - 'Z' or '+' or '-' means zero offset / positive offset / negative offset + // HH' - hour offset + // mm' - minute offset + + auto it = stream.cbegin(); + auto itEnd = stream.cend(); + + constexpr const char* PREFIX = "D:"; + if (stream.startsWith(PREFIX)) + { + std::advance(it, std::strlen(PREFIX)); + } + + auto readInteger = [&it, &itEnd](int size, int defaultValue) -> int + { + const int remaining = std::distance(it, itEnd); + if (size <= remaining) + { + int value = 0; + + for (int i = 0; i < size; ++i) + { + const char currentChar = *it++; + if (std::isdigit(static_cast(currentChar))) + { + value = value * 10 + currentChar - '0'; + } + else + { + // This means error - digit is supposed to be here + return -1; + } + } + + return value; + } + + // No remaining part, use default value + return defaultValue; + }; + + int year = readInteger(4, 0); + int month = readInteger(2, 1); + int day = readInteger(2, 1); + int hour = readInteger(2, 0); + int minute = readInteger(2, 0); + int second = readInteger(2, 0); + bool negative = it != itEnd && *it++ == '-'; + int hourOffset = readInteger(2, 0); + if (it != itEnd) + { + // Skip ' character + ++it; + } + int minuteOffset = readInteger(2, 0); + + const int offset = hourOffset * 3600 + minuteOffset * 60; + + QDate parsedDate(year, month, day); + QTime parsedTime(hour, minute, second); + QTimeZone parsedTimeZone(negative ? -offset : offset); + + if (parsedDate.isValid() && parsedTime.isValid() && parsedTimeZone.isValid()) + { + return QDateTime(parsedDate, parsedTime, parsedTimeZone); + } + + return QDateTime(); +} + +QByteArray PDFEncoding::convertDateTimeToString(QDateTime dateTime) +{ + QDateTime utcDateTime = dateTime.toUTC(); + QString convertedDateTime = QString("D:%1").arg(utcDateTime.toString("yyyyMMddhhmmss")); + return convertedDateTime.toLatin1(); +} + +const encoding::EncodingTable* PDFEncoding::getTableForEncoding(Encoding encoding) +{ + switch (encoding) + { + case Encoding::Standard: + return &pdf::encoding::STANDARD_ENCODING_CONVERSION_TABLE; + + case Encoding::MacRoman: + return &pdf::encoding::MAC_ROMAN_ENCODING_CONVERSION_TABLE; + + case Encoding::WinAnsi: + return &pdf::encoding::WIN_ANSI_ENCODING_CONVERSION_TABLE; + + case Encoding::PDFDoc: + return &pdf::encoding::PDF_DOC_ENCODING_CONVERSION_TABLE; + + case Encoding::MacExpert: + return &pdf::encoding::MAC_EXPERT_ENCODING_CONVERSION_TABLE; + + case Encoding::Symbol: + return &pdf::encoding::SYMBOL_SET_ENCODING_CONVERSION_TABLE; + + case Encoding::ZapfDingbats: + return &pdf::encoding::ZAPF_DINGBATS_ENCODING_CONVERSION_TABLE; + + case Encoding::MacOsRoman: + return &pdf::encoding::MAC_OS_ENCODING_CONVERSION_TABLE; + } + + // Unknown encoding? + Q_ASSERT(false); + return nullptr; +} + +QString PDFEncoding::convertSmartFromByteStringToUnicode(const QByteArray& stream, bool* isBinary) +{ + if (isBinary) + { + *isBinary = false; + } + + if (hasUnicodeLeadMarkings(stream)) + { + QTextCodec::ConverterState state = { }; + + { + QTextCodec* codec = QTextCodec::codecForName("UTF-16BE"); + QString text = codec->toUnicode(stream.constData(), stream.length(), &state); + if (state.invalidChars == 0) + { + return text; + } + } + + { + QTextCodec* codec = QTextCodec::codecForName("UTF-16LE"); + QString text = codec->toUnicode(stream.constData(), stream.length(), &state); + if (state.invalidChars == 0) + { + return text; + } + } + } + + if (hasUTF8LeadMarkings(stream)) + { + QTextCodec::ConverterState state = { }; + + QTextCodec* codec = QTextCodec::codecForName("UTF-8"); + QString text = codec->toUnicode(stream.constData(), stream.length(), &state); + if (state.invalidChars == 0) + { + return text; + } + } + + if (canConvertFromEncoding(stream, Encoding::PDFDoc)) + { + return convert(stream, Encoding::PDFDoc); + } + + if (isBinary) + { + *isBinary = true; + } + return QString::fromLatin1(stream.toHex()).toUpper(); +} + +QString PDFEncoding::convertSmartFromByteStringToRepresentableQString(const QByteArray& stream) +{ + if (stream.startsWith("D:")) + { + QDateTime dateTime = convertToDateTime(stream); + if (dateTime.isValid()) + { + return dateTime.toString(Qt::TextDate); + } + } + + bool isBinary = false; + QString text = convertSmartFromByteStringToUnicode(stream, &isBinary); + + if (!isBinary) + { + return text; + } + + return stream.toPercentEncoding(" ", QByteArray(), '%'); +} + +QString PDFEncoding::getEncodingCharacters(Encoding encoding) +{ + QString string; + + if (const encoding::EncodingTable* table = getTableForEncoding(encoding)) + { + for (const QChar& character : *table) + { + if (character != QChar(0xFFFD)) + { + string += character; + } + } + } + + return string; +} + +QByteArray PDFEncoding::getPrintableCharacters() +{ + QByteArray result; + + const char min = std::numeric_limits::min(); + const char max = std::numeric_limits::max(); + for (char i = min; i < max; ++i) + { + if (std::isprint(static_cast(i))) + { + result.push_back(i); + } + } + + return result; +} + +bool PDFEncoding::hasUnicodeLeadMarkings(const QByteArray& stream) +{ + if (stream.size() >= 2) + { + if (static_cast(stream[0]) == 0xFE && static_cast(stream[1]) == 0xFF) + { + // UTF 16-BE + return true; + } + if (static_cast(stream[0]) == 0xFF && static_cast(stream[1]) == 0xFE) + { + // UTF 16-LE, forbidden in PDF 2.0 standard, but used in some PDF producers (wrongly) + return true; + } + } + + return false; +} + +bool PDFEncoding::hasUTF8LeadMarkings(const QByteArray& stream) +{ + if (stream.size() >= 3) + { + if (static_cast(stream[0]) == 239 && + static_cast(stream[1]) == 187 && + static_cast(stream[2]) == 191) + { + // UTF-8 + return true; + } + } + + return false; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfencoding.h b/Pdf4QtLib/sources/pdfencoding.h index fe87dbc..a38a3d5 100644 --- a/Pdf4QtLib/sources/pdfencoding.h +++ b/Pdf4QtLib/sources/pdfencoding.h @@ -1,155 +1,155 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFENCODING_H -#define PDFENCODING_H - -#include "pdfglobal.h" - -#include -#include - -#include - -namespace pdf -{ - -namespace encoding -{ -using EncodingTable = std::array; -} - -/// This class can convert byte stream to the QString in unicode encoding. -/// PDF has several encodings, see PDF Reference 1.7, Appendix D. -class PDF4QTLIBSHARED_EXPORT PDFEncoding -{ -public: - explicit PDFEncoding() = delete; - - enum class Encoding - { - Standard, ///< Appendix D, Section D.1, StandardEncoding - MacRoman, ///< Appendix D, Section D.1, MacRomanEncoding - WinAnsi, ///< Appendix D, Section D.1, WinAnsiEncoding - PDFDoc, ///< Appendix D, Section D.1/D.2, PDFDocEncoding - MacExpert, ///< Appendix D, Section D.3, MacExpertEncoding - Symbol, ///< Appendix D, Section D.4, Symbol Set and Encoding - ZapfDingbats, ///< Appendix D, Section D.5, Zapf Dingbats Encoding - - // Following encodings are used for internal use only and are not a part of PDF reference - MacOsRoman, ///< Encoding for Mac OS, differs from MacRoman for 15 characters - Custom, - Invalid - }; - - /// Converts byte array to the unicode string using specified encoding - /// \param stream Stream (byte array string) to be processed - /// \param encoding Encoding used to convert to unicode string - /// \returns Converted unicode string - static QString convert(const QByteArray& stream, Encoding encoding); - - /// Converts unicode string to the byte array using the specified encoding. - /// It performs reverse functionality than function \p convert. If the character - /// in the encoding is not found, then it is converted to character code 0. - /// \param string String to be converted - /// \param encoding Encoding used in the conversion - /// \sa convert - static QByteArray convertToEncoding(const QString& string, Encoding encoding); - - /// Verifies, if string with given unicode characters can be converted using - /// the specified encoding (so, all unicode characters present in the string - /// are also present in given encoding). - /// \param string String to be tested - /// \param encoding Encoding used in verification of conversion - /// \param[out] invalidCharacters Storage, where not convertible characters are inserted - static bool canConvertToEncoding(const QString& string, Encoding encoding, QString* invalidCharacters); - - /// Checks, if stream can be converted to string using encoding (i.e. all - /// characters are defined). If all characters are valid, then true is - /// returned. This is only guess. - /// \param stream Stream - /// \param encoding Target encoding - static bool canConvertFromEncoding(const QByteArray& stream, Encoding encoding); - - /// Convert text string to the unicode string, using either PDFDocEncoding, - /// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If - /// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF - /// \param Stream - /// \returns Converted unicode string - static QString convertTextString(const QByteArray& stream); - - /// Converts byte array from UTF-16BE encoding to QString with same encoding. - /// \param Stream - /// \returns Converted unicode string - static QString convertFromUnicode(const QByteArray& stream); - - /// Convert stream to date time according to PDF Reference 1.7, Chapter 3.8.1. - /// If date cannot be converted (string is invalid), then invalid QDateTime - /// is returned. - /// \param stream Stream, from which date/time is read - static QDateTime convertToDateTime(const QByteArray& stream); - - /// Convert date/time to string according to PDF Reference 1.7, Chapter 3.8.1. - /// If date is invalid, empty byte array is returned. - /// \param dateTime Date and time to be converted - static QByteArray convertDateTimeToString(QDateTime dateTime); - - /// Returns conversion table for particular encoding - /// \param encoding Encoding - static const encoding::EncodingTable* getTableForEncoding(Encoding encoding); - - /// Tries to convert stream to unicode string. Stream can be binary. - /// If this is the case, then hexadecimal representation of stream is returned. - /// Function checks if stream can be converted to unicode by heuristic - /// way, it is not always reliable. - /// \param stream Stream - /// \param[out] isBinary If specified, it is set to true if conversion failed - /// \returns Unicode string or string converted to hexadecimal representation - static QString convertSmartFromByteStringToUnicode(const QByteArray& stream, bool* isBinary); - - /// Tries to convert stream to representable string. If it cannot be done, - /// percentage encoding is used. - /// \param stream Stream - /// \returns Unicode string or string converted to percentage representation - static QString convertSmartFromByteStringToRepresentableQString(const QByteArray& stream); - - /// Returns all characters of the given encoding - /// \param encoding Encoding - /// \returns All characters reprezentable by encoding. - static QString getEncodingCharacters(Encoding encoding); - - /// Returns all printable characters - static QByteArray getPrintableCharacters(); - -private: - /// Returns true, if byte array has UTF-16BE/LE unicode marking bytes at the - /// stream start. If they are present, then byte stream is probably encoded - /// as unicode. - /// \param stream Stream to be tested - static bool hasUnicodeLeadMarkings(const QByteArray& stream); - - /// Returns true, if byte array has UTF-8 unicode marking bytes at the stream - /// start. If they are present, then byte stream is probably encoded - /// as UTF-8 string. - /// \note UTF-8 strings were added in PDF 2.0 specification - /// \param stream Stream to be tested - static bool hasUTF8LeadMarkings(const QByteArray& stream); -}; - -} // namespace pdf - -#endif // PDFENCODING_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFENCODING_H +#define PDFENCODING_H + +#include "pdfglobal.h" + +#include +#include + +#include + +namespace pdf +{ + +namespace encoding +{ +using EncodingTable = std::array; +} + +/// This class can convert byte stream to the QString in unicode encoding. +/// PDF has several encodings, see PDF Reference 1.7, Appendix D. +class PDF4QTLIBSHARED_EXPORT PDFEncoding +{ +public: + explicit PDFEncoding() = delete; + + enum class Encoding + { + Standard, ///< Appendix D, Section D.1, StandardEncoding + MacRoman, ///< Appendix D, Section D.1, MacRomanEncoding + WinAnsi, ///< Appendix D, Section D.1, WinAnsiEncoding + PDFDoc, ///< Appendix D, Section D.1/D.2, PDFDocEncoding + MacExpert, ///< Appendix D, Section D.3, MacExpertEncoding + Symbol, ///< Appendix D, Section D.4, Symbol Set and Encoding + ZapfDingbats, ///< Appendix D, Section D.5, Zapf Dingbats Encoding + + // Following encodings are used for internal use only and are not a part of PDF reference + MacOsRoman, ///< Encoding for Mac OS, differs from MacRoman for 15 characters + Custom, + Invalid + }; + + /// Converts byte array to the unicode string using specified encoding + /// \param stream Stream (byte array string) to be processed + /// \param encoding Encoding used to convert to unicode string + /// \returns Converted unicode string + static QString convert(const QByteArray& stream, Encoding encoding); + + /// Converts unicode string to the byte array using the specified encoding. + /// It performs reverse functionality than function \p convert. If the character + /// in the encoding is not found, then it is converted to character code 0. + /// \param string String to be converted + /// \param encoding Encoding used in the conversion + /// \sa convert + static QByteArray convertToEncoding(const QString& string, Encoding encoding); + + /// Verifies, if string with given unicode characters can be converted using + /// the specified encoding (so, all unicode characters present in the string + /// are also present in given encoding). + /// \param string String to be tested + /// \param encoding Encoding used in verification of conversion + /// \param[out] invalidCharacters Storage, where not convertible characters are inserted + static bool canConvertToEncoding(const QString& string, Encoding encoding, QString* invalidCharacters); + + /// Checks, if stream can be converted to string using encoding (i.e. all + /// characters are defined). If all characters are valid, then true is + /// returned. This is only guess. + /// \param stream Stream + /// \param encoding Target encoding + static bool canConvertFromEncoding(const QByteArray& stream, Encoding encoding); + + /// Convert text string to the unicode string, using either PDFDocEncoding, + /// or UTF-16BE encoding. Please see PDF Reference 1.7, Chapter 3.8.1. If + /// UTF-16BE encoding is used, then leading bytes should be 0xFE and 0xFF + /// \param Stream + /// \returns Converted unicode string + static QString convertTextString(const QByteArray& stream); + + /// Converts byte array from UTF-16BE encoding to QString with same encoding. + /// \param Stream + /// \returns Converted unicode string + static QString convertFromUnicode(const QByteArray& stream); + + /// Convert stream to date time according to PDF Reference 1.7, Chapter 3.8.1. + /// If date cannot be converted (string is invalid), then invalid QDateTime + /// is returned. + /// \param stream Stream, from which date/time is read + static QDateTime convertToDateTime(const QByteArray& stream); + + /// Convert date/time to string according to PDF Reference 1.7, Chapter 3.8.1. + /// If date is invalid, empty byte array is returned. + /// \param dateTime Date and time to be converted + static QByteArray convertDateTimeToString(QDateTime dateTime); + + /// Returns conversion table for particular encoding + /// \param encoding Encoding + static const encoding::EncodingTable* getTableForEncoding(Encoding encoding); + + /// Tries to convert stream to unicode string. Stream can be binary. + /// If this is the case, then hexadecimal representation of stream is returned. + /// Function checks if stream can be converted to unicode by heuristic + /// way, it is not always reliable. + /// \param stream Stream + /// \param[out] isBinary If specified, it is set to true if conversion failed + /// \returns Unicode string or string converted to hexadecimal representation + static QString convertSmartFromByteStringToUnicode(const QByteArray& stream, bool* isBinary); + + /// Tries to convert stream to representable string. If it cannot be done, + /// percentage encoding is used. + /// \param stream Stream + /// \returns Unicode string or string converted to percentage representation + static QString convertSmartFromByteStringToRepresentableQString(const QByteArray& stream); + + /// Returns all characters of the given encoding + /// \param encoding Encoding + /// \returns All characters reprezentable by encoding. + static QString getEncodingCharacters(Encoding encoding); + + /// Returns all printable characters + static QByteArray getPrintableCharacters(); + +private: + /// Returns true, if byte array has UTF-16BE/LE unicode marking bytes at the + /// stream start. If they are present, then byte stream is probably encoded + /// as unicode. + /// \param stream Stream to be tested + static bool hasUnicodeLeadMarkings(const QByteArray& stream); + + /// Returns true, if byte array has UTF-8 unicode marking bytes at the stream + /// start. If they are present, then byte stream is probably encoded + /// as UTF-8 string. + /// \note UTF-8 strings were added in PDF 2.0 specification + /// \param stream Stream to be tested + static bool hasUTF8LeadMarkings(const QByteArray& stream); +}; + +} // namespace pdf + +#endif // PDFENCODING_H diff --git a/Pdf4QtLib/sources/pdfexception.h b/Pdf4QtLib/sources/pdfexception.h index 732d42d..d3ef59a 100644 --- a/Pdf4QtLib/sources/pdfexception.h +++ b/Pdf4QtLib/sources/pdfexception.h @@ -1,118 +1,118 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFEXCEPTION_H -#define PDFEXCEPTION_H - -#include - -namespace pdf -{ - -class PDFException : public std::exception -{ -public: - PDFException(const QString& message) : - m_message(message) - { - - } - - /// Returns error message - const QString& getMessage() const { return m_message; } - -private: - QString m_message; -}; - -enum RenderErrorType -{ - Error, - Warning, - NotImplemented, - NotSupported, - Information -}; - -struct PDFRenderError -{ - explicit PDFRenderError() = default; - explicit PDFRenderError(RenderErrorType type, QString message) : - type(type), - message(std::move(message)) - { - - } - - RenderErrorType type = RenderErrorType::Error; - QString message; -}; - -class PDFRendererException : public std::exception -{ -public: - explicit PDFRendererException(RenderErrorType type, QString message) : - m_error(type, std::move(message)) - { - - } - - const PDFRenderError& getError() const { return m_error; } - -private: - PDFRenderError m_error; -}; - -/// Abstract class for reporting render errors -class PDFRenderErrorReporter -{ -public: - explicit PDFRenderErrorReporter() = default; - virtual ~PDFRenderErrorReporter() = default; - - /// Reports render errors - /// \param type Error type - /// \param message Error message - virtual void reportRenderError(RenderErrorType type, QString message) = 0; - - /// Reports render error, but only once - if same error was already reported, - /// then no new error is reported. - /// \param type Error type - /// \param message Error message - virtual void reportRenderErrorOnce(RenderErrorType type, QString message) = 0; -}; - -/// Dummy class for reporting render errors -class PDFRenderErrorReporterDummy : public PDFRenderErrorReporter -{ -public: - virtual void reportRenderError(RenderErrorType type, QString message) override - { - Q_UNUSED(type); - Q_UNUSED(message); - } - - virtual void reportRenderErrorOnce(RenderErrorType type, QString message) override - { - Q_UNUSED(type); - Q_UNUSED(message); - } -}; - -} // namespace pdf - -#endif // PDFEXCEPTION_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFEXCEPTION_H +#define PDFEXCEPTION_H + +#include + +namespace pdf +{ + +class PDFException : public std::exception +{ +public: + PDFException(const QString& message) : + m_message(message) + { + + } + + /// Returns error message + const QString& getMessage() const { return m_message; } + +private: + QString m_message; +}; + +enum RenderErrorType +{ + Error, + Warning, + NotImplemented, + NotSupported, + Information +}; + +struct PDFRenderError +{ + explicit PDFRenderError() = default; + explicit PDFRenderError(RenderErrorType type, QString message) : + type(type), + message(std::move(message)) + { + + } + + RenderErrorType type = RenderErrorType::Error; + QString message; +}; + +class PDFRendererException : public std::exception +{ +public: + explicit PDFRendererException(RenderErrorType type, QString message) : + m_error(type, std::move(message)) + { + + } + + const PDFRenderError& getError() const { return m_error; } + +private: + PDFRenderError m_error; +}; + +/// Abstract class for reporting render errors +class PDFRenderErrorReporter +{ +public: + explicit PDFRenderErrorReporter() = default; + virtual ~PDFRenderErrorReporter() = default; + + /// Reports render errors + /// \param type Error type + /// \param message Error message + virtual void reportRenderError(RenderErrorType type, QString message) = 0; + + /// Reports render error, but only once - if same error was already reported, + /// then no new error is reported. + /// \param type Error type + /// \param message Error message + virtual void reportRenderErrorOnce(RenderErrorType type, QString message) = 0; +}; + +/// Dummy class for reporting render errors +class PDFRenderErrorReporterDummy : public PDFRenderErrorReporter +{ +public: + virtual void reportRenderError(RenderErrorType type, QString message) override + { + Q_UNUSED(type); + Q_UNUSED(message); + } + + virtual void reportRenderErrorOnce(RenderErrorType type, QString message) override + { + Q_UNUSED(type); + Q_UNUSED(message); + } +}; + +} // namespace pdf + +#endif // PDFEXCEPTION_H diff --git a/Pdf4QtLib/sources/pdfexecutionpolicy.cpp b/Pdf4QtLib/sources/pdfexecutionpolicy.cpp index a4f257b..e9069dd 100644 --- a/Pdf4QtLib/sources/pdfexecutionpolicy.cpp +++ b/Pdf4QtLib/sources/pdfexecutionpolicy.cpp @@ -1,151 +1,151 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#include "pdfexecutionpolicy.h" - -#include -#include - -namespace pdf -{ - -struct PDFExecutionPolicyHolder -{ - PDFExecutionPolicyHolder() - { - qAddPostRoutine(&PDFExecutionPolicy::finalize); - } - ~PDFExecutionPolicyHolder() - { - auxiliary.waitForDone(); - primary.waitForDone(); - } - - PDFExecutionPolicy policy; - QThreadPool primary; - QThreadPool auxiliary; -} s_execution_policy; - -void PDFExecutionPolicy::setStrategy(Strategy strategy) -{ - s_execution_policy.policy.m_strategy.store(strategy, std::memory_order_relaxed); -} - -bool PDFExecutionPolicy::isParallelizing(Scope scope) -{ - const Strategy strategy = s_execution_policy.policy.m_strategy.load(std::memory_order_relaxed); - switch (strategy) - { - case Strategy::SingleThreaded: - return false; - - case Strategy::PageMultithreaded: - { - switch (scope) - { - case Scope::Page: - case Scope::Unknown: - return true; // We are parallelizing pages... - - case Scope::Content: - return false; - } - - break; - } - - case Strategy::AlwaysMultithreaded: - return true; - } - - // It should never go here... - Q_ASSERT(false); - return false; -} - -int PDFExecutionPolicy::getActiveThreadCount(Scope scope) -{ - return getThreadPool(scope)->activeThreadCount(); -} - -int PDFExecutionPolicy::getMaxThreadCount(Scope scope) -{ - return getThreadPool(scope)->maxThreadCount(); -} - -void PDFExecutionPolicy::setMaxThreadCount(Scope scope, int count) -{ - // Sanitize value! - count = qMax(count, 1); - getThreadPool(scope)->setMaxThreadCount(count); -} - -int PDFExecutionPolicy::getIdealThreadCount(Scope scope) -{ - Q_UNUSED(scope); - return QThread::idealThreadCount(); -} - -int PDFExecutionPolicy::getContentStreamCount() -{ - return s_execution_policy.policy.m_contentStreamsCount.load(std::memory_order_relaxed); -} - -void PDFExecutionPolicy::startProcessingContentStream() -{ - ++s_execution_policy.policy.m_contentStreamsCount; -} - -void PDFExecutionPolicy::endProcessingContentStream() -{ - --s_execution_policy.policy.m_contentStreamsCount; -} - -void PDFExecutionPolicy::finalize() -{ - s_execution_policy.auxiliary.waitForDone(); - s_execution_policy.primary.waitForDone(); -} - -QThreadPool* PDFExecutionPolicy::getThreadPool(PDFExecutionPolicy::Scope scope) -{ - switch (scope) - { - case Scope::Page: - case Scope::Unknown: - return &s_execution_policy.primary; - - case Scope::Content: - return &s_execution_policy.auxiliary; - - default: - Q_ASSERT(false); - break; - } - - return nullptr; -} - -PDFExecutionPolicy::PDFExecutionPolicy() : - m_contentStreamsCount(0), - m_strategy(Strategy::PageMultithreaded) -{ - -} - -} // namespace pdf +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#include "pdfexecutionpolicy.h" + +#include +#include + +namespace pdf +{ + +struct PDFExecutionPolicyHolder +{ + PDFExecutionPolicyHolder() + { + qAddPostRoutine(&PDFExecutionPolicy::finalize); + } + ~PDFExecutionPolicyHolder() + { + auxiliary.waitForDone(); + primary.waitForDone(); + } + + PDFExecutionPolicy policy; + QThreadPool primary; + QThreadPool auxiliary; +} s_execution_policy; + +void PDFExecutionPolicy::setStrategy(Strategy strategy) +{ + s_execution_policy.policy.m_strategy.store(strategy, std::memory_order_relaxed); +} + +bool PDFExecutionPolicy::isParallelizing(Scope scope) +{ + const Strategy strategy = s_execution_policy.policy.m_strategy.load(std::memory_order_relaxed); + switch (strategy) + { + case Strategy::SingleThreaded: + return false; + + case Strategy::PageMultithreaded: + { + switch (scope) + { + case Scope::Page: + case Scope::Unknown: + return true; // We are parallelizing pages... + + case Scope::Content: + return false; + } + + break; + } + + case Strategy::AlwaysMultithreaded: + return true; + } + + // It should never go here... + Q_ASSERT(false); + return false; +} + +int PDFExecutionPolicy::getActiveThreadCount(Scope scope) +{ + return getThreadPool(scope)->activeThreadCount(); +} + +int PDFExecutionPolicy::getMaxThreadCount(Scope scope) +{ + return getThreadPool(scope)->maxThreadCount(); +} + +void PDFExecutionPolicy::setMaxThreadCount(Scope scope, int count) +{ + // Sanitize value! + count = qMax(count, 1); + getThreadPool(scope)->setMaxThreadCount(count); +} + +int PDFExecutionPolicy::getIdealThreadCount(Scope scope) +{ + Q_UNUSED(scope); + return QThread::idealThreadCount(); +} + +int PDFExecutionPolicy::getContentStreamCount() +{ + return s_execution_policy.policy.m_contentStreamsCount.load(std::memory_order_relaxed); +} + +void PDFExecutionPolicy::startProcessingContentStream() +{ + ++s_execution_policy.policy.m_contentStreamsCount; +} + +void PDFExecutionPolicy::endProcessingContentStream() +{ + --s_execution_policy.policy.m_contentStreamsCount; +} + +void PDFExecutionPolicy::finalize() +{ + s_execution_policy.auxiliary.waitForDone(); + s_execution_policy.primary.waitForDone(); +} + +QThreadPool* PDFExecutionPolicy::getThreadPool(PDFExecutionPolicy::Scope scope) +{ + switch (scope) + { + case Scope::Page: + case Scope::Unknown: + return &s_execution_policy.primary; + + case Scope::Content: + return &s_execution_policy.auxiliary; + + default: + Q_ASSERT(false); + break; + } + + return nullptr; +} + +PDFExecutionPolicy::PDFExecutionPolicy() : + m_contentStreamsCount(0), + m_strategy(Strategy::PageMultithreaded) +{ + +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfexecutionpolicy.h b/Pdf4QtLib/sources/pdfexecutionpolicy.h index f13e64e..01ce797 100644 --- a/Pdf4QtLib/sources/pdfexecutionpolicy.h +++ b/Pdf4QtLib/sources/pdfexecutionpolicy.h @@ -1,187 +1,187 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFEXECUTIONPOLICY_H -#define PDFEXECUTIONPOLICY_H - -#include "pdfglobal.h" - -#include -#include - -#include -#include - -namespace pdf -{ -struct PDFExecutionPolicyHolder; - -/// Defines thread execution policy based on settings and actual number of page content -/// streams being processed. It can regulate number of threads executed at each -/// point, where execution policy is used. -class PDF4QTLIBSHARED_EXPORT PDFExecutionPolicy -{ -public: - - enum class Scope - { - Page, ///< Used, when we are processing page objects - Content, ///< Used, when we are processing objects from page content streams - Unknown ///< Unknown scope, usually tries to parallelize - }; - - enum class Strategy - { - SingleThreaded, - PageMultithreaded, - AlwaysMultithreaded - }; - - /// Sets multithreading strategy - /// \param strategy Strategy - static void setStrategy(Strategy strategy); - - /// Determines, if we should parallelize for scope - /// \param scope Scope for which we want to determine execution policy - static bool isParallelizing(Scope scope); - - template - class Runnable : public QRunnable - { - public: - explicit inline Runnable(ForwardIt it, ForwardIt itEnd, UnaryFunction* function, QSemaphore* semaphore) : - m_forwardIt(qMove(it)), - m_forwardItEnd(qMove(itEnd)), - m_function(function), - m_semaphore(semaphore), - m_packSize(static_cast(std::distance(m_forwardIt, m_forwardItEnd))) - { - setAutoDelete(true); - } - - virtual void run() override - { - QSemaphoreReleaser semaphoreReleaser(m_semaphore, m_packSize); - for (auto it = m_forwardIt; it != m_forwardItEnd; ++it) - { - (*m_function)(*it); - } - } - - private: - ForwardIt m_forwardIt; - ForwardIt m_forwardItEnd; - UnaryFunction* m_function; - QSemaphore* m_semaphore; - int m_packSize; - }; - - template - static void execute(Scope scope, ForwardIt first, ForwardIt last, UnaryFunction f) - { - if (isParallelizing(scope)) - { - QSemaphore semaphore(0); - int count = static_cast(std::distance(first, last)); - int remainder = count; - - int bucketSize = 1; - - // For page scope, we do not divide the tasks into buckets, i.e. - // each bucket will have size 1. But if we are in a content scope, - // then we are processing smaller task, so we divide the work - // into buckets of appropriate size. - if (scope != Scope::Page) - { - const int buckets = 32 * QThread::idealThreadCount(); - bucketSize = qMax(1, count / buckets); - } - - QThreadPool* pool = getThreadPool(scope); - - // Divide tasks into buckets with given bucket size - auto it = first; - while (remainder > 0) - { - const int currentSize = qMin(remainder, bucketSize); - - auto itStart = it; - auto itEnd = std::next(it, currentSize); - pool->start(new Runnable(itStart, itEnd, &f, &semaphore)); - - remainder -= currentSize; - std::advance(it, currentSize); - } - - Q_ASSERT(it == last); - - semaphore.acquire(count); - } - else - { - std::for_each(std::execution::sequenced_policy(), first, last, f); - } - } - - template - static void sort(Scope scope, ForwardIt first, ForwardIt last, Comparator f) - { - Q_UNUSED(scope); - - // We always sort by single thread - std::sort(std::execution::sequenced_policy(), first, last, f); - } - - /// Returns number of active threads for given scope - static int getActiveThreadCount(Scope scope); - - /// Returns maximal number of threads for given scope - static int getMaxThreadCount(Scope scope); - - /// Sets maximal number of threads for given scope - static void setMaxThreadCount(Scope scope, int count); - - /// Returns ideal thread count for given scope - static int getIdealThreadCount(Scope scope); - - /// Returns number of currently processed content streams - static int getContentStreamCount(); - - /// Starts processing content stream - static void startProcessingContentStream(); - - /// Ends processing content stream - static void endProcessingContentStream(); - - /// Finalize multithreading - must be called at the end of program - static void finalize(); - -private: - friend struct PDFExecutionPolicyHolder; - - /// Returns thread pool based on scope - static QThreadPool* getThreadPool(Scope scope); - - explicit PDFExecutionPolicy(); - - std::atomic m_contentStreamsCount; - std::atomic m_strategy; -}; - -} // namespace pdf - -#endif // PDFEXECUTIONPOLICY_H +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFEXECUTIONPOLICY_H +#define PDFEXECUTIONPOLICY_H + +#include "pdfglobal.h" + +#include +#include + +#include +#include + +namespace pdf +{ +struct PDFExecutionPolicyHolder; + +/// Defines thread execution policy based on settings and actual number of page content +/// streams being processed. It can regulate number of threads executed at each +/// point, where execution policy is used. +class PDF4QTLIBSHARED_EXPORT PDFExecutionPolicy +{ +public: + + enum class Scope + { + Page, ///< Used, when we are processing page objects + Content, ///< Used, when we are processing objects from page content streams + Unknown ///< Unknown scope, usually tries to parallelize + }; + + enum class Strategy + { + SingleThreaded, + PageMultithreaded, + AlwaysMultithreaded + }; + + /// Sets multithreading strategy + /// \param strategy Strategy + static void setStrategy(Strategy strategy); + + /// Determines, if we should parallelize for scope + /// \param scope Scope for which we want to determine execution policy + static bool isParallelizing(Scope scope); + + template + class Runnable : public QRunnable + { + public: + explicit inline Runnable(ForwardIt it, ForwardIt itEnd, UnaryFunction* function, QSemaphore* semaphore) : + m_forwardIt(qMove(it)), + m_forwardItEnd(qMove(itEnd)), + m_function(function), + m_semaphore(semaphore), + m_packSize(static_cast(std::distance(m_forwardIt, m_forwardItEnd))) + { + setAutoDelete(true); + } + + virtual void run() override + { + QSemaphoreReleaser semaphoreReleaser(m_semaphore, m_packSize); + for (auto it = m_forwardIt; it != m_forwardItEnd; ++it) + { + (*m_function)(*it); + } + } + + private: + ForwardIt m_forwardIt; + ForwardIt m_forwardItEnd; + UnaryFunction* m_function; + QSemaphore* m_semaphore; + int m_packSize; + }; + + template + static void execute(Scope scope, ForwardIt first, ForwardIt last, UnaryFunction f) + { + if (isParallelizing(scope)) + { + QSemaphore semaphore(0); + int count = static_cast(std::distance(first, last)); + int remainder = count; + + int bucketSize = 1; + + // For page scope, we do not divide the tasks into buckets, i.e. + // each bucket will have size 1. But if we are in a content scope, + // then we are processing smaller task, so we divide the work + // into buckets of appropriate size. + if (scope != Scope::Page) + { + const int buckets = 32 * QThread::idealThreadCount(); + bucketSize = qMax(1, count / buckets); + } + + QThreadPool* pool = getThreadPool(scope); + + // Divide tasks into buckets with given bucket size + auto it = first; + while (remainder > 0) + { + const int currentSize = qMin(remainder, bucketSize); + + auto itStart = it; + auto itEnd = std::next(it, currentSize); + pool->start(new Runnable(itStart, itEnd, &f, &semaphore)); + + remainder -= currentSize; + std::advance(it, currentSize); + } + + Q_ASSERT(it == last); + + semaphore.acquire(count); + } + else + { + std::for_each(std::execution::sequenced_policy(), first, last, f); + } + } + + template + static void sort(Scope scope, ForwardIt first, ForwardIt last, Comparator f) + { + Q_UNUSED(scope); + + // We always sort by single thread + std::sort(std::execution::sequenced_policy(), first, last, f); + } + + /// Returns number of active threads for given scope + static int getActiveThreadCount(Scope scope); + + /// Returns maximal number of threads for given scope + static int getMaxThreadCount(Scope scope); + + /// Sets maximal number of threads for given scope + static void setMaxThreadCount(Scope scope, int count); + + /// Returns ideal thread count for given scope + static int getIdealThreadCount(Scope scope); + + /// Returns number of currently processed content streams + static int getContentStreamCount(); + + /// Starts processing content stream + static void startProcessingContentStream(); + + /// Ends processing content stream + static void endProcessingContentStream(); + + /// Finalize multithreading - must be called at the end of program + static void finalize(); + +private: + friend struct PDFExecutionPolicyHolder; + + /// Returns thread pool based on scope + static QThreadPool* getThreadPool(Scope scope); + + explicit PDFExecutionPolicy(); + + std::atomic m_contentStreamsCount; + std::atomic m_strategy; +}; + +} // namespace pdf + +#endif // PDFEXECUTIONPOLICY_H diff --git a/Pdf4QtLib/sources/pdffile.cpp b/Pdf4QtLib/sources/pdffile.cpp index 1bdb2e9..53578da 100644 --- a/Pdf4QtLib/sources/pdffile.cpp +++ b/Pdf4QtLib/sources/pdffile.cpp @@ -1,588 +1,588 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdffile.h" -#include "pdfdocument.h" -#include "pdfencoding.h" - -namespace pdf -{ - -QString PDFFileSpecification::getPlatformFileName() const -{ - // UF has maximal precedence, because it is unicode string - if (!m_UF.isEmpty()) - { - return m_UF; - } - - if (!m_F.isEmpty()) - { - return QString::fromLatin1(m_F); - } - -#ifdef Q_OS_WIN - for (const QByteArray& platformName : { m_DOS, m_Mac, m_Unix }) - { - if (!platformName.isEmpty()) - { - return QString::fromLatin1(platformName); - } - } -#endif - -#ifdef Q_OS_UNIX - for (const QByteArray& platformName : { m_Unix, m_Mac, m_DOS }) - { - if (!platformName.isEmpty()) - { - return QString::fromLatin1(platformName); - } - } -#endif - -#ifdef Q_OS_MAC - for (const QByteArray& platformName : { m_Mac, m_Unix, m_DOS }) - { - if (!platformName.isEmpty()) - { - return QString::fromLatin1(platformName); - } - } -#endif - - return QString(); -} - -const PDFEmbeddedFile* PDFFileSpecification::getPlatformFile() const -{ - if (m_embeddedFiles.count("UF")) - { - return &m_embeddedFiles.at("UF"); - } - - if (m_embeddedFiles.count("F")) - { - return &m_embeddedFiles.at("F"); - } - -#ifdef Q_OS_WIN - if (m_embeddedFiles.count("DOS")) - { - return &m_embeddedFiles.at("DOS"); - } -#endif - -#ifdef Q_OS_UNIX - if (m_embeddedFiles.count("Unix")) - { - return &m_embeddedFiles.at("Unix"); - } -#endif - -#ifdef Q_OS_MAC - if (m_embeddedFiles.count("Mac")) - { - return &m_embeddedFiles.at("Mac"); - } -#endif - - return nullptr; -} - -PDFFileSpecification PDFFileSpecification::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFFileSpecification result; - - if (object.isReference()) - { - result.m_selfReference = object.getReference(); - } - - object = storage->getObject(object); - - if (object.isString()) - { - result.m_UF = PDFEncoding::convertTextString(object.getString()); - } - else if (object.isDictionary()) - { - PDFDocumentDataLoaderDecorator loader(storage); - const PDFDictionary* dictionary = object.getDictionary(); - - result.m_fileSystem = loader.readNameFromDictionary(dictionary, "FS"); - result.m_F = loader.readStringFromDictionary(dictionary, "F"); - result.m_UF = loader.readTextStringFromDictionary(dictionary, "UF", QString()); - result.m_DOS = loader.readStringFromDictionary(dictionary, "DOS"); - result.m_Mac = loader.readStringFromDictionary(dictionary, "Mac"); - result.m_Unix = loader.readStringFromDictionary(dictionary, "Unix"); - result.m_id = PDFFileIdentifier::parse(storage, dictionary->get("ID")); - result.m_volatile = loader.readBooleanFromDictionary(dictionary, "V", false); - result.m_description = loader.readTextStringFromDictionary(dictionary, "Desc", QString()); - result.m_collection = loader.readReferenceFromDictionary(dictionary, "CI"); - result.m_thumbnailReference = loader.readReferenceFromDictionary(dictionary, "Thumb"); - result.m_encryptedPayload = dictionary->get("EP"); - - constexpr const std::array relationships = { - std::pair{ "Unspecified", AssociatedFileRelationship::Unspecified }, - std::pair{ "Source", AssociatedFileRelationship::Source }, - std::pair{ "Data", AssociatedFileRelationship::Data }, - std::pair{ "Alternative", AssociatedFileRelationship::Alternative }, - std::pair{ "Supplement", AssociatedFileRelationship::Supplement }, - std::pair{ "EncryptedPayload", AssociatedFileRelationship::EncryptedPayload }, - std::pair{ "FormData", AssociatedFileRelationship::FormData }, - std::pair{ "Schema", AssociatedFileRelationship::Schema }, - }; - - result.m_associatedFileRelationship = loader.readEnumByName(dictionary->get("AFRelationship"), relationships.begin(), relationships.end(), AssociatedFileRelationship::Unspecified); - - PDFObject embeddedFiles = storage->getObject(dictionary->get("EF")); - PDFObject relatedFiles = storage->getObject(dictionary->get("RF")); - if (embeddedFiles.isDictionary()) - { - const PDFDictionary* embeddedFilesDictionary = embeddedFiles.getDictionary(); - const PDFDictionary* relatedFilesDictionary = relatedFiles.isDictionary() ? relatedFiles.getDictionary() : nullptr; - for (size_t i = 0; i < embeddedFilesDictionary->getCount(); ++i) - { - QByteArray key = embeddedFilesDictionary->getKey(i).getString(); - result.m_embeddedFiles[key] = PDFEmbeddedFile::parse(storage, embeddedFilesDictionary->getValue(i)); - - if (relatedFilesDictionary) - { - PDFObject relatedFileArrayObject = storage->getObject(relatedFilesDictionary->get(key)); - if (relatedFileArrayObject.isArray()) - { - const PDFArray* relatedFileArray = relatedFileArrayObject.getArray(); - const size_t relatedFilesCount = relatedFileArray->getCount() / 2; - - RelatedFiles& relatedFiles = result.m_relatedFiles[key]; - relatedFiles.reserve(relatedFilesCount); - for (size_t i = 0; i < relatedFilesCount; ++i) - { - RelatedFile relatedFile; - relatedFile.name = loader.readString(relatedFileArray->getItem(2 * i)); - relatedFile.fileReference = loader.readReference(relatedFileArray->getItem(2 * i + 1)); - relatedFiles.emplace_back(qMove(relatedFile)); - } - } - } - } - } - } - - return result; -} - -PDFEmbeddedFile PDFEmbeddedFile::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFEmbeddedFile result; - object = storage->getObject(object); - - if (object.isStream()) - { - const PDFStream* stream = object.getStream(); - const PDFDictionary* dictionary = stream->getDictionary(); - PDFDocumentDataLoaderDecorator loader(storage); - result.m_stream = object; - result.m_subtype = loader.readNameFromDictionary(dictionary, "Subtype"); - - const PDFObject& paramsObject = storage->getObject(dictionary->get("Params")); - if (paramsObject.isDictionary()) - { - const PDFDictionary* paramsDictionary = paramsObject.getDictionary(); - auto getDateTime = [&loader, paramsDictionary](const char* name) - { - QByteArray ba = loader.readStringFromDictionary(paramsDictionary, name); - if (!ba.isEmpty()) - { - return PDFEncoding::convertToDateTime(ba); - } - return QDateTime(); - }; - - result.m_size = loader.readIntegerFromDictionary(paramsDictionary, "Size", -1); - result.m_creationDate = getDateTime("CreationDate"); - result.m_modifiedDate = getDateTime("ModDate"); - result.m_checksum = loader.readStringFromDictionary(paramsDictionary, "CheckSum"); - } - } - - return result; -} - -PDFFileIdentifier PDFFileIdentifier::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFFileIdentifier result; - PDFDocumentDataLoaderDecorator loader(storage); - std::vector identifiers = loader.readStringArray(object); - - if (identifiers.size() >= 1) - { - result.m_permanentIdentifier = qMove(identifiers[0]); - } - - if (identifiers.size() >= 2) - { - result.m_changingIdentifier = qMove(identifiers[1]); - } - - return result; -} - -PDFCollectionField PDFCollectionField::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFCollectionField result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - constexpr const std::array fieldKinds = { - std::pair{ "S", Kind::TextField }, - std::pair{ "D", Kind::DateField }, - std::pair{ "N", Kind::NumberField }, - std::pair{ "F", Kind::FileName }, - std::pair{ "Desc", Kind::Description }, - std::pair{ "ModDate", Kind::ModifiedDate }, - std::pair{ "CreationDate", Kind::CreationDate }, - std::pair{ "Size", Kind::Size }, - std::pair{ "CompressedSize", Kind::CompressedSize }, - }; - - result.m_kind = loader.readEnumByName(dictionary->get("Subtype"), fieldKinds.begin(), fieldKinds.end(), Kind::Invalid); - - switch (result.m_kind) - { - case Kind::Invalid: - result.m_value = Value::Invalid; - break; - - case Kind::TextField: - result.m_value = Value::TextString; - break; - - case Kind::DateField: - result.m_value = Value::DateTime; - break; - - case Kind::NumberField: - result.m_value = Value::Number; - break; - - case Kind::FileName: - result.m_value = Value::TextString; - break; - - case Kind::Description: - result.m_value = Value::TextString; - break; - - case Kind::ModifiedDate: - result.m_value = Value::DateTime; - break; - - case Kind::CreationDate: - result.m_value = Value::DateTime; - break; - - case Kind::Size: - result.m_value = Value::Number; - break; - - case Kind::CompressedSize: - result.m_value = Value::Number; - break; - - default: - Q_ASSERT(false); - break; - } - - result.m_fieldName = loader.readTextStringFromDictionary(dictionary, "N", QString()); - result.m_order = loader.readIntegerFromDictionary(dictionary, "O", 0); - result.m_visible = loader.readBooleanFromDictionary(dictionary, "V", true); - result.m_editable = loader.readBooleanFromDictionary(dictionary, "E", false); - } - - return result; -} - -const PDFCollectionField* PDFCollectionSchema::getField(const QByteArray& key) const -{ - auto it = m_fields.find(key); - if (it != m_fields.cend()) - { - return &it->second; - } - else - { - static PDFCollectionField dummy; - return &dummy; - } -} - -PDFCollectionSchema PDFCollectionSchema::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFCollectionSchema result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - const size_t count = dictionary->getCount(); - for (size_t i = 0; i < count; ++i) - { - QByteArray key = dictionary->getKey(i).getString(); - - if (key == "Type") - { - continue; - } - - result.m_fields[key] = PDFCollectionField::parse(storage, dictionary->getValue(i)); - } - } - - return result; -} - -PDFCollection PDFCollection::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFCollection result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - constexpr const std::array viewModes = { - std::pair{ "D", ViewMode::Details }, - std::pair{ "T", ViewMode::Tiles }, - std::pair{ "H", ViewMode::Hidden }, - std::pair{ "C", ViewMode::Navigation } - }; - - result.m_schema = PDFCollectionSchema::parse(storage, dictionary->get("Schema")); - result.m_document = loader.readStringFromDictionary(dictionary, "D"); - result.m_viewMode = loader.readEnumByName(dictionary->get("View"), viewModes.begin(), viewModes.end(), ViewMode::Details); - result.m_navigator = loader.readReferenceFromDictionary(dictionary, "Navigator"); - - const PDFDictionary* colorDictionary = storage->getDictionaryFromObject(dictionary->get("Colors")); - if (colorDictionary) - { - result.m_colors[Background] = loader.readRGBColorFromDictionary(colorDictionary, "Background", Qt::white); - result.m_colors[CardBackground] = loader.readRGBColorFromDictionary(colorDictionary, "CardBackground", Qt::white); - result.m_colors[CardBorder] = loader.readRGBColorFromDictionary(colorDictionary, "CardBorder", Qt::white); - result.m_colors[PrimaryText] = loader.readRGBColorFromDictionary(colorDictionary, "PrimaryText", Qt::black); - result.m_colors[SecondaryText] = loader.readRGBColorFromDictionary(colorDictionary, "SecondaryText", Qt::black); - } - - const PDFDictionary* sortDictionary = storage->getDictionaryFromObject(dictionary->get("Sort")); - if (sortDictionary) - { - result.m_sortAscending = loader.readBooleanFromDictionary(sortDictionary, "A", true); - const PDFObject& columns = storage->getObject(sortDictionary->get("S")); - if (columns.isName()) - { - result.m_sortColumns.emplace_back(SortColumn{loader.readName(columns), result.m_sortAscending}); - } - else - { - std::vector names = loader.readNameArray(columns); - for (QByteArray& name : names) - { - result.m_sortColumns.emplace_back(SortColumn{qMove(name), result.m_sortAscending}); - } - } - - const PDFObject& sortDirection = storage->getObject(sortDictionary->get("A")); - if (sortDirection.isArray()) - { - const PDFArray* sortDirectionArray = sortDirection.getArray(); - const size_t size = qMin(result.m_sortColumns.size(), sortDirectionArray->getCount()); - for (size_t i = 0; i < size; ++i) - { - result.m_sortColumns[i].ascending = loader.readBoolean(sortDirectionArray->getItem(i), result.m_sortAscending); - } - } - } - - result.m_folderRoot = loader.readReferenceFromDictionary(dictionary, "Folders"); - - const PDFDictionary* splitDictionary = storage->getDictionaryFromObject(dictionary->get("Split")); - if (splitDictionary) - { - constexpr const std::array splitModes = { - std::pair{ "H", SplitMode::Horizontally }, - std::pair{ "V", SplitMode::Vertically }, - std::pair{ "N", SplitMode::None } - }; - - result.m_splitMode = loader.readEnumByName(splitDictionary->get("Direction"), splitModes.begin(), splitModes.end(), SplitMode::None); - result.m_splitProportion = loader.readNumberFromDictionary(splitDictionary, "Position", 30); - } - } - - return result; -} - -PDFCollectionFolder PDFCollectionFolder::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFCollectionFolder result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - result.m_ID = loader.readIntegerFromDictionary(dictionary, "ID", 0); - result.m_name = loader.readTextStringFromDictionary(dictionary, "Name", QString()); - result.m_parent = loader.readReferenceFromDictionary(dictionary, "Parent"); - result.m_child = loader.readReferenceFromDictionary(dictionary, "Child"); - result.m_next = loader.readReferenceFromDictionary(dictionary, "Next"); - result.m_collection = loader.readReferenceFromDictionary(dictionary, "CI"); - result.m_description = loader.readTextStringFromDictionary(dictionary, "Desc", QString()); - - QByteArray createdString = loader.readStringFromDictionary(dictionary, "CreationDate"); - if (!createdString.isEmpty()) - { - result.m_created = PDFEncoding::convertToDateTime(createdString); - } - QByteArray modifiedString = loader.readStringFromDictionary(dictionary, "ModDate"); - if (!modifiedString.isEmpty()) - { - result.m_modified = PDFEncoding::convertToDateTime(modifiedString); - } - - result.m_thumbnail = loader.readReferenceFromDictionary(dictionary, "Thumb"); - result.m_freeIds = loader.readIntegerArrayFromDictionary(dictionary, "Free"); - } - - return result; -} - -PDFCollectionNavigator PDFCollectionNavigator::parse(const PDFObjectStorage* storage, PDFObject object) -{ - PDFCollectionNavigator result; - - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - - auto getLayout = [&loader](const PDFObject& object) - { - constexpr const std::array layouts = { - std::pair{ "D", Layout::Details }, - std::pair{ "T", Layout::Tile }, - std::pair{ "H", Layout::Hidden }, - std::pair{ "FilmStrip", Layout::FilmStrip }, - std::pair{ "FreeForm", Layout::FreeForm }, - std::pair{ "Linear", Layout::Linear }, - std::pair{ "Tree", Layout::Tree } - }; - - return loader.readEnumByName(object, layouts.begin(), layouts.end(), Layout::Invalid); - }; - - result.m_layouts = None; - - PDFObject layoutObject = storage->getObject(dictionary->get("Layout")); - if (layoutObject.isArray()) - { - for (const PDFObject& object : *layoutObject.getArray()) - { - result.m_layouts |= getLayout(object); - } - } - else - { - result.m_layouts = getLayout(layoutObject); - } - } - - return result; -} - -QString PDFCollectionItem::getString(const QByteArray& key, const PDFObjectStorage* storage) const -{ - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - PDFObject valueObject = storage->getObject(dictionary->get(key)); - - if (valueObject.isDictionary()) - { - return loader.readTextString(valueObject.getDictionary()->get("D"), QString()); - } - - return loader.readTextString(valueObject, QString()); - } - - return QString(); -} - -QDateTime PDFCollectionItem::getDateTime(const QByteArray& key, const PDFObjectStorage* storage) const -{ - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - PDFObject valueObject = storage->getObject(dictionary->get(key)); - - if (valueObject.isDictionary()) - { - valueObject = storage->getObject(valueObject.getDictionary()->get("D")); - } - - if (valueObject.isString()) - { - return PDFEncoding::convertToDateTime(valueObject.getString()); - } - } - - return QDateTime(); -} - -PDFInteger PDFCollectionItem::getNumber(const QByteArray& key, const PDFObjectStorage* storage) const -{ - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - PDFObject valueObject = storage->getObject(dictionary->get(key)); - - if (valueObject.isDictionary()) - { - return loader.readInteger(valueObject.getDictionary()->get("D"), 0); - } - - return loader.readInteger(valueObject, 0); - } - - return 0; -} - -QString PDFCollectionItem::getPrefixString(const QByteArray& key, const PDFObjectStorage* storage) const -{ - if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) - { - PDFDocumentDataLoaderDecorator loader(storage); - PDFObject valueObject = storage->getObject(dictionary->get(key)); - - if (valueObject.isDictionary()) - { - return loader.readTextString(valueObject.getDictionary()->get("P"), QString()); - } - } - - return QString(); -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdffile.h" +#include "pdfdocument.h" +#include "pdfencoding.h" + +namespace pdf +{ + +QString PDFFileSpecification::getPlatformFileName() const +{ + // UF has maximal precedence, because it is unicode string + if (!m_UF.isEmpty()) + { + return m_UF; + } + + if (!m_F.isEmpty()) + { + return QString::fromLatin1(m_F); + } + +#ifdef Q_OS_WIN + for (const QByteArray& platformName : { m_DOS, m_Mac, m_Unix }) + { + if (!platformName.isEmpty()) + { + return QString::fromLatin1(platformName); + } + } +#endif + +#ifdef Q_OS_UNIX + for (const QByteArray& platformName : { m_Unix, m_Mac, m_DOS }) + { + if (!platformName.isEmpty()) + { + return QString::fromLatin1(platformName); + } + } +#endif + +#ifdef Q_OS_MAC + for (const QByteArray& platformName : { m_Mac, m_Unix, m_DOS }) + { + if (!platformName.isEmpty()) + { + return QString::fromLatin1(platformName); + } + } +#endif + + return QString(); +} + +const PDFEmbeddedFile* PDFFileSpecification::getPlatformFile() const +{ + if (m_embeddedFiles.count("UF")) + { + return &m_embeddedFiles.at("UF"); + } + + if (m_embeddedFiles.count("F")) + { + return &m_embeddedFiles.at("F"); + } + +#ifdef Q_OS_WIN + if (m_embeddedFiles.count("DOS")) + { + return &m_embeddedFiles.at("DOS"); + } +#endif + +#ifdef Q_OS_UNIX + if (m_embeddedFiles.count("Unix")) + { + return &m_embeddedFiles.at("Unix"); + } +#endif + +#ifdef Q_OS_MAC + if (m_embeddedFiles.count("Mac")) + { + return &m_embeddedFiles.at("Mac"); + } +#endif + + return nullptr; +} + +PDFFileSpecification PDFFileSpecification::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFFileSpecification result; + + if (object.isReference()) + { + result.m_selfReference = object.getReference(); + } + + object = storage->getObject(object); + + if (object.isString()) + { + result.m_UF = PDFEncoding::convertTextString(object.getString()); + } + else if (object.isDictionary()) + { + PDFDocumentDataLoaderDecorator loader(storage); + const PDFDictionary* dictionary = object.getDictionary(); + + result.m_fileSystem = loader.readNameFromDictionary(dictionary, "FS"); + result.m_F = loader.readStringFromDictionary(dictionary, "F"); + result.m_UF = loader.readTextStringFromDictionary(dictionary, "UF", QString()); + result.m_DOS = loader.readStringFromDictionary(dictionary, "DOS"); + result.m_Mac = loader.readStringFromDictionary(dictionary, "Mac"); + result.m_Unix = loader.readStringFromDictionary(dictionary, "Unix"); + result.m_id = PDFFileIdentifier::parse(storage, dictionary->get("ID")); + result.m_volatile = loader.readBooleanFromDictionary(dictionary, "V", false); + result.m_description = loader.readTextStringFromDictionary(dictionary, "Desc", QString()); + result.m_collection = loader.readReferenceFromDictionary(dictionary, "CI"); + result.m_thumbnailReference = loader.readReferenceFromDictionary(dictionary, "Thumb"); + result.m_encryptedPayload = dictionary->get("EP"); + + constexpr const std::array relationships = { + std::pair{ "Unspecified", AssociatedFileRelationship::Unspecified }, + std::pair{ "Source", AssociatedFileRelationship::Source }, + std::pair{ "Data", AssociatedFileRelationship::Data }, + std::pair{ "Alternative", AssociatedFileRelationship::Alternative }, + std::pair{ "Supplement", AssociatedFileRelationship::Supplement }, + std::pair{ "EncryptedPayload", AssociatedFileRelationship::EncryptedPayload }, + std::pair{ "FormData", AssociatedFileRelationship::FormData }, + std::pair{ "Schema", AssociatedFileRelationship::Schema }, + }; + + result.m_associatedFileRelationship = loader.readEnumByName(dictionary->get("AFRelationship"), relationships.begin(), relationships.end(), AssociatedFileRelationship::Unspecified); + + PDFObject embeddedFiles = storage->getObject(dictionary->get("EF")); + PDFObject relatedFiles = storage->getObject(dictionary->get("RF")); + if (embeddedFiles.isDictionary()) + { + const PDFDictionary* embeddedFilesDictionary = embeddedFiles.getDictionary(); + const PDFDictionary* relatedFilesDictionary = relatedFiles.isDictionary() ? relatedFiles.getDictionary() : nullptr; + for (size_t i = 0; i < embeddedFilesDictionary->getCount(); ++i) + { + QByteArray key = embeddedFilesDictionary->getKey(i).getString(); + result.m_embeddedFiles[key] = PDFEmbeddedFile::parse(storage, embeddedFilesDictionary->getValue(i)); + + if (relatedFilesDictionary) + { + PDFObject relatedFileArrayObject = storage->getObject(relatedFilesDictionary->get(key)); + if (relatedFileArrayObject.isArray()) + { + const PDFArray* relatedFileArray = relatedFileArrayObject.getArray(); + const size_t relatedFilesCount = relatedFileArray->getCount() / 2; + + RelatedFiles& relatedFiles = result.m_relatedFiles[key]; + relatedFiles.reserve(relatedFilesCount); + for (size_t i = 0; i < relatedFilesCount; ++i) + { + RelatedFile relatedFile; + relatedFile.name = loader.readString(relatedFileArray->getItem(2 * i)); + relatedFile.fileReference = loader.readReference(relatedFileArray->getItem(2 * i + 1)); + relatedFiles.emplace_back(qMove(relatedFile)); + } + } + } + } + } + } + + return result; +} + +PDFEmbeddedFile PDFEmbeddedFile::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFEmbeddedFile result; + object = storage->getObject(object); + + if (object.isStream()) + { + const PDFStream* stream = object.getStream(); + const PDFDictionary* dictionary = stream->getDictionary(); + PDFDocumentDataLoaderDecorator loader(storage); + result.m_stream = object; + result.m_subtype = loader.readNameFromDictionary(dictionary, "Subtype"); + + const PDFObject& paramsObject = storage->getObject(dictionary->get("Params")); + if (paramsObject.isDictionary()) + { + const PDFDictionary* paramsDictionary = paramsObject.getDictionary(); + auto getDateTime = [&loader, paramsDictionary](const char* name) + { + QByteArray ba = loader.readStringFromDictionary(paramsDictionary, name); + if (!ba.isEmpty()) + { + return PDFEncoding::convertToDateTime(ba); + } + return QDateTime(); + }; + + result.m_size = loader.readIntegerFromDictionary(paramsDictionary, "Size", -1); + result.m_creationDate = getDateTime("CreationDate"); + result.m_modifiedDate = getDateTime("ModDate"); + result.m_checksum = loader.readStringFromDictionary(paramsDictionary, "CheckSum"); + } + } + + return result; +} + +PDFFileIdentifier PDFFileIdentifier::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFFileIdentifier result; + PDFDocumentDataLoaderDecorator loader(storage); + std::vector identifiers = loader.readStringArray(object); + + if (identifiers.size() >= 1) + { + result.m_permanentIdentifier = qMove(identifiers[0]); + } + + if (identifiers.size() >= 2) + { + result.m_changingIdentifier = qMove(identifiers[1]); + } + + return result; +} + +PDFCollectionField PDFCollectionField::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFCollectionField result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + constexpr const std::array fieldKinds = { + std::pair{ "S", Kind::TextField }, + std::pair{ "D", Kind::DateField }, + std::pair{ "N", Kind::NumberField }, + std::pair{ "F", Kind::FileName }, + std::pair{ "Desc", Kind::Description }, + std::pair{ "ModDate", Kind::ModifiedDate }, + std::pair{ "CreationDate", Kind::CreationDate }, + std::pair{ "Size", Kind::Size }, + std::pair{ "CompressedSize", Kind::CompressedSize }, + }; + + result.m_kind = loader.readEnumByName(dictionary->get("Subtype"), fieldKinds.begin(), fieldKinds.end(), Kind::Invalid); + + switch (result.m_kind) + { + case Kind::Invalid: + result.m_value = Value::Invalid; + break; + + case Kind::TextField: + result.m_value = Value::TextString; + break; + + case Kind::DateField: + result.m_value = Value::DateTime; + break; + + case Kind::NumberField: + result.m_value = Value::Number; + break; + + case Kind::FileName: + result.m_value = Value::TextString; + break; + + case Kind::Description: + result.m_value = Value::TextString; + break; + + case Kind::ModifiedDate: + result.m_value = Value::DateTime; + break; + + case Kind::CreationDate: + result.m_value = Value::DateTime; + break; + + case Kind::Size: + result.m_value = Value::Number; + break; + + case Kind::CompressedSize: + result.m_value = Value::Number; + break; + + default: + Q_ASSERT(false); + break; + } + + result.m_fieldName = loader.readTextStringFromDictionary(dictionary, "N", QString()); + result.m_order = loader.readIntegerFromDictionary(dictionary, "O", 0); + result.m_visible = loader.readBooleanFromDictionary(dictionary, "V", true); + result.m_editable = loader.readBooleanFromDictionary(dictionary, "E", false); + } + + return result; +} + +const PDFCollectionField* PDFCollectionSchema::getField(const QByteArray& key) const +{ + auto it = m_fields.find(key); + if (it != m_fields.cend()) + { + return &it->second; + } + else + { + static PDFCollectionField dummy; + return &dummy; + } +} + +PDFCollectionSchema PDFCollectionSchema::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFCollectionSchema result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + const size_t count = dictionary->getCount(); + for (size_t i = 0; i < count; ++i) + { + QByteArray key = dictionary->getKey(i).getString(); + + if (key == "Type") + { + continue; + } + + result.m_fields[key] = PDFCollectionField::parse(storage, dictionary->getValue(i)); + } + } + + return result; +} + +PDFCollection PDFCollection::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFCollection result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + constexpr const std::array viewModes = { + std::pair{ "D", ViewMode::Details }, + std::pair{ "T", ViewMode::Tiles }, + std::pair{ "H", ViewMode::Hidden }, + std::pair{ "C", ViewMode::Navigation } + }; + + result.m_schema = PDFCollectionSchema::parse(storage, dictionary->get("Schema")); + result.m_document = loader.readStringFromDictionary(dictionary, "D"); + result.m_viewMode = loader.readEnumByName(dictionary->get("View"), viewModes.begin(), viewModes.end(), ViewMode::Details); + result.m_navigator = loader.readReferenceFromDictionary(dictionary, "Navigator"); + + const PDFDictionary* colorDictionary = storage->getDictionaryFromObject(dictionary->get("Colors")); + if (colorDictionary) + { + result.m_colors[Background] = loader.readRGBColorFromDictionary(colorDictionary, "Background", Qt::white); + result.m_colors[CardBackground] = loader.readRGBColorFromDictionary(colorDictionary, "CardBackground", Qt::white); + result.m_colors[CardBorder] = loader.readRGBColorFromDictionary(colorDictionary, "CardBorder", Qt::white); + result.m_colors[PrimaryText] = loader.readRGBColorFromDictionary(colorDictionary, "PrimaryText", Qt::black); + result.m_colors[SecondaryText] = loader.readRGBColorFromDictionary(colorDictionary, "SecondaryText", Qt::black); + } + + const PDFDictionary* sortDictionary = storage->getDictionaryFromObject(dictionary->get("Sort")); + if (sortDictionary) + { + result.m_sortAscending = loader.readBooleanFromDictionary(sortDictionary, "A", true); + const PDFObject& columns = storage->getObject(sortDictionary->get("S")); + if (columns.isName()) + { + result.m_sortColumns.emplace_back(SortColumn{loader.readName(columns), result.m_sortAscending}); + } + else + { + std::vector names = loader.readNameArray(columns); + for (QByteArray& name : names) + { + result.m_sortColumns.emplace_back(SortColumn{qMove(name), result.m_sortAscending}); + } + } + + const PDFObject& sortDirection = storage->getObject(sortDictionary->get("A")); + if (sortDirection.isArray()) + { + const PDFArray* sortDirectionArray = sortDirection.getArray(); + const size_t size = qMin(result.m_sortColumns.size(), sortDirectionArray->getCount()); + for (size_t i = 0; i < size; ++i) + { + result.m_sortColumns[i].ascending = loader.readBoolean(sortDirectionArray->getItem(i), result.m_sortAscending); + } + } + } + + result.m_folderRoot = loader.readReferenceFromDictionary(dictionary, "Folders"); + + const PDFDictionary* splitDictionary = storage->getDictionaryFromObject(dictionary->get("Split")); + if (splitDictionary) + { + constexpr const std::array splitModes = { + std::pair{ "H", SplitMode::Horizontally }, + std::pair{ "V", SplitMode::Vertically }, + std::pair{ "N", SplitMode::None } + }; + + result.m_splitMode = loader.readEnumByName(splitDictionary->get("Direction"), splitModes.begin(), splitModes.end(), SplitMode::None); + result.m_splitProportion = loader.readNumberFromDictionary(splitDictionary, "Position", 30); + } + } + + return result; +} + +PDFCollectionFolder PDFCollectionFolder::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFCollectionFolder result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + result.m_ID = loader.readIntegerFromDictionary(dictionary, "ID", 0); + result.m_name = loader.readTextStringFromDictionary(dictionary, "Name", QString()); + result.m_parent = loader.readReferenceFromDictionary(dictionary, "Parent"); + result.m_child = loader.readReferenceFromDictionary(dictionary, "Child"); + result.m_next = loader.readReferenceFromDictionary(dictionary, "Next"); + result.m_collection = loader.readReferenceFromDictionary(dictionary, "CI"); + result.m_description = loader.readTextStringFromDictionary(dictionary, "Desc", QString()); + + QByteArray createdString = loader.readStringFromDictionary(dictionary, "CreationDate"); + if (!createdString.isEmpty()) + { + result.m_created = PDFEncoding::convertToDateTime(createdString); + } + QByteArray modifiedString = loader.readStringFromDictionary(dictionary, "ModDate"); + if (!modifiedString.isEmpty()) + { + result.m_modified = PDFEncoding::convertToDateTime(modifiedString); + } + + result.m_thumbnail = loader.readReferenceFromDictionary(dictionary, "Thumb"); + result.m_freeIds = loader.readIntegerArrayFromDictionary(dictionary, "Free"); + } + + return result; +} + +PDFCollectionNavigator PDFCollectionNavigator::parse(const PDFObjectStorage* storage, PDFObject object) +{ + PDFCollectionNavigator result; + + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + + auto getLayout = [&loader](const PDFObject& object) + { + constexpr const std::array layouts = { + std::pair{ "D", Layout::Details }, + std::pair{ "T", Layout::Tile }, + std::pair{ "H", Layout::Hidden }, + std::pair{ "FilmStrip", Layout::FilmStrip }, + std::pair{ "FreeForm", Layout::FreeForm }, + std::pair{ "Linear", Layout::Linear }, + std::pair{ "Tree", Layout::Tree } + }; + + return loader.readEnumByName(object, layouts.begin(), layouts.end(), Layout::Invalid); + }; + + result.m_layouts = None; + + PDFObject layoutObject = storage->getObject(dictionary->get("Layout")); + if (layoutObject.isArray()) + { + for (const PDFObject& object : *layoutObject.getArray()) + { + result.m_layouts |= getLayout(object); + } + } + else + { + result.m_layouts = getLayout(layoutObject); + } + } + + return result; +} + +QString PDFCollectionItem::getString(const QByteArray& key, const PDFObjectStorage* storage) const +{ + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + PDFObject valueObject = storage->getObject(dictionary->get(key)); + + if (valueObject.isDictionary()) + { + return loader.readTextString(valueObject.getDictionary()->get("D"), QString()); + } + + return loader.readTextString(valueObject, QString()); + } + + return QString(); +} + +QDateTime PDFCollectionItem::getDateTime(const QByteArray& key, const PDFObjectStorage* storage) const +{ + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + PDFObject valueObject = storage->getObject(dictionary->get(key)); + + if (valueObject.isDictionary()) + { + valueObject = storage->getObject(valueObject.getDictionary()->get("D")); + } + + if (valueObject.isString()) + { + return PDFEncoding::convertToDateTime(valueObject.getString()); + } + } + + return QDateTime(); +} + +PDFInteger PDFCollectionItem::getNumber(const QByteArray& key, const PDFObjectStorage* storage) const +{ + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + PDFObject valueObject = storage->getObject(dictionary->get(key)); + + if (valueObject.isDictionary()) + { + return loader.readInteger(valueObject.getDictionary()->get("D"), 0); + } + + return loader.readInteger(valueObject, 0); + } + + return 0; +} + +QString PDFCollectionItem::getPrefixString(const QByteArray& key, const PDFObjectStorage* storage) const +{ + if (const PDFDictionary* dictionary = storage->getDictionaryFromObject(m_object)) + { + PDFDocumentDataLoaderDecorator loader(storage); + PDFObject valueObject = storage->getObject(dictionary->get(key)); + + if (valueObject.isDictionary()) + { + return loader.readTextString(valueObject.getDictionary()->get("P"), QString()); + } + } + + return QString(); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdffile.h b/Pdf4QtLib/sources/pdffile.h index 7a65e63..2b166c6 100644 --- a/Pdf4QtLib/sources/pdffile.h +++ b/Pdf4QtLib/sources/pdffile.h @@ -1,475 +1,475 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFFILE_H -#define PDFFILE_H - -#include "pdfobject.h" - -#include -#include - -namespace pdf -{ -class PDFObjectStorage; - -/// File identifier according section 14.4 of PDF 2.0 specification. -/// Each identifier consists of two parts - permanent identifier, which -/// is unique identifier based on original document, and changing identifier, -/// which is updated when document is being modified. -class PDF4QTLIBSHARED_EXPORT PDFFileIdentifier -{ -public: - explicit inline PDFFileIdentifier() = default; - - const QByteArray& getPermanentIdentifier() const { return m_permanentIdentifier; } - const QByteArray& getChangingIdentifier() const { return m_changingIdentifier; } - - static PDFFileIdentifier parse(const PDFObjectStorage* storage, PDFObject object); - -private: - QByteArray m_permanentIdentifier; - QByteArray m_changingIdentifier; -}; - -/// Provides description of collection item property field. It describes it's -/// kind, data type, if content of the property should be presented to the user, -/// and ordering, visibility and editability. -class PDF4QTLIBSHARED_EXPORT PDFCollectionField -{ -public: - explicit inline PDFCollectionField() = default; - - enum class Kind - { - Invalid, - TextField, - DateField, - NumberField, - FileName, - Description, - ModifiedDate, - CreationDate, - Size, - CompressedSize - }; - - enum class Value - { - Invalid, - TextString, - DateTime, - Number - }; - - Kind getKind() const { return m_kind; } - Value getValue() const { return m_value; } - QString getFieldName() const { return m_fieldName; } - PDFInteger getOrder() const { return m_order; } - bool isVisible() const { return m_visible; } - bool isEditable() const { return m_editable; } - - static PDFCollectionField parse(const PDFObjectStorage* storage, PDFObject object); - -private: - Kind m_kind = Kind::Invalid; - Value m_value = Value::Invalid; - QString m_fieldName; - PDFInteger m_order = 0; - bool m_visible = true; - bool m_editable = false; -}; - -/// Collection schema. Contains a list of defined fields. -/// Schema can be queried for field definition. -class PDF4QTLIBSHARED_EXPORT PDFCollectionSchema -{ -public: - explicit inline PDFCollectionSchema() = default; - - /// Returns true, if schema is valid, i.e. some fields - /// are defined. - bool isValid() const { return !m_fields.empty(); } - - /// Returns collection field. This function always returns - /// valid field definition. If field can't be found, then - /// default field (invalid) is returned. - /// \param key Key - const PDFCollectionField* getField(const QByteArray& key) const; - - /// Returns true, if field definition with given key is valid. - /// \param key Key - bool isFieldValid(const QByteArray& key) const { return getField(key)->getKind() != PDFCollectionField::Kind::Invalid;} - - static PDFCollectionSchema parse(const PDFObjectStorage* storage, PDFObject object); - -private: - std::map m_fields; -}; - -/// Collection of file attachments. In the PDF file, attached files -/// can be grouped in collection (if they are related to each other). -class PDF4QTLIBSHARED_EXPORT PDFCollection -{ -public: - explicit inline PDFCollection() = default; - - enum class ViewMode - { - /// Collection items should be displayed in details mode, - /// for example, multi-column table, which contains icon, - /// file name, and all properties listed in the schema. - Details, - - /// Collection should be presented in tile mode, each file - /// should have icon, and some information from the schema - /// dictionary. - Tiles, - - /// Collection should be initially hidden and should be presented - /// to the user if user performs some explicit action. - Hidden, - - /// Collection should be presented via Navigation entry - Navigation - }; - - enum class SplitMode - { - /// List of files nad preview should be splitted horizontally - Horizontally, - /// List of files and preview should be splitted vertically - Vertically, - /// No preview should be shown - None - }; - - enum ColorRole - { - Background, - CardBackground, - CardBorder, - PrimaryText, - SecondaryText, - LastColorRole - }; - - struct SortColumn - { - QByteArray field; - bool ascending = true; - }; - using SortColumns = std::vector; - - /// Returns true, if collection schema is valid. If not, - /// default file properties should be used. - /// \returns true, if collection's schema is valid - bool isSchemaValid() const { return m_schema.isValid(); } - - /// Returns collection field. This function always returns - /// valid field definition. If field can't be found, then - /// default field (invalid) is returned. - /// \param key Key - const PDFCollectionField* getField(const QByteArray& key) const { return m_schema.getField(key); } - - /// Returns true, if field definition with given key is valid. - /// \param key Key - bool isFieldValid(const QByteArray& key) const { return m_schema.isFieldValid(key); } - - /// Returns file field schema. Fields are properties of the files, - /// such as size, description, date/time etc. - const PDFCollectionSchema& getSchema() const { return m_schema; } - - /// Returns initial document, which shall be presented to the user - /// in the ui (so interactive processor should display this file first). - const QByteArray& getDocument() const { return m_document; } - - /// Returns view mode of the collection. It defines how collection - /// should appear in user interface. - ViewMode getViewMode() const { return m_viewMode; } - - /// Returns reference to a navigator (if it exists) - PDFObjectReference getNavigator() const { return m_navigator; } - - /// Returns color for given role. Invalid color can be returned. - /// If this occurs, default color should be used. - /// \param role Color role - QColor getColor(ColorRole role) const { return m_colors.at(role); } - - /// Returns sorting information (list of columns, defining - /// sort key). If it is empty, default sorting should occur. - const SortColumns& getSortColumns() const { return m_sortColumns; } - - /// If sort columns are empty, then is default sorting ascending? - bool isSortAscending() const { return m_sortAscending; } - - /// Returns folder root (if collection has folders) - PDFObjectReference getFolderRoot() const { return m_folderRoot; } - - /// Returns split mode of list of files and preview - SplitMode getSplitMode() const { return m_splitMode; } - - /// Returns split proportion - PDFReal getSplitPropertion() const { return m_splitProportion; } - - static PDFCollection parse(const PDFObjectStorage* storage, PDFObject object); - -private: - PDFCollectionSchema m_schema; - QByteArray m_document; - ViewMode m_viewMode = ViewMode::Details; - PDFObjectReference m_navigator; - std::array m_colors; - SortColumns m_sortColumns; - bool m_sortAscending = true; - PDFObjectReference m_folderRoot; - SplitMode m_splitMode = SplitMode::None; - PDFReal m_splitProportion = 30; -}; - -/// Collection folder. Can contain subfolders and files. -class PDF4QTLIBSHARED_EXPORT PDFCollectionFolder -{ -public: - explicit inline PDFCollectionFolder() = default; - - PDFInteger getInteger() const { return m_ID; } - const QString& getName() const { return m_name; } - const QString& getDescription() const { return m_description; } - PDFObjectReference getParent() const { return m_parent; } - PDFObjectReference getChild() const { return m_child; } - PDFObjectReference getNext() const { return m_next; } - PDFObjectReference getCollection() const { return m_collection; } - PDFObjectReference getThumbnail() const { return m_thumbnail; } - const QDateTime& getCreatedDate() const { return m_created; } - const QDateTime& getModifiedDate() const { return m_modified; } - const std::vector& getFreeIds() const { return m_freeIds; } - - static PDFCollectionFolder parse(const PDFObjectStorage* storage, PDFObject object); - -private: - PDFInteger m_ID = 0; - QString m_name; - QString m_description; - PDFObjectReference m_parent; - PDFObjectReference m_child; - PDFObjectReference m_next; - PDFObjectReference m_collection; - PDFObjectReference m_thumbnail; - QDateTime m_created; - QDateTime m_modified; - std::vector m_freeIds; -}; - -/// Collection item. Contains properties of the collection item, -/// for example, embedded file. -class PDF4QTLIBSHARED_EXPORT PDFCollectionItem -{ -public: - explicit inline PDFCollectionItem() = default; - explicit inline PDFCollectionItem(const PDFObject& object) : m_object(object) { } - - /// Returns true, if collection item entry is valid - bool isValid() const { return m_object.isDictionary(); } - - /// Returns string from property key. If property is invalid, empty - /// string is returned, no exception is thrown. - /// \param key Key - /// \param storage Storage - QString getString(const QByteArray& key, const PDFObjectStorage* storage) const; - - /// Returns date/time from property key. If property is invalid, invalid - /// QDateTime is returned, no exception is thrown. - /// \param key Key - /// \param storage Storage - QDateTime getDateTime(const QByteArray& key, const PDFObjectStorage* storage) const; - - /// Returns integer from property key. If property is invalid, zero - /// integer is returned, no exception is thrown. - /// \param key Key - /// \param storage Storage - PDFInteger getNumber(const QByteArray& key, const PDFObjectStorage* storage) const; - - /// Returns prefix string from property key. If property is invalid, empty - /// string is returned, no exception is thrown. - /// \param key Key - /// \param storage Storage - QString getPrefixString(const QByteArray& key, const PDFObjectStorage* storage) const; - -private: - PDFObject m_object; -}; - -/// Collection navigator. It contains modes of display. Interactive -/// PDF processor should display first layout it is capable of. -class PDF4QTLIBSHARED_EXPORT PDFCollectionNavigator -{ -public: - explicit inline PDFCollectionNavigator() = default; - - enum Layout - { - None = 0x0000, - Invalid = 0x0001, - Details = 0x0002, - Tile = 0x0004, - Hidden = 0x0008, - FilmStrip = 0x0010, - FreeForm = 0x0020, - Linear = 0x0040, - Tree = 0x0080 - }; - Q_DECLARE_FLAGS(Layouts, Layout) - - /// Returns true, if navigator has valid layout - bool hasLayout() const { return (m_layouts != None) && !m_layouts.testFlag(Invalid); } - - /// Returns current layouts - Layouts getLayout() const { return m_layouts; } - - static PDFCollectionNavigator parse(const PDFObjectStorage* storage, PDFObject object); - -private: - Layouts m_layouts = None; -}; - -class PDF4QTLIBSHARED_EXPORT PDFEmbeddedFile -{ -public: - explicit PDFEmbeddedFile() = default; - - bool isValid() const { return m_stream.isStream(); } - const QByteArray& getSubtype() const { return m_subtype; } - PDFInteger getSize() const { return m_size; } - const QDateTime& getCreationDate() const { return m_creationDate; } - const QDateTime& getModifiedDate() const { return m_modifiedDate; } - const QByteArray& getChecksum() const { return m_checksum; } - const PDFStream* getStream() const { return m_stream.getStream(); } - - static PDFEmbeddedFile parse(const PDFObjectStorage* storage, PDFObject object); - -private: - PDFObject m_stream; - QByteArray m_subtype; - PDFInteger m_size = -1; - QDateTime m_creationDate; - QDateTime m_modifiedDate; - QByteArray m_checksum; -}; - -/// File specification -class PDF4QTLIBSHARED_EXPORT PDFFileSpecification -{ -public: - explicit PDFFileSpecification() = default; - - struct RelatedFile - { - QByteArray name; - PDFObjectReference fileReference; - }; - - using RelatedFiles = std::vector; - - enum class AssociatedFileRelationship - { - Unspecified, - Source, - Data, - Alternative, - Supplement, - EncryptedPayload, - FormData, - Schema - }; - - /// Returns platform file name as string. It looks into the UF, F, - /// and platform names and selects the appropriate one. If error - /// occurs. then empty string is returned. - QString getPlatformFileName() const; - - /// Returns platform file. - const PDFEmbeddedFile* getPlatformFile() const; - - const QByteArray& getFileSystem() const { return m_fileSystem; } - const QByteArray& getF() const { return m_F; } - const QString& getUF() const { return m_UF; } - const QByteArray& getDOS() const { return m_DOS; } - const QByteArray& getMac() const { return m_Mac; } - const QByteArray& getUnix() const { return m_Unix; } - const PDFFileIdentifier& getFileIdentifier() const { return m_id; } - bool isVolatile() const { return m_volatile; } - const QString& getDescription() const { return m_description; } - PDFObjectReference getSelfReference() const { return m_selfReference; } - PDFObjectReference getCollection() const { return m_collection; } - PDFObjectReference getThumbnail() const { return m_thumbnailReference; } - const std::map& getEmbeddedFiles() const { return m_embeddedFiles; } - const std::map& getRelatedFiles() const { return m_relatedFiles; } - const PDFObject& getEncryptedPayloadDictionary() const { return m_encryptedPayload; } - AssociatedFileRelationship getAssociatedFileRelationship() const { return m_associatedFileRelationship; } - - static PDFFileSpecification parse(const PDFObjectStorage* storage, PDFObject object); - -private: - /// Name of the file system used to interpret this file specification, - /// usually, it is URL (this is only file system defined in PDF specification 1.7). - QByteArray m_fileSystem; - - /// File specification string (for backward compatibility). If file system is URL, - /// it contains unified resource locator. - QByteArray m_F; - - /// File specification string as unicode. - QString m_UF; - - QByteArray m_DOS; - QByteArray m_Mac; - QByteArray m_Unix; - - PDFFileIdentifier m_id; - - /// Is file volatile? I.e it is, for example, link to a video file from online camera? - /// If this boolean is true, then file should never be cached. - bool m_volatile = false; - - /// Description of the file (for example, if file is embedded file stream) - QString m_description; - - /// Self reference - PDFObjectReference m_selfReference; - - /// Collection item dictionary reference - PDFObjectReference m_collection; - - /// Thumbnail reference - PDFObjectReference m_thumbnailReference; - - /// Embedded files - std::map m_embeddedFiles; - - /// Related files for embedded files - std::map m_relatedFiles; - - /// Encrypted payload dictionary (used in document wrapper) - PDFObject m_encryptedPayload; - - AssociatedFileRelationship m_associatedFileRelationship = AssociatedFileRelationship::Unspecified; -}; - -} // namespace pdf - -#endif // PDFFILE_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFFILE_H +#define PDFFILE_H + +#include "pdfobject.h" + +#include +#include + +namespace pdf +{ +class PDFObjectStorage; + +/// File identifier according section 14.4 of PDF 2.0 specification. +/// Each identifier consists of two parts - permanent identifier, which +/// is unique identifier based on original document, and changing identifier, +/// which is updated when document is being modified. +class PDF4QTLIBSHARED_EXPORT PDFFileIdentifier +{ +public: + explicit inline PDFFileIdentifier() = default; + + const QByteArray& getPermanentIdentifier() const { return m_permanentIdentifier; } + const QByteArray& getChangingIdentifier() const { return m_changingIdentifier; } + + static PDFFileIdentifier parse(const PDFObjectStorage* storage, PDFObject object); + +private: + QByteArray m_permanentIdentifier; + QByteArray m_changingIdentifier; +}; + +/// Provides description of collection item property field. It describes it's +/// kind, data type, if content of the property should be presented to the user, +/// and ordering, visibility and editability. +class PDF4QTLIBSHARED_EXPORT PDFCollectionField +{ +public: + explicit inline PDFCollectionField() = default; + + enum class Kind + { + Invalid, + TextField, + DateField, + NumberField, + FileName, + Description, + ModifiedDate, + CreationDate, + Size, + CompressedSize + }; + + enum class Value + { + Invalid, + TextString, + DateTime, + Number + }; + + Kind getKind() const { return m_kind; } + Value getValue() const { return m_value; } + QString getFieldName() const { return m_fieldName; } + PDFInteger getOrder() const { return m_order; } + bool isVisible() const { return m_visible; } + bool isEditable() const { return m_editable; } + + static PDFCollectionField parse(const PDFObjectStorage* storage, PDFObject object); + +private: + Kind m_kind = Kind::Invalid; + Value m_value = Value::Invalid; + QString m_fieldName; + PDFInteger m_order = 0; + bool m_visible = true; + bool m_editable = false; +}; + +/// Collection schema. Contains a list of defined fields. +/// Schema can be queried for field definition. +class PDF4QTLIBSHARED_EXPORT PDFCollectionSchema +{ +public: + explicit inline PDFCollectionSchema() = default; + + /// Returns true, if schema is valid, i.e. some fields + /// are defined. + bool isValid() const { return !m_fields.empty(); } + + /// Returns collection field. This function always returns + /// valid field definition. If field can't be found, then + /// default field (invalid) is returned. + /// \param key Key + const PDFCollectionField* getField(const QByteArray& key) const; + + /// Returns true, if field definition with given key is valid. + /// \param key Key + bool isFieldValid(const QByteArray& key) const { return getField(key)->getKind() != PDFCollectionField::Kind::Invalid;} + + static PDFCollectionSchema parse(const PDFObjectStorage* storage, PDFObject object); + +private: + std::map m_fields; +}; + +/// Collection of file attachments. In the PDF file, attached files +/// can be grouped in collection (if they are related to each other). +class PDF4QTLIBSHARED_EXPORT PDFCollection +{ +public: + explicit inline PDFCollection() = default; + + enum class ViewMode + { + /// Collection items should be displayed in details mode, + /// for example, multi-column table, which contains icon, + /// file name, and all properties listed in the schema. + Details, + + /// Collection should be presented in tile mode, each file + /// should have icon, and some information from the schema + /// dictionary. + Tiles, + + /// Collection should be initially hidden and should be presented + /// to the user if user performs some explicit action. + Hidden, + + /// Collection should be presented via Navigation entry + Navigation + }; + + enum class SplitMode + { + /// List of files nad preview should be splitted horizontally + Horizontally, + /// List of files and preview should be splitted vertically + Vertically, + /// No preview should be shown + None + }; + + enum ColorRole + { + Background, + CardBackground, + CardBorder, + PrimaryText, + SecondaryText, + LastColorRole + }; + + struct SortColumn + { + QByteArray field; + bool ascending = true; + }; + using SortColumns = std::vector; + + /// Returns true, if collection schema is valid. If not, + /// default file properties should be used. + /// \returns true, if collection's schema is valid + bool isSchemaValid() const { return m_schema.isValid(); } + + /// Returns collection field. This function always returns + /// valid field definition. If field can't be found, then + /// default field (invalid) is returned. + /// \param key Key + const PDFCollectionField* getField(const QByteArray& key) const { return m_schema.getField(key); } + + /// Returns true, if field definition with given key is valid. + /// \param key Key + bool isFieldValid(const QByteArray& key) const { return m_schema.isFieldValid(key); } + + /// Returns file field schema. Fields are properties of the files, + /// such as size, description, date/time etc. + const PDFCollectionSchema& getSchema() const { return m_schema; } + + /// Returns initial document, which shall be presented to the user + /// in the ui (so interactive processor should display this file first). + const QByteArray& getDocument() const { return m_document; } + + /// Returns view mode of the collection. It defines how collection + /// should appear in user interface. + ViewMode getViewMode() const { return m_viewMode; } + + /// Returns reference to a navigator (if it exists) + PDFObjectReference getNavigator() const { return m_navigator; } + + /// Returns color for given role. Invalid color can be returned. + /// If this occurs, default color should be used. + /// \param role Color role + QColor getColor(ColorRole role) const { return m_colors.at(role); } + + /// Returns sorting information (list of columns, defining + /// sort key). If it is empty, default sorting should occur. + const SortColumns& getSortColumns() const { return m_sortColumns; } + + /// If sort columns are empty, then is default sorting ascending? + bool isSortAscending() const { return m_sortAscending; } + + /// Returns folder root (if collection has folders) + PDFObjectReference getFolderRoot() const { return m_folderRoot; } + + /// Returns split mode of list of files and preview + SplitMode getSplitMode() const { return m_splitMode; } + + /// Returns split proportion + PDFReal getSplitPropertion() const { return m_splitProportion; } + + static PDFCollection parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFCollectionSchema m_schema; + QByteArray m_document; + ViewMode m_viewMode = ViewMode::Details; + PDFObjectReference m_navigator; + std::array m_colors; + SortColumns m_sortColumns; + bool m_sortAscending = true; + PDFObjectReference m_folderRoot; + SplitMode m_splitMode = SplitMode::None; + PDFReal m_splitProportion = 30; +}; + +/// Collection folder. Can contain subfolders and files. +class PDF4QTLIBSHARED_EXPORT PDFCollectionFolder +{ +public: + explicit inline PDFCollectionFolder() = default; + + PDFInteger getInteger() const { return m_ID; } + const QString& getName() const { return m_name; } + const QString& getDescription() const { return m_description; } + PDFObjectReference getParent() const { return m_parent; } + PDFObjectReference getChild() const { return m_child; } + PDFObjectReference getNext() const { return m_next; } + PDFObjectReference getCollection() const { return m_collection; } + PDFObjectReference getThumbnail() const { return m_thumbnail; } + const QDateTime& getCreatedDate() const { return m_created; } + const QDateTime& getModifiedDate() const { return m_modified; } + const std::vector& getFreeIds() const { return m_freeIds; } + + static PDFCollectionFolder parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFInteger m_ID = 0; + QString m_name; + QString m_description; + PDFObjectReference m_parent; + PDFObjectReference m_child; + PDFObjectReference m_next; + PDFObjectReference m_collection; + PDFObjectReference m_thumbnail; + QDateTime m_created; + QDateTime m_modified; + std::vector m_freeIds; +}; + +/// Collection item. Contains properties of the collection item, +/// for example, embedded file. +class PDF4QTLIBSHARED_EXPORT PDFCollectionItem +{ +public: + explicit inline PDFCollectionItem() = default; + explicit inline PDFCollectionItem(const PDFObject& object) : m_object(object) { } + + /// Returns true, if collection item entry is valid + bool isValid() const { return m_object.isDictionary(); } + + /// Returns string from property key. If property is invalid, empty + /// string is returned, no exception is thrown. + /// \param key Key + /// \param storage Storage + QString getString(const QByteArray& key, const PDFObjectStorage* storage) const; + + /// Returns date/time from property key. If property is invalid, invalid + /// QDateTime is returned, no exception is thrown. + /// \param key Key + /// \param storage Storage + QDateTime getDateTime(const QByteArray& key, const PDFObjectStorage* storage) const; + + /// Returns integer from property key. If property is invalid, zero + /// integer is returned, no exception is thrown. + /// \param key Key + /// \param storage Storage + PDFInteger getNumber(const QByteArray& key, const PDFObjectStorage* storage) const; + + /// Returns prefix string from property key. If property is invalid, empty + /// string is returned, no exception is thrown. + /// \param key Key + /// \param storage Storage + QString getPrefixString(const QByteArray& key, const PDFObjectStorage* storage) const; + +private: + PDFObject m_object; +}; + +/// Collection navigator. It contains modes of display. Interactive +/// PDF processor should display first layout it is capable of. +class PDF4QTLIBSHARED_EXPORT PDFCollectionNavigator +{ +public: + explicit inline PDFCollectionNavigator() = default; + + enum Layout + { + None = 0x0000, + Invalid = 0x0001, + Details = 0x0002, + Tile = 0x0004, + Hidden = 0x0008, + FilmStrip = 0x0010, + FreeForm = 0x0020, + Linear = 0x0040, + Tree = 0x0080 + }; + Q_DECLARE_FLAGS(Layouts, Layout) + + /// Returns true, if navigator has valid layout + bool hasLayout() const { return (m_layouts != None) && !m_layouts.testFlag(Invalid); } + + /// Returns current layouts + Layouts getLayout() const { return m_layouts; } + + static PDFCollectionNavigator parse(const PDFObjectStorage* storage, PDFObject object); + +private: + Layouts m_layouts = None; +}; + +class PDF4QTLIBSHARED_EXPORT PDFEmbeddedFile +{ +public: + explicit PDFEmbeddedFile() = default; + + bool isValid() const { return m_stream.isStream(); } + const QByteArray& getSubtype() const { return m_subtype; } + PDFInteger getSize() const { return m_size; } + const QDateTime& getCreationDate() const { return m_creationDate; } + const QDateTime& getModifiedDate() const { return m_modifiedDate; } + const QByteArray& getChecksum() const { return m_checksum; } + const PDFStream* getStream() const { return m_stream.getStream(); } + + static PDFEmbeddedFile parse(const PDFObjectStorage* storage, PDFObject object); + +private: + PDFObject m_stream; + QByteArray m_subtype; + PDFInteger m_size = -1; + QDateTime m_creationDate; + QDateTime m_modifiedDate; + QByteArray m_checksum; +}; + +/// File specification +class PDF4QTLIBSHARED_EXPORT PDFFileSpecification +{ +public: + explicit PDFFileSpecification() = default; + + struct RelatedFile + { + QByteArray name; + PDFObjectReference fileReference; + }; + + using RelatedFiles = std::vector; + + enum class AssociatedFileRelationship + { + Unspecified, + Source, + Data, + Alternative, + Supplement, + EncryptedPayload, + FormData, + Schema + }; + + /// Returns platform file name as string. It looks into the UF, F, + /// and platform names and selects the appropriate one. If error + /// occurs. then empty string is returned. + QString getPlatformFileName() const; + + /// Returns platform file. + const PDFEmbeddedFile* getPlatformFile() const; + + const QByteArray& getFileSystem() const { return m_fileSystem; } + const QByteArray& getF() const { return m_F; } + const QString& getUF() const { return m_UF; } + const QByteArray& getDOS() const { return m_DOS; } + const QByteArray& getMac() const { return m_Mac; } + const QByteArray& getUnix() const { return m_Unix; } + const PDFFileIdentifier& getFileIdentifier() const { return m_id; } + bool isVolatile() const { return m_volatile; } + const QString& getDescription() const { return m_description; } + PDFObjectReference getSelfReference() const { return m_selfReference; } + PDFObjectReference getCollection() const { return m_collection; } + PDFObjectReference getThumbnail() const { return m_thumbnailReference; } + const std::map& getEmbeddedFiles() const { return m_embeddedFiles; } + const std::map& getRelatedFiles() const { return m_relatedFiles; } + const PDFObject& getEncryptedPayloadDictionary() const { return m_encryptedPayload; } + AssociatedFileRelationship getAssociatedFileRelationship() const { return m_associatedFileRelationship; } + + static PDFFileSpecification parse(const PDFObjectStorage* storage, PDFObject object); + +private: + /// Name of the file system used to interpret this file specification, + /// usually, it is URL (this is only file system defined in PDF specification 1.7). + QByteArray m_fileSystem; + + /// File specification string (for backward compatibility). If file system is URL, + /// it contains unified resource locator. + QByteArray m_F; + + /// File specification string as unicode. + QString m_UF; + + QByteArray m_DOS; + QByteArray m_Mac; + QByteArray m_Unix; + + PDFFileIdentifier m_id; + + /// Is file volatile? I.e it is, for example, link to a video file from online camera? + /// If this boolean is true, then file should never be cached. + bool m_volatile = false; + + /// Description of the file (for example, if file is embedded file stream) + QString m_description; + + /// Self reference + PDFObjectReference m_selfReference; + + /// Collection item dictionary reference + PDFObjectReference m_collection; + + /// Thumbnail reference + PDFObjectReference m_thumbnailReference; + + /// Embedded files + std::map m_embeddedFiles; + + /// Related files for embedded files + std::map m_relatedFiles; + + /// Encrypted payload dictionary (used in document wrapper) + PDFObject m_encryptedPayload; + + AssociatedFileRelationship m_associatedFileRelationship = AssociatedFileRelationship::Unspecified; +}; + +} // namespace pdf + +#endif // PDFFILE_H diff --git a/Pdf4QtLib/sources/pdfflatarray.h b/Pdf4QtLib/sources/pdfflatarray.h index 09bb298..2a9634f 100644 --- a/Pdf4QtLib/sources/pdfflatarray.h +++ b/Pdf4QtLib/sources/pdfflatarray.h @@ -1,190 +1,190 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#ifndef PDFFLATARRAY_H -#define PDFFLATARRAY_H - -#include - -#include -#include -#include - -namespace pdf -{ - -/// This represents a fast array, consisting of "fast" block of fixed size \p FlatSize, -/// and "slow" block of variable size. Usually, this array is used when vast majority -/// of usage size is below FlatSize, only minority is above FlatSize. Typical example -/// of use of this class: -/// -/// We have colors in PDF, which can have usually 1, 3 or 4 color components. But in some -/// rare cases, we have much more components, for example for DeviceN color spaces. -/// For this reason, we will set FlatSize to 4 (so Gray, RGB and CMYK colors will not -/// use slow "variable" part). -template -class PDFFlatArray -{ -public: - PDFFlatArray() : - m_flatBlock(), - m_flatBlockItemCount(0), - m_variableBlock() - { - - } - - template::type = 0> - inline PDFFlatArray(Arguments... arguments) : - m_flatBlock({ arguments... }), - m_flatBlockItemCount(sizeof...(Arguments)), - m_variableBlock() - { - - } - - using value_type = T; - - /// Returns the size of the array - size_t size() const { return getFlatBlockSize() + m_variableBlock.size(); } - - /// Returns true, if array is empty - bool empty() const { return size() == 0; } - - template - const T& get() const - { - if constexpr (index < FlatSize) - { - return m_flatBlock[size]; - } - else - { - return m_variableBlock[size - FlatSize]; - } - } - - template - T& get() - { - if constexpr (index < FlatSize) - { - return m_flatBlock[size]; - } - else - { - return m_variableBlock[size - FlatSize]; - } - } - - const T& operator[] (size_t index) const - { - Q_ASSERT(index < size()); - - if (index < FlatSize) - { - return m_flatBlock[index]; - } - else - { - return m_variableBlock[index - FlatSize]; - } - } - - T& operator[] (size_t index) - { - Q_ASSERT(index < size()); - - if (index < FlatSize) - { - return m_flatBlock[index]; - } - else - { - return m_variableBlock[index - FlatSize]; - } - } - - void clear() - { - m_flatBlockItemCount = 0; - m_variableBlock.clear(); - } - - void push_back(T object) - { - if (m_flatBlockItemCount < m_flatBlock.size()) - { - m_flatBlock[m_flatBlockItemCount++] = std::move(object); - } - else - { - m_variableBlock.emplace_back(std::move(object)); - } - } - - void resize(std::size_t size) - { - if (size <= FlatSize) - { - m_flatBlockItemCount = size; - m_variableBlock.clear(); - } - else - { - m_flatBlockItemCount = FlatSize; - m_variableBlock.resize(size - FlatSize); - } - } - - /// Returns the last element of the array - inline const T& back() const { return m_variableBlock.empty() ? m_flatBlock[m_flatBlockItemCount - 1] : m_variableBlock.back(); } - - /// Erases the last element from the array - inline void pop_back() { resize(size() - 1); } - - bool operator==(const PDFFlatArray& other) const - { - const size_t size = this->size(); - if (size != other.size()) - { - return false; - } - - for (size_t i = 0; i < size; ++i) - { - if ((*this)[i] != other[i]) - { - return false; - } - } - - return true; - } - -private: - size_t getFlatBlockSize() const { return m_flatBlockItemCount; } - - std::array m_flatBlock; - size_t m_flatBlockItemCount; ///< Number of items in the flat block - std::vector m_variableBlock; -}; - -} // namespace pdf - -#endif // PDFFLATARRAY_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#ifndef PDFFLATARRAY_H +#define PDFFLATARRAY_H + +#include + +#include +#include +#include + +namespace pdf +{ + +/// This represents a fast array, consisting of "fast" block of fixed size \p FlatSize, +/// and "slow" block of variable size. Usually, this array is used when vast majority +/// of usage size is below FlatSize, only minority is above FlatSize. Typical example +/// of use of this class: +/// +/// We have colors in PDF, which can have usually 1, 3 or 4 color components. But in some +/// rare cases, we have much more components, for example for DeviceN color spaces. +/// For this reason, we will set FlatSize to 4 (so Gray, RGB and CMYK colors will not +/// use slow "variable" part). +template +class PDFFlatArray +{ +public: + PDFFlatArray() : + m_flatBlock(), + m_flatBlockItemCount(0), + m_variableBlock() + { + + } + + template::type = 0> + inline PDFFlatArray(Arguments... arguments) : + m_flatBlock({ arguments... }), + m_flatBlockItemCount(sizeof...(Arguments)), + m_variableBlock() + { + + } + + using value_type = T; + + /// Returns the size of the array + size_t size() const { return getFlatBlockSize() + m_variableBlock.size(); } + + /// Returns true, if array is empty + bool empty() const { return size() == 0; } + + template + const T& get() const + { + if constexpr (index < FlatSize) + { + return m_flatBlock[size]; + } + else + { + return m_variableBlock[size - FlatSize]; + } + } + + template + T& get() + { + if constexpr (index < FlatSize) + { + return m_flatBlock[size]; + } + else + { + return m_variableBlock[size - FlatSize]; + } + } + + const T& operator[] (size_t index) const + { + Q_ASSERT(index < size()); + + if (index < FlatSize) + { + return m_flatBlock[index]; + } + else + { + return m_variableBlock[index - FlatSize]; + } + } + + T& operator[] (size_t index) + { + Q_ASSERT(index < size()); + + if (index < FlatSize) + { + return m_flatBlock[index]; + } + else + { + return m_variableBlock[index - FlatSize]; + } + } + + void clear() + { + m_flatBlockItemCount = 0; + m_variableBlock.clear(); + } + + void push_back(T object) + { + if (m_flatBlockItemCount < m_flatBlock.size()) + { + m_flatBlock[m_flatBlockItemCount++] = std::move(object); + } + else + { + m_variableBlock.emplace_back(std::move(object)); + } + } + + void resize(std::size_t size) + { + if (size <= FlatSize) + { + m_flatBlockItemCount = size; + m_variableBlock.clear(); + } + else + { + m_flatBlockItemCount = FlatSize; + m_variableBlock.resize(size - FlatSize); + } + } + + /// Returns the last element of the array + inline const T& back() const { return m_variableBlock.empty() ? m_flatBlock[m_flatBlockItemCount - 1] : m_variableBlock.back(); } + + /// Erases the last element from the array + inline void pop_back() { resize(size() - 1); } + + bool operator==(const PDFFlatArray& other) const + { + const size_t size = this->size(); + if (size != other.size()) + { + return false; + } + + for (size_t i = 0; i < size; ++i) + { + if ((*this)[i] != other[i]) + { + return false; + } + } + + return true; + } + +private: + size_t getFlatBlockSize() const { return m_flatBlockItemCount; } + + std::array m_flatBlock; + size_t m_flatBlockItemCount; ///< Number of items in the flat block + std::vector m_variableBlock; +}; + +} // namespace pdf + +#endif // PDFFLATARRAY_H diff --git a/Pdf4QtLib/sources/pdfflatmap.h b/Pdf4QtLib/sources/pdfflatmap.h index 895621b..2732ed2 100644 --- a/Pdf4QtLib/sources/pdfflatmap.h +++ b/Pdf4QtLib/sources/pdfflatmap.h @@ -1,130 +1,130 @@ -// Copyright (C) 2018-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - - -#ifndef PDFFLATMAP_H -#define PDFFLATMAP_H - -#include -#include -#include - -namespace pdf -{ - -/// This class behaves like std::set, but have "flat" part, and if size of the set -/// is small (smaller than \p FlatSize), then no memory allocation is needed. This -/// container supports inserting, deleting and searching for the object presence. -template -class PDFFlatMap -{ -public: - constexpr inline PDFFlatMap(); - - /// Inserts a key in the container. Checks, if key is already present - /// in the container, in this case no insertion occurs. - /// \param key Key to be inserted - void insert(const Key& key); - - /// Erases a key in the container, if it is in the set - /// \param key Key to be erased - void erase(const Key& key); - - /// Searchs for a given key. If it is found, true is returned, false otherwise. - /// \param key Key to be searched - bool search(const Key& key) const; - - /// Returns size of the container - std::size_t size() const; - - /// Returns true, if container is empty - bool empty() const; - -private: - /// Flat part of the set - std::array m_flat; - - /// This iterator points to first empty position, or it is - /// the last iterator (pointing to the end of the array). - typename std::array::iterator m_flatEmptyPosition; - - std::set m_overflowContainer; -}; - -template -constexpr PDFFlatMap::PDFFlatMap() : - m_flat(), - m_flatEmptyPosition(m_flat.begin()), - m_overflowContainer() -{ - -} - -template -void PDFFlatMap::insert(const Key& key) -{ - if (!search(key)) - { - // Try to insert key in the flat part, if possible (we are not at end of the array) - if (m_flatEmptyPosition != m_flat.end()) - { - *m_flatEmptyPosition++ = key; - } - else - { - m_overflowContainer.insert(key); - } - } -} - -template -void PDFFlatMap::erase(const Key& key) -{ - // First we must check, if the key is present in the flat part. If yes, then remove key - // from the flat part and try to move one item from the overflow part to the flat part, if possible. - // Otherwise check overflow part. - m_flatEmptyPosition = std::remove_if(m_flat.begin(), m_flatEmptyPosition, [&key](const Key& otherKey) { return key == otherKey; }); - m_overflowContainer.erase(key); - - if (!m_overflowContainer.empty() && m_flatEmptyPosition != m_flat.end()) - { - *m_flatEmptyPosition++ = *m_overflowContainer.begin(); - m_overflowContainer.erase(m_overflowContainer.begin()); - } -} - -template -bool PDFFlatMap::search(const Key& key) const -{ - return std::find::const_iterator, Key>(m_flat.begin(), m_flatEmptyPosition, key) != m_flatEmptyPosition || static_cast(m_overflowContainer.count(key)); -} - -template -std::size_t PDFFlatMap::size() const -{ - return std::distance::const_iterator>(m_flat.begin(), m_flatEmptyPosition) + m_overflowContainer.size(); -} - -template -bool PDFFlatMap::empty() const -{ - return size() == 0; -} - -} // namespace pdf - -#endif // PDFFLATMAP_H +// Copyright (C) 2018-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + + +#ifndef PDFFLATMAP_H +#define PDFFLATMAP_H + +#include +#include +#include + +namespace pdf +{ + +/// This class behaves like std::set, but have "flat" part, and if size of the set +/// is small (smaller than \p FlatSize), then no memory allocation is needed. This +/// container supports inserting, deleting and searching for the object presence. +template +class PDFFlatMap +{ +public: + constexpr inline PDFFlatMap(); + + /// Inserts a key in the container. Checks, if key is already present + /// in the container, in this case no insertion occurs. + /// \param key Key to be inserted + void insert(const Key& key); + + /// Erases a key in the container, if it is in the set + /// \param key Key to be erased + void erase(const Key& key); + + /// Searchs for a given key. If it is found, true is returned, false otherwise. + /// \param key Key to be searched + bool search(const Key& key) const; + + /// Returns size of the container + std::size_t size() const; + + /// Returns true, if container is empty + bool empty() const; + +private: + /// Flat part of the set + std::array m_flat; + + /// This iterator points to first empty position, or it is + /// the last iterator (pointing to the end of the array). + typename std::array::iterator m_flatEmptyPosition; + + std::set m_overflowContainer; +}; + +template +constexpr PDFFlatMap::PDFFlatMap() : + m_flat(), + m_flatEmptyPosition(m_flat.begin()), + m_overflowContainer() +{ + +} + +template +void PDFFlatMap::insert(const Key& key) +{ + if (!search(key)) + { + // Try to insert key in the flat part, if possible (we are not at end of the array) + if (m_flatEmptyPosition != m_flat.end()) + { + *m_flatEmptyPosition++ = key; + } + else + { + m_overflowContainer.insert(key); + } + } +} + +template +void PDFFlatMap::erase(const Key& key) +{ + // First we must check, if the key is present in the flat part. If yes, then remove key + // from the flat part and try to move one item from the overflow part to the flat part, if possible. + // Otherwise check overflow part. + m_flatEmptyPosition = std::remove_if(m_flat.begin(), m_flatEmptyPosition, [&key](const Key& otherKey) { return key == otherKey; }); + m_overflowContainer.erase(key); + + if (!m_overflowContainer.empty() && m_flatEmptyPosition != m_flat.end()) + { + *m_flatEmptyPosition++ = *m_overflowContainer.begin(); + m_overflowContainer.erase(m_overflowContainer.begin()); + } +} + +template +bool PDFFlatMap::search(const Key& key) const +{ + return std::find::const_iterator, Key>(m_flat.begin(), m_flatEmptyPosition, key) != m_flatEmptyPosition || static_cast(m_overflowContainer.count(key)); +} + +template +std::size_t PDFFlatMap::size() const +{ + return std::distance::const_iterator>(m_flat.begin(), m_flatEmptyPosition) + m_overflowContainer.size(); +} + +template +bool PDFFlatMap::empty() const +{ + return size() == 0; +} + +} // namespace pdf + +#endif // PDFFLATMAP_H diff --git a/Pdf4QtLib/sources/pdffont.cpp b/Pdf4QtLib/sources/pdffont.cpp index 04ffee2..33a669c 100644 --- a/Pdf4QtLib/sources/pdffont.cpp +++ b/Pdf4QtLib/sources/pdffont.cpp @@ -1,2403 +1,2403 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdffont.h" -#include "pdfdocument.h" -#include "pdfparser.h" -#include "pdfnametounicode.h" -#include "pdfexception.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#ifdef Q_OS_WIN -#include "Windows.h" - -#pragma comment(lib, "Gdi32") -#pragma comment(lib, "User32") -#endif - -namespace pdf -{ - -/// Storage class for system fonts -class PDFSystemFontInfoStorage -{ -public: - - /// Returns instance of storage - static const PDFSystemFontInfoStorage* getInstance(); - - /// Loads font from descriptor - /// \param descriptor Descriptor describing the font - QByteArray loadFont(const FontDescriptor* descriptor, StandardFontType standardFontType, PDFRenderErrorReporter* reporter) const; - -private: - explicit PDFSystemFontInfoStorage(); - -#ifdef Q_OS_WIN - /// Callback for enumerating fonts - static int CALLBACK enumerateFontProc(const LOGFONT* font, const TEXTMETRIC* textMetrics, DWORD fontType, LPARAM lParam); - - /// Retrieves font data for desired font - static QByteArray getFontData(const LOGFONT* font, HDC hdc); - - /// Create a postscript name for comparation purposes - static QString getFontPostscriptName(QString fontName); - - struct FontInfo - { - QString faceName; - QString faceNameAdjusted; - LOGFONT logFont; - TEXTMETRIC textMetric; - }; - - struct CallbackInfo - { - PDFSystemFontInfoStorage* storage = nullptr; - HDC hdc = nullptr; - }; - - std::vector m_fontInfos; -#endif -}; - -const PDFSystemFontInfoStorage* PDFSystemFontInfoStorage::getInstance() -{ - static PDFSystemFontInfoStorage instance; - return &instance; -} - -QByteArray PDFSystemFontInfoStorage::loadFont(const FontDescriptor* descriptor, StandardFontType standardFontType, PDFRenderErrorReporter* reporter) const -{ - QByteArray result; - -#ifdef Q_OS_WIN - HDC hdc = GetDC(NULL); - - const BYTE lfItalic = (descriptor->italicAngle != 0.0 ? TRUE : FALSE); - - // Exact match font face name - QString fontName; - switch (standardFontType) - { - case StandardFontType::TimesRoman: - case StandardFontType::TimesRomanBold: - case StandardFontType::TimesRomanItalics: - case StandardFontType::TimesRomanBoldItalics: - { - fontName = "TimesNewRoman"; - break; - } - - case StandardFontType::Helvetica: - case StandardFontType::HelveticaBold: - case StandardFontType::HelveticaOblique: - case StandardFontType::HelveticaBoldOblique: - { - fontName = "Arial"; - break; - } - - case StandardFontType::Courier: - case StandardFontType::CourierBold: - case StandardFontType::CourierOblique: - case StandardFontType::CourierBoldOblique: - { - fontName = "CourierNew"; - break; - } - - case StandardFontType::Symbol: - case StandardFontType::ZapfDingbats: - { - fontName = "Symbol"; - break; - } - - default: - { - fontName = getFontPostscriptName(descriptor->fontName); - break; - } - } - - if (!fontName.isEmpty()) - { - for (const FontInfo& fontInfo : m_fontInfos) - { - if (fontInfo.faceNameAdjusted == fontName && - fontInfo.logFont.lfWeight == descriptor->fontWeight && - fontInfo.logFont.lfItalic == lfItalic) - { - result = getFontData(&fontInfo.logFont, hdc); - - if (!result.isEmpty()) - { - break; - } - } - } - - // Match for font family - if (result.isEmpty()) - { - for (const FontInfo& fontInfo : m_fontInfos) - { - if (fontInfo.faceNameAdjusted == fontName) - { - LOGFONT logFont = fontInfo.logFont; - logFont.lfWeight = descriptor->fontWeight; - logFont.lfItalic = lfItalic; - result = getFontData(&logFont, hdc); - - if (!result.isEmpty()) - { - break; - } - } - } - } - } - - // Exact match for font, if font can't be exact matched, then match font family - // and try to set weight - QString fontFamily = QString::fromLatin1(descriptor->fontFamily); - - if (!fontFamily.isEmpty() && result.isEmpty()) - { - for (const FontInfo& fontInfo : m_fontInfos) - { - if (fontInfo.faceName.contains(fontFamily) && - fontInfo.logFont.lfWeight == descriptor->fontWeight && - fontInfo.logFont.lfItalic == lfItalic) - { - result = getFontData(&fontInfo.logFont, hdc); - - if (!result.isEmpty()) - { - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by %2 using font family %3.").arg(fontName, fontInfo.faceNameAdjusted, fontFamily)); - break; - } - } - } - - // Match for font family - if (result.isEmpty()) - { - for (const FontInfo& fontInfo : m_fontInfos) - { - if (fontInfo.faceName.contains(fontFamily)) - { - LOGFONT logFont = fontInfo.logFont; - logFont.lfWeight = descriptor->fontWeight; - logFont.lfItalic = lfItalic; - result = getFontData(&logFont, hdc); - - if (!result.isEmpty()) - { - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by %2 using font family %3.").arg(fontName, fontInfo.faceNameAdjusted, fontFamily)); - break; - } - } - } - } - } - - // Try to inexact match for font name - find similar font - if (!fontName.isEmpty() && result.isEmpty()) - { - for (const FontInfo& fontInfo : m_fontInfos) - { - if (fontInfo.faceNameAdjusted.contains(fontName)) - { - LOGFONT logFont = fontInfo.logFont; - logFont.lfWeight = descriptor->fontWeight; - logFont.lfItalic = lfItalic; - result = getFontData(&logFont, hdc); - - if (!result.isEmpty()) - { - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by %2.").arg(fontName, fontInfo.faceNameAdjusted)); - break; - } - } - } - } - - ReleaseDC(NULL, hdc); -#endif - - if (result.isEmpty() && standardFontType == StandardFontType::Invalid) - { - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by standard font Times New Roman.").arg(fontName)); - result = loadFont(descriptor, StandardFontType::TimesRoman, reporter); - } - - return result; -} - -PDFSystemFontInfoStorage::PDFSystemFontInfoStorage() -{ -#ifdef Q_OS_WIN - LOGFONT logfont; - std::memset(&logfont, 0, sizeof(logfont)); - logfont.lfCharSet = DEFAULT_CHARSET; - logfont.lfFaceName[0] = 0; - logfont.lfPitchAndFamily = 0; - - HDC hdc = GetDC(NULL); - - CallbackInfo callbackInfo{ this, hdc}; - EnumFontFamiliesEx(hdc, &logfont, &PDFSystemFontInfoStorage::enumerateFontProc, reinterpret_cast(&callbackInfo), 0); - - ReleaseDC(NULL, hdc); -#endif -} - -#ifdef Q_OS_WIN -int PDFSystemFontInfoStorage::enumerateFontProc(const LOGFONT* font, const TEXTMETRIC* textMetrics, DWORD fontType, LPARAM lParam) -{ - if ((fontType & TRUETYPE_FONTTYPE) && (font->lfCharSet == ANSI_CHARSET)) - { - CallbackInfo* callbackInfo = reinterpret_cast(lParam); - - FontInfo fontInfo; - fontInfo.logFont = *font; - fontInfo.textMetric = *textMetrics; - fontInfo.faceName = QString::fromWCharArray(font->lfFaceName); - fontInfo.faceNameAdjusted = getFontPostscriptName(fontInfo.faceName); - callbackInfo->storage->m_fontInfos.push_back(qMove(fontInfo)); - - // For debug purposes only! -#if 0 - QByteArray byteArray = getFontData(font, callbackInfo->hdc); - qDebug() << "Font: " << QString::fromWCharArray(font->lfFaceName) << ", italic = " << font->lfItalic << ", weight = " << font->lfWeight << ", data size = " << byteArray.size(); -#endif - } - - return TRUE; -} - -QByteArray PDFSystemFontInfoStorage::getFontData(const LOGFONT* font, HDC hdc) -{ - QByteArray byteArray; - - if (HFONT fontHandle = ::CreateFontIndirect(font)) - { - HGDIOBJ oldFont = ::SelectObject(hdc, fontHandle); - - DWORD size = ::GetFontData(hdc, 0, 0, nullptr, 0); - if (size != GDI_ERROR) - { - byteArray.resize(static_cast(size)); - ::GetFontData(hdc, 0, 0, byteArray.data(), byteArray.size()); - } - - ::SelectObject(hdc, oldFont); - ::DeleteObject(fontHandle); - } - - return byteArray; -} - -QString PDFSystemFontInfoStorage::getFontPostscriptName(QString fontName) -{ - for (const char* string : { "PS", "MT", "Regular", "Bold", "Italic", "Oblique" }) - { - fontName.remove(QLatin1String(string), Qt::CaseInsensitive); - } - - return fontName.remove(QChar(' ')).remove(QChar('-')).remove(QChar(',')).trimmed(); -} - -#endif - -PDFFont::PDFFont(FontDescriptor fontDescriptor) : - m_fontDescriptor(qMove(fontDescriptor)) -{ - -} - -class IRealizedFontImpl -{ -public: - explicit IRealizedFontImpl() = default; - virtual ~IRealizedFontImpl() = default; - - /// Fills the text sequence by interpreting byte array according font data and - /// produces glyphs for the font. - /// \param byteArray Array of bytes to be interpreted - /// \param textSequence Text sequence to be filled - virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) = 0; - - /// Returns true, if font has horizontal writing system - virtual bool isHorizontalWritingSystem() const = 0; - - /// Dumps information about the font - virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const { Q_UNUSED(item); } - - /// Returns postscript name of the font - virtual QString getPostScriptName() const { return QString(); } - - /// Returns character info - virtual CharacterInfos getCharacterInfos() const = 0; -}; - -/// Implementation of the PDFRealizedFont class using PIMPL pattern for Type 3 fonts -class PDFRealizedType3FontImpl : public IRealizedFontImpl -{ -public: - explicit PDFRealizedType3FontImpl(PDFFontPointer parentFont, PDFReal pixelSize) : m_pixelSize(pixelSize), m_parentFont(parentFont) { } - virtual ~PDFRealizedType3FontImpl() override = default; - - PDFReal getPixelSize() const { return m_pixelSize; } - - virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) override; - virtual bool isHorizontalWritingSystem() const override; - virtual CharacterInfos getCharacterInfos() const override; - -private: - /// Pixel size of the font - PDFReal m_pixelSize = 0.0; - - /// Parent font - PDFFontPointer m_parentFont; -}; - -/// Implementation of the PDFRealizedFont class using PIMPL pattern -class PDFRealizedFontImpl : public IRealizedFontImpl -{ -public: - explicit PDFRealizedFontImpl(); - virtual ~PDFRealizedFontImpl(); - - virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) override; - virtual bool isHorizontalWritingSystem() const override { return !m_isVertical; } - virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const override; - virtual QString getPostScriptName() const override { return m_postScriptName; } - virtual CharacterInfos getCharacterInfos() const override; - - static constexpr const PDFReal PIXEL_SIZE_MULTIPLIER = 100.0; - -private: - friend class PDFRealizedFont; - - static constexpr const PDFReal FONT_WIDTH_MULTIPLIER = 1.0 / 1000.0; - static constexpr const PDFReal FORMAT_26_6_MULTIPLIER = 1 / 64.0; - static constexpr const PDFReal FONT_MULTIPLIER = FORMAT_26_6_MULTIPLIER / PIXEL_SIZE_MULTIPLIER; - - struct Glyph - { - QPainterPath glyph; - PDFReal advance = 0.0; - }; - - static int outlineMoveTo(const FT_Vector* to, void* user); - static int outlineLineTo(const FT_Vector* to, void* user); - static int outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user); - static int outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user); - - /// Get glyph for glyph index - const Glyph& getGlyph(unsigned int glyphIndex); - - /// Function checks, if error occured, and if yes, then exception is thrown - static void checkFreeTypeError(FT_Error error); - - /// Read/write lock for accessing the glyph data - QReadWriteLock m_readWriteLock; - - /// Glyph cache, must be protected by the mutex above - std::unordered_map m_glyphCache; - - /// For embedded fonts, this byte array contains embedded font data - QByteArray m_embeddedFontData; - - /// For system fonts, this byte array contains system font data - QByteArray m_systemFontData; - - /// Instance of FreeType library assigned to this font - FT_Library m_library; - - /// Face of the font - FT_Face m_face; - - /// Pixel size of the font - PDFReal m_pixelSize; - - /// Parent font - PDFFontPointer m_parentFont; - - /// True, if font is embedded - bool m_isEmbedded; - - /// True, if font has vertical writing system - bool m_isVertical; - - /// Postscript name of the font - QString m_postScriptName; -}; - -PDFRealizedFontImpl::PDFRealizedFontImpl() : - m_library(nullptr), - m_face(nullptr), - m_pixelSize(0.0), - m_parentFont(nullptr), - m_isEmbedded(false), - m_isVertical(false) -{ - -} - -PDFRealizedFontImpl::~PDFRealizedFontImpl() -{ - if (m_face) - { - FT_Done_Face(m_face); - m_face = nullptr; - } - - if (m_library) - { - FT_Done_FreeType(m_library); - m_library = nullptr; - } -} - -void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) -{ - switch (m_parentFont->getFontType()) - { - case FontType::Type1: - case FontType::TrueType: - case FontType::MMType1: - { - // We can use encoding - Q_ASSERT(dynamic_cast(m_parentFont.get())); - const PDFSimpleFont* font = static_cast(m_parentFont.get()); - const encoding::EncodingTable* encoding = font->getEncoding(); - const GlyphIndices* glyphIndices = font->getGlyphIndices(); - - textSequence.items.reserve(textSequence.items.size() + byteArray.size()); - for (int i = 0, count = byteArray.size(); i < count; ++i) - { - GID glyphIndex = (*glyphIndices)[static_cast(byteArray[i])]; - - if (!glyphIndex) - { - // Try to obtain glyph index from unicode - if (m_face->charmap && m_face->charmap->encoding == FT_ENCODING_UNICODE) - { - glyphIndex = FT_Get_Char_Index(m_face, (*encoding)[static_cast(byteArray[i])].unicode()); - } - } - - const PDFReal glyphWidth = font->getGlyphAdvance(static_cast(byteArray[i])); - - if (glyphIndex) - { - const Glyph& glyph = getGlyph(glyphIndex); - textSequence.items.emplace_back(&glyph.glyph, (*encoding)[static_cast(byteArray[i])], glyph.advance); - } - else - { - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Glyph for simple font character code '%1' not found.").arg(static_cast(byteArray[i]))); - if (glyphWidth > 0) - { - const QPainterPath* nullpath = nullptr; - textSequence.items.emplace_back(nullpath, QChar(), glyphWidth * m_pixelSize * FONT_WIDTH_MULTIPLIER); - } - } - } - break; - } - - case FontType::Type0: - { - Q_ASSERT(dynamic_cast(m_parentFont.get())); - const PDFType0Font* font = static_cast(m_parentFont.get()); - - const PDFFontCMap* cmap = font->getCMap(); - const PDFFontCMap* toUnicode = font->getToUnicode(); - const PDFCIDtoGIDMapper* CIDtoGIDmapper = font->getCIDtoGIDMapper(); - - std::vector cids = cmap->interpret(byteArray); - textSequence.items.reserve(textSequence.items.size() + cids.size()); - for (CID cid : cids) - { - const GID glyphIndex = CIDtoGIDmapper->map(cid); - const PDFReal glyphWidth = font->getGlyphAdvance(cid); - - if (glyphIndex) - { - QChar character = toUnicode->getToUnicode(cid); - const Glyph& glyph = getGlyph(glyphIndex); - textSequence.items.emplace_back(&glyph.glyph, character, glyph.advance); - } - else - { - if (cid > 0) - { - // Character with CID == 0 is treated as default whitespace, it hasn't glyph - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Glyph for composite font character with cid '%1' not found.").arg(cid)); - } - - if (glyphWidth > 0) - { - // We do not multiply advance with font size and FONT_WIDTH_MULTIPLIER, because in the code, - // "advance" is treated as in font space. - const QPainterPath* nullpath = nullptr; - textSequence.items.emplace_back(nullpath, QChar(), -glyphWidth); - } - } - } - - break; - } - - default: - { - // Unhandled font type - Q_ASSERT(false); - break; - } - } -} - -CharacterInfos PDFRealizedFontImpl::getCharacterInfos() const -{ - CharacterInfos result; - - switch (m_parentFont->getFontType()) - { - case FontType::Type1: - case FontType::TrueType: - case FontType::MMType1: - { - // We can use encoding - Q_ASSERT(dynamic_cast(m_parentFont.get())); - const PDFSimpleFont* font = static_cast(m_parentFont.get()); - const encoding::EncodingTable* encoding = font->getEncoding(); - const GlyphIndices* glyphIndices = font->getGlyphIndices(); - - for (size_t i = 0; i < encoding->size(); ++i) - { - QChar character = (*encoding)[i]; - GID glyphIndex = (*glyphIndices)[static_cast(i)]; - - if (!glyphIndex) - { - // Try to obtain glyph index from unicode - if (m_face->charmap && m_face->charmap->encoding == FT_ENCODING_UNICODE) - { - glyphIndex = FT_Get_Char_Index(m_face, character.unicode()); - } - } - - if (glyphIndex) - { - CharacterInfo info; - info.gid = glyphIndex; - info.character = character; - result.emplace_back(qMove(info)); - } - } - - break; - } - - case FontType::Type0: - { - Q_ASSERT(dynamic_cast(m_parentFont.get())); - const PDFType0Font* font = static_cast(m_parentFont.get()); - - const PDFFontCMap* toUnicode = font->getToUnicode(); - const PDFCIDtoGIDMapper* CIDtoGIDmapper = font->getCIDtoGIDMapper(); - - FT_UInt index = 0; - FT_ULong character = FT_Get_First_Char(m_face, &index); - while (index != 0) - { - const GID gid = index; - const CID cid = CIDtoGIDmapper->unmap(gid); - - CharacterInfo info; - info.gid = gid; - info.character = toUnicode->getToUnicode(cid); - result.emplace_back(qMove(info)); - - character = FT_Get_Next_Char(m_face, character, &index); - } - - if (result.empty()) - { - // We will try all reasonable high CIDs - for (CID cid = 0; cid < QChar::LastValidCodePoint; ++cid) - { - const GID gid = CIDtoGIDmapper->map(cid); - - if (!gid) - { - continue; - } - - if (!FT_Load_Glyph(m_face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) - { - CharacterInfo info; - info.gid = gid; - info.character = toUnicode->getToUnicode(cid); - result.emplace_back(qMove(info)); - } - } - } - - break; - } - - default: - { - // Unhandled font type - Q_ASSERT(false); - break; - } - } - - return result; -} - -void PDFRealizedFontImpl::dumpFontToTreeItem(QTreeWidgetItem* item) const -{ - QTreeWidgetItem* root = new QTreeWidgetItem(item, { PDFTranslationContext::tr("Details") }); - - if (m_face->family_name) - { - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Font"), QString::fromLatin1(m_face->family_name) }); - } - if (m_face->style_name) - { - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Style"), QString::fromLatin1(m_face->style_name) }); - } - - QString yesString = PDFTranslationContext::tr("Yes"); - QString noString = PDFTranslationContext::tr("No"); - - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Glyph count"), QString::number(m_face->num_glyphs) }); - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is CID keyed"), (m_face->face_flags & FT_FACE_FLAG_CID_KEYED) ? yesString : noString }); - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is bold"), (m_face->style_flags & FT_STYLE_FLAG_BOLD) ? yesString : noString }); - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is italics"), (m_face->style_flags & FT_STYLE_FLAG_ITALIC) ? yesString : noString }); - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has vertical writing system"), (m_face->face_flags & FT_FACE_FLAG_VERTICAL) ? yesString : noString }); - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has SFNT storage scheme"), (m_face->face_flags & FT_FACE_FLAG_SFNT) ? yesString : noString }); - new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has glyph names"), (m_face->face_flags & FT_FACE_FLAG_GLYPH_NAMES) ? yesString : noString }); - - if (m_face->num_charmaps > 0) - { - QTreeWidgetItem* encodingRoot = new QTreeWidgetItem(item, { PDFTranslationContext::tr("Encoding") }); - for (FT_Int i = 0; i < m_face->num_charmaps; ++i) - { - FT_CharMap charMap = m_face->charmaps[i]; - - const FT_Encoding encoding = charMap->encoding; - QString encodingName; - switch (encoding) - { - case FT_ENCODING_NONE: - encodingName = PDFTranslationContext::tr("None"); - break; - - case FT_ENCODING_UNICODE: - encodingName = PDFTranslationContext::tr("Unicode"); - break; - - case FT_ENCODING_MS_SYMBOL: - encodingName = PDFTranslationContext::tr("MS Symbol"); - break; - - case FT_ENCODING_SJIS: - encodingName = PDFTranslationContext::tr("Japanese Shift JIS"); - break; - - case FT_ENCODING_PRC: - encodingName = PDFTranslationContext::tr("PRC - Simplified Chinese"); - break; - - case FT_ENCODING_BIG5: - encodingName = PDFTranslationContext::tr("Traditional Chinese"); - break; - - case FT_ENCODING_WANSUNG: - encodingName = PDFTranslationContext::tr("Korean Extended Wansung"); - break; - - case FT_ENCODING_JOHAB: - encodingName = PDFTranslationContext::tr("Korean Standard"); - break; - - case FT_ENCODING_ADOBE_STANDARD: - encodingName = PDFTranslationContext::tr("Adobe Standard"); - break; - - case FT_ENCODING_ADOBE_EXPERT: - encodingName = PDFTranslationContext::tr("Adobe Expert"); - break; - case FT_ENCODING_ADOBE_CUSTOM: - encodingName = PDFTranslationContext::tr("Adobe Custom"); - break; - - case FT_ENCODING_ADOBE_LATIN_1: - encodingName = PDFTranslationContext::tr("Adobe Latin 1"); - break; - - case FT_ENCODING_OLD_LATIN_2: - encodingName = PDFTranslationContext::tr("Old Latin 1"); - break; - - case FT_ENCODING_APPLE_ROMAN: - encodingName = PDFTranslationContext::tr("Apple Roman"); - break; - - default: - encodingName = PDFTranslationContext::tr("Unknown"); - break; - } - - QString encodingString = PDFTranslationContext::tr("Platform/Encoding = %1 %2").arg(charMap->platform_id).arg(charMap->encoding_id); - new QTreeWidgetItem(encodingRoot, { encodingName, encodingString }); - } - } -} - -int PDFRealizedFontImpl::outlineMoveTo(const FT_Vector* to, void* user) -{ - Glyph* glyph = reinterpret_cast(user); - glyph->glyph.moveTo(to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); - return 0; -} - -int PDFRealizedFontImpl::outlineLineTo(const FT_Vector* to, void* user) -{ - Glyph* glyph = reinterpret_cast(user); - glyph->glyph.lineTo(to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); - return 0; -} - -int PDFRealizedFontImpl::outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user) -{ - Glyph* glyph = reinterpret_cast(user); - glyph->glyph.quadTo(control->x * FONT_MULTIPLIER, control->y * FONT_MULTIPLIER, to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); - return 0; -} - -int PDFRealizedFontImpl::outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user) -{ - Glyph* glyph = reinterpret_cast(user); - glyph->glyph.cubicTo(control1->x * FONT_MULTIPLIER, control1->y * FONT_MULTIPLIER, control2->x * FONT_MULTIPLIER, control2->y * FONT_MULTIPLIER, to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); - return 0; -} - -const PDFRealizedFontImpl::Glyph& PDFRealizedFontImpl::getGlyph(unsigned int glyphIndex) -{ - if (glyphIndex) - { - { - QReadLocker readLock(&m_readWriteLock); - - // First look into cache - auto it = m_glyphCache.find(glyphIndex); - if (it != m_glyphCache.cend()) - { - return it->second; - } - } - - QWriteLocker writeLock(&m_readWriteLock); - Glyph glyph; - - FT_Outline_Funcs glyphOutlineInterface; - glyphOutlineInterface.delta = 0; - glyphOutlineInterface.shift = 0; - glyphOutlineInterface.move_to = PDFRealizedFontImpl::outlineMoveTo; - glyphOutlineInterface.line_to = PDFRealizedFontImpl::outlineLineTo; - glyphOutlineInterface.conic_to = PDFRealizedFontImpl::outlineConicTo; - glyphOutlineInterface.cubic_to = PDFRealizedFontImpl::outlineCubicTo; - - checkFreeTypeError(FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)); - checkFreeTypeError(FT_Outline_Decompose(&m_face->glyph->outline, &glyphOutlineInterface, &glyph)); - glyph.glyph.closeSubpath(); - glyph.advance = !m_isVertical ? m_face->glyph->advance.x : m_face->glyph->advance.y; - glyph.advance *= FONT_MULTIPLIER; - - auto it = m_glyphCache.find(glyphIndex); - if (it == m_glyphCache.cend()) - { - it = m_glyphCache.insert(std::make_pair(glyphIndex, qMove(glyph))).first; - } - return it->second; - } - - static Glyph dummy; - return dummy; -} - -void PDFRealizedFontImpl::checkFreeTypeError(FT_Error error) -{ - if (error) - { - QString message; - if (const char* errorString = FT_Error_String(error)) - { - message = QString::fromLatin1(errorString); - } - - throw PDFException(PDFTranslationContext::tr("FreeType error code %1: %2").arg(error).arg(message)); - } -} - -PDFRealizedFont::~PDFRealizedFont() -{ - delete m_impl; -} - -void PDFRealizedFont::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) -{ - m_impl->fillTextSequence(byteArray, textSequence, reporter); -} - -bool PDFRealizedFont::isHorizontalWritingSystem() const -{ - return m_impl->isHorizontalWritingSystem(); -} - -void PDFRealizedFont::dumpFontToTreeItem(QTreeWidgetItem* item) const -{ - m_impl->dumpFontToTreeItem(item); -} - -QString PDFRealizedFont::getPostScriptName() const -{ - return m_impl->getPostScriptName(); -} - -CharacterInfos PDFRealizedFont::getCharacterInfos() const -{ - return m_impl->getCharacterInfos(); -} - -PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(PDFFontPointer font, PDFReal pixelSize, PDFRenderErrorReporter* reporter) -{ - PDFRealizedFontPointer result; - - if (font->getFontType() == FontType::Type3) - { - result.reset(new PDFRealizedFont(new PDFRealizedType3FontImpl(font, pixelSize))); - } - else - { - std::unique_ptr implPtr(new PDFRealizedFontImpl()); - - PDFRealizedFontImpl* impl = implPtr.get(); - impl->m_parentFont = font; - impl->m_pixelSize = pixelSize; - - const FontDescriptor* descriptor = font->getFontDescriptor(); - if (descriptor->isEmbedded()) - { - PDFRealizedFontImpl::checkFreeTypeError(FT_Init_FreeType(&impl->m_library)); - const QByteArray* embeddedFontData = descriptor->getEmbeddedFontData(); - Q_ASSERT(embeddedFontData); - impl->m_embeddedFontData = *embeddedFontData; - - // At this time, embedded font data should not be empty! - Q_ASSERT(!impl->m_embeddedFontData.isEmpty()); - - PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast(impl->m_embeddedFontData.constData()), impl->m_embeddedFontData.size(), 0, &impl->m_face)); - FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE); // We try to select unicode encoding, but if it fails, we don't do anything (use glyph indices instead) - PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize * PDFRealizedFontImpl::PIXEL_SIZE_MULTIPLIER))); - impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL; - impl->m_isEmbedded = true; - result.reset(new PDFRealizedFont(implPtr.release())); - } - else - { - StandardFontType standardFontType = StandardFontType::Invalid; - if (font->getFontType() == FontType::Type1 || font->getFontType() == FontType::MMType1) - { - Q_ASSERT(dynamic_cast(font.get())); - const PDFType1Font* type1Font = static_cast(font.get()); - standardFontType = type1Font->getStandardFontType(); - } - - const PDFSystemFontInfoStorage* fontStorage = PDFSystemFontInfoStorage::getInstance(); - impl->m_systemFontData = fontStorage->loadFont(descriptor, standardFontType, reporter); - - if (impl->m_systemFontData.isEmpty()) - { - throw PDFException(PDFTranslationContext::tr("Can't load system font '%1'.").arg(QString::fromLatin1(descriptor->fontName))); - } - - PDFRealizedFontImpl::checkFreeTypeError(FT_Init_FreeType(&impl->m_library)); - PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast(impl->m_systemFontData.constData()), impl->m_systemFontData.size(), 0, &impl->m_face)); - FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE); // We try to select unicode encoding, but if it fails, we don't do anything (use glyph indices instead) - PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize * PDFRealizedFontImpl::PIXEL_SIZE_MULTIPLIER))); - impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL; - impl->m_isEmbedded = false; - if (const char* postScriptName = FT_Get_Postscript_Name(impl->m_face)) - { - impl->m_postScriptName = QString::fromLatin1(postScriptName); - } - result.reset(new PDFRealizedFont(implPtr.release())); - } - } - - return result; -} - -FontDescriptor PDFFont::readFontDescriptor(const PDFObject& fontDescriptorObject, const PDFDocument* document) -{ - FontDescriptor fontDescriptor; - PDFDocumentDataLoaderDecorator fontLoader(document); - if (fontDescriptorObject.isDictionary()) - { - const PDFDictionary* fontDescriptorDictionary = fontDescriptorObject.getDictionary(); - fontDescriptor.fontName = fontLoader.readNameFromDictionary(fontDescriptorDictionary, "FontName"); - fontDescriptor.fontFamily = fontLoader.readStringFromDictionary(fontDescriptorDictionary, "FontFamily"); - - constexpr const std::array, 9> stretches = { - std::pair{ "UltraCondensed", QFont::UltraCondensed }, - std::pair{ "ExtraCondensed", QFont::ExtraCondensed }, - std::pair{ "Condensed", QFont::Condensed }, - std::pair{ "SemiCondensed", QFont::SemiCondensed }, - std::pair{ "Normal", QFont::Unstretched }, - std::pair{ "SemiExpanded", QFont::SemiExpanded }, - std::pair{ "Expanded", QFont::Expanded }, - std::pair{ "ExtraExpanded", QFont::ExtraExpanded }, - std::pair{ "UltraExpanded", QFont::UltraExpanded } - }; - fontDescriptor.fontStretch = fontLoader.readEnumByName(fontDescriptorDictionary->get("FontStretch"), stretches.cbegin(), stretches.cend(), QFont::Unstretched); - fontDescriptor.fontWeight = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "FontWeight", 500); - fontDescriptor.italicAngle = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "ItalicAngle", 0.0); - fontDescriptor.ascent = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "Ascent", 0.0); - fontDescriptor.descent = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "Descent", 0.0); - fontDescriptor.leading = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "Leading", 0.0); - fontDescriptor.capHeight = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "CapHeight", 0.0); - fontDescriptor.xHeight = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "XHeight", 0.0); - fontDescriptor.stemV = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "StemV", 0.0); - fontDescriptor.stemH = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "StemH", 0.0); - fontDescriptor.avgWidth = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "AvgWidth", 0.0); - fontDescriptor.maxWidth = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "MaxWidth", 0.0); - fontDescriptor.missingWidth = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "MissingWidth", 0.0); - fontDescriptor.flags = fontLoader.readIntegerFromDictionary(fontDescriptorDictionary, "Flags", 0); - fontDescriptor.boundingBox = fontLoader.readRectangle(fontDescriptorDictionary->get("FontBBox"), QRectF()); - fontDescriptor.charset = fontLoader.readStringFromDictionary(fontDescriptorDictionary, "Charset"); - - auto loadStream = [fontDescriptorDictionary, document](QByteArray& byteArray, const char* name) - { - if (fontDescriptorDictionary->hasKey(name)) - { - const PDFObject& streamObject = document->getObject(fontDescriptorDictionary->get(name)); - if (streamObject.isStream()) - { - byteArray = document->getDecodedStream(streamObject.getStream()); - } - } - }; - loadStream(fontDescriptor.fontFile, "FontFile"); - loadStream(fontDescriptor.fontFile2, "FontFile2"); - loadStream(fontDescriptor.fontFile3, "FontFile3"); - } - - return fontDescriptor; -} - -PDFFontPointer PDFFont::createFont(const PDFObject& object, const PDFDocument* document) -{ - const PDFObject& dereferencedFontDictionary = document->getObject(object); - if (!dereferencedFontDictionary.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Font object must be a dictionary.")); - } - - const PDFDictionary* fontDictionary = dereferencedFontDictionary.getDictionary(); - PDFDocumentDataLoaderDecorator fontLoader(document); - - // First, determine the font subtype - constexpr const std::array fontTypes = { - std::pair{ "Type0", FontType::Type0 }, - std::pair{ "Type1", FontType::Type1 }, - std::pair{ "TrueType", FontType::TrueType }, - std::pair{ "Type3", FontType::Type3}, - std::pair{ "MMType1", FontType::MMType1 } - }; - - const FontType fontType = fontLoader.readEnumByName(fontDictionary->get("Subtype"), fontTypes.cbegin(), fontTypes.cend(), FontType::Invalid); - if (fontType == FontType::Invalid) - { - throw PDFException(PDFTranslationContext::tr("Invalid font type.")); - } - - QByteArray name = fontLoader.readNameFromDictionary(fontDictionary, "Name"); - QByteArray baseFont = fontLoader.readNameFromDictionary(fontDictionary, "BaseFont"); - const PDFInteger firstChar = fontLoader.readIntegerFromDictionary(fontDictionary, "FirstChar", 0); - const PDFInteger lastChar = fontLoader.readIntegerFromDictionary(fontDictionary, "LastChar", 255); - std::vector widths = fontLoader.readIntegerArrayFromDictionary(fontDictionary, "Widths"); - - // Read standard font - constexpr const std::array, 14> standardFonts = { - std::pair{ "Times-Roman", StandardFontType::TimesRoman }, - std::pair{ "Times-Bold", StandardFontType::TimesRomanBold }, - std::pair{ "Times-Italic", StandardFontType::TimesRomanItalics }, - std::pair{ "Times-BoldItalic", StandardFontType::TimesRomanBoldItalics }, - std::pair{ "Helvetica", StandardFontType::Helvetica }, - std::pair{ "Helvetica-Bold", StandardFontType::HelveticaBold }, - std::pair{ "Helvetica-Oblique", StandardFontType::HelveticaOblique }, - std::pair{ "Helvetica-BoldOblique", StandardFontType::HelveticaBoldOblique }, - std::pair{ "Courier", StandardFontType::Courier }, - std::pair{ "Courier-Bold", StandardFontType::CourierBold }, - std::pair{ "Courier-Oblique", StandardFontType::CourierOblique }, - std::pair{ "Courier-BoldOblique", StandardFontType::CourierBoldOblique }, - std::pair{ "Symbol", StandardFontType::Symbol }, - std::pair{ "ZapfDingbats", StandardFontType::ZapfDingbats } - }; - const StandardFontType standardFont = fontLoader.readEnumByName(fontDictionary->get("BaseFont"), standardFonts.cbegin(), standardFonts.cend(), StandardFontType::Invalid); - - // Read Font Descriptor - const PDFObject& fontDescriptorObject = document->getObject(fontDictionary->get("FontDescriptor")); - FontDescriptor fontDescriptor = readFontDescriptor(fontDescriptorObject, document); - - // Read Font Encoding - // The font encoding for the simple font is determined by this algorithm: - // 1) Try to use Encoding dictionary to determine base encoding - // (it can be MacRomanEncoding, MacExpertEncoding, WinAnsiEncoding or StandardEncoding) - // 2) If it is not present, then try to obtain built-in encoding from the font file (usually, this is not possible) - // 3) Use default encoding for the font depending on the font type - // - one of the 14 base fonts - use builtin encoding for the font type - // - TrueType - use WinAnsiEncoding - // - all others - use StandardEncoding - // 4) Merge with Differences, if present - // 5) Fill missing characters from StandardEncoding - // After the encoding is obtained, try to extract glyph indices for embedded font. - - PDFEncoding::Encoding encoding = PDFEncoding::Encoding::Invalid; - encoding::EncodingTable simpleFontEncodingTable = { }; - GlyphIndices glyphIndexArray = { }; - switch (fontType) - { - case FontType::Type1: - case FontType::MMType1: - case FontType::TrueType: - { - bool hasDifferences = false; - encoding::EncodingTable differences = { }; - - if (fontDictionary->hasKey("Encoding")) - { - constexpr const std::array, 3> encodings = { - std::pair{ "MacRomanEncoding", PDFEncoding::Encoding::MacRoman }, - std::pair{ "MacExpertEncoding", PDFEncoding::Encoding::MacExpert }, - std::pair{ "WinAnsiEncoding", PDFEncoding::Encoding::WinAnsi } - }; - - const PDFObject& encodingObject = document->getObject(fontDictionary->get("Encoding")); - if (encodingObject.isName()) - { - // Decode name of the encoding - encoding = fontLoader.readEnumByName(encodingObject, encodings.cbegin(), encodings.cend(), PDFEncoding::Encoding::Invalid); - } - else if (encodingObject.isDictionary()) - { - // Dictionary with base encoding and differences (all optional) - const PDFDictionary* encodingDictionary = encodingObject.getDictionary(); - if (encodingDictionary->hasKey("BaseEncoding")) - { - encoding = fontLoader.readEnumByName(encodingDictionary->get("BaseEncoding"), encodings.cbegin(), encodings.cend(), PDFEncoding::Encoding::Invalid); - } - else - { - // We get encoding for the standard font. If we have invalid standard font, - // then we get standard encoding. So we shouldn't test it. - encoding = getEncodingForStandardFont(standardFont); - } - - if (encodingDictionary->hasKey("Differences")) - { - const PDFObject& differencesArray = document->getObject(encodingDictionary->get("Differences")); - if (differencesArray.isArray()) - { - hasDifferences = true; - const PDFArray* array = differencesArray.getArray(); - size_t currentOffset = 0; - for (size_t i = 0, count = array->getCount(); i < count; ++i) - { - const PDFObject& item = document->getObject(array->getItem(i)); - if (item.isInt()) - { - currentOffset = static_cast(item.getInteger()); - } - else if (item.isName()) - { - if (currentOffset >= differences.size()) - { - throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); - } - - QChar character = PDFNameToUnicode::getUnicodeUsingResolvedName(item.getString()); - differences[currentOffset] = character; - - ++currentOffset; - } - else - { - throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); - } - } - } - else - { - throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); - } - } - } - else - { - throw PDFException(PDFTranslationContext::tr("Invalid encoding entry of the font.")); - } - } - - if (encoding == PDFEncoding::Encoding::Invalid) - { - // We get encoding for the standard font. If we have invalid standard font, - // then we get standard encoding. So we shouldn't test it. - encoding = getEncodingForStandardFont(standardFont); - } - - if (encoding == PDFEncoding::Encoding::Invalid) - { - throw PDFException(PDFTranslationContext::tr("Invalid encoding entry of the font.")); - } - - simpleFontEncodingTable = *PDFEncoding::getTableForEncoding(encoding); - - auto finishFont = [&] - { - // Fill in differences - if (hasDifferences) - { - for (size_t i = 0; i < differences.size(); ++i) - { - if (!differences[i].isNull()) - { - simpleFontEncodingTable[i] = differences[i]; - } - } - - // Set the encoding to custom - encoding = PDFEncoding::Encoding::Custom; - } - - // Fill in missing characters from standard encoding - const encoding::EncodingTable& standardEncoding = *PDFEncoding::getTableForEncoding(PDFEncoding::Encoding::Standard); - for (size_t i = 0; i < standardEncoding.size(); ++i) - { - if ((simpleFontEncodingTable[i].isNull() || simpleFontEncodingTable[i] == QChar(QChar::SpecialCharacter::ReplacementCharacter)) && - (!standardEncoding[i].isNull() && standardEncoding[i] != QChar(QChar::SpecialCharacter::ReplacementCharacter))) - { - simpleFontEncodingTable[i] = standardEncoding[i]; - } - } - }; - - if (fontDescriptor.isEmbedded()) - { - // Return encoding from the embedded font - const QByteArray* embeddedFontData = fontDescriptor.getEmbeddedFontData(); - Q_ASSERT(embeddedFontData); - - FT_Library library; - if (!FT_Init_FreeType(&library)) - { - FT_Face face; - if (!FT_New_Memory_Face(library, reinterpret_cast(embeddedFontData->constData()), embeddedFontData->size(), 0, &face)) - { - if (FT_Has_PS_Glyph_Names(face)) - { - for (FT_Int i = 0; i < face->num_charmaps; ++i) - { - FT_CharMap charMap = face->charmaps[i]; - switch (charMap->encoding) - { - case FT_ENCODING_ADOBE_STANDARD: - case FT_ENCODING_ADOBE_LATIN_1: - case FT_ENCODING_ADOBE_CUSTOM: - case FT_ENCODING_ADOBE_EXPERT: - { - // Try to load data from the encoding - if (!FT_Set_Charmap(face, charMap)) - { - for (size_t i = 0; i < simpleFontEncodingTable.size(); ++i) - { - FT_UInt glyphIndex = FT_Get_Char_Index(face, static_cast(i)); - - if (glyphIndex == 0) - { - glyphIndex = FT_Get_Char_Index(face, static_cast(i + 0xF000)); - } - - if (glyphIndex == 0) - { - glyphIndex = FT_Get_Char_Index(face, static_cast(i + 0xF100)); - } - - if (glyphIndex > 0) - { - // Fill the glyph index array - glyphIndexArray[i] = glyphIndex; - - // Set mapping to unicode - char buffer[128] = { }; - if (!FT_Get_Glyph_Name(face, glyphIndex, buffer, static_cast(std::size(buffer)))) - { - QByteArray byteArrayBuffer(buffer); - QChar character = PDFNameToUnicode::getUnicodeForName(byteArrayBuffer); - if (character.isNull()) - { - character = PDFNameToUnicode::getUnicodeForNameZapfDingbats(byteArrayBuffer); - } - if (!character.isNull()) - { - encoding = PDFEncoding::Encoding::Custom; - simpleFontEncodingTable[i] = character; - } - } - } - } - } - - break; - } - - default: - break; - } - } - } - else if (!FT_Select_Charmap(face, FT_ENCODING_APPLE_ROMAN)) - { - // We have (1, 0) Mac Roman Encoding, which is slightly different, than Mac Roman Encoding defined - // in PDF (for 15 characters). - simpleFontEncodingTable = *PDFEncoding::getTableForEncoding(PDFEncoding::Encoding::MacOsRoman); - encoding = PDFEncoding::Encoding::Custom; - - for (size_t i = 0; i < simpleFontEncodingTable.size(); ++i) - { - FT_UInt glyphIndex = FT_Get_Char_Index(face, static_cast(i)); - if (glyphIndex > 0) - { - glyphIndexArray[i] = glyphIndex; - } - } - } - - finishFont(); - - // Fill the glyph index array from unicode, if we have unicode mapping - if (!FT_Select_Charmap(face, FT_ENCODING_UNICODE)) - { - for (size_t i = 0; i < simpleFontEncodingTable.size(); ++i) - { - QChar character = simpleFontEncodingTable[i]; - if (!character.isNull() && character != QChar(QChar::SpecialCharacter::ReplacementCharacter)) - { - const FT_UInt glyphIndex = FT_Get_Char_Index(face, character.unicode()); - if (glyphIndex > 0) - { - glyphIndexArray[i] = glyphIndex; - } - } - } - } - - FT_Done_Face(face); - } - - FT_Done_FreeType(library); - } - } - else - { - // Finish font - fill differences - finishFont(); - } - - break; - } - - case FontType::Type0: - { - // This is composite font (CID keyed font) - - // Load CMAP - PDFFontCMap cmap; - const PDFObject& cmapObject = document->getObject(fontDictionary->get("Encoding")); - if (cmapObject.isName()) - { - cmap = PDFFontCMap::createFromName(cmapObject.getString()); - } - else if (cmapObject.isStream()) - { - const PDFStream* stream = cmapObject.getStream(); - QByteArray decodedStream = document->getDecodedStream(stream); - cmap = PDFFontCMap::createFromData(decodedStream); - } - - if (!cmap.isValid()) - { - throw PDFException(PDFTranslationContext::tr("Invalid CMAP in CID-keyed font.")); - } - - const PDFObject& descendantFonts = document->getObject(fontDictionary->get("DescendantFonts")); - if (!descendantFonts.isArray()) - { - throw PDFException(PDFTranslationContext::tr("Invalid descendant font in CID-keyed font.")); - } - - const PDFArray* descendantFontsArray = descendantFonts.getArray(); - if (descendantFontsArray->getCount() != 1) - { - throw PDFException(PDFTranslationContext::tr("Invalid number (%1) of descendant fonts in CID-keyed font - exactly one is required.").arg(descendantFontsArray->getCount())); - } - - const PDFObject& descendantFont = document->getObject(descendantFontsArray->getItem(0)); - if (!descendantFont.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Invalid descendant font in CID-keyed font.")); - } - - const PDFDictionary* descendantFontDictionary = descendantFont.getDictionary(); - - const PDFObject& fontDescriptorObjectForCompositeFont = document->getObject(descendantFontDictionary->get("FontDescriptor")); - fontDescriptor = readFontDescriptor(fontDescriptorObjectForCompositeFont, document); - - QByteArray cidToGidMapping; - const PDFObject& cidToGidMappingObject = document->getObject(descendantFontDictionary->get("CIDtoGIDMap")); - if (cidToGidMappingObject.isStream()) - { - const PDFStream* cidToGidMappingStream = cidToGidMappingObject.getStream(); - cidToGidMapping = document->getDecodedStream(cidToGidMappingStream); - } - PDFCIDtoGIDMapper cidToGidMapper(qMove(cidToGidMapping)); - - baseFont = fontLoader.readNameFromDictionary(descendantFontDictionary, "BaseFont"); - - // Read default advance - PDFReal dw = fontLoader.readNumberFromDictionary(descendantFontDictionary, "DW", 1000.0); - std::array dw2 = { }; - fontLoader.readNumberArrayFromDictionary(descendantFontDictionary, "DW2", dw2.begin(), dw2.end()); - PDFReal defaultWidth = descendantFontDictionary->hasKey("DW") ? dw : dw2.back(); - - // Read horizontal advances - std::unordered_map advances; - if (descendantFontDictionary->hasKey("W")) - { - const PDFObject& wArrayObject = document->getObject(descendantFontDictionary->get("W")); - if (wArrayObject.isArray()) - { - const PDFArray* wArray = wArrayObject.getArray(); - const size_t size = wArray->getCount(); - - for (size_t i = 0; i < size;) - { - CID startCID = fontLoader.readInteger(wArray->getItem(i++), 0); - const PDFObject& arrayOrCID = document->getObject(wArray->getItem(i++)); - - if (arrayOrCID.isInt()) - { - CID endCID = arrayOrCID.getInteger(); - PDFReal width = fontLoader.readInteger(wArray->getItem(i++), 0); - for (CID currentCID = startCID; currentCID <= endCID; ++currentCID) - { - advances[currentCID] = width; - } - } - else if (arrayOrCID.isArray()) - { - const PDFArray* widthArray = arrayOrCID.getArray(); - const size_t widthArraySize = widthArray->getCount(); - for (size_t widthArrayIndex = 0; widthArrayIndex < widthArraySize; ++widthArrayIndex) - { - PDFReal width = fontLoader.readNumber(widthArray->getItem(widthArrayIndex), 0); - advances[startCID + static_cast(widthArrayIndex)] = width; - } - } - } - } - } - - PDFFontCMap toUnicodeCMap; - const PDFObject& toUnicode = document->getObject(fontDictionary->get("ToUnicode")); - if (toUnicode.isName()) - { - toUnicodeCMap = PDFFontCMap::createFromName(toUnicode.getString()); - } - else if (toUnicode.isStream()) - { - const PDFStream* stream = toUnicode.getStream(); - QByteArray decodedStream = document->getDecodedStream(stream); - toUnicodeCMap = PDFFontCMap::createFromData(decodedStream); - } - - return PDFFontPointer(new PDFType0Font(qMove(fontDescriptor), qMove(cmap), qMove(toUnicodeCMap), qMove(cidToGidMapper), defaultWidth, qMove(advances))); - } - - case FontType::Type3: - { - // Read the font matrix - std::vector fontMatrixValues = fontLoader.readNumberArrayFromDictionary(fontDictionary, "FontMatrix"); - - if (fontMatrixValues.size() != 6) - { - throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font matrix.")); - } - QMatrix fontMatrix(fontMatrixValues[0], fontMatrixValues[1], fontMatrixValues[2], fontMatrixValues[3], fontMatrixValues[4], fontMatrixValues[5]); - - PDFObject charProcs = document->getObject(fontDictionary->get("CharProcs")); - if (!charProcs.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font character content streams.")); - } - const PDFDictionary* charProcsDictionary = charProcs.getDictionary(); - - PDFInteger firstChar = fontLoader.readIntegerFromDictionary(fontDictionary, "FirstChar", -1); - PDFInteger lastChar = fontLoader.readIntegerFromDictionary(fontDictionary, "LastChar", -1); - - if (firstChar < 0 || lastChar > 255 || firstChar > lastChar) - { - throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font character range (from %1 to %2).").arg(firstChar).arg(lastChar)); - } - - const PDFObject& encoding = document->getObject(fontDictionary->get("Encoding")); - if (!encoding.isDictionary()) - { - throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font encoding.")); - } - - const PDFDictionary* encodingDictionary = encoding.getDictionary(); - const PDFObject& differences = document->getObject(encodingDictionary->get("Differences")); - if (!differences.isArray()) - { - throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font encoding.")); - } - - std::map characterContentStreams; - - const PDFArray* differencesArray = differences.getArray(); - size_t currentOffset = 0; - for (size_t i = 0, count = differencesArray->getCount(); i < count; ++i) - { - const PDFObject& item = document->getObject(differencesArray->getItem(i)); - if (item.isInt()) - { - currentOffset = static_cast(item.getInteger()); - } - else if (item.isName()) - { - if (currentOffset > 255) - { - throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of type 3 font.")); - } - - QByteArray characterName = item.getString(); - const PDFObject& characterContentStreamObject = document->getObject(charProcsDictionary->get(characterName)); - if (characterContentStreamObject.isStream()) - { - QByteArray contentStream = document->getDecodedStream(characterContentStreamObject.getStream()); - characterContentStreams[static_cast(currentOffset)] = qMove(contentStream); - } - - ++currentOffset; - } - else - { - throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of type 3 font.")); - } - } - - PDFFontCMap toUnicodeCMap; - const PDFObject& toUnicode = document->getObject(fontDictionary->get("ToUnicode")); - if (toUnicode.isName()) - { - toUnicodeCMap = PDFFontCMap::createFromName(toUnicode.getString()); - } - else if (toUnicode.isStream()) - { - const PDFStream* stream = toUnicode.getStream(); - QByteArray decodedStream = document->getDecodedStream(stream); - toUnicodeCMap = PDFFontCMap::createFromData(decodedStream); - } - - std::vector widths = fontLoader.readNumberArrayFromDictionary(fontDictionary, "Widths"); - return PDFFontPointer(new PDFType3Font(qMove(fontDescriptor), firstChar, lastChar, fontMatrix, qMove(characterContentStreams), qMove(widths), document->getObject(fontDictionary->get("Resources")), qMove(toUnicodeCMap))); - } - - default: - { - Q_ASSERT(false); - break; - } - } - - switch (fontType) - { - case FontType::Type1: - case FontType::MMType1: - return PDFFontPointer(new PDFType1Font(fontType, qMove(fontDescriptor), qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encoding, simpleFontEncodingTable, standardFont, glyphIndexArray)); - - case FontType::TrueType: - return PDFFontPointer(new PDFTrueTypeFont(qMove(fontDescriptor), qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encoding, simpleFontEncodingTable, glyphIndexArray)); - - default: - { - Q_ASSERT(false); - break; - } - } - - return PDFFontPointer(); -} - -PDFSimpleFont::PDFSimpleFont(FontDescriptor fontDescriptor, - QByteArray name, - QByteArray baseFont, - PDFInteger firstChar, - PDFInteger lastChar, - std::vector widths, - PDFEncoding::Encoding encodingType, - encoding::EncodingTable encoding, - GlyphIndices glyphIndices) : - PDFFont(qMove(fontDescriptor)), - m_name(qMove(name)), - m_baseFont(qMove(baseFont)), - m_firstChar(firstChar), - m_lastChar(lastChar), - m_widths(qMove(widths)), - m_encodingType(encodingType), - m_encoding(encoding), - m_glyphIndices(glyphIndices) -{ - -} - -PDFInteger PDFSimpleFont::getGlyphAdvance(size_t index) const -{ - const size_t min = m_firstChar; - const size_t max = m_lastChar; - - if (index >= min && index <= max) - { - const size_t adjustedIndex = index - min; - if (adjustedIndex < m_widths.size()) - { - return m_widths[adjustedIndex]; - } - } - - return 0; -} - -void PDFSimpleFont::dumpFontToTreeItem(QTreeWidgetItem* item) const -{ - BaseClass::dumpFontToTreeItem(item); - - QString encodingTypeString; - switch (m_encodingType) - { - case PDFEncoding::Encoding::Standard: - encodingTypeString = PDFTranslationContext::tr("Standard"); - break; - - case PDFEncoding::Encoding::MacRoman: - encodingTypeString = PDFTranslationContext::tr("Mac Roman"); - break; - - case PDFEncoding::Encoding::WinAnsi: - encodingTypeString = PDFTranslationContext::tr("Win Ansi"); - break; - - case PDFEncoding::Encoding::PDFDoc: - encodingTypeString = PDFTranslationContext::tr("PDF Doc"); - break; - - case PDFEncoding::Encoding::MacExpert: - encodingTypeString = PDFTranslationContext::tr("Mac Expert"); - break; - - case PDFEncoding::Encoding::Symbol: - encodingTypeString = PDFTranslationContext::tr("Symbol"); - break; - - case PDFEncoding::Encoding::ZapfDingbats: - encodingTypeString = PDFTranslationContext::tr("Zapf Dingbats"); - break; - - case PDFEncoding::Encoding::MacOsRoman: - encodingTypeString = PDFTranslationContext::tr("Mac OS Roman"); - break; - - case PDFEncoding::Encoding::Custom: - encodingTypeString = PDFTranslationContext::tr("Custom"); - break; - - default: - { - Q_ASSERT(false); - break; - } - } - - new QTreeWidgetItem(item, { PDFTranslationContext::tr("Encoding"), encodingTypeString }); -} - -PDFType1Font::PDFType1Font(FontType fontType, - FontDescriptor fontDescriptor, - QByteArray name, - QByteArray baseFont, - PDFInteger firstChar, - PDFInteger lastChar, - std::vector widths, - PDFEncoding::Encoding encodingType, - encoding::EncodingTable encoding, - StandardFontType standardFontType, - GlyphIndices glyphIndices) : - PDFSimpleFont(qMove(fontDescriptor), qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encodingType, encoding, glyphIndices), - m_fontType(fontType), - m_standardFontType(standardFontType) -{ - -} - -FontType PDFType1Font::getFontType() const -{ - return m_fontType; -} - -void PDFType1Font::dumpFontToTreeItem(QTreeWidgetItem* item) const -{ - BaseClass::dumpFontToTreeItem(item); - - if (m_standardFontType != StandardFontType::Invalid) - { - QString standardFontTypeString; - switch (m_standardFontType) - { - case StandardFontType::TimesRoman: - case StandardFontType::TimesRomanBold: - case StandardFontType::TimesRomanItalics: - case StandardFontType::TimesRomanBoldItalics: - standardFontTypeString = PDFTranslationContext::tr("Times Roman"); - break; - - case StandardFontType::Helvetica: - case StandardFontType::HelveticaBold: - case StandardFontType::HelveticaOblique: - case StandardFontType::HelveticaBoldOblique: - standardFontTypeString = PDFTranslationContext::tr("Helvetica"); - break; - - case StandardFontType::Courier: - case StandardFontType::CourierBold: - case StandardFontType::CourierOblique: - case StandardFontType::CourierBoldOblique: - standardFontTypeString = PDFTranslationContext::tr("Courier"); - break; - - case StandardFontType::Symbol: - standardFontTypeString = PDFTranslationContext::tr("Symbol"); - break; - - case StandardFontType::ZapfDingbats: - standardFontTypeString = PDFTranslationContext::tr("Zapf Dingbats"); - break; - - default: - Q_ASSERT(false); - break; - } - - new QTreeWidgetItem(item, { PDFTranslationContext::tr("Standard font"), standardFontTypeString }); - } -} - -FontType PDFTrueTypeFont::getFontType() const -{ - return FontType::TrueType; -} - -void PDFFontCache::setDocument(const PDFModifiedDocument& document) -{ - QMutexLocker lock(&m_mutex); - if (m_document != document) - { - m_document = document; - - // Jakub Melka: If document has not reset flag, then fonts of the - // document remains the same. So it is not needed to clear font cache. - if (document.hasReset()) - { - m_fontCache.clear(); - m_realizedFontCache.clear(); - } - } -} - -PDFFontPointer PDFFontCache::getFont(const PDFObject& fontObject) const -{ - if (fontObject.isReference()) - { - // Font is object reference. Look in the cache, if we have it, then return it. - - QMutexLocker lock(&m_mutex); - PDFObjectReference reference = fontObject.getReference(); - - auto it = m_fontCache.find(reference); - if (it == m_fontCache.cend()) - { - // We must create the font - PDFFontPointer font = PDFFont::createFont(fontObject, m_document); - - if (m_fontCacheShrinkDisabledObjects.empty() && m_fontCache.size() >= m_fontCacheLimit) - { - // We have exceeded the cache limit. Clear the cache. - m_fontCache.clear(); - } - - it = m_fontCache.insert(std::make_pair(reference, qMove(font))).first; - } - return it->second; - } - else - { - // Object is not a reference. Create font directly and return it. - return PDFFont::createFont(fontObject, m_document); - } -} - -PDFRealizedFontPointer PDFFontCache::getRealizedFont(const PDFFontPointer& font, PDFReal size, PDFRenderErrorReporter* reporter) const -{ - Q_ASSERT(font); - - QMutexLocker lock(&m_mutex); - auto it = m_realizedFontCache.find(std::make_pair(font, size)); - if (it == m_realizedFontCache.cend()) - { - // We must create the realized font - PDFRealizedFontPointer realizedFont = PDFRealizedFont::createRealizedFont(font, size, reporter); - - if (m_fontCacheShrinkDisabledObjects.empty() && m_realizedFontCache.size() >= m_realizedFontCacheLimit) - { - m_realizedFontCache.clear(); - } - - it = m_realizedFontCache.insert(std::make_pair(std::make_pair(font, size), qMove(realizedFont))).first; - } - - return it->second; -} - -void PDFFontCache::setCacheShrinkEnabled(const void* source, bool enabled) -{ - QMutexLocker lock(&m_mutex); - if (enabled) - { - m_fontCacheShrinkDisabledObjects.erase(source); - lock.unlock(); - shrink(); - } - else - { - m_fontCacheShrinkDisabledObjects.insert(source); - } -} - -void PDFFontCache::setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit) -{ - if (m_fontCacheLimit != fontCacheLimit || m_realizedFontCacheLimit != instancedFontCacheLimit) - { - m_fontCacheLimit = fontCacheLimit; - m_realizedFontCacheLimit = instancedFontCacheLimit; - shrink(); - } -} - -void PDFFontCache::shrink() -{ - QMutexLocker lock(&m_mutex); - if (m_fontCacheShrinkDisabledObjects.empty()) - { - if (m_fontCache.size() >= m_fontCacheLimit) - { - m_fontCache.clear(); - } - if (m_realizedFontCache.size() >= m_realizedFontCacheLimit) - { - m_realizedFontCache.clear(); - } - } -} - -const QByteArray* FontDescriptor::getEmbeddedFontData() const -{ - if (!fontFile.isEmpty()) - { - return &fontFile; - } - else if (!fontFile2.isEmpty()) - { - return &fontFile2; - } - else if (!fontFile3.isEmpty()) - { - return &fontFile3; - } - - return nullptr; -} - -PDFFontCMap PDFFontCMap::createFromName(const QByteArray& name) -{ - QFile file(QString(":/cmaps/%1").arg(QString::fromLatin1(name))); - if (file.exists()) - { - QByteArray data; - if (file.open(QFile::ReadOnly)) - { - data = file.readAll(); - file.close(); - } - - return createFromData(data); - } - - throw PDFException(PDFTranslationContext::tr("Can't load CID font mapping named '%1'.").arg(QString::fromLatin1(name))); - return PDFFontCMap(); -} - -PDFFontCMap PDFFontCMap::createFromData(const QByteArray& data) -{ - Entries entries; - entries.reserve(1024); // Arbitrary number, we have enough memory, better than perform reallocation each time - - std::vector additionalMappings; - PDFLexicalAnalyzer parser(data.constBegin(), data.constEnd()); - - bool vertical = false; - PDFLexicalAnalyzer::Token previousToken; - while (!parser.isAtEnd()) - { - PDFLexicalAnalyzer::Token token = parser.fetch(); - - if (token.type == PDFLexicalAnalyzer::TokenType::Name && token.data.toByteArray() == "WMode") - { - PDFLexicalAnalyzer::Token valueToken = parser.fetch(); - vertical = valueToken.type == PDFLexicalAnalyzer::TokenType::Integer && valueToken.data.value() == 1; - continue; - } - - auto fetchCode = [] (const PDFLexicalAnalyzer::Token& currentToken) -> std::pair - { - if (currentToken.type == PDFLexicalAnalyzer::TokenType::String) - { - QByteArray byteArray = currentToken.data.toByteArray(); - - unsigned int codeValue = 0; - for (int i = 0; i < byteArray.size(); ++i) - { - codeValue = (codeValue << 8) + static_cast(byteArray[i]); - } - - return std::make_pair(codeValue, byteArray.size()); - } - - throw PDFException(PDFTranslationContext::tr("Can't fetch code from CMap definition.")); - return std::pair(); - }; - - auto fetchCID = [] (const PDFLexicalAnalyzer::Token& currentToken) -> CID - { - if (currentToken.type == PDFLexicalAnalyzer::TokenType::Integer) - { - return currentToken.data.value(); - } - - throw PDFException(PDFTranslationContext::tr("Can't fetch CID from CMap definition.")); - return 0; - }; - - auto fetchUnicode = [](const PDFLexicalAnalyzer::Token& currentToken) -> CID - { - if (currentToken.type == PDFLexicalAnalyzer::TokenType::String) - { - QByteArray byteArray = currentToken.data.toByteArray(); - - if (byteArray.size() == 2) - { - CID unicodeValue = 0; - for (int i = 0; i < byteArray.size(); ++i) - { - unicodeValue = (unicodeValue << 8) + static_cast(byteArray[i]); - } - return unicodeValue; - } - } - - return 0; - }; - - if (token.type == PDFLexicalAnalyzer::TokenType::Command) - { - QByteArray command = token.data.toByteArray(); - if (command == "usecmap") - { - if (previousToken.type == PDFLexicalAnalyzer::TokenType::Name) - { - additionalMappings.emplace_back(createFromName(previousToken.data.toByteArray())); - } - else - { - throw PDFException(PDFTranslationContext::tr("Can't use cmap inside cmap file.")); - } - } - else if (command == "beginbfrange") - { - PDFLexicalAnalyzer::Token token1 = parser.fetch(); - - if (token1.type == PDFLexicalAnalyzer::TokenType::Command && - token1.data.toByteArray() == "endbfrange") - { - break; - } - - PDFLexicalAnalyzer::Token token2 = parser.fetch(); - PDFLexicalAnalyzer::Token token3 = parser.fetch(); - - std::pair from = fetchCode(token1); - std::pair to = fetchCode(token2); - CID cid = fetchUnicode(token3); - - entries.emplace_back(from.first, to.first, qMax(from.second, to.second), cid); - } - else if (command == "begincidrange") - { - while (true) - { - PDFLexicalAnalyzer::Token token1 = parser.fetch(); - - if (token1.type == PDFLexicalAnalyzer::TokenType::Command && - token1.data.toByteArray() == "endcidrange") - { - break; - } - - PDFLexicalAnalyzer::Token token2 = parser.fetch(); - PDFLexicalAnalyzer::Token token3 = parser.fetch(); - - std::pair from = fetchCode(token1); - std::pair to = fetchCode(token2); - CID cid = fetchCID(token3); - - entries.emplace_back(from.first, to.first, qMax(from.second, to.second), cid); - } - } - else if (command == "begincidchar") - { - while (true) - { - PDFLexicalAnalyzer::Token token1 = parser.fetch(); - - if (token1.type == PDFLexicalAnalyzer::TokenType::Command && - token1.data.toByteArray() == "endcidchar") - { - break; - } - - PDFLexicalAnalyzer::Token token2 = parser.fetch(); - - std::pair code = fetchCode(token1); - CID cid = fetchCID(token2); - - entries.emplace_back(code.first, code.first, code.second, cid); - } - } - else if (command == "beginbfchar") - { - while (true) - { - PDFLexicalAnalyzer::Token token1 = parser.fetch(); - - if (token1.type == PDFLexicalAnalyzer::TokenType::Command && - token1.data.toByteArray() == "endbfchar") - { - break; - } - - PDFLexicalAnalyzer::Token token2 = parser.fetch(); - - std::pair code = fetchCode(token1); - CID cid = fetchUnicode(token2); - - entries.emplace_back(code.first, code.first, code.second, cid); - } - } - } - - previousToken = token; - } - - std::sort(entries.begin(), entries.end()); - entries = optimize(entries); - - if (!additionalMappings.empty()) - { - for (const PDFFontCMap& map : additionalMappings) - { - entries.insert(entries.cend(), map.m_entries.cbegin(), map.m_entries.cend()); - } - } - - return PDFFontCMap(qMove(entries), vertical); -} - -QByteArray PDFFontCMap::serialize() const -{ - QByteArray result; - - { - QDataStream stream(&result, QIODevice::WriteOnly); - stream << m_maxKeyLength; - stream << m_vertical; - stream << m_entries.size(); - for (const Entry& entry : m_entries) - { - stream << entry.from; - stream << entry.to; - stream << entry.byteCount; - stream << entry.cid; - } - } - - return qCompress(result, 9); -} - -PDFFontCMap PDFFontCMap::deserialize(const QByteArray& byteArray) -{ - PDFFontCMap result; - QByteArray decompressed = qUncompress(byteArray); - QDataStream stream(&decompressed, QIODevice::ReadOnly); - stream >> result.m_maxKeyLength; - stream >> result.m_vertical; - - Entries::size_type size = 0; - stream >> size; - result.m_entries.reserve(size); - for (Entries::size_type i = 0; i < size; ++i) - { - Entry entry; - stream >> entry.from; - stream >> entry.to; - stream >> entry.byteCount; - stream >> entry.cid; - result.m_entries.push_back(entry); - } - - return result; -} - -std::vector PDFFontCMap::interpret(const QByteArray& byteArray) const -{ - std::vector result; - result.reserve(byteArray.size() / m_maxKeyLength); - - unsigned int value = 0; - int scannedBytes = 0; - - for (int i = 0, size = byteArray.size(); i < size; ++i) - { - value = (value << 8) + static_cast(byteArray[i]); - ++scannedBytes; - - // Find suitable mapping - auto it = std::find_if(m_entries.cbegin(), m_entries.cend(), [value, scannedBytes](const Entry& entry) { return entry.from <= value && entry.to >= value && entry.byteCount == scannedBytes; }); - if (it != m_entries.cend()) - { - const Entry& entry = *it; - const CID cid = value - entry.from + entry.cid; - result.push_back(cid); - - value = 0; - scannedBytes = 0; - } - else if (scannedBytes == m_maxKeyLength) - { - // This means error occured - fill empty CID - result.push_back(0); - value = 0; - scannedBytes = 0; - } - } - - return result; -} - -QChar PDFFontCMap::getToUnicode(CID cid) const -{ - if (isValid()) - { - auto it = std::find_if(m_entries.cbegin(), m_entries.cend(), [cid](const Entry& entry) { return entry.from <= cid && entry.to >= cid; }); - if (it != m_entries.cend()) - { - const Entry& entry = *it; - const CID unicodeCID = cid - entry.from + entry.cid; - return QChar(unicodeCID); - } - } - - return QChar(); -} - -PDFFontCMap::PDFFontCMap(Entries&& entries, bool vertical) : - m_entries(qMove(entries)), - m_maxKeyLength(0), - m_vertical(vertical) -{ - m_maxKeyLength = std::accumulate(m_entries.cbegin(), m_entries.cend(), 0, [](unsigned int a, const Entry& b) { return qMax(a, b.byteCount); }); -} - -PDFFontCMap::Entries PDFFontCMap::optimize(const PDFFontCMap::Entries& entries) -{ - Entries result; - result.reserve(entries.size()); - - if (!entries.empty()) - { - Entry current = entries.front(); - for (size_t i = 1, count = entries.size(); i < count; ++i) - { - Entry toMerge = entries[i]; - - if (current.canMerge(toMerge)) - { - current = current.merge(toMerge); - } - else - { - result.emplace_back(current); - current = toMerge; - } - } - result.emplace_back(current); - } - - result.shrink_to_fit(); - return result; -} - -PDFFontCMapRepository* PDFFontCMapRepository::getInstance() -{ - static PDFFontCMapRepository repository; - return &repository; -} - -void PDFFontCMapRepository::saveToFile(const QString& fileName) const -{ - QFile file(fileName); - if (file.open(QFile::WriteOnly | QFile::Truncate)) - { - size_t size = m_cmaps.size(); - - { - QDataStream stream(&file); - stream << size; - for (const auto& item : m_cmaps) - { - stream << item.first; - stream << item.second; - } - } - - file.close(); - } -} - -bool PDFFontCMapRepository::loadFromFile(const QString& fileName) -{ - QFile file(fileName); - if (file.open(QFile::ReadOnly)) - { - { - QDataStream stream(&file); - size_t size = 0; - stream >> size; - for (size_t i = 0; i < size; ++i) - { - QByteArray key; - QByteArray value; - stream >> key; - stream >> value; - m_cmaps[qMove(key)] = qMove(value); - } - } - - file.close(); - return true; - } - - return false; -} - -PDFFontCMapRepository::PDFFontCMapRepository() -{ - -} - -PDFReal PDFType0Font::getGlyphAdvance(CID cid) const -{ - auto it = m_advances.find(cid); - if (it != m_advances.cend()) - { - return it->second; - } - - return m_defaultAdvance; -} - -PDFType3Font::PDFType3Font(FontDescriptor fontDescriptor, - int firstCharacterIndex, - int lastCharacterIndex, - QMatrix fontMatrix, - std::map&& characterContentStreams, - std::vector&& widths, - const PDFObject& resources, - PDFFontCMap toUnicode) : - PDFFont(qMove(fontDescriptor)), - m_firstCharacterIndex(firstCharacterIndex), - m_lastCharacterIndex(lastCharacterIndex), - m_fontMatrix(fontMatrix), - m_characterContentStreams(qMove(characterContentStreams)), - m_widths(qMove(widths)), - m_resources(resources), - m_toUnicode(qMove(toUnicode)) -{ - -} - -FontType PDFType3Font::getFontType() const -{ - return FontType::Type3; -} - -void PDFType3Font::dumpFontToTreeItem(QTreeWidgetItem* item) const -{ - new QTreeWidgetItem(item, { PDFTranslationContext::tr("Character count"), QString::number(m_characterContentStreams.size()) }); -} - -double PDFType3Font::getWidth(int characterIndex) const -{ - if (characterIndex >= m_firstCharacterIndex && characterIndex <= m_lastCharacterIndex) - { - size_t index = characterIndex - m_firstCharacterIndex; - if (index < m_widths.size()) - { - return m_widths[index]; - } - } - - return 0.0; -} - -const QByteArray* PDFType3Font::getContentStream(int characterIndex) const -{ - auto it = m_characterContentStreams.find(characterIndex); - if (it != m_characterContentStreams.cend()) - { - return &it->second; - } - - return nullptr; -} - -void PDFRealizedType3FontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) -{ - Q_ASSERT(dynamic_cast(m_parentFont.get())); - const PDFType3Font* parentFont = static_cast(m_parentFont.get()); - - textSequence.items.reserve(byteArray.size()); - for (int i = 0, characterCount = byteArray.size(); i < characterCount; ++i) - { - int index = static_cast(byteArray[i]); - const QByteArray* contentStream = parentFont->getContentStream(index); - QChar character = parentFont->getUnicode(index); - const double width = parentFont->getWidth(index); - - if (contentStream) - { - textSequence.items.emplace_back(contentStream, character, width); - } - else - { - // Report error, and add advance, if we have it - reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Content stream for type 3 font character code '%1' not found.").arg(index)); - - if (width > 0.0) - { - textSequence.items.emplace_back(width); - } - } - } -} - -bool PDFRealizedType3FontImpl::isHorizontalWritingSystem() const -{ - return true; -} - -CharacterInfos PDFRealizedType3FontImpl::getCharacterInfos() const -{ - CharacterInfos result; - - Q_ASSERT(dynamic_cast(m_parentFont.get())); - const PDFType3Font* parentFont = static_cast(m_parentFont.get()); - - for (const auto& contentStreamItem : parentFont->getContentStreams()) - { - CharacterInfo info; - info.gid = contentStreamItem.first; - info.character = parentFont->getUnicode(contentStreamItem.first); - result.emplace_back(qMove(info)); - } - - return result; -} - -} // namespace pdf +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdffont.h" +#include "pdfdocument.h" +#include "pdfparser.h" +#include "pdfnametounicode.h" +#include "pdfexception.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include "Windows.h" + +#pragma comment(lib, "Gdi32") +#pragma comment(lib, "User32") +#endif + +namespace pdf +{ + +/// Storage class for system fonts +class PDFSystemFontInfoStorage +{ +public: + + /// Returns instance of storage + static const PDFSystemFontInfoStorage* getInstance(); + + /// Loads font from descriptor + /// \param descriptor Descriptor describing the font + QByteArray loadFont(const FontDescriptor* descriptor, StandardFontType standardFontType, PDFRenderErrorReporter* reporter) const; + +private: + explicit PDFSystemFontInfoStorage(); + +#ifdef Q_OS_WIN + /// Callback for enumerating fonts + static int CALLBACK enumerateFontProc(const LOGFONT* font, const TEXTMETRIC* textMetrics, DWORD fontType, LPARAM lParam); + + /// Retrieves font data for desired font + static QByteArray getFontData(const LOGFONT* font, HDC hdc); + + /// Create a postscript name for comparation purposes + static QString getFontPostscriptName(QString fontName); + + struct FontInfo + { + QString faceName; + QString faceNameAdjusted; + LOGFONT logFont; + TEXTMETRIC textMetric; + }; + + struct CallbackInfo + { + PDFSystemFontInfoStorage* storage = nullptr; + HDC hdc = nullptr; + }; + + std::vector m_fontInfos; +#endif +}; + +const PDFSystemFontInfoStorage* PDFSystemFontInfoStorage::getInstance() +{ + static PDFSystemFontInfoStorage instance; + return &instance; +} + +QByteArray PDFSystemFontInfoStorage::loadFont(const FontDescriptor* descriptor, StandardFontType standardFontType, PDFRenderErrorReporter* reporter) const +{ + QByteArray result; + +#ifdef Q_OS_WIN + HDC hdc = GetDC(NULL); + + const BYTE lfItalic = (descriptor->italicAngle != 0.0 ? TRUE : FALSE); + + // Exact match font face name + QString fontName; + switch (standardFontType) + { + case StandardFontType::TimesRoman: + case StandardFontType::TimesRomanBold: + case StandardFontType::TimesRomanItalics: + case StandardFontType::TimesRomanBoldItalics: + { + fontName = "TimesNewRoman"; + break; + } + + case StandardFontType::Helvetica: + case StandardFontType::HelveticaBold: + case StandardFontType::HelveticaOblique: + case StandardFontType::HelveticaBoldOblique: + { + fontName = "Arial"; + break; + } + + case StandardFontType::Courier: + case StandardFontType::CourierBold: + case StandardFontType::CourierOblique: + case StandardFontType::CourierBoldOblique: + { + fontName = "CourierNew"; + break; + } + + case StandardFontType::Symbol: + case StandardFontType::ZapfDingbats: + { + fontName = "Symbol"; + break; + } + + default: + { + fontName = getFontPostscriptName(descriptor->fontName); + break; + } + } + + if (!fontName.isEmpty()) + { + for (const FontInfo& fontInfo : m_fontInfos) + { + if (fontInfo.faceNameAdjusted == fontName && + fontInfo.logFont.lfWeight == descriptor->fontWeight && + fontInfo.logFont.lfItalic == lfItalic) + { + result = getFontData(&fontInfo.logFont, hdc); + + if (!result.isEmpty()) + { + break; + } + } + } + + // Match for font family + if (result.isEmpty()) + { + for (const FontInfo& fontInfo : m_fontInfos) + { + if (fontInfo.faceNameAdjusted == fontName) + { + LOGFONT logFont = fontInfo.logFont; + logFont.lfWeight = descriptor->fontWeight; + logFont.lfItalic = lfItalic; + result = getFontData(&logFont, hdc); + + if (!result.isEmpty()) + { + break; + } + } + } + } + } + + // Exact match for font, if font can't be exact matched, then match font family + // and try to set weight + QString fontFamily = QString::fromLatin1(descriptor->fontFamily); + + if (!fontFamily.isEmpty() && result.isEmpty()) + { + for (const FontInfo& fontInfo : m_fontInfos) + { + if (fontInfo.faceName.contains(fontFamily) && + fontInfo.logFont.lfWeight == descriptor->fontWeight && + fontInfo.logFont.lfItalic == lfItalic) + { + result = getFontData(&fontInfo.logFont, hdc); + + if (!result.isEmpty()) + { + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by %2 using font family %3.").arg(fontName, fontInfo.faceNameAdjusted, fontFamily)); + break; + } + } + } + + // Match for font family + if (result.isEmpty()) + { + for (const FontInfo& fontInfo : m_fontInfos) + { + if (fontInfo.faceName.contains(fontFamily)) + { + LOGFONT logFont = fontInfo.logFont; + logFont.lfWeight = descriptor->fontWeight; + logFont.lfItalic = lfItalic; + result = getFontData(&logFont, hdc); + + if (!result.isEmpty()) + { + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by %2 using font family %3.").arg(fontName, fontInfo.faceNameAdjusted, fontFamily)); + break; + } + } + } + } + } + + // Try to inexact match for font name - find similar font + if (!fontName.isEmpty() && result.isEmpty()) + { + for (const FontInfo& fontInfo : m_fontInfos) + { + if (fontInfo.faceNameAdjusted.contains(fontName)) + { + LOGFONT logFont = fontInfo.logFont; + logFont.lfWeight = descriptor->fontWeight; + logFont.lfItalic = lfItalic; + result = getFontData(&logFont, hdc); + + if (!result.isEmpty()) + { + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by %2.").arg(fontName, fontInfo.faceNameAdjusted)); + break; + } + } + } + } + + ReleaseDC(NULL, hdc); +#endif + + if (result.isEmpty() && standardFontType == StandardFontType::Invalid) + { + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Inexact font substitution: font %1 replaced by standard font Times New Roman.").arg(fontName)); + result = loadFont(descriptor, StandardFontType::TimesRoman, reporter); + } + + return result; +} + +PDFSystemFontInfoStorage::PDFSystemFontInfoStorage() +{ +#ifdef Q_OS_WIN + LOGFONT logfont; + std::memset(&logfont, 0, sizeof(logfont)); + logfont.lfCharSet = DEFAULT_CHARSET; + logfont.lfFaceName[0] = 0; + logfont.lfPitchAndFamily = 0; + + HDC hdc = GetDC(NULL); + + CallbackInfo callbackInfo{ this, hdc}; + EnumFontFamiliesEx(hdc, &logfont, &PDFSystemFontInfoStorage::enumerateFontProc, reinterpret_cast(&callbackInfo), 0); + + ReleaseDC(NULL, hdc); +#endif +} + +#ifdef Q_OS_WIN +int PDFSystemFontInfoStorage::enumerateFontProc(const LOGFONT* font, const TEXTMETRIC* textMetrics, DWORD fontType, LPARAM lParam) +{ + if ((fontType & TRUETYPE_FONTTYPE) && (font->lfCharSet == ANSI_CHARSET)) + { + CallbackInfo* callbackInfo = reinterpret_cast(lParam); + + FontInfo fontInfo; + fontInfo.logFont = *font; + fontInfo.textMetric = *textMetrics; + fontInfo.faceName = QString::fromWCharArray(font->lfFaceName); + fontInfo.faceNameAdjusted = getFontPostscriptName(fontInfo.faceName); + callbackInfo->storage->m_fontInfos.push_back(qMove(fontInfo)); + + // For debug purposes only! +#if 0 + QByteArray byteArray = getFontData(font, callbackInfo->hdc); + qDebug() << "Font: " << QString::fromWCharArray(font->lfFaceName) << ", italic = " << font->lfItalic << ", weight = " << font->lfWeight << ", data size = " << byteArray.size(); +#endif + } + + return TRUE; +} + +QByteArray PDFSystemFontInfoStorage::getFontData(const LOGFONT* font, HDC hdc) +{ + QByteArray byteArray; + + if (HFONT fontHandle = ::CreateFontIndirect(font)) + { + HGDIOBJ oldFont = ::SelectObject(hdc, fontHandle); + + DWORD size = ::GetFontData(hdc, 0, 0, nullptr, 0); + if (size != GDI_ERROR) + { + byteArray.resize(static_cast(size)); + ::GetFontData(hdc, 0, 0, byteArray.data(), byteArray.size()); + } + + ::SelectObject(hdc, oldFont); + ::DeleteObject(fontHandle); + } + + return byteArray; +} + +QString PDFSystemFontInfoStorage::getFontPostscriptName(QString fontName) +{ + for (const char* string : { "PS", "MT", "Regular", "Bold", "Italic", "Oblique" }) + { + fontName.remove(QLatin1String(string), Qt::CaseInsensitive); + } + + return fontName.remove(QChar(' ')).remove(QChar('-')).remove(QChar(',')).trimmed(); +} + +#endif + +PDFFont::PDFFont(FontDescriptor fontDescriptor) : + m_fontDescriptor(qMove(fontDescriptor)) +{ + +} + +class IRealizedFontImpl +{ +public: + explicit IRealizedFontImpl() = default; + virtual ~IRealizedFontImpl() = default; + + /// Fills the text sequence by interpreting byte array according font data and + /// produces glyphs for the font. + /// \param byteArray Array of bytes to be interpreted + /// \param textSequence Text sequence to be filled + virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) = 0; + + /// Returns true, if font has horizontal writing system + virtual bool isHorizontalWritingSystem() const = 0; + + /// Dumps information about the font + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const { Q_UNUSED(item); } + + /// Returns postscript name of the font + virtual QString getPostScriptName() const { return QString(); } + + /// Returns character info + virtual CharacterInfos getCharacterInfos() const = 0; +}; + +/// Implementation of the PDFRealizedFont class using PIMPL pattern for Type 3 fonts +class PDFRealizedType3FontImpl : public IRealizedFontImpl +{ +public: + explicit PDFRealizedType3FontImpl(PDFFontPointer parentFont, PDFReal pixelSize) : m_pixelSize(pixelSize), m_parentFont(parentFont) { } + virtual ~PDFRealizedType3FontImpl() override = default; + + PDFReal getPixelSize() const { return m_pixelSize; } + + virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) override; + virtual bool isHorizontalWritingSystem() const override; + virtual CharacterInfos getCharacterInfos() const override; + +private: + /// Pixel size of the font + PDFReal m_pixelSize = 0.0; + + /// Parent font + PDFFontPointer m_parentFont; +}; + +/// Implementation of the PDFRealizedFont class using PIMPL pattern +class PDFRealizedFontImpl : public IRealizedFontImpl +{ +public: + explicit PDFRealizedFontImpl(); + virtual ~PDFRealizedFontImpl(); + + virtual void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) override; + virtual bool isHorizontalWritingSystem() const override { return !m_isVertical; } + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const override; + virtual QString getPostScriptName() const override { return m_postScriptName; } + virtual CharacterInfos getCharacterInfos() const override; + + static constexpr const PDFReal PIXEL_SIZE_MULTIPLIER = 100.0; + +private: + friend class PDFRealizedFont; + + static constexpr const PDFReal FONT_WIDTH_MULTIPLIER = 1.0 / 1000.0; + static constexpr const PDFReal FORMAT_26_6_MULTIPLIER = 1 / 64.0; + static constexpr const PDFReal FONT_MULTIPLIER = FORMAT_26_6_MULTIPLIER / PIXEL_SIZE_MULTIPLIER; + + struct Glyph + { + QPainterPath glyph; + PDFReal advance = 0.0; + }; + + static int outlineMoveTo(const FT_Vector* to, void* user); + static int outlineLineTo(const FT_Vector* to, void* user); + static int outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user); + static int outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user); + + /// Get glyph for glyph index + const Glyph& getGlyph(unsigned int glyphIndex); + + /// Function checks, if error occured, and if yes, then exception is thrown + static void checkFreeTypeError(FT_Error error); + + /// Read/write lock for accessing the glyph data + QReadWriteLock m_readWriteLock; + + /// Glyph cache, must be protected by the mutex above + std::unordered_map m_glyphCache; + + /// For embedded fonts, this byte array contains embedded font data + QByteArray m_embeddedFontData; + + /// For system fonts, this byte array contains system font data + QByteArray m_systemFontData; + + /// Instance of FreeType library assigned to this font + FT_Library m_library; + + /// Face of the font + FT_Face m_face; + + /// Pixel size of the font + PDFReal m_pixelSize; + + /// Parent font + PDFFontPointer m_parentFont; + + /// True, if font is embedded + bool m_isEmbedded; + + /// True, if font has vertical writing system + bool m_isVertical; + + /// Postscript name of the font + QString m_postScriptName; +}; + +PDFRealizedFontImpl::PDFRealizedFontImpl() : + m_library(nullptr), + m_face(nullptr), + m_pixelSize(0.0), + m_parentFont(nullptr), + m_isEmbedded(false), + m_isVertical(false) +{ + +} + +PDFRealizedFontImpl::~PDFRealizedFontImpl() +{ + if (m_face) + { + FT_Done_Face(m_face); + m_face = nullptr; + } + + if (m_library) + { + FT_Done_FreeType(m_library); + m_library = nullptr; + } +} + +void PDFRealizedFontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) +{ + switch (m_parentFont->getFontType()) + { + case FontType::Type1: + case FontType::TrueType: + case FontType::MMType1: + { + // We can use encoding + Q_ASSERT(dynamic_cast(m_parentFont.get())); + const PDFSimpleFont* font = static_cast(m_parentFont.get()); + const encoding::EncodingTable* encoding = font->getEncoding(); + const GlyphIndices* glyphIndices = font->getGlyphIndices(); + + textSequence.items.reserve(textSequence.items.size() + byteArray.size()); + for (int i = 0, count = byteArray.size(); i < count; ++i) + { + GID glyphIndex = (*glyphIndices)[static_cast(byteArray[i])]; + + if (!glyphIndex) + { + // Try to obtain glyph index from unicode + if (m_face->charmap && m_face->charmap->encoding == FT_ENCODING_UNICODE) + { + glyphIndex = FT_Get_Char_Index(m_face, (*encoding)[static_cast(byteArray[i])].unicode()); + } + } + + const PDFReal glyphWidth = font->getGlyphAdvance(static_cast(byteArray[i])); + + if (glyphIndex) + { + const Glyph& glyph = getGlyph(glyphIndex); + textSequence.items.emplace_back(&glyph.glyph, (*encoding)[static_cast(byteArray[i])], glyph.advance); + } + else + { + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Glyph for simple font character code '%1' not found.").arg(static_cast(byteArray[i]))); + if (glyphWidth > 0) + { + const QPainterPath* nullpath = nullptr; + textSequence.items.emplace_back(nullpath, QChar(), glyphWidth * m_pixelSize * FONT_WIDTH_MULTIPLIER); + } + } + } + break; + } + + case FontType::Type0: + { + Q_ASSERT(dynamic_cast(m_parentFont.get())); + const PDFType0Font* font = static_cast(m_parentFont.get()); + + const PDFFontCMap* cmap = font->getCMap(); + const PDFFontCMap* toUnicode = font->getToUnicode(); + const PDFCIDtoGIDMapper* CIDtoGIDmapper = font->getCIDtoGIDMapper(); + + std::vector cids = cmap->interpret(byteArray); + textSequence.items.reserve(textSequence.items.size() + cids.size()); + for (CID cid : cids) + { + const GID glyphIndex = CIDtoGIDmapper->map(cid); + const PDFReal glyphWidth = font->getGlyphAdvance(cid); + + if (glyphIndex) + { + QChar character = toUnicode->getToUnicode(cid); + const Glyph& glyph = getGlyph(glyphIndex); + textSequence.items.emplace_back(&glyph.glyph, character, glyph.advance); + } + else + { + if (cid > 0) + { + // Character with CID == 0 is treated as default whitespace, it hasn't glyph + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Glyph for composite font character with cid '%1' not found.").arg(cid)); + } + + if (glyphWidth > 0) + { + // We do not multiply advance with font size and FONT_WIDTH_MULTIPLIER, because in the code, + // "advance" is treated as in font space. + const QPainterPath* nullpath = nullptr; + textSequence.items.emplace_back(nullpath, QChar(), -glyphWidth); + } + } + } + + break; + } + + default: + { + // Unhandled font type + Q_ASSERT(false); + break; + } + } +} + +CharacterInfos PDFRealizedFontImpl::getCharacterInfos() const +{ + CharacterInfos result; + + switch (m_parentFont->getFontType()) + { + case FontType::Type1: + case FontType::TrueType: + case FontType::MMType1: + { + // We can use encoding + Q_ASSERT(dynamic_cast(m_parentFont.get())); + const PDFSimpleFont* font = static_cast(m_parentFont.get()); + const encoding::EncodingTable* encoding = font->getEncoding(); + const GlyphIndices* glyphIndices = font->getGlyphIndices(); + + for (size_t i = 0; i < encoding->size(); ++i) + { + QChar character = (*encoding)[i]; + GID glyphIndex = (*glyphIndices)[static_cast(i)]; + + if (!glyphIndex) + { + // Try to obtain glyph index from unicode + if (m_face->charmap && m_face->charmap->encoding == FT_ENCODING_UNICODE) + { + glyphIndex = FT_Get_Char_Index(m_face, character.unicode()); + } + } + + if (glyphIndex) + { + CharacterInfo info; + info.gid = glyphIndex; + info.character = character; + result.emplace_back(qMove(info)); + } + } + + break; + } + + case FontType::Type0: + { + Q_ASSERT(dynamic_cast(m_parentFont.get())); + const PDFType0Font* font = static_cast(m_parentFont.get()); + + const PDFFontCMap* toUnicode = font->getToUnicode(); + const PDFCIDtoGIDMapper* CIDtoGIDmapper = font->getCIDtoGIDMapper(); + + FT_UInt index = 0; + FT_ULong character = FT_Get_First_Char(m_face, &index); + while (index != 0) + { + const GID gid = index; + const CID cid = CIDtoGIDmapper->unmap(gid); + + CharacterInfo info; + info.gid = gid; + info.character = toUnicode->getToUnicode(cid); + result.emplace_back(qMove(info)); + + character = FT_Get_Next_Char(m_face, character, &index); + } + + if (result.empty()) + { + // We will try all reasonable high CIDs + for (CID cid = 0; cid < QChar::LastValidCodePoint; ++cid) + { + const GID gid = CIDtoGIDmapper->map(cid); + + if (!gid) + { + continue; + } + + if (!FT_Load_Glyph(m_face, gid, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)) + { + CharacterInfo info; + info.gid = gid; + info.character = toUnicode->getToUnicode(cid); + result.emplace_back(qMove(info)); + } + } + } + + break; + } + + default: + { + // Unhandled font type + Q_ASSERT(false); + break; + } + } + + return result; +} + +void PDFRealizedFontImpl::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + QTreeWidgetItem* root = new QTreeWidgetItem(item, { PDFTranslationContext::tr("Details") }); + + if (m_face->family_name) + { + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Font"), QString::fromLatin1(m_face->family_name) }); + } + if (m_face->style_name) + { + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Style"), QString::fromLatin1(m_face->style_name) }); + } + + QString yesString = PDFTranslationContext::tr("Yes"); + QString noString = PDFTranslationContext::tr("No"); + + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Glyph count"), QString::number(m_face->num_glyphs) }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is CID keyed"), (m_face->face_flags & FT_FACE_FLAG_CID_KEYED) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is bold"), (m_face->style_flags & FT_STYLE_FLAG_BOLD) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Is italics"), (m_face->style_flags & FT_STYLE_FLAG_ITALIC) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has vertical writing system"), (m_face->face_flags & FT_FACE_FLAG_VERTICAL) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has SFNT storage scheme"), (m_face->face_flags & FT_FACE_FLAG_SFNT) ? yesString : noString }); + new QTreeWidgetItem(root, { PDFTranslationContext::tr("Has glyph names"), (m_face->face_flags & FT_FACE_FLAG_GLYPH_NAMES) ? yesString : noString }); + + if (m_face->num_charmaps > 0) + { + QTreeWidgetItem* encodingRoot = new QTreeWidgetItem(item, { PDFTranslationContext::tr("Encoding") }); + for (FT_Int i = 0; i < m_face->num_charmaps; ++i) + { + FT_CharMap charMap = m_face->charmaps[i]; + + const FT_Encoding encoding = charMap->encoding; + QString encodingName; + switch (encoding) + { + case FT_ENCODING_NONE: + encodingName = PDFTranslationContext::tr("None"); + break; + + case FT_ENCODING_UNICODE: + encodingName = PDFTranslationContext::tr("Unicode"); + break; + + case FT_ENCODING_MS_SYMBOL: + encodingName = PDFTranslationContext::tr("MS Symbol"); + break; + + case FT_ENCODING_SJIS: + encodingName = PDFTranslationContext::tr("Japanese Shift JIS"); + break; + + case FT_ENCODING_PRC: + encodingName = PDFTranslationContext::tr("PRC - Simplified Chinese"); + break; + + case FT_ENCODING_BIG5: + encodingName = PDFTranslationContext::tr("Traditional Chinese"); + break; + + case FT_ENCODING_WANSUNG: + encodingName = PDFTranslationContext::tr("Korean Extended Wansung"); + break; + + case FT_ENCODING_JOHAB: + encodingName = PDFTranslationContext::tr("Korean Standard"); + break; + + case FT_ENCODING_ADOBE_STANDARD: + encodingName = PDFTranslationContext::tr("Adobe Standard"); + break; + + case FT_ENCODING_ADOBE_EXPERT: + encodingName = PDFTranslationContext::tr("Adobe Expert"); + break; + case FT_ENCODING_ADOBE_CUSTOM: + encodingName = PDFTranslationContext::tr("Adobe Custom"); + break; + + case FT_ENCODING_ADOBE_LATIN_1: + encodingName = PDFTranslationContext::tr("Adobe Latin 1"); + break; + + case FT_ENCODING_OLD_LATIN_2: + encodingName = PDFTranslationContext::tr("Old Latin 1"); + break; + + case FT_ENCODING_APPLE_ROMAN: + encodingName = PDFTranslationContext::tr("Apple Roman"); + break; + + default: + encodingName = PDFTranslationContext::tr("Unknown"); + break; + } + + QString encodingString = PDFTranslationContext::tr("Platform/Encoding = %1 %2").arg(charMap->platform_id).arg(charMap->encoding_id); + new QTreeWidgetItem(encodingRoot, { encodingName, encodingString }); + } + } +} + +int PDFRealizedFontImpl::outlineMoveTo(const FT_Vector* to, void* user) +{ + Glyph* glyph = reinterpret_cast(user); + glyph->glyph.moveTo(to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); + return 0; +} + +int PDFRealizedFontImpl::outlineLineTo(const FT_Vector* to, void* user) +{ + Glyph* glyph = reinterpret_cast(user); + glyph->glyph.lineTo(to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); + return 0; +} + +int PDFRealizedFontImpl::outlineConicTo(const FT_Vector* control, const FT_Vector* to, void* user) +{ + Glyph* glyph = reinterpret_cast(user); + glyph->glyph.quadTo(control->x * FONT_MULTIPLIER, control->y * FONT_MULTIPLIER, to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); + return 0; +} + +int PDFRealizedFontImpl::outlineCubicTo(const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user) +{ + Glyph* glyph = reinterpret_cast(user); + glyph->glyph.cubicTo(control1->x * FONT_MULTIPLIER, control1->y * FONT_MULTIPLIER, control2->x * FONT_MULTIPLIER, control2->y * FONT_MULTIPLIER, to->x * FONT_MULTIPLIER, to->y * FONT_MULTIPLIER); + return 0; +} + +const PDFRealizedFontImpl::Glyph& PDFRealizedFontImpl::getGlyph(unsigned int glyphIndex) +{ + if (glyphIndex) + { + { + QReadLocker readLock(&m_readWriteLock); + + // First look into cache + auto it = m_glyphCache.find(glyphIndex); + if (it != m_glyphCache.cend()) + { + return it->second; + } + } + + QWriteLocker writeLock(&m_readWriteLock); + Glyph glyph; + + FT_Outline_Funcs glyphOutlineInterface; + glyphOutlineInterface.delta = 0; + glyphOutlineInterface.shift = 0; + glyphOutlineInterface.move_to = PDFRealizedFontImpl::outlineMoveTo; + glyphOutlineInterface.line_to = PDFRealizedFontImpl::outlineLineTo; + glyphOutlineInterface.conic_to = PDFRealizedFontImpl::outlineConicTo; + glyphOutlineInterface.cubic_to = PDFRealizedFontImpl::outlineCubicTo; + + checkFreeTypeError(FT_Load_Glyph(m_face, glyphIndex, FT_LOAD_NO_BITMAP | FT_LOAD_NO_HINTING)); + checkFreeTypeError(FT_Outline_Decompose(&m_face->glyph->outline, &glyphOutlineInterface, &glyph)); + glyph.glyph.closeSubpath(); + glyph.advance = !m_isVertical ? m_face->glyph->advance.x : m_face->glyph->advance.y; + glyph.advance *= FONT_MULTIPLIER; + + auto it = m_glyphCache.find(glyphIndex); + if (it == m_glyphCache.cend()) + { + it = m_glyphCache.insert(std::make_pair(glyphIndex, qMove(glyph))).first; + } + return it->second; + } + + static Glyph dummy; + return dummy; +} + +void PDFRealizedFontImpl::checkFreeTypeError(FT_Error error) +{ + if (error) + { + QString message; + if (const char* errorString = FT_Error_String(error)) + { + message = QString::fromLatin1(errorString); + } + + throw PDFException(PDFTranslationContext::tr("FreeType error code %1: %2").arg(error).arg(message)); + } +} + +PDFRealizedFont::~PDFRealizedFont() +{ + delete m_impl; +} + +void PDFRealizedFont::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) +{ + m_impl->fillTextSequence(byteArray, textSequence, reporter); +} + +bool PDFRealizedFont::isHorizontalWritingSystem() const +{ + return m_impl->isHorizontalWritingSystem(); +} + +void PDFRealizedFont::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + m_impl->dumpFontToTreeItem(item); +} + +QString PDFRealizedFont::getPostScriptName() const +{ + return m_impl->getPostScriptName(); +} + +CharacterInfos PDFRealizedFont::getCharacterInfos() const +{ + return m_impl->getCharacterInfos(); +} + +PDFRealizedFontPointer PDFRealizedFont::createRealizedFont(PDFFontPointer font, PDFReal pixelSize, PDFRenderErrorReporter* reporter) +{ + PDFRealizedFontPointer result; + + if (font->getFontType() == FontType::Type3) + { + result.reset(new PDFRealizedFont(new PDFRealizedType3FontImpl(font, pixelSize))); + } + else + { + std::unique_ptr implPtr(new PDFRealizedFontImpl()); + + PDFRealizedFontImpl* impl = implPtr.get(); + impl->m_parentFont = font; + impl->m_pixelSize = pixelSize; + + const FontDescriptor* descriptor = font->getFontDescriptor(); + if (descriptor->isEmbedded()) + { + PDFRealizedFontImpl::checkFreeTypeError(FT_Init_FreeType(&impl->m_library)); + const QByteArray* embeddedFontData = descriptor->getEmbeddedFontData(); + Q_ASSERT(embeddedFontData); + impl->m_embeddedFontData = *embeddedFontData; + + // At this time, embedded font data should not be empty! + Q_ASSERT(!impl->m_embeddedFontData.isEmpty()); + + PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast(impl->m_embeddedFontData.constData()), impl->m_embeddedFontData.size(), 0, &impl->m_face)); + FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE); // We try to select unicode encoding, but if it fails, we don't do anything (use glyph indices instead) + PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize * PDFRealizedFontImpl::PIXEL_SIZE_MULTIPLIER))); + impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL; + impl->m_isEmbedded = true; + result.reset(new PDFRealizedFont(implPtr.release())); + } + else + { + StandardFontType standardFontType = StandardFontType::Invalid; + if (font->getFontType() == FontType::Type1 || font->getFontType() == FontType::MMType1) + { + Q_ASSERT(dynamic_cast(font.get())); + const PDFType1Font* type1Font = static_cast(font.get()); + standardFontType = type1Font->getStandardFontType(); + } + + const PDFSystemFontInfoStorage* fontStorage = PDFSystemFontInfoStorage::getInstance(); + impl->m_systemFontData = fontStorage->loadFont(descriptor, standardFontType, reporter); + + if (impl->m_systemFontData.isEmpty()) + { + throw PDFException(PDFTranslationContext::tr("Can't load system font '%1'.").arg(QString::fromLatin1(descriptor->fontName))); + } + + PDFRealizedFontImpl::checkFreeTypeError(FT_Init_FreeType(&impl->m_library)); + PDFRealizedFontImpl::checkFreeTypeError(FT_New_Memory_Face(impl->m_library, reinterpret_cast(impl->m_systemFontData.constData()), impl->m_systemFontData.size(), 0, &impl->m_face)); + FT_Select_Charmap(impl->m_face, FT_ENCODING_UNICODE); // We try to select unicode encoding, but if it fails, we don't do anything (use glyph indices instead) + PDFRealizedFontImpl::checkFreeTypeError(FT_Set_Pixel_Sizes(impl->m_face, 0, qRound(pixelSize * PDFRealizedFontImpl::PIXEL_SIZE_MULTIPLIER))); + impl->m_isVertical = impl->m_face->face_flags & FT_FACE_FLAG_VERTICAL; + impl->m_isEmbedded = false; + if (const char* postScriptName = FT_Get_Postscript_Name(impl->m_face)) + { + impl->m_postScriptName = QString::fromLatin1(postScriptName); + } + result.reset(new PDFRealizedFont(implPtr.release())); + } + } + + return result; +} + +FontDescriptor PDFFont::readFontDescriptor(const PDFObject& fontDescriptorObject, const PDFDocument* document) +{ + FontDescriptor fontDescriptor; + PDFDocumentDataLoaderDecorator fontLoader(document); + if (fontDescriptorObject.isDictionary()) + { + const PDFDictionary* fontDescriptorDictionary = fontDescriptorObject.getDictionary(); + fontDescriptor.fontName = fontLoader.readNameFromDictionary(fontDescriptorDictionary, "FontName"); + fontDescriptor.fontFamily = fontLoader.readStringFromDictionary(fontDescriptorDictionary, "FontFamily"); + + constexpr const std::array, 9> stretches = { + std::pair{ "UltraCondensed", QFont::UltraCondensed }, + std::pair{ "ExtraCondensed", QFont::ExtraCondensed }, + std::pair{ "Condensed", QFont::Condensed }, + std::pair{ "SemiCondensed", QFont::SemiCondensed }, + std::pair{ "Normal", QFont::Unstretched }, + std::pair{ "SemiExpanded", QFont::SemiExpanded }, + std::pair{ "Expanded", QFont::Expanded }, + std::pair{ "ExtraExpanded", QFont::ExtraExpanded }, + std::pair{ "UltraExpanded", QFont::UltraExpanded } + }; + fontDescriptor.fontStretch = fontLoader.readEnumByName(fontDescriptorDictionary->get("FontStretch"), stretches.cbegin(), stretches.cend(), QFont::Unstretched); + fontDescriptor.fontWeight = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "FontWeight", 500); + fontDescriptor.italicAngle = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "ItalicAngle", 0.0); + fontDescriptor.ascent = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "Ascent", 0.0); + fontDescriptor.descent = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "Descent", 0.0); + fontDescriptor.leading = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "Leading", 0.0); + fontDescriptor.capHeight = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "CapHeight", 0.0); + fontDescriptor.xHeight = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "XHeight", 0.0); + fontDescriptor.stemV = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "StemV", 0.0); + fontDescriptor.stemH = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "StemH", 0.0); + fontDescriptor.avgWidth = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "AvgWidth", 0.0); + fontDescriptor.maxWidth = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "MaxWidth", 0.0); + fontDescriptor.missingWidth = fontLoader.readNumberFromDictionary(fontDescriptorDictionary, "MissingWidth", 0.0); + fontDescriptor.flags = fontLoader.readIntegerFromDictionary(fontDescriptorDictionary, "Flags", 0); + fontDescriptor.boundingBox = fontLoader.readRectangle(fontDescriptorDictionary->get("FontBBox"), QRectF()); + fontDescriptor.charset = fontLoader.readStringFromDictionary(fontDescriptorDictionary, "Charset"); + + auto loadStream = [fontDescriptorDictionary, document](QByteArray& byteArray, const char* name) + { + if (fontDescriptorDictionary->hasKey(name)) + { + const PDFObject& streamObject = document->getObject(fontDescriptorDictionary->get(name)); + if (streamObject.isStream()) + { + byteArray = document->getDecodedStream(streamObject.getStream()); + } + } + }; + loadStream(fontDescriptor.fontFile, "FontFile"); + loadStream(fontDescriptor.fontFile2, "FontFile2"); + loadStream(fontDescriptor.fontFile3, "FontFile3"); + } + + return fontDescriptor; +} + +PDFFontPointer PDFFont::createFont(const PDFObject& object, const PDFDocument* document) +{ + const PDFObject& dereferencedFontDictionary = document->getObject(object); + if (!dereferencedFontDictionary.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Font object must be a dictionary.")); + } + + const PDFDictionary* fontDictionary = dereferencedFontDictionary.getDictionary(); + PDFDocumentDataLoaderDecorator fontLoader(document); + + // First, determine the font subtype + constexpr const std::array fontTypes = { + std::pair{ "Type0", FontType::Type0 }, + std::pair{ "Type1", FontType::Type1 }, + std::pair{ "TrueType", FontType::TrueType }, + std::pair{ "Type3", FontType::Type3}, + std::pair{ "MMType1", FontType::MMType1 } + }; + + const FontType fontType = fontLoader.readEnumByName(fontDictionary->get("Subtype"), fontTypes.cbegin(), fontTypes.cend(), FontType::Invalid); + if (fontType == FontType::Invalid) + { + throw PDFException(PDFTranslationContext::tr("Invalid font type.")); + } + + QByteArray name = fontLoader.readNameFromDictionary(fontDictionary, "Name"); + QByteArray baseFont = fontLoader.readNameFromDictionary(fontDictionary, "BaseFont"); + const PDFInteger firstChar = fontLoader.readIntegerFromDictionary(fontDictionary, "FirstChar", 0); + const PDFInteger lastChar = fontLoader.readIntegerFromDictionary(fontDictionary, "LastChar", 255); + std::vector widths = fontLoader.readIntegerArrayFromDictionary(fontDictionary, "Widths"); + + // Read standard font + constexpr const std::array, 14> standardFonts = { + std::pair{ "Times-Roman", StandardFontType::TimesRoman }, + std::pair{ "Times-Bold", StandardFontType::TimesRomanBold }, + std::pair{ "Times-Italic", StandardFontType::TimesRomanItalics }, + std::pair{ "Times-BoldItalic", StandardFontType::TimesRomanBoldItalics }, + std::pair{ "Helvetica", StandardFontType::Helvetica }, + std::pair{ "Helvetica-Bold", StandardFontType::HelveticaBold }, + std::pair{ "Helvetica-Oblique", StandardFontType::HelveticaOblique }, + std::pair{ "Helvetica-BoldOblique", StandardFontType::HelveticaBoldOblique }, + std::pair{ "Courier", StandardFontType::Courier }, + std::pair{ "Courier-Bold", StandardFontType::CourierBold }, + std::pair{ "Courier-Oblique", StandardFontType::CourierOblique }, + std::pair{ "Courier-BoldOblique", StandardFontType::CourierBoldOblique }, + std::pair{ "Symbol", StandardFontType::Symbol }, + std::pair{ "ZapfDingbats", StandardFontType::ZapfDingbats } + }; + const StandardFontType standardFont = fontLoader.readEnumByName(fontDictionary->get("BaseFont"), standardFonts.cbegin(), standardFonts.cend(), StandardFontType::Invalid); + + // Read Font Descriptor + const PDFObject& fontDescriptorObject = document->getObject(fontDictionary->get("FontDescriptor")); + FontDescriptor fontDescriptor = readFontDescriptor(fontDescriptorObject, document); + + // Read Font Encoding + // The font encoding for the simple font is determined by this algorithm: + // 1) Try to use Encoding dictionary to determine base encoding + // (it can be MacRomanEncoding, MacExpertEncoding, WinAnsiEncoding or StandardEncoding) + // 2) If it is not present, then try to obtain built-in encoding from the font file (usually, this is not possible) + // 3) Use default encoding for the font depending on the font type + // - one of the 14 base fonts - use builtin encoding for the font type + // - TrueType - use WinAnsiEncoding + // - all others - use StandardEncoding + // 4) Merge with Differences, if present + // 5) Fill missing characters from StandardEncoding + // After the encoding is obtained, try to extract glyph indices for embedded font. + + PDFEncoding::Encoding encoding = PDFEncoding::Encoding::Invalid; + encoding::EncodingTable simpleFontEncodingTable = { }; + GlyphIndices glyphIndexArray = { }; + switch (fontType) + { + case FontType::Type1: + case FontType::MMType1: + case FontType::TrueType: + { + bool hasDifferences = false; + encoding::EncodingTable differences = { }; + + if (fontDictionary->hasKey("Encoding")) + { + constexpr const std::array, 3> encodings = { + std::pair{ "MacRomanEncoding", PDFEncoding::Encoding::MacRoman }, + std::pair{ "MacExpertEncoding", PDFEncoding::Encoding::MacExpert }, + std::pair{ "WinAnsiEncoding", PDFEncoding::Encoding::WinAnsi } + }; + + const PDFObject& encodingObject = document->getObject(fontDictionary->get("Encoding")); + if (encodingObject.isName()) + { + // Decode name of the encoding + encoding = fontLoader.readEnumByName(encodingObject, encodings.cbegin(), encodings.cend(), PDFEncoding::Encoding::Invalid); + } + else if (encodingObject.isDictionary()) + { + // Dictionary with base encoding and differences (all optional) + const PDFDictionary* encodingDictionary = encodingObject.getDictionary(); + if (encodingDictionary->hasKey("BaseEncoding")) + { + encoding = fontLoader.readEnumByName(encodingDictionary->get("BaseEncoding"), encodings.cbegin(), encodings.cend(), PDFEncoding::Encoding::Invalid); + } + else + { + // We get encoding for the standard font. If we have invalid standard font, + // then we get standard encoding. So we shouldn't test it. + encoding = getEncodingForStandardFont(standardFont); + } + + if (encodingDictionary->hasKey("Differences")) + { + const PDFObject& differencesArray = document->getObject(encodingDictionary->get("Differences")); + if (differencesArray.isArray()) + { + hasDifferences = true; + const PDFArray* array = differencesArray.getArray(); + size_t currentOffset = 0; + for (size_t i = 0, count = array->getCount(); i < count; ++i) + { + const PDFObject& item = document->getObject(array->getItem(i)); + if (item.isInt()) + { + currentOffset = static_cast(item.getInteger()); + } + else if (item.isName()) + { + if (currentOffset >= differences.size()) + { + throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); + } + + QChar character = PDFNameToUnicode::getUnicodeUsingResolvedName(item.getString()); + differences[currentOffset] = character; + + ++currentOffset; + } + else + { + throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); + } + } + } + else + { + throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of the font.")); + } + } + } + else + { + throw PDFException(PDFTranslationContext::tr("Invalid encoding entry of the font.")); + } + } + + if (encoding == PDFEncoding::Encoding::Invalid) + { + // We get encoding for the standard font. If we have invalid standard font, + // then we get standard encoding. So we shouldn't test it. + encoding = getEncodingForStandardFont(standardFont); + } + + if (encoding == PDFEncoding::Encoding::Invalid) + { + throw PDFException(PDFTranslationContext::tr("Invalid encoding entry of the font.")); + } + + simpleFontEncodingTable = *PDFEncoding::getTableForEncoding(encoding); + + auto finishFont = [&] + { + // Fill in differences + if (hasDifferences) + { + for (size_t i = 0; i < differences.size(); ++i) + { + if (!differences[i].isNull()) + { + simpleFontEncodingTable[i] = differences[i]; + } + } + + // Set the encoding to custom + encoding = PDFEncoding::Encoding::Custom; + } + + // Fill in missing characters from standard encoding + const encoding::EncodingTable& standardEncoding = *PDFEncoding::getTableForEncoding(PDFEncoding::Encoding::Standard); + for (size_t i = 0; i < standardEncoding.size(); ++i) + { + if ((simpleFontEncodingTable[i].isNull() || simpleFontEncodingTable[i] == QChar(QChar::SpecialCharacter::ReplacementCharacter)) && + (!standardEncoding[i].isNull() && standardEncoding[i] != QChar(QChar::SpecialCharacter::ReplacementCharacter))) + { + simpleFontEncodingTable[i] = standardEncoding[i]; + } + } + }; + + if (fontDescriptor.isEmbedded()) + { + // Return encoding from the embedded font + const QByteArray* embeddedFontData = fontDescriptor.getEmbeddedFontData(); + Q_ASSERT(embeddedFontData); + + FT_Library library; + if (!FT_Init_FreeType(&library)) + { + FT_Face face; + if (!FT_New_Memory_Face(library, reinterpret_cast(embeddedFontData->constData()), embeddedFontData->size(), 0, &face)) + { + if (FT_Has_PS_Glyph_Names(face)) + { + for (FT_Int i = 0; i < face->num_charmaps; ++i) + { + FT_CharMap charMap = face->charmaps[i]; + switch (charMap->encoding) + { + case FT_ENCODING_ADOBE_STANDARD: + case FT_ENCODING_ADOBE_LATIN_1: + case FT_ENCODING_ADOBE_CUSTOM: + case FT_ENCODING_ADOBE_EXPERT: + { + // Try to load data from the encoding + if (!FT_Set_Charmap(face, charMap)) + { + for (size_t i = 0; i < simpleFontEncodingTable.size(); ++i) + { + FT_UInt glyphIndex = FT_Get_Char_Index(face, static_cast(i)); + + if (glyphIndex == 0) + { + glyphIndex = FT_Get_Char_Index(face, static_cast(i + 0xF000)); + } + + if (glyphIndex == 0) + { + glyphIndex = FT_Get_Char_Index(face, static_cast(i + 0xF100)); + } + + if (glyphIndex > 0) + { + // Fill the glyph index array + glyphIndexArray[i] = glyphIndex; + + // Set mapping to unicode + char buffer[128] = { }; + if (!FT_Get_Glyph_Name(face, glyphIndex, buffer, static_cast(std::size(buffer)))) + { + QByteArray byteArrayBuffer(buffer); + QChar character = PDFNameToUnicode::getUnicodeForName(byteArrayBuffer); + if (character.isNull()) + { + character = PDFNameToUnicode::getUnicodeForNameZapfDingbats(byteArrayBuffer); + } + if (!character.isNull()) + { + encoding = PDFEncoding::Encoding::Custom; + simpleFontEncodingTable[i] = character; + } + } + } + } + } + + break; + } + + default: + break; + } + } + } + else if (!FT_Select_Charmap(face, FT_ENCODING_APPLE_ROMAN)) + { + // We have (1, 0) Mac Roman Encoding, which is slightly different, than Mac Roman Encoding defined + // in PDF (for 15 characters). + simpleFontEncodingTable = *PDFEncoding::getTableForEncoding(PDFEncoding::Encoding::MacOsRoman); + encoding = PDFEncoding::Encoding::Custom; + + for (size_t i = 0; i < simpleFontEncodingTable.size(); ++i) + { + FT_UInt glyphIndex = FT_Get_Char_Index(face, static_cast(i)); + if (glyphIndex > 0) + { + glyphIndexArray[i] = glyphIndex; + } + } + } + + finishFont(); + + // Fill the glyph index array from unicode, if we have unicode mapping + if (!FT_Select_Charmap(face, FT_ENCODING_UNICODE)) + { + for (size_t i = 0; i < simpleFontEncodingTable.size(); ++i) + { + QChar character = simpleFontEncodingTable[i]; + if (!character.isNull() && character != QChar(QChar::SpecialCharacter::ReplacementCharacter)) + { + const FT_UInt glyphIndex = FT_Get_Char_Index(face, character.unicode()); + if (glyphIndex > 0) + { + glyphIndexArray[i] = glyphIndex; + } + } + } + } + + FT_Done_Face(face); + } + + FT_Done_FreeType(library); + } + } + else + { + // Finish font - fill differences + finishFont(); + } + + break; + } + + case FontType::Type0: + { + // This is composite font (CID keyed font) + + // Load CMAP + PDFFontCMap cmap; + const PDFObject& cmapObject = document->getObject(fontDictionary->get("Encoding")); + if (cmapObject.isName()) + { + cmap = PDFFontCMap::createFromName(cmapObject.getString()); + } + else if (cmapObject.isStream()) + { + const PDFStream* stream = cmapObject.getStream(); + QByteArray decodedStream = document->getDecodedStream(stream); + cmap = PDFFontCMap::createFromData(decodedStream); + } + + if (!cmap.isValid()) + { + throw PDFException(PDFTranslationContext::tr("Invalid CMAP in CID-keyed font.")); + } + + const PDFObject& descendantFonts = document->getObject(fontDictionary->get("DescendantFonts")); + if (!descendantFonts.isArray()) + { + throw PDFException(PDFTranslationContext::tr("Invalid descendant font in CID-keyed font.")); + } + + const PDFArray* descendantFontsArray = descendantFonts.getArray(); + if (descendantFontsArray->getCount() != 1) + { + throw PDFException(PDFTranslationContext::tr("Invalid number (%1) of descendant fonts in CID-keyed font - exactly one is required.").arg(descendantFontsArray->getCount())); + } + + const PDFObject& descendantFont = document->getObject(descendantFontsArray->getItem(0)); + if (!descendantFont.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Invalid descendant font in CID-keyed font.")); + } + + const PDFDictionary* descendantFontDictionary = descendantFont.getDictionary(); + + const PDFObject& fontDescriptorObjectForCompositeFont = document->getObject(descendantFontDictionary->get("FontDescriptor")); + fontDescriptor = readFontDescriptor(fontDescriptorObjectForCompositeFont, document); + + QByteArray cidToGidMapping; + const PDFObject& cidToGidMappingObject = document->getObject(descendantFontDictionary->get("CIDtoGIDMap")); + if (cidToGidMappingObject.isStream()) + { + const PDFStream* cidToGidMappingStream = cidToGidMappingObject.getStream(); + cidToGidMapping = document->getDecodedStream(cidToGidMappingStream); + } + PDFCIDtoGIDMapper cidToGidMapper(qMove(cidToGidMapping)); + + baseFont = fontLoader.readNameFromDictionary(descendantFontDictionary, "BaseFont"); + + // Read default advance + PDFReal dw = fontLoader.readNumberFromDictionary(descendantFontDictionary, "DW", 1000.0); + std::array dw2 = { }; + fontLoader.readNumberArrayFromDictionary(descendantFontDictionary, "DW2", dw2.begin(), dw2.end()); + PDFReal defaultWidth = descendantFontDictionary->hasKey("DW") ? dw : dw2.back(); + + // Read horizontal advances + std::unordered_map advances; + if (descendantFontDictionary->hasKey("W")) + { + const PDFObject& wArrayObject = document->getObject(descendantFontDictionary->get("W")); + if (wArrayObject.isArray()) + { + const PDFArray* wArray = wArrayObject.getArray(); + const size_t size = wArray->getCount(); + + for (size_t i = 0; i < size;) + { + CID startCID = fontLoader.readInteger(wArray->getItem(i++), 0); + const PDFObject& arrayOrCID = document->getObject(wArray->getItem(i++)); + + if (arrayOrCID.isInt()) + { + CID endCID = arrayOrCID.getInteger(); + PDFReal width = fontLoader.readInteger(wArray->getItem(i++), 0); + for (CID currentCID = startCID; currentCID <= endCID; ++currentCID) + { + advances[currentCID] = width; + } + } + else if (arrayOrCID.isArray()) + { + const PDFArray* widthArray = arrayOrCID.getArray(); + const size_t widthArraySize = widthArray->getCount(); + for (size_t widthArrayIndex = 0; widthArrayIndex < widthArraySize; ++widthArrayIndex) + { + PDFReal width = fontLoader.readNumber(widthArray->getItem(widthArrayIndex), 0); + advances[startCID + static_cast(widthArrayIndex)] = width; + } + } + } + } + } + + PDFFontCMap toUnicodeCMap; + const PDFObject& toUnicode = document->getObject(fontDictionary->get("ToUnicode")); + if (toUnicode.isName()) + { + toUnicodeCMap = PDFFontCMap::createFromName(toUnicode.getString()); + } + else if (toUnicode.isStream()) + { + const PDFStream* stream = toUnicode.getStream(); + QByteArray decodedStream = document->getDecodedStream(stream); + toUnicodeCMap = PDFFontCMap::createFromData(decodedStream); + } + + return PDFFontPointer(new PDFType0Font(qMove(fontDescriptor), qMove(cmap), qMove(toUnicodeCMap), qMove(cidToGidMapper), defaultWidth, qMove(advances))); + } + + case FontType::Type3: + { + // Read the font matrix + std::vector fontMatrixValues = fontLoader.readNumberArrayFromDictionary(fontDictionary, "FontMatrix"); + + if (fontMatrixValues.size() != 6) + { + throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font matrix.")); + } + QMatrix fontMatrix(fontMatrixValues[0], fontMatrixValues[1], fontMatrixValues[2], fontMatrixValues[3], fontMatrixValues[4], fontMatrixValues[5]); + + PDFObject charProcs = document->getObject(fontDictionary->get("CharProcs")); + if (!charProcs.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font character content streams.")); + } + const PDFDictionary* charProcsDictionary = charProcs.getDictionary(); + + PDFInteger firstChar = fontLoader.readIntegerFromDictionary(fontDictionary, "FirstChar", -1); + PDFInteger lastChar = fontLoader.readIntegerFromDictionary(fontDictionary, "LastChar", -1); + + if (firstChar < 0 || lastChar > 255 || firstChar > lastChar) + { + throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font character range (from %1 to %2).").arg(firstChar).arg(lastChar)); + } + + const PDFObject& encoding = document->getObject(fontDictionary->get("Encoding")); + if (!encoding.isDictionary()) + { + throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font encoding.")); + } + + const PDFDictionary* encodingDictionary = encoding.getDictionary(); + const PDFObject& differences = document->getObject(encodingDictionary->get("Differences")); + if (!differences.isArray()) + { + throw PDFException(PDFTranslationContext::tr("Invalid Type 3 font encoding.")); + } + + std::map characterContentStreams; + + const PDFArray* differencesArray = differences.getArray(); + size_t currentOffset = 0; + for (size_t i = 0, count = differencesArray->getCount(); i < count; ++i) + { + const PDFObject& item = document->getObject(differencesArray->getItem(i)); + if (item.isInt()) + { + currentOffset = static_cast(item.getInteger()); + } + else if (item.isName()) + { + if (currentOffset > 255) + { + throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of type 3 font.")); + } + + QByteArray characterName = item.getString(); + const PDFObject& characterContentStreamObject = document->getObject(charProcsDictionary->get(characterName)); + if (characterContentStreamObject.isStream()) + { + QByteArray contentStream = document->getDecodedStream(characterContentStreamObject.getStream()); + characterContentStreams[static_cast(currentOffset)] = qMove(contentStream); + } + + ++currentOffset; + } + else + { + throw PDFException(PDFTranslationContext::tr("Invalid differences in encoding entry of type 3 font.")); + } + } + + PDFFontCMap toUnicodeCMap; + const PDFObject& toUnicode = document->getObject(fontDictionary->get("ToUnicode")); + if (toUnicode.isName()) + { + toUnicodeCMap = PDFFontCMap::createFromName(toUnicode.getString()); + } + else if (toUnicode.isStream()) + { + const PDFStream* stream = toUnicode.getStream(); + QByteArray decodedStream = document->getDecodedStream(stream); + toUnicodeCMap = PDFFontCMap::createFromData(decodedStream); + } + + std::vector widths = fontLoader.readNumberArrayFromDictionary(fontDictionary, "Widths"); + return PDFFontPointer(new PDFType3Font(qMove(fontDescriptor), firstChar, lastChar, fontMatrix, qMove(characterContentStreams), qMove(widths), document->getObject(fontDictionary->get("Resources")), qMove(toUnicodeCMap))); + } + + default: + { + Q_ASSERT(false); + break; + } + } + + switch (fontType) + { + case FontType::Type1: + case FontType::MMType1: + return PDFFontPointer(new PDFType1Font(fontType, qMove(fontDescriptor), qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encoding, simpleFontEncodingTable, standardFont, glyphIndexArray)); + + case FontType::TrueType: + return PDFFontPointer(new PDFTrueTypeFont(qMove(fontDescriptor), qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encoding, simpleFontEncodingTable, glyphIndexArray)); + + default: + { + Q_ASSERT(false); + break; + } + } + + return PDFFontPointer(); +} + +PDFSimpleFont::PDFSimpleFont(FontDescriptor fontDescriptor, + QByteArray name, + QByteArray baseFont, + PDFInteger firstChar, + PDFInteger lastChar, + std::vector widths, + PDFEncoding::Encoding encodingType, + encoding::EncodingTable encoding, + GlyphIndices glyphIndices) : + PDFFont(qMove(fontDescriptor)), + m_name(qMove(name)), + m_baseFont(qMove(baseFont)), + m_firstChar(firstChar), + m_lastChar(lastChar), + m_widths(qMove(widths)), + m_encodingType(encodingType), + m_encoding(encoding), + m_glyphIndices(glyphIndices) +{ + +} + +PDFInteger PDFSimpleFont::getGlyphAdvance(size_t index) const +{ + const size_t min = m_firstChar; + const size_t max = m_lastChar; + + if (index >= min && index <= max) + { + const size_t adjustedIndex = index - min; + if (adjustedIndex < m_widths.size()) + { + return m_widths[adjustedIndex]; + } + } + + return 0; +} + +void PDFSimpleFont::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + BaseClass::dumpFontToTreeItem(item); + + QString encodingTypeString; + switch (m_encodingType) + { + case PDFEncoding::Encoding::Standard: + encodingTypeString = PDFTranslationContext::tr("Standard"); + break; + + case PDFEncoding::Encoding::MacRoman: + encodingTypeString = PDFTranslationContext::tr("Mac Roman"); + break; + + case PDFEncoding::Encoding::WinAnsi: + encodingTypeString = PDFTranslationContext::tr("Win Ansi"); + break; + + case PDFEncoding::Encoding::PDFDoc: + encodingTypeString = PDFTranslationContext::tr("PDF Doc"); + break; + + case PDFEncoding::Encoding::MacExpert: + encodingTypeString = PDFTranslationContext::tr("Mac Expert"); + break; + + case PDFEncoding::Encoding::Symbol: + encodingTypeString = PDFTranslationContext::tr("Symbol"); + break; + + case PDFEncoding::Encoding::ZapfDingbats: + encodingTypeString = PDFTranslationContext::tr("Zapf Dingbats"); + break; + + case PDFEncoding::Encoding::MacOsRoman: + encodingTypeString = PDFTranslationContext::tr("Mac OS Roman"); + break; + + case PDFEncoding::Encoding::Custom: + encodingTypeString = PDFTranslationContext::tr("Custom"); + break; + + default: + { + Q_ASSERT(false); + break; + } + } + + new QTreeWidgetItem(item, { PDFTranslationContext::tr("Encoding"), encodingTypeString }); +} + +PDFType1Font::PDFType1Font(FontType fontType, + FontDescriptor fontDescriptor, + QByteArray name, + QByteArray baseFont, + PDFInteger firstChar, + PDFInteger lastChar, + std::vector widths, + PDFEncoding::Encoding encodingType, + encoding::EncodingTable encoding, + StandardFontType standardFontType, + GlyphIndices glyphIndices) : + PDFSimpleFont(qMove(fontDescriptor), qMove(name), qMove(baseFont), firstChar, lastChar, qMove(widths), encodingType, encoding, glyphIndices), + m_fontType(fontType), + m_standardFontType(standardFontType) +{ + +} + +FontType PDFType1Font::getFontType() const +{ + return m_fontType; +} + +void PDFType1Font::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + BaseClass::dumpFontToTreeItem(item); + + if (m_standardFontType != StandardFontType::Invalid) + { + QString standardFontTypeString; + switch (m_standardFontType) + { + case StandardFontType::TimesRoman: + case StandardFontType::TimesRomanBold: + case StandardFontType::TimesRomanItalics: + case StandardFontType::TimesRomanBoldItalics: + standardFontTypeString = PDFTranslationContext::tr("Times Roman"); + break; + + case StandardFontType::Helvetica: + case StandardFontType::HelveticaBold: + case StandardFontType::HelveticaOblique: + case StandardFontType::HelveticaBoldOblique: + standardFontTypeString = PDFTranslationContext::tr("Helvetica"); + break; + + case StandardFontType::Courier: + case StandardFontType::CourierBold: + case StandardFontType::CourierOblique: + case StandardFontType::CourierBoldOblique: + standardFontTypeString = PDFTranslationContext::tr("Courier"); + break; + + case StandardFontType::Symbol: + standardFontTypeString = PDFTranslationContext::tr("Symbol"); + break; + + case StandardFontType::ZapfDingbats: + standardFontTypeString = PDFTranslationContext::tr("Zapf Dingbats"); + break; + + default: + Q_ASSERT(false); + break; + } + + new QTreeWidgetItem(item, { PDFTranslationContext::tr("Standard font"), standardFontTypeString }); + } +} + +FontType PDFTrueTypeFont::getFontType() const +{ + return FontType::TrueType; +} + +void PDFFontCache::setDocument(const PDFModifiedDocument& document) +{ + QMutexLocker lock(&m_mutex); + if (m_document != document) + { + m_document = document; + + // Jakub Melka: If document has not reset flag, then fonts of the + // document remains the same. So it is not needed to clear font cache. + if (document.hasReset()) + { + m_fontCache.clear(); + m_realizedFontCache.clear(); + } + } +} + +PDFFontPointer PDFFontCache::getFont(const PDFObject& fontObject) const +{ + if (fontObject.isReference()) + { + // Font is object reference. Look in the cache, if we have it, then return it. + + QMutexLocker lock(&m_mutex); + PDFObjectReference reference = fontObject.getReference(); + + auto it = m_fontCache.find(reference); + if (it == m_fontCache.cend()) + { + // We must create the font + PDFFontPointer font = PDFFont::createFont(fontObject, m_document); + + if (m_fontCacheShrinkDisabledObjects.empty() && m_fontCache.size() >= m_fontCacheLimit) + { + // We have exceeded the cache limit. Clear the cache. + m_fontCache.clear(); + } + + it = m_fontCache.insert(std::make_pair(reference, qMove(font))).first; + } + return it->second; + } + else + { + // Object is not a reference. Create font directly and return it. + return PDFFont::createFont(fontObject, m_document); + } +} + +PDFRealizedFontPointer PDFFontCache::getRealizedFont(const PDFFontPointer& font, PDFReal size, PDFRenderErrorReporter* reporter) const +{ + Q_ASSERT(font); + + QMutexLocker lock(&m_mutex); + auto it = m_realizedFontCache.find(std::make_pair(font, size)); + if (it == m_realizedFontCache.cend()) + { + // We must create the realized font + PDFRealizedFontPointer realizedFont = PDFRealizedFont::createRealizedFont(font, size, reporter); + + if (m_fontCacheShrinkDisabledObjects.empty() && m_realizedFontCache.size() >= m_realizedFontCacheLimit) + { + m_realizedFontCache.clear(); + } + + it = m_realizedFontCache.insert(std::make_pair(std::make_pair(font, size), qMove(realizedFont))).first; + } + + return it->second; +} + +void PDFFontCache::setCacheShrinkEnabled(const void* source, bool enabled) +{ + QMutexLocker lock(&m_mutex); + if (enabled) + { + m_fontCacheShrinkDisabledObjects.erase(source); + lock.unlock(); + shrink(); + } + else + { + m_fontCacheShrinkDisabledObjects.insert(source); + } +} + +void PDFFontCache::setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit) +{ + if (m_fontCacheLimit != fontCacheLimit || m_realizedFontCacheLimit != instancedFontCacheLimit) + { + m_fontCacheLimit = fontCacheLimit; + m_realizedFontCacheLimit = instancedFontCacheLimit; + shrink(); + } +} + +void PDFFontCache::shrink() +{ + QMutexLocker lock(&m_mutex); + if (m_fontCacheShrinkDisabledObjects.empty()) + { + if (m_fontCache.size() >= m_fontCacheLimit) + { + m_fontCache.clear(); + } + if (m_realizedFontCache.size() >= m_realizedFontCacheLimit) + { + m_realizedFontCache.clear(); + } + } +} + +const QByteArray* FontDescriptor::getEmbeddedFontData() const +{ + if (!fontFile.isEmpty()) + { + return &fontFile; + } + else if (!fontFile2.isEmpty()) + { + return &fontFile2; + } + else if (!fontFile3.isEmpty()) + { + return &fontFile3; + } + + return nullptr; +} + +PDFFontCMap PDFFontCMap::createFromName(const QByteArray& name) +{ + QFile file(QString(":/cmaps/%1").arg(QString::fromLatin1(name))); + if (file.exists()) + { + QByteArray data; + if (file.open(QFile::ReadOnly)) + { + data = file.readAll(); + file.close(); + } + + return createFromData(data); + } + + throw PDFException(PDFTranslationContext::tr("Can't load CID font mapping named '%1'.").arg(QString::fromLatin1(name))); + return PDFFontCMap(); +} + +PDFFontCMap PDFFontCMap::createFromData(const QByteArray& data) +{ + Entries entries; + entries.reserve(1024); // Arbitrary number, we have enough memory, better than perform reallocation each time + + std::vector additionalMappings; + PDFLexicalAnalyzer parser(data.constBegin(), data.constEnd()); + + bool vertical = false; + PDFLexicalAnalyzer::Token previousToken; + while (!parser.isAtEnd()) + { + PDFLexicalAnalyzer::Token token = parser.fetch(); + + if (token.type == PDFLexicalAnalyzer::TokenType::Name && token.data.toByteArray() == "WMode") + { + PDFLexicalAnalyzer::Token valueToken = parser.fetch(); + vertical = valueToken.type == PDFLexicalAnalyzer::TokenType::Integer && valueToken.data.value() == 1; + continue; + } + + auto fetchCode = [] (const PDFLexicalAnalyzer::Token& currentToken) -> std::pair + { + if (currentToken.type == PDFLexicalAnalyzer::TokenType::String) + { + QByteArray byteArray = currentToken.data.toByteArray(); + + unsigned int codeValue = 0; + for (int i = 0; i < byteArray.size(); ++i) + { + codeValue = (codeValue << 8) + static_cast(byteArray[i]); + } + + return std::make_pair(codeValue, byteArray.size()); + } + + throw PDFException(PDFTranslationContext::tr("Can't fetch code from CMap definition.")); + return std::pair(); + }; + + auto fetchCID = [] (const PDFLexicalAnalyzer::Token& currentToken) -> CID + { + if (currentToken.type == PDFLexicalAnalyzer::TokenType::Integer) + { + return currentToken.data.value(); + } + + throw PDFException(PDFTranslationContext::tr("Can't fetch CID from CMap definition.")); + return 0; + }; + + auto fetchUnicode = [](const PDFLexicalAnalyzer::Token& currentToken) -> CID + { + if (currentToken.type == PDFLexicalAnalyzer::TokenType::String) + { + QByteArray byteArray = currentToken.data.toByteArray(); + + if (byteArray.size() == 2) + { + CID unicodeValue = 0; + for (int i = 0; i < byteArray.size(); ++i) + { + unicodeValue = (unicodeValue << 8) + static_cast(byteArray[i]); + } + return unicodeValue; + } + } + + return 0; + }; + + if (token.type == PDFLexicalAnalyzer::TokenType::Command) + { + QByteArray command = token.data.toByteArray(); + if (command == "usecmap") + { + if (previousToken.type == PDFLexicalAnalyzer::TokenType::Name) + { + additionalMappings.emplace_back(createFromName(previousToken.data.toByteArray())); + } + else + { + throw PDFException(PDFTranslationContext::tr("Can't use cmap inside cmap file.")); + } + } + else if (command == "beginbfrange") + { + PDFLexicalAnalyzer::Token token1 = parser.fetch(); + + if (token1.type == PDFLexicalAnalyzer::TokenType::Command && + token1.data.toByteArray() == "endbfrange") + { + break; + } + + PDFLexicalAnalyzer::Token token2 = parser.fetch(); + PDFLexicalAnalyzer::Token token3 = parser.fetch(); + + std::pair from = fetchCode(token1); + std::pair to = fetchCode(token2); + CID cid = fetchUnicode(token3); + + entries.emplace_back(from.first, to.first, qMax(from.second, to.second), cid); + } + else if (command == "begincidrange") + { + while (true) + { + PDFLexicalAnalyzer::Token token1 = parser.fetch(); + + if (token1.type == PDFLexicalAnalyzer::TokenType::Command && + token1.data.toByteArray() == "endcidrange") + { + break; + } + + PDFLexicalAnalyzer::Token token2 = parser.fetch(); + PDFLexicalAnalyzer::Token token3 = parser.fetch(); + + std::pair from = fetchCode(token1); + std::pair to = fetchCode(token2); + CID cid = fetchCID(token3); + + entries.emplace_back(from.first, to.first, qMax(from.second, to.second), cid); + } + } + else if (command == "begincidchar") + { + while (true) + { + PDFLexicalAnalyzer::Token token1 = parser.fetch(); + + if (token1.type == PDFLexicalAnalyzer::TokenType::Command && + token1.data.toByteArray() == "endcidchar") + { + break; + } + + PDFLexicalAnalyzer::Token token2 = parser.fetch(); + + std::pair code = fetchCode(token1); + CID cid = fetchCID(token2); + + entries.emplace_back(code.first, code.first, code.second, cid); + } + } + else if (command == "beginbfchar") + { + while (true) + { + PDFLexicalAnalyzer::Token token1 = parser.fetch(); + + if (token1.type == PDFLexicalAnalyzer::TokenType::Command && + token1.data.toByteArray() == "endbfchar") + { + break; + } + + PDFLexicalAnalyzer::Token token2 = parser.fetch(); + + std::pair code = fetchCode(token1); + CID cid = fetchUnicode(token2); + + entries.emplace_back(code.first, code.first, code.second, cid); + } + } + } + + previousToken = token; + } + + std::sort(entries.begin(), entries.end()); + entries = optimize(entries); + + if (!additionalMappings.empty()) + { + for (const PDFFontCMap& map : additionalMappings) + { + entries.insert(entries.cend(), map.m_entries.cbegin(), map.m_entries.cend()); + } + } + + return PDFFontCMap(qMove(entries), vertical); +} + +QByteArray PDFFontCMap::serialize() const +{ + QByteArray result; + + { + QDataStream stream(&result, QIODevice::WriteOnly); + stream << m_maxKeyLength; + stream << m_vertical; + stream << m_entries.size(); + for (const Entry& entry : m_entries) + { + stream << entry.from; + stream << entry.to; + stream << entry.byteCount; + stream << entry.cid; + } + } + + return qCompress(result, 9); +} + +PDFFontCMap PDFFontCMap::deserialize(const QByteArray& byteArray) +{ + PDFFontCMap result; + QByteArray decompressed = qUncompress(byteArray); + QDataStream stream(&decompressed, QIODevice::ReadOnly); + stream >> result.m_maxKeyLength; + stream >> result.m_vertical; + + Entries::size_type size = 0; + stream >> size; + result.m_entries.reserve(size); + for (Entries::size_type i = 0; i < size; ++i) + { + Entry entry; + stream >> entry.from; + stream >> entry.to; + stream >> entry.byteCount; + stream >> entry.cid; + result.m_entries.push_back(entry); + } + + return result; +} + +std::vector PDFFontCMap::interpret(const QByteArray& byteArray) const +{ + std::vector result; + result.reserve(byteArray.size() / m_maxKeyLength); + + unsigned int value = 0; + int scannedBytes = 0; + + for (int i = 0, size = byteArray.size(); i < size; ++i) + { + value = (value << 8) + static_cast(byteArray[i]); + ++scannedBytes; + + // Find suitable mapping + auto it = std::find_if(m_entries.cbegin(), m_entries.cend(), [value, scannedBytes](const Entry& entry) { return entry.from <= value && entry.to >= value && entry.byteCount == scannedBytes; }); + if (it != m_entries.cend()) + { + const Entry& entry = *it; + const CID cid = value - entry.from + entry.cid; + result.push_back(cid); + + value = 0; + scannedBytes = 0; + } + else if (scannedBytes == m_maxKeyLength) + { + // This means error occured - fill empty CID + result.push_back(0); + value = 0; + scannedBytes = 0; + } + } + + return result; +} + +QChar PDFFontCMap::getToUnicode(CID cid) const +{ + if (isValid()) + { + auto it = std::find_if(m_entries.cbegin(), m_entries.cend(), [cid](const Entry& entry) { return entry.from <= cid && entry.to >= cid; }); + if (it != m_entries.cend()) + { + const Entry& entry = *it; + const CID unicodeCID = cid - entry.from + entry.cid; + return QChar(unicodeCID); + } + } + + return QChar(); +} + +PDFFontCMap::PDFFontCMap(Entries&& entries, bool vertical) : + m_entries(qMove(entries)), + m_maxKeyLength(0), + m_vertical(vertical) +{ + m_maxKeyLength = std::accumulate(m_entries.cbegin(), m_entries.cend(), 0, [](unsigned int a, const Entry& b) { return qMax(a, b.byteCount); }); +} + +PDFFontCMap::Entries PDFFontCMap::optimize(const PDFFontCMap::Entries& entries) +{ + Entries result; + result.reserve(entries.size()); + + if (!entries.empty()) + { + Entry current = entries.front(); + for (size_t i = 1, count = entries.size(); i < count; ++i) + { + Entry toMerge = entries[i]; + + if (current.canMerge(toMerge)) + { + current = current.merge(toMerge); + } + else + { + result.emplace_back(current); + current = toMerge; + } + } + result.emplace_back(current); + } + + result.shrink_to_fit(); + return result; +} + +PDFFontCMapRepository* PDFFontCMapRepository::getInstance() +{ + static PDFFontCMapRepository repository; + return &repository; +} + +void PDFFontCMapRepository::saveToFile(const QString& fileName) const +{ + QFile file(fileName); + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + size_t size = m_cmaps.size(); + + { + QDataStream stream(&file); + stream << size; + for (const auto& item : m_cmaps) + { + stream << item.first; + stream << item.second; + } + } + + file.close(); + } +} + +bool PDFFontCMapRepository::loadFromFile(const QString& fileName) +{ + QFile file(fileName); + if (file.open(QFile::ReadOnly)) + { + { + QDataStream stream(&file); + size_t size = 0; + stream >> size; + for (size_t i = 0; i < size; ++i) + { + QByteArray key; + QByteArray value; + stream >> key; + stream >> value; + m_cmaps[qMove(key)] = qMove(value); + } + } + + file.close(); + return true; + } + + return false; +} + +PDFFontCMapRepository::PDFFontCMapRepository() +{ + +} + +PDFReal PDFType0Font::getGlyphAdvance(CID cid) const +{ + auto it = m_advances.find(cid); + if (it != m_advances.cend()) + { + return it->second; + } + + return m_defaultAdvance; +} + +PDFType3Font::PDFType3Font(FontDescriptor fontDescriptor, + int firstCharacterIndex, + int lastCharacterIndex, + QMatrix fontMatrix, + std::map&& characterContentStreams, + std::vector&& widths, + const PDFObject& resources, + PDFFontCMap toUnicode) : + PDFFont(qMove(fontDescriptor)), + m_firstCharacterIndex(firstCharacterIndex), + m_lastCharacterIndex(lastCharacterIndex), + m_fontMatrix(fontMatrix), + m_characterContentStreams(qMove(characterContentStreams)), + m_widths(qMove(widths)), + m_resources(resources), + m_toUnicode(qMove(toUnicode)) +{ + +} + +FontType PDFType3Font::getFontType() const +{ + return FontType::Type3; +} + +void PDFType3Font::dumpFontToTreeItem(QTreeWidgetItem* item) const +{ + new QTreeWidgetItem(item, { PDFTranslationContext::tr("Character count"), QString::number(m_characterContentStreams.size()) }); +} + +double PDFType3Font::getWidth(int characterIndex) const +{ + if (characterIndex >= m_firstCharacterIndex && characterIndex <= m_lastCharacterIndex) + { + size_t index = characterIndex - m_firstCharacterIndex; + if (index < m_widths.size()) + { + return m_widths[index]; + } + } + + return 0.0; +} + +const QByteArray* PDFType3Font::getContentStream(int characterIndex) const +{ + auto it = m_characterContentStreams.find(characterIndex); + if (it != m_characterContentStreams.cend()) + { + return &it->second; + } + + return nullptr; +} + +void PDFRealizedType3FontImpl::fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter) +{ + Q_ASSERT(dynamic_cast(m_parentFont.get())); + const PDFType3Font* parentFont = static_cast(m_parentFont.get()); + + textSequence.items.reserve(byteArray.size()); + for (int i = 0, characterCount = byteArray.size(); i < characterCount; ++i) + { + int index = static_cast(byteArray[i]); + const QByteArray* contentStream = parentFont->getContentStream(index); + QChar character = parentFont->getUnicode(index); + const double width = parentFont->getWidth(index); + + if (contentStream) + { + textSequence.items.emplace_back(contentStream, character, width); + } + else + { + // Report error, and add advance, if we have it + reporter->reportRenderError(RenderErrorType::Warning, PDFTranslationContext::tr("Content stream for type 3 font character code '%1' not found.").arg(index)); + + if (width > 0.0) + { + textSequence.items.emplace_back(width); + } + } + } +} + +bool PDFRealizedType3FontImpl::isHorizontalWritingSystem() const +{ + return true; +} + +CharacterInfos PDFRealizedType3FontImpl::getCharacterInfos() const +{ + CharacterInfos result; + + Q_ASSERT(dynamic_cast(m_parentFont.get())); + const PDFType3Font* parentFont = static_cast(m_parentFont.get()); + + for (const auto& contentStreamItem : parentFont->getContentStreams()) + { + CharacterInfo info; + info.gid = contentStreamItem.first; + info.character = parentFont->getUnicode(contentStreamItem.first); + result.emplace_back(qMove(info)); + } + + return result; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdffont.h b/Pdf4QtLib/sources/pdffont.h index 749826d..83fcec8 100644 --- a/Pdf4QtLib/sources/pdffont.h +++ b/Pdf4QtLib/sources/pdffont.h @@ -1,660 +1,660 @@ -// Copyright (C) 2019-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFFONT_H -#define PDFFONT_H - -#include "pdfglobal.h" -#include "pdfencoding.h" -#include "pdfobject.h" - -#include -#include -#include - -#include -#include - -class QPainterPath; -class QTreeWidgetItem; - -namespace pdf -{ -class PDFDocument; -class PDFModifiedDocument; -class PDFRenderErrorReporter; -class PDFFontCMap; - -using CID = unsigned int; -using GID = unsigned int; - -using GlyphIndices = std::array; - -enum class TextRenderingMode -{ - Fill = 0, - Stroke = 1, - FillStroke = 2, - Invisible = 3, - FillClip = 4, - StrokeClip = 5, - FillStrokeClip = 6, - Clip = 7 -}; - -/// Item of the text sequence (either single character, or advance) -struct TextSequenceItem -{ - inline explicit TextSequenceItem() = default; - inline explicit TextSequenceItem(const QPainterPath* glyph, QChar character, PDFReal advance) : glyph(glyph), character(character), advance(advance) { } - inline explicit TextSequenceItem(PDFReal advance) : character(), advance(advance) { } - inline explicit TextSequenceItem(const QByteArray* characterContentStream, QChar character, PDFReal advance) : characterContentStream(characterContentStream), character(character), advance(advance) { } - - inline bool isContentStream() const { return characterContentStream; } - inline bool isCharacter() const { return glyph; } - inline bool isAdvance() const { return advance != 0.0; } - inline bool isNull() const { return !isCharacter() && !isAdvance(); } - - const QPainterPath* glyph = nullptr; - const QByteArray* characterContentStream = nullptr; - QChar character; - PDFReal advance = 0; -}; - -struct TextSequence -{ - std::vector items; -}; - -constexpr bool isTextRenderingModeFilled(TextRenderingMode mode) -{ - switch (mode) - { - case TextRenderingMode::Fill: - case TextRenderingMode::FillClip: - case TextRenderingMode::FillStroke: - case TextRenderingMode::FillStrokeClip: - return true; - - default: - return false; - } -} - -constexpr bool isTextRenderingModeStroked(TextRenderingMode mode) -{ - switch (mode) - { - case TextRenderingMode::Stroke: - case TextRenderingMode::FillStroke: - case TextRenderingMode::StrokeClip: - case TextRenderingMode::FillStrokeClip: - return true; - - default: - return false; - } -} - -constexpr bool isTextRenderingModeClipped(TextRenderingMode mode) -{ - switch (mode) - { - case TextRenderingMode::Clip: - case TextRenderingMode::FillClip: - case TextRenderingMode::StrokeClip: - case TextRenderingMode::FillStrokeClip: - return true; - - default: - return false; - } -} - -enum class FontType -{ - Invalid, - Type0, - Type1, - MMType1, - TrueType, - Type3 -}; - -/// Standard Type1 fonts -enum class StandardFontType -{ - Invalid, - TimesRoman, - TimesRomanBold, - TimesRomanItalics, - TimesRomanBoldItalics, - Helvetica, - HelveticaBold, - HelveticaOblique, - HelveticaBoldOblique, - Courier, - CourierBold, - CourierOblique, - CourierBoldOblique, - Symbol, - ZapfDingbats -}; - -/// Returns builtin encoding for the standard font -static constexpr PDFEncoding::Encoding getEncodingForStandardFont(StandardFontType standardFont) -{ - switch (standardFont) - { - case StandardFontType::Symbol: - return PDFEncoding::Encoding::Symbol; - - case StandardFontType::ZapfDingbats: - return PDFEncoding::Encoding::ZapfDingbats; - - default: - return PDFEncoding::Encoding::Standard; - } -} - -struct PDF4QTLIBSHARED_EXPORT FontDescriptor -{ - bool isEmbedded() const { return !fontFile.isEmpty() || !fontFile2.isEmpty() || !fontFile3.isEmpty(); } - - /// Returns embedded font data, or nullptr, if font is not embedded - const QByteArray* getEmbeddedFontData() const; - - QByteArray fontName; - QByteArray fontFamily; - QFont::Stretch fontStretch = QFont::AnyStretch; - PDFReal fontWeight = 400.0; - PDFInteger flags; - QRectF boundingBox; - PDFReal italicAngle = 0.0; - PDFReal ascent = 0.0; - PDFReal descent = 0.0; - PDFReal leading = 0.0; - PDFReal capHeight = 0.0; - PDFReal xHeight = 0.0; - PDFReal stemV = 0.0; - PDFReal stemH = 0.0; - PDFReal avgWidth = 0.0; - PDFReal maxWidth = 0.0; - PDFReal missingWidth = 0.0; - - /// Byte array with Type 1 font program (embedded font) - QByteArray fontFile; - - /// Byte array with TrueType font program (embedded font) - QByteArray fontFile2; - - /// Byte array with font program, whose format is defined by the Subtype array - /// in the font dictionary. - QByteArray fontFile3; - - /// Character set - QByteArray charset; -}; - -class PDFFont; - -using PDFFontPointer = QSharedPointer; - -class PDFRealizedFont; -class IRealizedFontImpl; - -using PDFRealizedFontPointer = QSharedPointer; - -struct CharacterInfo -{ - GID gid = 0; - QChar character; -}; -using CharacterInfos = std::vector; - -/// Font, which has fixed pixel size. It is programmed as PIMPL, because we need -/// to remove FreeType types from the interface (so we do not include FreeType in the interface). -class PDF4QTLIBSHARED_EXPORT PDFRealizedFont -{ -public: - ~PDFRealizedFont(); - - /// Fills the text sequence by interpreting byte array according font data and - /// produces glyphs for the font. - /// \param byteArray Array of bytes to be interpreted - /// \param textSequence Text sequence to be filled - /// \param reporter Error reporter - void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter); - - /// Return true, if we have horizontal writing system - bool isHorizontalWritingSystem() const; - - /// Adds information about the font into tree item - void dumpFontToTreeItem(QTreeWidgetItem* item) const; - - /// Returns postscript name of the font - QString getPostScriptName() const; - - /// Returns character info - CharacterInfos getCharacterInfos() const; - - /// Creates new realized font from the standard font. If font can't be created, - /// then exception is thrown. - static PDFRealizedFontPointer createRealizedFont(PDFFontPointer font, PDFReal pixelSize, PDFRenderErrorReporter* reporter); - -private: - /// Constructs new realized font - explicit PDFRealizedFont(IRealizedFontImpl* impl) : m_impl(impl) { } - - IRealizedFontImpl* m_impl; -}; - -/// Base class representing font in the PDF file -class PDF4QTLIBSHARED_EXPORT PDFFont -{ -public: - explicit PDFFont(FontDescriptor fontDescriptor); - virtual ~PDFFont() = default; - - /// Returns the font type - virtual FontType getFontType() const = 0; - - /// Returns ToUnicode mapping (or nullptr, if font has no mapping to unicode) - virtual const PDFFontCMap* getToUnicode() const { return nullptr; } - - /// Returns font descriptor - const FontDescriptor* getFontDescriptor() const { return &m_fontDescriptor; } - - /// Adds information about the font into tree item - virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const { Q_UNUSED(item); } - - /// Creates font from the object. If font can't be created, exception is thrown. - /// \param object Font dictionary - /// \param document Document - static PDFFontPointer createFont(const PDFObject& object, const PDFDocument* document); - -protected: - FontDescriptor m_fontDescriptor; - -private: - /// Tries to read font descriptor from the object - /// \param fontDescriptorObject Font descriptor dictionary - /// \param document Document - static FontDescriptor readFontDescriptor(const PDFObject& fontDescriptorObject, const PDFDocument* document); -}; - -/// Simple font, see PDF reference 1.7, chapter 5.5. Simple fonts have encoding table, -/// which maps single-byte character to the glyph in the font. -class PDFSimpleFont : public PDFFont -{ - using BaseClass = PDFFont; - -public: - explicit PDFSimpleFont(FontDescriptor fontDescriptor, - QByteArray name, - QByteArray baseFont, - PDFInteger firstChar, - PDFInteger lastChar, - std::vector widths, - PDFEncoding::Encoding encodingType, - encoding::EncodingTable encoding, - GlyphIndices glyphIndices); - virtual ~PDFSimpleFont() override = default; - - const PDFEncoding::Encoding getEncodingType() const { return m_encodingType; } - const encoding::EncodingTable* getEncoding() const { return &m_encoding; } - const GlyphIndices* getGlyphIndices() const { return &m_glyphIndices; } - - /// Returns the glyph advance (or zero, if glyph advance is invalid) - PDFInteger getGlyphAdvance(size_t index) const; - - virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const override; - -protected: - QByteArray m_name; - QByteArray m_baseFont; - PDFInteger m_firstChar; - PDFInteger m_lastChar; - std::vector m_widths; - PDFEncoding::Encoding m_encodingType; - encoding::EncodingTable m_encoding; - GlyphIndices m_glyphIndices; -}; - -class PDFType1Font : public PDFSimpleFont -{ - using BaseClass = PDFSimpleFont; - -public: - explicit PDFType1Font(FontType fontType, - FontDescriptor fontDescriptor, - QByteArray name, - QByteArray baseFont, - PDFInteger firstChar, - PDFInteger lastChar, - std::vector widths, - PDFEncoding::Encoding encodingType, - encoding::EncodingTable encoding, - StandardFontType standardFontType, - GlyphIndices glyphIndices); - virtual ~PDFType1Font() override = default; - - virtual FontType getFontType() const override; - virtual void dumpFontToTreeItem(QTreeWidgetItem*item) const override; - - /// Returns the assigned standard font (or invalid, if font is not standard) - StandardFontType getStandardFontType() const { return m_standardFontType; } - -private: - FontType m_fontType; - StandardFontType m_standardFontType; ///< Type of the standard font (or invalid, if it is not a standard font) -}; - -class PDFTrueTypeFont : public PDFSimpleFont -{ -public: - using PDFSimpleFont::PDFSimpleFont; - - virtual FontType getFontType() const override; -}; - -/// Font cache which caches both fonts, and realized fonts. Cache has individual limit -/// for fonts, and realized fonts. -class PDF4QTLIBSHARED_EXPORT PDFFontCache -{ -public: - inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) : - m_fontCacheLimit(fontCacheLimit), - m_realizedFontCacheLimit(realizedFontCacheLimit), - m_document(nullptr) - { - - } - - /// Sets the document to the cache. Whole cache is cleared, - /// if it is needed. - /// \param document Document to be setted - void setDocument(const PDFModifiedDocument& document); - - /// Retrieves font from the cache. If font can't be accessed or created, - /// then exception is thrown. - /// \param fontObject Font object - PDFFontPointer getFont(const PDFObject& fontObject) const; - - /// Retrieves realized font from the cache. If realized font can't be accessed or created, - /// then exception is thrown. - /// \param font Font, which should be realized - /// \param size Size of the font (in pixels) - /// \param reporter Error reporter - PDFRealizedFontPointer getRealizedFont(const PDFFontPointer& font, PDFReal size, PDFRenderErrorReporter* reporter) const; - - /// Sets or unsets font shrinking (i.e. font can be deleted from the cache). In multithreading environment, - /// font deletion is not thread safe. For this reason, disable font deletion by calling this function. - /// First parameter, \p source determines which object enables cache shrinking (so some objects can - /// enable shrinking, while some objects will disable it). Only if all objects enables cache shrinking, - /// then cache can shrink. - /// \param source Source object - /// \param enabled Enable or disable cache shrinking - void setCacheShrinkEnabled(const void* source, bool enabled); - - /// Set font cache limits - void setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit); - - /// If shrinking is enabled, then erase font, if cache limit is exceeded. - void shrink(); - -private: - size_t m_fontCacheLimit; - size_t m_realizedFontCacheLimit; - mutable QMutex m_mutex; - const PDFDocument* m_document; - mutable std::map m_fontCache; - mutable std::map, PDFRealizedFontPointer> m_realizedFontCache; - mutable std::set m_fontCacheShrinkDisabledObjects; -}; - -/// Performs mapping from CID to GID (even identity mapping, if byte array is empty) -class PDFCIDtoGIDMapper -{ -public: - explicit inline PDFCIDtoGIDMapper(QByteArray&& mapping) : m_mapping(qMove(mapping)) { } - - /// Maps CID to GID (glyph identifier) - GID map(CID cid) const - { - if (m_mapping.isEmpty()) - { - // This means identity mapping - return cid; - } - else if ((2 * cid + 1) < CID(m_mapping.size())) - { - return (GID(m_mapping[2 * cid]) << 8) + GID(m_mapping[2 * cid + 1]); - } - - // This should occur only in case of bad (damaged) PDF file - because in this case, - // encoding is missing. Return invalid glyph index. - return 0; - } - - /// Maps GID to CID (inverse mapping) - CID unmap(GID gid) const - { - if (m_mapping.isEmpty()) - { - // This means identity mapping - return gid; - } - else - { - CID lastCid = CID(m_mapping.size() / 2); - for (CID i = 0; i < lastCid; ++i) - { - if (map(i) == gid) - { - return i; - } - } - } - - // This should occur only in case of bad (damaged) PDF file - because in this case, - // encoding is missing. Return invalid character index. - return 0; - } - -private: - QByteArray m_mapping; -}; - -/// Represents a font CMAP (mapping of CIDs) -class PDF4QTLIBSHARED_EXPORT PDFFontCMap -{ -public: - explicit PDFFontCMap() = default; - - /// Returns true, if mapping is valid - bool isValid() const { return !m_entries.empty(); } - - /// Creates mapping from name (name must be one of predefined names) - static PDFFontCMap createFromName(const QByteArray& name); - - /// Creates mapping from data (data must be a byte array containing the CMap) - static PDFFontCMap createFromData(const QByteArray& data); - - /// Serializes the CMap to the byte array - QByteArray serialize() const; - - /// Deserializes the CMap from the byte array - static PDFFontCMap deserialize(const QByteArray& byteArray); - - /// Converts byte array to array of CIDs - std::vector interpret(const QByteArray& byteArray) const; - - /// Converts CID to QChar, use only on ToUnicode CMaps - QChar getToUnicode(CID cid) const; - -private: - - struct Entry - { - constexpr explicit inline Entry() = default; - constexpr explicit inline Entry(unsigned int from, unsigned int to, unsigned int byteCount, CID cid) : from(from), to(to), byteCount(byteCount), cid(cid) { } - - unsigned int from = 0; - unsigned int to = 0; - unsigned int byteCount = 0; - CID cid = 0; - - // Can merge from other CID entry? - bool canMerge(const Entry& other) const - { - const bool sameBytes = byteCount == other.byteCount; - const bool compatibleRange = (to + 1) == other.from; - const bool compatibleCID = (cid + to + 1) - from == other.cid; - return sameBytes && compatibleRange && compatibleCID; - } - - inline constexpr Entry merge(const Entry& other) const - { - return Entry(from, other.to, byteCount, cid); - } - - inline constexpr bool operator<(const Entry& other) const - { - return std::tie(byteCount, from) < std::tie(other.byteCount, other.from); - } - }; - - using Entries = std::vector; - - explicit PDFFontCMap(Entries&& entries, bool vertical); - - /// Optimizes the entries - merges entries, which can be merged. This function - /// requires, that entries are sorted. - static Entries optimize(const Entries& entries); - - Entries m_entries; - unsigned int m_maxKeyLength = 0; - bool m_vertical = false; -}; - -class PDFType3Font : public PDFFont -{ -public: - explicit PDFType3Font(FontDescriptor fontDescriptor, - int firstCharacterIndex, - int lastCharacterIndex, - QMatrix fontMatrix, - std::map&& characterContentStreams, - std::vector&& widths, - const PDFObject& resources, - PDFFontCMap toUnicode); - - virtual FontType getFontType() const override; - virtual void dumpFontToTreeItem(QTreeWidgetItem*item) const override; - virtual const PDFFontCMap* getToUnicode() const override { return &m_toUnicode; } - - /// Returns width of the character. If character doesn't exist, then zero is returned. - double getWidth(int characterIndex) const; - - /// Return content stream for the character. If character doesn't exist, then nullptr - /// is returned. - const QByteArray* getContentStream(int characterIndex) const; - - const QMatrix& getFontMatrix() const { return m_fontMatrix; } - const PDFObject& getResources() const { return m_resources; } - const std::map& getContentStreams() const { return m_characterContentStreams; } - - /// Returns unicode character for given character index. If unicode mapping is not - /// present, empty (null) character is returned. - QChar getUnicode(int characterIndex) const { return m_toUnicode.getToUnicode(characterIndex); } - -private: - int m_firstCharacterIndex; - int m_lastCharacterIndex; - QMatrix m_fontMatrix; - std::map m_characterContentStreams; - std::vector m_widths; - PDFObject m_resources; - PDFFontCMap m_toUnicode; -}; - -/// Composite font (CID-keyed font) -class PDFType0Font : public PDFFont -{ -public: - explicit inline PDFType0Font(FontDescriptor fontDescriptor, PDFFontCMap cmap, PDFFontCMap toUnicode, PDFCIDtoGIDMapper mapper, PDFReal defaultAdvance, std::unordered_map advances) : - PDFFont(qMove(fontDescriptor)), - m_cmap(qMove(cmap)), - m_toUnicode(qMove(toUnicode)), - m_mapper(qMove(mapper)), - m_defaultAdvance(defaultAdvance), - m_advances(qMove(advances)) - { - - } - - virtual ~PDFType0Font() = default; - - virtual FontType getFontType() const override { return FontType::Type0; } - virtual const PDFFontCMap* getToUnicode() const override { return &m_toUnicode; } - - const PDFFontCMap* getCMap() const { return &m_cmap; } - const PDFCIDtoGIDMapper* getCIDtoGIDMapper() const { return &m_mapper; } - - /// Returns the glyph advance, if it can be obtained, or zero, if it cannot - /// be obtained or error occurs. - /// \param cid CID of the glyph - PDFReal getGlyphAdvance(CID cid) const; - -private: - PDFFontCMap m_cmap; - PDFFontCMap m_toUnicode; - PDFCIDtoGIDMapper m_mapper; - PDFReal m_defaultAdvance; - std::unordered_map m_advances; -}; - -/// Repository with predefined CMaps -class PDF4QTLIBSHARED_EXPORT PDFFontCMapRepository -{ -public: - /// Returns instance of CMAP repository - static PDFFontCMapRepository* getInstance(); - - /// Adds CMAP to the repository - void add(const QByteArray& key, QByteArray value) { m_cmaps[key] = qMove(value); } - - /// Clears the repository - void clear() { m_cmaps.clear(); } - - /// Saves the repository content to the file - void saveToFile(const QString& fileName) const; - - /// Loads the repository content from the file - bool loadFromFile(const QString& fileName); - -private: - explicit PDFFontCMapRepository(); - - /// Storage for predefined cmaps - std::map m_cmaps; -}; - -} // namespace pdf - -#endif // PDFFONT_H +// Copyright (C) 2019-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFFONT_H +#define PDFFONT_H + +#include "pdfglobal.h" +#include "pdfencoding.h" +#include "pdfobject.h" + +#include +#include +#include + +#include +#include + +class QPainterPath; +class QTreeWidgetItem; + +namespace pdf +{ +class PDFDocument; +class PDFModifiedDocument; +class PDFRenderErrorReporter; +class PDFFontCMap; + +using CID = unsigned int; +using GID = unsigned int; + +using GlyphIndices = std::array; + +enum class TextRenderingMode +{ + Fill = 0, + Stroke = 1, + FillStroke = 2, + Invisible = 3, + FillClip = 4, + StrokeClip = 5, + FillStrokeClip = 6, + Clip = 7 +}; + +/// Item of the text sequence (either single character, or advance) +struct TextSequenceItem +{ + inline explicit TextSequenceItem() = default; + inline explicit TextSequenceItem(const QPainterPath* glyph, QChar character, PDFReal advance) : glyph(glyph), character(character), advance(advance) { } + inline explicit TextSequenceItem(PDFReal advance) : character(), advance(advance) { } + inline explicit TextSequenceItem(const QByteArray* characterContentStream, QChar character, PDFReal advance) : characterContentStream(characterContentStream), character(character), advance(advance) { } + + inline bool isContentStream() const { return characterContentStream; } + inline bool isCharacter() const { return glyph; } + inline bool isAdvance() const { return advance != 0.0; } + inline bool isNull() const { return !isCharacter() && !isAdvance(); } + + const QPainterPath* glyph = nullptr; + const QByteArray* characterContentStream = nullptr; + QChar character; + PDFReal advance = 0; +}; + +struct TextSequence +{ + std::vector items; +}; + +constexpr bool isTextRenderingModeFilled(TextRenderingMode mode) +{ + switch (mode) + { + case TextRenderingMode::Fill: + case TextRenderingMode::FillClip: + case TextRenderingMode::FillStroke: + case TextRenderingMode::FillStrokeClip: + return true; + + default: + return false; + } +} + +constexpr bool isTextRenderingModeStroked(TextRenderingMode mode) +{ + switch (mode) + { + case TextRenderingMode::Stroke: + case TextRenderingMode::FillStroke: + case TextRenderingMode::StrokeClip: + case TextRenderingMode::FillStrokeClip: + return true; + + default: + return false; + } +} + +constexpr bool isTextRenderingModeClipped(TextRenderingMode mode) +{ + switch (mode) + { + case TextRenderingMode::Clip: + case TextRenderingMode::FillClip: + case TextRenderingMode::StrokeClip: + case TextRenderingMode::FillStrokeClip: + return true; + + default: + return false; + } +} + +enum class FontType +{ + Invalid, + Type0, + Type1, + MMType1, + TrueType, + Type3 +}; + +/// Standard Type1 fonts +enum class StandardFontType +{ + Invalid, + TimesRoman, + TimesRomanBold, + TimesRomanItalics, + TimesRomanBoldItalics, + Helvetica, + HelveticaBold, + HelveticaOblique, + HelveticaBoldOblique, + Courier, + CourierBold, + CourierOblique, + CourierBoldOblique, + Symbol, + ZapfDingbats +}; + +/// Returns builtin encoding for the standard font +static constexpr PDFEncoding::Encoding getEncodingForStandardFont(StandardFontType standardFont) +{ + switch (standardFont) + { + case StandardFontType::Symbol: + return PDFEncoding::Encoding::Symbol; + + case StandardFontType::ZapfDingbats: + return PDFEncoding::Encoding::ZapfDingbats; + + default: + return PDFEncoding::Encoding::Standard; + } +} + +struct PDF4QTLIBSHARED_EXPORT FontDescriptor +{ + bool isEmbedded() const { return !fontFile.isEmpty() || !fontFile2.isEmpty() || !fontFile3.isEmpty(); } + + /// Returns embedded font data, or nullptr, if font is not embedded + const QByteArray* getEmbeddedFontData() const; + + QByteArray fontName; + QByteArray fontFamily; + QFont::Stretch fontStretch = QFont::AnyStretch; + PDFReal fontWeight = 400.0; + PDFInteger flags; + QRectF boundingBox; + PDFReal italicAngle = 0.0; + PDFReal ascent = 0.0; + PDFReal descent = 0.0; + PDFReal leading = 0.0; + PDFReal capHeight = 0.0; + PDFReal xHeight = 0.0; + PDFReal stemV = 0.0; + PDFReal stemH = 0.0; + PDFReal avgWidth = 0.0; + PDFReal maxWidth = 0.0; + PDFReal missingWidth = 0.0; + + /// Byte array with Type 1 font program (embedded font) + QByteArray fontFile; + + /// Byte array with TrueType font program (embedded font) + QByteArray fontFile2; + + /// Byte array with font program, whose format is defined by the Subtype array + /// in the font dictionary. + QByteArray fontFile3; + + /// Character set + QByteArray charset; +}; + +class PDFFont; + +using PDFFontPointer = QSharedPointer; + +class PDFRealizedFont; +class IRealizedFontImpl; + +using PDFRealizedFontPointer = QSharedPointer; + +struct CharacterInfo +{ + GID gid = 0; + QChar character; +}; +using CharacterInfos = std::vector; + +/// Font, which has fixed pixel size. It is programmed as PIMPL, because we need +/// to remove FreeType types from the interface (so we do not include FreeType in the interface). +class PDF4QTLIBSHARED_EXPORT PDFRealizedFont +{ +public: + ~PDFRealizedFont(); + + /// Fills the text sequence by interpreting byte array according font data and + /// produces glyphs for the font. + /// \param byteArray Array of bytes to be interpreted + /// \param textSequence Text sequence to be filled + /// \param reporter Error reporter + void fillTextSequence(const QByteArray& byteArray, TextSequence& textSequence, PDFRenderErrorReporter* reporter); + + /// Return true, if we have horizontal writing system + bool isHorizontalWritingSystem() const; + + /// Adds information about the font into tree item + void dumpFontToTreeItem(QTreeWidgetItem* item) const; + + /// Returns postscript name of the font + QString getPostScriptName() const; + + /// Returns character info + CharacterInfos getCharacterInfos() const; + + /// Creates new realized font from the standard font. If font can't be created, + /// then exception is thrown. + static PDFRealizedFontPointer createRealizedFont(PDFFontPointer font, PDFReal pixelSize, PDFRenderErrorReporter* reporter); + +private: + /// Constructs new realized font + explicit PDFRealizedFont(IRealizedFontImpl* impl) : m_impl(impl) { } + + IRealizedFontImpl* m_impl; +}; + +/// Base class representing font in the PDF file +class PDF4QTLIBSHARED_EXPORT PDFFont +{ +public: + explicit PDFFont(FontDescriptor fontDescriptor); + virtual ~PDFFont() = default; + + /// Returns the font type + virtual FontType getFontType() const = 0; + + /// Returns ToUnicode mapping (or nullptr, if font has no mapping to unicode) + virtual const PDFFontCMap* getToUnicode() const { return nullptr; } + + /// Returns font descriptor + const FontDescriptor* getFontDescriptor() const { return &m_fontDescriptor; } + + /// Adds information about the font into tree item + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const { Q_UNUSED(item); } + + /// Creates font from the object. If font can't be created, exception is thrown. + /// \param object Font dictionary + /// \param document Document + static PDFFontPointer createFont(const PDFObject& object, const PDFDocument* document); + +protected: + FontDescriptor m_fontDescriptor; + +private: + /// Tries to read font descriptor from the object + /// \param fontDescriptorObject Font descriptor dictionary + /// \param document Document + static FontDescriptor readFontDescriptor(const PDFObject& fontDescriptorObject, const PDFDocument* document); +}; + +/// Simple font, see PDF reference 1.7, chapter 5.5. Simple fonts have encoding table, +/// which maps single-byte character to the glyph in the font. +class PDFSimpleFont : public PDFFont +{ + using BaseClass = PDFFont; + +public: + explicit PDFSimpleFont(FontDescriptor fontDescriptor, + QByteArray name, + QByteArray baseFont, + PDFInteger firstChar, + PDFInteger lastChar, + std::vector widths, + PDFEncoding::Encoding encodingType, + encoding::EncodingTable encoding, + GlyphIndices glyphIndices); + virtual ~PDFSimpleFont() override = default; + + const PDFEncoding::Encoding getEncodingType() const { return m_encodingType; } + const encoding::EncodingTable* getEncoding() const { return &m_encoding; } + const GlyphIndices* getGlyphIndices() const { return &m_glyphIndices; } + + /// Returns the glyph advance (or zero, if glyph advance is invalid) + PDFInteger getGlyphAdvance(size_t index) const; + + virtual void dumpFontToTreeItem(QTreeWidgetItem* item) const override; + +protected: + QByteArray m_name; + QByteArray m_baseFont; + PDFInteger m_firstChar; + PDFInteger m_lastChar; + std::vector m_widths; + PDFEncoding::Encoding m_encodingType; + encoding::EncodingTable m_encoding; + GlyphIndices m_glyphIndices; +}; + +class PDFType1Font : public PDFSimpleFont +{ + using BaseClass = PDFSimpleFont; + +public: + explicit PDFType1Font(FontType fontType, + FontDescriptor fontDescriptor, + QByteArray name, + QByteArray baseFont, + PDFInteger firstChar, + PDFInteger lastChar, + std::vector widths, + PDFEncoding::Encoding encodingType, + encoding::EncodingTable encoding, + StandardFontType standardFontType, + GlyphIndices glyphIndices); + virtual ~PDFType1Font() override = default; + + virtual FontType getFontType() const override; + virtual void dumpFontToTreeItem(QTreeWidgetItem*item) const override; + + /// Returns the assigned standard font (or invalid, if font is not standard) + StandardFontType getStandardFontType() const { return m_standardFontType; } + +private: + FontType m_fontType; + StandardFontType m_standardFontType; ///< Type of the standard font (or invalid, if it is not a standard font) +}; + +class PDFTrueTypeFont : public PDFSimpleFont +{ +public: + using PDFSimpleFont::PDFSimpleFont; + + virtual FontType getFontType() const override; +}; + +/// Font cache which caches both fonts, and realized fonts. Cache has individual limit +/// for fonts, and realized fonts. +class PDF4QTLIBSHARED_EXPORT PDFFontCache +{ +public: + inline explicit PDFFontCache(size_t fontCacheLimit, size_t realizedFontCacheLimit) : + m_fontCacheLimit(fontCacheLimit), + m_realizedFontCacheLimit(realizedFontCacheLimit), + m_document(nullptr) + { + + } + + /// Sets the document to the cache. Whole cache is cleared, + /// if it is needed. + /// \param document Document to be setted + void setDocument(const PDFModifiedDocument& document); + + /// Retrieves font from the cache. If font can't be accessed or created, + /// then exception is thrown. + /// \param fontObject Font object + PDFFontPointer getFont(const PDFObject& fontObject) const; + + /// Retrieves realized font from the cache. If realized font can't be accessed or created, + /// then exception is thrown. + /// \param font Font, which should be realized + /// \param size Size of the font (in pixels) + /// \param reporter Error reporter + PDFRealizedFontPointer getRealizedFont(const PDFFontPointer& font, PDFReal size, PDFRenderErrorReporter* reporter) const; + + /// Sets or unsets font shrinking (i.e. font can be deleted from the cache). In multithreading environment, + /// font deletion is not thread safe. For this reason, disable font deletion by calling this function. + /// First parameter, \p source determines which object enables cache shrinking (so some objects can + /// enable shrinking, while some objects will disable it). Only if all objects enables cache shrinking, + /// then cache can shrink. + /// \param source Source object + /// \param enabled Enable or disable cache shrinking + void setCacheShrinkEnabled(const void* source, bool enabled); + + /// Set font cache limits + void setCacheLimits(int fontCacheLimit, int instancedFontCacheLimit); + + /// If shrinking is enabled, then erase font, if cache limit is exceeded. + void shrink(); + +private: + size_t m_fontCacheLimit; + size_t m_realizedFontCacheLimit; + mutable QMutex m_mutex; + const PDFDocument* m_document; + mutable std::map m_fontCache; + mutable std::map, PDFRealizedFontPointer> m_realizedFontCache; + mutable std::set m_fontCacheShrinkDisabledObjects; +}; + +/// Performs mapping from CID to GID (even identity mapping, if byte array is empty) +class PDFCIDtoGIDMapper +{ +public: + explicit inline PDFCIDtoGIDMapper(QByteArray&& mapping) : m_mapping(qMove(mapping)) { } + + /// Maps CID to GID (glyph identifier) + GID map(CID cid) const + { + if (m_mapping.isEmpty()) + { + // This means identity mapping + return cid; + } + else if ((2 * cid + 1) < CID(m_mapping.size())) + { + return (GID(m_mapping[2 * cid]) << 8) + GID(m_mapping[2 * cid + 1]); + } + + // This should occur only in case of bad (damaged) PDF file - because in this case, + // encoding is missing. Return invalid glyph index. + return 0; + } + + /// Maps GID to CID (inverse mapping) + CID unmap(GID gid) const + { + if (m_mapping.isEmpty()) + { + // This means identity mapping + return gid; + } + else + { + CID lastCid = CID(m_mapping.size() / 2); + for (CID i = 0; i < lastCid; ++i) + { + if (map(i) == gid) + { + return i; + } + } + } + + // This should occur only in case of bad (damaged) PDF file - because in this case, + // encoding is missing. Return invalid character index. + return 0; + } + +private: + QByteArray m_mapping; +}; + +/// Represents a font CMAP (mapping of CIDs) +class PDF4QTLIBSHARED_EXPORT PDFFontCMap +{ +public: + explicit PDFFontCMap() = default; + + /// Returns true, if mapping is valid + bool isValid() const { return !m_entries.empty(); } + + /// Creates mapping from name (name must be one of predefined names) + static PDFFontCMap createFromName(const QByteArray& name); + + /// Creates mapping from data (data must be a byte array containing the CMap) + static PDFFontCMap createFromData(const QByteArray& data); + + /// Serializes the CMap to the byte array + QByteArray serialize() const; + + /// Deserializes the CMap from the byte array + static PDFFontCMap deserialize(const QByteArray& byteArray); + + /// Converts byte array to array of CIDs + std::vector interpret(const QByteArray& byteArray) const; + + /// Converts CID to QChar, use only on ToUnicode CMaps + QChar getToUnicode(CID cid) const; + +private: + + struct Entry + { + constexpr explicit inline Entry() = default; + constexpr explicit inline Entry(unsigned int from, unsigned int to, unsigned int byteCount, CID cid) : from(from), to(to), byteCount(byteCount), cid(cid) { } + + unsigned int from = 0; + unsigned int to = 0; + unsigned int byteCount = 0; + CID cid = 0; + + // Can merge from other CID entry? + bool canMerge(const Entry& other) const + { + const bool sameBytes = byteCount == other.byteCount; + const bool compatibleRange = (to + 1) == other.from; + const bool compatibleCID = (cid + to + 1) - from == other.cid; + return sameBytes && compatibleRange && compatibleCID; + } + + inline constexpr Entry merge(const Entry& other) const + { + return Entry(from, other.to, byteCount, cid); + } + + inline constexpr bool operator<(const Entry& other) const + { + return std::tie(byteCount, from) < std::tie(other.byteCount, other.from); + } + }; + + using Entries = std::vector; + + explicit PDFFontCMap(Entries&& entries, bool vertical); + + /// Optimizes the entries - merges entries, which can be merged. This function + /// requires, that entries are sorted. + static Entries optimize(const Entries& entries); + + Entries m_entries; + unsigned int m_maxKeyLength = 0; + bool m_vertical = false; +}; + +class PDFType3Font : public PDFFont +{ +public: + explicit PDFType3Font(FontDescriptor fontDescriptor, + int firstCharacterIndex, + int lastCharacterIndex, + QMatrix fontMatrix, + std::map&& characterContentStreams, + std::vector&& widths, + const PDFObject& resources, + PDFFontCMap toUnicode); + + virtual FontType getFontType() const override; + virtual void dumpFontToTreeItem(QTreeWidgetItem*item) const override; + virtual const PDFFontCMap* getToUnicode() const override { return &m_toUnicode; } + + /// Returns width of the character. If character doesn't exist, then zero is returned. + double getWidth(int characterIndex) const; + + /// Return content stream for the character. If character doesn't exist, then nullptr + /// is returned. + const QByteArray* getContentStream(int characterIndex) const; + + const QMatrix& getFontMatrix() const { return m_fontMatrix; } + const PDFObject& getResources() const { return m_resources; } + const std::map& getContentStreams() const { return m_characterContentStreams; } + + /// Returns unicode character for given character index. If unicode mapping is not + /// present, empty (null) character is returned. + QChar getUnicode(int characterIndex) const { return m_toUnicode.getToUnicode(characterIndex); } + +private: + int m_firstCharacterIndex; + int m_lastCharacterIndex; + QMatrix m_fontMatrix; + std::map m_characterContentStreams; + std::vector m_widths; + PDFObject m_resources; + PDFFontCMap m_toUnicode; +}; + +/// Composite font (CID-keyed font) +class PDFType0Font : public PDFFont +{ +public: + explicit inline PDFType0Font(FontDescriptor fontDescriptor, PDFFontCMap cmap, PDFFontCMap toUnicode, PDFCIDtoGIDMapper mapper, PDFReal defaultAdvance, std::unordered_map advances) : + PDFFont(qMove(fontDescriptor)), + m_cmap(qMove(cmap)), + m_toUnicode(qMove(toUnicode)), + m_mapper(qMove(mapper)), + m_defaultAdvance(defaultAdvance), + m_advances(qMove(advances)) + { + + } + + virtual ~PDFType0Font() = default; + + virtual FontType getFontType() const override { return FontType::Type0; } + virtual const PDFFontCMap* getToUnicode() const override { return &m_toUnicode; } + + const PDFFontCMap* getCMap() const { return &m_cmap; } + const PDFCIDtoGIDMapper* getCIDtoGIDMapper() const { return &m_mapper; } + + /// Returns the glyph advance, if it can be obtained, or zero, if it cannot + /// be obtained or error occurs. + /// \param cid CID of the glyph + PDFReal getGlyphAdvance(CID cid) const; + +private: + PDFFontCMap m_cmap; + PDFFontCMap m_toUnicode; + PDFCIDtoGIDMapper m_mapper; + PDFReal m_defaultAdvance; + std::unordered_map m_advances; +}; + +/// Repository with predefined CMaps +class PDF4QTLIBSHARED_EXPORT PDFFontCMapRepository +{ +public: + /// Returns instance of CMAP repository + static PDFFontCMapRepository* getInstance(); + + /// Adds CMAP to the repository + void add(const QByteArray& key, QByteArray value) { m_cmaps[key] = qMove(value); } + + /// Clears the repository + void clear() { m_cmaps.clear(); } + + /// Saves the repository content to the file + void saveToFile(const QString& fileName) const; + + /// Loads the repository content from the file + bool loadFromFile(const QString& fileName); + +private: + explicit PDFFontCMapRepository(); + + /// Storage for predefined cmaps + std::map m_cmaps; +}; + +} // namespace pdf + +#endif // PDFFONT_H diff --git a/Pdf4QtLib/sources/pdfform.cpp b/Pdf4QtLib/sources/pdfform.cpp index 5e0e472..ef9a604 100644 --- a/Pdf4QtLib/sources/pdfform.cpp +++ b/Pdf4QtLib/sources/pdfform.cpp @@ -1,4180 +1,4180 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#include "pdfform.h" -#include "pdfdocument.h" -#include "pdfdrawspacecontroller.h" -#include "pdfdrawwidget.h" -#include "pdfdocumentbuilder.h" -#include "pdfpainterutils.h" - -#include -#include -#include -#include -#include -#include - -namespace pdf -{ - -/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline. -/// Passwords can also be edited and editor can be read only. -class PDFTextEditPseudowidget -{ -public: - explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags); - - void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); - void keyPressEvent(QWidget* widget, QKeyEvent* event); - - inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } - inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); } - inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); } - inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); } - inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); } - - inline bool isEmpty() const { return m_editText.isEmpty(); } - inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; } - inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); } - - inline int getTextLength() const { return m_editText.length(); } - inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; } - - inline int getPositionCursor() const { return m_positionCursor; } - inline int getPositionStart() const { return 0; } - inline int getPositionEnd() const { return getTextLength(); } - - inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; } - - inline const QString& getText() const { return m_editText; } - inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); } - - /// Sets (updates) text selection - /// \param startPosition From where we are selecting text - /// \param selectionLength Selection length (positive - to the right, negative - to the left) - void setSelection(int startPosition, int selectionLength); - - /// Moves cursor position. It behaves as usual in text boxes, - /// when user moves the cursor. If \p select is true, then - /// selection is updated. - /// \param position New position of the cursor - /// \param select Select text when moving the cursor? - void setCursorPosition(int position, bool select); - - /// Sets text content of the widget. This functions sets the text, - /// even if widget is readonly. - /// \param text Text to be set - void setText(const QString& text); - - /// Sets widget appearance, such as font, font size, color, text alignment, - /// and rectangle, in which widget resides on page (in page coordinates) - /// \param appearance Appearance - /// \param textAlignment Text alignment - /// \param rect Widget rectangle in page coordinates - /// \param maxTextLength Maximal text length - void setAppearance(const PDFAnnotationDefaultAppearance& appearance, - Qt::Alignment textAlignment, - QRectF rect, - int maxTextLength); - - void performCut(); - void performCopy(); - void performPaste(); - void performClear(); - void performSelectAll(); - void performBackspace(); - void performDelete(); - void performRemoveSelectedText(); - void performInsertText(const QString& text); - - /// Draw text edit using given parameters - /// \param parameters Parameters - void draw(AnnotationDrawParameters& parameters, bool edit) const; - - /// Returns valid cursor position retrieved from position in the widget. - /// \param point Point in page coordinate space - /// \param edit Are we using edit transformations? - /// \returns Cursor position - int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const; - - inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); } - inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); } - inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); } - inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); } - inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); } - inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); } - inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); } - inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); } - inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); } - inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); } - inline int getCursorNextLine() const { return getCurrentLineTextEnd(); } - inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); } - - const QRectF& getWidgetRect() const { return m_widgetRect; } - QFont getFont() const { return m_textLayout.font(); } - QColor getFontColor() const { return m_textColor; } - -private: - /// This function does following things: - /// 1) Clamps edit text to fit maximum length - /// 2) Creates display string from edit string - /// 3) Updates text layout - void updateTextLayout(); - - /// Returns single step forward, which is determined - /// by cursor move style and layout direction. - int getSingleStepForward() const; - - /// Returns single step backward, which is determined - /// by cursor move style and layout direction. - int getSingleStepBackward() const { return -getSingleStepForward(); } - - /// Returns next/previous position, by number of steps, - /// using given cursor mode (skipping characters or whole words). - /// \param steps Number of steps to proceed (can be negative number) - /// \param mode Skip mode - letters or words? - int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); } - - /// Returns next/previous position from reference cursor position, by number of steps, - /// using given cursor mode (skipping characters or whole words). - /// \param referencePosition Reference cursor position - /// \param steps Number of steps to proceed (can be negative number) - /// \param mode Skip mode - letters or words? - int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const; - - /// Returns current line text start position - int getCurrentLineTextStart() const; - - /// Returns current line text end position - int getCurrentLineTextEnd() const; - - /// Creates text box transform matrix, which transforms from - /// widget space to page space. - /// \param edit Create matrix for text editing? - QMatrix createTextBoxTransformMatrix(bool edit) const; - - /// Returns vector of cursor positions (which may be not equal - /// to edit string length, because edit string can contain surrogates, - /// or graphemes, which are single glyphs, but represented by more - /// 16-bit unicode codes). - std::vector getCursorPositions() const; - - int getCursorLineUp() const; - int getCursorLineDown() const; - - PDFFormField::FieldFlags m_flags; - - /// Text edited by the user - QString m_editText; - - /// Text, which is displayed. It can differ from text - /// edited by user, in case password is being entered. - QString m_displayText; - - /// Text layout - QTextLayout m_textLayout; - - /// Character for displaying passwords - QChar m_passwordReplacementCharacter; - - int m_selectionStart; - int m_selectionEnd; - int m_positionCursor; - int m_maxTextLength; - - QRectF m_widgetRect; - QColor m_textColor; -}; - -/// Editor for button-like editors -class PDFFormFieldAbstractButtonEditor : public PDFFormFieldWidgetEditor -{ -private: - using BaseClass = PDFFormFieldWidgetEditor; - -public: - explicit PDFFormFieldAbstractButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget); - virtual ~PDFFormFieldAbstractButtonEditor() = default; - - virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - -protected: - virtual void click() = 0; -}; - -/// Editor for push buttons -class PDFFormFieldPushButtonEditor : public PDFFormFieldAbstractButtonEditor -{ -private: - using BaseClass = PDFFormFieldAbstractButtonEditor; - -public: - explicit PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget); - virtual ~PDFFormFieldPushButtonEditor() = default; - -protected: - virtual void click() override; -}; - -/// Editor for check boxes or radio buttons -class PDFFormFieldCheckableButtonEditor : public PDFFormFieldAbstractButtonEditor -{ -private: - using BaseClass = PDFFormFieldAbstractButtonEditor; - -public: - explicit PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget); - virtual ~PDFFormFieldCheckableButtonEditor() = default; - -protected: - virtual void click() override; -}; - -/// Editor for text fields -class PDFFormFieldTextBoxEditor : public PDFFormFieldWidgetEditor -{ -private: - using BaseClass = PDFFormFieldWidgetEditor; - -public: - explicit PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget); - virtual ~PDFFormFieldTextBoxEditor() = default; - - virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void reloadValue() override; - virtual bool isEditorDrawEnabled() const override { return m_hasFocus; } - virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override; - - /// Initializes text edit using actual form field value, - /// font, color for text edit appearance. - /// \param textEdit Text editor - void initializeTextEdit(PDFTextEditPseudowidget* textEdit) const; - -protected: - virtual void setFocusImpl(bool focused) override; - -private: - PDFTextEditPseudowidget m_textEdit; -}; - -/// "Pseudo" widget, which is emulating list box. It can contain scrollbar. -class PDFListBoxPseudowidget -{ -public: - explicit PDFListBoxPseudowidget(PDFFormField::FieldFlags flags); - - using Options = PDFFormFieldChoice::Options; - - inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } - inline bool isMultiSelect() const { return m_flags.testFlag(PDFFormField::MultiSelect); } - inline bool isCommitOnSelectionChange() const { return m_flags.testFlag(PDFFormField::CommitOnSelectionChange); } - - void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); - void keyPressEvent(QWidget* widget, QKeyEvent* event); - - /// Sets widget appearance, such as font, font size, color, text alignment, - /// and rectangle, in which widget resides on page (in page coordinates) - /// \param appearance Appearance - /// \param textAlignment Text alignment - /// \param rect Widget rectangle in page coordinates - /// \param options Options selectable by list box - /// \param topIndex Index of first visible item - void setAppearance(const PDFAnnotationDefaultAppearance& appearance, - Qt::Alignment textAlignment, - QRectF rect, - const Options& options, - int topIndex, - std::set selection); - - /// Draw text edit using given parameters - /// \param parameters Parameters - void draw(AnnotationDrawParameters& parameters, bool edit) const; - - /// Sets current selection. If commit on selection change flag - /// is set, then contents of the widgets are commited. New selection - /// is not set, if field is readonly and \p force is false - /// \param selection New selection - /// \param force Set selection even if readonly - void setSelection(std::set selection, bool force); - - /// Returns active item selection - const std::set& getSelection() const { return m_selection; } - - /// Returns top index - int getTopItemIndex() const { return m_topIndex; } - - /// Set top item index - /// \param index Top item index - void setTopItemIndex(int index); - - /// Scrolls the list box in such a way, that index is visible - /// \param index Index to scroll to - void scrollTo(int index); - - /// Returns valid index from index (i.e. index which ranges from first - /// row to the last one). If index itself is valid, then it is returned. - /// \param index Index, from which we want to get valid one - int getValidIndex(int index) const; - - /// Returns true, if index is valid - bool isValidIndex(int index) const { return index == getValidIndex(index); } - - /// Returns number of rows in the viewport - int getViewportRowCount() const { return qFloor(m_widgetRect.height() / m_lineSpacing); } - - /// Returns item index from widget position (i.e. transforms - /// point in the widget to the index of item, which is under the point) - /// \param point Widget point - int getIndexFromWidgetPosition(const QPointF& point) const; - - /// Sets current item and updates selection based on keyboard modifiers - /// \param index New index - /// \param modifiers Keyboard modifiers - void setCurrentItem(int index, Qt::KeyboardModifiers modifiers); - - /// Returns text of selected item text. If no item is selected, - /// or multiple items are selected, then empty string is returned. - /// \returns Selected text - QString getSelectedItemText() const; - - /// Returns true, if single item is selected - bool isSingleItemSelected() const { return m_selection.size() == 1; } - - /// Returns index of option, in the list box option list. - /// If option is not found, then -1 is returned. - /// \param option Option - int findOption(const QString& option) const; - - /// Sets widget appearance, such as font, font size, color, text alignment, - /// and rectangle, in which widget resides on page (in page coordinates) - /// \param font Font - /// \param fontColor Font color - /// \param textAlignment Text alignment - /// \param rect Widget rectangle in page coordinates - /// \param options Options selectable by list box - /// \param topIndex Index of first visible item - void initialize(QFont font, QColor fontColor, Qt::Alignment textAlignment, QRectF rect, - const Options& options, int topIndex, std::set selection); - -private: - int getStartItemIndex() const { return 0; } - int getEndItemIndex() const { return m_options.empty() ? 0 : int(m_options.size() - 1); } - - int getSingleStep() const { return 1; } - int getPageStep() const { return qMax(getViewportRowCount() - 1, getSingleStep()); } - - /// Returns true, if row with this index is visible in the widget - /// (it is in viewport). - /// \param index Index - bool isVisible(int index) const; - - /// Moves current item by offset (negative is up, positive is down) - /// \param offset Offset - /// \param modifiers Keyboard modifiers - void moveCurrentItemIndexByOffset(int offset, Qt::KeyboardModifiers modifiers) { setCurrentItem(m_currentIndex + offset, modifiers); } - - /// Returns true, if list box has continuous selection - bool hasContinuousSelection() const; - - /// Creates transformation matrix, which transforms widget coordinates to page coordinates - QMatrix createListBoxTransformMatrix() const; - - PDFFormField::FieldFlags m_flags; - - Options m_options; - Qt::Alignment m_textAlignment = 0; - int m_topIndex = 0; - int m_currentIndex = 0; - std::set m_selection; - QFont m_font; - PDFReal m_lineSpacing = 0.0; - QRectF m_widgetRect; - QColor m_textColor; -}; - -/// Editor for combo boxes -class PDFFormFieldComboBoxEditor : public PDFFormFieldWidgetEditor -{ -private: - using BaseClass = PDFFormFieldWidgetEditor; - -public: - explicit PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget); - virtual ~PDFFormFieldComboBoxEditor() = default; - - virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) override; - virtual void reloadValue() override; - virtual bool isEditorDrawEnabled() const override { return m_hasFocus; } - virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override; - virtual QRectF getActiveEditorRectangle() const override; - - /// Initializes text edit using actual form field value, - /// font, color for text edit appearance. - /// \param textEdit Text editor - void initializeTextEdit(PDFTextEditPseudowidget* textEdit) const; - - /// Initializes list box using actual form field font, color for text edit appearance. - /// This listbox is used when combo box is in "down" mode, displaying options. - /// \param listBox List box - void initializeListBox(PDFListBoxPseudowidget* listBox) const; - -protected: - virtual void setFocusImpl(bool focused) override; - -private: - static PDFFormField::FieldFlags getTextEditFlags(PDFFormField::FieldFlags flags); - - /// Updates list box selection based on current text. If current text - /// corresponds to some item in the list box, then item is selected - /// and scrolled to. If text is not found in the text box, then - /// selection is cleared. - void updateListBoxSelection(); - - PDFTextEditPseudowidget m_textEdit; - PDFListBoxPseudowidget m_listBox; - QRectF m_listBoxPopupRectangle; - QRectF m_dropDownButtonRectangle; - bool m_listBoxVisible; -}; - -/// Editor for list boxes -class PDFFormFieldListBoxEditor : public PDFFormFieldWidgetEditor -{ -private: - using BaseClass = PDFFormFieldWidgetEditor; - -public: - explicit PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget); - virtual ~PDFFormFieldListBoxEditor() = default; - - virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; - virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; - virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; - virtual void wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) override; - virtual void reloadValue() override; - virtual bool isEditorDrawEnabled() const override { return m_hasFocus; } - virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override; - - /// Initializes list box using actual form field value, - /// font, color for text edit appearance. - /// \param listBox List box - void initializeListBox(PDFListBoxPseudowidget* listBox) const; - -protected: - virtual void setFocusImpl(bool focused) override; - -private: - /// Returns list of selected items parsed from the object. - /// If object is invalid, empty selection is returned. Indices are - /// primarily read from \p indices object, if it fails, indices - /// are being read from \p value object. - /// \param value Selection (string or array of strings) - /// \param indices Selected indices - std::set getSelectedItems(PDFObject value, PDFObject indices) const; - - /// Commits data to the PDF document - void commit(); - - PDFListBoxPseudowidget m_listBox; -}; - -PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object) -{ - PDFForm form; - - if (const PDFDictionary* formDictionary = document->getDictionaryFromObject(object)) - { - PDFDocumentDataLoaderDecorator loader(document); - - std::vector fieldRoots = loader.readReferenceArrayFromDictionary(formDictionary, "Fields"); - form.m_formFields.reserve(fieldRoots.size()); - for (const PDFObjectReference& fieldRootReference : fieldRoots) - { - form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), fieldRootReference, nullptr)); - } - - form.m_formType = FormType::AcroForm; - form.m_needAppearances = loader.readBooleanFromDictionary(formDictionary, "NeedAppearances", false); - form.m_signatureFlags = static_cast(loader.readIntegerFromDictionary(formDictionary, "SigFlags", 0)); - form.m_calculationOrder = loader.readReferenceArrayFromDictionary(formDictionary, "CO"); - form.m_resources = formDictionary->get("DR"); - form.m_defaultAppearance = loader.readOptionalStringFromDictionary(formDictionary, "DA"); - form.m_quadding = loader.readOptionalIntegerFromDictionary(formDictionary, "Q"); - form.m_xfa = formDictionary->get("XFA"); - - if (!form.m_xfa.isNull()) - { - // Jakub Melka: handle XFA form - form.m_formType = FormType::XFAForm; - } - - // As post-processing, delete all form fields, which are nullptr (are incorrectly defined) - form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end()); - form.updateWidgetToFormFieldMapping(); - - // If we have form, then we must also look for 'rogue' form fields, which are - // incorrectly not in the 'Fields' entry of this form. We do this by iterating - // all pages, and their annotations and try to find these 'rogue' fields. - bool rogueFieldFound = false; - const size_t pageCount = document->getCatalog()->getPageCount(); - for (size_t i = 0; i < pageCount; ++i) - { - const PDFPage* page = document->getCatalog()->getPage(i); - for (PDFObjectReference annotationReference : page->getAnnotations()) - { - const PDFDictionary* annotationDictionary = document->getDictionaryFromObject(document->getObjectByReference(annotationReference)); - - if (form.m_widgetToFormField.count(annotationReference)) - { - // This widget/form field is already present - continue; - } - - if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget" && - !annotationDictionary->hasKey("Kids")) - { - rogueFieldFound = true; - form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), annotationReference, nullptr)); - } - } - } - - // As post-processing, delete all form fields, which are nullptr (are incorrectly defined) - form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end()); - - if (rogueFieldFound) - { - form.updateWidgetToFormFieldMapping(); - } - } - - return form; -} - -void PDFForm::updateWidgetToFormFieldMapping() -{ - m_widgetToFormField.clear(); - - if (isAcroForm() || isXFAForm()) - { - for (const PDFFormFieldPointer& formFieldPtr : getFormFields()) - { - formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField); - } - } -} - -Qt::Alignment PDFForm::getDefaultAlignment() const -{ - Qt::Alignment alignment = Qt::AlignVCenter; - - switch (getQuadding().value_or(0)) - { - default: - case 0: - alignment |= Qt::AlignLeft; - break; - - case 1: - alignment |= Qt::AlignHCenter; - break; - - case 2: - alignment |= Qt::AlignRight; - break; - } - - return alignment; -} - -const PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) const -{ - auto it = m_widgetToFormField.find(widget); - if (it != m_widgetToFormField.cend()) - { - return it->second; - } - - return nullptr; -} - -PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) -{ - auto it = m_widgetToFormField.find(widget); - if (it != m_widgetToFormField.cend()) - { - return it->second; - } - - return nullptr; -} - -void PDFForm::apply(const std::function& functor) const -{ - for (const PDFFormFieldPointer& childField : getFormFields()) - { - childField->apply(functor); - } -} - -void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping) -{ - for (const auto& childField : m_childFields) - { - childField->fillWidgetToFormFieldMapping(mapping); - } - - for (const PDFFormWidget& formWidget : m_widgets) - { - mapping[formWidget.getWidget()] = formWidget.getParent(); - } -} - -void PDFFormField::reloadValue(const PDFObjectStorage* storage, PDFObject parentValue) -{ - Q_ASSERT(storage); - - if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(getSelfReference()))) - { - m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentValue; - } - - for (const PDFFormFieldPointer& childField : m_childFields) - { - childField->reloadValue(storage, m_value); - } -} - -void PDFFormField::apply(const std::function& functor) const -{ - functor(this); - - for (const PDFFormFieldPointer& childField : m_childFields) - { - childField->apply(functor); - } -} - -void PDFFormField::modify(const std::function& functor) -{ - functor(this); - - for (const PDFFormFieldPointer& childField : m_childFields) - { - childField->modify(functor); - } -} - -PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) -{ - PDFFormFieldPointer result; - - if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference))) - { - PDFFormField* formField = nullptr; - PDFFormFieldButton* formFieldButton = nullptr; - PDFFormFieldText* formFieldText = nullptr; - PDFFormFieldChoice* formFieldChoice = nullptr; - PDFFormFieldSignature* formFieldSignature = nullptr; - - constexpr const std::array, 4> fieldTypes = { - std::pair{ "Btn", FieldType::Button }, - std::pair{ "Tx", FieldType::Text }, - std::pair{ "Ch", FieldType::Choice }, - std::pair{ "Sig", FieldType::Signature } - }; - - PDFDocumentDataLoaderDecorator loader(storage); - FieldType formFieldType = parentField ? parentField->getFieldType() : FieldType::Invalid; - if (fieldDictionary->hasKey("FT")) - { - formFieldType = loader.readEnumByName(fieldDictionary->get("FT"), fieldTypes.begin(), fieldTypes.end(), FieldType::Invalid); - } - - switch (formFieldType) - { - case FieldType::Invalid: - formField = new PDFFormField(); - break; - - case FieldType::Button: - formFieldButton = new PDFFormFieldButton(); - formField = formFieldButton; - break; - - case FieldType::Text: - formFieldText = new PDFFormFieldText(); - formField = formFieldText; - break; - - case FieldType::Choice: - formFieldChoice = new PDFFormFieldChoice(); - formField = formFieldChoice; - break; - - case FieldType::Signature: - formFieldSignature = new PDFFormFieldSignature(); - formField = formFieldSignature; - break; - - default: - Q_ASSERT(false); - break; - } - result.reset(formField); - - PDFObject parentV = parentField ? parentField->getValue() : PDFObject(); - PDFObject parentDV = parentField ? parentField->getDefaultValue() : PDFObject(); - FieldFlags parentFlags = parentField ? parentField->getFlags() : None; - - formField->m_selfReference = reference; - formField->m_fieldType = formFieldType; - formField->m_parentField = parentField; - formField->m_fieldNames[Partial] = loader.readTextStringFromDictionary(fieldDictionary, "T", QString()); - formField->m_fieldNames[UserCaption] = loader.readTextStringFromDictionary(fieldDictionary, "TU", QString()); - formField->m_fieldNames[Export] = loader.readTextStringFromDictionary(fieldDictionary, "TM", QString()); - formField->m_fieldFlags = fieldDictionary->hasKey("Ff") ? static_cast(loader.readIntegerFromDictionary(fieldDictionary, "Ff", 0)) : parentFlags; - formField->m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentV; - formField->m_defaultValue = fieldDictionary->hasKey("DV") ? fieldDictionary->get("DV") : parentDV; - formField->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, fieldDictionary->get("AA"), fieldDictionary->get("A")); - - // Generate fully qualified name. If partial name is empty, then fully qualified name - // is generated from parent fully qualified name (i.e. it is same as parent's name). - // This is according the PDF specification 1.7. - QStringList names; - if (parentField) - { - names << parentField->getName(FullyQualified); - } - names << formField->m_fieldNames[Partial]; - names.removeAll(QString()); - - formField->m_fieldNames[FullyQualified] = names.join("."); - - std::vector kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids"); - if (kids.empty()) - { - // This means, that field's dictionary is merged with annotation's dictionary, - // so, we will add pointer to self to the form widgets. But we must test, if we - // really have merged annotation's dictionary - we test it by checking for 'Subtype' - // presence. - if (loader.readNameFromDictionary(fieldDictionary, "Subtype") == "Widget") - { - formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, reference, formField)); - } - } - else - { - // Otherwise we must scan all references, and determine, if kid is another form field, - // or it is a widget annotation. Widget annotations has required field 'Subtype', which - // has value 'Widget', form field has required field 'Parent' (for non-root fields). - - for (const PDFObjectReference& kid : kids) - { - if (const PDFDictionary* childDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(kid))) - { - const bool isWidget = loader.readNameFromDictionary(childDictionary, "Subtype") == "Widget"; - const bool isField = loader.readReferenceFromDictionary(childDictionary, "Parent").isValid(); - - if (isField) - { - // This is form field (potentially merged with widget) - formField->m_childFields.emplace_back(PDFFormField::parse(storage, kid, formField)); - } - else if (isWidget) - { - // This is pure widget (with no form field) - formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, kid, formField)); - } - } - } - - } - - if (formFieldButton) - { - formFieldButton->m_options = loader.readTextStringList(fieldDictionary->get("Opt")); - } - - if (formFieldText) - { - PDFInteger maxLengthDefault = 0; - QByteArray defaultAppearance; - Qt::Alignment alignment = 0; - - if (PDFFormFieldText* parentTextField = dynamic_cast(parentField)) - { - maxLengthDefault = parentTextField->getTextMaximalLength(); - defaultAppearance = parentTextField->getDefaultAppearance(); - alignment = parentTextField->getAlignment(); - } - - if (fieldDictionary->hasKey("DA")) - { - defaultAppearance = loader.readStringFromDictionary(fieldDictionary, "DA"); - } - if (fieldDictionary->hasKey("Q")) - { - const PDFInteger quadding = loader.readIntegerFromDictionary(fieldDictionary, "Q", -1); - switch (quadding) - { - case 0: - alignment = Qt::AlignLeft; - break; - - case 1: - alignment = Qt::AlignHCenter; - break; - - case 2: - alignment = Qt::AlignRight; - break; - - default: - break; - } - } - alignment |= Qt::AlignVCenter; - - formFieldText->m_maxLength = loader.readIntegerFromDictionary(fieldDictionary, "MaxLen", maxLengthDefault); - formFieldText->m_defaultAppearance = defaultAppearance; - formFieldText->m_alignment = alignment; - formFieldText->m_defaultStyle = loader.readTextStringFromDictionary(fieldDictionary, "DS", QString()); - formFieldText->m_richTextValue = loader.readTextStringFromDictionary(fieldDictionary, "RV", QString()); - } - - if (formFieldChoice) - { - // Parse options - it is array of options values. Option value can be either a single text - // string, or array of two values - export value and text to be displayed. - PDFObject options = storage->getObject(fieldDictionary->get("Opt")); - if (options.isArray()) - { - const PDFArray* optionsArray = options.getArray(); - formFieldChoice->m_options.reserve(optionsArray->getCount()); - for (size_t i = 0; i < optionsArray->getCount(); ++i) - { - PDFFormFieldChoice::Option option; - - PDFObject optionItemObject = storage->getObject(optionsArray->getItem(i)); - if (optionItemObject.isArray()) - { - QStringList stringList = loader.readTextStringList(optionItemObject); - if (stringList.size() == 2) - { - option.exportString = stringList[0]; - option.userString = stringList[1]; - } - } - else - { - option.userString = loader.readTextString(optionItemObject, QString()); - option.exportString = option.userString; - } - - formFieldChoice->m_options.emplace_back(qMove(option)); - } - } - - formFieldChoice->m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0); - formFieldChoice->m_selection = fieldDictionary->get("I"); - } - - if (formFieldSignature) - { - formFieldSignature->m_signature = PDFSignature::parse(storage, fieldDictionary->get("V")); - } - } - - return result; -} - -bool PDFFormField::setValue(const SetValueParameters& parameters) -{ - Q_UNUSED(parameters); - - // Default behaviour: return false, value cannot be set - return false; -} - -void PDFFormField::resetValue(const ResetValueParameters& parameters) -{ - Q_UNUSED(parameters); - - // Default behaviour: do nothing -} - -PDFFormWidget::PDFFormWidget(PDFObjectReference page, PDFObjectReference widget, PDFFormField* parentField, PDFAnnotationAdditionalActions actions) : - m_page(page), - m_widget(widget), - m_parentField(parentField), - m_actions(qMove(actions)) -{ - -} - -PDFFormWidget PDFFormWidget::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) -{ - PDFObjectReference pageReference; - PDFAnnotationAdditionalActions actions; - if (const PDFDictionary* annotationDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference))) - { - PDFDocumentDataLoaderDecorator loader(storage); - pageReference = loader.readReferenceFromDictionary(annotationDictionary, "P"); - actions = PDFAnnotationAdditionalActions::parse(storage, annotationDictionary->get("AA"), annotationDictionary->get("A")); - } - - return PDFFormWidget(pageReference, reference, parentField, qMove(actions)); -} - -PDFFormFieldButton::ButtonType PDFFormFieldButton::getButtonType() const -{ - if (m_fieldFlags.testFlag(PushButton)) - { - return ButtonType::PushButton; - } - else if (m_fieldFlags.testFlag(Radio)) - { - return ButtonType::RadioButton; - } - - return ButtonType::CheckBox; -} - -QByteArray PDFFormFieldButton::getOnAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget) -{ - Q_ASSERT(formManager); - Q_ASSERT(widget); - - const PDFDocument* document = formManager->getDocument(); - Q_ASSERT(document); - - if (const PDFDictionary* dictionary = document->getDictionaryFromObject(document->getObjectByReference(widget->getWidget()))) - { - PDFAppeareanceStreams streams = PDFAppeareanceStreams::parse(&document->getStorage(), dictionary->get("AP")); - QByteArrayList states = streams.getAppearanceStates(PDFAppeareanceStreams::Appearance::Normal); - - for (const QByteArray& state : states) - { - if (!state.isEmpty() && state != "Off") - { - return state; - } - } - } - - return QByteArray(); -} - -QByteArray PDFFormFieldButton::getOffAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget) -{ - Q_UNUSED(formManager); - Q_UNUSED(widget); - - // 'Off' value is specified by PDF 1.7 specification. It has always value 'Off'. - // 'On' values can have different appearance states. - return "Off"; -} - -bool PDFFormFieldButton::setValue(const SetValueParameters& parameters) -{ - // Do not allow to set value to push buttons - if (getFlags().testFlag(PushButton)) - { - return false; - } - - // If form field is readonly, and scope is user (form field is changed by user, - // not by calculated value), then we must not allow value change. - if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User) - { - return false; - } - - Q_ASSERT(parameters.formManager); - Q_ASSERT(parameters.modifier); - Q_ASSERT(parameters.value.isName()); - - PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); - QByteArray state = parameters.value.getString(); - parameters.modifier->markFormFieldChanged(); - builder->setFormFieldValue(getSelfReference(), parameters.value); - - // Change widget appearance states - const bool isRadio = getFlags().testFlag(Radio); - const bool isRadioInUnison = getFlags().testFlag(RadiosInUnison); - const bool isSameValueForAllWidgets = !isRadio || isRadioInUnison; - const bool isAllowedToCheckAllOff = !getFlags().testFlag(NoToggleToOff); - bool hasWidgets = !m_widgets.empty(); - bool isAnyWidgetToggledOn = false; - - for (const PDFFormWidget& formWidget : getWidgets()) - { - QByteArray onState = PDFFormFieldButton::getOnAppearanceState(parameters.formManager, &formWidget); - - // We set appearance to 'On' if following two conditions both hold: - // 1) State equals to widget's "On" state - // 2) Either we are setting value to invoking widget, or setting of same - // value to other widgets is allowed (it is a checkbox, or radio in unison) - if (state == onState && (isSameValueForAllWidgets || formWidget.getWidget() == parameters.invokingWidget)) - { - isAnyWidgetToggledOn = true; - builder->setAnnotationAppearanceState(formWidget.getWidget(), onState); - } - else - { - QByteArray offState = PDFFormFieldButton::getOffAppearanceState(parameters.formManager, &formWidget); - builder->setAnnotationAppearanceState(formWidget.getWidget(), offState); - } - parameters.modifier->markAnnotationsChanged(); - } - - // We must check, if correct value has been set. If form field has no widgets, - // but has same qualified name, then no check is performed (just form field value is set - // to same value in all form fields with same qualified name, according to the PDF specification. - if (hasWidgets && !isAnyWidgetToggledOn && !isAllowedToCheckAllOff) - { - return false; - } - - return true; -} - -void PDFFormFieldButton::resetValue(const ResetValueParameters& parameters) -{ - // Do not allow to reset value of push buttons - if (getFlags().testFlag(PushButton)) - { - return; - } - - Q_ASSERT(parameters.modifier); - Q_ASSERT(parameters.formManager); - - PDFObject defaultValue = getDefaultValue(); - PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); - parameters.modifier->markFormFieldChanged(); - builder->setFormFieldValue(getSelfReference(), defaultValue); - - PDFDocumentDataLoaderDecorator loader(parameters.formManager->getDocument()); - QByteArray defaultState = loader.readString(defaultValue); - - for (const PDFFormWidget& formWidget : getWidgets()) - { - QByteArray onState = PDFFormFieldButton::getOnAppearanceState(parameters.formManager, &formWidget); - if (defaultState == onState) - { - builder->setAnnotationAppearanceState(formWidget.getWidget(), onState); - } - else - { - QByteArray offState = PDFFormFieldButton::getOffAppearanceState(parameters.formManager, &formWidget); - builder->setAnnotationAppearanceState(formWidget.getWidget(), offState); - } - parameters.modifier->markAnnotationsChanged(); - } -} - -PDFFormManager::PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent) : - BaseClass(parent), - m_proxy(proxy), - m_annotationManager(nullptr), - m_document(nullptr), - m_flags(getDefaultApperanceFlags()), - m_isCommitDisabled(false), - m_focusedEditor(nullptr) -{ - Q_ASSERT(proxy); -} - -PDFFormManager::~PDFFormManager() -{ - clearEditors(); -} - -PDFAnnotationManager* PDFFormManager::getAnnotationManager() const -{ - return m_annotationManager; -} - -void PDFFormManager::setAnnotationManager(PDFAnnotationManager* annotationManager) -{ - m_annotationManager = annotationManager; -} - -const PDFDocument* PDFFormManager::getDocument() const -{ - return m_document; -} - -void PDFFormManager::setDocument(const PDFModifiedDocument& document) -{ - if (m_document != document) - { - PDFTemporaryValueChange change(&m_isCommitDisabled, true); - m_document = document; - - if (document.hasReset()) - { - if (m_document) - { - m_form = PDFForm::parse(m_document, m_document->getCatalog()->getFormObject()); - } - else - { - // Clean the form - m_form = PDFForm(); - } - - updateFormWidgetEditors(); - } - else if (document.hasFlag(PDFModifiedDocument::FormField)) - { - // Just update field values - updateFieldValues(); - } - } -} - -PDFFormManager::FormAppearanceFlags PDFFormManager::getAppearanceFlags() const -{ - return m_flags; -} - -void PDFFormManager::setAppearanceFlags(FormAppearanceFlags flags) -{ - m_flags = flags; -} - -bool PDFFormManager::hasFormFieldWidgetText(PDFObjectReference widgetAnnotation) const -{ - if (const PDFFormField* formField = getFormFieldForWidget(widgetAnnotation)) - { - switch (formField->getFieldType()) - { - case PDFFormField::FieldType::Text: - return true; - - case PDFFormField::FieldType::Choice: - { - PDFFormField::FieldFlags flags = formField->getFlags(); - return flags.testFlag(PDFFormField::Combo) && flags.testFlag(PDFFormField::Edit); - } - - default: - break; - } - } - - return false; -} - -PDFFormWidgets PDFFormManager::getWidgets() const -{ - PDFFormWidgets result; - - auto functor = [&result](const PDFFormField* formField) - { - const PDFFormWidgets& widgets = formField->getWidgets(); - result.insert(result.cend(), widgets.cbegin(), widgets.cend()); - }; - apply(functor); - - return result; -} - -void PDFFormManager::apply(const std::function& functor) const -{ - m_form.apply(functor); -} - -void PDFFormManager::modify(const std::function& functor) const -{ - for (const PDFFormFieldPointer& childField : m_form.getFormFields()) - { - childField->modify(functor); - } -} - -void PDFFormManager::setFocusToEditor(PDFFormFieldWidgetEditor* editor) -{ - if (m_focusedEditor != editor) - { - if (m_focusedEditor) - { - m_focusedEditor->setFocus(false); - } - - m_focusedEditor = editor; - - if (m_focusedEditor) - { - m_focusedEditor->setFocus(true); - } - - // Request repaint, because focus changed - Q_ASSERT(m_proxy); - m_proxy->repaintNeeded(); - } -} - -bool PDFFormManager::focusNextPrevFormField(bool next) -{ - if (m_widgetEditors.empty()) - { - return false; - } - - std::vector::const_iterator newFocusIterator = m_widgetEditors.cend(); - - if (!m_focusedEditor) - { - // We are setting a new focus - if (next) - { - newFocusIterator = m_widgetEditors.cbegin(); - } - else - { - newFocusIterator = std::prev(m_widgetEditors.cend()); - } - } - else - { - std::vector::const_iterator it = std::find(m_widgetEditors.cbegin(), m_widgetEditors.cend(), m_focusedEditor); - Q_ASSERT(it != m_widgetEditors.cend()); - - if (next) - { - newFocusIterator = std::next(it); - } - else if (it != m_widgetEditors.cbegin()) - { - newFocusIterator = std::prev(it); - } - } - - if (newFocusIterator != m_widgetEditors.cend()) - { - setFocusToEditor(*newFocusIterator); - return true; - } - else - { - // Jakub Melka: We must remove focus out of editor, because - setFocusToEditor(nullptr); - } - - return false; -} - -bool PDFFormManager::isFocused(PDFObjectReference widget) const -{ - if (m_focusedEditor) - { - return m_focusedEditor->getWidgetAnnotation() == widget; - } - - return false; -} - -const PDFAction* PDFFormManager::getAction(PDFAnnotationAdditionalActions::Action actionType, const PDFFormWidget* widget) -{ - if (const PDFAction* action = widget->getAction(actionType)) - { - return action; - } - - for (const PDFFormField* formField = widget->getParent(); formField; formField = formField->getParentField()) - { - if (const PDFAction* action = formField->getAction(actionType)) - { - return action; - } - } - - return nullptr; -} - -void PDFFormManager::setFormFieldValue(PDFFormField::SetValueParameters parameters) -{ - if (!m_document) - { - // This can happen, when we are closing the document and some editor is opened - return; - } - - Q_ASSERT(parameters.invokingFormField); - Q_ASSERT(parameters.invokingWidget.isValid()); - - parameters.formManager = this; - parameters.scope = PDFFormField::SetValueParameters::Scope::User; - - PDFDocumentModifier modifier(m_document); - modifier.getBuilder()->setFormManager(this); - parameters.modifier = &modifier; - - if (parameters.invokingFormField->setValue(parameters)) - { - // We must also set dependent fields with same name - QString qualifiedFormFieldName = parameters.invokingFormField->getName(PDFFormField::NameType::FullyQualified); - if (!qualifiedFormFieldName.isEmpty()) - { - parameters.scope = PDFFormField::SetValueParameters::Scope::Internal; - auto updateDependentField = [¶meters, &qualifiedFormFieldName](PDFFormField* formField) - { - if (parameters.invokingFormField == formField) - { - // Do not update self - return; - } - - if (qualifiedFormFieldName == formField->getName(PDFFormField::NameType::FullyQualified)) - { - formField->setValue(parameters); - } - }; - modify(updateDependentField); - } - - if (modifier.finalize()) - { - emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } - } -} - -QRectF PDFFormManager::getWidgetRectangle(const PDFFormWidget& widget) const -{ - if (const PDFDictionary* dictionary = m_document->getDictionaryFromObject(m_document->getObjectByReference(widget.getWidget()))) - { - PDFDocumentDataLoaderDecorator loader(m_document); - return loader.readRectangle(dictionary->get("Rect"), QRectF()); - } - - return QRectF(); -} - - -void PDFFormManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - if (m_focusedEditor) - { - m_focusedEditor->shortcutOverrideEvent(widget, event); - } -} - -void PDFFormManager::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - if (m_focusedEditor) - { - m_focusedEditor->keyPressEvent(widget, event); - } -} - -void PDFFormManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event) -{ - if (m_focusedEditor) - { - m_focusedEditor->keyReleaseEvent(widget, event); - } -} - -void PDFFormManager::mousePressEvent(QWidget* widget, QMouseEvent* event) -{ - if (!hasForm()) - { - return; - } - - MouseEventInfo info = getMouseEventInfo(widget, event->pos()); - if (info.isValid()) - { - Q_ASSERT(info.editor); - - // We try to set focus on editor - if (event->button() == Qt::LeftButton) - { - setFocusToEditor(info.editor); - } - - info.editor->mousePressEvent(widget, event, info.mousePosition); - grabMouse(info, event); - } - else if (!isMouseGrabbed()) - { - // Mouse is not grabbed, user clicked elsewhere, unfocus editor - setFocusToEditor(nullptr); - } -} - -void PDFFormManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) -{ - if (!hasForm()) - { - return; - } - - MouseEventInfo info = getMouseEventInfo(widget, event->pos()); - if (info.isValid()) - { - Q_ASSERT(info.editor); - info.editor->mouseDoubleClickEvent(widget, event, info.mousePosition); - - // If mouse is grabbed, then event is accepted always (because - // we get Press event, when we grabbed the mouse, then we will - // wait for corresponding release event while all mouse move events - // will be accepted, even if editor doesn't accept them. - if (isMouseGrabbed()) - { - event->accept(); - } - } -} - -void PDFFormManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) -{ - if (!hasForm()) - { - return; - } - - MouseEventInfo info = getMouseEventInfo(widget, event->pos()); - if (info.isValid()) - { - Q_ASSERT(info.editor); - info.editor->mouseReleaseEvent(widget, event, info.mousePosition); - ungrabMouse(info, event); - } -} - -void PDFFormManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) -{ - if (!hasForm()) - { - return; - } - - MouseEventInfo info = getMouseEventInfo(widget, event->pos()); - if (info.isValid()) - { - Q_ASSERT(info.editor); - info.editor->mouseMoveEvent(widget, event, info.mousePosition); - - // If mouse is grabbed, then event is accepted always (because - // we get Press event, when we grabbed the mouse, then we will - // wait for corresponding release event while all mouse move events - // will be accepted, even if editor doesn't accept them. - if (isMouseGrabbed()) - { - event->accept(); - } - - if (hasFormFieldWidgetText(info.editor->getWidgetAnnotation())) - { - m_mouseCursor = QCursor(Qt::IBeamCursor); - } - else - { - m_mouseCursor = QCursor(Qt::ArrowCursor); - } - } - else - { - m_mouseCursor = std::nullopt; - } -} - -void PDFFormManager::wheelEvent(QWidget* widget, QWheelEvent* event) -{ - if (!hasForm()) - { - return; - } - - MouseEventInfo info = getMouseEventInfo(widget, event->pos()); - if (info.isValid()) - { - Q_ASSERT(info.editor); - info.editor->wheelEvent(widget, event, info.mousePosition); - - // We will accept mouse wheel events, if we are grabbing the mouse. - // We do not want to zoom in/zoom out while grabbing. - if (isMouseGrabbed()) - { - event->accept(); - } - } -} - -void PDFFormManager::grabMouse(const MouseEventInfo& info, QMouseEvent* event) -{ - if (event->type() == QEvent::MouseButtonDblClick) - { - // Double clicks doesn't grab the mouse - return; - } - - Q_ASSERT(event->type() == QEvent::MouseButtonPress); - - if (isMouseGrabbed()) - { - // If mouse is already grabbed, then when new mouse button is pressed, - // we just increase nesting level and accept the mouse event. We are - // accepting all mouse events, if mouse is grabbed. - ++m_mouseGrabInfo.mouseGrabNesting; - event->accept(); - } - else if (event->isAccepted()) - { - // Event is accepted and we are not grabbing the mouse. We must start - // grabbing the mouse. - Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0); - ++m_mouseGrabInfo.mouseGrabNesting; - m_mouseGrabInfo.info = info; - } -} - -void PDFFormManager::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event) -{ - Q_UNUSED(info); - Q_ASSERT(event->type() == QEvent::MouseButtonRelease); - - if (isMouseGrabbed()) - { - // Mouse is being grabbed, decrease nesting level. We must also accept - // mouse release event, because mouse is being grabbed. - --m_mouseGrabInfo.mouseGrabNesting; - event->accept(); - - if (!isMouseGrabbed()) - { - m_mouseGrabInfo.info = MouseEventInfo(); - } - } - - Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); -} - -PDFFormManager::MouseEventInfo PDFFormManager::getMouseEventInfo(QWidget* widget, QPoint point) -{ - MouseEventInfo result; - - if (isMouseGrabbed()) - { - result = m_mouseGrabInfo.info; - result.mousePosition = result.deviceToWidget.map(point); - return result; - } - - std::vector currentPages = m_proxy->getWidget()->getDrawWidget()->getCurrentPages(); - - if (!m_annotationManager->hasAnyPageAnnotation(currentPages)) - { - // All pages doesn't have annotation - return result; - } - - PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); - for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) - { - const PDFAnnotationManager::PageAnnotations& pageAnnotations = m_annotationManager->getPageAnnotations(snapshotItem.pageIndex); - for (const PDFAnnotationManager::PageAnnotation& pageAnnotation : pageAnnotations.annotations) - { - if (pageAnnotation.annotation->isReplyTo()) - { - // Annotation is reply to another annotation, do not interact with it - continue; - } - - if (pageAnnotation.annotation->getType() != AnnotationType::Widget) - { - // Annotation is not widget annotation (form field), do not interact with it - continue; - } - - if (PDFFormField* formField = getFormFieldForWidget(pageAnnotation.annotation->getSelfReference())) - { - PDFFormFieldWidgetEditor* editor = getEditor(formField); - QRectF annotationRect = editor ? editor->getActiveEditorRectangle() : QRectF(); - if (!annotationRect.isValid()) - { - annotationRect = pageAnnotation.annotation->getRectangle(); - } - QMatrix widgetToDevice = m_annotationManager->prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect); - - QPainterPath path; - path.addRect(annotationRect); - path = widgetToDevice.map(path); - - if (path.contains(point)) - { - result.formField = formField; - result.deviceToWidget = widgetToDevice.inverted(); - result.mousePosition = result.deviceToWidget.map(point); - result.editor = editor; - return result; - } - } - } - } - - return result; -} - -const std::optional& PDFFormManager::getCursor() const -{ - return m_mouseCursor; -} - -void PDFFormManager::clearEditors() -{ - qDeleteAll(m_widgetEditors); - m_widgetEditors.clear(); -} - -void PDFFormManager::updateFormWidgetEditors() -{ - setFocusToEditor(nullptr); - clearEditors(); - - for (PDFFormWidget widget : getWidgets()) - { - const PDFFormField* formField = widget.getParent(); - switch (formField->getFieldType()) - { - case PDFFormField::FieldType::Button: - { - Q_ASSERT(dynamic_cast(formField)); - const PDFFormFieldButton* formFieldButton = static_cast(formField); - switch (formFieldButton->getButtonType()) - { - case PDFFormFieldButton::ButtonType::PushButton: - { - m_widgetEditors.push_back(new PDFFormFieldPushButtonEditor(this, widget)); - break; - } - - case PDFFormFieldButton::ButtonType::RadioButton: - case PDFFormFieldButton::ButtonType::CheckBox: - { - m_widgetEditors.push_back(new PDFFormFieldCheckableButtonEditor(this, widget)); - break; - } - - default: - Q_ASSERT(false); - break; - } - - break; - } - - case PDFFormField::FieldType::Text: - { - m_widgetEditors.push_back(new PDFFormFieldTextBoxEditor(this, widget)); - break; - } - - case PDFFormField::FieldType::Choice: - { - Q_ASSERT(dynamic_cast(formField)); - const PDFFormFieldChoice* formFieldChoice = static_cast(formField); - if (formFieldChoice->isComboBox()) - { - m_widgetEditors.push_back(new PDFFormFieldComboBoxEditor(this, widget)); - } - else if (formFieldChoice->isListBox()) - { - m_widgetEditors.push_back(new PDFFormFieldListBoxEditor(this, widget)); - } - else - { - // Uknown field choice - Q_ASSERT(false); - } - - break; - } - - case PDFFormField::FieldType::Signature: - // Signature fields doesn't have editor - break; - - default: - Q_ASSERT(false); - break; - } - } -} - -void PDFFormManager::updateFieldValues() -{ - if (m_document) - { - for (const PDFFormFieldPointer& childField : m_form.getFormFields()) - { - childField->reloadValue(&m_document->getStorage(), PDFObject()); - } - - for (PDFFormFieldWidgetEditor* editor : m_widgetEditors) - { - editor->reloadValue(); - } - } -} - -PDFFormFieldWidgetEditor* PDFFormManager::getEditor(const PDFFormField* formField) const -{ - for (PDFFormFieldWidgetEditor* editor : m_widgetEditors) - { - if (editor->getFormField() == formField) - { - return editor; - } - } - - return nullptr; -} - -void PDFFormManager::performResetAction(const PDFActionResetForm* action) -{ - Q_ASSERT(action); - Q_ASSERT(m_document); - - PDFDocumentModifier modifier(m_document); - modifier.getBuilder()->setFormManager(this); - - auto resetFieldValue = [this, action, &modifier](PDFFormField* formField) - { - const PDFFormAction::FieldList& fieldList = action->getFieldList(); - - // Akceptujeme form field dle daného filtru? - bool accept = false; - bool isInFieldList = std::find(fieldList.fieldReferences.cbegin(), fieldList.fieldReferences.cend(), formField->getSelfReference()) != fieldList.fieldReferences.cend() || - fieldList.qualifiedNames.contains(formField->getName(PDFFormField::NameType::FullyQualified)); - switch (action->getFieldScope()) - { - case PDFFormAction::FieldScope::All: - accept = true; - break; - - case PDFFormAction::FieldScope::Include: - accept = isInFieldList; - break; - - case PDFFormAction::FieldScope::Exclude: - accept = !isInFieldList; - break; - - default: - Q_ASSERT(false); - break; - } - - if (accept) - { - PDFFormField::ResetValueParameters parameters; - parameters.formManager = this; - parameters.modifier = &modifier; - formField->resetValue(parameters); - } - }; - modify(resetFieldValue); - - if (modifier.finalize()) - { - emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); - } -} - -PDFFormFieldWidgetEditor::PDFFormFieldWidgetEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - m_formManager(formManager), - m_formWidget(formWidget), - m_hasFocus(false) -{ - Q_ASSERT(m_formManager); -} - -void PDFFormFieldWidgetEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFFormFieldWidgetEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFFormFieldWidgetEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - Q_UNUSED(event); -} - -void PDFFormFieldWidgetEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(widget); - Q_UNUSED(event); - Q_UNUSED(mousePagePosition); -} - -void PDFFormFieldWidgetEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(widget); - Q_UNUSED(event); - Q_UNUSED(mousePagePosition); -} - -void PDFFormFieldWidgetEditor::mouseReleaseEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(widget); - Q_UNUSED(event); - Q_UNUSED(mousePagePosition); -} - -void PDFFormFieldWidgetEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(widget); - Q_UNUSED(event); - Q_UNUSED(mousePagePosition); -} - -void PDFFormFieldWidgetEditor::wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(widget); - Q_UNUSED(event); - Q_UNUSED(mousePagePosition); -} - -void PDFFormFieldWidgetEditor::setFocus(bool hasFocus) -{ - if (m_hasFocus != hasFocus) - { - m_hasFocus = hasFocus; - setFocusImpl(m_hasFocus); - } -} - -void PDFFormFieldWidgetEditor::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - Q_UNUSED(parameters); - Q_UNUSED(edit) -} - -void PDFFormFieldWidgetEditor::performKeypadNavigation(QWidget* widget, QKeyEvent* event) -{ - int key = event->key(); - - const bool isLeft = key == Qt::Key_Left; - const bool isRight = key == Qt::Key_Right; - const bool isDown = key == Qt::Key_Down; - const bool isHorizontal = isLeft || isRight; - - Qt::NavigationMode navigationMode = Qt::NavigationModeKeypadDirectional; -#ifdef QT_KEYPAD_NAVIGATION - navigationMode = QApplication::navigationMode(); -#endif - - switch (navigationMode) - { - case Qt::NavigationModeKeypadTabOrder: - { - // According the Qt's documentation, Up/Down arrows are used - // to change focus. So, if user pressed Left/Right, we must - // ignore this event. - if (isHorizontal) - { - return; - } - break; - } - - case Qt::NavigationModeKeypadDirectional: - // Default behaviour - break; - - default: - // Nothing happens - return; - } - - bool next = false; - if (isHorizontal) - { - switch (widget->layoutDirection()) - { - case Qt::LeftToRight: - case Qt::LayoutDirectionAuto: - next = isRight; - break; - - case Qt::RightToLeft: - next = isLeft; - break; - - default: - Q_ASSERT(false); - break; - } - } - else - { - // Vertical - next = isDown; - } - - m_formManager->focusNextPrevFormField(next); -} - -PDFFormFieldPushButtonEditor::PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - BaseClass(formManager, formWidget) -{ - -} - -PDFFormFieldAbstractButtonEditor::PDFFormFieldAbstractButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - BaseClass(formManager, formWidget) -{ - -} - -void PDFFormFieldAbstractButtonEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - switch (event->key()) - { - case Qt::Key_Enter: - case Qt::Key_Return: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Up: - case Qt::Key_Down: - { - event->accept(); - break; - } - - default: - break; - } -} - -void PDFFormFieldAbstractButtonEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - switch (event->key()) - { - case Qt::Key_Enter: - case Qt::Key_Return: - { - click(); - event->accept(); - break; - } - - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Up: - case Qt::Key_Down: - { - performKeypadNavigation(widget, event); - break; - } - - default: - break; - } -} - -void PDFFormFieldAbstractButtonEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - switch (event->key()) - { - case Qt::Key_Select: - case Qt::Key_Space: - { - click(); - event->accept(); - break; - } - - default: - break; - } -} - -void PDFFormFieldAbstractButtonEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(widget); - Q_UNUSED(mousePagePosition); - - if (event->button() == Qt::LeftButton) - { - click(); - event->accept(); - } -} - -void PDFFormFieldPushButtonEditor::click() -{ - if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::MousePressed, getFormWidget())) - { - emit m_formManager->actionTriggered(action); - } - else if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::Default, getFormWidget())) - { - emit m_formManager->actionTriggered(action); - } -} - -PDFFormFieldCheckableButtonEditor::PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - BaseClass(formManager, formWidget) -{ - -} - -void PDFFormFieldCheckableButtonEditor::click() -{ - QByteArray newState; - - // First, check current state of form field - PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); - QByteArray state = loader.readName(m_formWidget.getParent()->getValue()); - QByteArray onState = PDFFormFieldButton::getOnAppearanceState(m_formManager, &m_formWidget); - if (state != onState) - { - newState = onState; - } - else - { - newState = PDFFormFieldButton::getOffAppearanceState(m_formManager, &m_formWidget); - } - - // We have a new state, try to apply it to form field - PDFFormField::SetValueParameters parameters; - parameters.formManager = m_formManager; - parameters.invokingWidget = m_formWidget.getWidget(); - parameters.invokingFormField = m_formWidget.getParent(); - parameters.scope = PDFFormField::SetValueParameters::Scope::User; - parameters.value = PDFObject::createName(qMove(newState)); - m_formManager->setFormFieldValue(parameters); -} - -PDFFormFieldComboBoxEditor::PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - BaseClass(formManager, formWidget), - m_textEdit(getTextEditFlags(formWidget.getParent()->getFlags())), - m_listBox(formWidget.getParent()->getFlags()), - m_listBoxVisible(false) -{ - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - initializeTextEdit(&m_textEdit); - - QFontMetricsF fontMetrics(m_textEdit.getFont()); - const qreal lineSpacing = fontMetrics.lineSpacing(); - - const int listBoxItems = qMin(7, int(parentField->getOptions().size())); - QRectF comboBoxRectangle = m_formManager->getWidgetRectangle(m_formWidget); - QRectF listBoxPopupRectangle = comboBoxRectangle; - listBoxPopupRectangle.translate(0, -lineSpacing * (listBoxItems)); - listBoxPopupRectangle.setHeight(lineSpacing * listBoxItems); - m_listBoxPopupRectangle = listBoxPopupRectangle; - m_dropDownButtonRectangle = comboBoxRectangle; - m_dropDownButtonRectangle.setLeft(m_dropDownButtonRectangle.right() - m_dropDownButtonRectangle.height()); - initializeListBox(&m_listBox); -} - -void PDFFormFieldComboBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - if (!m_hasFocus || !m_listBoxVisible) - { - m_textEdit.shortcutOverrideEvent(widget, event); - } - else - { - m_listBox.shortcutOverrideEvent(widget, event); - } -} - -void PDFFormFieldComboBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_ASSERT(!m_textEdit.isMultiline()); - - if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) - { - // If popup list box is shown, then use text and hide it, - // otherwise close the editor. - if (m_listBoxVisible) - { - if (m_listBox.isSingleItemSelected()) - { - m_textEdit.setText(m_listBox.getSelectedItemText()); - } - m_listBoxVisible = false; - } - else - { - m_formManager->setFocusToEditor(nullptr); - } - - event->accept(); - } - else if (event->key() == Qt::Key_Escape) - { - // If popup list box is shown, hide it, if not, cancel the editing - if (m_listBoxVisible) - { - m_listBoxVisible = false; - } - else - { - reloadValue(); - m_formManager->setFocusToEditor(nullptr); - } - event->accept(); - } - else if (!m_listBoxVisible) - { - m_textEdit.keyPressEvent(widget, event); - } - else - { - m_listBox.keyPressEvent(widget, event); - } - - if (event->isAccepted()) - { - widget->update(); - } -} - -void PDFFormFieldComboBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - if (event->button() == Qt::LeftButton && m_hasFocus) - { - // If popup list box is visible, delegate mouse click to - // this list box only. - if (m_listBoxVisible) - { - const int index = m_listBox.getIndexFromWidgetPosition(mousePagePosition); - m_listBox.setCurrentItem(index, event->modifiers()); - - if (m_listBox.isSingleItemSelected()) - { - m_textEdit.setText(m_listBox.getSelectedItemText()); - } - - m_listBoxVisible = false; - } - else - { - // Do we click popup button? - if (m_dropDownButtonRectangle.contains(mousePagePosition)) - { - m_listBoxVisible = true; - updateListBoxSelection(); - } - else - { - const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); - m_textEdit.setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier); - } - } - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldComboBoxEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - if (event->button() == Qt::LeftButton) - { - const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); - m_textEdit.setCursorPosition(cursorPosition, false); - m_textEdit.setCursorPosition(m_textEdit.getCursorWordBackward(), false); - m_textEdit.setCursorPosition(m_textEdit.getCursorWordForward(), true); - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldComboBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - // We must test, if left mouse button is pressed while - // we are moving the mouse - if yes, then select the text. - if (event->buttons() & Qt::LeftButton && !m_listBoxVisible) - { - const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); - m_textEdit.setCursorPosition(cursorPosition, true); - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldComboBoxEditor::wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(mousePagePosition); - - if (m_listBoxVisible) - { - if (event->angleDelta().y() < 0) - { - m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() + m_listBox.getViewportRowCount())); - } - else - { - m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() - 1)); - } - - widget->update(); - event->accept(); - } -} - -void PDFFormFieldComboBoxEditor::updateListBoxSelection() -{ - QString text = m_textEdit.getText(); - - const int index = m_listBox.findOption(text); - if (m_listBox.isValidIndex(index)) - { - m_listBox.setSelection({ index }, true); - m_listBox.scrollTo(index); - } - else - { - m_listBox.setTopItemIndex(0); - m_listBox.setSelection({ }, true); - } -} - -void PDFFormFieldComboBoxEditor::reloadValue() -{ - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); - m_textEdit.setText(loader.readTextString(m_formWidget.getParent()->getValue(), QString())); - - m_listBoxVisible = false; -} - -void PDFFormFieldComboBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - if (edit) - { - // Draw text edit always - { - PDFPainterStateGuard guard(parameters.painter); - m_textEdit.draw(parameters, true); - } - - // Draw down button - { - PDFPainterStateGuard guard(parameters.painter); - - parameters.painter->translate(m_dropDownButtonRectangle.bottomLeft()); - parameters.painter->scale(1.0, -1.0); - - QStyleOption option; - option.state = QStyle::State_Enabled; - option.rect = QRect(0, 0, qFloor(m_dropDownButtonRectangle.width()), qFloor(m_dropDownButtonRectangle.height())); - QApplication::style()->drawPrimitive(QStyle::PE_IndicatorButtonDropDown, &option, parameters.painter, nullptr); - } - - if (m_listBoxVisible) - { - PDFPainterStateGuard guard(parameters.painter); - - AnnotationDrawParameters listBoxParameters = parameters; - listBoxParameters.boundingRectangle = m_listBoxPopupRectangle; - - QColor color = parameters.invertColors ? Qt::black : Qt::white; - listBoxParameters.painter->fillRect(listBoxParameters.boundingRectangle, color); - - m_listBox.draw(listBoxParameters, true); - } - } - else - { - // Draw static contents - PDFTextEditPseudowidget pseudowidget(m_formWidget.getParent()->getFlags()); - initializeTextEdit(&pseudowidget); - pseudowidget.draw(parameters, false); - } -} - -QRectF PDFFormFieldComboBoxEditor::getActiveEditorRectangle() const -{ - if (m_hasFocus && m_listBoxVisible) - { - return m_textEdit.getWidgetRect().united(m_listBoxPopupRectangle); - } - - return QRectF(); -} - -void PDFFormFieldComboBoxEditor::initializeTextEdit(PDFTextEditPseudowidget* textEdit) const -{ - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); - - QByteArray defaultAppearance = m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray()); - Qt::Alignment alignment = m_formManager->getForm()->getDefaultAlignment(); - - // Initialize text edit - textEdit->setAppearance(PDFAnnotationDefaultAppearance::parse(defaultAppearance), alignment, m_formManager->getWidgetRectangle(m_formWidget), 0); - textEdit->setText(loader.readTextString(parentField->getValue(), QString())); -} - -void PDFFormFieldComboBoxEditor::initializeListBox(PDFListBoxPseudowidget* listBox) const -{ - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - // Initialize popup list box - listBox->initialize(m_textEdit.getFont(), m_textEdit.getFontColor(), m_formManager->getForm()->getDefaultAlignment(), m_listBoxPopupRectangle, parentField->getOptions(), 0, { }); -} - -void PDFFormFieldComboBoxEditor::setFocusImpl(bool focused) -{ - if (focused) - { - m_textEdit.setCursorPosition(m_textEdit.getPositionEnd(), false); - m_textEdit.performSelectAll(); - } - else if (!m_formManager->isCommitDisabled()) - { - // If text has been changed, then commit it - PDFObject object = PDFObjectFactory::createTextString(m_textEdit.getText()); - - if (object != m_formWidget.getParent()->getValue()) - { - PDFFormField::SetValueParameters parameters; - parameters.formManager = m_formManager; - parameters.invokingWidget = m_formWidget.getWidget(); - parameters.invokingFormField = m_formWidget.getParent(); - parameters.scope = PDFFormField::SetValueParameters::Scope::User; - parameters.value = qMove(object); - m_formManager->setFormFieldValue(parameters); - } - } -} - -PDFFormField::FieldFlags PDFFormFieldComboBoxEditor::getTextEditFlags(PDFFormField::FieldFlags flags) -{ - if (flags.testFlag(PDFFormField::ReadOnly) || !flags.testFlag(PDFFormField::Edit)) - { - return PDFFormField::ReadOnly; - } - - return PDFFormField::None; -} - -void PDFFormFieldTextBoxEditor::initializeTextEdit(PDFTextEditPseudowidget* textEdit) const -{ - const PDFFormFieldText* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); - - QByteArray defaultAppearance = parentField->getDefaultAppearance(); - if (defaultAppearance.isEmpty()) - { - defaultAppearance = m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray()); - } - Qt::Alignment alignment = parentField->getAlignment(); - if (!(alignment & Qt::AlignHorizontal_Mask)) - { - alignment |= m_formManager->getForm()->getDefaultAlignment() & Qt::AlignHorizontal_Mask; - } - - // Initialize text edit - textEdit->setAppearance(PDFAnnotationDefaultAppearance::parse(defaultAppearance), alignment, m_formManager->getWidgetRectangle(m_formWidget), parentField->getTextMaximalLength()); - textEdit->setText(loader.readTextString(parentField->getValue(), QString())); -} - -void PDFFormFieldTextBoxEditor::setFocusImpl(bool focused) -{ - if (focused) - { - m_textEdit.setCursorPosition(m_textEdit.getPositionEnd(), false); - m_textEdit.performSelectAll(); - } - else if (!m_textEdit.isPassword() && !m_formManager->isCommitDisabled()) // Passwords are not saved in the document - { - // If text has been changed, then commit it - PDFObject object = PDFObjectFactory::createTextString(m_textEdit.getText()); - - if (object != m_formWidget.getParent()->getValue()) - { - PDFFormField::SetValueParameters parameters; - parameters.formManager = m_formManager; - parameters.invokingWidget = m_formWidget.getWidget(); - parameters.invokingFormField = m_formWidget.getParent(); - parameters.scope = PDFFormField::SetValueParameters::Scope::User; - parameters.value = qMove(object); - m_formManager->setFormFieldValue(parameters); - } - } -} - -PDFFormFieldTextBoxEditor::PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - BaseClass(formManager, formWidget), - m_textEdit(formWidget.getParent()->getFlags()) -{ - initializeTextEdit(&m_textEdit); -} - -void PDFFormFieldTextBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - m_textEdit.shortcutOverrideEvent(widget, event); -} - -void PDFFormFieldTextBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - if (!m_textEdit.isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) - { - // Commit the editor - m_formManager->setFocusToEditor(nullptr); - event->accept(); - return; - } - - if (event->key() == Qt::Key_Escape) - { - // Cancel the editor - reloadValue(); - m_formManager->setFocusToEditor(nullptr); - event->accept(); - return; - } - - m_textEdit.keyPressEvent(widget, event); - - if (event->isAccepted()) - { - widget->update(); - } -} - -void PDFFormFieldTextBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - if (event->button() == Qt::LeftButton) - { - const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); - m_textEdit.setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier); - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldTextBoxEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - if (event->button() == Qt::LeftButton) - { - const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); - m_textEdit.setCursorPosition(cursorPosition, false); - m_textEdit.setCursorPosition(m_textEdit.getCursorWordBackward(), false); - m_textEdit.setCursorPosition(m_textEdit.getCursorWordForward(), true); - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldTextBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - // We must test, if left mouse button is pressed while - // we are moving the mouse - if yes, then select the text. - if (event->buttons() & Qt::LeftButton) - { - const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); - m_textEdit.setCursorPosition(cursorPosition, true); - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldTextBoxEditor::reloadValue() -{ - PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); - m_textEdit.setText(loader.readTextString(m_formWidget.getParent()->getValue(), QString())); -} - -void PDFFormFieldTextBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - if (edit) - { - m_textEdit.draw(parameters, true); - } - else - { - // Draw static contents - PDFTextEditPseudowidget pseudowidget(m_formWidget.getParent()->getFlags()); - initializeTextEdit(&pseudowidget); - pseudowidget.draw(parameters, false); - } -} - -PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) : - m_flags(flags), - m_selectionStart(0), - m_selectionEnd(0), - m_positionCursor(0), - m_maxTextLength(0) -{ - m_textLayout.setCacheEnabled(true); - m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter); -} - -void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste, - QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar, - QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine, - QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine, - QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument, - QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar, - QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine, - QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine, - QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument, - QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord, - QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace }; - - if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) - { - event->accept(); - return; - } - - switch (event->key()) - { - case Qt::Key_Direction_L: - case Qt::Key_Direction_R: - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - event->accept(); - break; - - default: - break; - } - - if (!event->text().isEmpty()) - { - event->accept(); - for (const QChar& character : event->text()) - { - if (!character.isPrint()) - { - event->ignore(); - break; - } - } - } -} - -void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - /* - We will support following key sequences: - Delete - Cut, - Copy, - Paste, - SelectAll, - MoveToNextChar, - MoveToPreviousChar, - MoveToNextWord, - MoveToPreviousWord, - MoveToNextLine, - MoveToPreviousLine, - MoveToStartOfLine, - MoveToEndOfLine, - MoveToStartOfBlock, - MoveToEndOfBlock, - MoveToStartOfDocument, - MoveToEndOfDocument, - SelectNextChar, - SelectPreviousChar, - SelectNextWord, - SelectPreviousWord, - SelectNextLine, - SelectPreviousLine, - SelectStartOfLine, - SelectEndOfLine, - SelectStartOfBlock, - SelectEndOfBlock, - SelectStartOfDocument, - SelectEndOfDocument, - DeleteStartOfWord, - DeleteEndOfWord, - DeleteEndOfLine, - Deselect, - DeleteCompleteLine, - Backspace, - * */ - - event->accept(); - - if (event == QKeySequence::Delete) - { - performDelete(); - } - else if (event == QKeySequence::Cut) - { - performCut(); - } - else if (event == QKeySequence::Copy) - { - performCopy(); - } - else if (event == QKeySequence::Paste) - { - performPaste(); - } - else if (event == QKeySequence::SelectAll) - { - setSelection(0, getTextLength()); - } - else if (event == QKeySequence::MoveToNextChar) - { - setCursorPosition(getCursorCharacterForward(), false); - } - else if (event == QKeySequence::MoveToPreviousChar) - { - setCursorPosition(getCursorCharacterBackward(), false); - } - else if (event == QKeySequence::MoveToNextWord) - { - setCursorPosition(getCursorWordForward(), false); - } - else if (event == QKeySequence::MoveToPreviousWord) - { - setCursorPosition(getCursorWordBackward(), false); - } - else if (event == QKeySequence::MoveToNextLine) - { - setCursorPosition(getCursorLineDown(), false); - } - else if (event == QKeySequence::MoveToPreviousLine) - { - setCursorPosition(getCursorLineUp(), false); - } - else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) - { - setCursorPosition(getCursorLineStart(), false); - } - else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) - { - setCursorPosition(getCursorLineEnd(), false); - } - else if (event == QKeySequence::MoveToStartOfDocument) - { - setCursorPosition(getCursorDocumentStart(), false); - } - else if (event == QKeySequence::MoveToEndOfDocument) - { - setCursorPosition(getCursorDocumentEnd(), false); - } - else if (event == QKeySequence::SelectNextChar) - { - setCursorPosition(getCursorCharacterForward(), true); - } - else if (event == QKeySequence::SelectPreviousChar) - { - setCursorPosition(getCursorCharacterBackward(), true); - } - else if (event == QKeySequence::SelectNextWord) - { - setCursorPosition(getCursorWordForward(), true); - } - else if (event == QKeySequence::SelectPreviousWord) - { - setCursorPosition(getCursorWordBackward(), true); - } - else if (event == QKeySequence::SelectNextLine) - { - setCursorPosition(getCursorLineDown(), true); - } - else if (event == QKeySequence::SelectPreviousLine) - { - setCursorPosition(getCursorLineUp(), true); - } - else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) - { - setCursorPosition(getCursorLineStart(), true); - } - else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) - { - setCursorPosition(getCursorLineEnd(), true); - } - else if (event == QKeySequence::SelectStartOfDocument) - { - setCursorPosition(getCursorDocumentStart(), true); - } - else if (event == QKeySequence::SelectEndOfDocument) - { - setCursorPosition(getCursorDocumentEnd(), true); - } - else if (event == QKeySequence::DeleteStartOfWord) - { - if (!isReadonly()) - { - setCursorPosition(getCursorWordBackward(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::DeleteEndOfWord) - { - if (!isReadonly()) - { - setCursorPosition(getCursorWordForward(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::DeleteEndOfLine) - { - if (!isReadonly()) - { - setCursorPosition(getCursorLineEnd(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::Deselect) - { - clearSelection(); - } - else if (event == QKeySequence::DeleteCompleteLine) - { - if (!isReadonly()) - { - m_selectionStart = getCurrentLineTextStart(); - m_selectionEnd = getCurrentLineTextEnd(); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace) - { - performBackspace(); - } - else if (event->key() == Qt::Key_Direction_L) - { - QTextOption option = m_textLayout.textOption(); - option.setTextDirection(Qt::LeftToRight); - m_textLayout.setTextOption(qMove(option)); - updateTextLayout(); - } - else if (event->key() == Qt::Key_Direction_R) - { - QTextOption option = m_textLayout.textOption(); - option.setTextDirection(Qt::LeftToRight); - m_textLayout.setTextOption(qMove(option)); - updateTextLayout(); - } - else if (event->key() == Qt::Key_Up) - { - setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Down) - { - setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Left) - { - const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward(); - setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Right) - { - const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward(); - setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) - { - performInsertText(QString::fromUtf16(u"\u2028")); - } - else - { - QString text = event->text(); - if (!text.isEmpty()) - { - performInsertText(text); - } - else - { - event->ignore(); - } - } -} - -void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength) -{ - if (selectionLength > 0) - { - // We are selecting to the right - m_selectionStart = startPosition; - m_selectionEnd = qMin(startPosition + selectionLength, getTextLength()); - m_positionCursor = m_selectionEnd; - } - else if (selectionLength < 0) - { - // We are selecting to the left - m_selectionStart = qMax(startPosition + selectionLength, 0); - m_selectionEnd = startPosition; - m_positionCursor = m_selectionStart; - } - else - { - // Clear the selection - m_selectionStart = 0; - m_selectionEnd = 0; - m_positionCursor = startPosition; - } -} - -void PDFTextEditPseudowidget::setCursorPosition(int position, bool select) -{ - if (select) - { - const bool isTextSelected = this->isTextSelected(); - const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor; - const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor; - - // Do we have selected text, and cursor is at the end of selected text? - // In this case, we must preserve start of the selection (we are manipulating - // with the end of selection. - if (isCursorAtEndOfSelection) - { - m_selectionStart = qMin(m_selectionStart, position); - m_selectionEnd = qMax(m_selectionStart, position); - } - else if (isCursorAtStartOfSelection) - { - // We must preserve end of the text selection, because we are manipulating - // with start of text selection. - m_selectionStart = qMin(m_selectionEnd, position); - m_selectionEnd = qMax(m_selectionEnd, position); - } - else - { - // Otherwise we are manipulating with cursor - m_selectionStart = qMin(m_positionCursor, position); - m_selectionEnd = qMax(m_positionCursor, position); - } - } - - // Why we are clearing text selection, even if we doesn't have it? - // We can have, for example m_selectionStart == m_selectionEnd == 3, - // and we want to have it both zero. - if (!select || !isTextSelected()) - { - clearSelection(); - } - - m_positionCursor = position; -} - -void PDFTextEditPseudowidget::setText(const QString& text) -{ - clearSelection(); - m_editText = text; - setCursorPosition(getPositionEnd(), false); - updateTextLayout(); -} - -void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength) -{ - // Set appearance - qreal fontSize = appearance.getFontSize(); - if (qFuzzyIsNull(fontSize)) - { - fontSize = rect.height(); - } - - QFont font(appearance.getFontName()); - font.setHintingPreference(QFont::PreferNoHinting); - font.setPixelSize(qCeil(fontSize)); - font.setStyleStrategy(QFont::ForceOutline); - m_textLayout.setFont(font); - - QTextOption option = m_textLayout.textOption(); - option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); - option.setAlignment(textAlignment); - option.setUseDesignMetrics(true); - m_textLayout.setTextOption(option); - - m_textColor = appearance.getFontColor(); - if (!m_textColor.isValid()) - { - m_textColor = Qt::black; - } - - m_maxTextLength = maxTextLength; - m_widgetRect = rect; -} - -void PDFTextEditPseudowidget::performCut() -{ - if (isReadonly()) - { - return; - } - - performCopy(); - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performCopy() -{ - if (isTextSelected() && !isPassword()) - { - QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard); - } -} - -void PDFTextEditPseudowidget::performPaste() -{ - // We always insert text, even if it is empty. Because we want - // to erase selected text. - performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard)); -} - -void PDFTextEditPseudowidget::performClear() -{ - if (isReadonly()) - { - return; - } - - performSelectAll(); - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performSelectAll() -{ - m_selectionStart = getPositionStart(); - m_selectionEnd = getPositionEnd(); -} - -void PDFTextEditPseudowidget::performBackspace() -{ - if (isReadonly()) - { - return; - } - - // If we have selection, then delete selected text. If we do not have - // selection, then we delete previous character. - if (!isTextSelected()) - { - setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); - } - - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performDelete() -{ - if (isReadonly()) - { - return; - } - - // If we have selection, then delete selected text. If we do not have - // selection, then we delete previous character. - if (!isTextSelected()) - { - setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); - } - - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performRemoveSelectedText() -{ - if (isTextSelected()) - { - m_editText.remove(m_selectionStart, getSelectionLength()); - setCursorPosition(m_selectionStart, false); - clearSelection(); - updateTextLayout(); - } -} - -void PDFTextEditPseudowidget::performInsertText(const QString& text) -{ - if (isReadonly()) - { - return; - } - - // Insert text at the cursor - performRemoveSelectedText(); - m_editText.insert(m_positionCursor, text); - setCursorPosition(m_positionCursor + text.length(), false); - updateTextLayout(); -} - -QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const -{ - QMatrix matrix; - - matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); - matrix.scale(1.0, -1.0); - - if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor)) - { - // Jakub Melka: we must scroll the control, so cursor is always - // visible in the widget area. If we are not editing, this not the - // case, because we always show the text from the beginning. - - const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - if (line.isValid()) - { - const qreal xCursorPosition = line.cursorToX(m_positionCursor); - if (xCursorPosition >= m_widgetRect.width()) - { - const qreal delta = xCursorPosition - m_widgetRect.width(); - matrix.translate(-delta, 0.0); - } - - // Check, if we aren't outside of y position - const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height(); - const qreal lineBottom = lineSpacing * (line.lineNumber() + 1); - - if (lineBottom >= m_widgetRect.height()) - { - const qreal delta = lineBottom - m_widgetRect.height(); - matrix.translate(0.0, -delta); - } - } - } - - if (!isMultiline() && !isComb()) - { - // If text is single line, then adjust text position to the vertical center - QTextLine textLine = m_textLayout.lineAt(0); - if (textLine.isValid()) - { - const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height(); - const qreal textBoxHeight = m_widgetRect.height(); - - if (lineSpacing < textBoxHeight) - { - const qreal delta = (textBoxHeight - lineSpacing) * 0.5; - matrix.translate(0.0, delta); - } - } - } - - return matrix; -} - -std::vector PDFTextEditPseudowidget::getCursorPositions() const -{ - std::vector result; - result.reserve(m_editText.length()); - result.push_back(0); - - int currentPos = 0; - while (currentPos < m_editText.length()) - { - currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); - result.push_back(currentPos); - } - - return result; -} - -void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - pdf::PDFPainterStateGuard guard(parameters.painter); - parameters.boundingRectangle = parameters.annotation->getRectangle(); - - QPalette palette = QApplication::palette(); - - auto getAdjustedColor = [¶meters](QColor color) - { - if (parameters.invertColors) - { - return invertColor(color); - } - - return color; - }; - - QPainter* painter = parameters.painter; - - if (edit) - { - pdf::PDFPainterStateGuard guard(painter); - painter->setPen(getAdjustedColor(Qt::black)); - painter->setBrush(Qt::NoBrush); - painter->drawRect(parameters.boundingRectangle); - } - - painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); - painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true); - painter->setPen(getAdjustedColor(Qt::black)); - - if (isComb()) - { - const qreal combCount = qMax(m_maxTextLength, 1); - qreal combWidth = m_widgetRect.width() / combCount; - QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); - painter->setFont(m_textLayout.font()); - - QColor textColor = getAdjustedColor(m_textColor); - QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); - QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); - - std::vector positions = getCursorPositions(); - for (size_t i = 1; i < positions.size(); ++i) - { - if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd) - { - // We are in text selection - painter->fillRect(combRect, highlightColor); - painter->setPen(highlightTextColor); - } - else - { - // We are not in text selection - painter->setPen(textColor); - } - - int length = positions[i] - positions[i - 1]; - QString text = m_displayText.mid(positions[i - 1], length); - painter->drawText(combRect, Qt::AlignCenter, text); - - // Draw the cursor? - if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i]) - { - QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); - painter->fillRect(cursorRect, textColor); - } - - combRect.translate(combWidth, 0.0); - } - - // Draw the cursor onto next unfilled cell? - if (edit && m_positionCursor == getPositionEnd()) - { - QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); - painter->fillRect(cursorRect, textColor); - } - } - else - { - QVector selections; - - QTextLayout::FormatRange defaultFormat; - defaultFormat.start = getPositionStart(); - defaultFormat.length = getTextLength(); - defaultFormat.format.clearBackground(); - defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); - - // If we are editing, draw selections - if (edit && isTextSelected()) - { - QTextLayout::FormatRange before = defaultFormat; - QTextLayout::FormatRange after = defaultFormat; - - before.start = getPositionStart(); - before.length = m_selectionStart; - after.start = m_selectionEnd; - after.length = getTextLength() - m_selectionEnd; - - QTextLayout::FormatRange selectedFormat = defaultFormat; - selectedFormat.start = m_selectionStart; - selectedFormat.length = getSelectionLength(); - selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); - selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); - - selections = { before, selectedFormat, after}; - } - else - { - selections.push_back(defaultFormat); - } - - // Draw text - m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF()); - - // If we are editing, also draw text - if (edit && !isReadonly()) - { - m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor); - } - } -} - -int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const -{ - QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit); - QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted(); - - QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point); - - if (isComb()) - { - // If it is comb, then characters are spaced equidistantly - const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width()); - const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width())); - std::vector positions = getCursorPositions(); - if (position < positions.size()) - { - return positions[position]; - } - - return positions.back(); - } - else if (m_textLayout.lineCount() > 0) - { - QTextLine line; - qreal yPos = 0.0; - - // Find line under cursor - for (int i = 0; i < m_textLayout.lineCount(); ++i) - { - QTextLine currentLine = m_textLayout.lineAt(i); - const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height(); - const qreal yNextPos = yPos + lineSpacing; - - if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos) - { - line = currentLine; - break; - } - - yPos = yNextPos; - } - - // If no line is found, select last line - if (!line.isValid()) - { - if (textBoxPoint.y() < 0.0) - { - line = m_textLayout.lineAt(0); - } - else - { - line = m_textLayout.lineAt(m_textLayout.lineCount() - 1); - } - } - - return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters); - } - - return 0; -} - -void PDFTextEditPseudowidget::updateTextLayout() -{ - // Prepare display text - if (isPassword()) - { - m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter); - } - else - { - m_displayText = m_editText; - } - - // Perform text layout - m_textLayout.clearLayout(); - m_textLayout.setText(m_displayText); - m_textLayout.beginLayout(); - - QPointF textLinePosition(0.0, 0.0); - - while (true) - { - QTextLine textLine = m_textLayout.createLine(); - if (!textLine.isValid()) - { - // We are finished with layout - break; - } - - textLinePosition.ry() += textLine.leading(); - textLine.setLineWidth(m_widgetRect.width()); - textLine.setPosition(textLinePosition); - textLinePosition.ry() += textLine.height(); - } - m_textLayout.endLayout(); - - // Check length - if (m_maxTextLength > 0) - { - int length = 0; - int currentPos = 0; - - while (currentPos < m_editText.length()) - { - currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); - ++length; - - if (length == m_maxTextLength) - { - break; - } - } - - if (currentPos < m_editText.length()) - { - m_editText = m_editText.left(currentPos); - m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd()); - updateTextLayout(); - } - } -} - -int PDFTextEditPseudowidget::getSingleStepForward() const -{ - // If direction is right-to-left, then move backward (because - // text is painted from right to left. - return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1; -} - -int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const -{ - int cursor = referencePosition; - - if (steps > 0) - { - for (int i = 0; i < steps; ++i) - { - cursor = m_textLayout.nextCursorPosition(cursor, mode); - } - } - else if (steps < 0) - { - for (int i = 0; i < -steps; ++i) - { - cursor = m_textLayout.previousCursorPosition(cursor, mode); - } - } - - return cursor; -} - -int PDFTextEditPseudowidget::getCurrentLineTextStart() const -{ - return m_textLayout.lineForTextPosition(m_positionCursor).textStart(); -} - -int PDFTextEditPseudowidget::getCurrentLineTextEnd() const -{ - QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor); - return textLine.textStart() + textLine.textLength(); -} - -int PDFTextEditPseudowidget::getCursorLineUp() const -{ - QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - const int lineIndex = line.lineNumber() - 1; - - if (lineIndex >= 0) - { - QTextLine upLine = m_textLayout.lineAt(lineIndex); - return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); - } - - return m_positionCursor; -} - -int PDFTextEditPseudowidget::getCursorLineDown() const -{ - QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - const int lineIndex = line.lineNumber() + 1; - - if (lineIndex < m_textLayout.lineCount()) - { - QTextLine downLine = m_textLayout.lineAt(lineIndex); - return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); - } - - return m_positionCursor; -} - -bool PDFFormFieldText::setValue(const SetValueParameters& parameters) -{ - // If form field is readonly, and scope is user (form field is changed by user, - // not by calculated value), then we must not allow value change. - if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User) - { - return false; - } - - Q_ASSERT(parameters.formManager); - Q_ASSERT(parameters.modifier); - - PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); - parameters.modifier->markFormFieldChanged(); - builder->setFormFieldValue(getSelfReference(), parameters.value); - m_value = parameters.value; - - // Change widget appearance states - for (const PDFFormWidget& formWidget : getWidgets()) - { - builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); - parameters.modifier->markAnnotationsChanged(); - } - - return true; -} - -void PDFFormFieldText::resetValue(const ResetValueParameters& parameters) -{ - Q_ASSERT(parameters.formManager); - Q_ASSERT(parameters.modifier); - - PDFObject defaultValue = getDefaultValue(); - PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); - parameters.modifier->markFormFieldChanged(); - builder->setFormFieldValue(getSelfReference(), defaultValue); - m_value = defaultValue; - - // Change widget appearance states - for (const PDFFormWidget& formWidget : getWidgets()) - { - builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); - parameters.modifier->markAnnotationsChanged(); - } -} - -PDFListBoxPseudowidget::PDFListBoxPseudowidget(PDFFormField::FieldFlags flags) : - m_flags(flags), - m_topIndex(0), - m_currentIndex(0) -{ - -} - -void PDFListBoxPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - if (event == QKeySequence::Copy) - { - event->accept(); - return; - } - - if (event == QKeySequence::SelectAll) - { - // Select All can be processed only, if multiselection is allowed - if (isMultiSelect()) - { - event->accept(); - } - return; - } - - switch (event->key()) - { - case Qt::Key_Home: - case Qt::Key_End: - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - event->accept(); - break; - - default: - break; - } -} - -void PDFListBoxPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - event->accept(); - - if (event == QKeySequence::Copy) - { - // Copy the item text - if (m_currentIndex >= 0 && m_currentIndex < m_options.size()) - { - QApplication::clipboard()->setText(m_options[m_currentIndex].userString, QClipboard::Clipboard); - } - return; - } - - if (event == QKeySequence::SelectAll && isMultiSelect()) - { - std::set selection; - for (int i = 0; i < m_options.size(); ++i) - { - selection.insert(i); - } - setSelection(qMove(selection), false); - return; - } - - switch (event->key()) - { - case Qt::Key_Home: - setCurrentItem(getStartItemIndex(), event->modifiers()); - break; - - case Qt::Key_End: - setCurrentItem(getEndItemIndex(), event->modifiers()); - break; - - case Qt::Key_Up: - moveCurrentItemIndexByOffset(-getSingleStep(), event->modifiers()); - break; - - case Qt::Key_Down: - moveCurrentItemIndexByOffset(getSingleStep(), event->modifiers()); - break; - - case Qt::Key_PageUp: - moveCurrentItemIndexByOffset(-getPageStep(), event->modifiers()); - break; - - case Qt::Key_PageDown: - moveCurrentItemIndexByOffset(getPageStep(), event->modifiers()); - break; - - default: - event->ignore(); - break; - } -} - -void PDFListBoxPseudowidget::initialize(QFont font, QColor fontColor, Qt::Alignment textAlignment, QRectF rect, - const Options& options, int topIndex, std::set selection) -{ - m_font = font; - - QFontMetricsF fontMetrics(m_font); - m_lineSpacing = fontMetrics.lineSpacing(); - - m_textColor = fontColor; - if (!m_textColor.isValid()) - { - m_textColor = Qt::black; - } - - m_textAlignment = textAlignment; - m_widgetRect = rect; - m_options = options; - m_topIndex = getValidIndex(topIndex); - m_selection = qMove(selection); - m_currentIndex = m_topIndex; -} - -void PDFListBoxPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, - Qt::Alignment textAlignment, - QRectF rect, - const PDFListBoxPseudowidget::Options& options, - int topIndex, - std::set selection) -{ - // Set appearance - qreal fontSize = appearance.getFontSize(); - if (qFuzzyIsNull(fontSize)) - { - fontSize = qMax(rect.height() / qMax(options.size(), 1), qreal(12.0)); - } - - QColor fontColor = appearance.getFontColor(); - - QFont font(appearance.getFontName()); - font.setHintingPreference(QFont::PreferNoHinting); - font.setPixelSize(qCeil(fontSize)); - font.setStyleStrategy(QFont::ForceOutline); - - initialize(font, fontColor, textAlignment, rect, options, topIndex, qMove(selection)); -} - -QMatrix PDFListBoxPseudowidget::createListBoxTransformMatrix() const -{ - QMatrix matrix; - matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); - matrix.scale(1.0, -1.0); - return matrix; -} - -void PDFListBoxPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - pdf::PDFPainterStateGuard guard(parameters.painter); - - if (!parameters.boundingRectangle.isValid()) - { - parameters.boundingRectangle = parameters.annotation->getRectangle(); - } - - QPalette palette = QApplication::palette(); - - auto getAdjustedColor = [¶meters](QColor color) - { - if (parameters.invertColors) - { - return invertColor(color); - } - - return color; - }; - - QMatrix matrix = createListBoxTransformMatrix(); - - QPainter* painter = parameters.painter; - - if (edit) - { - pdf::PDFPainterStateGuard guard(painter); - painter->setPen(getAdjustedColor(Qt::black)); - painter->setBrush(Qt::NoBrush); - painter->drawRect(parameters.boundingRectangle); - } - - painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); - painter->setWorldMatrix(matrix, true); - painter->setPen(getAdjustedColor(m_textColor)); - painter->setFont(m_font); - - QColor textColor = getAdjustedColor(m_textColor); - QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); - QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); - - QRectF rect(0, 0, m_widgetRect.width(), m_lineSpacing); - for (int i = m_topIndex; i < m_options.size(); ++i) - { - if (m_selection.count(i)) - { - painter->fillRect(rect, highlightColor); - painter->setPen(highlightTextColor); - } - else - { - painter->setPen(textColor); - } - - painter->drawText(rect, m_textAlignment | Qt::TextSingleLine, m_options[i].userString); - - if (edit && m_currentIndex == i) - { - pdf::PDFPainterStateGuard guard(painter); - painter->setBrush(Qt::NoBrush); - painter->setPen(Qt::DotLine); - painter->drawRect(rect); - } - - rect.translate(0, m_lineSpacing); - } -} - -void PDFListBoxPseudowidget::setSelection(std::set selection, bool force) -{ - if (isReadonly() && !force) - { - // Field is readonly - return; - } - - // Jakub Melka: Here should be also commit, when flag CommitOnSelectionChange is set, - // but we do it only, when widget loses focus (so no need to update appearance often). - // I hope it will be OK. - - m_selection = qMove(selection); -} - -void PDFListBoxPseudowidget::setTopItemIndex(int index) -{ - m_topIndex = getValidIndex(index); -} - -bool PDFListBoxPseudowidget::isVisible(int index) const -{ - return index >= m_topIndex && index < m_topIndex + getViewportRowCount(); -} - -void PDFListBoxPseudowidget::scrollTo(int index) -{ - while (!isVisible(index)) - { - if (index < m_topIndex) - { - --m_topIndex; - } - else - { - ++m_topIndex; - } - } -} - -void PDFListBoxPseudowidget::setCurrentItem(int index, Qt::KeyboardModifiers modifiers) -{ - index = getValidIndex(index); - - if (m_currentIndex == index) - { - return; - } - - std::set newSelection; - if (!isMultiSelect() || !modifiers.testFlag(Qt::ShiftModifier)) - { - newSelection = { index }; - } - else - { - int indexFrom = index; - int indexTo = index; - - if (hasContinuousSelection()) - { - indexFrom = qMin(index, *m_selection.begin()); - indexTo = qMax(index, *m_selection.rbegin()); - } - else - { - indexFrom = qMin(index, m_currentIndex); - indexTo = qMax(index, m_currentIndex); - } - - for (int i = indexFrom; i <= indexTo; ++i) - { - newSelection.insert(i); - } - } - - m_currentIndex = index; - setSelection(qMove(newSelection), false); - scrollTo(m_currentIndex); -} - -QString PDFListBoxPseudowidget::getSelectedItemText() const -{ - if (m_selection.size() == 1) - { - const int selectedIndex = *m_selection.begin(); - return m_options[selectedIndex].userString; - } - - return QString(); -} - -int PDFListBoxPseudowidget::findOption(const QString& option) const -{ - auto it = std::find_if(m_options.cbegin(), m_options.cend(), [&option](const auto& currentOption) { return currentOption.userString == option; }); - if (it != m_options.cend()) - { - return static_cast(std::distance(m_options.cbegin(), it)); - } - - return -1; -} - -bool PDFListBoxPseudowidget::hasContinuousSelection() const -{ - if (m_selection.empty()) - { - return false; - } - - return *m_selection.rbegin() - *m_selection.begin() + 1 == m_selection.size(); -} - -int PDFListBoxPseudowidget::getValidIndex(int index) const -{ - return qBound(getStartItemIndex(), index, getEndItemIndex()); -} - -int PDFListBoxPseudowidget::getIndexFromWidgetPosition(const QPointF& point) const -{ - QMatrix widgetToPageMatrix = createListBoxTransformMatrix(); - QMatrix pageToWidgetMatrix = widgetToPageMatrix.inverted(); - - QPointF widgetPoint = pageToWidgetMatrix.map(point); - const qreal y = widgetPoint.y(); - const int visualIndex = qFloor(y / m_lineSpacing); - return m_topIndex + visualIndex; -} - -PDFFormFieldListBoxEditor::PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : - BaseClass(formManager, formWidget), - m_listBox(formWidget.getParent()->getFlags()) -{ - initializeListBox(&m_listBox); -} - -void PDFFormFieldListBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - m_listBox.shortcutOverrideEvent(widget, event); -} - -void PDFFormFieldListBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) - { - // Commit the editor - m_formManager->setFocusToEditor(nullptr); - event->accept(); - return; - } - - if (event->key() == Qt::Key_Escape) - { - // Cancel the editor - reloadValue(); - m_formManager->setFocusToEditor(nullptr); - event->accept(); - return; - } - - m_listBox.keyPressEvent(widget, event); - - if (event->isAccepted()) - { - widget->update(); - } -} - -void PDFFormFieldListBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - if (event->button() == Qt::LeftButton && m_hasFocus) - { - const int index = m_listBox.getIndexFromWidgetPosition(mousePagePosition); - - if (event->modifiers() & Qt::ControlModifier) - { - std::set selection = m_listBox.getSelection(); - if (selection.count(index)) - { - selection.erase(index); - } - else - { - selection.insert(index); - } - m_listBox.setSelection(qMove(selection), false); - } - else - { - m_listBox.setCurrentItem(index, event->modifiers()); - } - - event->accept(); - widget->update(); - } -} - -void PDFFormFieldListBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) -{ - if (event->buttons() & Qt::LeftButton) - { - const int index = m_listBox.getIndexFromWidgetPosition(mousePagePosition); - - if (!(event->modifiers() & Qt::ControlModifier)) - { - m_listBox.setCurrentItem(index, event->modifiers()); - - event->accept(); - widget->update(); - } - } -} - -void PDFFormFieldListBoxEditor::wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) -{ - Q_UNUSED(mousePagePosition); - - if (m_hasFocus) - { - if (event->angleDelta().y() < 0) - { - m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() + m_listBox.getViewportRowCount())); - } - else - { - m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() - 1)); - } - - widget->update(); - event->accept(); - } -} - -void PDFFormFieldListBoxEditor::reloadValue() -{ - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - m_listBox.setTopItemIndex(parentField->getTopIndex()); - m_listBox.setSelection(getSelectedItems(parentField->getValue(), parentField->getSelection()), true); -} - -void PDFFormFieldListBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - if (edit) - { - m_listBox.draw(parameters, true); - } - else - { - // Draw static contents - PDFListBoxPseudowidget pseudowidget(m_formWidget.getParent()->getFlags()); - initializeListBox(&pseudowidget); - pseudowidget.draw(parameters, false); - } -} - -void PDFFormFieldListBoxEditor::initializeListBox(PDFListBoxPseudowidget* listBox) const -{ - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - listBox->setAppearance(PDFAnnotationDefaultAppearance::parse(m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray())), - m_formManager->getForm()->getDefaultAlignment(), - m_formManager->getWidgetRectangle(m_formWidget), - parentField->getOptions(), - parentField->getTopIndex(), - getSelectedItems(parentField->getValue(), parentField->getSelection())); -} - -void PDFFormFieldListBoxEditor::setFocusImpl(bool focused) -{ - if (!focused && !m_listBox.isReadonly() && !m_formManager->isCommitDisabled()) - { - commit(); - } -} - -std::set PDFFormFieldListBoxEditor::getSelectedItems(PDFObject value, PDFObject indices) const -{ - std::set result; - - PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); - value = m_formManager->getDocument()->getObject(value); - - std::vector indicesVector = loader.readIntegerArray(indices); - if (indicesVector.empty()) - { - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - const PDFFormFieldChoice::Options& options = parentField->getOptions(); - auto addOption = [&options, &result](const QString& optionString) - { - for (size_t i = 0; i < options.size(); ++i) - { - const PDFFormFieldChoice::Option& option = options[i]; - if (option.userString == optionString) - { - result.insert(int(i)); - } - } - }; - - if (value.isString()) - { - addOption(loader.readTextString(value, QString())); - } - else if (value.isArray()) - { - for (const QString& string : loader.readTextStringList(value)) - { - addOption(string); - } - } - } - else - { - std::sort(indicesVector.begin(), indicesVector.end()); - std::copy(indicesVector.cbegin(), indicesVector.cend(), std::inserter(result, result.cend())); - } - - return result; -} - -void PDFFormFieldListBoxEditor::commit() -{ - PDFObject object; - - const std::set& selection = m_listBox.getSelection(); - if (!selection.empty()) - { - const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); - Q_ASSERT(parentField); - - const PDFFormFieldChoice::Options& options = parentField->getOptions(); - std::set values; - - for (const int index : selection) - { - values.insert(options[index].userString); - } - - if (values.size() == 1) - { - object = PDFObjectFactory::createTextString(*values.begin()); - } - else - { - PDFObjectFactory objectFactory; - objectFactory.beginArray(); - - for (const QString& string : values) - { - objectFactory << string; - } - - objectFactory.endArray(); - object = objectFactory.takeObject(); - } - } - - if (object != m_formWidget.getParent()->getValue()) - { - PDFFormField::SetValueParameters parameters; - parameters.formManager = m_formManager; - parameters.invokingWidget = m_formWidget.getWidget(); - parameters.invokingFormField = m_formWidget.getParent(); - parameters.scope = PDFFormField::SetValueParameters::Scope::User; - parameters.value = qMove(object); - parameters.listboxTopIndex = m_listBox.getTopItemIndex(); - std::copy(selection.cbegin(), selection.cend(), std::back_inserter(parameters.listboxChoices)); - m_formManager->setFormFieldValue(parameters); - } -} - -bool PDFFormFieldChoice::setValue(const SetValueParameters& parameters) -{ - // If form field is readonly, and scope is user (form field is changed by user, - // not by calculated value), then we must not allow value change. - if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User) - { - return false; - } - - Q_ASSERT(parameters.formManager); - Q_ASSERT(parameters.modifier); - - PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); - parameters.modifier->markFormFieldChanged(); - builder->setFormFieldValue(getSelfReference(), parameters.value); - - if (isListBox()) - { - // Listbox has special values, which must be set - builder->setFormFieldChoiceTopIndex(getSelfReference(), parameters.listboxTopIndex); - builder->setFormFieldChoiceIndices(getSelfReference(), parameters.listboxChoices); - } - - m_value = parameters.value; - m_topIndex = parameters.listboxTopIndex; - - PDFObjectFactory objectFactory; - objectFactory << parameters.listboxChoices; - m_selection = objectFactory.takeObject(); - - // Change widget appearance states - for (const PDFFormWidget& formWidget : getWidgets()) - { - builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); - parameters.modifier->markAnnotationsChanged(); - } - - return true; -} - -void PDFFormFieldChoice::resetValue(const PDFFormField::ResetValueParameters& parameters) -{ - Q_ASSERT(parameters.formManager); - Q_ASSERT(parameters.modifier); - - PDFObject defaultValue = getDefaultValue(); - PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); - parameters.modifier->markFormFieldChanged(); - builder->setFormFieldValue(getSelfReference(), defaultValue); - m_value = defaultValue; - m_selection = PDFObject(); - - if (isListBox()) - { - // Listbox has special values, which must be set - builder->setFormFieldChoiceTopIndex(getSelfReference(), 0); - builder->setFormFieldChoiceIndices(getSelfReference(), { }); - } - - // Change widget appearance states - for (const PDFFormWidget& formWidget : getWidgets()) - { - builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); - parameters.modifier->markAnnotationsChanged(); - } -} - -void PDFFormFieldChoice::reloadValue(const PDFObjectStorage* storage, PDFObject parentValue) -{ - BaseClass::reloadValue(storage, parentValue); - - if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(getSelfReference()))) - { - PDFDocumentDataLoaderDecorator loader(storage); - m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0); - m_selection = fieldDictionary->get("I"); - } -} - -} // namespace pdf +// Copyright (C) 2020-2021 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfform.h" +#include "pdfdocument.h" +#include "pdfdrawspacecontroller.h" +#include "pdfdrawwidget.h" +#include "pdfdocumentbuilder.h" +#include "pdfpainterutils.h" + +#include +#include +#include +#include +#include +#include + +namespace pdf +{ + +/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline. +/// Passwords can also be edited and editor can be read only. +class PDFTextEditPseudowidget +{ +public: + explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags); + + void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); + void keyPressEvent(QWidget* widget, QKeyEvent* event); + + inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } + inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); } + inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); } + inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); } + inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); } + + inline bool isEmpty() const { return m_editText.isEmpty(); } + inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; } + inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); } + + inline int getTextLength() const { return m_editText.length(); } + inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; } + + inline int getPositionCursor() const { return m_positionCursor; } + inline int getPositionStart() const { return 0; } + inline int getPositionEnd() const { return getTextLength(); } + + inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; } + + inline const QString& getText() const { return m_editText; } + inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); } + + /// Sets (updates) text selection + /// \param startPosition From where we are selecting text + /// \param selectionLength Selection length (positive - to the right, negative - to the left) + void setSelection(int startPosition, int selectionLength); + + /// Moves cursor position. It behaves as usual in text boxes, + /// when user moves the cursor. If \p select is true, then + /// selection is updated. + /// \param position New position of the cursor + /// \param select Select text when moving the cursor? + void setCursorPosition(int position, bool select); + + /// Sets text content of the widget. This functions sets the text, + /// even if widget is readonly. + /// \param text Text to be set + void setText(const QString& text); + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param appearance Appearance + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param maxTextLength Maximal text length + void setAppearance(const PDFAnnotationDefaultAppearance& appearance, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength); + + void performCut(); + void performCopy(); + void performPaste(); + void performClear(); + void performSelectAll(); + void performBackspace(); + void performDelete(); + void performRemoveSelectedText(); + void performInsertText(const QString& text); + + /// Draw text edit using given parameters + /// \param parameters Parameters + void draw(AnnotationDrawParameters& parameters, bool edit) const; + + /// Returns valid cursor position retrieved from position in the widget. + /// \param point Point in page coordinate space + /// \param edit Are we using edit transformations? + /// \returns Cursor position + int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const; + + inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); } + inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); } + inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); } + inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); } + inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); } + inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); } + inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); } + inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); } + inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); } + inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); } + inline int getCursorNextLine() const { return getCurrentLineTextEnd(); } + inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); } + + const QRectF& getWidgetRect() const { return m_widgetRect; } + QFont getFont() const { return m_textLayout.font(); } + QColor getFontColor() const { return m_textColor; } + +private: + /// This function does following things: + /// 1) Clamps edit text to fit maximum length + /// 2) Creates display string from edit string + /// 3) Updates text layout + void updateTextLayout(); + + /// Returns single step forward, which is determined + /// by cursor move style and layout direction. + int getSingleStepForward() const; + + /// Returns single step backward, which is determined + /// by cursor move style and layout direction. + int getSingleStepBackward() const { return -getSingleStepForward(); } + + /// Returns next/previous position, by number of steps, + /// using given cursor mode (skipping characters or whole words). + /// \param steps Number of steps to proceed (can be negative number) + /// \param mode Skip mode - letters or words? + int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); } + + /// Returns next/previous position from reference cursor position, by number of steps, + /// using given cursor mode (skipping characters or whole words). + /// \param referencePosition Reference cursor position + /// \param steps Number of steps to proceed (can be negative number) + /// \param mode Skip mode - letters or words? + int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const; + + /// Returns current line text start position + int getCurrentLineTextStart() const; + + /// Returns current line text end position + int getCurrentLineTextEnd() const; + + /// Creates text box transform matrix, which transforms from + /// widget space to page space. + /// \param edit Create matrix for text editing? + QMatrix createTextBoxTransformMatrix(bool edit) const; + + /// Returns vector of cursor positions (which may be not equal + /// to edit string length, because edit string can contain surrogates, + /// or graphemes, which are single glyphs, but represented by more + /// 16-bit unicode codes). + std::vector getCursorPositions() const; + + int getCursorLineUp() const; + int getCursorLineDown() const; + + PDFFormField::FieldFlags m_flags; + + /// Text edited by the user + QString m_editText; + + /// Text, which is displayed. It can differ from text + /// edited by user, in case password is being entered. + QString m_displayText; + + /// Text layout + QTextLayout m_textLayout; + + /// Character for displaying passwords + QChar m_passwordReplacementCharacter; + + int m_selectionStart; + int m_selectionEnd; + int m_positionCursor; + int m_maxTextLength; + + QRectF m_widgetRect; + QColor m_textColor; +}; + +/// Editor for button-like editors +class PDFFormFieldAbstractButtonEditor : public PDFFormFieldWidgetEditor +{ +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldAbstractButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget); + virtual ~PDFFormFieldAbstractButtonEditor() = default; + + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + +protected: + virtual void click() = 0; +}; + +/// Editor for push buttons +class PDFFormFieldPushButtonEditor : public PDFFormFieldAbstractButtonEditor +{ +private: + using BaseClass = PDFFormFieldAbstractButtonEditor; + +public: + explicit PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget); + virtual ~PDFFormFieldPushButtonEditor() = default; + +protected: + virtual void click() override; +}; + +/// Editor for check boxes or radio buttons +class PDFFormFieldCheckableButtonEditor : public PDFFormFieldAbstractButtonEditor +{ +private: + using BaseClass = PDFFormFieldAbstractButtonEditor; + +public: + explicit PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget); + virtual ~PDFFormFieldCheckableButtonEditor() = default; + +protected: + virtual void click() override; +}; + +/// Editor for text fields +class PDFFormFieldTextBoxEditor : public PDFFormFieldWidgetEditor +{ +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget); + virtual ~PDFFormFieldTextBoxEditor() = default; + + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void reloadValue() override; + virtual bool isEditorDrawEnabled() const override { return m_hasFocus; } + virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override; + + /// Initializes text edit using actual form field value, + /// font, color for text edit appearance. + /// \param textEdit Text editor + void initializeTextEdit(PDFTextEditPseudowidget* textEdit) const; + +protected: + virtual void setFocusImpl(bool focused) override; + +private: + PDFTextEditPseudowidget m_textEdit; +}; + +/// "Pseudo" widget, which is emulating list box. It can contain scrollbar. +class PDFListBoxPseudowidget +{ +public: + explicit PDFListBoxPseudowidget(PDFFormField::FieldFlags flags); + + using Options = PDFFormFieldChoice::Options; + + inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } + inline bool isMultiSelect() const { return m_flags.testFlag(PDFFormField::MultiSelect); } + inline bool isCommitOnSelectionChange() const { return m_flags.testFlag(PDFFormField::CommitOnSelectionChange); } + + void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); + void keyPressEvent(QWidget* widget, QKeyEvent* event); + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param appearance Appearance + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param options Options selectable by list box + /// \param topIndex Index of first visible item + void setAppearance(const PDFAnnotationDefaultAppearance& appearance, + Qt::Alignment textAlignment, + QRectF rect, + const Options& options, + int topIndex, + std::set selection); + + /// Draw text edit using given parameters + /// \param parameters Parameters + void draw(AnnotationDrawParameters& parameters, bool edit) const; + + /// Sets current selection. If commit on selection change flag + /// is set, then contents of the widgets are commited. New selection + /// is not set, if field is readonly and \p force is false + /// \param selection New selection + /// \param force Set selection even if readonly + void setSelection(std::set selection, bool force); + + /// Returns active item selection + const std::set& getSelection() const { return m_selection; } + + /// Returns top index + int getTopItemIndex() const { return m_topIndex; } + + /// Set top item index + /// \param index Top item index + void setTopItemIndex(int index); + + /// Scrolls the list box in such a way, that index is visible + /// \param index Index to scroll to + void scrollTo(int index); + + /// Returns valid index from index (i.e. index which ranges from first + /// row to the last one). If index itself is valid, then it is returned. + /// \param index Index, from which we want to get valid one + int getValidIndex(int index) const; + + /// Returns true, if index is valid + bool isValidIndex(int index) const { return index == getValidIndex(index); } + + /// Returns number of rows in the viewport + int getViewportRowCount() const { return qFloor(m_widgetRect.height() / m_lineSpacing); } + + /// Returns item index from widget position (i.e. transforms + /// point in the widget to the index of item, which is under the point) + /// \param point Widget point + int getIndexFromWidgetPosition(const QPointF& point) const; + + /// Sets current item and updates selection based on keyboard modifiers + /// \param index New index + /// \param modifiers Keyboard modifiers + void setCurrentItem(int index, Qt::KeyboardModifiers modifiers); + + /// Returns text of selected item text. If no item is selected, + /// or multiple items are selected, then empty string is returned. + /// \returns Selected text + QString getSelectedItemText() const; + + /// Returns true, if single item is selected + bool isSingleItemSelected() const { return m_selection.size() == 1; } + + /// Returns index of option, in the list box option list. + /// If option is not found, then -1 is returned. + /// \param option Option + int findOption(const QString& option) const; + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param font Font + /// \param fontColor Font color + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param options Options selectable by list box + /// \param topIndex Index of first visible item + void initialize(QFont font, QColor fontColor, Qt::Alignment textAlignment, QRectF rect, + const Options& options, int topIndex, std::set selection); + +private: + int getStartItemIndex() const { return 0; } + int getEndItemIndex() const { return m_options.empty() ? 0 : int(m_options.size() - 1); } + + int getSingleStep() const { return 1; } + int getPageStep() const { return qMax(getViewportRowCount() - 1, getSingleStep()); } + + /// Returns true, if row with this index is visible in the widget + /// (it is in viewport). + /// \param index Index + bool isVisible(int index) const; + + /// Moves current item by offset (negative is up, positive is down) + /// \param offset Offset + /// \param modifiers Keyboard modifiers + void moveCurrentItemIndexByOffset(int offset, Qt::KeyboardModifiers modifiers) { setCurrentItem(m_currentIndex + offset, modifiers); } + + /// Returns true, if list box has continuous selection + bool hasContinuousSelection() const; + + /// Creates transformation matrix, which transforms widget coordinates to page coordinates + QMatrix createListBoxTransformMatrix() const; + + PDFFormField::FieldFlags m_flags; + + Options m_options; + Qt::Alignment m_textAlignment = 0; + int m_topIndex = 0; + int m_currentIndex = 0; + std::set m_selection; + QFont m_font; + PDFReal m_lineSpacing = 0.0; + QRectF m_widgetRect; + QColor m_textColor; +}; + +/// Editor for combo boxes +class PDFFormFieldComboBoxEditor : public PDFFormFieldWidgetEditor +{ +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget); + virtual ~PDFFormFieldComboBoxEditor() = default; + + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) override; + virtual void reloadValue() override; + virtual bool isEditorDrawEnabled() const override { return m_hasFocus; } + virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override; + virtual QRectF getActiveEditorRectangle() const override; + + /// Initializes text edit using actual form field value, + /// font, color for text edit appearance. + /// \param textEdit Text editor + void initializeTextEdit(PDFTextEditPseudowidget* textEdit) const; + + /// Initializes list box using actual form field font, color for text edit appearance. + /// This listbox is used when combo box is in "down" mode, displaying options. + /// \param listBox List box + void initializeListBox(PDFListBoxPseudowidget* listBox) const; + +protected: + virtual void setFocusImpl(bool focused) override; + +private: + static PDFFormField::FieldFlags getTextEditFlags(PDFFormField::FieldFlags flags); + + /// Updates list box selection based on current text. If current text + /// corresponds to some item in the list box, then item is selected + /// and scrolled to. If text is not found in the text box, then + /// selection is cleared. + void updateListBoxSelection(); + + PDFTextEditPseudowidget m_textEdit; + PDFListBoxPseudowidget m_listBox; + QRectF m_listBoxPopupRectangle; + QRectF m_dropDownButtonRectangle; + bool m_listBoxVisible; +}; + +/// Editor for list boxes +class PDFFormFieldListBoxEditor : public PDFFormFieldWidgetEditor +{ +private: + using BaseClass = PDFFormFieldWidgetEditor; + +public: + explicit PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget); + virtual ~PDFFormFieldListBoxEditor() = default; + + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) override; + virtual void reloadValue() override; + virtual bool isEditorDrawEnabled() const override { return m_hasFocus; } + virtual void draw(AnnotationDrawParameters& parameters, bool edit) const override; + + /// Initializes list box using actual form field value, + /// font, color for text edit appearance. + /// \param listBox List box + void initializeListBox(PDFListBoxPseudowidget* listBox) const; + +protected: + virtual void setFocusImpl(bool focused) override; + +private: + /// Returns list of selected items parsed from the object. + /// If object is invalid, empty selection is returned. Indices are + /// primarily read from \p indices object, if it fails, indices + /// are being read from \p value object. + /// \param value Selection (string or array of strings) + /// \param indices Selected indices + std::set getSelectedItems(PDFObject value, PDFObject indices) const; + + /// Commits data to the PDF document + void commit(); + + PDFListBoxPseudowidget m_listBox; +}; + +PDFForm PDFForm::parse(const PDFDocument* document, PDFObject object) +{ + PDFForm form; + + if (const PDFDictionary* formDictionary = document->getDictionaryFromObject(object)) + { + PDFDocumentDataLoaderDecorator loader(document); + + std::vector fieldRoots = loader.readReferenceArrayFromDictionary(formDictionary, "Fields"); + form.m_formFields.reserve(fieldRoots.size()); + for (const PDFObjectReference& fieldRootReference : fieldRoots) + { + form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), fieldRootReference, nullptr)); + } + + form.m_formType = FormType::AcroForm; + form.m_needAppearances = loader.readBooleanFromDictionary(formDictionary, "NeedAppearances", false); + form.m_signatureFlags = static_cast(loader.readIntegerFromDictionary(formDictionary, "SigFlags", 0)); + form.m_calculationOrder = loader.readReferenceArrayFromDictionary(formDictionary, "CO"); + form.m_resources = formDictionary->get("DR"); + form.m_defaultAppearance = loader.readOptionalStringFromDictionary(formDictionary, "DA"); + form.m_quadding = loader.readOptionalIntegerFromDictionary(formDictionary, "Q"); + form.m_xfa = formDictionary->get("XFA"); + + if (!form.m_xfa.isNull()) + { + // Jakub Melka: handle XFA form + form.m_formType = FormType::XFAForm; + } + + // As post-processing, delete all form fields, which are nullptr (are incorrectly defined) + form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end()); + form.updateWidgetToFormFieldMapping(); + + // If we have form, then we must also look for 'rogue' form fields, which are + // incorrectly not in the 'Fields' entry of this form. We do this by iterating + // all pages, and their annotations and try to find these 'rogue' fields. + bool rogueFieldFound = false; + const size_t pageCount = document->getCatalog()->getPageCount(); + for (size_t i = 0; i < pageCount; ++i) + { + const PDFPage* page = document->getCatalog()->getPage(i); + for (PDFObjectReference annotationReference : page->getAnnotations()) + { + const PDFDictionary* annotationDictionary = document->getDictionaryFromObject(document->getObjectByReference(annotationReference)); + + if (form.m_widgetToFormField.count(annotationReference)) + { + // This widget/form field is already present + continue; + } + + if (loader.readNameFromDictionary(annotationDictionary, "Subtype") == "Widget" && + !annotationDictionary->hasKey("Kids")) + { + rogueFieldFound = true; + form.m_formFields.emplace_back(PDFFormField::parse(&document->getStorage(), annotationReference, nullptr)); + } + } + } + + // As post-processing, delete all form fields, which are nullptr (are incorrectly defined) + form.m_formFields.erase(std::remove_if(form.m_formFields.begin(), form.m_formFields.end(), [](const auto& field){ return !field; }), form.m_formFields.end()); + + if (rogueFieldFound) + { + form.updateWidgetToFormFieldMapping(); + } + } + + return form; +} + +void PDFForm::updateWidgetToFormFieldMapping() +{ + m_widgetToFormField.clear(); + + if (isAcroForm() || isXFAForm()) + { + for (const PDFFormFieldPointer& formFieldPtr : getFormFields()) + { + formFieldPtr->fillWidgetToFormFieldMapping(m_widgetToFormField); + } + } +} + +Qt::Alignment PDFForm::getDefaultAlignment() const +{ + Qt::Alignment alignment = Qt::AlignVCenter; + + switch (getQuadding().value_or(0)) + { + default: + case 0: + alignment |= Qt::AlignLeft; + break; + + case 1: + alignment |= Qt::AlignHCenter; + break; + + case 2: + alignment |= Qt::AlignRight; + break; + } + + return alignment; +} + +const PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) const +{ + auto it = m_widgetToFormField.find(widget); + if (it != m_widgetToFormField.cend()) + { + return it->second; + } + + return nullptr; +} + +PDFFormField* PDFForm::getFormFieldForWidget(PDFObjectReference widget) +{ + auto it = m_widgetToFormField.find(widget); + if (it != m_widgetToFormField.cend()) + { + return it->second; + } + + return nullptr; +} + +void PDFForm::apply(const std::function& functor) const +{ + for (const PDFFormFieldPointer& childField : getFormFields()) + { + childField->apply(functor); + } +} + +void PDFFormField::fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping) +{ + for (const auto& childField : m_childFields) + { + childField->fillWidgetToFormFieldMapping(mapping); + } + + for (const PDFFormWidget& formWidget : m_widgets) + { + mapping[formWidget.getWidget()] = formWidget.getParent(); + } +} + +void PDFFormField::reloadValue(const PDFObjectStorage* storage, PDFObject parentValue) +{ + Q_ASSERT(storage); + + if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(getSelfReference()))) + { + m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentValue; + } + + for (const PDFFormFieldPointer& childField : m_childFields) + { + childField->reloadValue(storage, m_value); + } +} + +void PDFFormField::apply(const std::function& functor) const +{ + functor(this); + + for (const PDFFormFieldPointer& childField : m_childFields) + { + childField->apply(functor); + } +} + +void PDFFormField::modify(const std::function& functor) +{ + functor(this); + + for (const PDFFormFieldPointer& childField : m_childFields) + { + childField->modify(functor); + } +} + +PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) +{ + PDFFormFieldPointer result; + + if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference))) + { + PDFFormField* formField = nullptr; + PDFFormFieldButton* formFieldButton = nullptr; + PDFFormFieldText* formFieldText = nullptr; + PDFFormFieldChoice* formFieldChoice = nullptr; + PDFFormFieldSignature* formFieldSignature = nullptr; + + constexpr const std::array, 4> fieldTypes = { + std::pair{ "Btn", FieldType::Button }, + std::pair{ "Tx", FieldType::Text }, + std::pair{ "Ch", FieldType::Choice }, + std::pair{ "Sig", FieldType::Signature } + }; + + PDFDocumentDataLoaderDecorator loader(storage); + FieldType formFieldType = parentField ? parentField->getFieldType() : FieldType::Invalid; + if (fieldDictionary->hasKey("FT")) + { + formFieldType = loader.readEnumByName(fieldDictionary->get("FT"), fieldTypes.begin(), fieldTypes.end(), FieldType::Invalid); + } + + switch (formFieldType) + { + case FieldType::Invalid: + formField = new PDFFormField(); + break; + + case FieldType::Button: + formFieldButton = new PDFFormFieldButton(); + formField = formFieldButton; + break; + + case FieldType::Text: + formFieldText = new PDFFormFieldText(); + formField = formFieldText; + break; + + case FieldType::Choice: + formFieldChoice = new PDFFormFieldChoice(); + formField = formFieldChoice; + break; + + case FieldType::Signature: + formFieldSignature = new PDFFormFieldSignature(); + formField = formFieldSignature; + break; + + default: + Q_ASSERT(false); + break; + } + result.reset(formField); + + PDFObject parentV = parentField ? parentField->getValue() : PDFObject(); + PDFObject parentDV = parentField ? parentField->getDefaultValue() : PDFObject(); + FieldFlags parentFlags = parentField ? parentField->getFlags() : None; + + formField->m_selfReference = reference; + formField->m_fieldType = formFieldType; + formField->m_parentField = parentField; + formField->m_fieldNames[Partial] = loader.readTextStringFromDictionary(fieldDictionary, "T", QString()); + formField->m_fieldNames[UserCaption] = loader.readTextStringFromDictionary(fieldDictionary, "TU", QString()); + formField->m_fieldNames[Export] = loader.readTextStringFromDictionary(fieldDictionary, "TM", QString()); + formField->m_fieldFlags = fieldDictionary->hasKey("Ff") ? static_cast(loader.readIntegerFromDictionary(fieldDictionary, "Ff", 0)) : parentFlags; + formField->m_value = fieldDictionary->hasKey("V") ? fieldDictionary->get("V") : parentV; + formField->m_defaultValue = fieldDictionary->hasKey("DV") ? fieldDictionary->get("DV") : parentDV; + formField->m_additionalActions = PDFAnnotationAdditionalActions::parse(storage, fieldDictionary->get("AA"), fieldDictionary->get("A")); + + // Generate fully qualified name. If partial name is empty, then fully qualified name + // is generated from parent fully qualified name (i.e. it is same as parent's name). + // This is according the PDF specification 1.7. + QStringList names; + if (parentField) + { + names << parentField->getName(FullyQualified); + } + names << formField->m_fieldNames[Partial]; + names.removeAll(QString()); + + formField->m_fieldNames[FullyQualified] = names.join("."); + + std::vector kids = loader.readReferenceArrayFromDictionary(fieldDictionary, "Kids"); + if (kids.empty()) + { + // This means, that field's dictionary is merged with annotation's dictionary, + // so, we will add pointer to self to the form widgets. But we must test, if we + // really have merged annotation's dictionary - we test it by checking for 'Subtype' + // presence. + if (loader.readNameFromDictionary(fieldDictionary, "Subtype") == "Widget") + { + formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, reference, formField)); + } + } + else + { + // Otherwise we must scan all references, and determine, if kid is another form field, + // or it is a widget annotation. Widget annotations has required field 'Subtype', which + // has value 'Widget', form field has required field 'Parent' (for non-root fields). + + for (const PDFObjectReference& kid : kids) + { + if (const PDFDictionary* childDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(kid))) + { + const bool isWidget = loader.readNameFromDictionary(childDictionary, "Subtype") == "Widget"; + const bool isField = loader.readReferenceFromDictionary(childDictionary, "Parent").isValid(); + + if (isField) + { + // This is form field (potentially merged with widget) + formField->m_childFields.emplace_back(PDFFormField::parse(storage, kid, formField)); + } + else if (isWidget) + { + // This is pure widget (with no form field) + formField->m_widgets.emplace_back(PDFFormWidget::parse(storage, kid, formField)); + } + } + } + + } + + if (formFieldButton) + { + formFieldButton->m_options = loader.readTextStringList(fieldDictionary->get("Opt")); + } + + if (formFieldText) + { + PDFInteger maxLengthDefault = 0; + QByteArray defaultAppearance; + Qt::Alignment alignment = 0; + + if (PDFFormFieldText* parentTextField = dynamic_cast(parentField)) + { + maxLengthDefault = parentTextField->getTextMaximalLength(); + defaultAppearance = parentTextField->getDefaultAppearance(); + alignment = parentTextField->getAlignment(); + } + + if (fieldDictionary->hasKey("DA")) + { + defaultAppearance = loader.readStringFromDictionary(fieldDictionary, "DA"); + } + if (fieldDictionary->hasKey("Q")) + { + const PDFInteger quadding = loader.readIntegerFromDictionary(fieldDictionary, "Q", -1); + switch (quadding) + { + case 0: + alignment = Qt::AlignLeft; + break; + + case 1: + alignment = Qt::AlignHCenter; + break; + + case 2: + alignment = Qt::AlignRight; + break; + + default: + break; + } + } + alignment |= Qt::AlignVCenter; + + formFieldText->m_maxLength = loader.readIntegerFromDictionary(fieldDictionary, "MaxLen", maxLengthDefault); + formFieldText->m_defaultAppearance = defaultAppearance; + formFieldText->m_alignment = alignment; + formFieldText->m_defaultStyle = loader.readTextStringFromDictionary(fieldDictionary, "DS", QString()); + formFieldText->m_richTextValue = loader.readTextStringFromDictionary(fieldDictionary, "RV", QString()); + } + + if (formFieldChoice) + { + // Parse options - it is array of options values. Option value can be either a single text + // string, or array of two values - export value and text to be displayed. + PDFObject options = storage->getObject(fieldDictionary->get("Opt")); + if (options.isArray()) + { + const PDFArray* optionsArray = options.getArray(); + formFieldChoice->m_options.reserve(optionsArray->getCount()); + for (size_t i = 0; i < optionsArray->getCount(); ++i) + { + PDFFormFieldChoice::Option option; + + PDFObject optionItemObject = storage->getObject(optionsArray->getItem(i)); + if (optionItemObject.isArray()) + { + QStringList stringList = loader.readTextStringList(optionItemObject); + if (stringList.size() == 2) + { + option.exportString = stringList[0]; + option.userString = stringList[1]; + } + } + else + { + option.userString = loader.readTextString(optionItemObject, QString()); + option.exportString = option.userString; + } + + formFieldChoice->m_options.emplace_back(qMove(option)); + } + } + + formFieldChoice->m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0); + formFieldChoice->m_selection = fieldDictionary->get("I"); + } + + if (formFieldSignature) + { + formFieldSignature->m_signature = PDFSignature::parse(storage, fieldDictionary->get("V")); + } + } + + return result; +} + +bool PDFFormField::setValue(const SetValueParameters& parameters) +{ + Q_UNUSED(parameters); + + // Default behaviour: return false, value cannot be set + return false; +} + +void PDFFormField::resetValue(const ResetValueParameters& parameters) +{ + Q_UNUSED(parameters); + + // Default behaviour: do nothing +} + +PDFFormWidget::PDFFormWidget(PDFObjectReference page, PDFObjectReference widget, PDFFormField* parentField, PDFAnnotationAdditionalActions actions) : + m_page(page), + m_widget(widget), + m_parentField(parentField), + m_actions(qMove(actions)) +{ + +} + +PDFFormWidget PDFFormWidget::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField) +{ + PDFObjectReference pageReference; + PDFAnnotationAdditionalActions actions; + if (const PDFDictionary* annotationDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(reference))) + { + PDFDocumentDataLoaderDecorator loader(storage); + pageReference = loader.readReferenceFromDictionary(annotationDictionary, "P"); + actions = PDFAnnotationAdditionalActions::parse(storage, annotationDictionary->get("AA"), annotationDictionary->get("A")); + } + + return PDFFormWidget(pageReference, reference, parentField, qMove(actions)); +} + +PDFFormFieldButton::ButtonType PDFFormFieldButton::getButtonType() const +{ + if (m_fieldFlags.testFlag(PushButton)) + { + return ButtonType::PushButton; + } + else if (m_fieldFlags.testFlag(Radio)) + { + return ButtonType::RadioButton; + } + + return ButtonType::CheckBox; +} + +QByteArray PDFFormFieldButton::getOnAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget) +{ + Q_ASSERT(formManager); + Q_ASSERT(widget); + + const PDFDocument* document = formManager->getDocument(); + Q_ASSERT(document); + + if (const PDFDictionary* dictionary = document->getDictionaryFromObject(document->getObjectByReference(widget->getWidget()))) + { + PDFAppeareanceStreams streams = PDFAppeareanceStreams::parse(&document->getStorage(), dictionary->get("AP")); + QByteArrayList states = streams.getAppearanceStates(PDFAppeareanceStreams::Appearance::Normal); + + for (const QByteArray& state : states) + { + if (!state.isEmpty() && state != "Off") + { + return state; + } + } + } + + return QByteArray(); +} + +QByteArray PDFFormFieldButton::getOffAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget) +{ + Q_UNUSED(formManager); + Q_UNUSED(widget); + + // 'Off' value is specified by PDF 1.7 specification. It has always value 'Off'. + // 'On' values can have different appearance states. + return "Off"; +} + +bool PDFFormFieldButton::setValue(const SetValueParameters& parameters) +{ + // Do not allow to set value to push buttons + if (getFlags().testFlag(PushButton)) + { + return false; + } + + // If form field is readonly, and scope is user (form field is changed by user, + // not by calculated value), then we must not allow value change. + if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User) + { + return false; + } + + Q_ASSERT(parameters.formManager); + Q_ASSERT(parameters.modifier); + Q_ASSERT(parameters.value.isName()); + + PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); + QByteArray state = parameters.value.getString(); + parameters.modifier->markFormFieldChanged(); + builder->setFormFieldValue(getSelfReference(), parameters.value); + + // Change widget appearance states + const bool isRadio = getFlags().testFlag(Radio); + const bool isRadioInUnison = getFlags().testFlag(RadiosInUnison); + const bool isSameValueForAllWidgets = !isRadio || isRadioInUnison; + const bool isAllowedToCheckAllOff = !getFlags().testFlag(NoToggleToOff); + bool hasWidgets = !m_widgets.empty(); + bool isAnyWidgetToggledOn = false; + + for (const PDFFormWidget& formWidget : getWidgets()) + { + QByteArray onState = PDFFormFieldButton::getOnAppearanceState(parameters.formManager, &formWidget); + + // We set appearance to 'On' if following two conditions both hold: + // 1) State equals to widget's "On" state + // 2) Either we are setting value to invoking widget, or setting of same + // value to other widgets is allowed (it is a checkbox, or radio in unison) + if (state == onState && (isSameValueForAllWidgets || formWidget.getWidget() == parameters.invokingWidget)) + { + isAnyWidgetToggledOn = true; + builder->setAnnotationAppearanceState(formWidget.getWidget(), onState); + } + else + { + QByteArray offState = PDFFormFieldButton::getOffAppearanceState(parameters.formManager, &formWidget); + builder->setAnnotationAppearanceState(formWidget.getWidget(), offState); + } + parameters.modifier->markAnnotationsChanged(); + } + + // We must check, if correct value has been set. If form field has no widgets, + // but has same qualified name, then no check is performed (just form field value is set + // to same value in all form fields with same qualified name, according to the PDF specification. + if (hasWidgets && !isAnyWidgetToggledOn && !isAllowedToCheckAllOff) + { + return false; + } + + return true; +} + +void PDFFormFieldButton::resetValue(const ResetValueParameters& parameters) +{ + // Do not allow to reset value of push buttons + if (getFlags().testFlag(PushButton)) + { + return; + } + + Q_ASSERT(parameters.modifier); + Q_ASSERT(parameters.formManager); + + PDFObject defaultValue = getDefaultValue(); + PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); + parameters.modifier->markFormFieldChanged(); + builder->setFormFieldValue(getSelfReference(), defaultValue); + + PDFDocumentDataLoaderDecorator loader(parameters.formManager->getDocument()); + QByteArray defaultState = loader.readString(defaultValue); + + for (const PDFFormWidget& formWidget : getWidgets()) + { + QByteArray onState = PDFFormFieldButton::getOnAppearanceState(parameters.formManager, &formWidget); + if (defaultState == onState) + { + builder->setAnnotationAppearanceState(formWidget.getWidget(), onState); + } + else + { + QByteArray offState = PDFFormFieldButton::getOffAppearanceState(parameters.formManager, &formWidget); + builder->setAnnotationAppearanceState(formWidget.getWidget(), offState); + } + parameters.modifier->markAnnotationsChanged(); + } +} + +PDFFormManager::PDFFormManager(PDFDrawWidgetProxy* proxy, QObject* parent) : + BaseClass(parent), + m_proxy(proxy), + m_annotationManager(nullptr), + m_document(nullptr), + m_flags(getDefaultApperanceFlags()), + m_isCommitDisabled(false), + m_focusedEditor(nullptr) +{ + Q_ASSERT(proxy); +} + +PDFFormManager::~PDFFormManager() +{ + clearEditors(); +} + +PDFAnnotationManager* PDFFormManager::getAnnotationManager() const +{ + return m_annotationManager; +} + +void PDFFormManager::setAnnotationManager(PDFAnnotationManager* annotationManager) +{ + m_annotationManager = annotationManager; +} + +const PDFDocument* PDFFormManager::getDocument() const +{ + return m_document; +} + +void PDFFormManager::setDocument(const PDFModifiedDocument& document) +{ + if (m_document != document) + { + PDFTemporaryValueChange change(&m_isCommitDisabled, true); + m_document = document; + + if (document.hasReset()) + { + if (m_document) + { + m_form = PDFForm::parse(m_document, m_document->getCatalog()->getFormObject()); + } + else + { + // Clean the form + m_form = PDFForm(); + } + + updateFormWidgetEditors(); + } + else if (document.hasFlag(PDFModifiedDocument::FormField)) + { + // Just update field values + updateFieldValues(); + } + } +} + +PDFFormManager::FormAppearanceFlags PDFFormManager::getAppearanceFlags() const +{ + return m_flags; +} + +void PDFFormManager::setAppearanceFlags(FormAppearanceFlags flags) +{ + m_flags = flags; +} + +bool PDFFormManager::hasFormFieldWidgetText(PDFObjectReference widgetAnnotation) const +{ + if (const PDFFormField* formField = getFormFieldForWidget(widgetAnnotation)) + { + switch (formField->getFieldType()) + { + case PDFFormField::FieldType::Text: + return true; + + case PDFFormField::FieldType::Choice: + { + PDFFormField::FieldFlags flags = formField->getFlags(); + return flags.testFlag(PDFFormField::Combo) && flags.testFlag(PDFFormField::Edit); + } + + default: + break; + } + } + + return false; +} + +PDFFormWidgets PDFFormManager::getWidgets() const +{ + PDFFormWidgets result; + + auto functor = [&result](const PDFFormField* formField) + { + const PDFFormWidgets& widgets = formField->getWidgets(); + result.insert(result.cend(), widgets.cbegin(), widgets.cend()); + }; + apply(functor); + + return result; +} + +void PDFFormManager::apply(const std::function& functor) const +{ + m_form.apply(functor); +} + +void PDFFormManager::modify(const std::function& functor) const +{ + for (const PDFFormFieldPointer& childField : m_form.getFormFields()) + { + childField->modify(functor); + } +} + +void PDFFormManager::setFocusToEditor(PDFFormFieldWidgetEditor* editor) +{ + if (m_focusedEditor != editor) + { + if (m_focusedEditor) + { + m_focusedEditor->setFocus(false); + } + + m_focusedEditor = editor; + + if (m_focusedEditor) + { + m_focusedEditor->setFocus(true); + } + + // Request repaint, because focus changed + Q_ASSERT(m_proxy); + m_proxy->repaintNeeded(); + } +} + +bool PDFFormManager::focusNextPrevFormField(bool next) +{ + if (m_widgetEditors.empty()) + { + return false; + } + + std::vector::const_iterator newFocusIterator = m_widgetEditors.cend(); + + if (!m_focusedEditor) + { + // We are setting a new focus + if (next) + { + newFocusIterator = m_widgetEditors.cbegin(); + } + else + { + newFocusIterator = std::prev(m_widgetEditors.cend()); + } + } + else + { + std::vector::const_iterator it = std::find(m_widgetEditors.cbegin(), m_widgetEditors.cend(), m_focusedEditor); + Q_ASSERT(it != m_widgetEditors.cend()); + + if (next) + { + newFocusIterator = std::next(it); + } + else if (it != m_widgetEditors.cbegin()) + { + newFocusIterator = std::prev(it); + } + } + + if (newFocusIterator != m_widgetEditors.cend()) + { + setFocusToEditor(*newFocusIterator); + return true; + } + else + { + // Jakub Melka: We must remove focus out of editor, because + setFocusToEditor(nullptr); + } + + return false; +} + +bool PDFFormManager::isFocused(PDFObjectReference widget) const +{ + if (m_focusedEditor) + { + return m_focusedEditor->getWidgetAnnotation() == widget; + } + + return false; +} + +const PDFAction* PDFFormManager::getAction(PDFAnnotationAdditionalActions::Action actionType, const PDFFormWidget* widget) +{ + if (const PDFAction* action = widget->getAction(actionType)) + { + return action; + } + + for (const PDFFormField* formField = widget->getParent(); formField; formField = formField->getParentField()) + { + if (const PDFAction* action = formField->getAction(actionType)) + { + return action; + } + } + + return nullptr; +} + +void PDFFormManager::setFormFieldValue(PDFFormField::SetValueParameters parameters) +{ + if (!m_document) + { + // This can happen, when we are closing the document and some editor is opened + return; + } + + Q_ASSERT(parameters.invokingFormField); + Q_ASSERT(parameters.invokingWidget.isValid()); + + parameters.formManager = this; + parameters.scope = PDFFormField::SetValueParameters::Scope::User; + + PDFDocumentModifier modifier(m_document); + modifier.getBuilder()->setFormManager(this); + parameters.modifier = &modifier; + + if (parameters.invokingFormField->setValue(parameters)) + { + // We must also set dependent fields with same name + QString qualifiedFormFieldName = parameters.invokingFormField->getName(PDFFormField::NameType::FullyQualified); + if (!qualifiedFormFieldName.isEmpty()) + { + parameters.scope = PDFFormField::SetValueParameters::Scope::Internal; + auto updateDependentField = [¶meters, &qualifiedFormFieldName](PDFFormField* formField) + { + if (parameters.invokingFormField == formField) + { + // Do not update self + return; + } + + if (qualifiedFormFieldName == formField->getName(PDFFormField::NameType::FullyQualified)) + { + formField->setValue(parameters); + } + }; + modify(updateDependentField); + } + + if (modifier.finalize()) + { + emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } +} + +QRectF PDFFormManager::getWidgetRectangle(const PDFFormWidget& widget) const +{ + if (const PDFDictionary* dictionary = m_document->getDictionaryFromObject(m_document->getObjectByReference(widget.getWidget()))) + { + PDFDocumentDataLoaderDecorator loader(m_document); + return loader.readRectangle(dictionary->get("Rect"), QRectF()); + } + + return QRectF(); +} + + +void PDFFormManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + if (m_focusedEditor) + { + m_focusedEditor->shortcutOverrideEvent(widget, event); + } +} + +void PDFFormManager::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + if (m_focusedEditor) + { + m_focusedEditor->keyPressEvent(widget, event); + } +} + +void PDFFormManager::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + if (m_focusedEditor) + { + m_focusedEditor->keyReleaseEvent(widget, event); + } +} + +void PDFFormManager::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + + // We try to set focus on editor + if (event->button() == Qt::LeftButton) + { + setFocusToEditor(info.editor); + } + + info.editor->mousePressEvent(widget, event, info.mousePosition); + grabMouse(info, event); + } + else if (!isMouseGrabbed()) + { + // Mouse is not grabbed, user clicked elsewhere, unfocus editor + setFocusToEditor(nullptr); + } +} + +void PDFFormManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + info.editor->mouseDoubleClickEvent(widget, event, info.mousePosition); + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } + } +} + +void PDFFormManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + info.editor->mouseReleaseEvent(widget, event, info.mousePosition); + ungrabMouse(info, event); + } +} + +void PDFFormManager::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + info.editor->mouseMoveEvent(widget, event, info.mousePosition); + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } + + if (hasFormFieldWidgetText(info.editor->getWidgetAnnotation())) + { + m_mouseCursor = QCursor(Qt::IBeamCursor); + } + else + { + m_mouseCursor = QCursor(Qt::ArrowCursor); + } + } + else + { + m_mouseCursor = std::nullopt; + } +} + +void PDFFormManager::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + if (!hasForm()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + Q_ASSERT(info.editor); + info.editor->wheelEvent(widget, event, info.mousePosition); + + // We will accept mouse wheel events, if we are grabbing the mouse. + // We do not want to zoom in/zoom out while grabbing. + if (isMouseGrabbed()) + { + event->accept(); + } + } +} + +void PDFFormManager::grabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + if (event->type() == QEvent::MouseButtonDblClick) + { + // Double clicks doesn't grab the mouse + return; + } + + Q_ASSERT(event->type() == QEvent::MouseButtonPress); + + if (isMouseGrabbed()) + { + // If mouse is already grabbed, then when new mouse button is pressed, + // we just increase nesting level and accept the mouse event. We are + // accepting all mouse events, if mouse is grabbed. + ++m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + } + else if (event->isAccepted()) + { + // Event is accepted and we are not grabbing the mouse. We must start + // grabbing the mouse. + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0); + ++m_mouseGrabInfo.mouseGrabNesting; + m_mouseGrabInfo.info = info; + } +} + +void PDFFormManager::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_UNUSED(info); + Q_ASSERT(event->type() == QEvent::MouseButtonRelease); + + if (isMouseGrabbed()) + { + // Mouse is being grabbed, decrease nesting level. We must also accept + // mouse release event, because mouse is being grabbed. + --m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + + if (!isMouseGrabbed()) + { + m_mouseGrabInfo.info = MouseEventInfo(); + } + } + + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); +} + +PDFFormManager::MouseEventInfo PDFFormManager::getMouseEventInfo(QWidget* widget, QPoint point) +{ + MouseEventInfo result; + + if (isMouseGrabbed()) + { + result = m_mouseGrabInfo.info; + result.mousePosition = result.deviceToWidget.map(point); + return result; + } + + std::vector currentPages = m_proxy->getWidget()->getDrawWidget()->getCurrentPages(); + + if (!m_annotationManager->hasAnyPageAnnotation(currentPages)) + { + // All pages doesn't have annotation + return result; + } + + PDFWidgetSnapshot snapshot = m_proxy->getSnapshot(); + for (const PDFWidgetSnapshot::SnapshotItem& snapshotItem : snapshot.items) + { + const PDFAnnotationManager::PageAnnotations& pageAnnotations = m_annotationManager->getPageAnnotations(snapshotItem.pageIndex); + for (const PDFAnnotationManager::PageAnnotation& pageAnnotation : pageAnnotations.annotations) + { + if (pageAnnotation.annotation->isReplyTo()) + { + // Annotation is reply to another annotation, do not interact with it + continue; + } + + if (pageAnnotation.annotation->getType() != AnnotationType::Widget) + { + // Annotation is not widget annotation (form field), do not interact with it + continue; + } + + if (PDFFormField* formField = getFormFieldForWidget(pageAnnotation.annotation->getSelfReference())) + { + PDFFormFieldWidgetEditor* editor = getEditor(formField); + QRectF annotationRect = editor ? editor->getActiveEditorRectangle() : QRectF(); + if (!annotationRect.isValid()) + { + annotationRect = pageAnnotation.annotation->getRectangle(); + } + QMatrix widgetToDevice = m_annotationManager->prepareTransformations(snapshotItem.pageToDeviceMatrix, widget, pageAnnotation.annotation->getEffectiveFlags(), m_document->getCatalog()->getPage(snapshotItem.pageIndex), annotationRect); + + QPainterPath path; + path.addRect(annotationRect); + path = widgetToDevice.map(path); + + if (path.contains(point)) + { + result.formField = formField; + result.deviceToWidget = widgetToDevice.inverted(); + result.mousePosition = result.deviceToWidget.map(point); + result.editor = editor; + return result; + } + } + } + } + + return result; +} + +const std::optional& PDFFormManager::getCursor() const +{ + return m_mouseCursor; +} + +void PDFFormManager::clearEditors() +{ + qDeleteAll(m_widgetEditors); + m_widgetEditors.clear(); +} + +void PDFFormManager::updateFormWidgetEditors() +{ + setFocusToEditor(nullptr); + clearEditors(); + + for (PDFFormWidget widget : getWidgets()) + { + const PDFFormField* formField = widget.getParent(); + switch (formField->getFieldType()) + { + case PDFFormField::FieldType::Button: + { + Q_ASSERT(dynamic_cast(formField)); + const PDFFormFieldButton* formFieldButton = static_cast(formField); + switch (formFieldButton->getButtonType()) + { + case PDFFormFieldButton::ButtonType::PushButton: + { + m_widgetEditors.push_back(new PDFFormFieldPushButtonEditor(this, widget)); + break; + } + + case PDFFormFieldButton::ButtonType::RadioButton: + case PDFFormFieldButton::ButtonType::CheckBox: + { + m_widgetEditors.push_back(new PDFFormFieldCheckableButtonEditor(this, widget)); + break; + } + + default: + Q_ASSERT(false); + break; + } + + break; + } + + case PDFFormField::FieldType::Text: + { + m_widgetEditors.push_back(new PDFFormFieldTextBoxEditor(this, widget)); + break; + } + + case PDFFormField::FieldType::Choice: + { + Q_ASSERT(dynamic_cast(formField)); + const PDFFormFieldChoice* formFieldChoice = static_cast(formField); + if (formFieldChoice->isComboBox()) + { + m_widgetEditors.push_back(new PDFFormFieldComboBoxEditor(this, widget)); + } + else if (formFieldChoice->isListBox()) + { + m_widgetEditors.push_back(new PDFFormFieldListBoxEditor(this, widget)); + } + else + { + // Uknown field choice + Q_ASSERT(false); + } + + break; + } + + case PDFFormField::FieldType::Signature: + // Signature fields doesn't have editor + break; + + default: + Q_ASSERT(false); + break; + } + } +} + +void PDFFormManager::updateFieldValues() +{ + if (m_document) + { + for (const PDFFormFieldPointer& childField : m_form.getFormFields()) + { + childField->reloadValue(&m_document->getStorage(), PDFObject()); + } + + for (PDFFormFieldWidgetEditor* editor : m_widgetEditors) + { + editor->reloadValue(); + } + } +} + +PDFFormFieldWidgetEditor* PDFFormManager::getEditor(const PDFFormField* formField) const +{ + for (PDFFormFieldWidgetEditor* editor : m_widgetEditors) + { + if (editor->getFormField() == formField) + { + return editor; + } + } + + return nullptr; +} + +void PDFFormManager::performResetAction(const PDFActionResetForm* action) +{ + Q_ASSERT(action); + Q_ASSERT(m_document); + + PDFDocumentModifier modifier(m_document); + modifier.getBuilder()->setFormManager(this); + + auto resetFieldValue = [this, action, &modifier](PDFFormField* formField) + { + const PDFFormAction::FieldList& fieldList = action->getFieldList(); + + // Akceptujeme form field dle daného filtru? + bool accept = false; + bool isInFieldList = std::find(fieldList.fieldReferences.cbegin(), fieldList.fieldReferences.cend(), formField->getSelfReference()) != fieldList.fieldReferences.cend() || + fieldList.qualifiedNames.contains(formField->getName(PDFFormField::NameType::FullyQualified)); + switch (action->getFieldScope()) + { + case PDFFormAction::FieldScope::All: + accept = true; + break; + + case PDFFormAction::FieldScope::Include: + accept = isInFieldList; + break; + + case PDFFormAction::FieldScope::Exclude: + accept = !isInFieldList; + break; + + default: + Q_ASSERT(false); + break; + } + + if (accept) + { + PDFFormField::ResetValueParameters parameters; + parameters.formManager = this; + parameters.modifier = &modifier; + formField->resetValue(parameters); + } + }; + modify(resetFieldValue); + + if (modifier.finalize()) + { + emit documentModified(PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } +} + +PDFFormFieldWidgetEditor::PDFFormFieldWidgetEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + m_formManager(formManager), + m_formWidget(formWidget), + m_hasFocus(false) +{ + Q_ASSERT(m_formManager); +} + +void PDFFormFieldWidgetEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormFieldWidgetEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormFieldWidgetEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + Q_UNUSED(event); +} + +void PDFFormFieldWidgetEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + Q_UNUSED(mousePagePosition); +} + +void PDFFormFieldWidgetEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + Q_UNUSED(mousePagePosition); +} + +void PDFFormFieldWidgetEditor::mouseReleaseEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + Q_UNUSED(mousePagePosition); +} + +void PDFFormFieldWidgetEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + Q_UNUSED(mousePagePosition); +} + +void PDFFormFieldWidgetEditor::wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(widget); + Q_UNUSED(event); + Q_UNUSED(mousePagePosition); +} + +void PDFFormFieldWidgetEditor::setFocus(bool hasFocus) +{ + if (m_hasFocus != hasFocus) + { + m_hasFocus = hasFocus; + setFocusImpl(m_hasFocus); + } +} + +void PDFFormFieldWidgetEditor::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + Q_UNUSED(parameters); + Q_UNUSED(edit) +} + +void PDFFormFieldWidgetEditor::performKeypadNavigation(QWidget* widget, QKeyEvent* event) +{ + int key = event->key(); + + const bool isLeft = key == Qt::Key_Left; + const bool isRight = key == Qt::Key_Right; + const bool isDown = key == Qt::Key_Down; + const bool isHorizontal = isLeft || isRight; + + Qt::NavigationMode navigationMode = Qt::NavigationModeKeypadDirectional; +#ifdef QT_KEYPAD_NAVIGATION + navigationMode = QApplication::navigationMode(); +#endif + + switch (navigationMode) + { + case Qt::NavigationModeKeypadTabOrder: + { + // According the Qt's documentation, Up/Down arrows are used + // to change focus. So, if user pressed Left/Right, we must + // ignore this event. + if (isHorizontal) + { + return; + } + break; + } + + case Qt::NavigationModeKeypadDirectional: + // Default behaviour + break; + + default: + // Nothing happens + return; + } + + bool next = false; + if (isHorizontal) + { + switch (widget->layoutDirection()) + { + case Qt::LeftToRight: + case Qt::LayoutDirectionAuto: + next = isRight; + break; + + case Qt::RightToLeft: + next = isLeft; + break; + + default: + Q_ASSERT(false); + break; + } + } + else + { + // Vertical + next = isDown; + } + + m_formManager->focusNextPrevFormField(next); +} + +PDFFormFieldPushButtonEditor::PDFFormFieldPushButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + BaseClass(formManager, formWidget) +{ + +} + +PDFFormFieldAbstractButtonEditor::PDFFormFieldAbstractButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + BaseClass(formManager, formWidget) +{ + +} + +void PDFFormFieldAbstractButtonEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + switch (event->key()) + { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + { + event->accept(); + break; + } + + default: + break; + } +} + +void PDFFormFieldAbstractButtonEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Enter: + case Qt::Key_Return: + { + click(); + event->accept(); + break; + } + + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + { + performKeypadNavigation(widget, event); + break; + } + + default: + break; + } +} + +void PDFFormFieldAbstractButtonEditor::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + switch (event->key()) + { + case Qt::Key_Select: + case Qt::Key_Space: + { + click(); + event->accept(); + break; + } + + default: + break; + } +} + +void PDFFormFieldAbstractButtonEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(widget); + Q_UNUSED(mousePagePosition); + + if (event->button() == Qt::LeftButton) + { + click(); + event->accept(); + } +} + +void PDFFormFieldPushButtonEditor::click() +{ + if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::MousePressed, getFormWidget())) + { + emit m_formManager->actionTriggered(action); + } + else if (const PDFAction* action = m_formManager->getAction(PDFAnnotationAdditionalActions::Default, getFormWidget())) + { + emit m_formManager->actionTriggered(action); + } +} + +PDFFormFieldCheckableButtonEditor::PDFFormFieldCheckableButtonEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + BaseClass(formManager, formWidget) +{ + +} + +void PDFFormFieldCheckableButtonEditor::click() +{ + QByteArray newState; + + // First, check current state of form field + PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); + QByteArray state = loader.readName(m_formWidget.getParent()->getValue()); + QByteArray onState = PDFFormFieldButton::getOnAppearanceState(m_formManager, &m_formWidget); + if (state != onState) + { + newState = onState; + } + else + { + newState = PDFFormFieldButton::getOffAppearanceState(m_formManager, &m_formWidget); + } + + // We have a new state, try to apply it to form field + PDFFormField::SetValueParameters parameters; + parameters.formManager = m_formManager; + parameters.invokingWidget = m_formWidget.getWidget(); + parameters.invokingFormField = m_formWidget.getParent(); + parameters.scope = PDFFormField::SetValueParameters::Scope::User; + parameters.value = PDFObject::createName(qMove(newState)); + m_formManager->setFormFieldValue(parameters); +} + +PDFFormFieldComboBoxEditor::PDFFormFieldComboBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + BaseClass(formManager, formWidget), + m_textEdit(getTextEditFlags(formWidget.getParent()->getFlags())), + m_listBox(formWidget.getParent()->getFlags()), + m_listBoxVisible(false) +{ + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + initializeTextEdit(&m_textEdit); + + QFontMetricsF fontMetrics(m_textEdit.getFont()); + const qreal lineSpacing = fontMetrics.lineSpacing(); + + const int listBoxItems = qMin(7, int(parentField->getOptions().size())); + QRectF comboBoxRectangle = m_formManager->getWidgetRectangle(m_formWidget); + QRectF listBoxPopupRectangle = comboBoxRectangle; + listBoxPopupRectangle.translate(0, -lineSpacing * (listBoxItems)); + listBoxPopupRectangle.setHeight(lineSpacing * listBoxItems); + m_listBoxPopupRectangle = listBoxPopupRectangle; + m_dropDownButtonRectangle = comboBoxRectangle; + m_dropDownButtonRectangle.setLeft(m_dropDownButtonRectangle.right() - m_dropDownButtonRectangle.height()); + initializeListBox(&m_listBox); +} + +void PDFFormFieldComboBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + if (!m_hasFocus || !m_listBoxVisible) + { + m_textEdit.shortcutOverrideEvent(widget, event); + } + else + { + m_listBox.shortcutOverrideEvent(widget, event); + } +} + +void PDFFormFieldComboBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_ASSERT(!m_textEdit.isMultiline()); + + if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + // If popup list box is shown, then use text and hide it, + // otherwise close the editor. + if (m_listBoxVisible) + { + if (m_listBox.isSingleItemSelected()) + { + m_textEdit.setText(m_listBox.getSelectedItemText()); + } + m_listBoxVisible = false; + } + else + { + m_formManager->setFocusToEditor(nullptr); + } + + event->accept(); + } + else if (event->key() == Qt::Key_Escape) + { + // If popup list box is shown, hide it, if not, cancel the editing + if (m_listBoxVisible) + { + m_listBoxVisible = false; + } + else + { + reloadValue(); + m_formManager->setFocusToEditor(nullptr); + } + event->accept(); + } + else if (!m_listBoxVisible) + { + m_textEdit.keyPressEvent(widget, event); + } + else + { + m_listBox.keyPressEvent(widget, event); + } + + if (event->isAccepted()) + { + widget->update(); + } +} + +void PDFFormFieldComboBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + if (event->button() == Qt::LeftButton && m_hasFocus) + { + // If popup list box is visible, delegate mouse click to + // this list box only. + if (m_listBoxVisible) + { + const int index = m_listBox.getIndexFromWidgetPosition(mousePagePosition); + m_listBox.setCurrentItem(index, event->modifiers()); + + if (m_listBox.isSingleItemSelected()) + { + m_textEdit.setText(m_listBox.getSelectedItemText()); + } + + m_listBoxVisible = false; + } + else + { + // Do we click popup button? + if (m_dropDownButtonRectangle.contains(mousePagePosition)) + { + m_listBoxVisible = true; + updateListBoxSelection(); + } + else + { + const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); + m_textEdit.setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier); + } + } + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldComboBoxEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + if (event->button() == Qt::LeftButton) + { + const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); + m_textEdit.setCursorPosition(cursorPosition, false); + m_textEdit.setCursorPosition(m_textEdit.getCursorWordBackward(), false); + m_textEdit.setCursorPosition(m_textEdit.getCursorWordForward(), true); + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldComboBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + // We must test, if left mouse button is pressed while + // we are moving the mouse - if yes, then select the text. + if (event->buttons() & Qt::LeftButton && !m_listBoxVisible) + { + const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); + m_textEdit.setCursorPosition(cursorPosition, true); + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldComboBoxEditor::wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(mousePagePosition); + + if (m_listBoxVisible) + { + if (event->angleDelta().y() < 0) + { + m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() + m_listBox.getViewportRowCount())); + } + else + { + m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() - 1)); + } + + widget->update(); + event->accept(); + } +} + +void PDFFormFieldComboBoxEditor::updateListBoxSelection() +{ + QString text = m_textEdit.getText(); + + const int index = m_listBox.findOption(text); + if (m_listBox.isValidIndex(index)) + { + m_listBox.setSelection({ index }, true); + m_listBox.scrollTo(index); + } + else + { + m_listBox.setTopItemIndex(0); + m_listBox.setSelection({ }, true); + } +} + +void PDFFormFieldComboBoxEditor::reloadValue() +{ + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); + m_textEdit.setText(loader.readTextString(m_formWidget.getParent()->getValue(), QString())); + + m_listBoxVisible = false; +} + +void PDFFormFieldComboBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + if (edit) + { + // Draw text edit always + { + PDFPainterStateGuard guard(parameters.painter); + m_textEdit.draw(parameters, true); + } + + // Draw down button + { + PDFPainterStateGuard guard(parameters.painter); + + parameters.painter->translate(m_dropDownButtonRectangle.bottomLeft()); + parameters.painter->scale(1.0, -1.0); + + QStyleOption option; + option.state = QStyle::State_Enabled; + option.rect = QRect(0, 0, qFloor(m_dropDownButtonRectangle.width()), qFloor(m_dropDownButtonRectangle.height())); + QApplication::style()->drawPrimitive(QStyle::PE_IndicatorButtonDropDown, &option, parameters.painter, nullptr); + } + + if (m_listBoxVisible) + { + PDFPainterStateGuard guard(parameters.painter); + + AnnotationDrawParameters listBoxParameters = parameters; + listBoxParameters.boundingRectangle = m_listBoxPopupRectangle; + + QColor color = parameters.invertColors ? Qt::black : Qt::white; + listBoxParameters.painter->fillRect(listBoxParameters.boundingRectangle, color); + + m_listBox.draw(listBoxParameters, true); + } + } + else + { + // Draw static contents + PDFTextEditPseudowidget pseudowidget(m_formWidget.getParent()->getFlags()); + initializeTextEdit(&pseudowidget); + pseudowidget.draw(parameters, false); + } +} + +QRectF PDFFormFieldComboBoxEditor::getActiveEditorRectangle() const +{ + if (m_hasFocus && m_listBoxVisible) + { + return m_textEdit.getWidgetRect().united(m_listBoxPopupRectangle); + } + + return QRectF(); +} + +void PDFFormFieldComboBoxEditor::initializeTextEdit(PDFTextEditPseudowidget* textEdit) const +{ + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); + + QByteArray defaultAppearance = m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray()); + Qt::Alignment alignment = m_formManager->getForm()->getDefaultAlignment(); + + // Initialize text edit + textEdit->setAppearance(PDFAnnotationDefaultAppearance::parse(defaultAppearance), alignment, m_formManager->getWidgetRectangle(m_formWidget), 0); + textEdit->setText(loader.readTextString(parentField->getValue(), QString())); +} + +void PDFFormFieldComboBoxEditor::initializeListBox(PDFListBoxPseudowidget* listBox) const +{ + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + // Initialize popup list box + listBox->initialize(m_textEdit.getFont(), m_textEdit.getFontColor(), m_formManager->getForm()->getDefaultAlignment(), m_listBoxPopupRectangle, parentField->getOptions(), 0, { }); +} + +void PDFFormFieldComboBoxEditor::setFocusImpl(bool focused) +{ + if (focused) + { + m_textEdit.setCursorPosition(m_textEdit.getPositionEnd(), false); + m_textEdit.performSelectAll(); + } + else if (!m_formManager->isCommitDisabled()) + { + // If text has been changed, then commit it + PDFObject object = PDFObjectFactory::createTextString(m_textEdit.getText()); + + if (object != m_formWidget.getParent()->getValue()) + { + PDFFormField::SetValueParameters parameters; + parameters.formManager = m_formManager; + parameters.invokingWidget = m_formWidget.getWidget(); + parameters.invokingFormField = m_formWidget.getParent(); + parameters.scope = PDFFormField::SetValueParameters::Scope::User; + parameters.value = qMove(object); + m_formManager->setFormFieldValue(parameters); + } + } +} + +PDFFormField::FieldFlags PDFFormFieldComboBoxEditor::getTextEditFlags(PDFFormField::FieldFlags flags) +{ + if (flags.testFlag(PDFFormField::ReadOnly) || !flags.testFlag(PDFFormField::Edit)) + { + return PDFFormField::ReadOnly; + } + + return PDFFormField::None; +} + +void PDFFormFieldTextBoxEditor::initializeTextEdit(PDFTextEditPseudowidget* textEdit) const +{ + const PDFFormFieldText* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); + + QByteArray defaultAppearance = parentField->getDefaultAppearance(); + if (defaultAppearance.isEmpty()) + { + defaultAppearance = m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray()); + } + Qt::Alignment alignment = parentField->getAlignment(); + if (!(alignment & Qt::AlignHorizontal_Mask)) + { + alignment |= m_formManager->getForm()->getDefaultAlignment() & Qt::AlignHorizontal_Mask; + } + + // Initialize text edit + textEdit->setAppearance(PDFAnnotationDefaultAppearance::parse(defaultAppearance), alignment, m_formManager->getWidgetRectangle(m_formWidget), parentField->getTextMaximalLength()); + textEdit->setText(loader.readTextString(parentField->getValue(), QString())); +} + +void PDFFormFieldTextBoxEditor::setFocusImpl(bool focused) +{ + if (focused) + { + m_textEdit.setCursorPosition(m_textEdit.getPositionEnd(), false); + m_textEdit.performSelectAll(); + } + else if (!m_textEdit.isPassword() && !m_formManager->isCommitDisabled()) // Passwords are not saved in the document + { + // If text has been changed, then commit it + PDFObject object = PDFObjectFactory::createTextString(m_textEdit.getText()); + + if (object != m_formWidget.getParent()->getValue()) + { + PDFFormField::SetValueParameters parameters; + parameters.formManager = m_formManager; + parameters.invokingWidget = m_formWidget.getWidget(); + parameters.invokingFormField = m_formWidget.getParent(); + parameters.scope = PDFFormField::SetValueParameters::Scope::User; + parameters.value = qMove(object); + m_formManager->setFormFieldValue(parameters); + } + } +} + +PDFFormFieldTextBoxEditor::PDFFormFieldTextBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + BaseClass(formManager, formWidget), + m_textEdit(formWidget.getParent()->getFlags()) +{ + initializeTextEdit(&m_textEdit); +} + +void PDFFormFieldTextBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + m_textEdit.shortcutOverrideEvent(widget, event); +} + +void PDFFormFieldTextBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + if (!m_textEdit.isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + // Commit the editor + m_formManager->setFocusToEditor(nullptr); + event->accept(); + return; + } + + if (event->key() == Qt::Key_Escape) + { + // Cancel the editor + reloadValue(); + m_formManager->setFocusToEditor(nullptr); + event->accept(); + return; + } + + m_textEdit.keyPressEvent(widget, event); + + if (event->isAccepted()) + { + widget->update(); + } +} + +void PDFFormFieldTextBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + if (event->button() == Qt::LeftButton) + { + const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); + m_textEdit.setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier); + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldTextBoxEditor::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + if (event->button() == Qt::LeftButton) + { + const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); + m_textEdit.setCursorPosition(cursorPosition, false); + m_textEdit.setCursorPosition(m_textEdit.getCursorWordBackward(), false); + m_textEdit.setCursorPosition(m_textEdit.getCursorWordForward(), true); + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldTextBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + // We must test, if left mouse button is pressed while + // we are moving the mouse - if yes, then select the text. + if (event->buttons() & Qt::LeftButton) + { + const int cursorPosition = m_textEdit.getCursorPositionFromWidgetPosition(mousePagePosition, m_hasFocus); + m_textEdit.setCursorPosition(cursorPosition, true); + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldTextBoxEditor::reloadValue() +{ + PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); + m_textEdit.setText(loader.readTextString(m_formWidget.getParent()->getValue(), QString())); +} + +void PDFFormFieldTextBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + if (edit) + { + m_textEdit.draw(parameters, true); + } + else + { + // Draw static contents + PDFTextEditPseudowidget pseudowidget(m_formWidget.getParent()->getFlags()); + initializeTextEdit(&pseudowidget); + pseudowidget.draw(parameters, false); + } +} + +PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) : + m_flags(flags), + m_selectionStart(0), + m_selectionEnd(0), + m_positionCursor(0), + m_maxTextLength(0) +{ + m_textLayout.setCacheEnabled(true); + m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter); +} + +void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste, + QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar, + QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine, + QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine, + QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument, + QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar, + QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine, + QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine, + QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument, + QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord, + QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace }; + + if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) + { + event->accept(); + return; + } + + switch (event->key()) + { + case Qt::Key_Direction_L: + case Qt::Key_Direction_R: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + event->accept(); + break; + + default: + break; + } + + if (!event->text().isEmpty()) + { + event->accept(); + for (const QChar& character : event->text()) + { + if (!character.isPrint()) + { + event->ignore(); + break; + } + } + } +} + +void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + /* + We will support following key sequences: + Delete + Cut, + Copy, + Paste, + SelectAll, + MoveToNextChar, + MoveToPreviousChar, + MoveToNextWord, + MoveToPreviousWord, + MoveToNextLine, + MoveToPreviousLine, + MoveToStartOfLine, + MoveToEndOfLine, + MoveToStartOfBlock, + MoveToEndOfBlock, + MoveToStartOfDocument, + MoveToEndOfDocument, + SelectNextChar, + SelectPreviousChar, + SelectNextWord, + SelectPreviousWord, + SelectNextLine, + SelectPreviousLine, + SelectStartOfLine, + SelectEndOfLine, + SelectStartOfBlock, + SelectEndOfBlock, + SelectStartOfDocument, + SelectEndOfDocument, + DeleteStartOfWord, + DeleteEndOfWord, + DeleteEndOfLine, + Deselect, + DeleteCompleteLine, + Backspace, + * */ + + event->accept(); + + if (event == QKeySequence::Delete) + { + performDelete(); + } + else if (event == QKeySequence::Cut) + { + performCut(); + } + else if (event == QKeySequence::Copy) + { + performCopy(); + } + else if (event == QKeySequence::Paste) + { + performPaste(); + } + else if (event == QKeySequence::SelectAll) + { + setSelection(0, getTextLength()); + } + else if (event == QKeySequence::MoveToNextChar) + { + setCursorPosition(getCursorCharacterForward(), false); + } + else if (event == QKeySequence::MoveToPreviousChar) + { + setCursorPosition(getCursorCharacterBackward(), false); + } + else if (event == QKeySequence::MoveToNextWord) + { + setCursorPosition(getCursorWordForward(), false); + } + else if (event == QKeySequence::MoveToPreviousWord) + { + setCursorPosition(getCursorWordBackward(), false); + } + else if (event == QKeySequence::MoveToNextLine) + { + setCursorPosition(getCursorLineDown(), false); + } + else if (event == QKeySequence::MoveToPreviousLine) + { + setCursorPosition(getCursorLineUp(), false); + } + else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) + { + setCursorPosition(getCursorLineStart(), false); + } + else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) + { + setCursorPosition(getCursorLineEnd(), false); + } + else if (event == QKeySequence::MoveToStartOfDocument) + { + setCursorPosition(getCursorDocumentStart(), false); + } + else if (event == QKeySequence::MoveToEndOfDocument) + { + setCursorPosition(getCursorDocumentEnd(), false); + } + else if (event == QKeySequence::SelectNextChar) + { + setCursorPosition(getCursorCharacterForward(), true); + } + else if (event == QKeySequence::SelectPreviousChar) + { + setCursorPosition(getCursorCharacterBackward(), true); + } + else if (event == QKeySequence::SelectNextWord) + { + setCursorPosition(getCursorWordForward(), true); + } + else if (event == QKeySequence::SelectPreviousWord) + { + setCursorPosition(getCursorWordBackward(), true); + } + else if (event == QKeySequence::SelectNextLine) + { + setCursorPosition(getCursorLineDown(), true); + } + else if (event == QKeySequence::SelectPreviousLine) + { + setCursorPosition(getCursorLineUp(), true); + } + else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) + { + setCursorPosition(getCursorLineStart(), true); + } + else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) + { + setCursorPosition(getCursorLineEnd(), true); + } + else if (event == QKeySequence::SelectStartOfDocument) + { + setCursorPosition(getCursorDocumentStart(), true); + } + else if (event == QKeySequence::SelectEndOfDocument) + { + setCursorPosition(getCursorDocumentEnd(), true); + } + else if (event == QKeySequence::DeleteStartOfWord) + { + if (!isReadonly()) + { + setCursorPosition(getCursorWordBackward(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::DeleteEndOfWord) + { + if (!isReadonly()) + { + setCursorPosition(getCursorWordForward(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::DeleteEndOfLine) + { + if (!isReadonly()) + { + setCursorPosition(getCursorLineEnd(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::Deselect) + { + clearSelection(); + } + else if (event == QKeySequence::DeleteCompleteLine) + { + if (!isReadonly()) + { + m_selectionStart = getCurrentLineTextStart(); + m_selectionEnd = getCurrentLineTextEnd(); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace) + { + performBackspace(); + } + else if (event->key() == Qt::Key_Direction_L) + { + QTextOption option = m_textLayout.textOption(); + option.setTextDirection(Qt::LeftToRight); + m_textLayout.setTextOption(qMove(option)); + updateTextLayout(); + } + else if (event->key() == Qt::Key_Direction_R) + { + QTextOption option = m_textLayout.textOption(); + option.setTextDirection(Qt::LeftToRight); + m_textLayout.setTextOption(qMove(option)); + updateTextLayout(); + } + else if (event->key() == Qt::Key_Up) + { + setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Down) + { + setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Left) + { + const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward(); + setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Right) + { + const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward(); + setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + performInsertText(QString::fromUtf16(u"\u2028")); + } + else + { + QString text = event->text(); + if (!text.isEmpty()) + { + performInsertText(text); + } + else + { + event->ignore(); + } + } +} + +void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength) +{ + if (selectionLength > 0) + { + // We are selecting to the right + m_selectionStart = startPosition; + m_selectionEnd = qMin(startPosition + selectionLength, getTextLength()); + m_positionCursor = m_selectionEnd; + } + else if (selectionLength < 0) + { + // We are selecting to the left + m_selectionStart = qMax(startPosition + selectionLength, 0); + m_selectionEnd = startPosition; + m_positionCursor = m_selectionStart; + } + else + { + // Clear the selection + m_selectionStart = 0; + m_selectionEnd = 0; + m_positionCursor = startPosition; + } +} + +void PDFTextEditPseudowidget::setCursorPosition(int position, bool select) +{ + if (select) + { + const bool isTextSelected = this->isTextSelected(); + const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor; + const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor; + + // Do we have selected text, and cursor is at the end of selected text? + // In this case, we must preserve start of the selection (we are manipulating + // with the end of selection. + if (isCursorAtEndOfSelection) + { + m_selectionStart = qMin(m_selectionStart, position); + m_selectionEnd = qMax(m_selectionStart, position); + } + else if (isCursorAtStartOfSelection) + { + // We must preserve end of the text selection, because we are manipulating + // with start of text selection. + m_selectionStart = qMin(m_selectionEnd, position); + m_selectionEnd = qMax(m_selectionEnd, position); + } + else + { + // Otherwise we are manipulating with cursor + m_selectionStart = qMin(m_positionCursor, position); + m_selectionEnd = qMax(m_positionCursor, position); + } + } + + // Why we are clearing text selection, even if we doesn't have it? + // We can have, for example m_selectionStart == m_selectionEnd == 3, + // and we want to have it both zero. + if (!select || !isTextSelected()) + { + clearSelection(); + } + + m_positionCursor = position; +} + +void PDFTextEditPseudowidget::setText(const QString& text) +{ + clearSelection(); + m_editText = text; + setCursorPosition(getPositionEnd(), false); + updateTextLayout(); +} + +void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength) +{ + // Set appearance + qreal fontSize = appearance.getFontSize(); + if (qFuzzyIsNull(fontSize)) + { + fontSize = rect.height(); + } + + QFont font(appearance.getFontName()); + font.setHintingPreference(QFont::PreferNoHinting); + font.setPixelSize(qCeil(fontSize)); + font.setStyleStrategy(QFont::ForceOutline); + m_textLayout.setFont(font); + + QTextOption option = m_textLayout.textOption(); + option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); + option.setAlignment(textAlignment); + option.setUseDesignMetrics(true); + m_textLayout.setTextOption(option); + + m_textColor = appearance.getFontColor(); + if (!m_textColor.isValid()) + { + m_textColor = Qt::black; + } + + m_maxTextLength = maxTextLength; + m_widgetRect = rect; +} + +void PDFTextEditPseudowidget::performCut() +{ + if (isReadonly()) + { + return; + } + + performCopy(); + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performCopy() +{ + if (isTextSelected() && !isPassword()) + { + QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard); + } +} + +void PDFTextEditPseudowidget::performPaste() +{ + // We always insert text, even if it is empty. Because we want + // to erase selected text. + performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard)); +} + +void PDFTextEditPseudowidget::performClear() +{ + if (isReadonly()) + { + return; + } + + performSelectAll(); + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performSelectAll() +{ + m_selectionStart = getPositionStart(); + m_selectionEnd = getPositionEnd(); +} + +void PDFTextEditPseudowidget::performBackspace() +{ + if (isReadonly()) + { + return; + } + + // If we have selection, then delete selected text. If we do not have + // selection, then we delete previous character. + if (!isTextSelected()) + { + setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); + } + + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performDelete() +{ + if (isReadonly()) + { + return; + } + + // If we have selection, then delete selected text. If we do not have + // selection, then we delete previous character. + if (!isTextSelected()) + { + setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); + } + + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performRemoveSelectedText() +{ + if (isTextSelected()) + { + m_editText.remove(m_selectionStart, getSelectionLength()); + setCursorPosition(m_selectionStart, false); + clearSelection(); + updateTextLayout(); + } +} + +void PDFTextEditPseudowidget::performInsertText(const QString& text) +{ + if (isReadonly()) + { + return; + } + + // Insert text at the cursor + performRemoveSelectedText(); + m_editText.insert(m_positionCursor, text); + setCursorPosition(m_positionCursor + text.length(), false); + updateTextLayout(); +} + +QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const +{ + QMatrix matrix; + + matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); + matrix.scale(1.0, -1.0); + + if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor)) + { + // Jakub Melka: we must scroll the control, so cursor is always + // visible in the widget area. If we are not editing, this not the + // case, because we always show the text from the beginning. + + const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + if (line.isValid()) + { + const qreal xCursorPosition = line.cursorToX(m_positionCursor); + if (xCursorPosition >= m_widgetRect.width()) + { + const qreal delta = xCursorPosition - m_widgetRect.width(); + matrix.translate(-delta, 0.0); + } + + // Check, if we aren't outside of y position + const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height(); + const qreal lineBottom = lineSpacing * (line.lineNumber() + 1); + + if (lineBottom >= m_widgetRect.height()) + { + const qreal delta = lineBottom - m_widgetRect.height(); + matrix.translate(0.0, -delta); + } + } + } + + if (!isMultiline() && !isComb()) + { + // If text is single line, then adjust text position to the vertical center + QTextLine textLine = m_textLayout.lineAt(0); + if (textLine.isValid()) + { + const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height(); + const qreal textBoxHeight = m_widgetRect.height(); + + if (lineSpacing < textBoxHeight) + { + const qreal delta = (textBoxHeight - lineSpacing) * 0.5; + matrix.translate(0.0, delta); + } + } + } + + return matrix; +} + +std::vector PDFTextEditPseudowidget::getCursorPositions() const +{ + std::vector result; + result.reserve(m_editText.length()); + result.push_back(0); + + int currentPos = 0; + while (currentPos < m_editText.length()) + { + currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); + result.push_back(currentPos); + } + + return result; +} + +void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + pdf::PDFPainterStateGuard guard(parameters.painter); + parameters.boundingRectangle = parameters.annotation->getRectangle(); + + QPalette palette = QApplication::palette(); + + auto getAdjustedColor = [¶meters](QColor color) + { + if (parameters.invertColors) + { + return invertColor(color); + } + + return color; + }; + + QPainter* painter = parameters.painter; + + if (edit) + { + pdf::PDFPainterStateGuard guard(painter); + painter->setPen(getAdjustedColor(Qt::black)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(parameters.boundingRectangle); + } + + painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); + painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true); + painter->setPen(getAdjustedColor(Qt::black)); + + if (isComb()) + { + const qreal combCount = qMax(m_maxTextLength, 1); + qreal combWidth = m_widgetRect.width() / combCount; + QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); + painter->setFont(m_textLayout.font()); + + QColor textColor = getAdjustedColor(m_textColor); + QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); + QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); + + std::vector positions = getCursorPositions(); + for (size_t i = 1; i < positions.size(); ++i) + { + if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd) + { + // We are in text selection + painter->fillRect(combRect, highlightColor); + painter->setPen(highlightTextColor); + } + else + { + // We are not in text selection + painter->setPen(textColor); + } + + int length = positions[i] - positions[i - 1]; + QString text = m_displayText.mid(positions[i - 1], length); + painter->drawText(combRect, Qt::AlignCenter, text); + + // Draw the cursor? + if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i]) + { + QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); + painter->fillRect(cursorRect, textColor); + } + + combRect.translate(combWidth, 0.0); + } + + // Draw the cursor onto next unfilled cell? + if (edit && m_positionCursor == getPositionEnd()) + { + QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); + painter->fillRect(cursorRect, textColor); + } + } + else + { + QVector selections; + + QTextLayout::FormatRange defaultFormat; + defaultFormat.start = getPositionStart(); + defaultFormat.length = getTextLength(); + defaultFormat.format.clearBackground(); + defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); + + // If we are editing, draw selections + if (edit && isTextSelected()) + { + QTextLayout::FormatRange before = defaultFormat; + QTextLayout::FormatRange after = defaultFormat; + + before.start = getPositionStart(); + before.length = m_selectionStart; + after.start = m_selectionEnd; + after.length = getTextLength() - m_selectionEnd; + + QTextLayout::FormatRange selectedFormat = defaultFormat; + selectedFormat.start = m_selectionStart; + selectedFormat.length = getSelectionLength(); + selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); + selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); + + selections = { before, selectedFormat, after}; + } + else + { + selections.push_back(defaultFormat); + } + + // Draw text + m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF()); + + // If we are editing, also draw text + if (edit && !isReadonly()) + { + m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor); + } + } +} + +int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const +{ + QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit); + QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted(); + + QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point); + + if (isComb()) + { + // If it is comb, then characters are spaced equidistantly + const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width()); + const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width())); + std::vector positions = getCursorPositions(); + if (position < positions.size()) + { + return positions[position]; + } + + return positions.back(); + } + else if (m_textLayout.lineCount() > 0) + { + QTextLine line; + qreal yPos = 0.0; + + // Find line under cursor + for (int i = 0; i < m_textLayout.lineCount(); ++i) + { + QTextLine currentLine = m_textLayout.lineAt(i); + const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height(); + const qreal yNextPos = yPos + lineSpacing; + + if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos) + { + line = currentLine; + break; + } + + yPos = yNextPos; + } + + // If no line is found, select last line + if (!line.isValid()) + { + if (textBoxPoint.y() < 0.0) + { + line = m_textLayout.lineAt(0); + } + else + { + line = m_textLayout.lineAt(m_textLayout.lineCount() - 1); + } + } + + return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters); + } + + return 0; +} + +void PDFTextEditPseudowidget::updateTextLayout() +{ + // Prepare display text + if (isPassword()) + { + m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter); + } + else + { + m_displayText = m_editText; + } + + // Perform text layout + m_textLayout.clearLayout(); + m_textLayout.setText(m_displayText); + m_textLayout.beginLayout(); + + QPointF textLinePosition(0.0, 0.0); + + while (true) + { + QTextLine textLine = m_textLayout.createLine(); + if (!textLine.isValid()) + { + // We are finished with layout + break; + } + + textLinePosition.ry() += textLine.leading(); + textLine.setLineWidth(m_widgetRect.width()); + textLine.setPosition(textLinePosition); + textLinePosition.ry() += textLine.height(); + } + m_textLayout.endLayout(); + + // Check length + if (m_maxTextLength > 0) + { + int length = 0; + int currentPos = 0; + + while (currentPos < m_editText.length()) + { + currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); + ++length; + + if (length == m_maxTextLength) + { + break; + } + } + + if (currentPos < m_editText.length()) + { + m_editText = m_editText.left(currentPos); + m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd()); + updateTextLayout(); + } + } +} + +int PDFTextEditPseudowidget::getSingleStepForward() const +{ + // If direction is right-to-left, then move backward (because + // text is painted from right to left. + return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1; +} + +int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const +{ + int cursor = referencePosition; + + if (steps > 0) + { + for (int i = 0; i < steps; ++i) + { + cursor = m_textLayout.nextCursorPosition(cursor, mode); + } + } + else if (steps < 0) + { + for (int i = 0; i < -steps; ++i) + { + cursor = m_textLayout.previousCursorPosition(cursor, mode); + } + } + + return cursor; +} + +int PDFTextEditPseudowidget::getCurrentLineTextStart() const +{ + return m_textLayout.lineForTextPosition(m_positionCursor).textStart(); +} + +int PDFTextEditPseudowidget::getCurrentLineTextEnd() const +{ + QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor); + return textLine.textStart() + textLine.textLength(); +} + +int PDFTextEditPseudowidget::getCursorLineUp() const +{ + QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + const int lineIndex = line.lineNumber() - 1; + + if (lineIndex >= 0) + { + QTextLine upLine = m_textLayout.lineAt(lineIndex); + return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); + } + + return m_positionCursor; +} + +int PDFTextEditPseudowidget::getCursorLineDown() const +{ + QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + const int lineIndex = line.lineNumber() + 1; + + if (lineIndex < m_textLayout.lineCount()) + { + QTextLine downLine = m_textLayout.lineAt(lineIndex); + return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); + } + + return m_positionCursor; +} + +bool PDFFormFieldText::setValue(const SetValueParameters& parameters) +{ + // If form field is readonly, and scope is user (form field is changed by user, + // not by calculated value), then we must not allow value change. + if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User) + { + return false; + } + + Q_ASSERT(parameters.formManager); + Q_ASSERT(parameters.modifier); + + PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); + parameters.modifier->markFormFieldChanged(); + builder->setFormFieldValue(getSelfReference(), parameters.value); + m_value = parameters.value; + + // Change widget appearance states + for (const PDFFormWidget& formWidget : getWidgets()) + { + builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); + parameters.modifier->markAnnotationsChanged(); + } + + return true; +} + +void PDFFormFieldText::resetValue(const ResetValueParameters& parameters) +{ + Q_ASSERT(parameters.formManager); + Q_ASSERT(parameters.modifier); + + PDFObject defaultValue = getDefaultValue(); + PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); + parameters.modifier->markFormFieldChanged(); + builder->setFormFieldValue(getSelfReference(), defaultValue); + m_value = defaultValue; + + // Change widget appearance states + for (const PDFFormWidget& formWidget : getWidgets()) + { + builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); + parameters.modifier->markAnnotationsChanged(); + } +} + +PDFListBoxPseudowidget::PDFListBoxPseudowidget(PDFFormField::FieldFlags flags) : + m_flags(flags), + m_topIndex(0), + m_currentIndex(0) +{ + +} + +void PDFListBoxPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + if (event == QKeySequence::Copy) + { + event->accept(); + return; + } + + if (event == QKeySequence::SelectAll) + { + // Select All can be processed only, if multiselection is allowed + if (isMultiSelect()) + { + event->accept(); + } + return; + } + + switch (event->key()) + { + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + event->accept(); + break; + + default: + break; + } +} + +void PDFListBoxPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + event->accept(); + + if (event == QKeySequence::Copy) + { + // Copy the item text + if (m_currentIndex >= 0 && m_currentIndex < m_options.size()) + { + QApplication::clipboard()->setText(m_options[m_currentIndex].userString, QClipboard::Clipboard); + } + return; + } + + if (event == QKeySequence::SelectAll && isMultiSelect()) + { + std::set selection; + for (int i = 0; i < m_options.size(); ++i) + { + selection.insert(i); + } + setSelection(qMove(selection), false); + return; + } + + switch (event->key()) + { + case Qt::Key_Home: + setCurrentItem(getStartItemIndex(), event->modifiers()); + break; + + case Qt::Key_End: + setCurrentItem(getEndItemIndex(), event->modifiers()); + break; + + case Qt::Key_Up: + moveCurrentItemIndexByOffset(-getSingleStep(), event->modifiers()); + break; + + case Qt::Key_Down: + moveCurrentItemIndexByOffset(getSingleStep(), event->modifiers()); + break; + + case Qt::Key_PageUp: + moveCurrentItemIndexByOffset(-getPageStep(), event->modifiers()); + break; + + case Qt::Key_PageDown: + moveCurrentItemIndexByOffset(getPageStep(), event->modifiers()); + break; + + default: + event->ignore(); + break; + } +} + +void PDFListBoxPseudowidget::initialize(QFont font, QColor fontColor, Qt::Alignment textAlignment, QRectF rect, + const Options& options, int topIndex, std::set selection) +{ + m_font = font; + + QFontMetricsF fontMetrics(m_font); + m_lineSpacing = fontMetrics.lineSpacing(); + + m_textColor = fontColor; + if (!m_textColor.isValid()) + { + m_textColor = Qt::black; + } + + m_textAlignment = textAlignment; + m_widgetRect = rect; + m_options = options; + m_topIndex = getValidIndex(topIndex); + m_selection = qMove(selection); + m_currentIndex = m_topIndex; +} + +void PDFListBoxPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, + Qt::Alignment textAlignment, + QRectF rect, + const PDFListBoxPseudowidget::Options& options, + int topIndex, + std::set selection) +{ + // Set appearance + qreal fontSize = appearance.getFontSize(); + if (qFuzzyIsNull(fontSize)) + { + fontSize = qMax(rect.height() / qMax(options.size(), 1), qreal(12.0)); + } + + QColor fontColor = appearance.getFontColor(); + + QFont font(appearance.getFontName()); + font.setHintingPreference(QFont::PreferNoHinting); + font.setPixelSize(qCeil(fontSize)); + font.setStyleStrategy(QFont::ForceOutline); + + initialize(font, fontColor, textAlignment, rect, options, topIndex, qMove(selection)); +} + +QMatrix PDFListBoxPseudowidget::createListBoxTransformMatrix() const +{ + QMatrix matrix; + matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); + matrix.scale(1.0, -1.0); + return matrix; +} + +void PDFListBoxPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + pdf::PDFPainterStateGuard guard(parameters.painter); + + if (!parameters.boundingRectangle.isValid()) + { + parameters.boundingRectangle = parameters.annotation->getRectangle(); + } + + QPalette palette = QApplication::palette(); + + auto getAdjustedColor = [¶meters](QColor color) + { + if (parameters.invertColors) + { + return invertColor(color); + } + + return color; + }; + + QMatrix matrix = createListBoxTransformMatrix(); + + QPainter* painter = parameters.painter; + + if (edit) + { + pdf::PDFPainterStateGuard guard(painter); + painter->setPen(getAdjustedColor(Qt::black)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(parameters.boundingRectangle); + } + + painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); + painter->setWorldMatrix(matrix, true); + painter->setPen(getAdjustedColor(m_textColor)); + painter->setFont(m_font); + + QColor textColor = getAdjustedColor(m_textColor); + QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); + QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); + + QRectF rect(0, 0, m_widgetRect.width(), m_lineSpacing); + for (int i = m_topIndex; i < m_options.size(); ++i) + { + if (m_selection.count(i)) + { + painter->fillRect(rect, highlightColor); + painter->setPen(highlightTextColor); + } + else + { + painter->setPen(textColor); + } + + painter->drawText(rect, m_textAlignment | Qt::TextSingleLine, m_options[i].userString); + + if (edit && m_currentIndex == i) + { + pdf::PDFPainterStateGuard guard(painter); + painter->setBrush(Qt::NoBrush); + painter->setPen(Qt::DotLine); + painter->drawRect(rect); + } + + rect.translate(0, m_lineSpacing); + } +} + +void PDFListBoxPseudowidget::setSelection(std::set selection, bool force) +{ + if (isReadonly() && !force) + { + // Field is readonly + return; + } + + // Jakub Melka: Here should be also commit, when flag CommitOnSelectionChange is set, + // but we do it only, when widget loses focus (so no need to update appearance often). + // I hope it will be OK. + + m_selection = qMove(selection); +} + +void PDFListBoxPseudowidget::setTopItemIndex(int index) +{ + m_topIndex = getValidIndex(index); +} + +bool PDFListBoxPseudowidget::isVisible(int index) const +{ + return index >= m_topIndex && index < m_topIndex + getViewportRowCount(); +} + +void PDFListBoxPseudowidget::scrollTo(int index) +{ + while (!isVisible(index)) + { + if (index < m_topIndex) + { + --m_topIndex; + } + else + { + ++m_topIndex; + } + } +} + +void PDFListBoxPseudowidget::setCurrentItem(int index, Qt::KeyboardModifiers modifiers) +{ + index = getValidIndex(index); + + if (m_currentIndex == index) + { + return; + } + + std::set newSelection; + if (!isMultiSelect() || !modifiers.testFlag(Qt::ShiftModifier)) + { + newSelection = { index }; + } + else + { + int indexFrom = index; + int indexTo = index; + + if (hasContinuousSelection()) + { + indexFrom = qMin(index, *m_selection.begin()); + indexTo = qMax(index, *m_selection.rbegin()); + } + else + { + indexFrom = qMin(index, m_currentIndex); + indexTo = qMax(index, m_currentIndex); + } + + for (int i = indexFrom; i <= indexTo; ++i) + { + newSelection.insert(i); + } + } + + m_currentIndex = index; + setSelection(qMove(newSelection), false); + scrollTo(m_currentIndex); +} + +QString PDFListBoxPseudowidget::getSelectedItemText() const +{ + if (m_selection.size() == 1) + { + const int selectedIndex = *m_selection.begin(); + return m_options[selectedIndex].userString; + } + + return QString(); +} + +int PDFListBoxPseudowidget::findOption(const QString& option) const +{ + auto it = std::find_if(m_options.cbegin(), m_options.cend(), [&option](const auto& currentOption) { return currentOption.userString == option; }); + if (it != m_options.cend()) + { + return static_cast(std::distance(m_options.cbegin(), it)); + } + + return -1; +} + +bool PDFListBoxPseudowidget::hasContinuousSelection() const +{ + if (m_selection.empty()) + { + return false; + } + + return *m_selection.rbegin() - *m_selection.begin() + 1 == m_selection.size(); +} + +int PDFListBoxPseudowidget::getValidIndex(int index) const +{ + return qBound(getStartItemIndex(), index, getEndItemIndex()); +} + +int PDFListBoxPseudowidget::getIndexFromWidgetPosition(const QPointF& point) const +{ + QMatrix widgetToPageMatrix = createListBoxTransformMatrix(); + QMatrix pageToWidgetMatrix = widgetToPageMatrix.inverted(); + + QPointF widgetPoint = pageToWidgetMatrix.map(point); + const qreal y = widgetPoint.y(); + const int visualIndex = qFloor(y / m_lineSpacing); + return m_topIndex + visualIndex; +} + +PDFFormFieldListBoxEditor::PDFFormFieldListBoxEditor(PDFFormManager* formManager, PDFFormWidget formWidget) : + BaseClass(formManager, formWidget), + m_listBox(formWidget.getParent()->getFlags()) +{ + initializeListBox(&m_listBox); +} + +void PDFFormFieldListBoxEditor::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + m_listBox.shortcutOverrideEvent(widget, event); +} + +void PDFFormFieldListBoxEditor::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + // Commit the editor + m_formManager->setFocusToEditor(nullptr); + event->accept(); + return; + } + + if (event->key() == Qt::Key_Escape) + { + // Cancel the editor + reloadValue(); + m_formManager->setFocusToEditor(nullptr); + event->accept(); + return; + } + + m_listBox.keyPressEvent(widget, event); + + if (event->isAccepted()) + { + widget->update(); + } +} + +void PDFFormFieldListBoxEditor::mousePressEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + if (event->button() == Qt::LeftButton && m_hasFocus) + { + const int index = m_listBox.getIndexFromWidgetPosition(mousePagePosition); + + if (event->modifiers() & Qt::ControlModifier) + { + std::set selection = m_listBox.getSelection(); + if (selection.count(index)) + { + selection.erase(index); + } + else + { + selection.insert(index); + } + m_listBox.setSelection(qMove(selection), false); + } + else + { + m_listBox.setCurrentItem(index, event->modifiers()); + } + + event->accept(); + widget->update(); + } +} + +void PDFFormFieldListBoxEditor::mouseMoveEvent(QWidget* widget, QMouseEvent* event, const QPointF& mousePagePosition) +{ + if (event->buttons() & Qt::LeftButton) + { + const int index = m_listBox.getIndexFromWidgetPosition(mousePagePosition); + + if (!(event->modifiers() & Qt::ControlModifier)) + { + m_listBox.setCurrentItem(index, event->modifiers()); + + event->accept(); + widget->update(); + } + } +} + +void PDFFormFieldListBoxEditor::wheelEvent(QWidget* widget, QWheelEvent* event, const QPointF& mousePagePosition) +{ + Q_UNUSED(mousePagePosition); + + if (m_hasFocus) + { + if (event->angleDelta().y() < 0) + { + m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() + m_listBox.getViewportRowCount())); + } + else + { + m_listBox.scrollTo(m_listBox.getValidIndex(m_listBox.getTopItemIndex() - 1)); + } + + widget->update(); + event->accept(); + } +} + +void PDFFormFieldListBoxEditor::reloadValue() +{ + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + m_listBox.setTopItemIndex(parentField->getTopIndex()); + m_listBox.setSelection(getSelectedItems(parentField->getValue(), parentField->getSelection()), true); +} + +void PDFFormFieldListBoxEditor::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + if (edit) + { + m_listBox.draw(parameters, true); + } + else + { + // Draw static contents + PDFListBoxPseudowidget pseudowidget(m_formWidget.getParent()->getFlags()); + initializeListBox(&pseudowidget); + pseudowidget.draw(parameters, false); + } +} + +void PDFFormFieldListBoxEditor::initializeListBox(PDFListBoxPseudowidget* listBox) const +{ + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + listBox->setAppearance(PDFAnnotationDefaultAppearance::parse(m_formManager->getForm()->getDefaultAppearance().value_or(QByteArray())), + m_formManager->getForm()->getDefaultAlignment(), + m_formManager->getWidgetRectangle(m_formWidget), + parentField->getOptions(), + parentField->getTopIndex(), + getSelectedItems(parentField->getValue(), parentField->getSelection())); +} + +void PDFFormFieldListBoxEditor::setFocusImpl(bool focused) +{ + if (!focused && !m_listBox.isReadonly() && !m_formManager->isCommitDisabled()) + { + commit(); + } +} + +std::set PDFFormFieldListBoxEditor::getSelectedItems(PDFObject value, PDFObject indices) const +{ + std::set result; + + PDFDocumentDataLoaderDecorator loader(m_formManager->getDocument()); + value = m_formManager->getDocument()->getObject(value); + + std::vector indicesVector = loader.readIntegerArray(indices); + if (indicesVector.empty()) + { + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + const PDFFormFieldChoice::Options& options = parentField->getOptions(); + auto addOption = [&options, &result](const QString& optionString) + { + for (size_t i = 0; i < options.size(); ++i) + { + const PDFFormFieldChoice::Option& option = options[i]; + if (option.userString == optionString) + { + result.insert(int(i)); + } + } + }; + + if (value.isString()) + { + addOption(loader.readTextString(value, QString())); + } + else if (value.isArray()) + { + for (const QString& string : loader.readTextStringList(value)) + { + addOption(string); + } + } + } + else + { + std::sort(indicesVector.begin(), indicesVector.end()); + std::copy(indicesVector.cbegin(), indicesVector.cend(), std::inserter(result, result.cend())); + } + + return result; +} + +void PDFFormFieldListBoxEditor::commit() +{ + PDFObject object; + + const std::set& selection = m_listBox.getSelection(); + if (!selection.empty()) + { + const PDFFormFieldChoice* parentField = dynamic_cast(m_formWidget.getParent()); + Q_ASSERT(parentField); + + const PDFFormFieldChoice::Options& options = parentField->getOptions(); + std::set values; + + for (const int index : selection) + { + values.insert(options[index].userString); + } + + if (values.size() == 1) + { + object = PDFObjectFactory::createTextString(*values.begin()); + } + else + { + PDFObjectFactory objectFactory; + objectFactory.beginArray(); + + for (const QString& string : values) + { + objectFactory << string; + } + + objectFactory.endArray(); + object = objectFactory.takeObject(); + } + } + + if (object != m_formWidget.getParent()->getValue()) + { + PDFFormField::SetValueParameters parameters; + parameters.formManager = m_formManager; + parameters.invokingWidget = m_formWidget.getWidget(); + parameters.invokingFormField = m_formWidget.getParent(); + parameters.scope = PDFFormField::SetValueParameters::Scope::User; + parameters.value = qMove(object); + parameters.listboxTopIndex = m_listBox.getTopItemIndex(); + std::copy(selection.cbegin(), selection.cend(), std::back_inserter(parameters.listboxChoices)); + m_formManager->setFormFieldValue(parameters); + } +} + +bool PDFFormFieldChoice::setValue(const SetValueParameters& parameters) +{ + // If form field is readonly, and scope is user (form field is changed by user, + // not by calculated value), then we must not allow value change. + if (getFlags().testFlag(ReadOnly) && parameters.scope == SetValueParameters::Scope::User) + { + return false; + } + + Q_ASSERT(parameters.formManager); + Q_ASSERT(parameters.modifier); + + PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); + parameters.modifier->markFormFieldChanged(); + builder->setFormFieldValue(getSelfReference(), parameters.value); + + if (isListBox()) + { + // Listbox has special values, which must be set + builder->setFormFieldChoiceTopIndex(getSelfReference(), parameters.listboxTopIndex); + builder->setFormFieldChoiceIndices(getSelfReference(), parameters.listboxChoices); + } + + m_value = parameters.value; + m_topIndex = parameters.listboxTopIndex; + + PDFObjectFactory objectFactory; + objectFactory << parameters.listboxChoices; + m_selection = objectFactory.takeObject(); + + // Change widget appearance states + for (const PDFFormWidget& formWidget : getWidgets()) + { + builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); + parameters.modifier->markAnnotationsChanged(); + } + + return true; +} + +void PDFFormFieldChoice::resetValue(const PDFFormField::ResetValueParameters& parameters) +{ + Q_ASSERT(parameters.formManager); + Q_ASSERT(parameters.modifier); + + PDFObject defaultValue = getDefaultValue(); + PDFDocumentBuilder* builder = parameters.modifier->getBuilder(); + parameters.modifier->markFormFieldChanged(); + builder->setFormFieldValue(getSelfReference(), defaultValue); + m_value = defaultValue; + m_selection = PDFObject(); + + if (isListBox()) + { + // Listbox has special values, which must be set + builder->setFormFieldChoiceTopIndex(getSelfReference(), 0); + builder->setFormFieldChoiceIndices(getSelfReference(), { }); + } + + // Change widget appearance states + for (const PDFFormWidget& formWidget : getWidgets()) + { + builder->updateAnnotationAppearanceStreams(formWidget.getWidget()); + parameters.modifier->markAnnotationsChanged(); + } +} + +void PDFFormFieldChoice::reloadValue(const PDFObjectStorage* storage, PDFObject parentValue) +{ + BaseClass::reloadValue(storage, parentValue); + + if (const PDFDictionary* fieldDictionary = storage->getDictionaryFromObject(storage->getObjectByReference(getSelfReference()))) + { + PDFDocumentDataLoaderDecorator loader(storage); + m_topIndex = loader.readIntegerFromDictionary(fieldDictionary, "TI", 0); + m_selection = fieldDictionary->get("I"); + } +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfform.h b/Pdf4QtLib/sources/pdfform.h index f1b9d10..9d05f9d 100644 --- a/Pdf4QtLib/sources/pdfform.h +++ b/Pdf4QtLib/sources/pdfform.h @@ -1,731 +1,731 @@ -// Copyright (C) 2020-2021 Jakub Melka -// -// This file is part of PDF4QT. -// -// PDF4QT is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// with the written consent of the copyright owner, any later version. -// -// PDF4QT 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with PDF4QT. If not, see . - -#ifndef PDFFORM_H -#define PDFFORM_H - -#include "pdfobject.h" -#include "pdfdocument.h" -#include "pdfannotation.h" -#include "pdfdocumentdrawinterface.h" -#include "pdfsignaturehandler.h" - -#include - -#include - -namespace pdf -{ -class PDFFormField; -class PDFFormManager; -class PDFObjectStorage; -class PDFModifiedDocument; -class PDFDocumentModifier; - -using PDFFormFieldPointer = QSharedPointer; -using PDFFormFields = std::vector; -using PDFWidgetToFormFieldMapping = std::map; - -/// A simple proxy to the widget annotation -class PDFFormWidget -{ -public: - explicit inline PDFFormWidget() = default; - explicit inline PDFFormWidget(PDFObjectReference page, PDFObjectReference widget, PDFFormField* parentField, PDFAnnotationAdditionalActions actions); - - PDFObjectReference getPage() const { return m_page; } - PDFObjectReference getWidget() const { return m_widget; } - PDFFormField* getParent() const { return m_parentField; } - const PDFAction* getAction(PDFAnnotationAdditionalActions::Action action) const { return m_actions.getAction(action); } - - /// Parses form widget from the object reference. If some error occurs - /// then empty object is returned, no exception is thrown. - /// \param storage Storage - /// \param reference Widget reference - /// \param parentField Parent field - static PDFFormWidget parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); - -private: - PDFObjectReference m_page; - PDFObjectReference m_widget; - PDFFormField* m_parentField; - PDFAnnotationAdditionalActions m_actions; -}; - -using PDFFormWidgets = std::vector; - -/// Form field is terminal or non-terminal field (non-terminal fields -/// have children), fields represents various interactive widgets, such as -/// checks, radio buttons, text edits etc., which are editable and user -/// can interact with them. -class PDFFormField -{ -public: - explicit inline PDFFormField() = default; - virtual ~PDFFormField() = default; - - enum class FieldType - { - Invalid, - Button, - Text, - Choice, - Signature - }; - - enum NameType - { - Partial, ///< Partial name for this field - UserCaption, ///< Name used in GUI (for example, in message boxes) - FullyQualified, ///< Fully qualified name (according to the PDF specification 1.7) - Export, ///< Name for export - NameTypeEnd - }; - - using FieldNames = std::array; - - enum FieldFlag - { - None = 0, - - /// Field is read only, it doesn't respond on mouse clicks (if it is a button), - /// associated widget annotation will not interact with the user. Field can't - /// change it's value. Mainly used for calculable fields. - ReadOnly = 1 << 0, - - /// If set, field is required, when submitting form by submit action. If submit - /// action is triggered, then all fields with this flag must have a value. - Required = 1 << 1, - - /// If set, field value must not be exported by submit form action. - NoExport = 1 << 2, - - /// Text fields only. If set, then text can span multiple lines. Otherwise, - /// text is restricted to single line. - Multiline = 1 << 12, - - /// Text fields only. If set, field is intended to display text edit, which - /// can edit passwords. Password characters should not be echoed to the screen - /// and value of this field should not be stored in PDF file. - Password = 1 << 13, - - /// Only for radio buttons. If set, at least one radio button is checked. - /// If user clicks on checked radio button, it is not checked off. Otherwise - /// user can uncheck checked radio button (so no button is selected). - NoToggleToOff = 1 << 14, - - /// Valid only for buttons which have PushButton flag unset. If Radio flag is set, - /// then widget is radio button, otherwise widget is push button. - Radio = 1 << 15, - - /// Valid only for buttons. If set, button is push button. - PushButton = 1 << 16, - - /// For choice fields only. If set, choice field is combo box. - /// If clear, it is a list box. - Combo = 1 << 17, - - /// For choice fields combo boxes only. If set, combo box is editable, - /// i.e. user can enter custom text. If this flag is cleared, then combo box - /// is not editable and user can only select items from the drop down list. - Edit = 1 << 18, - - /// For choice fields only. If set, the field option's items should be sorted - /// alphabetically, but not by the viewer application, but by author of the form. - /// Viewer application should respect Opt array and display items in that order. - Sort = 1 << 19, - - /// Text fields only. Text field is used to select file path, whose contents - /// should be submitted as the value of the field. - FileSelect = 1 << 20, - - /// For choice fields only. If set, then user can select multiple choices - /// simultaneously, if clear, then only one item should be selected at the time. - MultiSelect = 1 << 21, - - /// Text fields only. If set, texts entered in this field should not be spell checked. - DoNotSpellcheck = 1 << 22, - - /// Text fields only. Allow only so much text, which fits field's area. If field's area is filled, - /// then do not allow the user to store more text in the field. - DoNotScroll = 1 << 23, - - /// Text fields only. If set, then MaxLen entry and annotation rectangle is used - /// to divide space equally for each character. Text is laid out into those spaces. - Comb = 1 << 24, - - /// Valid only for radio buttons. Radio buttons in a group of radio buttons, - /// which have same value for 'On' state, will turn On and Off in unison, if one - /// is checked, all are checked. If clear, radio buttons are mutually exclusive. - RadiosInUnison = 1 << 25, - - /// Text fields only. Value of this field should be a rich text. - RichText = 1 << 25, - - /// Choice fields only. If set, then when user selects choice by mouse, - /// data is immediately commited. Otherwise data are commited only, when - /// widget lose focus. - CommitOnSelectionChange = 1 << 26 - }; - - Q_DECLARE_FLAGS(FieldFlags, FieldFlag) - - PDFObjectReference getSelfReference() const { return m_selfReference; } - FieldType getFieldType() const { return m_fieldType; } - const PDFFormField* getParentField() const { return m_parentField; } - const PDFFormFields& getChildFields() const { return m_childFields; } - const PDFFormWidgets& getWidgets() const { return m_widgets; } - const QString& getName(NameType nameType) const { return m_fieldNames.at(nameType); } - FieldFlags getFlags() const { return m_fieldFlags; } - const PDFObject& getValue() const { return m_value; } - const PDFObject& getDefaultValue() const { return m_defaultValue; } - - /// Fills widget to form field mapping - /// \param mapping Form field mapping - void fillWidgetToFormFieldMapping(PDFWidgetToFormFieldMapping& mapping); - - /// Reloads value from object storage. Actually stored value is lost. - virtual void reloadValue(const PDFObjectStorage* storage, PDFObject parentValue); - - /// Applies function to this form field and all its descendants, - /// in pre-order (first application is to the parent, following - /// calls to apply for children). - /// \param functor Functor to apply - void apply(const std::function& functor) const; - - /// Applies function to this form field and all its descendants, - /// in pre-order (first application is to the parent, following - /// calls to apply for children). - /// \param functor Functor to apply - void modify(const std::function& functor); - - /// Returns action by type. If action is not found, nullptr is returned - /// \param action Action type - const PDFAction* getAction(PDFAnnotationAdditionalActions::Action action) const { return m_additionalActions.getAction(action); } - - /// Returns container of actions - const PDFAnnotationAdditionalActions& getActions() const { return m_additionalActions; } - - /// Parses form field from the object reference. If some error occurs - /// then null pointer is returned, no exception is thrown. - /// \param storage Storage - /// \param reference Field reference - /// \param parentField Parent field (or nullptr, if it is root field) - static PDFFormFieldPointer parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); - - struct SetValueParameters - { - enum class Scope - { - User, ///< Changed value comes from user input - Internal ///< Value is changed by some program operation (for example, calculation) - }; - - PDFObject value; - PDFObjectReference invokingWidget; - PDFFormField* invokingFormField = nullptr; - PDFDocumentModifier* modifier = nullptr; - PDFFormManager* formManager = nullptr; - Scope scope = Scope::User; - - // Choice list box field only - PDFInteger listboxTopIndex= 0; - std::vector listboxChoices; - }; - - /// Sets value to the form field. If value has been correctly - /// set, then true is returned, otherwise false is returned. - /// This function also verifies, if value can be set (i.e. form field - /// is editable, and value is valid). - /// \param parameters Parameters - virtual bool setValue(const SetValueParameters& parameters); - - struct ResetValueParameters - { - PDFDocumentModifier* modifier = nullptr; - PDFFormManager* formManager = nullptr; - }; - - /// Resets value to the field's default value. Widget annotation - /// appearances are also updated. - /// \param parameters Parameters - virtual void resetValue(const ResetValueParameters& parameters); - -protected: - PDFObjectReference m_selfReference; - FieldType m_fieldType = FieldType::Invalid; - PDFFormField* m_parentField = nullptr; - PDFFormFields m_childFields; - PDFFormWidgets m_widgets; - FieldNames m_fieldNames; - FieldFlags m_fieldFlags = None; - PDFObject m_value; - PDFObject m_defaultValue; - PDFAnnotationAdditionalActions m_additionalActions; -}; - -/// Represents pushbutton, checkbox and radio button (which is distinguished -/// by flags). -class PDFFormFieldButton : public PDFFormField -{ -public: - explicit inline PDFFormFieldButton() = default; - - enum class ButtonType - { - PushButton, - RadioButton, - CheckBox - }; - - /// Determines button type from form field's flags - ButtonType getButtonType() const; - - const QStringList& getOptions() const { return m_options; } - - /// Returns appearance state, which corresponds to the checked - /// state of checkbox or radio button. If error occurs, then - /// empty byte array is returned. - /// \param formManager Form manager - /// \param widget Widget - static QByteArray getOnAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget); - - /// Returns appearance state, which corresponds to the unchecked - /// state of checkbox or radio button. If error occurs, then - /// empty byte array is returned. - /// \param formManager Form manager - /// \param widget Widget - static QByteArray getOffAppearanceState(const PDFFormManager* formManager, const PDFFormWidget* widget); - - virtual bool setValue(const SetValueParameters& parameters) override; - virtual void resetValue(const ResetValueParameters& parameters) override; - -private: - friend PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); - - /// List of export names of 'On' state for radio buttons. In widget annotation's appearance - /// dictionaries, state names are computer generated numbers (for example /1, /3, ...), - /// which are indices to this string list. This allows to distinguish between - /// different widget annotations, even if they have same value in m_options array. - QStringList m_options; -}; - -/// Represents single line, or multiline text field -class PDFFormFieldText : public PDFFormField -{ -public: - explicit inline PDFFormFieldText() = default; - - PDFInteger getTextMaximalLength() const { return m_maxLength; } - const QByteArray& getDefaultAppearance() const { return m_defaultAppearance; } - Qt::Alignment getAlignment() const { return m_alignment; } - const QString& getRichTextDefaultStyle() const { return m_defaultStyle; } - const QString& getRichTextValue() const { return m_richTextValue; } - - virtual bool setValue(const SetValueParameters& parameters) override; - virtual void resetValue(const ResetValueParameters& parameters) override; - -private: - friend PDFFormFieldPointer PDFFormField::parse(const PDFObjectStorage* storage, PDFObjectReference reference, PDFFormField* parentField); - - /// Maximal length of text in the field. If zero, - /// no maximal length is specified. - PDFInteger m_maxLength = 0; - - /// Default appearance - QByteArray m_defaultAppearance; - - /// Text field alignment - Qt::Alignment m_alignment = 0; - - /// Default style - QString m_defaultStyle; - - /// Rich text value - QString m_richTextValue; -}; - -class PDFFormFieldChoice : public PDFFormField -{ - using BaseClass = PDFFormField; - -public: - explicit inline PDFFormFieldChoice() = default; - - bool isComboBox() const { return m_fieldFlags.testFlag(Combo); } - bool isEditableComboBox() const { return m_fieldFlags.testFlag(Edit); } - bool isListBox() const { return !isComboBox(); } - - struct Option - { - QString exportString; - QString userString; - }; - - using Options = std::vector