-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpage.go
156 lines (126 loc) · 3.61 KB
/
page.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package dossier
import (
"context"
"errors"
"math"
rtree "github.com/dhconnelly/rtreego"
"github.com/hansmi/dossier/pkg/content"
"github.com/hansmi/dossier/pkg/geometry"
"github.com/hansmi/dossier/pkg/renderformat"
)
func toRTreeRect(r geometry.Rect) (rtree.Rect, error) {
return rtree.NewRectFromPoints(
rtree.Point{r.Top.Pt(), r.Left.Pt()},
rtree.Point{r.Bottom.Pt(), r.Right.Pt()},
)
}
type spatialAdapter struct {
elem content.Element
bounds rtree.Rect
}
var _ rtree.Spatial = (*spatialAdapter)(nil)
func newAdapter(e content.Element) (rtree.Spatial, error) {
bounds, err := toRTreeRect(e.Bounds())
if err != nil {
return nil, err
}
return &spatialAdapter{
elem: e,
bounds: bounds,
}, nil
}
func (a *spatialAdapter) Bounds() rtree.Rect {
return a.bounds
}
var ErrStopVisitation = errors.New("stop visitation")
type PageElementVisitorFunc func(content.Element) error
// AsPageElementVisitor returns a visitor function wrapper filtering for
// elements of type T.
func AsPageElementVisitor[T content.Element](fn func(T) error) PageElementVisitorFunc {
return func(elem content.Element) error {
if v, ok := elem.(T); ok {
return fn(v)
}
return nil
}
}
type Page struct {
doc *Document
num int
size geometry.Size
elems []content.Element
tree *rtree.Rtree
}
func newPage(doc *Document, p content.Page) (*Page, error) {
objects := make([]rtree.Spatial, 0, len(p.Elements()))
for _, e := range p.Elements() {
obj, err := newAdapter(e)
if err != nil {
return nil, err
}
objects = append(objects, obj)
}
return &Page{
doc: doc,
num: p.Number(),
size: p.Size(),
elems: p.Elements(),
tree: rtree.NewTree(2, 2, 8, objects...),
}, nil
}
// Document returns the source document for the page.
func (p *Page) Document() *Document {
return p.doc
}
// 1-based page number.
func (p *Page) Number() int {
return p.num
}
// Physical page size.
func (p *Page) Size() geometry.Size {
return p.size
}
func (p *Page) visitElements(bounds rtree.Rect, visitor PageElementVisitorFunc) error {
var err error
p.tree.SearchIntersect(bounds, func(_ []rtree.Spatial, obj rtree.Spatial) (refuse, abort bool) {
// The filter function may still be called even after it requested the
// search to be aborted. This is an apparent bug in the rtreego
// upstream code. The condition on err avoids invoking the handler in
// such cases.
if err == nil {
err = visitor(obj.(*spatialAdapter).elem)
}
return true, (err != nil)
})
if errors.Is(err, ErrStopVisitation) {
err = nil
}
return err
}
// VisitElements invokes the visitor function for all elements. The visitation
// continues until either all elements have been visited or the visitor
// function returns a non-nil error. [ErrStopVisitation] stops the search
// immediately without failing the overall search. The visitation order is
// undefined.
func (p *Page) VisitElements(visitor PageElementVisitorFunc) error {
rbounds, err := rtree.NewRectFromPoints(
rtree.Point{-math.MaxFloat64, -math.MaxFloat64},
rtree.Point{math.MaxFloat64, math.MaxFloat64},
)
if err != nil {
return err
}
return p.visitElements(rbounds, visitor)
}
// VisitElementsIntersecting is like [VisitElements] with the additional
// restriction that only elements within the specified bounds are visited.
func (p *Page) VisitElementsIntersecting(bounds geometry.Rect, visitor PageElementVisitorFunc) error {
rbounds, err := toRTreeRect(bounds)
if err != nil {
return err
}
return p.visitElements(rbounds, visitor)
}
func (p *Page) RenderUsing(ctx context.Context, r renderformat.Renderer) error {
return p.doc.RenderPageUsing(ctx, p.num, r)
}