• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The PDFium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 
7 #include "xfa/fwl/cfwl_edit.h"
8 
9 #include <algorithm>
10 #include <memory>
11 #include <utility>
12 #include <vector>
13 
14 #include "build/build_config.h"
15 #include "core/fxcrt/check.h"
16 #include "core/fxcrt/numerics/safe_conversions.h"
17 #include "core/fxge/cfx_renderdevice.h"
18 #include "core/fxge/text_char_pos.h"
19 #include "v8/include/cppgc/visitor.h"
20 #include "xfa/fde/cfde_textout.h"
21 #include "xfa/fgas/font/cfgas_gefont.h"
22 #include "xfa/fgas/graphics/cfgas_gegraphics.h"
23 #include "xfa/fgas/graphics/cfgas_gepath.h"
24 #include "xfa/fwl/cfwl_app.h"
25 #include "xfa/fwl/cfwl_caret.h"
26 #include "xfa/fwl/cfwl_event.h"
27 #include "xfa/fwl/cfwl_eventtextwillchange.h"
28 #include "xfa/fwl/cfwl_eventvalidate.h"
29 #include "xfa/fwl/cfwl_messagekey.h"
30 #include "xfa/fwl/cfwl_messagemouse.h"
31 #include "xfa/fwl/cfwl_themebackground.h"
32 #include "xfa/fwl/cfwl_themepart.h"
33 #include "xfa/fwl/cfwl_widgetmgr.h"
34 #include "xfa/fwl/fwl_widgetdef.h"
35 #include "xfa/fwl/ifwl_themeprovider.h"
36 #include "xfa/fwl/theme/cfwl_utils.h"
37 
38 namespace pdfium {
39 
40 namespace {
41 
42 constexpr int kEditMargin = 3;
43 
44 #if BUILDFLAG(IS_APPLE)
45 constexpr XFA_FWL_KeyFlag kEditingModifier = XFA_FWL_KeyFlag::kCommand;
46 #else
47 constexpr XFA_FWL_KeyFlag kEditingModifier = XFA_FWL_KeyFlag::kCtrl;
48 #endif
49 
50 }  // namespace
51 
CFWL_Edit(CFWL_App * app,const Properties & properties,CFWL_Widget * pOuter)52 CFWL_Edit::CFWL_Edit(CFWL_App* app,
53                      const Properties& properties,
54                      CFWL_Widget* pOuter)
55     : CFWL_Widget(app, properties, pOuter),
56       m_pEditEngine(std::make_unique<CFDE_TextEditEngine>()) {
57   m_pEditEngine->SetDelegate(this);
58 }
59 
60 CFWL_Edit::~CFWL_Edit() = default;
61 
PreFinalize()62 void CFWL_Edit::PreFinalize() {
63   m_pEditEngine->SetDelegate(nullptr);
64   if (m_Properties.m_dwStates & FWL_STATE_WGT_Focused)
65     HideCaret(nullptr);
66   CFWL_Widget::PreFinalize();
67 }
68 
Trace(cppgc::Visitor * visitor) const69 void CFWL_Edit::Trace(cppgc::Visitor* visitor) const {
70   CFWL_Widget::Trace(visitor);
71   visitor->Trace(m_pVertScrollBar);
72   visitor->Trace(m_pCaret);
73 }
74 
GetClassID() const75 FWL_Type CFWL_Edit::GetClassID() const {
76   return FWL_Type::Edit;
77 }
78 
GetWidgetRect()79 CFX_RectF CFWL_Edit::GetWidgetRect() {
80   CFX_RectF rect = m_WidgetRect;
81   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
82     float scrollbarWidth = GetThemeProvider()->GetScrollBarWidth();
83     if (IsShowVertScrollBar()) {
84       rect.width += scrollbarWidth;
85       rect.width += kEditMargin;
86     }
87   }
88   return rect;
89 }
90 
GetAutosizedWidgetRect()91 CFX_RectF CFWL_Edit::GetAutosizedWidgetRect() {
92   CFX_RectF rect;
93   if (m_pEditEngine->GetLength() > 0) {
94     CFX_SizeF size = CalcTextSize(
95         m_pEditEngine->GetText(),
96         !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine));
97     rect = CFX_RectF(0, 0, size);
98   }
99   InflateWidgetRect(rect);
100   return rect;
101 }
102 
SetStates(uint32_t dwStates)103 void CFWL_Edit::SetStates(uint32_t dwStates) {
104   if ((m_Properties.m_dwStates & FWL_STATE_WGT_Invisible) ||
105       (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)) {
106     HideCaret(nullptr);
107   }
108   CFWL_Widget::SetStates(dwStates);
109 }
110 
Update()111 void CFWL_Edit::Update() {
112   if (IsLocked())
113     return;
114 
115   Layout();
116   if (m_ClientRect.IsEmpty())
117     return;
118 
119   UpdateEditEngine();
120   UpdateVAlignment();
121   UpdateScroll();
122   InitCaret();
123 }
124 
HitTest(const CFX_PointF & point)125 FWL_WidgetHit CFWL_Edit::HitTest(const CFX_PointF& point) {
126   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
127     if (IsShowVertScrollBar()) {
128       if (m_pVertScrollBar->GetWidgetRect().Contains(point))
129         return FWL_WidgetHit::VScrollBar;
130     }
131   }
132   if (m_ClientRect.Contains(point))
133     return FWL_WidgetHit::Edit;
134   return FWL_WidgetHit::Unknown;
135 }
136 
DrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)137 void CFWL_Edit::DrawWidget(CFGAS_GEGraphics* pGraphics,
138                            const CFX_Matrix& matrix) {
139   if (!pGraphics)
140     return;
141 
142   if (m_ClientRect.IsEmpty())
143     return;
144 
145   DrawContent(pGraphics, matrix);
146   if (HasBorder())
147     DrawBorder(pGraphics, CFWL_ThemePart::Part::kBorder, matrix);
148 }
149 
SetText(const WideString & wsText)150 void CFWL_Edit::SetText(const WideString& wsText) {
151   m_pEditEngine->Clear();
152   m_pEditEngine->Insert(0, wsText,
153                         CFDE_TextEditEngine::RecordOperation::kInsertRecord);
154 }
155 
SetTextSkipNotify(const WideString & wsText)156 void CFWL_Edit::SetTextSkipNotify(const WideString& wsText) {
157   m_pEditEngine->Clear();
158   m_pEditEngine->Insert(0, wsText,
159                         CFDE_TextEditEngine::RecordOperation::kSkipNotify);
160 }
161 
GetTextLength() const162 size_t CFWL_Edit::GetTextLength() const {
163   return m_pEditEngine->GetLength();
164 }
165 
GetText() const166 WideString CFWL_Edit::GetText() const {
167   return m_pEditEngine->GetText();
168 }
169 
ClearText()170 void CFWL_Edit::ClearText() {
171   m_pEditEngine->Clear();
172 }
173 
SelectAll()174 void CFWL_Edit::SelectAll() {
175   m_pEditEngine->SelectAll();
176 }
177 
HasSelection() const178 bool CFWL_Edit::HasSelection() const {
179   return m_pEditEngine->HasSelection();
180 }
181 
GetSelection() const182 std::pair<size_t, size_t> CFWL_Edit::GetSelection() const {
183   return m_pEditEngine->GetSelection();
184 }
185 
ClearSelection()186 void CFWL_Edit::ClearSelection() {
187   return m_pEditEngine->ClearSelection();
188 }
189 
GetLimit() const190 int32_t CFWL_Edit::GetLimit() const {
191   return m_nLimit;
192 }
193 
SetLimit(int32_t nLimit)194 void CFWL_Edit::SetLimit(int32_t nLimit) {
195   m_nLimit = nLimit;
196 
197   if (m_nLimit > 0) {
198     m_pEditEngine->SetHasCharacterLimit(true);
199     m_pEditEngine->SetCharacterLimit(nLimit);
200   } else {
201     m_pEditEngine->SetHasCharacterLimit(false);
202   }
203 }
204 
SetAliasChar(wchar_t wAlias)205 void CFWL_Edit::SetAliasChar(wchar_t wAlias) {
206   m_pEditEngine->SetAliasChar(wAlias);
207 }
208 
Copy()209 std::optional<WideString> CFWL_Edit::Copy() {
210   if (!m_pEditEngine->HasSelection())
211     return std::nullopt;
212 
213   return m_pEditEngine->GetSelectedText();
214 }
215 
Cut()216 std::optional<WideString> CFWL_Edit::Cut() {
217   if (!m_pEditEngine->HasSelection())
218     return std::nullopt;
219 
220   WideString cut_text = m_pEditEngine->DeleteSelectedText();
221   UpdateCaret();
222   return cut_text;
223 }
224 
Paste(const WideString & wsPaste)225 bool CFWL_Edit::Paste(const WideString& wsPaste) {
226   if (m_pEditEngine->HasSelection())
227     m_pEditEngine->ReplaceSelectedText(wsPaste);
228   else
229     m_pEditEngine->Insert(m_CursorPosition, wsPaste);
230 
231   return true;
232 }
233 
Undo()234 bool CFWL_Edit::Undo() {
235   return CanUndo() && m_pEditEngine->Undo();
236 }
237 
Redo()238 bool CFWL_Edit::Redo() {
239   return CanRedo() && m_pEditEngine->Redo();
240 }
241 
CanUndo()242 bool CFWL_Edit::CanUndo() {
243   return m_pEditEngine->CanUndo();
244 }
245 
CanRedo()246 bool CFWL_Edit::CanRedo() {
247   return m_pEditEngine->CanRedo();
248 }
249 
NotifyTextFull()250 void CFWL_Edit::NotifyTextFull() {
251   CFWL_Event evt(CFWL_Event::Type::TextFull, this);
252   DispatchEvent(&evt);
253 }
254 
OnCaretChanged()255 void CFWL_Edit::OnCaretChanged() {
256   if (m_EngineRect.IsEmpty())
257     return;
258   if ((m_Properties.m_dwStates & FWL_STATE_WGT_Focused) == 0)
259     return;
260 
261   bool bRepaintContent = UpdateOffset();
262   UpdateCaret();
263   CFX_RectF rtInvalid;
264   bool bRepaintScroll = false;
265   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine) {
266     CFWL_ScrollBar* pScroll = UpdateScroll();
267     if (pScroll) {
268       rtInvalid = pScroll->GetWidgetRect();
269       bRepaintScroll = true;
270     }
271   }
272   if (bRepaintContent || bRepaintScroll) {
273     if (bRepaintContent)
274       rtInvalid.Union(m_EngineRect);
275     RepaintRect(rtInvalid);
276   }
277 }
278 
OnTextWillChange(CFDE_TextEditEngine::TextChange * change)279 void CFWL_Edit::OnTextWillChange(CFDE_TextEditEngine::TextChange* change) {
280   CFWL_EventTextWillChange event(this, change->text, change->previous_text,
281                                  change->selection_start,
282                                  change->selection_end);
283   DispatchEvent(&event);
284 
285   change->text = event.GetChangeText();
286   change->selection_start = event.GetSelectionStart();
287   change->selection_end = event.GetSelectionEnd();
288   change->cancelled = event.GetCancelled();
289 }
290 
OnTextChanged()291 void CFWL_Edit::OnTextChanged() {
292   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_VAlignMask)
293     UpdateVAlignment();
294 
295   LayoutScrollBar();
296   RepaintRect(GetClientRect());
297 }
298 
OnSelChanged()299 void CFWL_Edit::OnSelChanged() {
300   RepaintRect(GetClientRect());
301 }
302 
OnValidate(const WideString & wsText)303 bool CFWL_Edit::OnValidate(const WideString& wsText) {
304   CFWL_EventValidate event(this, wsText);
305   DispatchEvent(&event);
306   return event.GetValidate();
307 }
308 
SetScrollOffset(float fScrollOffset)309 void CFWL_Edit::SetScrollOffset(float fScrollOffset) {
310   m_fScrollOffsetY = fScrollOffset;
311 }
312 
DrawContent(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)313 void CFWL_Edit::DrawContent(CFGAS_GEGraphics* pGraphics,
314                             const CFX_Matrix& mtMatrix) {
315   DrawContentNonComb(pGraphics, mtMatrix);
316   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_CombText) {
317     CFGAS_GEGraphics::StateRestorer restorer(pGraphics);
318     CFGAS_GEPath path;
319     const int32_t iLimit = m_nLimit > 0 ? m_nLimit : 1;
320     const float fStep = m_EngineRect.width / iLimit;
321     float fLeft = m_EngineRect.left + 1;
322     for (int32_t i = 1; i < iLimit; i++) {
323       fLeft += fStep;
324       path.AddLine(CFX_PointF(fLeft, m_ClientRect.top),
325                    CFX_PointF(fLeft, m_ClientRect.bottom()));
326     }
327     CFWL_ThemeBackground param(CFWL_ThemePart::Part::kCombTextLine, this,
328                                pGraphics);
329     param.m_matrix = mtMatrix;
330     param.SetPath(&path);
331     GetThemeProvider()->DrawBackground(param);
332   }
333 }
334 
DrawContentNonComb(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & mtMatrix)335 void CFWL_Edit::DrawContentNonComb(CFGAS_GEGraphics* pGraphics,
336                                    const CFX_Matrix& mtMatrix) {
337   CFGAS_GEGraphics::StateRestorer restorer(pGraphics);
338   CFX_RectF rtClip = m_EngineRect;
339   float fOffSetX = m_EngineRect.left - m_fScrollOffsetX;
340   float fOffSetY = m_EngineRect.top - m_fScrollOffsetY + m_fVAlignOffset;
341   CFX_Matrix mt(1, 0, 0, 1, fOffSetX, fOffSetY);
342   rtClip = mtMatrix.TransformRect(rtClip);
343   mt.Concat(mtMatrix);
344 
345   bool bShowSel = !!(m_Properties.m_dwStates & FWL_STATE_WGT_Focused);
346   if (bShowSel && m_pEditEngine->HasSelection()) {
347     auto [sel_start, count] = m_pEditEngine->GetSelection();
348     std::vector<CFX_RectF> rects = m_pEditEngine->GetCharacterRectsInRange(
349         checked_cast<int32_t>(sel_start), checked_cast<int32_t>(count));
350 
351     CFGAS_GEPath path;
352     for (auto& rect : rects) {
353       rect.left += fOffSetX;
354       rect.top += fOffSetY;
355       path.AddRectangle(rect.left, rect.top, rect.width, rect.height);
356     }
357     pGraphics->SetClipRect(rtClip);
358 
359     CFWL_ThemeBackground param(CFWL_ThemePart::Part::kBackground, this,
360                                pGraphics);
361     param.m_matrix = mtMatrix;
362     param.SetPath(&path);
363     GetThemeProvider()->DrawBackground(param);
364   }
365 
366   CFX_RenderDevice* pRenderDev = pGraphics->GetRenderDevice();
367   RenderText(pRenderDev, rtClip, mt);
368 }
369 
RenderText(CFX_RenderDevice * pRenderDev,const CFX_RectF & clipRect,const CFX_Matrix & mt)370 void CFWL_Edit::RenderText(CFX_RenderDevice* pRenderDev,
371                            const CFX_RectF& clipRect,
372                            const CFX_Matrix& mt) {
373   DCHECK(pRenderDev);
374 
375   RetainPtr<CFGAS_GEFont> font = m_pEditEngine->GetFont();
376   if (!font)
377     return;
378 
379   pRenderDev->SetClip_Rect(clipRect.GetOuterRect());
380 
381   CFX_RectF rtDocClip = clipRect;
382   if (rtDocClip.IsEmpty()) {
383     rtDocClip.left = 0;
384     rtDocClip.top = 0;
385     rtDocClip.width = static_cast<float>(pRenderDev->GetWidth());
386     rtDocClip.height = static_cast<float>(pRenderDev->GetHeight());
387   }
388   rtDocClip = mt.GetInverse().TransformRect(rtDocClip);
389 
390   for (const FDE_TEXTEDITPIECE& info : m_pEditEngine->GetTextPieces()) {
391     // If this character is outside the clip, skip it.
392     if (!rtDocClip.IntersectWith(info.rtPiece))
393       continue;
394 
395     std::vector<TextCharPos> char_pos = m_pEditEngine->GetDisplayPos(info);
396     if (char_pos.empty())
397       continue;
398 
399     CFDE_TextOut::DrawString(pRenderDev, m_pEditEngine->GetFontColor(), font,
400                              char_pos, m_pEditEngine->GetFontSize(), mt);
401   }
402 }
403 
UpdateEditEngine()404 void CFWL_Edit::UpdateEditEngine() {
405   UpdateEditParams();
406   UpdateEditLayout();
407 }
408 
UpdateEditParams()409 void CFWL_Edit::UpdateEditParams() {
410   m_pEditEngine->SetAvailableWidth(m_EngineRect.width);
411   m_pEditEngine->SetCombText(
412       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_CombText));
413 
414   m_pEditEngine->EnableValidation(
415       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_Validate));
416   m_pEditEngine->EnablePasswordMode(
417       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_Password));
418 
419   uint32_t alignment = 0;
420   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_HAlignMask) {
421     case FWL_STYLEEXT_EDT_HNear: {
422       alignment |= CFX_TxtLineAlignment_Left;
423       break;
424     }
425     case FWL_STYLEEXT_EDT_HCenter: {
426       alignment |= CFX_TxtLineAlignment_Center;
427       break;
428     }
429     case FWL_STYLEEXT_EDT_HFar: {
430       alignment |= CFX_TxtLineAlignment_Right;
431       break;
432     }
433     default:
434       break;
435   }
436   switch (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_HAlignModeMask) {
437     case FWL_STYLEEXT_EDT_Justified: {
438       alignment |= CFX_TxtLineAlignment_Justified;
439       break;
440     }
441     default:
442       break;
443   }
444   m_pEditEngine->SetAlignment(alignment);
445 
446   bool auto_hscroll =
447       !!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_AutoHScroll);
448   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine) {
449     m_pEditEngine->EnableMultiLine(true);
450     m_pEditEngine->EnableLineWrap(!auto_hscroll);
451     m_pEditEngine->LimitVerticalScroll(
452         (m_Properties.m_dwStyles & FWL_STYLE_WGT_VScroll) == 0 &&
453         (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_AutoVScroll) == 0);
454   } else {
455     m_pEditEngine->EnableMultiLine(false);
456     m_pEditEngine->EnableLineWrap(false);
457     m_pEditEngine->LimitVerticalScroll(false);
458   }
459   m_pEditEngine->LimitHorizontalScroll(!auto_hscroll);
460 
461   IFWL_ThemeProvider* theme = GetThemeProvider();
462   CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
463   m_fFontSize = theme->GetFontSize(part);
464 
465   RetainPtr<CFGAS_GEFont> pFont = theme->GetFont(part);
466   if (!pFont)
467     return;
468 
469   m_pEditEngine->SetFont(pFont);
470   m_pEditEngine->SetFontColor(theme->GetTextColor(part));
471   m_pEditEngine->SetFontSize(m_fFontSize);
472   m_pEditEngine->SetLineSpace(theme->GetLineHeight(part));
473   m_pEditEngine->SetTabWidth(m_fFontSize);
474   m_pEditEngine->SetVisibleLineCount(m_EngineRect.height /
475                                      theme->GetLineHeight(part));
476 }
477 
UpdateEditLayout()478 void CFWL_Edit::UpdateEditLayout() {
479   m_pEditEngine->Layout();
480 }
481 
UpdateOffset()482 bool CFWL_Edit::UpdateOffset() {
483   CFX_RectF rtCaret = m_CaretRect;
484 
485   float fOffSetX = m_EngineRect.left - m_fScrollOffsetX;
486   float fOffSetY = m_EngineRect.top - m_fScrollOffsetY + m_fVAlignOffset;
487   rtCaret.Offset(fOffSetX, fOffSetY);
488 
489   const CFX_RectF& edit_bounds = m_EngineRect;
490   if (edit_bounds.Contains(rtCaret)) {
491     CFX_RectF contents_bounds = m_pEditEngine->GetContentsBoundingBox();
492     contents_bounds.Offset(fOffSetX, fOffSetY);
493     if (contents_bounds.right() < edit_bounds.right() && m_fScrollOffsetX > 0) {
494       m_fScrollOffsetX += contents_bounds.right() - edit_bounds.right();
495       m_fScrollOffsetX = std::max(m_fScrollOffsetX, 0.0f);
496     }
497     if (contents_bounds.bottom() < edit_bounds.bottom() &&
498         m_fScrollOffsetY > 0) {
499       m_fScrollOffsetY += contents_bounds.bottom() - edit_bounds.bottom();
500       m_fScrollOffsetY = std::max(m_fScrollOffsetY, 0.0f);
501     }
502     return false;
503   }
504 
505   float offsetX = 0.0;
506   float offsetY = 0.0;
507   if (rtCaret.left < edit_bounds.left)
508     offsetX = rtCaret.left - edit_bounds.left;
509   if (rtCaret.right() > edit_bounds.right())
510     offsetX = rtCaret.right() - edit_bounds.right();
511   if (rtCaret.top < edit_bounds.top)
512     offsetY = rtCaret.top - edit_bounds.top;
513   if (rtCaret.bottom() > edit_bounds.bottom())
514     offsetY = rtCaret.bottom() - edit_bounds.bottom();
515 
516   m_fScrollOffsetX += offsetX;
517   m_fScrollOffsetY += offsetY;
518   if (m_fFontSize > m_EngineRect.height)
519     m_fScrollOffsetY = 0;
520 
521   return true;
522 }
523 
UpdateOffset(CFWL_ScrollBar * pScrollBar,float fPosChanged)524 bool CFWL_Edit::UpdateOffset(CFWL_ScrollBar* pScrollBar, float fPosChanged) {
525   m_fScrollOffsetY += fPosChanged;
526   return true;
527 }
528 
UpdateVAlignment()529 void CFWL_Edit::UpdateVAlignment() {
530   IFWL_ThemeProvider* theme = GetThemeProvider();
531   CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
532   const CFX_SizeF pSpace = theme->GetSpaceAboveBelow(part);
533   const float fSpaceAbove = pSpace.width >= 0.1f ? pSpace.width : 0.0f;
534   const float fSpaceBelow = pSpace.height >= 0.1f ? pSpace.height : 0.0f;
535   float fOffsetY = 0.0f;
536   CFX_RectF contents_bounds = m_pEditEngine->GetContentsBoundingBox();
537   if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_VCenter) {
538     fOffsetY = (m_EngineRect.height - contents_bounds.height) / 2.0f;
539     if (fOffsetY < (fSpaceAbove + fSpaceBelow) / 2.0f &&
540         fSpaceAbove < fSpaceBelow) {
541       return;
542     }
543     fOffsetY += (fSpaceAbove - fSpaceBelow) / 2.0f;
544   } else if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_VFar) {
545     fOffsetY = (m_EngineRect.height - contents_bounds.height);
546     fOffsetY -= fSpaceBelow;
547   } else {
548     fOffsetY += fSpaceAbove;
549   }
550   m_fVAlignOffset = std::max(fOffsetY, 0.0f);
551 }
552 
UpdateCaret()553 void CFWL_Edit::UpdateCaret() {
554   CFX_RectF rtCaret = m_CaretRect;
555   rtCaret.Offset(m_EngineRect.left - m_fScrollOffsetX,
556                  m_EngineRect.top - m_fScrollOffsetY + m_fVAlignOffset);
557 
558   CFX_RectF rtClient = GetClientRect();
559   rtCaret.Intersect(rtClient);
560   if (rtCaret.left > rtClient.right()) {
561     float right = rtCaret.right();
562     rtCaret.left = rtClient.right() - 1;
563     rtCaret.width = right - rtCaret.left;
564   }
565 
566   if (m_Properties.m_dwStates & FWL_STATE_WGT_Focused && !rtCaret.IsEmpty())
567     ShowCaret(&rtCaret);
568   else
569     HideCaret(&rtCaret);
570 }
571 
UpdateScroll()572 CFWL_ScrollBar* CFWL_Edit::UpdateScroll() {
573   bool bShowVert = m_pVertScrollBar && m_pVertScrollBar->IsVisible();
574   if (!bShowVert)
575     return nullptr;
576 
577   CFX_RectF contents_bounds = m_pEditEngine->GetContentsBoundingBox();
578   CFX_RectF rtScroll = m_pVertScrollBar->GetWidgetRect();
579   if (rtScroll.height < contents_bounds.height) {
580     float fStep = m_pEditEngine->GetLineSpace();
581     float fRange =
582         std::max(contents_bounds.height - m_EngineRect.height, fStep);
583     m_pVertScrollBar->SetRange(0.0f, fRange);
584     float fPos = std::clamp(m_fScrollOffsetY, 0.0f, fRange);
585     m_pVertScrollBar->SetPos(fPos);
586     m_pVertScrollBar->SetTrackPos(fPos);
587     m_pVertScrollBar->SetPageSize(rtScroll.height);
588     m_pVertScrollBar->SetStepSize(fStep);
589     m_pVertScrollBar->RemoveStates(FWL_STATE_WGT_Disabled);
590     m_pVertScrollBar->Update();
591     return m_pVertScrollBar;
592   }
593   if ((m_pVertScrollBar->GetStates() & FWL_STATE_WGT_Disabled) == 0) {
594     m_pVertScrollBar->SetRange(0, -1);
595     m_pVertScrollBar->SetStates(FWL_STATE_WGT_Disabled);
596     m_pVertScrollBar->Update();
597     return m_pVertScrollBar;
598   }
599   return nullptr;
600 }
601 
IsShowVertScrollBar() const602 bool CFWL_Edit::IsShowVertScrollBar() const {
603   const bool bShow =
604       !(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ShowScrollbarFocus) ||
605       (m_Properties.m_dwStates & FWL_STATE_WGT_Focused);
606   return bShow && (m_Properties.m_dwStyles & FWL_STYLE_WGT_VScroll) &&
607          (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_MultiLine) &&
608          IsContentHeightOverflow();
609 }
610 
IsContentHeightOverflow() const611 bool CFWL_Edit::IsContentHeightOverflow() const {
612   return m_pEditEngine->GetContentsBoundingBox().height >
613          m_EngineRect.height + 1.0f;
614 }
615 
Layout()616 void CFWL_Edit::Layout() {
617   m_ClientRect = GetClientRect();
618   m_EngineRect = m_ClientRect;
619 
620   IFWL_ThemeProvider* theme = GetThemeProvider();
621   float fWidth = theme->GetScrollBarWidth();
622   if (!GetOuter()) {
623     CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, this);
624     CFX_RectF pUIMargin = theme->GetUIMargin(part);
625     m_EngineRect.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
626                          pUIMargin.height);
627   } else if (GetOuter()->GetClassID() == FWL_Type::DateTimePicker) {
628     CFWL_ThemePart part(CFWL_ThemePart::Part::kNone, GetOuter());
629     CFX_RectF pUIMargin = theme->GetUIMargin(part);
630     m_EngineRect.Deflate(pUIMargin.left, pUIMargin.top, pUIMargin.width,
631                          pUIMargin.height);
632   }
633 
634   bool bShowVertScrollbar = IsShowVertScrollBar();
635   if (bShowVertScrollbar) {
636     InitVerticalScrollBar();
637 
638     CFX_RectF rtVertScr;
639     if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
640       rtVertScr = CFX_RectF(m_ClientRect.right() + kEditMargin,
641                             m_ClientRect.top, fWidth, m_ClientRect.height);
642     } else {
643       rtVertScr = CFX_RectF(m_ClientRect.right() - fWidth, m_ClientRect.top,
644                             fWidth, m_ClientRect.height);
645       m_EngineRect.width -= fWidth;
646     }
647 
648     m_pVertScrollBar->SetWidgetRect(rtVertScr);
649     m_pVertScrollBar->RemoveStates(FWL_STATE_WGT_Invisible);
650     m_pVertScrollBar->Update();
651   } else if (m_pVertScrollBar) {
652     m_pVertScrollBar->SetStates(FWL_STATE_WGT_Invisible);
653   }
654 }
655 
LayoutScrollBar()656 void CFWL_Edit::LayoutScrollBar() {
657   if (!(m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ShowScrollbarFocus))
658     return;
659 
660   bool bShowVertScrollbar = IsShowVertScrollBar();
661   IFWL_ThemeProvider* theme = GetThemeProvider();
662   float fWidth = theme->GetScrollBarWidth();
663   if (bShowVertScrollbar) {
664     if (!m_pVertScrollBar) {
665       InitVerticalScrollBar();
666       CFX_RectF rtVertScr;
667       if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_OuterScrollbar) {
668         rtVertScr = CFX_RectF(m_ClientRect.right() + kEditMargin,
669                               m_ClientRect.top, fWidth, m_ClientRect.height);
670       } else {
671         rtVertScr = CFX_RectF(m_ClientRect.right() - fWidth, m_ClientRect.top,
672                               fWidth, m_ClientRect.height);
673       }
674       m_pVertScrollBar->SetWidgetRect(rtVertScr);
675       m_pVertScrollBar->Update();
676     }
677     m_pVertScrollBar->RemoveStates(FWL_STATE_WGT_Invisible);
678   } else if (m_pVertScrollBar) {
679     m_pVertScrollBar->SetStates(FWL_STATE_WGT_Invisible);
680   }
681   if (bShowVertScrollbar)
682     UpdateScroll();
683 }
684 
DeviceToEngine(const CFX_PointF & pt)685 CFX_PointF CFWL_Edit::DeviceToEngine(const CFX_PointF& pt) {
686   return pt + CFX_PointF(m_fScrollOffsetX - m_EngineRect.left,
687                          m_fScrollOffsetY - m_EngineRect.top - m_fVAlignOffset);
688 }
689 
InitVerticalScrollBar()690 void CFWL_Edit::InitVerticalScrollBar() {
691   if (m_pVertScrollBar)
692     return;
693 
694   m_pVertScrollBar = cppgc::MakeGarbageCollected<CFWL_ScrollBar>(
695       GetFWLApp()->GetHeap()->GetAllocationHandle(), GetFWLApp(),
696       Properties{0, FWL_STYLEEXT_SCB_Vert,
697                  FWL_STATE_WGT_Disabled | FWL_STATE_WGT_Invisible},
698       this);
699 }
700 
ShowCaret(CFX_RectF * pRect)701 void CFWL_Edit::ShowCaret(CFX_RectF* pRect) {
702   if (m_pCaret) {
703     m_pCaret->ShowCaret();
704     if (!pRect->IsEmpty())
705       m_pCaret->SetWidgetRect(*pRect);
706     RepaintRect(m_EngineRect);
707     return;
708   }
709 
710   CFWL_Widget* pOuter = this;
711   pRect->Offset(m_WidgetRect.left, m_WidgetRect.top);
712   while (pOuter->GetOuter()) {
713     pOuter = pOuter->GetOuter();
714     CFX_RectF rtOuter = pOuter->GetWidgetRect();
715     pRect->Offset(rtOuter.left, rtOuter.top);
716   }
717 
718   CFWL_Widget::AdapterIface* pXFAWidget = pOuter->GetAdapterIface();
719   if (!pXFAWidget)
720     return;
721 
722   CFX_RectF rt = pXFAWidget->GetRotateMatrix().TransformRect(*pRect);
723   pXFAWidget->DisplayCaret(true, &rt);
724 }
725 
HideCaret(CFX_RectF * pRect)726 void CFWL_Edit::HideCaret(CFX_RectF* pRect) {
727   if (m_pCaret) {
728     m_pCaret->HideCaret();
729     RepaintRect(m_EngineRect);
730     return;
731   }
732 
733   CFWL_Widget* pOuter = this;
734   while (pOuter->GetOuter())
735     pOuter = pOuter->GetOuter();
736 
737   CFWL_Widget::AdapterIface* pXFAWidget = pOuter->GetAdapterIface();
738   if (!pXFAWidget)
739     return;
740 
741   pXFAWidget->DisplayCaret(false, pRect);
742 }
743 
InitCaret()744 void CFWL_Edit::InitCaret() {
745   if (m_pCaret)
746     return;
747 
748   m_pCaret = cppgc::MakeGarbageCollected<CFWL_Caret>(
749       GetFWLApp()->GetHeap()->GetAllocationHandle(), GetFWLApp(), Properties(),
750       this);
751   m_pCaret->SetStates(m_Properties.m_dwStates);
752   UpdateCursorRect();
753 }
754 
UpdateCursorRect()755 void CFWL_Edit::UpdateCursorRect() {
756   int32_t bidi_level;
757   if (m_pEditEngine->CanGenerateCharacterInfo()) {
758     std::tie(bidi_level, m_CaretRect) = m_pEditEngine->GetCharacterInfo(
759         checked_cast<int32_t>(m_CursorPosition));
760   } else {
761     bidi_level = 0;
762     m_CaretRect = CFX_RectF();
763   }
764 
765   // TODO(dsinclair): This should handle bidi level  ...
766 
767   m_CaretRect.width = 1.0f;
768 
769   // TODO(hnakashima): Handle correctly edits with empty text instead of using
770   // these defaults.
771   if (m_CaretRect.height == 0)
772     m_CaretRect.height = 8.0f;
773 }
774 
SetCursorPosition(size_t position)775 void CFWL_Edit::SetCursorPosition(size_t position) {
776   if (m_CursorPosition == position)
777     return;
778 
779   m_CursorPosition = std::min(position, m_pEditEngine->GetLength());
780   UpdateCursorRect();
781   OnCaretChanged();
782 }
783 
OnProcessMessage(CFWL_Message * pMessage)784 void CFWL_Edit::OnProcessMessage(CFWL_Message* pMessage) {
785   switch (pMessage->GetType()) {
786     case CFWL_Message::Type::kSetFocus:
787       OnFocusGained();
788       break;
789     case CFWL_Message::Type::kKillFocus:
790       OnFocusLost();
791       break;
792     case CFWL_Message::Type::kMouse: {
793       CFWL_MessageMouse* pMsg = static_cast<CFWL_MessageMouse*>(pMessage);
794       switch (pMsg->m_dwCmd) {
795         case CFWL_MessageMouse::MouseCommand::kLeftButtonDown:
796           OnLButtonDown(pMsg);
797           break;
798         case CFWL_MessageMouse::MouseCommand::kLeftButtonUp:
799           OnLButtonUp(pMsg);
800           break;
801         case CFWL_MessageMouse::MouseCommand::kLeftButtonDblClk:
802           OnButtonDoubleClick(pMsg);
803           break;
804         case CFWL_MessageMouse::MouseCommand::kMove:
805           OnMouseMove(pMsg);
806           break;
807         case CFWL_MessageMouse::MouseCommand::kRightButtonDown:
808           DoRButtonDown(pMsg);
809           break;
810         default:
811           break;
812       }
813       break;
814     }
815     case CFWL_Message::Type::kKey: {
816       CFWL_MessageKey* pKey = static_cast<CFWL_MessageKey*>(pMessage);
817       if (pKey->m_dwCmd == CFWL_MessageKey::KeyCommand::kKeyDown)
818         OnKeyDown(pKey);
819       else if (pKey->m_dwCmd == CFWL_MessageKey::KeyCommand::kChar)
820         OnChar(pKey);
821       break;
822     }
823     default:
824       break;
825   }
826   // Dst target could be |this|, continue only if not destroyed by above.
827   if (pMessage->GetDstTarget())
828     CFWL_Widget::OnProcessMessage(pMessage);
829 }
830 
OnProcessEvent(CFWL_Event * pEvent)831 void CFWL_Edit::OnProcessEvent(CFWL_Event* pEvent) {
832   if (!pEvent || pEvent->GetType() != CFWL_Event::Type::Scroll)
833     return;
834 
835   CFWL_Widget* pSrcTarget = pEvent->GetSrcTarget();
836   if ((pSrcTarget == m_pVertScrollBar && m_pVertScrollBar)) {
837     CFWL_EventScroll* pScrollEvent = static_cast<CFWL_EventScroll*>(pEvent);
838     OnScroll(static_cast<CFWL_ScrollBar*>(pSrcTarget),
839              pScrollEvent->GetScrollCode(), pScrollEvent->GetPos());
840   }
841 }
842 
OnDrawWidget(CFGAS_GEGraphics * pGraphics,const CFX_Matrix & matrix)843 void CFWL_Edit::OnDrawWidget(CFGAS_GEGraphics* pGraphics,
844                              const CFX_Matrix& matrix) {
845   DrawWidget(pGraphics, matrix);
846 }
847 
DoRButtonDown(CFWL_MessageMouse * pMsg)848 void CFWL_Edit::DoRButtonDown(CFWL_MessageMouse* pMsg) {
849   SetCursorPosition(
850       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos)));
851 }
852 
OnFocusGained()853 void CFWL_Edit::OnFocusGained() {
854   m_Properties.m_dwStates |= FWL_STATE_WGT_Focused;
855   UpdateVAlignment();
856   UpdateOffset();
857   UpdateCaret();
858   LayoutScrollBar();
859 }
860 
OnFocusLost()861 void CFWL_Edit::OnFocusLost() {
862   bool bRepaint = false;
863   if (m_Properties.m_dwStates & FWL_STATE_WGT_Focused) {
864     m_Properties.m_dwStates &= ~FWL_STATE_WGT_Focused;
865     HideCaret(nullptr);
866     if (HasSelection()) {
867       ClearSelection();
868       bRepaint = true;
869     }
870     UpdateOffset();
871   }
872   LayoutScrollBar();
873   if (!bRepaint)
874     return;
875 
876   RepaintRect(CFX_RectF(0, 0, m_WidgetRect.width, m_WidgetRect.height));
877 }
878 
OnLButtonDown(CFWL_MessageMouse * pMsg)879 void CFWL_Edit::OnLButtonDown(CFWL_MessageMouse* pMsg) {
880   if (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)
881     return;
882 
883   m_bLButtonDown = true;
884   SetGrab(true);
885 
886   bool bRepaint = false;
887   if (m_pEditEngine->HasSelection()) {
888     m_pEditEngine->ClearSelection();
889     bRepaint = true;
890   }
891 
892   size_t index_at_click =
893       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos));
894 
895   if (index_at_click != m_CursorPosition &&
896       !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kShift)) {
897     size_t start = std::min(m_CursorPosition, index_at_click);
898     size_t end = std::max(m_CursorPosition, index_at_click);
899 
900     m_pEditEngine->SetSelection(start, end - start);
901     bRepaint = true;
902   } else {
903     SetCursorPosition(index_at_click);
904   }
905 
906   if (bRepaint)
907     RepaintRect(m_EngineRect);
908 }
909 
OnLButtonUp(CFWL_MessageMouse * pMsg)910 void CFWL_Edit::OnLButtonUp(CFWL_MessageMouse* pMsg) {
911   m_bLButtonDown = false;
912   SetGrab(false);
913 }
914 
OnButtonDoubleClick(CFWL_MessageMouse * pMsg)915 void CFWL_Edit::OnButtonDoubleClick(CFWL_MessageMouse* pMsg) {
916   size_t click_idx =
917       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos));
918   auto [start_idx, count] = m_pEditEngine->BoundsForWordAt(click_idx);
919 
920   m_pEditEngine->SetSelection(start_idx, count);
921   m_CursorPosition = start_idx + count;
922   RepaintRect(m_EngineRect);
923 }
924 
OnMouseMove(CFWL_MessageMouse * pMsg)925 void CFWL_Edit::OnMouseMove(CFWL_MessageMouse* pMsg) {
926   bool shift = !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kShift);
927   if (!m_bLButtonDown || !shift)
928     return;
929 
930   size_t old_cursor_pos = m_CursorPosition;
931   SetCursorPosition(
932       m_pEditEngine->GetIndexForPoint(DeviceToEngine(pMsg->m_pos)));
933   if (old_cursor_pos == m_CursorPosition)
934     return;
935 
936   size_t length = m_pEditEngine->GetLength();
937   if (m_CursorPosition > length)
938     SetCursorPosition(length);
939 
940   size_t sel_start = 0;
941   size_t count = 0;
942   if (m_pEditEngine->HasSelection())
943     std::tie(sel_start, count) = m_pEditEngine->GetSelection();
944   else
945     sel_start = old_cursor_pos;
946 
947   size_t start_pos = std::min(sel_start, m_CursorPosition);
948   size_t end_pos = std::max(sel_start, m_CursorPosition);
949   m_pEditEngine->SetSelection(start_pos, end_pos - start_pos);
950 }
951 
OnKeyDown(CFWL_MessageKey * pMsg)952 void CFWL_Edit::OnKeyDown(CFWL_MessageKey* pMsg) {
953   bool bShift = !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kShift);
954   bool bCtrl = !!(pMsg->m_dwFlags & XFA_FWL_KeyFlag::kCtrl);
955 
956   size_t sel_start = m_CursorPosition;
957   if (m_pEditEngine->HasSelection()) {
958     auto [start_idx, count] = m_pEditEngine->GetSelection();
959     sel_start = start_idx;
960   }
961 
962   switch (pMsg->m_dwKeyCodeOrChar) {
963     case XFA_FWL_VKEY_Left:
964       SetCursorPosition(m_pEditEngine->GetIndexLeft(m_CursorPosition));
965       break;
966     case XFA_FWL_VKEY_Right:
967       SetCursorPosition(m_pEditEngine->GetIndexRight(m_CursorPosition));
968       break;
969     case XFA_FWL_VKEY_Up:
970       SetCursorPosition(m_pEditEngine->GetIndexUp(m_CursorPosition));
971       break;
972     case XFA_FWL_VKEY_Down:
973       SetCursorPosition(m_pEditEngine->GetIndexDown(m_CursorPosition));
974       break;
975     case XFA_FWL_VKEY_Home:
976       SetCursorPosition(
977           bCtrl ? 0 : m_pEditEngine->GetIndexAtStartOfLine(m_CursorPosition));
978       break;
979     case XFA_FWL_VKEY_End:
980       SetCursorPosition(
981           bCtrl ? m_pEditEngine->GetLength()
982                 : m_pEditEngine->GetIndexAtEndOfLine(m_CursorPosition));
983       break;
984     case XFA_FWL_VKEY_Delete: {
985       if ((m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ReadOnly) ||
986           (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)) {
987         break;
988       }
989 
990       m_pEditEngine->Delete(m_CursorPosition, 1);
991       UpdateCaret();
992       break;
993     }
994     case XFA_FWL_VKEY_Insert:
995     case XFA_FWL_VKEY_F2:
996     case XFA_FWL_VKEY_Tab:
997     default:
998       break;
999   }
1000 
1001   // Update the selection.
1002   if (bShift && sel_start != m_CursorPosition) {
1003     m_pEditEngine->SetSelection(std::min(sel_start, m_CursorPosition),
1004                                 std::max(sel_start, m_CursorPosition));
1005     RepaintRect(m_EngineRect);
1006   }
1007 }
1008 
OnChar(CFWL_MessageKey * pMsg)1009 void CFWL_Edit::OnChar(CFWL_MessageKey* pMsg) {
1010   if ((m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_ReadOnly) ||
1011       (m_Properties.m_dwStates & FWL_STATE_WGT_Disabled)) {
1012     return;
1013   }
1014 
1015   wchar_t c = static_cast<wchar_t>(pMsg->m_dwKeyCodeOrChar);
1016   switch (c) {
1017     case L'\b':
1018       if (m_CursorPosition > 0) {
1019         SetCursorPosition(m_CursorPosition - 1);
1020         m_pEditEngine->Delete(m_CursorPosition, 1);
1021         UpdateCaret();
1022       }
1023       break;
1024     case L'\n':
1025     case 27:   // Esc
1026     case 127:  // Delete
1027       break;
1028     case L'\t':
1029       m_pEditEngine->Insert(m_CursorPosition, L"\t");
1030       SetCursorPosition(m_CursorPosition + 1);
1031       break;
1032     case L'\r':
1033       if (m_Properties.m_dwStyleExts & FWL_STYLEEXT_EDT_WantReturn) {
1034         m_pEditEngine->Insert(m_CursorPosition, L"\n");
1035         SetCursorPosition(m_CursorPosition + 1);
1036       }
1037       break;
1038     default: {
1039       if (pMsg->m_dwFlags & kEditingModifier)
1040         break;
1041 
1042       m_pEditEngine->Insert(m_CursorPosition, WideString(c));
1043       SetCursorPosition(m_CursorPosition + 1);
1044       break;
1045     }
1046   }
1047 }
1048 
OnScroll(CFWL_ScrollBar * pScrollBar,CFWL_EventScroll::Code dwCode,float fPos)1049 bool CFWL_Edit::OnScroll(CFWL_ScrollBar* pScrollBar,
1050                          CFWL_EventScroll::Code dwCode,
1051                          float fPos) {
1052   float fMin;
1053   float fMax;
1054   pScrollBar->GetRange(&fMin, &fMax);
1055   float iCurPos = pScrollBar->GetPos();
1056   float fStep = pScrollBar->GetStepSize();
1057   switch (dwCode) {
1058     case CFWL_EventScroll::Code::Min: {
1059       fPos = fMin;
1060       break;
1061     }
1062     case CFWL_EventScroll::Code::Max: {
1063       fPos = fMax;
1064       break;
1065     }
1066     case CFWL_EventScroll::Code::StepBackward: {
1067       fPos -= fStep;
1068       if (fPos < fMin + fStep / 2) {
1069         fPos = fMin;
1070       }
1071       break;
1072     }
1073     case CFWL_EventScroll::Code::StepForward: {
1074       fPos += fStep;
1075       if (fPos > fMax - fStep / 2) {
1076         fPos = fMax;
1077       }
1078       break;
1079     }
1080     case CFWL_EventScroll::Code::PageBackward: {
1081       fPos -= pScrollBar->GetPageSize();
1082       if (fPos < fMin) {
1083         fPos = fMin;
1084       }
1085       break;
1086     }
1087     case CFWL_EventScroll::Code::PageForward: {
1088       fPos += pScrollBar->GetPageSize();
1089       if (fPos > fMax) {
1090         fPos = fMax;
1091       }
1092       break;
1093     }
1094     case CFWL_EventScroll::Code::Pos:
1095     case CFWL_EventScroll::Code::TrackPos:
1096     case CFWL_EventScroll::Code::None:
1097       break;
1098     case CFWL_EventScroll::Code::EndScroll:
1099       return false;
1100   }
1101   if (iCurPos == fPos)
1102     return true;
1103 
1104   pScrollBar->SetPos(fPos);
1105   pScrollBar->SetTrackPos(fPos);
1106   UpdateOffset(pScrollBar, fPos - iCurPos);
1107   UpdateCaret();
1108 
1109   CFX_RectF rect = GetWidgetRect();
1110   RepaintRect(CFX_RectF(0, 0, rect.width + 2, rect.height + 2));
1111   return true;
1112 }
1113 
1114 }  // namespace pdfium
1115