Если мы хотим отображать PDF-файлы в нашем приложении UWP, есть несколько способов их заархивировать. Мы могли бы искать сторонние библиотеки, которые предоставляют нам функции PDF. Моя проблема в том, что я не смог найти бесплатную библиотеку PDF. Большинство из них проприетарные, и за них нужно платить.

Другой способ - использовать пространство имен Windows.Data.Pdf, предоставляемое Microsoft. Это пространство имен может преобразовывать файлы PDF в изображения, которые затем могут отображаться в нашем приложении. Проблема с изображением в том, что оно не содержит текста, доступного для поиска. Таким образом, показанные страницы PDF недоступны для поиска.

В следующей статье я опишу способ визуализации файлов PDF с использованием пространства имен Windows.Data.Pdf и обеспечения их доступности для поиска с помощью пространства имен Windows.Media.Ocr. Таким образом, нам не нужно платить за библиотеку, и даже отсканированные PDF-файлы будут доступны для поиска.

Сначала мы создадим простую программу просмотра PDF, используя пространство имен Windows.Data.Pdf.
Для этого давайте создадим ScrollViewer, который содержит ItemsControl, который будет отображать визуализированные изображения в поле с именем PdfViewer.xaml.

Как видите, ItemsControl привязывается к объекту с именем PdfPages. Этот объект является ObservableCollection и будет предоставлен моделью представления, которую мы сейчас создаем:

Теперь нам нужно открыть файл PDF. Для этого мы создаем метод с именем OpenPdf в модели представления, который использует FileOpenPicker для выбора файла:

После этого нам нужно отрендерить изображения. Для этого мы перебираем страницы загруженного документа. Для каждого PdfPage мы вызываем метод RenderToStreamAsync (), который отображает страницу PDF в заданном потоке. Этот поток теперь используется для декодирования из него SoftwareBitmap. Мы используем SoftwareBitmap, потому что он нам понадобится позже для механизма распознавания текста. Теперь нам нужно создать SoftwareBitmapSource, который мы будем использовать для отображения изображений:

Как вы видите в приведенном выше коде, мы создаем новый объект класса Page и добавляем этот объект в PdfPages ObservableCollection. Класс Page реализован нами и будет содержать информацию о отображаемой странице. На данный момент класс Page выглядит так:

Теперь все, что нам нужно сделать для просмотра файлов PDF, - это подключить представление к модели представления и создать кнопку, запускающую метод OpenFile. Чтобы связать представление с моделью представления, мы создаем экземпляр нового объекта класса PdfViewerViewModel в коде файла представления.

Затем мы создаем кнопку в файле XAML, которая запускает метод щелчка в коде файла, который вызывает метод OpenPdf.

Теперь вы можете запустить приложение, загрузить файл PDF, и оно вам его покажет.

Следующим шагом будет использование OCREngine для извлечения слов из изображений.

Давайте создадим еще один ItemsControl в файле PdfViewer.xaml, который мы будем использовать для пометки найденных слов. Поэтому мы помещаем новый ItemsControl в существующий ItemsControl для страниц PDF. Мы делаем это, потому что OcrEngine даст нам прямоугольник, содержащий область, в которой было найдено слово, относительно страницы PDF, на которой оно было найдено. Затем мы можем просто привязать к смещениям нашел слова без дальнейших вычислений. Тогда содержимое ScrollViewer будет выглядеть следующим образом:

После этого мы создаем ObservableCollection с именем Words, к которому привязывается новый ItemsControl, в классе Page.

Теперь нам нужен метод, который извлекает слова из изображений, сгенерированных из файла PDF. Этот метод будет создан в классе PdfViewModel. Первый шаг - создать объект OcrEngine. Мы делаем это, вызывая TryCreateFromUserProfileLanguage (). Поступая таким образом, мы создаем OcrEngine и настраиваем его с помощью языковых настроек пользователя. Затем мы перебираем изображения и извлекаем слова из каждого изображения.

