Извлечение аннотаций
PDF Oxide предоставляет доступ ко всем типам аннотаций, определённым в спецификации PDF (ISO 32000-1:2008, раздел 12.5): текстовые заметки, гиперссылки, выделения, штампы, рукописные аннотации и многое другое. Также доступно оглавление документа (закладки) для построения навигационных структур.
Используйте get_annotations() на PdfDocument для получения сырых данных аннотаций или DOM API PdfPage, предоставляющий унифицированный интерфейс AnnotationWrapper с поддержкой чтения и записи.
Поддержка в биндингах. Извлечение аннотаций доступно в Python (
doc.get_annotations(page)), Rust (doc.get_annotations(page)), WASM (doc.getAnnotations(page)) и Go (doc.Annotations(page)). Публичный API C# пока не предоставляет обёрткуGetAnnotations— нативный FFI-метод (PdfDocumentGetPageAnnotations) существует, но не обёрнут. Для извлечения аннотаций из C# используйте Rust CLI (pdf-oxide annotations doc.pdf) или вызывайтеPdfPageGetAnnotationsCount/pdf_get_annotations_by_typeнапрямую через P/Invoke до появления публичной обёртки.
Быстрый пример
Python
from pdf_oxide import PdfDocument
doc = PdfDocument("annotated.pdf")
page = doc.page(0)
for annot in page.annotations():
print(f"{annot.subtype}: {annot.contents}")
Node.js
const { PdfDocument } = require("pdf-oxide");
const doc = new PdfDocument("annotated.pdf");
const annotations = doc.getPageAnnotations(0);
for (const annot of annotations) {
console.log(`${annot.subtype}: ${annot.contents}`);
}
doc.close();
Go
import pdfoxide "github.com/yfedoseev/pdf_oxide/go"
doc, _ := pdfoxide.Open("annotated.pdf")
defer doc.Close()
annotations, _ := doc.Annotations(0)
for _, annot := range annotations {
fmt.Printf("%s: %s\n", annot.Subtype, annot.Content)
}
<!-- C#: no equivalent on PdfDocument — annotations not exposed on csharp/PdfOxide/Core/PdfDocument.cs -->
WASM
const doc = new WasmPdfDocument(bytes);
const annotations = doc.getAnnotations(0);
for (const annot of annotations) {
console.log(`${annot.subtype}: ${annot.contents}`);
}
Rust
use pdf_oxide::PdfDocument;
let mut doc = PdfDocument::open("annotated.pdf")?;
let annotations = doc.get_annotations(0)?;
for annot in &annotations {
println!("{:?}: {:?}", annot.subtype_enum, annot.contents);
}
Java
import fyi.oxide.pdf.*;
import fyi.oxide.pdf.annotation.Annotation;
import java.nio.file.Path;
try (PdfDocument doc = PdfDocument.open(Path.of("annotated.pdf"))) {
for (Annotation annot : doc.page(0).annotations()) {
System.out.println(annot.type() + ": " + annot.contents().orElse(""));
}
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("annotated.pdf");
for (const auto& annot : doc.page_annotations(0)) {
std::cout << annot.subtype << ": " << annot.content << "\n";
}
Swift
import PdfOxide
let doc = try Document.open("annotated.pdf")
for annot in try doc.pageAnnotations(0) {
print("\(annot.subtype): \(annot.content)")
}
Kotlin
import fyi.oxide.pdf.*
PdfDocument.open(java.nio.file.Path.of("annotated.pdf")).use { doc ->
for (annot in doc.page(0).annotations()) {
println("${annot.type()}: ${annot.contents().orElse("")}")
}
}
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('annotated.pdf');
for (final annot in doc.pageAnnotations(0)) {
print('${annot.subtype}: ${annot.content}');
}
doc.close();
R
library(pdfoxide)
doc <- pdf_open("annotated.pdf")
for (annot in pdf_page_annotations(doc, 0)) {
cat(sprintf("%s: %s\n", annot$subtype, annot$content))
}
Julia
using PdfOxide
doc = open_document("annotated.pdf")
for annot in page_annotations(doc, 0)
println("$(annot.subtype): $(annot.content)")
end
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("annotated.pdf");
defer doc.deinit();
const annotations = try doc.pageAnnotations(a, 0);
defer pdf_oxide.Document.freeAnnotations(a, annotations);
for (annotations) |annot| {
std.debug.print("{s}: {s}\n", .{ annot.subtype, annot.content });
}
Scala
import fyi.oxide.pdf.{PdfDocument, annotationsSeq, contentsOption}
import scala.util.Using
Using.resource(PdfDocument.open("annotated.pdf")) { doc =>
for (annot <- doc.page(0).annotationsSeq) {
println(s"${annot.`type`()}: ${annot.contentsOption.getOrElse("")}")
}
}
Clojure
(require '[pdf-oxide.core :as pdf])
(with-open [d (pdf/open "annotated.pdf")]
(doseq [annot (pdf/annotations (pdf/page d 0))]
(println (str (.type annot) ": " (.orElse (.contents annot) "")))))
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"annotated.pdf" error:&err];
for (POXAnnotation *annot in [doc pageAnnotations:0 error:&err]) {
NSLog(@"%@: %@", annot.subtype, annot.content);
}
Elixir
{:ok, doc} = PdfOxide.open("annotated.pdf")
{:ok, annots} = PdfOxide.page_annotations(doc, 0)
Enum.each(annots, fn a -> IO.puts("#{a.subtype}: #{a.content}") end)
Справочник по API
get_annotations(page_index) -> Vec<Annotation>
Извлекает сырые аннотации с указанной страницы. Возвращает все типы аннотаций, присутствующих на странице.
| Параметр | Тип | Описание |
|---|---|---|
page_index |
usize |
Индекс страницы, начиная с нуля |
Возвращает: вектор объектов Annotation.
Поля аннотации
| Поле | Тип | Описание |
|---|---|---|
annotation_type |
String |
Всегда "Annot" |
subtype |
Option<String> |
Сырая строка подтипа (например, "Text", "Highlight") |
subtype_enum |
AnnotationSubtype |
Разобранное перечисление подтипа |
contents |
Option<String> |
Текстовое содержимое аннотации |
rect |
Option<[f64; 4]> |
Ограничивающий прямоугольник [x1, y1, x2, y2] |
author |
Option<String> |
Автор/создатель (запись /T) |
creation_date |
Option<String> |
Дата создания |
modification_date |
Option<String> |
Дата последнего изменения |
subject |
Option<String> |
Тема аннотации |
destination |
Option<LinkDestination> |
Цель ссылки (для аннотаций типа Link) |
action |
Option<LinkAction> |
Действие ссылки (для аннотаций типа Link) |
color |
Option<Vec<f64>> |
Компоненты цвета аннотации |
flags |
Option<AnnotationFlags> |
Флаги аннотации (invisible, hidden, print и др.) |
Варианты AnnotationSubtype
| Вариант | Описание |
|---|---|
Text |
Аннотация-стикер |
Link |
Гиперссылка |
FreeText |
Текстовое поле |
Line |
Линия |
Square |
Прямоугольник |
Circle |
Эллипс |
Polygon |
Многоугольник |
PolyLine |
Ломаная линия |
Highlight |
Выделение текста |
Underline |
Подчёркивание текста |
Squiggly |
Волнистое подчёркивание |
StrikeOut |
Зачёркивание |
Stamp |
Штамп |
Ink |
Рукописный рисунок |
Popup |
Всплывающая заметка, связанная с другой аннотацией |
FileAttachment |
Вложенный файл |
Sound |
Звуковая аннотация |
Movie |
Видеоаннотация |
Screen |
Экранная аннотация |
Widget |
Виджет поля формы |
PrinterMark |
Метка принтера |
TrapNet |
Ловушечная сеть |
Watermark |
Водяной знак |
ThreeDimensional |
3D-аннотация |
Redact |
Редакция (замазывание) |
Caret |
Курсорная аннотация (точка вставки) |
RichMedia |
Мультимедийная аннотация |
Unknown |
Нераспознанный тип аннотации |
get_outline() -> Option<Vec<OutlineItem>>
Получает оглавление документа (закладки), если оно есть. Возвращает иерархическое дерево элементов оглавления для навигации по документу.
Возвращает:
Some(Vec<OutlineItem>)– закладки найдены и разобраныNone– в документе нет закладок
Поля OutlineItem
| Поле | Тип | Описание |
|---|---|---|
title |
String |
Текст заголовка закладки |
dest |
Option<Destination> |
Цель навигации |
children |
Vec<OutlineItem> |
Вложенные дочерние закладки |
Варианты Destination
| Вариант | Описание |
|---|---|
PageIndex(usize) |
Прямая ссылка на страницу (индекс с нуля) |
Named(String) |
Идентификатор именованной цели |
Rust
let mut doc = PdfDocument::open("book.pdf")?;
if let Some(outline) = doc.get_outline()? {
for item in &outline {
println!(" {}", item.title);
for child in &item.children {
println!(" {}", child.title);
}
}
} else {
println!("No bookmarks found.");
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("book.pdf");
std::string outline = doc.get_outline(); // JSON tree of bookmarks
std::cout << outline << "\n";
Swift
import PdfOxide
let doc = try Document.open("book.pdf")
let outline = try doc.outline() // JSON tree of bookmarks
print(outline)
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('book.pdf');
final outline = doc.getOutline(); // JSON tree of bookmarks
print(outline);
doc.close();
R
library(pdfoxide)
doc <- pdf_open("book.pdf")
outline <- pdf_get_outline(doc) # JSON tree of bookmarks
cat(outline, "\n")
Julia
using PdfOxide
doc = open_document("book.pdf")
outline = get_outline(doc) # JSON tree of bookmarks
println(outline)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("book.pdf");
defer doc.deinit();
const outline = try doc.outline(a); // JSON tree of bookmarks; caller owns it
defer a.free(outline);
std.debug.print("{s}\n", .{outline});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"book.pdf" error:&err];
NSString *outline = [doc outlineWithError:&err]; // JSON tree of bookmarks
NSLog(@"%@", outline);
Elixir
{:ok, doc} = PdfOxide.open("book.pdf")
{:ok, outline} = PdfOxide.outline(doc) # JSON tree of bookmarks
IO.puts(outline)
API аннотаций PdfPage (DOM)
Объект PdfPage из DocumentEditor предоставляет высокоуровневый интерфейс AnnotationWrapper, поддерживающий как чтение существующих аннотаций, так и добавление новых.
page.annotations() -> &[AnnotationWrapper]
Получить все аннотации на странице в виде обёрнутых объектов.
page.find_annotations_by_type(subtype) -> Vec<&AnnotationWrapper>
Найти аннотации определённого типа.
page.add_annotation(annotation)
Добавить новую аннотацию на страницу.
page.remove_annotation(index) -> Option<AnnotationWrapper>
Удалить аннотацию по индексу.
page.find_annotations_in_region(rect) -> Vec<&AnnotationWrapper>
Найти аннотации, ограничивающие прямоугольники которых пересекают заданную область.
Методы AnnotationWrapper
| Метод | Возвращает | Описание |
|---|---|---|
id() |
AnnotationId |
Уникальный ID сессии |
subtype() |
AnnotationSubtype |
Тип аннотации |
rect() |
Rect |
Ограничивающий прямоугольник |
contents() |
Option<&str> |
Текстовое содержимое |
color() |
Option<(f32, f32, f32)> |
RGB-цвет (0.0–1.0) |
is_modified() |
bool |
Была ли аннотация изменена |
Python
doc = PdfDocument("annotated.pdf")
page = doc.page(0)
# List all annotations
for annot in page.annotations():
print(f"[{annot.subtype}] {annot.contents} at {annot.rect}")
# Find highlights
highlights = [a for a in page.annotations() if a.subtype == "Highlight"]
print(f"Found {len(highlights)} highlights")
Node.js
const doc = new PdfDocument("annotated.pdf");
const annotations = doc.getPageAnnotations(0);
// List all annotations
for (const annot of annotations) {
console.log(`[${annot.subtype}] ${annot.contents}`);
}
// Find highlights
const highlights = annotations.filter(a => a.subtype === "Highlight");
console.log(`Found ${highlights.length} highlights`);
doc.close();
Go
doc, _ := pdfoxide.Open("annotated.pdf")
defer doc.Close()
annotations, _ := doc.Annotations(0)
// List all annotations
for _, annot := range annotations {
fmt.Printf("[%s] %s\n", annot.Subtype, annot.Content)
}
// Find highlights
highlights := 0
for _, a := range annotations {
if a.Subtype == "Highlight" {
highlights++
}
}
fmt.Printf("Found %d highlights\n", highlights)
<!-- C#: no equivalent on PdfDocument — annotations not exposed on csharp/PdfOxide/Core/PdfDocument.cs -->
WASM
const doc = new WasmPdfDocument(bytes);
const annotations = doc.getAnnotations(0);
// List all annotations
for (const annot of annotations) {
console.log(`[${annot.subtype}] ${annot.contents}`);
}
// Find highlights
const highlights = annotations.filter(a => a.subtype === "Highlight");
console.log(`Found ${highlights.length} highlights`);
Rust
use pdf_oxide::editor::{DocumentEditor, EditableDocument};
use pdf_oxide::annotation_types::AnnotationSubtype;
let mut editor = DocumentEditor::open("annotated.pdf")?;
let page = editor.get_page(0)?;
// Find all highlight annotations
let highlights = page.find_annotations_by_type(AnnotationSubtype::Highlight);
for h in &highlights {
println!("Highlight at {:?}: {:?}", h.rect(), h.contents());
}
Java
import fyi.oxide.pdf.*;
import fyi.oxide.pdf.annotation.Annotation;
import fyi.oxide.pdf.annotation.AnnotationType;
import java.nio.file.Path;
try (PdfDocument doc = PdfDocument.open(Path.of("annotated.pdf"))) {
var annotations = doc.page(0).annotations();
// List all annotations
for (Annotation annot : annotations) {
System.out.println("[" + annot.type() + "] " + annot.contents().orElse(""));
}
// Find highlights
long highlights = annotations.stream()
.filter(a -> a.type() == AnnotationType.HIGHLIGHT).count();
System.out.println("Found " + highlights + " highlights");
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("annotated.pdf");
auto annotations = doc.page_annotations(0);
// List all annotations
for (const auto& annot : annotations) {
std::cout << "[" << annot.subtype << "] " << annot.content << "\n";
}
// Find highlights
int highlights = 0;
for (const auto& a : annotations) {
if (a.subtype == "Highlight") highlights++;
}
std::cout << "Found " << highlights << " highlights\n";
Swift
import PdfOxide
let doc = try Document.open("annotated.pdf")
let annotations = try doc.pageAnnotations(0)
// List all annotations
for annot in annotations {
print("[\(annot.subtype)] \(annot.content)")
}
// Find highlights
let highlights = annotations.filter { $0.subtype == "Highlight" }
print("Found \(highlights.count) highlights")
Kotlin
import fyi.oxide.pdf.*
import fyi.oxide.pdf.annotation.AnnotationType
PdfDocument.open(java.nio.file.Path.of("annotated.pdf")).use { doc ->
val annotations = doc.page(0).annotations()
// List all annotations
for (annot in annotations) {
println("[${annot.type()}] ${annot.contents().orElse("")}")
}
// Find highlights
val highlights = annotations.count { it.type() == AnnotationType.HIGHLIGHT }
println("Found $highlights highlights")
}
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('annotated.pdf');
final annotations = doc.pageAnnotations(0);
// List all annotations
for (final annot in annotations) {
print('[${annot.subtype}] ${annot.content}');
}
// Find highlights
final highlights = annotations.where((a) => a.subtype == 'Highlight');
print('Found ${highlights.length} highlights');
doc.close();
R
library(pdfoxide)
doc <- pdf_open("annotated.pdf")
annotations <- pdf_page_annotations(doc, 0)
# List all annotations
for (annot in annotations) {
cat(sprintf("[%s] %s\n", annot$subtype, annot$content))
}
# Find highlights
highlights <- Filter(function(a) a$subtype == "Highlight", annotations)
cat(sprintf("Found %d highlights\n", length(highlights)))
Julia
using PdfOxide
doc = open_document("annotated.pdf")
annotations = page_annotations(doc, 0)
# List all annotations
for annot in annotations
println("[$(annot.subtype)] $(annot.content)")
end
# Find highlights
highlights = filter(a -> a.subtype == "Highlight", annotations)
println("Found $(length(highlights)) highlights")
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("annotated.pdf");
defer doc.deinit();
const annotations = try doc.pageAnnotations(a, 0);
defer pdf_oxide.Document.freeAnnotations(a, annotations);
// List all annotations
for (annotations) |annot| {
std.debug.print("[{s}] {s}\n", .{ annot.subtype, annot.content });
}
// Find highlights
var highlights: usize = 0;
for (annotations) |annot| {
if (std.mem.eql(u8, annot.subtype, "Highlight")) highlights += 1;
}
std.debug.print("Found {d} highlights\n", .{highlights});
Scala
import fyi.oxide.pdf.{PdfDocument, annotationsSeq, contentsOption}
import fyi.oxide.pdf.annotation.AnnotationType
import scala.util.Using
Using.resource(PdfDocument.open("annotated.pdf")) { doc =>
val annotations = doc.page(0).annotationsSeq
// List all annotations
for (annot <- annotations) {
println(s"[${annot.`type`()}] ${annot.contentsOption.getOrElse("")}")
}
// Find highlights
val highlights = annotations.count(_.`type`() == AnnotationType.HIGHLIGHT)
println(s"Found $highlights highlights")
}
Clojure
(require '[pdf-oxide.core :as pdf])
(import 'fyi.oxide.pdf.annotation.AnnotationType)
(with-open [d (pdf/open "annotated.pdf")]
(let [annotations (pdf/annotations (pdf/page d 0))]
;; List all annotations
(doseq [annot annotations]
(println (str "[" (.type annot) "] " (.orElse (.contents annot) ""))))
;; Find highlights
(let [highlights (count (filter #(= (.type %) AnnotationType/HIGHLIGHT) annotations))]
(println (str "Found " highlights " highlights")))))
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"annotated.pdf" error:&err];
NSArray<POXAnnotation*> *annotations = [doc pageAnnotations:0 error:&err];
// List all annotations
for (POXAnnotation *annot in annotations) {
NSLog(@"[%@] %@", annot.subtype, annot.content);
}
// Find highlights
NSPredicate *p = [NSPredicate predicateWithFormat:@"subtype == %@", @"Highlight"];
NSUInteger highlights = [annotations filteredArrayUsingPredicate:p].count;
NSLog(@"Found %lu highlights", (unsigned long)highlights);
Elixir
{:ok, doc} = PdfOxide.open("annotated.pdf")
{:ok, annotations} = PdfOxide.page_annotations(doc, 0)
# List all annotations
Enum.each(annotations, fn a -> IO.puts("[#{a.subtype}] #{a.content}") end)
# Find highlights
highlights = Enum.count(annotations, &(&1.subtype == "Highlight"))
IO.puts("Found #{highlights} highlights")
annotations_to_json — сериализация аннотаций страницы
annotations_to_json сериализует весь список аннотаций в JSON-массив за один FFI-вызов. Биндинг Go использует её внутренне для преобразования в []Annotation; Swift предоставляет её напрямую как annotationsToJson. Сигнатура C ABI:
char *pdf_oxide_annotations_to_json(const FfiAnnotationList *annotations, int32_t *error_code);
Возвращаемая UTF-8 строка принадлежит вызывающей стороне (освобождается через free_string). Схема соответствует структуре Annotation в Go — поля: type, subtype, content, x, y, width, height, author, borderWidth, color, creationDate, modificationDate, linkURI, textIconName, isHidden, isPrintable, isReadOnly, isMarkedDeleted.
Swift
import PdfOxide
let doc = try Document.open("annotated.pdf")
let json = try doc.annotationsToJson(0) // String of JSON
print(json)
C ABI
#include "pdf_oxide.h"
int32_t err = 0;
FfiAnnotationList *list = pdf_document_get_page_annotations(doc, /*page=*/0, &err);
char *json = pdf_oxide_annotations_to_json(list, &err);
printf("%s\n", json);
free_string(json);
pdf_oxide_annotation_list_free(list);
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("annotated.pdf");
std::string json = doc.annotations_to_json(0); // JSON string
std::cout << json << "\n";
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('annotated.pdf');
final json = doc.annotationsToJson(0); // JSON string
print(json);
doc.close();
R
library(pdfoxide)
doc <- pdf_open("annotated.pdf")
json <- pdf_annotations_to_json(doc, 0) # JSON string
cat(json, "\n")
Julia
using PdfOxide
doc = open_document("annotated.pdf")
json = annotations_to_json(doc, 0) # JSON string
println(json)
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("annotated.pdf");
defer doc.deinit();
var list = try doc.annotationList(0);
defer list.deinit();
const json = try list.toJson(a); // caller owns the slice
defer a.free(json);
std.debug.print("{s}\n", .{json});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"annotated.pdf" error:&err];
NSString *json = [doc annotationsJson:0 error:&err]; // JSON string
NSLog(@"%@", json);
Elixir
{:ok, doc} = PdfOxide.open("annotated.pdf")
{:ok, json} = PdfOxide.annotations_to_json(doc, 0) # JSON string
IO.puts(json)
Поддержка в биндингах.
annotations_to_jsonдоступна напрямую в Swift (doc.annotationsToJson(page)), C++ (doc.annotations_to_json(page)), Dart (doc.annotationsToJson(page)), R (pdf_annotations_to_json(doc, page)), Julia (annotations_to_json(doc, page)), Zig (doc.annotationList(page).toJson(...)), Objective-C ([doc annotationsJson:page error:]), Elixir (PdfOxide.annotations_to_json(doc, page)) и C ABI (pdf_oxide_annotations_to_json). Биндинг Go вызывает её внутренне для декодированияdoc.Annotations(page)в типизированные структуры. Для WASM-таргета функция исключается из компиляции.
annotation_extras — расширенные атрибуты аннотации
annotation_extras читает расширенные атрибуты отдельной аннотации, не входящие в базовое представление Annotation: цвет, временны́е метки создания и изменения, четыре флага видимости (hidden, marked-deleted, printable, read-only), URI ссылки для аннотаций типа Link, имя иконки для текстовых аннотаций и координаты четырёхугольников выделения/разметки.
В Swift они возвращаются единой структурой AnnotationExtras через annotationExtras(page, index:). В Go те же поля встроены непосредственно в структуру Annotation (Color, CreationDate, ModificationDate, LinkURI, TextIconName, IsHidden, IsPrintable, IsReadOnly, IsMarkedDeleted). Под капотом оба варианта используют семейство C ABI-аксессоров pdf_oxide_annotation_get_* / pdf_oxide_*_annotation_get_*.
Swift
import PdfOxide
let doc = try Document.open("annotated.pdf")
let extras = try doc.annotationExtras(0, index: 0) // AnnotationExtras
print("color=\(extras.color) created=\(extras.creationDate)")
print("hidden=\(extras.hidden) printable=\(extras.printable) readOnly=\(extras.readOnly)")
if !extras.uri.isEmpty { print("link URI: \(extras.uri)") }
if !extras.iconName.isEmpty { print("icon: \(extras.iconName)") }
for q in extras.quadPoints {
print("quad: (\(q.x1),\(q.y1)) (\(q.x2),\(q.y2)) (\(q.x3),\(q.y3)) (\(q.x4),\(q.y4))")
}
Go
import pdfoxide "github.com/yfedoseev/pdf_oxide/go"
doc, _ := pdfoxide.Open("annotated.pdf")
defer doc.Close()
annotations, _ := doc.Annotations(0)
a := annotations[0]
fmt.Printf("color=%d created=%d modified=%d\n", a.Color, a.CreationDate, a.ModificationDate)
fmt.Printf("hidden=%v printable=%v readOnly=%v deleted=%v\n",
a.IsHidden, a.IsPrintable, a.IsReadOnly, a.IsMarkedDeleted)
if a.LinkURI != "" {
fmt.Printf("link URI: %s\n", a.LinkURI)
}
if a.TextIconName != "" {
fmt.Printf("icon: %s\n", a.TextIconName)
}
C++
#include <pdf_oxide/pdf_oxide.hpp>
auto doc = pdf_oxide::Document::open("annotated.pdf");
std::cout << "color=" << doc.annotation_get_color(0, 0)
<< " created=" << doc.annotation_get_creation_date(0, 0) << "\n";
std::cout << "hidden=" << doc.annotation_is_hidden(0, 0)
<< " printable=" << doc.annotation_is_printable(0, 0)
<< " readOnly=" << doc.annotation_is_read_only(0, 0) << "\n";
auto uri = doc.link_annotation_get_uri(0, 0);
if (!uri.empty()) std::cout << "link URI: " << uri << "\n";
auto icon = doc.text_annotation_get_icon_name(0, 0);
if (!icon.empty()) std::cout << "icon: " << icon << "\n";
Dart
import 'package:pdf_oxide/pdf_oxide.dart';
final doc = PdfDocument.open('annotated.pdf');
final a = doc.pageAnnotationDetails(0)[0]; // AnnotationDetails
print('color=${a.color} created=${a.creationDate} modified=${a.modificationDate}');
print('hidden=${a.hidden} printable=${a.printable} readOnly=${a.readOnly}');
if (a.linkUri.isNotEmpty) print('link URI: ${a.linkUri}');
if (a.iconName.isNotEmpty) print('icon: ${a.iconName}');
doc.close();
R
library(pdfoxide)
doc <- pdf_open("annotated.pdf")
cat(sprintf("color=%d created=%d\n",
pdf_annotation_get_color(doc, 0, 0),
pdf_annotation_get_creation_date(doc, 0, 0)))
cat(sprintf("hidden=%s printable=%s readOnly=%s\n",
pdf_annotation_is_hidden(doc, 0, 0),
pdf_annotation_is_printable(doc, 0, 0),
pdf_annotation_is_read_only(doc, 0, 0)))
uri <- pdf_link_annotation_get_uri(doc, 0, 0)
if (nzchar(uri)) cat(sprintf("link URI: %s\n", uri))
icon <- pdf_text_annotation_get_icon_name(doc, 0, 0)
if (nzchar(icon)) cat(sprintf("icon: %s\n", icon))
Julia
using PdfOxide
doc = open_document("annotated.pdf")
println("color=$(annotation_get_color(doc, 0, 0)) created=$(annotation_creation_date(doc, 0, 0))")
println("hidden=$(annotation_is_hidden(doc, 0, 0)) printable=$(annotation_is_printable(doc, 0, 0)) readOnly=$(annotation_is_read_only(doc, 0, 0))")
uri = link_annotation_uri(doc, 0, 0)
isempty(uri) || println("link URI: $uri")
icon = text_annotation_icon_name(doc, 0, 0)
isempty(icon) || println("icon: $icon")
Zig
const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;
var doc = try pdf_oxide.Document.open("annotated.pdf");
defer doc.deinit();
var list = try doc.annotationList(0);
defer list.deinit();
std.debug.print("color={d} created={d}\n", .{ try list.getColor(0), try list.getCreationDate(0) });
std.debug.print("hidden={} printable={} readOnly={}\n", .{ try list.isHidden(0), try list.isPrintable(0), try list.isReadOnly(0) });
const uri = try list.linkUri(a, 0);
defer a.free(uri);
if (uri.len != 0) std.debug.print("link URI: {s}\n", .{uri});
const icon = try list.textIconName(a, 0);
defer a.free(icon);
if (icon.len != 0) std.debug.print("icon: {s}\n", .{icon});
Objective-C
#import "POXPdfOxide.h"
NSError *err = nil;
POXDocument *doc = [POXDocument openPath:@"annotated.pdf" error:&err];
POXAnnotation *a = [doc pageAnnotations:0 error:&err].firstObject;
NSLog(@"color=%u created=%lld modified=%lld", a.color, a.creationDate, a.modificationDate);
NSLog(@"hidden=%d printable=%d readOnly=%d", a.hidden, a.printable, a.readOnly);
if (a.linkUri.length) NSLog(@"link URI: %@", a.linkUri);
if (a.iconName.length) NSLog(@"icon: %@", a.iconName);
Elixir
{:ok, doc} = PdfOxide.open("annotated.pdf")
{:ok, color} = PdfOxide.annotation_color(doc, 0, 0)
{:ok, created} = PdfOxide.annotation_creation_date(doc, 0, 0)
IO.puts("color=#{color} created=#{created}")
{:ok, hidden} = PdfOxide.annotation_hidden?(doc, 0, 0)
{:ok, printable} = PdfOxide.annotation_printable?(doc, 0, 0)
{:ok, read_only} = PdfOxide.annotation_read_only?(doc, 0, 0)
IO.puts("hidden=#{hidden} printable=#{printable} readOnly=#{read_only}")
{:ok, uri} = PdfOxide.link_annotation_uri(doc, 0, 0)
if uri != "", do: IO.puts("link URI: #{uri}")
{:ok, icon} = PdfOxide.text_annotation_icon_name(doc, 0, 0)
if icon != "", do: IO.puts("icon: #{icon}")
Поля AnnotationExtras (Swift)
| Поле | Тип | Описание |
|---|---|---|
color |
UInt32 |
Упакованный цвет аннотации |
creationDate |
Int64 |
Временна́я метка создания |
modificationDate |
Int64 |
Временна́я метка изменения |
hidden |
Bool |
Флаг hidden |
markedDeleted |
Bool |
Флаг marked-deleted |
printable |
Bool |
Флаг print |
readOnly |
Bool |
Флаг read-only |
uri |
String |
URI ссылки (пустая строка, если отсутствует) |
iconName |
String |
Имя иконки текстовой аннотации (пустая строка, если отсутствует) |
quadPoints |
[QuadPoint] |
Четырёхугольники выделения/разметки (4 угла каждый) |
Поддержка в биндингах.
annotation_extrasпредоставляется как специальная структураAnnotationExtrasв Swift (doc.annotationExtras(page, index:)) и через семейство C ABI-аксессоровpdf_oxide_annotation_get_*. То же семейство аксессоров по индексу обёрнуто в C++ (doc.annotation_get_*), R (pdf_annotation_get_*), Julia (annotation_*), Zig (AnnotationList.getColor/isHidden/...) и Elixir (PdfOxide.annotation_color/...). В Go, Dart (doc.pageAnnotationDetails(page)) и Objective-C (встроено вPOXAnnotation) те же атрибуты расположены непосредственно внутри каждой аннотации. Для WASM-таргета аксессоры исключаются из компиляции.
Расширенные примеры
Построение оглавления из закладок
use pdf_oxide::PdfDocument;
use pdf_oxide::outline::Destination;
let mut doc = PdfDocument::open("book.pdf")?;
fn print_toc(items: &[pdf_oxide::outline::OutlineItem], depth: usize) {
for item in items {
let indent = " ".repeat(depth);
let page = match &item.dest {
Some(Destination::PageIndex(p)) => format!("page {}", p + 1),
Some(Destination::Named(n)) => format!("dest '{}'", n),
None => "no dest".to_string(),
};
println!("{}{} ({})", indent, item.title, page);
print_toc(&item.children, depth + 1);
}
}
if let Some(outline) = doc.get_outline()? {
println!("Table of Contents:");
print_toc(&outline, 0);
}
Извлечение всех комментариев (аннотации типа Text)
use pdf_oxide::PdfDocument;
use pdf_oxide::annotation_types::AnnotationSubtype;
let mut doc = PdfDocument::open("reviewed.pdf")?;
let page_count = doc.page_count()?;
for page_idx in 0..page_count {
let annotations = doc.get_annotations(page_idx)?;
let comments: Vec<_> = annotations.iter()
.filter(|a| a.subtype_enum == AnnotationSubtype::Text)
.collect();
if !comments.is_empty() {
println!("Page {}:", page_idx + 1);
for c in &comments {
let author = c.author.as_deref().unwrap_or("Unknown");
let text = c.contents.as_deref().unwrap_or("");
println!(" [{}] {}", author, text);
}
}
}
Извлечение всех гиперссылок
use pdf_oxide::PdfDocument;
use pdf_oxide::annotation_types::AnnotationSubtype;
let mut doc = PdfDocument::open("report.pdf")?;
let annotations = doc.get_annotations(0)?;
let links: Vec<_> = annotations.iter()
.filter(|a| a.subtype_enum == AnnotationSubtype::Link)
.collect();
for link in &links {
if let Some(ref action) = link.action {
println!("Link: {:?}", action);
}
if let Some(ref dest) = link.destination {
println!("Internal link: {:?}", dest);
}
}
Часто задаваемые вопросы
В чём разница между get_annotations и annotation_extras?
get_annotations возвращает базовое представление аннотации (подтип, содержимое, прямоугольник, автор, даты, цвет, флаги). annotation_extras добавляет расширенные атрибуты — упакованный цвет, временны́е метки, четыре флага видимости, URI ссылки, имя иконки текстовой аннотации и координаты четырёхугольников выделения. В Go они объединены в одну структуру Annotation; в Swift — это отдельная структура AnnotationExtras.
Какова JSON-схема, генерируемая annotations_to_json?
JSON-массив, соответствующий структуре Annotation в Go: поля type, subtype, content, x, y, width, height, author, borderWidth, color, creationDate, modificationDate, linkURI, textIconName, isHidden, isPrintable, isReadOnly, isMarkedDeleted.
Почему URI ссылок и имена иконок иногда пустые?
Эти поля применимы только к определённым подтипам: uri — для аннотаций Link, iconName — для аннотаций Text (стикеров). Для остальных подтипов они возвращаются как пустые строки.
Насколько быстро работает извлечение аннотаций? Очень быстро. Ядро извлечения PDF Oxide демонстрирует на тестовом корпусе среднее время около 0,8 мс и p99 в 9 мс при 100% прохождении тестов.
Связанные страницы
- Извлечение данных форм – извлечение полей форм (аннотации Widget)
- Извлечение текста – извлечение текстового содержимого со страниц
- Метаданные и XMP – чтение свойств документа и закладок
- Извлечение изображений – встроенные изображения и аксессор элементов страницы