Commit 5b2e543089e24aae0557835234ef7f733446dc5b
1 parent
6cf04b0a
Honor repeated overlay/underlay
Showing
9 changed files
with
137 additions
and
66 deletions
ChangeLog
| 1 | +2024-01-10 Jay Berkenbilt <ejb@ql.org> | ||
| 2 | + | ||
| 3 | + * Allow --overlay and --underlay to be repeated. They may appear | ||
| 4 | + multiple times on the command-line and will be stacked in the | ||
| 5 | + order in which they appear. In QPDFJob JSON, the overlay and | ||
| 6 | + underlay keys may contain arrays. For compatibility, they may also | ||
| 7 | + contain a single dictionary. | ||
| 8 | + | ||
| 1 | 2024-01-09 Jay Berkenbilt <ejb@ql.org> | 9 | 2024-01-09 Jay Berkenbilt <ejb@ql.org> |
| 2 | 10 | ||
| 3 | * Add new command-line arguments --file and --range which can be | 11 | * Add new command-line arguments --file and --range which can be |
include/qpdf/QPDFJob.hh
| @@ -514,14 +514,16 @@ class QPDFJob | @@ -514,14 +514,16 @@ class QPDFJob | ||
| 514 | void handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_heap); | 514 | void handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_heap); |
| 515 | bool shouldRemoveUnreferencedResources(QPDF& pdf); | 515 | bool shouldRemoveUnreferencedResources(QPDF& pdf); |
| 516 | void handleRotations(QPDF& pdf); | 516 | void handleRotations(QPDF& pdf); |
| 517 | - void getUOPagenos(UnderOverlay& uo, std::map<int, std::vector<int>>& pagenos); | 517 | + void getUOPagenos( |
| 518 | + std::vector<UnderOverlay>& uo, std::map<int, std::map<size_t, std::vector<int>>>& pagenos); | ||
| 518 | void handleUnderOverlay(QPDF& pdf); | 519 | void handleUnderOverlay(QPDF& pdf); |
| 519 | std::string doUnderOverlayForPage( | 520 | std::string doUnderOverlayForPage( |
| 520 | QPDF& pdf, | 521 | QPDF& pdf, |
| 521 | UnderOverlay& uo, | 522 | UnderOverlay& uo, |
| 522 | - std::map<int, std::vector<int>>& pagenos, | 523 | + std::map<int, std::map<size_t, std::vector<int>>>& pagenos, |
| 523 | size_t page_idx, | 524 | size_t page_idx, |
| 524 | - std::map<int, QPDFObjectHandle>& fo, | 525 | + size_t uo_idx, |
| 526 | + std::map<int, std::map<size_t, QPDFObjectHandle>>& fo, | ||
| 525 | std::vector<QPDFPageObjectHelper>& pages, | 527 | std::vector<QPDFPageObjectHelper>& pages, |
| 526 | QPDFPageObjectHelper& dest_page); | 528 | QPDFPageObjectHelper& dest_page); |
| 527 | void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo); | 529 | void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo); |
| @@ -696,8 +698,8 @@ class QPDFJob | @@ -696,8 +698,8 @@ class QPDFJob | ||
| 696 | size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT}; | 698 | size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT}; |
| 697 | size_t oi_min_area{DEFAULT_OI_MIN_AREA}; | 699 | size_t oi_min_area{DEFAULT_OI_MIN_AREA}; |
| 698 | size_t ii_min_bytes{DEFAULT_II_MIN_BYTES}; | 700 | size_t ii_min_bytes{DEFAULT_II_MIN_BYTES}; |
| 699 | - UnderOverlay underlay{"underlay"}; | ||
| 700 | - UnderOverlay overlay{"overlay"}; | 701 | + std::vector<UnderOverlay> underlay; |
| 702 | + std::vector<UnderOverlay> overlay; | ||
| 701 | UnderOverlay* under_overlay{nullptr}; | 703 | UnderOverlay* under_overlay{nullptr}; |
| 702 | std::vector<PageSpec> page_specs; | 704 | std::vector<PageSpec> page_specs; |
| 703 | std::map<std::string, RotationSpec> rotations; | 705 | std::map<std::string, RotationSpec> rotations; |
job.sums
| @@ -9,12 +9,12 @@ include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa8 | @@ -9,12 +9,12 @@ include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa8 | ||
| 9 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 | 9 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 |
| 10 | job.yml 53cad86659db6722e8f415aacb19fc51ab81bb1589c3cb8f65ec893bb4bf5566 | 10 | job.yml 53cad86659db6722e8f415aacb19fc51ab81bb1589c3cb8f65ec893bb4bf5566 |
| 11 | libqpdf/qpdf/auto_job_decl.hh 20d6affe1e260f5a1af4f1d82a820b933835440ff03020e877382da2e8dac6c6 | 11 | libqpdf/qpdf/auto_job_decl.hh 20d6affe1e260f5a1af4f1d82a820b933835440ff03020e877382da2e8dac6c6 |
| 12 | -libqpdf/qpdf/auto_job_help.hh 5808d936f6cd41af278ca298ed0c0762ce0a16956cbe1757a40e4443485cf31e | 12 | +libqpdf/qpdf/auto_job_help.hh e4bb9e097516f35b4dbc676e1de99f294d8f42912541c8e3844ea401e44336ef |
| 13 | libqpdf/qpdf/auto_job_init.hh 19d1da7c4c0c635bd1c5db8d5f17df8edad3442f8eba006adb075cec295fa158 | 13 | libqpdf/qpdf/auto_job_init.hh 19d1da7c4c0c635bd1c5db8d5f17df8edad3442f8eba006adb075cec295fa158 |
| 14 | libqpdf/qpdf/auto_job_json_decl.hh 843892c8e8652a86b7eb573893ef24050b7f36fe313f7251874be5cd4cdbe3fd | 14 | libqpdf/qpdf/auto_job_json_decl.hh 843892c8e8652a86b7eb573893ef24050b7f36fe313f7251874be5cd4cdbe3fd |
| 15 | libqpdf/qpdf/auto_job_json_init.hh a87256c082427ec0318223762472970b2eced535c0c8b0288d45c8cdaaf62f74 | 15 | libqpdf/qpdf/auto_job_json_init.hh a87256c082427ec0318223762472970b2eced535c0c8b0288d45c8cdaaf62f74 |
| 16 | libqpdf/qpdf/auto_job_schema.hh 5dac568dff39614e161a0af59a0f328f1e28edf69b96f08bb76fd592d51bb053 | 16 | libqpdf/qpdf/auto_job_schema.hh 5dac568dff39614e161a0af59a0f328f1e28edf69b96f08bb76fd592d51bb053 |
| 17 | manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 | 17 | manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580 |
| 18 | -manual/cli.rst 0e6a957defa4839abb9a69414de6a5ec5524fd6ff56fe9abf8f241bee54813e2 | ||
| 19 | -manual/qpdf.1 7250b4e26033fca6b6b9cb23a51e1f46c26f8033663901d4af06b451e287e814 | 18 | +manual/cli.rst 98219ac9942824b78119cca7cd75691f7c98a31ed3c8b4f108d60a699087c418 |
| 19 | +manual/qpdf.1 2544e085c5f0f92e242944eea3bc5736e1036f67595a7a7c988f4ea8d75da901 | ||
| 20 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b | 20 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b |
libqpdf/QPDFJob.cc
| @@ -1834,9 +1834,6 @@ QPDFJob::processInputSource( | @@ -1834,9 +1834,6 @@ QPDFJob::processInputSource( | ||
| 1834 | void | 1834 | void |
| 1835 | QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) | 1835 | QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) |
| 1836 | { | 1836 | { |
| 1837 | - if (uo->filename.empty()) { | ||
| 1838 | - return; | ||
| 1839 | - } | ||
| 1840 | QPDFPageDocumentHelper main_pdh(pdf); | 1837 | QPDFPageDocumentHelper main_pdh(pdf); |
| 1841 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); | 1838 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); |
| 1842 | processFile(uo->pdf, uo->filename.c_str(), uo->password.get(), true, false); | 1839 | processFile(uo->pdf, uo->filename.c_str(), uo->password.get(), true, false); |
| @@ -1878,14 +1875,15 @@ std::string | @@ -1878,14 +1875,15 @@ std::string | ||
| 1878 | QPDFJob::doUnderOverlayForPage( | 1875 | QPDFJob::doUnderOverlayForPage( |
| 1879 | QPDF& pdf, | 1876 | QPDF& pdf, |
| 1880 | UnderOverlay& uo, | 1877 | UnderOverlay& uo, |
| 1881 | - std::map<int, std::vector<int>>& pagenos, | 1878 | + std::map<int, std::map<size_t, std::vector<int>>>& pagenos, |
| 1882 | size_t page_idx, | 1879 | size_t page_idx, |
| 1883 | - std::map<int, QPDFObjectHandle>& fo, | 1880 | + size_t uo_idx, |
| 1881 | + std::map<int, std::map<size_t, QPDFObjectHandle>>& fo, | ||
| 1884 | std::vector<QPDFPageObjectHelper>& pages, | 1882 | std::vector<QPDFPageObjectHelper>& pages, |
| 1885 | QPDFPageObjectHelper& dest_page) | 1883 | QPDFPageObjectHelper& dest_page) |
| 1886 | { | 1884 | { |
| 1887 | int pageno = 1 + QIntC::to_int(page_idx); | 1885 | int pageno = 1 + QIntC::to_int(page_idx); |
| 1888 | - if (!pagenos.count(pageno)) { | 1886 | + if (!(pagenos.count(pageno) && pagenos[pageno].count(uo_idx))) { |
| 1889 | return ""; | 1887 | return ""; |
| 1890 | } | 1888 | } |
| 1891 | 1889 | ||
| @@ -1899,13 +1897,13 @@ QPDFJob::doUnderOverlayForPage( | @@ -1899,13 +1897,13 @@ QPDFJob::doUnderOverlayForPage( | ||
| 1899 | std::string content; | 1897 | std::string content; |
| 1900 | int min_suffix = 1; | 1898 | int min_suffix = 1; |
| 1901 | QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); | 1899 | QPDFObjectHandle resources = dest_page.getAttribute("/Resources", true); |
| 1902 | - for (int from_pageno: pagenos[pageno]) { | 1900 | + for (int from_pageno: pagenos[pageno][uo_idx]) { |
| 1903 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 1901 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 1904 | v << " " << uo.which << " " << from_pageno << "\n"; | 1902 | v << " " << uo.which << " " << from_pageno << "\n"; |
| 1905 | }); | 1903 | }); |
| 1906 | auto from_page = pages.at(QIntC::to_size(from_pageno - 1)); | 1904 | auto from_page = pages.at(QIntC::to_size(from_pageno - 1)); |
| 1907 | - if (0 == fo.count(from_pageno)) { | ||
| 1908 | - fo[from_pageno] = pdf.copyForeignObject(from_page.getFormXObjectForPage()); | 1905 | + if (fo[from_pageno].count(uo_idx) == 0) { |
| 1906 | + fo[from_pageno][uo_idx] = pdf.copyForeignObject(from_page.getFormXObjectForPage()); | ||
| 1909 | } | 1907 | } |
| 1910 | 1908 | ||
| 1911 | // If the same page is overlaid or underlaid multiple times, we'll generate multiple names | 1909 | // If the same page is overlaid or underlaid multiple times, we'll generate multiple names |
| @@ -1913,13 +1911,13 @@ QPDFJob::doUnderOverlayForPage( | @@ -1913,13 +1911,13 @@ QPDFJob::doUnderOverlayForPage( | ||
| 1913 | std::string name = resources.getUniqueResourceName("/Fx", min_suffix); | 1911 | std::string name = resources.getUniqueResourceName("/Fx", min_suffix); |
| 1914 | QPDFMatrix cm; | 1912 | QPDFMatrix cm; |
| 1915 | std::string new_content = dest_page.placeFormXObject( | 1913 | std::string new_content = dest_page.placeFormXObject( |
| 1916 | - fo[from_pageno], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); | 1914 | + fo[from_pageno][uo_idx], name, dest_page.getTrimBox().getArrayAsRectangle(), cm); |
| 1917 | dest_page.copyAnnotations(from_page, cm, dest_afdh, make_afdh(from_page)); | 1915 | dest_page.copyAnnotations(from_page, cm, dest_afdh, make_afdh(from_page)); |
| 1918 | if (!new_content.empty()) { | 1916 | if (!new_content.empty()) { |
| 1919 | resources.mergeResources("<< /XObject << >> >>"_qpdf); | 1917 | resources.mergeResources("<< /XObject << >> >>"_qpdf); |
| 1920 | auto xobject = resources.getKey("/XObject"); | 1918 | auto xobject = resources.getKey("/XObject"); |
| 1921 | if (xobject.isDictionary()) { | 1919 | if (xobject.isDictionary()) { |
| 1922 | - xobject.replaceKey(name, fo[from_pageno]); | 1920 | + xobject.replaceKey(name, fo[from_pageno][uo_idx]); |
| 1923 | } | 1921 | } |
| 1924 | ++min_suffix; | 1922 | ++min_suffix; |
| 1925 | content += new_content; | 1923 | content += new_content; |
| @@ -1929,73 +1927,104 @@ QPDFJob::doUnderOverlayForPage( | @@ -1929,73 +1927,104 @@ QPDFJob::doUnderOverlayForPage( | ||
| 1929 | } | 1927 | } |
| 1930 | 1928 | ||
| 1931 | void | 1929 | void |
| 1932 | -QPDFJob::getUOPagenos(QPDFJob::UnderOverlay& uo, std::map<int, std::vector<int>>& pagenos) | ||
| 1933 | -{ | ||
| 1934 | - size_t idx = 0; | ||
| 1935 | - size_t from_size = uo.from_pagenos.size(); | ||
| 1936 | - size_t repeat_size = uo.repeat_pagenos.size(); | ||
| 1937 | - for (int to_pageno: uo.to_pagenos) { | ||
| 1938 | - if (idx < from_size) { | ||
| 1939 | - pagenos[to_pageno].push_back(uo.from_pagenos.at(idx)); | ||
| 1940 | - } else if (repeat_size) { | ||
| 1941 | - pagenos[to_pageno].push_back(uo.repeat_pagenos.at((idx - from_size) % repeat_size)); | 1930 | +QPDFJob::getUOPagenos( |
| 1931 | + std::vector<QPDFJob::UnderOverlay>& uos, | ||
| 1932 | + std::map<int, std::map<size_t, std::vector<int>>>& pagenos) | ||
| 1933 | +{ | ||
| 1934 | + size_t uo_idx = 0; | ||
| 1935 | + for (auto const& uo: uos) { | ||
| 1936 | + size_t page_idx = 0; | ||
| 1937 | + size_t from_size = uo.from_pagenos.size(); | ||
| 1938 | + size_t repeat_size = uo.repeat_pagenos.size(); | ||
| 1939 | + for (int to_pageno: uo.to_pagenos) { | ||
| 1940 | + if (page_idx < from_size) { | ||
| 1941 | + pagenos[to_pageno][uo_idx].push_back(uo.from_pagenos.at(page_idx)); | ||
| 1942 | + } else if (repeat_size) { | ||
| 1943 | + pagenos[to_pageno][uo_idx].push_back( | ||
| 1944 | + uo.repeat_pagenos.at((page_idx - from_size) % repeat_size)); | ||
| 1945 | + } | ||
| 1946 | + ++page_idx; | ||
| 1942 | } | 1947 | } |
| 1943 | - ++idx; | 1948 | + ++uo_idx; |
| 1944 | } | 1949 | } |
| 1945 | } | 1950 | } |
| 1946 | 1951 | ||
| 1947 | void | 1952 | void |
| 1948 | QPDFJob::handleUnderOverlay(QPDF& pdf) | 1953 | QPDFJob::handleUnderOverlay(QPDF& pdf) |
| 1949 | { | 1954 | { |
| 1950 | - validateUnderOverlay(pdf, &m->underlay); | ||
| 1951 | - validateUnderOverlay(pdf, &m->overlay); | ||
| 1952 | - if ((nullptr == m->underlay.pdf) && (nullptr == m->overlay.pdf)) { | 1955 | + if (m->underlay.empty() && m->overlay.empty()) { |
| 1953 | return; | 1956 | return; |
| 1954 | } | 1957 | } |
| 1955 | - std::map<int, std::vector<int>> underlay_pagenos; | ||
| 1956 | - getUOPagenos(m->underlay, underlay_pagenos); | ||
| 1957 | - std::map<int, std::vector<int>> overlay_pagenos; | ||
| 1958 | - getUOPagenos(m->overlay, overlay_pagenos); | ||
| 1959 | - std::map<int, QPDFObjectHandle> underlay_fo; | ||
| 1960 | - std::map<int, QPDFObjectHandle> overlay_fo; | ||
| 1961 | - std::vector<QPDFPageObjectHelper> upages; | ||
| 1962 | - if (m->underlay.pdf.get()) { | ||
| 1963 | - upages = QPDFPageDocumentHelper(*(m->underlay.pdf)).getAllPages(); | 1958 | + for (auto& uo: m->underlay) { |
| 1959 | + validateUnderOverlay(pdf, &uo); | ||
| 1964 | } | 1960 | } |
| 1965 | - std::vector<QPDFPageObjectHelper> opages; | ||
| 1966 | - if (m->overlay.pdf.get()) { | ||
| 1967 | - opages = QPDFPageDocumentHelper(*(m->overlay.pdf)).getAllPages(); | 1961 | + for (auto& uo: m->overlay) { |
| 1962 | + validateUnderOverlay(pdf, &uo); | ||
| 1968 | } | 1963 | } |
| 1969 | 1964 | ||
| 1970 | - QPDFPageDocumentHelper main_pdh(pdf); | ||
| 1971 | - std::vector<QPDFPageObjectHelper> main_pages = main_pdh.getAllPages(); | ||
| 1972 | - size_t main_npages = main_pages.size(); | 1965 | + // First map key is 1-based page number. Second is index into the overlay/underlay vector. Watch |
| 1966 | + // out to not reverse the keys or be off by one. | ||
| 1967 | + std::map<int, std::map<size_t, std::vector<int>>> underlay_pagenos; | ||
| 1968 | + std::map<int, std::map<size_t, std::vector<int>>> overlay_pagenos; | ||
| 1969 | + getUOPagenos(m->underlay, underlay_pagenos); | ||
| 1970 | + getUOPagenos(m->overlay, overlay_pagenos); | ||
| 1973 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { | 1971 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 1974 | v << prefix << ": processing underlay/overlay\n"; | 1972 | v << prefix << ": processing underlay/overlay\n"; |
| 1975 | }); | 1973 | }); |
| 1976 | - for (size_t i = 0; i < main_npages; ++i) { | 1974 | + |
| 1975 | + auto get_pages = [](std::vector<UnderOverlay>& v, | ||
| 1976 | + std::vector<std::vector<QPDFPageObjectHelper>>& v_out) { | ||
| 1977 | + for (auto const& uo: v) { | ||
| 1978 | + if (uo.pdf) { | ||
| 1979 | + v_out.push_back(QPDFPageDocumentHelper(*(uo.pdf)).getAllPages()); | ||
| 1980 | + } | ||
| 1981 | + } | ||
| 1982 | + }; | ||
| 1983 | + std::vector<std::vector<QPDFPageObjectHelper>> upages; | ||
| 1984 | + get_pages(m->underlay, upages); | ||
| 1985 | + std::vector<std::vector<QPDFPageObjectHelper>> opages; | ||
| 1986 | + get_pages(m->overlay, opages); | ||
| 1987 | + | ||
| 1988 | + std::map<int, std::map<size_t, QPDFObjectHandle>> underlay_fo; | ||
| 1989 | + std::map<int, std::map<size_t, QPDFObjectHandle>> overlay_fo; | ||
| 1990 | + QPDFPageDocumentHelper main_pdh(pdf); | ||
| 1991 | + auto main_pages = main_pdh.getAllPages(); | ||
| 1992 | + size_t main_npages = main_pages.size(); | ||
| 1993 | + for (size_t page_idx = 0; page_idx < main_npages; ++page_idx) { | ||
| 1994 | + auto pageno = QIntC::to_int(page_idx) + 1; | ||
| 1977 | doIfVerbose( | 1995 | doIfVerbose( |
| 1978 | - [&](Pipeline& v, std::string const& prefix) { v << " page " << 1 + i << "\n"; }); | ||
| 1979 | - auto pageno = QIntC::to_int(i) + 1; | ||
| 1980 | - if (!(underlay_pagenos.count(pageno) || overlay_pagenos.count(pageno))) { | 1996 | + [&](Pipeline& v, std::string const& prefix) { v << " page " << pageno << "\n"; }); |
| 1997 | + if (underlay_pagenos[pageno].empty() && overlay_pagenos[pageno].empty()) { | ||
| 1981 | continue; | 1998 | continue; |
| 1982 | } | 1999 | } |
| 1983 | // This code converts the original page, any underlays, and any overlays to form XObjects. | 2000 | // This code converts the original page, any underlays, and any overlays to form XObjects. |
| 1984 | // Then it concatenates display of all underlays, the original page, and all overlays. Prior | 2001 | // Then it concatenates display of all underlays, the original page, and all overlays. Prior |
| 1985 | // to 11.3.0, the original page contents were wrapped in q/Q, but this didn't work if the | 2002 | // to 11.3.0, the original page contents were wrapped in q/Q, but this didn't work if the |
| 1986 | - // original page had unbalanced q/Q operators. See github issue #904. | ||
| 1987 | - auto& dest_page = main_pages.at(i); | 2003 | + // original page had unbalanced q/Q operators. See GitHub issue #904. |
| 2004 | + auto& dest_page = main_pages.at(page_idx); | ||
| 1988 | auto dest_page_oh = dest_page.getObjectHandle(); | 2005 | auto dest_page_oh = dest_page.getObjectHandle(); |
| 1989 | auto this_page_fo = dest_page.getFormXObjectForPage(); | 2006 | auto this_page_fo = dest_page.getFormXObjectForPage(); |
| 1990 | // The resulting form xobject lazily reads the content from the original page, which we are | 2007 | // The resulting form xobject lazily reads the content from the original page, which we are |
| 1991 | - // going to replace. Therefore we have to explicitly copy it. | 2008 | + // going to replace. Therefore, we have to explicitly copy it. |
| 1992 | auto content_data = this_page_fo.getRawStreamData(); | 2009 | auto content_data = this_page_fo.getRawStreamData(); |
| 1993 | this_page_fo.replaceStreamData(content_data, QPDFObjectHandle(), QPDFObjectHandle()); | 2010 | this_page_fo.replaceStreamData(content_data, QPDFObjectHandle(), QPDFObjectHandle()); |
| 1994 | auto resources = | 2011 | auto resources = |
| 1995 | dest_page_oh.replaceKeyAndGetNew("/Resources", "<< /XObject << >> >>"_qpdf); | 2012 | dest_page_oh.replaceKeyAndGetNew("/Resources", "<< /XObject << >> >>"_qpdf); |
| 1996 | resources.getKey("/XObject").replaceKeyAndGetNew("/Fx0", this_page_fo); | 2013 | resources.getKey("/XObject").replaceKeyAndGetNew("/Fx0", this_page_fo); |
| 1997 | - auto content = doUnderOverlayForPage( | ||
| 1998 | - pdf, m->underlay, underlay_pagenos, i, underlay_fo, upages, dest_page); | 2014 | + size_t uo_idx{0}; |
| 2015 | + std::string content; | ||
| 2016 | + for (auto& underlay: m->underlay) { | ||
| 2017 | + content += doUnderOverlayForPage( | ||
| 2018 | + pdf, | ||
| 2019 | + underlay, | ||
| 2020 | + underlay_pagenos, | ||
| 2021 | + page_idx, | ||
| 2022 | + uo_idx, | ||
| 2023 | + underlay_fo, | ||
| 2024 | + upages[uo_idx], | ||
| 2025 | + dest_page); | ||
| 2026 | + ++uo_idx; | ||
| 2027 | + } | ||
| 1999 | content += dest_page.placeFormXObject( | 2028 | content += dest_page.placeFormXObject( |
| 2000 | this_page_fo, | 2029 | this_page_fo, |
| 2001 | "/Fx0", | 2030 | "/Fx0", |
| @@ -2003,8 +2032,19 @@ QPDFJob::handleUnderOverlay(QPDF& pdf) | @@ -2003,8 +2032,19 @@ QPDFJob::handleUnderOverlay(QPDF& pdf) | ||
| 2003 | true, | 2032 | true, |
| 2004 | false, | 2033 | false, |
| 2005 | false); | 2034 | false); |
| 2006 | - content += doUnderOverlayForPage( | ||
| 2007 | - pdf, m->overlay, overlay_pagenos, i, overlay_fo, opages, dest_page); | 2035 | + uo_idx = 0; |
| 2036 | + for (auto& overlay: m->overlay) { | ||
| 2037 | + content += doUnderOverlayForPage( | ||
| 2038 | + pdf, | ||
| 2039 | + overlay, | ||
| 2040 | + overlay_pagenos, | ||
| 2041 | + page_idx, | ||
| 2042 | + uo_idx, | ||
| 2043 | + overlay_fo, | ||
| 2044 | + opages[uo_idx], | ||
| 2045 | + dest_page); | ||
| 2046 | + ++uo_idx; | ||
| 2047 | + } | ||
| 2008 | dest_page_oh.replaceKey("/Contents", pdf.newStream(content)); | 2048 | dest_page_oh.replaceKey("/Contents", pdf.newStream(content)); |
| 2009 | } | 2049 | } |
| 2010 | } | 2050 | } |
| @@ -3057,9 +3097,10 @@ QPDFJob::writeOutfile(QPDF& pdf) | @@ -3057,9 +3097,10 @@ QPDFJob::writeOutfile(QPDF& pdf) | ||
| 3057 | try { | 3097 | try { |
| 3058 | QUtil::remove_file(backup.c_str()); | 3098 | QUtil::remove_file(backup.c_str()); |
| 3059 | } catch (QPDFSystemError& e) { | 3099 | } catch (QPDFSystemError& e) { |
| 3060 | - *m->log->getError() << m->message_prefix << ": unable to delete original file (" | ||
| 3061 | - << e.what() << ");" << " original file left in " << backup | ||
| 3062 | - << ", but the input was successfully replaced\n"; | 3100 | + *m->log->getError() |
| 3101 | + << m->message_prefix << ": unable to delete original file (" << e.what() << ");" | ||
| 3102 | + << " original file left in " << backup | ||
| 3103 | + << ", but the input was successfully replaced\n"; | ||
| 3063 | } | 3104 | } |
| 3064 | } | 3105 | } |
| 3065 | } | 3106 | } |
libqpdf/QPDFJob_config.cc
| @@ -1010,14 +1010,16 @@ QPDFJob::PagesConfig::password(std::string const& arg) | @@ -1010,14 +1010,16 @@ QPDFJob::PagesConfig::password(std::string const& arg) | ||
| 1010 | std::shared_ptr<QPDFJob::UOConfig> | 1010 | std::shared_ptr<QPDFJob::UOConfig> |
| 1011 | QPDFJob::Config::overlay() | 1011 | QPDFJob::Config::overlay() |
| 1012 | { | 1012 | { |
| 1013 | - o.m->under_overlay = &o.m->overlay; | 1013 | + o.m->overlay.emplace_back("overlay"); |
| 1014 | + o.m->under_overlay = &o.m->overlay.back(); | ||
| 1014 | return std::shared_ptr<UOConfig>(new UOConfig(this)); | 1015 | return std::shared_ptr<UOConfig>(new UOConfig(this)); |
| 1015 | } | 1016 | } |
| 1016 | 1017 | ||
| 1017 | std::shared_ptr<QPDFJob::UOConfig> | 1018 | std::shared_ptr<QPDFJob::UOConfig> |
| 1018 | QPDFJob::Config::underlay() | 1019 | QPDFJob::Config::underlay() |
| 1019 | { | 1020 | { |
| 1020 | - o.m->under_overlay = &o.m->underlay; | 1021 | + o.m->underlay.emplace_back("underlay"); |
| 1022 | + o.m->under_overlay = &o.m->underlay.back(); | ||
| 1021 | return std::shared_ptr<UOConfig>(new UOConfig(this)); | 1023 | return std::shared_ptr<UOConfig>(new UOConfig(this)); |
| 1022 | } | 1024 | } |
| 1023 | 1025 |
libqpdf/qpdf/auto_job_help.hh
| @@ -711,6 +711,9 @@ of the primary output until it runs out of pages, and any extra pages are | @@ -711,6 +711,9 @@ of the primary output until it runs out of pages, and any extra pages are | ||
| 711 | ignored. You can also give a page range with --repeat to cause | 711 | ignored. You can also give a page range with --repeat to cause |
| 712 | those pages to be repeated after the original pages are exhausted. | 712 | those pages to be repeated after the original pages are exhausted. |
| 713 | 713 | ||
| 714 | +This options are repeatable. Pages will be stacked in order of | ||
| 715 | +appearance: first underlays, then the original page, then overlays. | ||
| 716 | + | ||
| 714 | Run qpdf --help=page-ranges for help with page ranges. | 717 | Run qpdf --help=page-ranges for help with page ranges. |
| 715 | )"); | 718 | )"); |
| 716 | } | 719 | } |
manual/cli.rst
| @@ -2805,6 +2805,9 @@ Overlay and Underlay | @@ -2805,6 +2805,9 @@ Overlay and Underlay | ||
| 2805 | ignored. You can also give a page range with --repeat to cause | 2805 | ignored. You can also give a page range with --repeat to cause |
| 2806 | those pages to be repeated after the original pages are exhausted. | 2806 | those pages to be repeated after the original pages are exhausted. |
| 2807 | 2807 | ||
| 2808 | + This options are repeatable. Pages will be stacked in order of | ||
| 2809 | + appearance: first underlays, then the original page, then overlays. | ||
| 2810 | + | ||
| 2808 | Run qpdf --help=page-ranges for help with page ranges. | 2811 | Run qpdf --help=page-ranges for help with page ranges. |
| 2809 | 2812 | ||
| 2810 | You can use :command:`qpdf` to overlay or underlay pages from other | 2813 | You can use :command:`qpdf` to overlay or underlay pages from other |
| @@ -2823,8 +2826,10 @@ are applied, possibly obscured by the original page, and overlay files | @@ -2823,8 +2826,10 @@ are applied, possibly obscured by the original page, and overlay files | ||
| 2823 | are drawn on top of the page to which they are applied, possibly | 2826 | are drawn on top of the page to which they are applied, possibly |
| 2824 | obscuring the page. The ability to specify the file using the | 2827 | obscuring the page. The ability to specify the file using the |
| 2825 | :qpdf:ref:`--file` option was added in qpdf 11.9.0. You can combine | 2828 | :qpdf:ref:`--file` option was added in qpdf 11.9.0. You can combine |
| 2826 | -overlay and underlay, but you can only specify each option at most one | ||
| 2827 | -time. | 2829 | +overlay and underlay. Starting in qpdf 11.9.0, you can specify these |
| 2830 | +options multiple times. The final page will be a stack containing the | ||
| 2831 | +underlays in order of appearance, then the original page, then the | ||
| 2832 | +overlays in order of appearance. | ||
| 2828 | 2833 | ||
| 2829 | The default behavior of overlay and underlay is that pages are taken | 2834 | The default behavior of overlay and underlay is that pages are taken |
| 2830 | from the overlay/underlay file in sequence and applied to | 2835 | from the overlay/underlay file in sequence and applied to |
manual/qpdf.1
| @@ -849,6 +849,9 @@ of the primary output until it runs out of pages, and any extra pages are | @@ -849,6 +849,9 @@ of the primary output until it runs out of pages, and any extra pages are | ||
| 849 | ignored. You can also give a page range with --repeat to cause | 849 | ignored. You can also give a page range with --repeat to cause |
| 850 | those pages to be repeated after the original pages are exhausted. | 850 | those pages to be repeated after the original pages are exhausted. |
| 851 | 851 | ||
| 852 | +This options are repeatable. Pages will be stacked in order of | ||
| 853 | +appearance: first underlays, then the original page, then overlays. | ||
| 854 | + | ||
| 852 | Run qpdf --help=page-ranges for help with page ranges. | 855 | Run qpdf --help=page-ranges for help with page ranges. |
| 853 | .PP | 856 | .PP |
| 854 | Related Options: | 857 | Related Options: |
manual/release-notes.rst
| @@ -48,6 +48,13 @@ Planned changes for future 12.x (subject to change): | @@ -48,6 +48,13 @@ Planned changes for future 12.x (subject to change): | ||
| 48 | as well. These new options can be freely intermixed with | 48 | as well. These new options can be freely intermixed with |
| 49 | positional arguments. | 49 | positional arguments. |
| 50 | 50 | ||
| 51 | + - Allow :qpdf:ref:`--overlay` and :qpdf:ref:`--underlay` to be | ||
| 52 | + repeated. They may appear multiple times on the command-line and | ||
| 53 | + will be stacked in the order in which they appear. In QPDFJob | ||
| 54 | + JSON (see :ref:`qpdf-job`), the `overlay` and `underlay` keys | ||
| 55 | + may contain arrays. For compatibility, they may also contain a | ||
| 56 | + single dictionary. | ||
| 57 | + | ||
| 51 | - Library Enhancements | 58 | - Library Enhancements |
| 52 | 59 | ||
| 53 | - Add ``file()``, ``range()``, and ``password()`` to | 60 | - Add ``file()``, ``range()``, and ``password()`` to |