blob: 6af460969dfe9864fce8eb6d8c6f7d6a85db1d44 [file]
// 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 {
// Performs an inverse operation of `EventPositionToCanonicalPosition()`, to
// convert from canonical coordinates to screen coordinates.
// TODO(crbug.com/379003898): Change EventPositionToCanonicalPosition() to
// return gfx::AxisTransform2d, so that callers can just use the inverse of
// the transform instead of this helper.
gfx::PointF CanonicalPositionToScreenPosition(
const gfx::PointF& canonical_position,
PageOrientation orientation,
const gfx::Rect& page_content_rect,
float scale_factor) {
CHECK_GT(scale_factor, 0.0f);
CHECK(!page_content_rect.IsEmpty());
gfx::PointF screen_position = canonical_position;
screen_position.Scale(scale_factor);
switch (orientation) {
case PageOrientation::kOriginal:
// No further modification needed.
break;
case PageOrientation::kClockwise90:
screen_position.SetPoint(
page_content_rect.width() - screen_position.y() - 1,
screen_position.x());
break;
case PageOrientation::kClockwise180:
screen_position.SetPoint(
page_content_rect.width() - screen_position.x() - 1,
page_content_rect.height() - screen_position.y() - 1);
break;
case PageOrientation::kClockwise270:
screen_position.SetPoint(
screen_position.y(),
page_content_rect.height() - screen_position.x() - 1);
break;
}
// Account for scrolling, which is in the page content's origin.
screen_position += page_content_rect.origin().OffsetFromOrigin();
return screen_position;
}
gfx::Size GetOriginalUnrotatedSize(PageOrientation orientation,
const gfx::Size& size) {
switch (orientation) {
case PageOrientation::kOriginal:
case PageOrientation::kClockwise180:
return size;
case PageOrientation::kClockwise90:
case PageOrientation::kClockwise270:
gfx::Size transposed_size(size);
transposed_size.Transpose();
return transposed_size;
}
NOTREACHED();
}
} // namespace
gfx::PointF EventPositionToCanonicalPosition(const gfx::PointF& event_position,
PageOrientation orientation,
const gfx::Rect& page_content_rect,
float scale_factor) {
CHECK_GT(scale_factor, 0.0f);
CHECK(!page_content_rect.IsEmpty());
gfx::PointF page_position =
event_position - page_content_rect.OffsetFromOrigin();
switch (orientation) {
case PageOrientation::kOriginal:
// No further modification needed.
break;
case PageOrientation::kClockwise90:
page_position.SetPoint(page_position.y(),
page_content_rect.width() - page_position.x() - 1);
break;
case PageOrientation::kClockwise180:
page_position.SetPoint(
page_content_rect.width() - page_position.x() - 1,
page_content_rect.height() - page_position.y() - 1);
break;
case PageOrientation::kClockwise270:
page_position.SetPoint(page_content_rect.height() - page_position.y() - 1,
page_position.x());
break;
}
page_position.InvScale(scale_factor);
return page_position;
}
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::PointF p1 = CanonicalPositionToScreenPosition(
gfx::PointF(ink_rect->XMin(), ink_rect->YMin()), orientation,
page_content_rect, scale_factor);
gfx::PointF p2 = CanonicalPositionToScreenPosition(
gfx::PointF(ink_rect->XMax(), ink_rect->YMax()), orientation,
page_content_rect, scale_factor);
// 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