OcrEngine предоставляет нам OcrObject, который содержит объекты OcrLine. Итак, теперь мы перебираем строки, а затем слова в строке, чтобы сохранить объекты OcrWord в FoundWords ObservableCollection каждого объекта PdfPage.

Наш метод извлечения OCR готов к использованию. Назовем его в конце метода OpenLocal ().

Теперь вы можете запустить проект, открыть файл PDF и отметить каждое обнаруженное слово.

Наконец, нам нужна функция поиска.

Поэтому мы добавляем элемент управления SeachBar в ваше представление XAML.

Как видите, SeachBar вызывает метод SeachNext при возникновении события QuerySubmitted. Здесь мы даже немного злоупотребляем. Он будет появляться каждый раз при нажатии на увеличительное стекло или при нажатии клавиши ввода. Итак, в этом случае мы хотим перейти к следующему слову. Мы будем запускать новый поиск каждый раз, когда возникает событие QueryChanged.

Перед тем, как реализовать эти методы, мы сначала создаем серверную часть поиска в модели представления. Нам нужен метод, который находит все слова, соответствующие строке поиска. И мы будем использовать Enumerator, который перечисляет все найденные страницы. Позже мы будем использовать это для перехода к страницам, содержащим найденные слова. Метод FindWord принимает строку поиска в качестве входных данных и выглядит следующим образом:

Мы вызываем метод find FindWord для каждой страницы и добавляем страницу в список при обнаружении совпадающих слов. После этого перечислитель FoundPages устанавливается на счетчик списка.
Если строка поиска пуста, мы очищаем все объекты, которые содержат информацию о поиске (не удивляйтесь свойству WordOverlay класса Page, мы создадим его в момент). Таким образом мы перехватываем выделение каждого слова, когда строка поиска пуста.
Теперь мы изменим класс Page. Сначала мы меняем тип коллекции Words с ObservableCollection на Collection, потому что мы не хотим выделять все слова, а только совпадающие. Затем мы создаем новую коллекцию ObservableCollection с именем WordOverlays. Теперь мы меняем привязку внутреннего элемента ItemsControl формы представления XAML Words на WordOverlays.

Следующим шагом является создание метода FindWord в классе страницы. Этот метод просто перебирает все слова на странице и добавляет совпадающие в коллекцию WordsOverlays.

Последнее, что нужно сделать, - это реализовать методы SearchNext и Search в коде файла представления.
Начнем с метода Search. Он вызовет метод FindWords модели представления и автоматически перейдет к первой странице, содержащей совпадающее слово.

Строка 6 выполняет автоматическую прокрутку. Здесь мы получаем текущую страницу перечислителя FoundPages (которая является первой страницей, содержащей подходящее слово здесь). Затем мы вызываем метод ContainerFromItem для PdfContainer, который является именем внешнего элемента ItemsControl, который содержит все страницы PDF. Метод ContainerFromItem предоставляет нам контейнер объекта страницы как DependencyObject. Теперь мы приводим полученный объект к FrameworkElement, потому что дочерние элементы ItemsControl - это ContentPresenter, то есть FrameworkElements. Теперь мы можем вызвать метод StartBringIntoView, который запустит процесс прокрутки.

Метод SearchNext теперь просто переместит счетчик, который перечисляет страницы, содержащие совпадающие слова, в следующую позицию, а затем прокручивает до этой страницы.

Теперь мы закончили. Мы создали программу просмотра PDF-файлов UWP, которая дает нам возможность выполнять поиск в PDF-файлах. Мы заархивировали это, используя только бесплатную библиотеку Microsoft. Еще одна интересная особенность нашей программы просмотра PDF-файлов заключается в том, что она также делает изображения в PDF-файлах доступными для поиска.

Полный код можно найти на моем GitHub: https://github.com/lukaskroeger/Searchable-PDF-Viewer
Если вы обнаружите ошибки или у вас есть предложения по моему коду, не стесняйтесь открывать вопросы или запросы на вытягивание.

Надеюсь, статья вам помогла, и вы с удовольствием реализовали программу просмотра.