P5a review: outlines from named destinations; pool race; fast over-limit; revalidation under the limiter; Accept pdf
- pdftext/outline: resolve named destinations through the catalog /Dests and the /Names /Dests tree, resolve indirect /Dest, /A and /Title, and treat reader.Null (what Dict.Get returns for a missing key) as absent. Before this every LaTeX and Acrobat PDF had an empty outline. pdftest gains NamedDests; TestOutlineNamedDestinations. - api: pool() always goes through its Once (data race on concurrent first use); TestDefaultPoolConcurrentFirstUse. Revalidation of a stale cache entry counts against the caller/host limit. - pdftext/runner: output over MaxText kills the child at once instead of waiting for the deadline; TestRunnerTooMuchTextFailsFast. - fetch: Accept includes application/pdf;q=0.8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,10 @@ type Doc struct {
|
||||
Pages []string
|
||||
Outline []Outline
|
||||
Scanned []int
|
||||
// NamedDests writes the outline the way LaTeX's hyperref and Acrobat do: each item is a
|
||||
// /GoTo action to a named destination (resolved through the catalog's /Names /Dests tree)
|
||||
// and its /Title is an indirect object. Without it items carry a direct /Dest array.
|
||||
NamedDests bool
|
||||
}
|
||||
|
||||
// Build returns the PDF bytes for d. It panics on a Doc it cannot express (an outline entry
|
||||
@@ -51,9 +55,15 @@ func Build(d Doc) []byte {
|
||||
scanned[p] = true
|
||||
}
|
||||
|
||||
// With NamedDests the outline items are followed by one title object per item and then
|
||||
// the name tree, so the numbers below stay fixed by position.
|
||||
nameTree := outlineRoot + 1 + 2*len(d.Outline)
|
||||
catalog := "<< /Type /Catalog /Pages 2 0 R"
|
||||
if len(d.Outline) > 0 {
|
||||
catalog += fmt.Sprintf(" /Outlines %d 0 R /PageMode /UseOutlines", outlineRoot)
|
||||
if d.NamedDests {
|
||||
catalog += fmt.Sprintf(" /Names << /Dests %d 0 R >>", nameTree)
|
||||
}
|
||||
}
|
||||
obj(catalog + " >>")
|
||||
var kids []string
|
||||
@@ -93,6 +103,9 @@ func Build(d Doc) []byte {
|
||||
}
|
||||
n := first + i
|
||||
item := fmt.Sprintf("<< /Title (%s) /Parent %d 0 R /Dest [%d 0 R /XYZ null null null]", esc(o.Title), outlineRoot, pageObj(o.Page-1))
|
||||
if d.NamedDests {
|
||||
item = fmt.Sprintf("<< /Title %d 0 R /Parent %d 0 R /A << /S /GoTo /D (dest.%d) >>", last+1+i, outlineRoot, i)
|
||||
}
|
||||
if i > 0 {
|
||||
item += fmt.Sprintf(" /Prev %d 0 R", n-1)
|
||||
}
|
||||
@@ -101,6 +114,14 @@ func Build(d Doc) []byte {
|
||||
}
|
||||
obj(item + " >>")
|
||||
}
|
||||
if d.NamedDests {
|
||||
var names []string
|
||||
for i, o := range d.Outline {
|
||||
obj(fmt.Sprintf("(%s)", esc(o.Title)))
|
||||
names = append(names, fmt.Sprintf("(dest.%d) [%d 0 R /XYZ null null null]", i, pageObj(o.Page-1)))
|
||||
}
|
||||
obj(fmt.Sprintf("<< /Names [%s] >>", strings.Join(names, " ")))
|
||||
}
|
||||
}
|
||||
|
||||
xref := b.Len()
|
||||
|
||||
Reference in New Issue
Block a user