1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "pdf/ui/thumbnail.h"
#include <stddef.h>
#include <algorithm>
#include <cmath>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/numerics/checked_math.h"
#include "base/values.h"
#include "ui/gfx/geometry/size.h"
namespace chrome_pdf {
namespace {
constexpr float kMinDevicePixelRatio = 0.25;
constexpr float kMaxDevicePixelRatio = 2;
constexpr int kImageColorChannels = 4;
// TODO(crbug.com/702993): Reevaluate the thumbnail size cap when the PDF
// component migrates off of PPAPI.
// The maximum thumbnail area is essentially arbitrary, but the value was chosen
// considering the fact that when sending array buffers through PPAPI, if the
// size of the data is over 256KiB, it gets sent using shared memory instead of
// IPC. Thumbnail sizes are capped at 255KiB to avoid the 256KiB threshold for
// images and their metadata, such as size.
constexpr int kMaxThumbnailPixels = 255 * 1024 / kImageColorChannels;
// Maximum CSS dimensions are set to match UX specifications.
// These constants should be kept in sync with `PORTRAIT_WIDTH` and
// `LANDSCAPE_WIDTH` in
// chrome/browser/resources/pdf/elements/viewer-thumbnail.ts.
constexpr int kMaxWidthPortraitPx = 108;
constexpr int kMaxWidthLandscapePx = 140;
// PDF page size limits in default user space units, as defined by PDF 1.7 Annex
// C.2, "Architectural limits".
constexpr int kPdfPageMinDimension = 3;
constexpr int kPdfPageMaxDimension = 14400;
constexpr int kPdfMaxAspectRatio = kPdfPageMaxDimension / kPdfPageMinDimension;
// Limit the proportions within PDF limits to handle pathological PDF pages.
gfx::Size LimitAspectRatio(gfx::Size page_size) {
// Bump up any lengths of 0 to 1.
page_size.SetToMax(gfx::Size(1, 1));
if (page_size.height() / page_size.width() > kPdfMaxAspectRatio)
return gfx::Size(kPdfPageMinDimension, kPdfPageMaxDimension);
if (page_size.width() / page_size.height() > kPdfMaxAspectRatio)
return gfx::Size(kPdfPageMaxDimension, kPdfPageMinDimension);
return page_size;
}
// Calculate the size of a thumbnail image in device pixels using `page_size` in
// any units and `device_pixel_ratio`.
gfx::Size CalculateBestFitSize(const gfx::Size& page_size,
float device_pixel_ratio) {
gfx::Size safe_page_size = LimitAspectRatio(page_size);
// Return the larger of the unrotated and rotated sizes to over-sample the PDF
// page so that the thumbnail looks good in different orientations.
float scale_portrait =
static_cast<float>(kMaxWidthPortraitPx) /
std::min(safe_page_size.width(), safe_page_size.height());
float scale_landscape =
static_cast<float>(kMaxWidthLandscapePx) /
std::max(safe_page_size.width(), safe_page_size.height());
float scale = std::max(scale_portrait, scale_landscape) * device_pixel_ratio;
// Using gfx::ScaleToFlooredSize() is fine because `scale` will not yield an
// empty size unless `device_pixel_ratio` is very small (close to 0).
// However, `device_pixel_ratio` support is limited to between 0.25 and 2.
gfx::Size scaled_size = gfx::ScaleToFlooredSize(safe_page_size, scale);
if (scaled_size.GetCheckedArea().ValueOrDefault(kMaxThumbnailPixels + 1) >
kMaxThumbnailPixels) {
// Recalculate `scale` to accommodate pixel size limit such that:
// (scale * safe_page_size.width()) * (scale * safe_page_size.height()) ==
// kMaxThumbnailPixels;
scale = std::sqrt(static_cast<float>(kMaxThumbnailPixels) /
safe_page_size.width() / safe_page_size.height());
return gfx::ScaleToFlooredSize(safe_page_size, scale);
}
return scaled_size;
}
int CalculateStride(int width) {
base::CheckedNumeric<size_t> stride = kImageColorChannels;
stride *= width;
return stride.ValueOrDie<int>();
}
size_t CalculateImageDataSize(int stride, int height) {
base::CheckedNumeric<int> size = stride;
size *= height;
return size.ValueOrDie<size_t>();
}
} // namespace
Thumbnail::Thumbnail(const gfx::Size& page_size, float device_pixel_ratio)
: device_pixel_ratio_(std::clamp(device_pixel_ratio,
kMinDevicePixelRatio,
kMaxDevicePixelRatio)),
image_size_(CalculateBestFitSize(page_size, device_pixel_ratio_)),
stride_(CalculateStride(image_size_.width())),
image_data_(CalculateImageDataSize(stride(), image_size().height())) {
DCHECK(!image_data_.empty());
}
Thumbnail::Thumbnail(Thumbnail&& other) = default;
Thumbnail& Thumbnail::operator=(Thumbnail&& other) = default;
Thumbnail::~Thumbnail() = default;
base::Value::BlobStorage& Thumbnail::GetImageData() {
DCHECK(!image_data_.empty());
return image_data_;
}
base::Value::BlobStorage Thumbnail::TakeData() {
DCHECK(!image_data_.empty());
return std::move(image_data_);
}
} // namespace chrome_pdf
|