blob: c4653e1d6b7ab83b04cae26c01a235f62835b4ab [file] [log] [blame]
// Copyright 2024 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/pdf_ink_transform.h"
#include <algorithm>
#include <optional>
#include "base/check_op.h"
#include "base/notreached.h"
#include "pdf/page_rotation.h"
#include "printing/units.h"
#include "third_party/ink/src/ink/geometry/envelope.h"
#include "third_party/ink/src/ink/geometry/rect.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/geometry/size_f.h"
#include "ui/gfx/geometry/transform.h"
#include "ui/gfx/geometry/vector2d_f.h"
using printing::kUnitConversionFactorPixelsToPoints;
namespace chrome_pdf {
namespace {
gfx::Size GetOriginalUnrotatedSize(PageOrientation orientation,
const gfx::Size& size) {
if (!IsTransposedPageOrientation(orientation)) {
return size;
}
gfx::Size transposed_size(size);
transposed_size.Transpose();
return transposed_size;
}
} // namespace
gfx::Transform GetEventToCanonicalTransform(PageOrientation orientation,
const gfx::Rect& page_content_rect,
float scale_factor) {
CHECK_GT(scale_factor, 0.0f);
CHECK(!page_content_rect.IsEmpty());
gfx::Transform transform;
transform.PostTranslate(-page_content_rect.OffsetFromOrigin());
switch (orientation) {
case PageOrientation::kOriginal:
// No further modification needed.
break;
case PageOrientation::kClockwise90:
transform.PostConcat(gfx::Transform::Make270degRotation());
transform.PostTranslate(0, page_content_rect.width() - 1);
break;
case PageOrientation::kClockwise180:
transform.PostConcat(gfx::Transform::Make180degRotation());
transform.PostTranslate(page_content_rect.width() - 1,
page_content_rect.height() - 1);
break;
case PageOrientation::kClockwise270:
transform.PostConcat(gfx::Transform::Make90degRotation());
transform.PostTranslate(page_content_rect.height() - 1, 0);
break;
}
transform.PostScale(1 / scale_factor);
return transform;
}
ink::AffineTransform GetInkRenderTransform(
const gfx::Vector2dF& viewport_origin_offset,
PageOrientation orientation,
const gfx::Rect& page_content_rect,
const gfx::SizeF& page_size_in_points) {
CHECK_GE(viewport_origin_offset.x(), 0.0f);
CHECK_GE(viewport_origin_offset.y(), 0.0f);
CHECK(!page_content_rect.IsEmpty());
CHECK(!page_size_in_points.IsEmpty());
// To avoid a noticeable shift in position of an in-progress vs. applied
// Ink stroke, the rendering transform generated here needs to match the
// matrix setup done in PDFium's `CPDF_Page::GetDisplayMatrix()`.
const float dx = viewport_origin_offset.x() + page_content_rect.x();
const float dy = viewport_origin_offset.y() + page_content_rect.y();
const gfx::Size original_unrotated_page_size =
GetOriginalUnrotatedSize(orientation, page_content_rect.size());
const float scale_factor_x = original_unrotated_page_size.width() *
kUnitConversionFactorPixelsToPoints /
page_size_in_points.width();
const float scale_factor_y = original_unrotated_page_size.height() *
kUnitConversionFactorPixelsToPoints /
page_size_in_points.height();
switch (orientation) {
case PageOrientation::kOriginal:
return ink::AffineTransform(scale_factor_x, 0, dx, 0, scale_factor_y, dy);
case PageOrientation::kClockwise90:
return ink::AffineTransform(0, -scale_factor_x,
dx + page_content_rect.width(),
scale_factor_y, 0, dy);
case PageOrientation::kClockwise180:
return ink::AffineTransform(
-scale_factor_x, 0, dx + page_content_rect.width(), 0,
-scale_factor_y, dy + page_content_rect.height());
case PageOrientation::kClockwise270:
return ink::AffineTransform(0, scale_factor_x, dx, -scale_factor_y, 0,
dy + page_content_rect.height());
}
NOTREACHED();
}
ink::AffineTransform GetInkThumbnailTransform(
const gfx::Size& canvas_size,
PageOrientation orientation,
const gfx::Rect& page_content_rect,
float scale_factor) {
// Since thumbnails are always drawn without any rotation, the transform only
// needs to perform scaling.
//
// However, `page_content_rect` may be rotated, so normalize it as needed.
gfx::Size content_size = page_content_rect.size();
if (orientation == PageOrientation::kClockwise90 ||
orientation == PageOrientation::kClockwise270) {
content_size.Transpose();
}
const float ratio =
scale_factor *
std::min(
static_cast<float>(canvas_size.width()) / content_size.width(),
static_cast<float>(canvas_size.height()) / content_size.height());
return {ratio, 0, 0, 0, ratio, 0};
}
gfx::Rect CanonicalInkEnvelopeToInvalidationScreenRect(
const ink::Envelope& envelope,
PageOrientation orientation,
const gfx::Rect& page_content_rect,
float scale_factor) {
const std::optional<ink::Rect>& ink_rect = envelope.AsRect();
CHECK(ink_rect.has_value());
gfx::Transform transform =
GetEventToCanonicalTransform(orientation, page_content_rect, scale_factor)
.GetCheckedInverse();
gfx::PointF p1 =
transform.MapPoint(gfx::PointF(ink_rect->XMin(), ink_rect->YMin()));
gfx::PointF p2 =
transform.MapPoint(gfx::PointF(ink_rect->XMax(), ink_rect->YMax()));
// Width and height get +1 since both of the points are to be included in the
// area; otherwise it would be an open rectangle on two edges.
float x = std::min(p1.x(), p2.x());
float y = std::min(p1.y(), p2.y());
float w = std::max(p1.x(), p2.x()) - x + 1;
float h = std::max(p1.y(), p2.y()) - y + 1;
return gfx::ToEnclosingRect(gfx::RectF(x, y, w, h));
}
gfx::Transform GetCanonicalToPdfTransform(const gfx::SizeF& page_size,
PageRotation page_rotation,
const gfx::Vector2dF& translate) {
CHECK_GE(page_size.width(), 0);
CHECK_GE(page_size.height(), 0);
auto transform =
gfx::Transform::MakeScale(kUnitConversionFactorPixelsToPoints,
-kUnitConversionFactorPixelsToPoints);
switch (page_rotation) {
case PageRotation::kRotate0:
transform.PostTranslate(
{translate.x(), page_size.height() + translate.y()});
return transform;
case PageRotation::kRotate90:
transform.PostConcat(gfx::Transform::Make90degRotation());
transform.PostTranslate({translate.x(), translate.y()});
return transform;
case PageRotation::kRotate180:
transform.PostConcat(gfx::Transform::Make180degRotation());
transform.PostTranslate(
{page_size.width() + translate.x(), translate.y()});
return transform;
case PageRotation::kRotate270:
transform.PostConcat(gfx::Transform::Make270degRotation());
transform.PostTranslate({page_size.height() + translate.x(),
page_size.width() + translate.y()});
return transform;
}
NOTREACHED();
}
} // namespace chrome_pdf