#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"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/size_f.h"
namespace chrome_pdf {
namespace {
constexpr int kImageColorChannels = 4;
constexpr int kMaxThumbnailPixels = 255 * 1024 / kImageColorChannels;
constexpr int kMaxWidthPortraitPx = 108;
constexpr int kMaxWidthLandscapePx = 140;
constexpr int kPdfPageMinDimension = 3;
constexpr int kPdfPageMaxDimension = 14400;
constexpr int kPdfMaxAspectRatio = kPdfPageMaxDimension / kPdfPageMinDimension;
float ClampDevicePixelRatio(float device_pixel_ratio) {
static constexpr float kMinDevicePixelRatio = 0.25;
static constexpr float kMaxDevicePixelRatio = 2;
return std::clamp(device_pixel_ratio, kMinDevicePixelRatio,
kMaxDevicePixelRatio);
}
gfx::SizeF LimitAspectRatio(gfx::SizeF page_size) {
page_size.SetToMax(gfx::SizeF(1, 1));
if (page_size.height() / page_size.width() > kPdfMaxAspectRatio) {
return gfx::SizeF(kPdfPageMinDimension, kPdfPageMaxDimension);
}
if (page_size.width() / page_size.height() > kPdfMaxAspectRatio) {
return gfx::SizeF(kPdfPageMaxDimension, kPdfPageMinDimension);
}
return page_size;
}
gfx::Size CalculateBestFitSize(const gfx::SizeF& page_size,
float device_pixel_ratio) {
gfx::SizeF safe_page_size = LimitAspectRatio(page_size);
const std::pair<float, float> minmax =
std::minmax(safe_page_size.width(), safe_page_size.height());
float scale_portrait = static_cast<float>(kMaxWidthPortraitPx) / minmax.first;
float scale_landscape =
static_cast<float>(kMaxWidthLandscapePx) / minmax.second;
float scale = std::max(scale_portrait, scale_landscape) * device_pixel_ratio;
gfx::Size scaled_size =
gfx::ToFlooredSize(gfx::ScaleSize(safe_page_size, scale));
if (scaled_size.GetCheckedArea().ValueOrDefault(kMaxThumbnailPixels + 1) >
kMaxThumbnailPixels) {
scale = std::sqrt(static_cast<float>(kMaxThumbnailPixels) /
safe_page_size.width() / safe_page_size.height());
return gfx::ToFlooredSize(gfx::ScaleSize(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>();
}
}
gfx::Size Thumbnail::CalculateImageSize(const gfx::SizeF& page_size,
float device_pixel_ratio) {
return CalculateBestFitSize(page_size,
ClampDevicePixelRatio(device_pixel_ratio));
}
Thumbnail::Thumbnail(const gfx::SizeF& page_size, float device_pixel_ratio)
: device_pixel_ratio_(ClampDevicePixelRatio(device_pixel_ratio)),
image_size_(CalculateBestFitSize(page_size, device_pixel_ratio_)),
should_render_blank_(page_size.IsEmpty()),
stride_(CalculateStride(image_size_.width())),
image_data_(CalculateImageDataSize(stride(), image_size().height())) {
DCHECK(!image_data_.empty());
}
Thumbnail::Thumbnail(Thumbnail&& other) noexcept = default;
Thumbnail& Thumbnail::operator=(Thumbnail&& other) noexcept = 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_);
}
}