Home | History | Annotate | Download | only in util
      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 import android.util.Pair;
     22 
     23 import java.util.ArrayList;
     24 import java.util.Arrays;
     25 import java.util.Comparator;
     26 
     27 /**
     28  * This class contains utility functions for working with page ranges.
     29  */
     30 public final class PageRangeUtils {
     31 
     32     private static final PageRange[] ALL_PAGES_RANGE = new PageRange[] {PageRange.ALL_PAGES};
     33 
     34     private static final Comparator<PageRange> sComparator = new Comparator<PageRange>() {
     35         @Override
     36         public int compare(PageRange lhs, PageRange rhs) {
     37             return lhs.getStart() - rhs.getStart();
     38         }
     39     };
     40 
     41     private PageRangeUtils() {
     42         /* do nothing - hide constructor */
     43     }
     44 
     45     /**
     46      * Gets whether page ranges contains a given page.
     47      *
     48      * @param pageRanges The page ranges.
     49      * @param pageIndex The page for which to check.
     50      * @return Whether the page is within the ranges.
     51      */
     52     public static boolean contains(PageRange[] pageRanges, int pageIndex) {
     53         final int rangeCount = pageRanges.length;
     54         for (int i = 0; i < rangeCount; i++) {
     55             PageRange pageRange = pageRanges[i];
     56             if (pageRange.contains(pageIndex)) {
     57                 return true;
     58             }
     59         }
     60         return false;
     61     }
     62 
     63     /**
     64      * Checks whether one page range array contains another one.
     65      *
     66      * @param ourRanges The container page ranges.
     67      * @param otherRanges The contained page ranges.
     68      * @param pageCount The total number of pages.
     69      * @return Whether the container page ranges contains the contained ones.
     70      */
     71     public static boolean contains(PageRange[] ourRanges, PageRange[] otherRanges, int pageCount) {
     72         if (ourRanges == null || otherRanges == null) {
     73             return false;
     74         }
     75 
     76         if (Arrays.equals(ourRanges, ALL_PAGES_RANGE)) {
     77             return true;
     78         }
     79 
     80         if (Arrays.equals(otherRanges, ALL_PAGES_RANGE)) {
     81             otherRanges[0] = new PageRange(0, pageCount - 1);
     82         }
     83 
     84         ourRanges = normalize(ourRanges);
     85         otherRanges = normalize(otherRanges);
     86 
     87         // Note that the code below relies on the ranges being normalized
     88         // which is they contain monotonically increasing non-intersecting
     89         // sub-ranges whose start is less that or equal to the end.
     90         int otherRangeIdx = 0;
     91         final int ourRangeCount = ourRanges.length;
     92         final int otherRangeCount = otherRanges.length;
     93         for (int ourRangeIdx = 0; ourRangeIdx < ourRangeCount; ourRangeIdx++) {
     94             PageRange ourRange = ourRanges[ourRangeIdx];
     95             for (; otherRangeIdx < otherRangeCount; otherRangeIdx++) {
     96                 PageRange otherRange = otherRanges[otherRangeIdx];
     97                 if (otherRange.getStart() > ourRange.getEnd()) {
     98                     break;
     99                 }
    100                 if (otherRange.getStart() < ourRange.getStart()
    101                         || otherRange.getEnd() > ourRange.getEnd()) {
    102                     return false;
    103                 }
    104             }
    105         }
    106         return (otherRangeIdx >= otherRangeCount);
    107     }
    108 
    109     /**
    110      * Normalizes a page range, which is the resulting page ranges are
    111      * non-overlapping with the start lesser than or equal to the end
    112      * and ordered in an ascending order.
    113      *
    114      * @param pageRanges The page ranges to normalize.
    115      * @return The normalized page ranges.
    116      */
    117     public static PageRange[] normalize(PageRange[] pageRanges) {
    118         if (pageRanges == null) {
    119             return null;
    120         }
    121 
    122         final int oldRangeCount = pageRanges.length;
    123         if (oldRangeCount <= 1) {
    124             return pageRanges;
    125         }
    126 
    127         Arrays.sort(pageRanges, sComparator);
    128 
    129         int newRangeCount = 1;
    130         for (int i = 0; i < oldRangeCount - 1; i++) {
    131             PageRange currentRange = pageRanges[i];
    132             PageRange nextRange = pageRanges[i + 1];
    133             if (currentRange.getEnd() + 1 >= nextRange.getStart()) {
    134                 pageRanges[i] = null;
    135                 pageRanges[i + 1] = new PageRange(currentRange.getStart(),
    136                         Math.max(currentRange.getEnd(), nextRange.getEnd()));
    137             } else {
    138                 newRangeCount++;
    139             }
    140         }
    141 
    142         if (newRangeCount == oldRangeCount) {
    143             return pageRanges;
    144         }
    145 
    146         int normalRangeIndex = 0;
    147         PageRange[] normalRanges = new PageRange[newRangeCount];
    148         for (int i = 0; i < oldRangeCount; i++) {
    149             PageRange normalRange = pageRanges[i];
    150             if (normalRange != null) {
    151                 normalRanges[normalRangeIndex] = normalRange;
    152                 normalRangeIndex++;
    153             }
    154         }
    155 
    156         return normalRanges;
    157     }
    158 
    159     /**
    160      * Return the next position after {@code pos} that is not a space character.
    161      *
    162      * @param s   The string to parse
    163      * @param pos The starting position
    164      *
    165      * @return The position of the first space character
    166      */
    167     private static int readWhiteSpace(CharSequence s, int pos) {
    168         while (pos < s.length() && s.charAt(pos) == ' ') {
    169             pos++;
    170         }
    171 
    172         return pos;
    173     }
    174 
    175     /**
    176      * Read a number from a string at a certain position.
    177      *
    178      * @param s   The string to parse
    179      * @param pos The starting position
    180      *
    181      * @return The position after the number + the number read or null if the number was not found
    182      */
    183     private static Pair<Integer, Integer> readNumber(CharSequence s, int pos) {
    184         Integer result = 0;
    185         while (pos < s.length() && s.charAt(pos) >= '0' && s.charAt(pos) <= '9') {
    186             // Number cannot start with 0
    187             if (result == 0 && s.charAt(pos) == '0') {
    188                 break;
    189             }
    190             result = result * 10 + (s.charAt(pos) - '0');
    191             // Abort on overflow
    192             if (result < 0) {
    193                 break;
    194             }
    195             pos++;
    196         }
    197 
    198         // 0 is not a valid page number
    199         if (result == 0) {
    200             return new Pair<>(pos, null);
    201         } else {
    202             return new Pair<>(pos, result);
    203         }
    204     }
    205 
    206     /**
    207      * Read a single character from a string at a certain position.
    208      *
    209      * @param s            The string to parse
    210      * @param pos          The starting position
    211      * @param expectedChar The character to read
    212      *
    213      * @return The position after the character + the character read or null if the character was
    214      *         not found
    215      */
    216     private static Pair<Integer, Character> readChar(CharSequence s, int pos, char expectedChar) {
    217         if (pos < s.length() && s.charAt(pos) == expectedChar) {
    218             return new Pair<>(pos + 1, expectedChar);
    219         } else {
    220             return new Pair<>(pos, null);
    221         }
    222     }
    223 
    224     /**
    225      * Read a page range character from a string at a certain position.
    226      *
    227      * @param s             The string to parse
    228      * @param pos           The starting position
    229      * @param maxPageNumber The highest page number to accept.
    230      *
    231      * @return The position after the page range + the page range read or null if the page range was
    232      *         not found
    233      */
    234     private static Pair<Integer, PageRange> readRange(CharSequence s, int pos, int maxPageNumber) {
    235         Pair<Integer, Integer> retInt;
    236         Pair<Integer, Character> retChar;
    237 
    238         Character comma;
    239         if (pos == 0) {
    240             // When we reading the first range, we do not want to have a comma
    241             comma = ',';
    242         } else {
    243             retChar = readChar(s, pos, ',');
    244             pos = retChar.first;
    245             comma = retChar.second;
    246         }
    247 
    248         pos = readWhiteSpace(s, pos);
    249 
    250         retInt = readNumber(s, pos);
    251         pos = retInt.first;
    252         Integer start = retInt.second;
    253 
    254         pos = readWhiteSpace(s, pos);
    255 
    256         retChar = readChar(s, pos, '-');
    257         pos = retChar.first;
    258         Character separator = retChar.second;
    259 
    260         pos = readWhiteSpace(s, pos);
    261 
    262         retInt = readNumber(s, pos);
    263         pos = retInt.first;
    264         Integer end = retInt.second;
    265 
    266         pos = readWhiteSpace(s, pos);
    267 
    268         if (comma != null &&
    269                 // range, maybe unbounded
    270                 ((separator != null && (start != null || end != null)) ||
    271                         // single page
    272                         (separator == null && start != null && end == null))) {
    273             if (start == null) {
    274                 start = 1;
    275             }
    276 
    277             if (end == null) {
    278                 if (separator == null) {
    279                     end = start;
    280                 } else {
    281                     end = maxPageNumber;
    282                 }
    283             }
    284 
    285             if (start <= end && start >= 1 && end <= maxPageNumber) {
    286                 return new Pair<>(pos, new PageRange(start - 1, end - 1));
    287             }
    288         }
    289 
    290         return new Pair<>(pos, null);
    291     }
    292 
    293     /**
    294      * Parse a string into an array of page ranges.
    295      *
    296      * @param s             The string to parse
    297      * @param maxPageNumber The highest page number to accept.
    298      *
    299      * @return The parsed ranges or null if the string could not be parsed.
    300      */
    301     public static PageRange[] parsePageRanges(CharSequence s, int maxPageNumber) {
    302         ArrayList<PageRange> ranges = new ArrayList<>();
    303 
    304         int pos = 0;
    305         while (pos < s.length()) {
    306             Pair<Integer, PageRange> retRange = readRange(s, pos, maxPageNumber);
    307 
    308             if (retRange.second == null) {
    309                 ranges.clear();
    310                 break;
    311             }
    312 
    313             ranges.add(retRange.second);
    314             pos = retRange.first;
    315         }
    316 
    317         return PageRangeUtils.normalize(ranges.toArray(new PageRange[ranges.size()]));
    318     }
    319 
    320     /**
    321      * Offsets a the start and end of page ranges with the given value.
    322      *
    323      * @param pageRanges The page ranges to offset.
    324      * @param offset The offset value.
    325      */
    326     public static void offset(PageRange[] pageRanges, int offset) {
    327         if (offset == 0) {
    328             return;
    329         }
    330         final int pageRangeCount = pageRanges.length;
    331         for (int i = 0; i < pageRangeCount; i++) {
    332             final int start = pageRanges[i].getStart() + offset;
    333             final int end = pageRanges[i].getEnd() + offset;
    334             pageRanges[i] = new PageRange(start, end);
    335         }
    336     }
    337 
    338     /**
    339      * Gets the number of pages in a normalized range array.
    340      *
    341      * @param pageRanges Normalized page ranges.
    342      * @param layoutPageCount Page count after reported after layout pass.
    343      * @return The page count in the ranges.
    344      */
    345     public static int getNormalizedPageCount(PageRange[] pageRanges, int layoutPageCount) {
    346         int pageCount = 0;
    347         if (pageRanges != null) {
    348             final int pageRangeCount = pageRanges.length;
    349             for (int i = 0; i < pageRangeCount; i++) {
    350                 PageRange pageRange = pageRanges[i];
    351                 if (PageRange.ALL_PAGES.equals(pageRange)) {
    352                     return layoutPageCount;
    353                 }
    354                 pageCount += pageRange.getSize();
    355             }
    356         }
    357         return pageCount;
    358     }
    359 
    360     public static PageRange asAbsoluteRange(PageRange pageRange, int pageCount) {
    361         if (PageRange.ALL_PAGES.equals(pageRange)) {
    362             return new PageRange(0, pageCount - 1);
    363         }
    364         return pageRange;
    365     }
    366 
    367     public static boolean isAllPages(PageRange[] pageRanges) {
    368         final int pageRangeCount = pageRanges.length;
    369         for (int i = 0; i < pageRangeCount; i++) {
    370             PageRange pageRange = pageRanges[i];
    371             if (isAllPages(pageRange)) {
    372                 return true;
    373             }
    374         }
    375         return false;
    376     }
    377 
    378     public static boolean isAllPages(PageRange pageRange) {
    379         return PageRange.ALL_PAGES.equals(pageRange);
    380     }
    381 
    382     public static boolean isAllPages(PageRange[] pageRanges, int pageCount) {
    383         final int pageRangeCount = pageRanges.length;
    384         for (int i = 0; i < pageRangeCount; i++) {
    385             PageRange pageRange = pageRanges[i];
    386             if (isAllPages(pageRange, pageCount)) {
    387                 return true;
    388             }
    389         }
    390         return false;
    391     }
    392 
    393     public static boolean isAllPages(PageRange pageRanges, int pageCount) {
    394         return pageRanges.getStart() == 0 && pageRanges.getEnd() == pageCount - 1;
    395     }
    396 
    397     public static PageRange[] computePrintedPages(PageRange[] requestedPages,
    398             PageRange[] writtenPages, int pageCount) {
    399         // Adjust the print job pages based on what was requested and written.
    400         // The cases are ordered in the most expected to the least expected
    401         // with a special case first where the app does not know the page count
    402         // so we ask for all to be written.
    403         if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
    404                 && pageCount == PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
    405             return ALL_PAGES_RANGE;
    406         } else if (Arrays.equals(writtenPages, requestedPages)) {
    407             // We got a document with exactly the pages we wanted. Hence,
    408             // the printer has to print all pages in the data.
    409             return ALL_PAGES_RANGE;
    410         } else if (Arrays.equals(writtenPages, ALL_PAGES_RANGE)) {
    411             // We requested specific pages but got all of them. Hence,
    412             // the printer has to print only the requested pages.
    413             return requestedPages;
    414         } else if (PageRangeUtils.contains(writtenPages, requestedPages, pageCount)) {
    415             // We requested specific pages and got more but not all pages.
    416             // Hence, we have to offset appropriately the printed pages to
    417             // be based off the start of the written ones instead of zero.
    418             // The written pages are always non-null and not empty.
    419             final int offset = -writtenPages[0].getStart();
    420             PageRangeUtils.offset(requestedPages, offset);
    421             return requestedPages;
    422         } else if (Arrays.equals(requestedPages, ALL_PAGES_RANGE)
    423                 && isAllPages(writtenPages, pageCount)) {
    424             // We requested all pages via the special constant and got all
    425             // of them as an explicit enumeration. Hence, the printer has
    426             // to print only the requested pages.
    427             return ALL_PAGES_RANGE;
    428         }
    429 
    430         return null;
    431     }
    432 }
    433