1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.printspooler.util;
18
19import android.print.PageRange;
20import android.print.PrintDocumentInfo;
21
22import java.util.Arrays;
23import java.util.Comparator;
24
25/**
26 * This class contains utility functions for working with page ranges.
27 */
28public final class PageRangeUtils {
29
30    private static final PageRange[] ALL_PAGES_RANGE = new PageRange[] {PageRange.ALL_PAGES};
31
32    private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
33        @Override
34        public int compare(PageRange lhs, PageRange rhs) {
35            return lhs.getStart() - rhs.getStart();
36        }
37    };
38
39    private PageRangeUtils() {
40        /* do nothing - hide constructor */
41    }
42
43    /**
44     * Gets whether page ranges contains a given page.
45     *
46     * @param pageRanges The page ranges.
47     * @param pageIndex The page for which to check.
48     * @return Whether the page is within the ranges.
49     */
50    public static boolean contains(PageRange[] pageRanges, int pageIndex) {
51        final int rangeCount = pageRanges.length;
52        for (int i = 0; i < rangeCount; i++) {
53            PageRange pageRange = pageRanges[i];
54            if (pageRange.contains(pageIndex)) {
55                return true;
56            }
57        }
58        return false;
59    }
60
61    /**
62     * Checks whether one page range array contains another one.
63     *
64     * @param ourRanges The container page ranges.
65     * @param otherRanges The contained page ranges.
66     * @param pageCount The total number of pages.
67     * @return Whether the container page ranges contains the contained ones.
68     */
69    public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges, int pageCount) {
70        if (ourRanges == null || otherRanges == null) {
71            return false;
72        }
73
74        if (Arrays.equals(ourRanges, ALL_PAGES_RANGE)) {
75            return true;
76        }
77
78        if (Arrays.equals(otherRanges, ALL_PAGES_RANGE)) {
79            otherRanges[0] = new PageRange(0, pageCount - 1);
80        }
81
82        ourRanges = normalize(ourRanges);
83        otherRanges = normalize(otherRanges);
84
85        // Note that the code below relies on the ranges being normalized
86        // which is they contain monotonically increasing non-intersecting
87        // sub-ranges whose start is less that or equal to the end.
88        int otherRangeIdx = 0;
89        final int ourRangeCount = ourRanges.length;
90        final int otherRangeCount = otherRanges.length;
91        for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
92            PageRange ourRange = ourRanges[ourRangeIdx];
93            for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
94                PageRange otherRange = otherRanges[otherRangeIdx];
95                if (otherRange.getStart() > ourRange.getEnd()) {
96                    break;
97                }
98                if (otherRange.getStart() < ourRange.getStart()
99                        || otherRange.getEnd() > ourRange.getEnd()) {
100                    return false;
101                }
102            }
103        }
104        return (otherRangeIdx >= otherRangeCount);
105    }
106
107    /**
108     * Normalizes a page range, which is the resulting page ranges are
109     * non-overlapping with the start lesser than or equal to the end
110     * and ordered in an ascending order.
111     *
112     * @param pageRanges The page ranges to normalize.
113     * @return The normalized page ranges.
114     */
115    public static PageRange[] normalize(PageRange[] pageRanges) {
116        if (pageRanges == null) {
117            return null;
118        }
119
120        final int oldRangeCount = pageRanges.length;
121        if (oldRangeCount <= 1) {
122            return pageRanges;
123        }
124
125        Arrays.sort(pageRanges, sComparator);
126
127        int newRangeCount = 1;
128        for (int i = 0; i < oldRangeCount - 1; i++) {
129            PageRange currentRange = pageRanges[i];
130            PageRange nextRange = pageRanges[i + 1];
131            if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
132                pageRanges[i] = null;
133                pageRanges[i + 1] = new PageRange(currentRange.getStart(),
134                        Math.max(currentRange.getEnd(), nextRange.getEnd()));
135            } else {
136                newRangeCount++;
137            }
138        }
139
140        if (newRangeCount == oldRangeCount) {
141            return pageRanges;
142        }
143
144        int normalRangeIndex = 0;
145        PageRange[] normalRanges = new PageRange[newRangeCount];
146        for (int i = 0; i < oldRangeCount; i++) {
147            PageRange normalRange = pageRanges[i];
148            if (normalRange != null) {
149                normalRanges[normalRangeIndex] = normalRange;
150                normalRangeIndex++;
151            }
152        }
153
154        return normalRanges;
155    }
156
157    /**
158     * Offsets a the start and end of page ranges with the given value.
159     *
160     * @param pageRanges The page ranges to offset.
161     * @param offset The offset value.
162     */
163    public static void offset(PageRange[] pageRanges, int offset) {
164        if (offset == 0) {
165            return;
166        }
167        final int pageRangeCount = pageRanges.length;
168        for (int i = 0; i < pageRangeCount; i++) {
169            final int start = pageRanges[i].getStart() + offset;
170            final int end = pageRanges[i].getEnd() + offset;
171            pageRanges[i] = new PageRange(start, end);
172        }
173    }
174
175    /**
176     * Gets the number of pages in a normalized range array.
177     *
178     * @param pageRanges Normalized page ranges.
179     * @param layoutPageCount Page count after reported after layout pass.
180     * @return The page count in the ranges.
181     */
182    public static int getNormalizedPageCount(PageRange[] pageRanges, int layoutPageCount) {
183        int pageCount = 0;
184        if (pageRanges != null) {
185            final int pageRangeCount = pageRanges.length;
186            for (int i = 0; i < pageRangeCount; i++) {
187                PageRange pageRange = pageRanges[i];
188                if (PageRange.ALL_PAGES.equals(pageRange)) {
189                    return layoutPageCount;
190                }
191                pageCount += pageRange.getSize();
192            }
193        }
194        return pageCount;
195    }
196
197    public static PageRange asAbsoluteRange(PageRange pageRange, int pageCount) {
198        if (PageRange.ALL_PAGES.equals(pageRange)) {
199            return new PageRange(0, pageCount - 1);
200        }
201        return pageRange;
202    }
203
204    public static boolean isAllPages(PageRange[] pageRanges) {
205        final int pageRangeCount = pageRanges.length;
206        for (int i = 0; i < pageRangeCount; i++) {
207            PageRange pageRange = pageRanges[i];
208            if (isAllPages(pageRange)) {
209                return true;
210            }
211        }
212        return false;
213    }
214
215    public static boolean isAllPages(PageRange pageRange) {
216        return PageRange.ALL_PAGES.equals(pageRange);
217    }
218
219    public static boolean isAllPages(PageRange[] pageRanges, int pageCount) {
220        final int pageRangeCount = pageRanges.length;
221        for (int i = 0; i < pageRangeCount; i++) {
222            PageRange pageRange = pageRanges[i];
223            if (isAllPages(pageRange, pageCount)) {
224                return true;
225            }
226        }
227        return false;
228    }
229
230    public static boolean isAllPages(PageRange pageRanges, int pageCount) {
231        return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
232    }
233
234    public static PageRange[] computePrintedPages(PageRange[] requestedPages,
235            PageRange[] writtenPages, int pageCount) {
236        // Adjust the print job pages based on what was requested and written.
237        // The cases are ordered in the most expected to the least expected
238        // with a special case first where the app does not know the page count
239        // so we ask for all to be written.
240        if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
241                && pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
242            return ALL_PAGES_RANGE;
243        } else if (Arrays.equals(writtenPages, requestedPages)) {
244            // We got a document with exactly the pages we wanted. Hence,
245            // the printer has to print all pages in the data.
246            return ALL_PAGES_RANGE;
247        } else if (Arrays.equals(writtenPages, ALL_PAGES_RANGE)) {
248            // We requested specific pages but got all of them. Hence,
249            // the printer has to print only the requested pages.
250            return requestedPages;
251        } else if (PageRangeUtils.contains(writtenPages, requestedPages, pageCount)) {
252            // We requested specific pages and got more but not all pages.
253            // Hence, we have to offset appropriately the printed pages to
254            // be based off the start of the written ones instead of zero.
255            // The written pages are always non-null and not empty.
256            final int offset = -writtenPages[0].getStart();
257            PageRangeUtils.offset(requestedPages, offset);
258            return requestedPages;
259        } else if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
260                && isAllPages(writtenPages, pageCount)) {
261            // We requested all pages via the special constant and got all
262            // of them as an explicit enumeration. Hence, the printer has
263            // to print only the requested pages.
264            return ALL_PAGES_RANGE;
265        }
266
267        return null;
268    }
269}
270