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 | 9 | 2024-01-09 Jay Berkenbilt <ejb@ql.org> |
| 2 | 10 | |
| 3 | 11 | * Add new command-line arguments --file and --range which can be | ... | ... |
include/qpdf/QPDFJob.hh
| ... | ... | @@ -514,14 +514,16 @@ class QPDFJob |
| 514 | 514 | void handlePageSpecs(QPDF& pdf, std::vector<std::unique_ptr<QPDF>>& page_heap); |
| 515 | 515 | bool shouldRemoveUnreferencedResources(QPDF& pdf); |
| 516 | 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 | 519 | void handleUnderOverlay(QPDF& pdf); |
| 519 | 520 | std::string doUnderOverlayForPage( |
| 520 | 521 | QPDF& pdf, |
| 521 | 522 | UnderOverlay& uo, |
| 522 | - std::map<int, std::vector<int>>& pagenos, | |
| 523 | + std::map<int, std::map<size_t, std::vector<int>>>& pagenos, | |
| 523 | 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 | 527 | std::vector<QPDFPageObjectHelper>& pages, |
| 526 | 528 | QPDFPageObjectHelper& dest_page); |
| 527 | 529 | void validateUnderOverlay(QPDF& pdf, UnderOverlay* uo); |
| ... | ... | @@ -696,8 +698,8 @@ class QPDFJob |
| 696 | 698 | size_t oi_min_height{DEFAULT_OI_MIN_HEIGHT}; |
| 697 | 699 | size_t oi_min_area{DEFAULT_OI_MIN_AREA}; |
| 698 | 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 | 703 | UnderOverlay* under_overlay{nullptr}; |
| 702 | 704 | std::vector<PageSpec> page_specs; |
| 703 | 705 | std::map<std::string, RotationSpec> rotations; | ... | ... |
job.sums
| ... | ... | @@ -9,12 +9,12 @@ include/qpdf/auto_job_c_pages.hh 09ca15649cc94fdaf6d9bdae28a20723f2a66616bf15aa8 |
| 9 | 9 | include/qpdf/auto_job_c_uo.hh 9c2f98a355858dd54d0bba444b73177a59c9e56833e02fa6406f429c07f39e62 |
| 10 | 10 | job.yml 53cad86659db6722e8f415aacb19fc51ab81bb1589c3cb8f65ec893bb4bf5566 |
| 11 | 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 | 13 | libqpdf/qpdf/auto_job_init.hh 19d1da7c4c0c635bd1c5db8d5f17df8edad3442f8eba006adb075cec295fa158 |
| 14 | 14 | libqpdf/qpdf/auto_job_json_decl.hh 843892c8e8652a86b7eb573893ef24050b7f36fe313f7251874be5cd4cdbe3fd |
| 15 | 15 | libqpdf/qpdf/auto_job_json_init.hh a87256c082427ec0318223762472970b2eced535c0c8b0288d45c8cdaaf62f74 |
| 16 | 16 | libqpdf/qpdf/auto_job_schema.hh 5dac568dff39614e161a0af59a0f328f1e28edf69b96f08bb76fd592d51bb053 |
| 17 | 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 | 20 | manual/qpdf.1.in 436ecc85d45c4c9e2dbd1725fb7f0177fb627179469f114561adf3cb6cbb677b | ... | ... |
libqpdf/QPDFJob.cc
| ... | ... | @@ -1834,9 +1834,6 @@ QPDFJob::processInputSource( |
| 1834 | 1834 | void |
| 1835 | 1835 | QPDFJob::validateUnderOverlay(QPDF& pdf, UnderOverlay* uo) |
| 1836 | 1836 | { |
| 1837 | - if (uo->filename.empty()) { | |
| 1838 | - return; | |
| 1839 | - } | |
| 1840 | 1837 | QPDFPageDocumentHelper main_pdh(pdf); |
| 1841 | 1838 | int main_npages = QIntC::to_int(main_pdh.getAllPages().size()); |
| 1842 | 1839 | processFile(uo->pdf, uo->filename.c_str(), uo->password.get(), true, false); |
| ... | ... | @@ -1878,14 +1875,15 @@ std::string |
| 1878 | 1875 | QPDFJob::doUnderOverlayForPage( |
| 1879 | 1876 | QPDF& pdf, |
| 1880 | 1877 | UnderOverlay& uo, |
| 1881 | - std::map<int, std::vector<int>>& pagenos, | |
| 1878 | + std::map<int, std::map<size_t, std::vector<int>>>& pagenos, | |
| 1882 | 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 | 1882 | std::vector<QPDFPageObjectHelper>& pages, |
| 1885 | 1883 | QPDFPageObjectHelper& dest_page) |
| 1886 | 1884 | { |
| 1887 | 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 | 1887 | return ""; |
| 1890 | 1888 | } |
| 1891 | 1889 | |
| ... | ... | @@ -1899,13 +1897,13 @@ QPDFJob::doUnderOverlayForPage( |
| 1899 | 1897 | std::string content; |
| 1900 | 1898 | int min_suffix = 1; |
| 1901 | 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 | 1901 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 1904 | 1902 | v << " " << uo.which << " " << from_pageno << "\n"; |
| 1905 | 1903 | }); |
| 1906 | 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 | 1909 | // If the same page is overlaid or underlaid multiple times, we'll generate multiple names |
| ... | ... | @@ -1913,13 +1911,13 @@ QPDFJob::doUnderOverlayForPage( |
| 1913 | 1911 | std::string name = resources.getUniqueResourceName("/Fx", min_suffix); |
| 1914 | 1912 | QPDFMatrix cm; |
| 1915 | 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 | 1915 | dest_page.copyAnnotations(from_page, cm, dest_afdh, make_afdh(from_page)); |
| 1918 | 1916 | if (!new_content.empty()) { |
| 1919 | 1917 | resources.mergeResources("<< /XObject << >> >>"_qpdf); |
| 1920 | 1918 | auto xobject = resources.getKey("/XObject"); |
| 1921 | 1919 | if (xobject.isDictionary()) { |
| 1922 | - xobject.replaceKey(name, fo[from_pageno]); | |
| 1920 | + xobject.replaceKey(name, fo[from_pageno][uo_idx]); | |
| 1923 | 1921 | } |
| 1924 | 1922 | ++min_suffix; |
| 1925 | 1923 | content += new_content; |
| ... | ... | @@ -1929,73 +1927,104 @@ QPDFJob::doUnderOverlayForPage( |
| 1929 | 1927 | } |
| 1930 | 1928 | |
| 1931 | 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 | 1952 | void |
| 1948 | 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 | 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 | 1971 | doIfVerbose([&](Pipeline& v, std::string const& prefix) { |
| 1974 | 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 | 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 | 1998 | continue; |
| 1982 | 1999 | } |
| 1983 | 2000 | // This code converts the original page, any underlays, and any overlays to form XObjects. |
| 1984 | 2001 | // Then it concatenates display of all underlays, the original page, and all overlays. Prior |
| 1985 | 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 | 2005 | auto dest_page_oh = dest_page.getObjectHandle(); |
| 1989 | 2006 | auto this_page_fo = dest_page.getFormXObjectForPage(); |
| 1990 | 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 | 2009 | auto content_data = this_page_fo.getRawStreamData(); |
| 1993 | 2010 | this_page_fo.replaceStreamData(content_data, QPDFObjectHandle(), QPDFObjectHandle()); |
| 1994 | 2011 | auto resources = |
| 1995 | 2012 | dest_page_oh.replaceKeyAndGetNew("/Resources", "<< /XObject << >> >>"_qpdf); |
| 1996 | 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 | 2028 | content += dest_page.placeFormXObject( |
| 2000 | 2029 | this_page_fo, |
| 2001 | 2030 | "/Fx0", |
| ... | ... | @@ -2003,8 +2032,19 @@ QPDFJob::handleUnderOverlay(QPDF& pdf) |
| 2003 | 2032 | true, |
| 2004 | 2033 | false, |
| 2005 | 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 | 2048 | dest_page_oh.replaceKey("/Contents", pdf.newStream(content)); |
| 2009 | 2049 | } |
| 2010 | 2050 | } |
| ... | ... | @@ -3057,9 +3097,10 @@ QPDFJob::writeOutfile(QPDF& pdf) |
| 3057 | 3097 | try { |
| 3058 | 3098 | QUtil::remove_file(backup.c_str()); |
| 3059 | 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 | 1010 | std::shared_ptr<QPDFJob::UOConfig> |
| 1011 | 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 | 1015 | return std::shared_ptr<UOConfig>(new UOConfig(this)); |
| 1015 | 1016 | } |
| 1016 | 1017 | |
| 1017 | 1018 | std::shared_ptr<QPDFJob::UOConfig> |
| 1018 | 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 | 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 | 711 | ignored. You can also give a page range with --repeat to cause |
| 712 | 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 | 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 | 2805 | ignored. You can also give a page range with --repeat to cause |
| 2806 | 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 | 2811 | Run qpdf --help=page-ranges for help with page ranges. |
| 2809 | 2812 | |
| 2810 | 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 | 2826 | are drawn on top of the page to which they are applied, possibly |
| 2824 | 2827 | obscuring the page. The ability to specify the file using the |
| 2825 | 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 | 2834 | The default behavior of overlay and underlay is that pages are taken |
| 2830 | 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 | 849 | ignored. You can also give a page range with --repeat to cause |
| 850 | 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 | 855 | Run qpdf --help=page-ranges for help with page ranges. |
| 853 | 856 | .PP |
| 854 | 857 | Related Options: | ... | ... |
manual/release-notes.rst
| ... | ... | @@ -48,6 +48,13 @@ Planned changes for future 12.x (subject to change): |
| 48 | 48 | as well. These new options can be freely intermixed with |
| 49 | 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 | 58 | - Library Enhancements |
| 52 | 59 | |
| 53 | 60 | - Add ``file()``, ``range()``, and ``password()`` to | ... | ... |