@@ -146,9 +146,16 @@ public BitmapSource PageSource
146146 /// </summary>
147147 public bool TiltEnabled { get ; set ; } = true ;
148148
149+ // Custom stroke collection to prevent WPF's InkCanvas from clearing strokes
150+ // during visibility or EditingMode toggles.
151+ private readonly StrokeCollection _strokes = new StrokeCollection ( ) ;
152+
149153 public PdfPageControl ( )
150154 {
151155 InitializeComponent ( ) ;
156+
157+ // Assign stable stroke collection
158+ InkCanvas . Strokes = _strokes ;
152159
153160 _drawingAttributes = new DrawingAttributes
154161 {
@@ -164,6 +171,8 @@ public PdfPageControl()
164171 } ;
165172
166173 InkCanvas . DefaultDrawingAttributes = _drawingAttributes ;
174+ InkCanvas . UseCustomCursor = true ;
175+ InkCanvas . Cursor = Cursors . None ;
167176 InkCanvas . EditingMode = InkCanvasEditingMode . None ;
168177 InkCanvas . EditingModeInverted = InkCanvasEditingMode . None ; // Disable native inverted erasing so we can use custom logic
169178 InkCanvas . StrokeCollected += InkCanvas_StrokeCollected ;
@@ -178,6 +187,25 @@ public PdfPageControl()
178187 Unloaded += PdfPageControl_Unloaded ;
179188 }
180189
190+ /// <summary>
191+ /// Returns true if the given stylus device is a finger touch rather
192+ /// than a pen/stylus. Used to let finger events pass through to the
193+ /// WPF manipulation system for pan/zoom gestures.
194+ /// </summary>
195+ private static bool IsTouchFinger ( StylusDevice device )
196+ {
197+ if ( device == null ) return false ;
198+ var tablet = device . TabletDevice ;
199+ if ( tablet == null ) return false ;
200+ // Pen devices report as Stylus. Some pen-as-touch devices (e.g.
201+ // Huawei M-Pencil) report as Touch but have multiple stylus buttons.
202+ // Real fingers have TabletDeviceType.Touch with ≤1 button.
203+ if ( tablet . Type == System . Windows . Input . TabletDeviceType . Stylus )
204+ return false ;
205+ return tablet . Type == System . Windows . Input . TabletDeviceType . Touch
206+ && device . StylusButtons . Count <= 1 ;
207+ }
208+
181209 /// <summary>
182210 /// Set the shared <see cref="WindowsPenService"/> so this control can
183211 /// probe stylus devices and read pressure/tilt capabilities.
@@ -291,8 +319,9 @@ private void PdfPageControl_RequestBringIntoView(object sender, RequestBringInto
291319
292320 private void InkCanvas_MouseEnter ( object sender , MouseEventArgs e )
293321 {
294- if ( _currentMode == CustomInkInputProcessingMode . Erasing || _isStylusInverted )
322+ if ( _currentMode == CustomInkInputProcessingMode . Erasing || _isStylusInverted || _currentMode == CustomInkInputProcessingMode . Inking )
295323 {
324+ UpdateBrushIndicatorStyle ( ) ;
296325 EraserIndicator . Visibility = Visibility . Visible ;
297326 Cursor = Cursors . None ;
298327 UpdateEraserIndicatorPosition ( e . GetPosition ( PageGrid ) ) ;
@@ -344,6 +373,12 @@ private void InkCanvas_StylusInAirMove(object sender, StylusEventArgs e)
344373
345374 if ( inverted )
346375 {
376+ UpdateBrushIndicatorStyle ( ) ;
377+ UpdateEraserIndicatorPosition ( e . GetPosition ( PageGrid ) ) ;
378+ }
379+ else if ( _currentMode == CustomInkInputProcessingMode . Inking )
380+ {
381+ UpdateBrushIndicatorStyle ( ) ;
347382 UpdateEraserIndicatorPosition ( e . GetPosition ( PageGrid ) ) ;
348383 }
349384 }
@@ -399,6 +434,11 @@ private void PdfTextSelectionCanvas_StylusDown(object sender, StylusDownEventArg
399434 if ( ! _isPdfTextSelectionEnabled )
400435 return ;
401436
437+ // Let finger touches pass through to WPF manipulation (pan/zoom).
438+ // Only pen/stylus devices should trigger PDF text selection.
439+ if ( IsTouchFinger ( e . StylusDevice ) )
440+ return ;
441+
402442 PdfTextSelectionCanvas . CaptureStylus ( ) ;
403443 PdfTextSelectionPointerPressed ? . Invoke ( this , new PdfTextSelectionPointerEventArgs ( e . GetPosition ( PageGrid ) , MouseButtonState . Pressed ) ) ;
404444 e . Handled = true ;
@@ -409,6 +449,9 @@ private void PdfTextSelectionCanvas_StylusMove(object sender, StylusEventArgs e)
409449 if ( ! _isPdfTextSelectionEnabled )
410450 return ;
411451
452+ if ( IsTouchFinger ( e . StylusDevice ) )
453+ return ;
454+
412455 PdfTextSelectionPointerMoved ? . Invoke ( this , new PdfTextSelectionPointerEventArgs ( e . GetPosition ( PageGrid ) , MouseButtonState . Pressed ) ) ;
413456 if ( PdfTextSelectionCanvas . IsStylusCaptured )
414457 e . Handled = true ;
@@ -419,6 +462,9 @@ private void PdfTextSelectionCanvas_StylusUp(object sender, StylusEventArgs e)
419462 if ( ! _isPdfTextSelectionEnabled )
420463 return ;
421464
465+ if ( IsTouchFinger ( e . StylusDevice ) )
466+ return ;
467+
422468 PdfTextSelectionPointerReleased ? . Invoke ( this , new PdfTextSelectionPointerEventArgs ( e . GetPosition ( PageGrid ) , MouseButtonState . Released ) ) ;
423469 if ( PdfTextSelectionCanvas . IsStylusCaptured )
424470 PdfTextSelectionCanvas . ReleaseStylusCapture ( ) ;
@@ -527,6 +573,7 @@ private void InkCanvas_StylusDown(object sender, StylusDownEventArgs e)
527573 InkCanvas . EditingMode = InkCanvasEditingMode . None ;
528574
529575 // Show eraser indicator at contact point
576+ UpdateBrushIndicatorStyle ( ) ;
530577 EraserIndicator . Visibility = Visibility . Visible ;
531578 InkCanvas . Cursor = Cursors . None ;
532579 UpdateEraserIndicatorPosition ( e . GetPosition ( PageGrid ) ) ;
@@ -575,24 +622,46 @@ private void InkCanvas_StylusUp(object sender, StylusEventArgs e)
575622
576623 private void InkCanvas_MouseMove ( object sender , MouseEventArgs e )
577624 {
578- if ( _currentMode == CustomInkInputProcessingMode . Erasing )
625+ if ( _currentMode == CustomInkInputProcessingMode . Erasing || _currentMode == CustomInkInputProcessingMode . Inking )
579626 {
627+ UpdateBrushIndicatorStyle ( ) ;
580628 var point = e . GetPosition ( PageGrid ) ;
581629 UpdateEraserIndicatorPosition ( point ) ;
582630
583- if ( e . LeftButton == MouseButtonState . Pressed )
631+ if ( _currentMode == CustomInkInputProcessingMode . Erasing && e . LeftButton == MouseButtonState . Pressed )
584632 {
585633 EraseStrokesAtPoints ( new StylusPointCollection { new StylusPoint ( e . GetPosition ( InkCanvas ) . X , e . GetPosition ( InkCanvas ) . Y ) } ) ;
586634 }
587635 }
588636 }
589637
638+ private void UpdateBrushIndicatorStyle ( )
639+ {
640+ if ( _currentMode == CustomInkInputProcessingMode . Erasing || _isStylusInverted )
641+ {
642+ EraserIndicator . Width = _eraserSize ;
643+ EraserIndicator . Height = _eraserSize ;
644+ EraserIndicator . Stroke = new SolidColorBrush ( Color . FromArgb ( 255 , 0 , 120 , 212 ) ) ;
645+ EraserIndicator . Fill = new SolidColorBrush ( Color . FromArgb ( 16 , 0 , 120 , 212 ) ) ;
646+ }
647+ else if ( _currentMode == CustomInkInputProcessingMode . Inking )
648+ {
649+ double size = _drawingAttributes . Width ;
650+ EraserIndicator . Width = Math . Max ( 4 , size ) ;
651+ EraserIndicator . Height = Math . Max ( 4 , size ) ;
652+
653+ Color c = _drawingAttributes . Color ;
654+ EraserIndicator . Stroke = new SolidColorBrush ( Color . FromArgb ( 200 , c . R , c . G , c . B ) ) ;
655+ EraserIndicator . Fill = _drawingAttributes . IsHighlighter ? new SolidColorBrush ( Color . FromArgb ( 50 , c . R , c . G , c . B ) ) : Brushes . Transparent ;
656+ }
657+ }
658+
590659 private void UpdateEraserIndicatorPosition ( Point point )
591660 {
592- EraserIndicator . Width = _eraserSize ;
593- EraserIndicator . Height = _eraserSize ;
594- Canvas . SetLeft ( EraserIndicator , point . X - _eraserSize / 2 ) ;
595- Canvas . SetTop ( EraserIndicator , point . Y - _eraserSize / 2 ) ;
661+ double w = EraserIndicator . Width ;
662+ double h = EraserIndicator . Height ;
663+ Canvas . SetLeft ( EraserIndicator , point . X - w / 2 ) ;
664+ Canvas . SetTop ( EraserIndicator , point . Y - h / 2 ) ;
596665 EraserIndicator . Visibility = Visibility . Visible ;
597666 }
598667
@@ -717,7 +786,9 @@ private static void OnPageSourceChanged(DependencyObject d, DependencyPropertyCh
717786
718787 public void SetMode ( bool isTextMode )
719788 {
720- TextOverlayCanvas . IsHitTestVisible = isTextMode ;
789+ // Always keep TextOverlayCanvas hit-testable so textboxes can be clicked in any tool mode.
790+ // Its background is transparent, so non-textbox clicks fall through to InkCanvas below.
791+ TextOverlayCanvas . IsHitTestVisible = true ;
721792 }
722793
723794 public void SetPdfTextSelectionEnabled ( bool enabled )
@@ -797,7 +868,7 @@ public void SetInputMode(CustomInkInputProcessingMode mode)
797868 if ( ! _isPdfTextSelectionEnabled )
798869 InkCanvas . IsHitTestVisible = true ;
799870 InkCanvas . EditingMode = InkCanvasEditingMode . Ink ;
800- EraserIndicator . Visibility = Visibility . Collapsed ;
871+ // Keep indicator visible since we're updating it on mouse/stylus move now
801872 InkCanvas . Cursor = Cursors . Cross ;
802873 Cursor = Cursors . Arrow ;
803874 break ;
@@ -1488,6 +1559,58 @@ private void SelectionOverlayCanvas_StylusUp(object sender, StylusEventArgs e)
14881559 SelectionOverlayCanvas_MouseLeftButtonUpCore ( ) ;
14891560 e . Handled = true ;
14901561 }
1562+
1563+ private readonly List < HighlightAnnotation > _highlights = new ( ) ;
1564+
1565+ public IReadOnlyList < HighlightAnnotation > GetHighlights ( ) => _highlights ;
1566+
1567+ public void AddHighlightAnnotation ( IReadOnlyList < Rect > rects , Color color )
1568+ {
1569+ var highlight = new HighlightAnnotation
1570+ {
1571+ R = color . R ,
1572+ G = color . G ,
1573+ B = color . B ,
1574+ A = 120 // Semi-transparent overlay
1575+ } ;
1576+
1577+ foreach ( var r in rects )
1578+ {
1579+ highlight . Rects . Add ( new double [ ] { r . X , r . Y , r . Width , r . Height } ) ;
1580+ }
1581+
1582+ _highlights . Add ( highlight ) ;
1583+ RenderHighlightVisual ( highlight ) ;
1584+ }
1585+
1586+ public void AddHighlight ( HighlightAnnotation highlight )
1587+ {
1588+ _highlights . Add ( highlight ) ;
1589+ RenderHighlightVisual ( highlight ) ;
1590+ }
1591+
1592+ private void RenderHighlightVisual ( HighlightAnnotation highlight )
1593+ {
1594+ var color = Color . FromArgb ( highlight . A , highlight . R , highlight . G , highlight . B ) ;
1595+ var brush = new SolidColorBrush ( color ) ;
1596+
1597+ foreach ( var rectInfo in highlight . Rects )
1598+ {
1599+ if ( rectInfo . Length >= 4 )
1600+ {
1601+ var rect = new System . Windows . Shapes . Rectangle
1602+ {
1603+ Width = rectInfo [ 2 ] ,
1604+ Height = rectInfo [ 3 ] ,
1605+ Fill = brush ,
1606+ IsHitTestVisible = false
1607+ } ;
1608+ Canvas . SetLeft ( rect , rectInfo [ 0 ] ) ;
1609+ Canvas . SetTop ( rect , rectInfo [ 1 ] ) ;
1610+ HighlightsCanvas . Children . Add ( rect ) ;
1611+ }
1612+ }
1613+ }
14911614 }
14921615}
14931616
0 commit comments