Skip to content

Commit

Permalink
[UnifiedPDF] Find in PDF without overlays
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=269328
rdar://problem/122917321

Reviewed by Tim Horton.

Provides the initial support for the find in PDF functionality. This
is done by querying the PDFDocument for the string that was passed in
for search.

In order to accomplish this we need to keep track of the last string
that was searched for. If the target string is the same as the last one
then we need to continue the search from the last match/result we found.
We keep track of the results as the current selection. If the target is
not as the last string we found, then we need to clear the selection
before we attempt to search for the string.

When we find the string we will update its selection to the one that
was returned to us from PDFDocument and potentially scroll to it if it
is currently out of view.

* Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h:
* Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.h:
* Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/PDFDocumentLayout.h:
* Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/PDFDocumentLayout.mm:
(WebKit::PDFDocumentLayout::pdfPageRectToDocumentRect const):
* Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/UnifiedPDFPlugin.h:
* Source/WebKit/WebProcess/Plugins/PDF/UnifiedPDF/UnifiedPDFPlugin.mm:
(WebKit::UnifiedPDFPlugin::convertFromPageToDocument const):
(WebKit::UnifiedPDFPlugin::convertFromPageToContents const):
(WebKit::UnifiedPDFPlugin::countFindMatches):
(WebKit::UnifiedPDFPlugin::findString):

Canonical link: https://commits.webkit.org/274646@main
  • Loading branch information
