0

[PDF Ink Signatures] Fill in WriteStrokeToPage() implementation

Fill in WriteStrokeToPage() to write strokes to the PDFium page.  Update
tests to show the resulting PDFs include modified stroke images.

Bug: 335517469
Change-Id: I417c94a5d73a325e89068b691ee7d908a743eb89
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5894144
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1361367}
This commit is contained in:
Alan Screen
2024-09-27 21:07:50 +00:00
committed by Chromium LUCI CQ
parent b1f3a4fb40
commit ef6b7825fc
4 changed files with 144 additions and 3 deletions

@ -4,16 +4,134 @@
#include "pdf/pdfium/pdfium_ink_writer.h"
#include <optional>
#include "base/check.h"
#include "base/containers/span.h"
#include "printing/units.h"
#include "third_party/ink/src/ink/geometry/modeled_shape.h"
#include "third_party/ink/src/ink/strokes/stroke.h"
#include "third_party/pdfium/public/cpp/fpdf_scopers.h"
#include "third_party/pdfium/public/fpdf_edit.h"
using printing::kPixelsPerInch;
using printing::kPointsPerInch;
namespace chrome_pdf {
namespace {
// TODO(crbug.com/353904284): Choose real marker name that doesn't conflict
// with other writers.
constexpr char kInkAnnotationIdentifierKey[] = "ink-annot-id";
// Wrapper around an ink::ModeledShape to allow for iterating through all the
// triangles that make up its many meshes.
class TriangleIterator {
public:
explicit TriangleIterator(const ink::ModeledShape& shape)
: meshes_(shape.Meshes()) {}
std::optional<ink::Triangle> GetAndAdvance() {
if (mesh_index_ == meshes_.size()) {
return std::nullopt;
}
// Get the triangle to be returned.
ink::Triangle triangle = meshes_[mesh_index_].GetTriangle(triangle_index_);
// Advance to next triangle in preparation for the next call. When all
// triangles of a mesh have been consumed, advance to the next mesh. Meshes
// are guaranteed by ink::ModeledShape to never be empty.
++triangle_index_;
if (triangle_index_ == meshes_[mesh_index_].TriangleCount()) {
++mesh_index_;
triangle_index_ = 0;
}
return triangle;
}
private:
const base::span<const ink::Mesh> meshes_;
size_t mesh_index_ = 0;
uint32_t triangle_index_ = 0;
};
} // namespace
bool WriteStrokeToPage(FPDF_PAGE page, const ink::Stroke& stroke) {
if (!page) {
return false;
}
// TODO(crbug.com/335517469): Implement.
// A shape is made up of meshes, which in turn are made up of triangles.
// All of these get combined into a single PDF path. The first triangle is
// special because its first point is used to create the path.
TriangleIterator triangle_iter(stroke.GetShape());
std::optional<ink::Triangle> triangle = triangle_iter.GetAndAdvance();
if (!triangle.has_value()) {
return false; // No meshes with actual shape data.
}
ScopedFPDFPageObject path(
FPDFPageObj_CreateNewPath(triangle.value().p0.x, triangle.value().p0.y));
CHECK(path);
// Outline the edges of the first triangle.
bool line_to_result_p1 =
FPDFPath_LineTo(path.get(), triangle.value().p1.x, triangle.value().p1.y);
CHECK(line_to_result_p1);
bool line_to_result_p2 =
FPDFPath_LineTo(path.get(), triangle.value().p2.x, triangle.value().p2.y);
CHECK(line_to_result_p2);
// Work through the remaining triangles, which are part of the same path.
for (triangle = triangle_iter.GetAndAdvance(); triangle.has_value();
triangle = triangle_iter.GetAndAdvance()) {
bool move_to_result = FPDFPath_MoveTo(path.get(), triangle.value().p0.x,
triangle.value().p0.y);
CHECK(move_to_result);
line_to_result_p1 = FPDFPath_LineTo(path.get(), triangle.value().p1.x,
triangle.value().p1.y);
CHECK(line_to_result_p1);
line_to_result_p2 = FPDFPath_LineTo(path.get(), triangle.value().p2.x,
triangle.value().p2.y);
CHECK(line_to_result_p2);
}
// All triangles of the shape completed. Initialize the path's transform,
// draw mode, and color.
// The transform converts from canonical coordinates (which has a top-left
// origin and a different DPI), to PDF coordinates (which has a bottom-left
// origin).
constexpr float kScreenToPageScale =
static_cast<float>(kPointsPerInch) / kPixelsPerInch;
FS_MATRIX transform{kScreenToPageScale, 0, 0,
-kScreenToPageScale, 0, FPDF_GetPageHeightF(page)};
FPDFPageObj_TransformF(path.get(), &transform);
bool draw_mode_result =
FPDFPath_SetDrawMode(path.get(), FPDF_FILLMODE_WINDING,
/*stroke=*/false);
CHECK(draw_mode_result);
const auto rgba =
stroke.GetBrush().GetColor().AsUint8(ink::Color::Format::kGammaEncoded);
bool fill_color_result =
FPDFPageObj_SetFillColor(path.get(), rgba.r, rgba.g, rgba.b, rgba.a);
CHECK(fill_color_result);
// Path completed, close and mark it with an ID.
bool close_result = FPDFPath_Close(path.get());
CHECK(close_result);
bool add_mark_result =
FPDFPageObj_AddMark(path.get(), kInkAnnotationIdentifierKey);
CHECK(add_mark_result);
// Path is ready for the page.
FPDFPage_InsertObject(page, path.release());
return true;
}

@ -17,6 +17,8 @@ class Stroke;
namespace chrome_pdf {
// Writes `stroke` into `page`. Returns whether the operation succeeded or not.
// If the provided `stroke` is empty then it will return false and the `page`
// is left unchanged.
bool WriteStrokeToPage(FPDF_PAGE page, const ink::Stroke& stroke);
} // namespace chrome_pdf

@ -25,6 +25,7 @@
#include "third_party/ink/src/ink/strokes/input/stroke_input.h"
#include "third_party/ink/src/ink/strokes/input/stroke_input_batch.h"
#include "third_party/ink/src/ink/strokes/stroke.h"
#include "third_party/pdfium/public/fpdf_edit.h"
#include "third_party/pdfium/public/fpdfview.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImage.h"
@ -150,15 +151,33 @@ TEST_P(PDFiumInkWriterTest, Basic) {
ink::Stroke stroke(brush->GetInkBrush(), inputs.value());
ASSERT_TRUE(WriteStrokeToPage(page, stroke));
ASSERT_TRUE(FPDFPage_GenerateContent(page));
std::vector<uint8_t> saved_pdf_data = engine->GetSaveData();
ASSERT_TRUE(!saved_pdf_data.empty());
// TODO(crbug.com/335517469): The drawing should look different.
CheckPdfRendering(saved_pdf_data,
/*page_number=*/0, gfx::Size(200, 200),
GetReferenceFilePath("basic.png"));
}
TEST_P(PDFiumInkWriterTest, EmptyStroke) {
TestClient client;
std::unique_ptr<PDFiumEngine> engine =
InitializeEngine(&client, FILE_PATH_LITERAL("blank.pdf"));
ASSERT_TRUE(engine);
PDFiumPage* pdfium_page = engine->GetPage(0);
ASSERT_TRUE(pdfium_page);
FPDF_PAGE page = pdfium_page->GetPage();
ASSERT_TRUE(page);
auto brush =
std::make_unique<PdfInkBrush>(PdfInkBrush::Type::kPen, kBasicBrushParams);
ink::Stroke unused_stroke(brush->GetInkBrush());
ASSERT_FALSE(WriteStrokeToPage(page, unused_stroke));
}
TEST_P(PDFiumInkWriterTest, NoPage) {
auto brush =
std::make_unique<PdfInkBrush>(PdfInkBrush::Type::kPen, kBasicBrushParams);
@ -166,6 +185,8 @@ TEST_P(PDFiumInkWriterTest, NoPage) {
ASSERT_FALSE(WriteStrokeToPage(/*page=*/nullptr, unused_stroke));
}
INSTANTIATE_TEST_SUITE_P(All, PDFiumInkWriterTest, testing::Bool());
// Don't be concerned about any slight rendering differences in AGG vs. Skia,
// covering one of these is sufficient for checking how data is written out.
INSTANTIATE_TEST_SUITE_P(All, PDFiumInkWriterTest, testing::Values(false));
} // namespace chrome_pdf

Binary file not shown.

Before

(image error) Size: 100 B

After

(image error) Size: 540 B