[PDF Ink Signatures] Add stroke input device type metric
Add a metric to track the input device type of a drawn stroke. An input device type can be mouse, touch, or pen. To do so, track the tool type during the lifetime of an erase stroke. Draw strokes already track this. Bug: 380433757 Change-Id: Iff1b72460510179ccd6da50e4dc7557e56b9f264 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6150443 Reviewed-by: Alan Screen <awscreen@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Commit-Queue: Andy Phan <andyphan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1406908}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
139b1278f9
commit
09e49a1a48
pdf
pdf_ink_metrics_handler.ccpdf_ink_metrics_handler.hpdf_ink_module.ccpdf_ink_module.hpdf_ink_module_unittest.cc
tools/metrics/histograms/metadata/pdf
@ -105,9 +105,29 @@ void ReportStrokeTypeAndSize(StrokeMetricBrushType type,
|
||||
base::UmaHistogramEnumeration(size_metric, size);
|
||||
}
|
||||
|
||||
void ReportStrokeInputDeviceType(ink::StrokeInput::ToolType tool_type) {
|
||||
StrokeMetricInputDeviceType type;
|
||||
switch (tool_type) {
|
||||
case ink::StrokeInput::ToolType::kMouse:
|
||||
type = StrokeMetricInputDeviceType::kMouse;
|
||||
break;
|
||||
case ink::StrokeInput::ToolType::kTouch:
|
||||
type = StrokeMetricInputDeviceType::kTouch;
|
||||
break;
|
||||
case ink::StrokeInput::ToolType::kStylus:
|
||||
type = StrokeMetricInputDeviceType::kPen;
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
};
|
||||
base::UmaHistogramEnumeration("PDF.Ink2StrokeInputDeviceType", type);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ReportDrawStroke(PdfInkBrush::Type type, const ink::Brush& brush) {
|
||||
void ReportDrawStroke(PdfInkBrush::Type type,
|
||||
const ink::Brush& brush,
|
||||
ink::StrokeInput::ToolType tool_type) {
|
||||
bool is_pen = type == PdfInkBrush::Type::kPen;
|
||||
const base::fixed_flat_map<float, StrokeMetricBrushSize, 5>& sizes =
|
||||
is_pen ? kPenAndEraserSizes : kHighlighterSizes;
|
||||
@ -116,6 +136,7 @@ void ReportDrawStroke(PdfInkBrush::Type type, const ink::Brush& brush) {
|
||||
ReportStrokeTypeAndSize(is_pen ? StrokeMetricBrushType::kPen
|
||||
: StrokeMetricBrushType::kHighlighter,
|
||||
size_iter->second);
|
||||
ReportStrokeInputDeviceType(tool_type);
|
||||
|
||||
SkColor sk_color = GetSkColorFromInkBrush(brush);
|
||||
if (is_pen) {
|
||||
@ -130,10 +151,11 @@ void ReportDrawStroke(PdfInkBrush::Type type, const ink::Brush& brush) {
|
||||
}
|
||||
}
|
||||
|
||||
void ReportEraseStroke(float size) {
|
||||
void ReportEraseStroke(float size, ink::StrokeInput::ToolType tool_type) {
|
||||
auto iter = kPenAndEraserSizes.find(size);
|
||||
CHECK(iter != kPenAndEraserSizes.end());
|
||||
ReportStrokeTypeAndSize(StrokeMetricBrushType::kEraser, iter->second);
|
||||
ReportStrokeInputDeviceType(tool_type);
|
||||
}
|
||||
|
||||
} // namespace chrome_pdf
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "pdf/buildflags.h"
|
||||
#include "pdf/pdf_ink_brush.h"
|
||||
#include "third_party/ink/src/ink/strokes/input/stroke_input.h"
|
||||
|
||||
static_assert(BUILDFLAG(ENABLE_PDF_INK2), "ENABLE_PDF_INK2 not set to true");
|
||||
|
||||
@ -57,6 +58,18 @@ enum class StrokeMetricHighlighterColor {
|
||||
};
|
||||
// LINT.ThenChange(//tools/metrics/histograms/metadata/pdf/enums.xml:PDFInk2StrokeHighlighterColor)
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
//
|
||||
// LINT.IfChange(PDFInk2StrokeInputDeviceType)
|
||||
enum class StrokeMetricInputDeviceType {
|
||||
kMouse = 0,
|
||||
kTouch = 1,
|
||||
kPen = 2,
|
||||
kMaxValue = 2,
|
||||
};
|
||||
// LINT.ThenChange(//tools/metrics/histograms/metadata/pdf/enums.xml:PDFInk2StrokeInputDeviceType)
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
//
|
||||
@ -86,9 +99,11 @@ enum class StrokeMetricPenColor {
|
||||
};
|
||||
// LINT.ThenChange(//tools/metrics/histograms/metadata/pdf/enums.xml:PDFInk2StrokePenColor)
|
||||
|
||||
void ReportDrawStroke(PdfInkBrush::Type type, const ink::Brush& brush);
|
||||
void ReportDrawStroke(PdfInkBrush::Type type,
|
||||
const ink::Brush& brush,
|
||||
ink::StrokeInput::ToolType tool_type);
|
||||
|
||||
void ReportEraseStroke(float size);
|
||||
void ReportEraseStroke(float size, ink::StrokeInput::ToolType tool_type);
|
||||
|
||||
} // namespace chrome_pdf
|
||||
|
||||
|
@ -330,9 +330,10 @@ bool PdfInkModule::OnMouseDown(const blink::WebMouseEvent& event) {
|
||||
}
|
||||
|
||||
gfx::PointF position = normalized_event.PositionInWidget();
|
||||
return is_drawing_stroke() ? StartStroke(position, event.TimeStamp(),
|
||||
ink::StrokeInput::ToolType::kMouse)
|
||||
: StartEraseStroke(position);
|
||||
return is_drawing_stroke()
|
||||
? StartStroke(position, event.TimeStamp(),
|
||||
ink::StrokeInput::ToolType::kMouse)
|
||||
: StartEraseStroke(position, ink::StrokeInput::ToolType::kMouse);
|
||||
}
|
||||
|
||||
bool PdfInkModule::OnMouseUp(const blink::WebMouseEvent& event) {
|
||||
@ -343,9 +344,10 @@ bool PdfInkModule::OnMouseUp(const blink::WebMouseEvent& event) {
|
||||
}
|
||||
|
||||
gfx::PointF position = event.PositionInWidget();
|
||||
return is_drawing_stroke() ? FinishStroke(position, event.TimeStamp(),
|
||||
ink::StrokeInput::ToolType::kMouse)
|
||||
: FinishEraseStroke(position);
|
||||
return is_drawing_stroke()
|
||||
? FinishStroke(position, event.TimeStamp(),
|
||||
ink::StrokeInput::ToolType::kMouse)
|
||||
: FinishEraseStroke(position, ink::StrokeInput::ToolType::kMouse);
|
||||
}
|
||||
|
||||
bool PdfInkModule::OnMouseMove(const blink::WebMouseEvent& event) {
|
||||
@ -358,7 +360,8 @@ bool PdfInkModule::OnMouseMove(const blink::WebMouseEvent& event) {
|
||||
return is_drawing_stroke()
|
||||
? ContinueStroke(position, event.TimeStamp(),
|
||||
ink::StrokeInput::ToolType::kMouse)
|
||||
: ContinueEraseStroke(position);
|
||||
: ContinueEraseStroke(position,
|
||||
ink::StrokeInput::ToolType::kMouse);
|
||||
}
|
||||
|
||||
// Some other view consumed the input events sometime after the stroke was
|
||||
@ -402,7 +405,7 @@ bool PdfInkModule::OnTouchStart(const blink::WebTouchEvent& event) {
|
||||
ink::StrokeInput::ToolType tool_type = GetToolTypeFromTouchEvent(event);
|
||||
return is_drawing_stroke()
|
||||
? StartStroke(position, event.TimeStamp(), tool_type)
|
||||
: StartEraseStroke(position);
|
||||
: StartEraseStroke(position, tool_type);
|
||||
}
|
||||
|
||||
bool PdfInkModule::OnTouchEnd(const blink::WebTouchEvent& event) {
|
||||
@ -416,7 +419,7 @@ bool PdfInkModule::OnTouchEnd(const blink::WebTouchEvent& event) {
|
||||
ink::StrokeInput::ToolType tool_type = GetToolTypeFromTouchEvent(event);
|
||||
return is_drawing_stroke()
|
||||
? FinishStroke(position, event.TimeStamp(), tool_type)
|
||||
: FinishEraseStroke(position);
|
||||
: FinishEraseStroke(position, tool_type);
|
||||
}
|
||||
|
||||
bool PdfInkModule::OnTouchMove(const blink::WebTouchEvent& event) {
|
||||
@ -430,7 +433,7 @@ bool PdfInkModule::OnTouchMove(const blink::WebTouchEvent& event) {
|
||||
ink::StrokeInput::ToolType tool_type = GetToolTypeFromTouchEvent(event);
|
||||
return is_drawing_stroke()
|
||||
? ContinueStroke(position, event.TimeStamp(), tool_type)
|
||||
: ContinueEraseStroke(position);
|
||||
: ContinueEraseStroke(position, tool_type);
|
||||
}
|
||||
|
||||
bool PdfInkModule::StartStroke(const gfx::PointF& position,
|
||||
@ -594,7 +597,7 @@ bool PdfInkModule::FinishStroke(const gfx::PointF& position,
|
||||
bool undo_redo_success = undo_redo_model_.FinishDraw();
|
||||
CHECK(undo_redo_success);
|
||||
|
||||
ReportDrawStroke(state.brush_type, GetDrawingBrush().ink_brush());
|
||||
ReportDrawStroke(state.brush_type, GetDrawingBrush().ink_brush(), tool_type);
|
||||
|
||||
// Reset `state` now that the stroke operation is done.
|
||||
state.inputs.clear();
|
||||
@ -607,7 +610,8 @@ bool PdfInkModule::FinishStroke(const gfx::PointF& position,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkModule::StartEraseStroke(const gfx::PointF& position) {
|
||||
bool PdfInkModule::StartEraseStroke(const gfx::PointF& position,
|
||||
ink::StrokeInput::ToolType tool_type) {
|
||||
int page_index = client_->VisiblePageIndexFromPoint(position);
|
||||
if (page_index < 0) {
|
||||
// Do not erase when not on a page.
|
||||
@ -631,17 +635,21 @@ bool PdfInkModule::StartEraseStroke(const gfx::PointF& position) {
|
||||
// Remember this position to possibly compensate for missed input events.
|
||||
CHECK(!state.input_last_event_position.has_value());
|
||||
state.input_last_event_position = position;
|
||||
state.tool_type = tool_type;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkModule::ContinueEraseStroke(const gfx::PointF& position) {
|
||||
bool PdfInkModule::ContinueEraseStroke(const gfx::PointF& position,
|
||||
ink::StrokeInput::ToolType tool_type) {
|
||||
CHECK(is_erasing_stroke());
|
||||
EraserState& state = erasing_stroke_state();
|
||||
if (!state.erasing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.tool_type = tool_type;
|
||||
|
||||
int page_index = client_->VisiblePageIndexFromPoint(position);
|
||||
if (page_index < 0) {
|
||||
// Do nothing when the eraser tool is in use, but the event position is
|
||||
@ -662,10 +670,11 @@ bool PdfInkModule::ContinueEraseStroke(const gfx::PointF& position) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PdfInkModule::FinishEraseStroke(const gfx::PointF& position) {
|
||||
bool PdfInkModule::FinishEraseStroke(const gfx::PointF& position,
|
||||
ink::StrokeInput::ToolType tool_type) {
|
||||
// Process `position` as though it was the last point of movement first,
|
||||
// before moving on to various bookkeeping tasks.
|
||||
if (!ContinueEraseStroke(position)) {
|
||||
if (!ContinueEraseStroke(position, tool_type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -680,13 +689,14 @@ bool PdfInkModule::FinishEraseStroke(const gfx::PointF& position) {
|
||||
client_->UpdateThumbnail(page_index);
|
||||
}
|
||||
|
||||
ReportEraseStroke(eraser_size_);
|
||||
ReportEraseStroke(eraser_size_, tool_type);
|
||||
}
|
||||
|
||||
// Reset `state` now that the erase operation is done.
|
||||
state.erasing = false;
|
||||
state.page_indices_with_erasures.clear();
|
||||
state.input_last_event_position.reset();
|
||||
state.tool_type = ink::StrokeInput::ToolType::kUnknown;
|
||||
|
||||
MaybeSetDrawingBrushAndCursor();
|
||||
|
||||
@ -863,7 +873,8 @@ void PdfInkModule::HandleSetAnnotationBrushMessage(
|
||||
// An erasing stroke is in-progress. Finish that off before
|
||||
// transitioning, using the last known input.
|
||||
CHECK(state.input_last_event_position.has_value());
|
||||
FinishEraseStroke(state.input_last_event_position.value());
|
||||
FinishEraseStroke(state.input_last_event_position.value(),
|
||||
state.tool_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,6 +274,9 @@ class PdfInkModule {
|
||||
// The event position for the last input, similar to what is stored in
|
||||
// `DrawingStrokeState` for compensating for missed input events.
|
||||
std::optional<gfx::PointF> input_last_event_position;
|
||||
|
||||
// The type of tool used to generate the input.
|
||||
ink::StrokeInput::ToolType tool_type;
|
||||
};
|
||||
|
||||
// Drawing brush state changes that are pending the completion of an
|
||||
@ -304,9 +307,12 @@ class PdfInkModule {
|
||||
ink::StrokeInput::ToolType tool_type);
|
||||
|
||||
// Return values have the same semantics as On{Mouse,Touch}*() above.
|
||||
bool StartEraseStroke(const gfx::PointF& position);
|
||||
bool ContinueEraseStroke(const gfx::PointF& position);
|
||||
bool FinishEraseStroke(const gfx::PointF& position);
|
||||
bool StartEraseStroke(const gfx::PointF& position,
|
||||
ink::StrokeInput::ToolType tool_type);
|
||||
bool ContinueEraseStroke(const gfx::PointF& position,
|
||||
ink::StrokeInput::ToolType tool_type);
|
||||
bool FinishEraseStroke(const gfx::PointF& position,
|
||||
ink::StrokeInput::ToolType tool_type);
|
||||
|
||||
// Shared code for the Erase methods above. Returns if something got erased or
|
||||
// not.
|
||||
|
@ -2643,6 +2643,7 @@ class PdfInkModuleMetricsTest : public PdfInkModuleUndoRedoTest {
|
||||
static constexpr char kPenColorMetric[] = "PDF.Ink2StrokePenColor";
|
||||
static constexpr char kHighlighterColorMetric[] =
|
||||
"PDF.Ink2StrokeHighlighterColor";
|
||||
static constexpr char kInputDeviceMetric[] = "PDF.Ink2StrokeInputDeviceType";
|
||||
static constexpr char kPenSizeMetric[] = "PDF.Ink2StrokePenSize";
|
||||
static constexpr char kHighlighterSizeMetric[] =
|
||||
"PDF.Ink2StrokeHighlighterSize";
|
||||
@ -2658,6 +2659,8 @@ TEST_F(PdfInkModuleMetricsTest, StrokeUndoRedoDoesNotAffectMetrics) {
|
||||
RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
|
||||
|
||||
histograms.ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kMouse, 1);
|
||||
histograms.ExpectUniqueSample(kPenSizeMetric, StrokeMetricBrushSize::kMedium,
|
||||
1);
|
||||
histograms.ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
|
||||
@ -2669,6 +2672,8 @@ TEST_F(PdfInkModuleMetricsTest, StrokeUndoRedoDoesNotAffectMetrics) {
|
||||
|
||||
// The metrics should stay the same.
|
||||
histograms.ExpectUniqueSample(kTypeMetric, StrokeMetricBrushType::kPen, 1);
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kMouse, 1);
|
||||
histograms.ExpectUniqueSample(kPenSizeMetric, StrokeMetricBrushSize::kMedium,
|
||||
1);
|
||||
histograms.ExpectUniqueSample(kPenColorMetric, StrokeMetricPenColor::kBlack,
|
||||
@ -2895,4 +2900,98 @@ TEST_F(PdfInkModuleMetricsTest, StrokeBrushType) {
|
||||
histograms.ExpectTotalCount(kTypeMetric, 4);
|
||||
}
|
||||
|
||||
TEST_F(PdfInkModuleMetricsTest, StrokeInputDeviceMouse) {
|
||||
InitializeSimpleSinglePageBasicLayout();
|
||||
base::HistogramTester histograms;
|
||||
|
||||
RunStrokeCheckTest(/*annotation_mode_enabled=*/false);
|
||||
|
||||
histograms.ExpectTotalCount(kInputDeviceMetric, 0);
|
||||
|
||||
// Draw a stroke with a mouse.
|
||||
RunStrokeCheckTest(/*annotation_mode_enabled=*/true);
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kMouse, 1);
|
||||
|
||||
// Draw an eraser stroke with a mouse that erases the first stroke.
|
||||
SelectEraserToolOfSize(3.0f);
|
||||
ApplyStrokeWithMouseAtMouseDownPoint();
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kMouse, 2);
|
||||
|
||||
// Draw another eraser stroke with a mouse that erases nothing.
|
||||
ApplyStrokeWithMouseAtMouseDownPoint();
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kMouse, 2);
|
||||
}
|
||||
|
||||
TEST_F(PdfInkModuleMetricsTest, StrokeInputDeviceTouch) {
|
||||
InitializeSimpleSinglePageBasicLayout();
|
||||
base::HistogramTester histograms;
|
||||
|
||||
RunStrokeTouchCheckTest(/*annotation_mode_enabled=*/false);
|
||||
|
||||
histograms.ExpectTotalCount(kInputDeviceMetric, 0);
|
||||
|
||||
// Draw a stroke with touch.
|
||||
RunStrokeTouchCheckTest(/*annotation_mode_enabled=*/true);
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kTouch, 1);
|
||||
|
||||
// Draw an eraser stroke with touch that erases the first stroke.
|
||||
SelectEraserToolOfSize(3.0f);
|
||||
const std::vector<base::span<const gfx::PointF>> move_point{
|
||||
base::span_from_ref(kMouseDownPoint),
|
||||
};
|
||||
ApplyStrokeWithTouchAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
|
||||
base::span_from_ref(kMouseDownPoint));
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kTouch, 2);
|
||||
|
||||
// Draw another eraser stroke with touch that erases nothing.
|
||||
ApplyStrokeWithTouchAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
|
||||
base::span_from_ref(kMouseDownPoint));
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kTouch, 2);
|
||||
}
|
||||
|
||||
TEST_F(PdfInkModuleMetricsTest, StrokeInputDevicePen) {
|
||||
InitializeSimpleSinglePageBasicLayout();
|
||||
base::HistogramTester histograms;
|
||||
|
||||
RunStrokePenCheckTest(/*annotation_mode_enabled=*/false);
|
||||
|
||||
histograms.ExpectTotalCount(kInputDeviceMetric, 0);
|
||||
|
||||
// Draw a stroke with a pen.
|
||||
RunStrokePenCheckTest(/*annotation_mode_enabled=*/true);
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kPen, 1);
|
||||
|
||||
// Draw an eraser stroke with a pen that erases the first stroke.
|
||||
SelectEraserToolOfSize(3.0f);
|
||||
const std::vector<base::span<const gfx::PointF>> move_point{
|
||||
base::span_from_ref(kMouseDownPoint),
|
||||
};
|
||||
ApplyStrokeWithPenAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
|
||||
base::span_from_ref(kMouseDownPoint));
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kPen, 2);
|
||||
|
||||
// Draw another eraser stroke with a pen that erases nothing.
|
||||
ApplyStrokeWithPenAtPoints(base::span_from_ref(kMouseDownPoint), move_point,
|
||||
base::span_from_ref(kMouseDownPoint));
|
||||
|
||||
histograms.ExpectUniqueSample(kInputDeviceMetric,
|
||||
StrokeMetricInputDeviceType::kPen, 2);
|
||||
}
|
||||
|
||||
} // namespace chrome_pdf
|
||||
|
@ -183,6 +183,16 @@ chromium-metrics-reviews@google.com.
|
||||
|
||||
<!-- LINT.ThenChange(//pdf/pdf_ink_metrics_handler.h:PDFInk2StrokeHighlighterColor) -->
|
||||
|
||||
<!-- LINT.IfChange(PDFInk2StrokeInputDeviceType) -->
|
||||
|
||||
<enum name="PDFInk2StrokeInputDeviceType">
|
||||
<int value="0" label="Mouse"/>
|
||||
<int value="1" label="Touch"/>
|
||||
<int value="2" label="Pen"/>
|
||||
</enum>
|
||||
|
||||
<!-- LINT.ThenChange(//pdf/pdf_ink_metrics_handler.h:PDFInk2StrokeInputDeviceType) -->
|
||||
|
||||
<!-- LINT.IfChange(PDFInk2StrokePenColor) -->
|
||||
|
||||
<enum name="PDFInk2StrokePenColor">
|
||||
|
@ -102,6 +102,20 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="PDF.Ink2StrokeInputDeviceType"
|
||||
enum="PDFInk2StrokeInputDeviceType" expires_after="2025-12-01">
|
||||
<owner>andyphan@chromium.org</owner>
|
||||
<owner>thestig@chromium.org</owner>
|
||||
<summary>
|
||||
Tracks the input device type used for an Ink2 stroke modification in the PDF
|
||||
viewer. This includes new drawing strokes as well as erasing strokes. Erase
|
||||
strokes that erase pre-existing strokes in a PDF are also included. This is
|
||||
only recorded when drawing or erasing actions are performed by the user, but
|
||||
not if they occur as part of undo or redo operations. Eraser strokes that do
|
||||
not erase any other strokes are ignored.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="PDF.Ink2StrokePenColor" enum="PDFInk2StrokePenColor"
|
||||
expires_after="2025-12-01">
|
||||
<owner>andyphan@chromium.org</owner>
|
||||
|
Reference in New Issue
Block a user