sammygill committed Feb 14, 2024
1 parent bbc2111 commit 3bfd751
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 8 deletions.
1 change: 0 additions & 1 deletion Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ class PDFPlugin final : public PDFPluginBase {
RefPtr<PDFPluginPasswordField> m_passwordField;

String m_temporaryPDFUUID;
String m_lastFoundString;

RetainPtr<WKPDFLayerControllerDelegate> m_pdfLayerControllerDelegate;

Expand Down
6 changes: 3 additions & 3 deletions Source/WebKit/WebProcess/Plugins/PDF/PDFPlugin.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1347,19 +1347,19 @@ static bool getEventTypeFromWebEvent(const WebEvent& event, NSEventType& eventTy
auto searchSelection = [m_pdfLayerController searchSelection];
[m_pdfLayerController findString:target caseSensitive:caseSensitive highlightMatches:YES];
[m_pdfLayerController setSearchSelection:searchSelection];
m_lastFoundString = emptyString();
m_lastFindString = emptyString();
return false;
}

if (m_lastFoundString == target) {
if (m_lastFindString == target) {
auto selection = nextMatchForString(target, searchForward, caseSensitive, wrapSearch, [m_pdfLayerController searchSelection], NO);
if (!selection)
return false;
[m_pdfLayerController setSearchSelection:selection];
[m_pdfLayerController gotoSelection:selection];
} else {
[m_pdfLayerController findString:target caseSensitive:caseSensitive highlightMatches:YES];
m_lastFoundString = target;
m_lastFindString = target;
}

return foundMatch;
Expand Down
2 changes: 2 additions & 0 deletions Source/WebKit/WebProcess/Plugins/PDF/PDFPluginBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ class PDFPluginBase : public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<PDF

String m_suggestedFilename;

String m_lastFindString;

WebCore::IntSize m_size;
WebCore::AffineTransform m_rootViewToPluginTransform;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class PDFDocumentLayout {

WebCore::FloatPoint documentPointToPDFPagePoint(WebCore::FloatPoint documentPoint, PageIndex) const;
WebCore::FloatPoint pdfPagePointToDocumentPoint(WebCore::FloatPoint pagePoint, PageIndex) const;
WebCore::FloatRect pdfPageRectToDocumentRect(WebCore::FloatRect pageRect, PageIndex) const;

// This is the scale that scales the largest page or pair of pages up or down to fit the available width.
float scale() const { return m_scale; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,22 @@
return mappedPoint;
}

WebCore::FloatRect PDFDocumentLayout::pdfPageRectToDocumentRect(const WebCore::FloatRect pageSpaceRect, PageIndex pageIndex) const
{
if (pageIndex >= m_pageGeometry.size())
return pageSpaceRect;

auto& pageGeometry = m_pageGeometry[pageIndex];

auto matrix = toPageTransform(pageGeometry);
auto mappedRect = matrix.inverse().value_or(AffineTransform { }).mapRect(pageSpaceRect);

mappedRect.setY(pageGeometry.layoutBounds.height() - mappedRect.y());
mappedRect.moveBy(pageGeometry.layoutBounds.location());

return mappedRect;
}

} // namespace WebKit

#endif // ENABLE(UNIFIED_PDF)
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,10 @@ class UnifiedPDFPlugin final : public PDFPluginBase, public WebCore::GraphicsLay

WebCore::IntPoint convertFromDocumentToPage(const WebCore::IntPoint&, PDFDocumentLayout::PageIndex) const;
WebCore::IntPoint convertFromPageToDocument(const WebCore::IntPoint&, PDFDocumentLayout::PageIndex) const;
WebCore::IntRect convertFromPageToDocument(const WebCore::IntRect&, PDFDocumentLayout::PageIndex) const;

WebCore::IntPoint convertFromPageToContents(const WebCore::IntPoint&, PDFDocumentLayout::PageIndex) const;
WebCore::IntRect convertFromPageToContents(const WebCore::IntRect&, PDFDocumentLayout::PageIndex) const;

std::optional<PDFDocumentLayout::PageIndex> pageIndexForDocumentPoint(const WebCore::IntPoint&) const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,12 @@ static String mutationObserverNotificationString()
return roundedIntPoint(m_documentLayout.pdfPagePointToDocumentPoint(pageSpacePoint, pageIndex));
}

WebCore::IntRect UnifiedPDFPlugin::convertFromPageToDocument(const WebCore::IntRect& pageSpaceRect, PDFDocumentLayout::PageIndex pageIndex) const
{
ASSERT(pageIndex < m_documentLayout.pageCount());
return IntRect { m_documentLayout.pdfPageRectToDocumentRect(pageSpaceRect, pageIndex) };
}

WebCore::IntPoint UnifiedPDFPlugin::convertFromPageToContents(const WebCore::IntPoint& pageSpacePoint, PDFDocumentLayout::PageIndex pageIndex) const
{
FloatPoint transformedPoint = convertFromPageToDocument(pageSpacePoint, pageIndex);
Expand All @@ -1258,6 +1264,16 @@ static String mutationObserverNotificationString()
return roundedIntPoint(transformedPoint);
}

WebCore::IntRect UnifiedPDFPlugin::convertFromPageToContents(const WebCore::IntRect& pageSpaceRect, PDFDocumentLayout::PageIndex pageIndex) const
{
auto transformedRect = convertFromPageToDocument(pageSpaceRect, pageIndex);
transformedRect.scale(m_documentLayout.scale());
auto padding = centeringOffset();
transformedRect.move(padding.width(), padding.height());
transformedRect.scale(m_scaleFactor);
return transformedRect;
}

#if !LOG_DISABLED
static TextStream& operator<<(TextStream& ts, UnifiedPDFPlugin::PDFElementType elementType)
{
Expand Down Expand Up @@ -2061,14 +2077,78 @@ static IntRect computeMarqueeSelectionRect(const WebCore::IntPoint& point1, cons

#pragma mark -

unsigned UnifiedPDFPlugin::countFindMatches(const String& target, WebCore::FindOptions, unsigned maxMatchCount)
unsigned UnifiedPDFPlugin::countFindMatches(const String& target, WebCore::FindOptions options, unsigned maxMatchCount)
{
return 0;
// FIXME: Why is it OK to ignore the passed-in maximum match count?

if (!target.length())
return 0;

NSStringCompareOptions nsOptions = options.contains(WebCore::CaseInsensitive) ? NSCaseInsensitiveSearch : 0;
return [[m_pdfDocument findString:target withOptions:nsOptions] count];
}

bool UnifiedPDFPlugin::findString(const String& target, WebCore::FindOptions, unsigned maxMatchCount)
bool UnifiedPDFPlugin::findString(const String& target, WebCore::FindOptions options, unsigned maxMatchCount)
{
return false;
if (target.isEmpty()) {
m_lastFindString = target;
setCurrentSelection(nullptr);
return false;
}

if (options.contains(WebCore::DoNotSetSelection)) {
// If the max was zero, any result means we exceeded the max, so we can skip computing the actual count.
// FIXME: How can always returning true without searching if passed a max of 0 be right?
// Even if it is right, why not put that special case inside countFindMatches instead of here?
return !target.isEmpty() && (!maxMatchCount || countFindMatches(target, options, maxMatchCount));
}

bool searchForward = !options.contains(WebCore::Backwards);
bool isCaseSensitive = !options.contains(WebCore::CaseInsensitive);
bool wrapSearch = options.contains(WebCore::WrapAround);

auto nextMatchForString = [&]() -> PDFSelection * {
if (!target.length())
return nullptr;
NSStringCompareOptions options = 0;
if (!searchForward)
options |= NSBackwardsSearch;
if (!isCaseSensitive)
options |= NSCaseInsensitiveSearch;
PDFSelection *foundSelection = [m_pdfDocument findString:target fromSelection:m_currentSelection.get() withOptions:options];
if (!foundSelection && wrapSearch) {
auto emptySelection = adoptNS([allocPDFSelectionInstance() initWithDocument:m_pdfDocument.get()]);
foundSelection = [m_pdfDocument findString:target fromSelection:emptySelection.get() withOptions:options];
}
return foundSelection;
};

if (m_lastFindString != target) {
setCurrentSelection(nullptr);
m_lastFindString = target;
}

RetainPtr selection = nextMatchForString();
if (!selection)
return false;

RetainPtr firstPageForSelection = [[selection pages] firstObject];
if (!firstPageForSelection)
return false;

auto firstPageIndex = m_documentLayout.indexForPage(firstPageForSelection);
if (!firstPageIndex)
return false;

auto selectionContentsSpaceBounds = convertFromPageToContents(IntRect { [selection boundsForPage:firstPageForSelection.get()] }, firstPageIndex.value());
selectionContentsSpaceBounds.setY(selectionContentsSpaceBounds.location().y() - selectionContentsSpaceBounds.size().height());
auto currentScrollPositionY = scrollPosition().y();

if (selectionContentsSpaceBounds.y() < currentScrollPositionY || selectionContentsSpaceBounds.y() > currentScrollPositionY + size().height())
scrollToPositionWithoutAnimation(selectionContentsSpaceBounds.location());

setCurrentSelection(WTFMove(selection));
return true;
}

bool UnifiedPDFPlugin::performDictionaryLookupAtLocation(const FloatPoint&)
Expand Down

0 comments on commit 3bfd751

Please sign in to comment.