docs: private int loadPage(final int userPage, final int nbOfPartsLoadable) {
  
          // Finds the document page associated with the given userPage
          int documentPage = userPage;
          if (filteredUserPages != null) {
              if (userPage < 0 || userPage >= filteredUserPages.length) {
                  return 0;
              } else {
                  documentPage = filteredUserPages[userPage];
              }
          }
          final int documentPageFinal = documentPage;
          if (documentPage < 0 || userPage >= documentPageCount) {
              return 0;
          }
  
          // Render thumbnail of the page
          if (!cacheManager.containsThumbnail(userPage, documentPage, //
                  (int) (optimalPageWidth * Constants.THUMBNAIL_RATIO), //
                  (int) (optimalPageHeight * Constants.THUMBNAIL_RATIO), //
                  new RectF(0, 0, 1, 1))) {
              renderingAsyncTask.addRenderingTask(userPage, documentPage, //
                      (int) (optimalPageWidth * Constants.THUMBNAIL_RATIO), //
                      (int) (optimalPageHeight * Constants.THUMBNAIL_RATIO), //
                      new RectF(0, 0, 1, 1), true, 0);
          }
  
          // When we want to render a 256x256 bloc, we also need to provide
          // the bounds (left, top, right, bottom) of the rendered part in
          // the PDF page. These four coordinates are ratios (0 -> 1), where
          // (0,0) is the top left corner of the PDF page, and (1,1) is the
          // bottom right corner.
          float ratioX = 1f / (float) optimalPageWidth;
          float ratioY = 1f / (float) optimalPageHeight;
          final float partHeight = (Constants.PART_SIZE * ratioY) / zoom;
          final float partWidth = (Constants.PART_SIZE * ratioX) / zoom;
          final int nbRows = (int) Math.ceil(1f / partHeight);
          final int nbCols = (int) Math.ceil(1f / partWidth);
          final float pageRelativePartWidth = 1f / (float) nbCols;
          final float pageRelativePartHeight = 1f / (float) nbRows;
  
          // To improve user experience, we need to start displaying the
          // 256x256 blocs with the middle of the screen. Imagine the cut
          // page as a grid. This part calculates which cell of this grid
          // is currently in the middle of the screen, given the current
          // zoom level and the offsets.
          float middleOfScreenX = (-currentXOffset + getWidth() / 2);
          float middleOfScreenY = (-currentYOffset + getHeight() / 2);
          float middleOfScreenPageX;
          float middleOfScreenPageY;
          if (!swipeVertical) {
          	middleOfScreenPageX = middleOfScreenX - userPage * toCurrentScale(optimalPageWidth);
          	middleOfScreenPageY = middleOfScreenY;
          } else {
          	middleOfScreenPageY = middleOfScreenY - userPage * toCurrentScale(optimalPageHeight);
          	middleOfScreenPageX = middleOfScreenX;
          }
          float middleOfScreenPageXRatio = middleOfScreenPageX / toCurrentScale(optimalPageWidth);
          float middleOfScreenPageYRatio = middleOfScreenPageY / toCurrentScale(optimalPageHeight);
          int startingRow = (int) (middleOfScreenPageYRatio * nbRows);
          int startingCol = (int) (middleOfScreenPageXRatio * nbCols);
  
          // Avoid outside values
          startingRow = NumberUtils.limit(startingRow, 0, nbRows);
          startingCol = NumberUtils.limit(startingCol, 0, nbCols);
  
          // Prepare the loop listener
          class SpiralLoopListenerImpl implements SpiralLoopManager.SpiralLoopListener {
              int nbItemTreated = 0;
  
              @Override
              public boolean onLoop(int row, int col) {
  
                  // Create relative page bounds
                  float relX = pageRelativePartWidth * col;
                  float relY = pageRelativePartHeight * row;
                  float relWidth = pageRelativePartWidth;
                  float relHeight = pageRelativePartHeight;
  
                  // Adjust width and height to
                  // avoid being outside the page
                  float renderWidth = Constants.PART_SIZE / relWidth;
                  float renderHeight = Constants.PART_SIZE / relHeight;
                  if (relX + relWidth > 1) {
                      relWidth = 1 - relX;
                  }
                  if (relY + relHeight > 1) {
                      relHeight = 1 - relY;
                  }
                  renderWidth *= relWidth;
                  renderHeight *= relHeight;
                  RectF pageRelativeBounds = new RectF(relX, relY, relX + relWidth, relY + relHeight);
  
                  if (renderWidth != 0 && renderHeight != 0) {
  
                      // Check it the calculated part is already contained in the Cache
                      // If it is, this call will insure the part will go to the right
                      // place in the cache and won't be deleted if the cache need space.
                      if (!cacheManager.upPartIfContained(userPage, documentPageFinal, //
                              renderWidth, renderHeight, pageRelativeBounds, nbItemTreated)) {
  
                          // If not already in cache, register the rendering
                          // task for further execution.
                          renderingAsyncTask.addRenderingTask(userPage, documentPageFinal, //
                                  renderWidth, renderHeight, pageRelativeBounds, false, nbItemTreated);
                      }
  
                  }
  
                  nbItemTreated++;
                  if (nbItemTreated >= nbOfPartsLoadable) {
                      // Return false to stop the loop
                      return false;
                  }
                  return true;
              }
          }
  
          // Starts the loop
          SpiralLoopListenerImpl spiralLoopListener;
          new SpiralLoopManager(spiralLoopListener = new SpiralLoopListenerImpl())//
                  .startLoop(nbRows, nbCols, startingRow, startingCol);
  
          return spiralLoopListener.nbItemTreated;
      }