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 
17 package com.android.printspooler.util;
18 
19 import android.print.PageRange;
20 import android.print.PrintDocumentInfo;
21 
22 import java.util.Arrays;
23 import java.util.Comparator;
24 
25 /**
26  * This class contains utility functions for working with page ranges.
27  */
28 public 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 
PageRangeUtils()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      */
contains(PageRange[] pageRanges, int pageIndex)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      */
contains(PageRange[] ourRanges, PageRange[] otherRanges, int pageCount)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      */
normalize(PageRange[] pageRanges)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      */
offset(PageRange[] pageRanges, int offset)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      */
getNormalizedPageCount(PageRange[] pageRanges, int layoutPageCount)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 
asAbsoluteRange(PageRange pageRange, int pageCount)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 
isAllPages(PageRange[] pageRanges)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 
isAllPages(PageRange pageRange)215     public static boolean isAllPages(PageRange pageRange) {
216         return PageRange.ALL_PAGES.equals(pageRange);
217     }
218 
isAllPages(PageRange[] pageRanges, int pageCount)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 
isAllPages(PageRange pageRanges, int pageCount)230     public static boolean isAllPages(PageRange pageRanges, int pageCount) {
231         return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
232     }
233 
computePrintedPages(PageRange[] requestedPages, PageRange[] writtenPages, int pageCount)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