mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-27 13:48:51 +08:00
Merge pull request #233 from adrg/improve-outline-dest-parsing
Improve outline destination parsing
This commit is contained in:
commit
843a48ed7b
115
model/outline.go
115
model/outline.go
@ -6,6 +6,10 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/unidoc/unipdf/v3/common"
|
||||||
"github.com/unidoc/unipdf/v3/core"
|
"github.com/unidoc/unipdf/v3/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,8 +17,10 @@ import (
|
|||||||
// It holds the page and the position on the page an outline item points to.
|
// It holds the page and the position on the page an outline item points to.
|
||||||
type OutlineDest struct {
|
type OutlineDest struct {
|
||||||
Page int64 `json:"page"`
|
Page int64 `json:"page"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
X float64 `json:"x"`
|
X float64 `json:"x"`
|
||||||
Y float64 `json:"y"`
|
Y float64 `json:"y"`
|
||||||
|
Zoom float64 `json:"zoom"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutlineDest returns a new outline destination which can be used
|
// NewOutlineDest returns a new outline destination which can be used
|
||||||
@ -22,6 +28,7 @@ type OutlineDest struct {
|
|||||||
func NewOutlineDest(page int64, x, y float64) OutlineDest {
|
func NewOutlineDest(page int64, x, y float64) OutlineDest {
|
||||||
return OutlineDest{
|
return OutlineDest{
|
||||||
Page: page,
|
Page: page,
|
||||||
|
Mode: "XYZ",
|
||||||
X: x,
|
X: x,
|
||||||
Y: y,
|
Y: y,
|
||||||
}
|
}
|
||||||
@ -29,29 +36,21 @@ func NewOutlineDest(page int64, x, y float64) OutlineDest {
|
|||||||
|
|
||||||
// newOutlineDestFromPdfObject creates a new outline destination from the
|
// newOutlineDestFromPdfObject creates a new outline destination from the
|
||||||
// specified PDF object.
|
// specified PDF object.
|
||||||
func newOutlineDestFromPdfObject(o core.PdfObject, r *PdfReader) OutlineDest {
|
func newOutlineDestFromPdfObject(o core.PdfObject, r *PdfReader) (*OutlineDest, error) {
|
||||||
dest := OutlineDest{}
|
// Validate input PDF object.
|
||||||
|
|
||||||
destArr, ok := core.GetArray(o)
|
destArr, ok := core.GetArray(o)
|
||||||
if !ok {
|
if !ok {
|
||||||
return dest
|
return nil, errors.New("outline destination object must be an array")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Covered destination formats:
|
|
||||||
// [pageObj|pageNum /Fit]
|
|
||||||
// [pageObj|pageNum /FitB]
|
|
||||||
// [pageObj|pageNum /FitH top]
|
|
||||||
// [pageObj|pageNum /FitV left]
|
|
||||||
// [pageObj|pageNum /FitBH top]
|
|
||||||
// [pageObj|pageNum /XYZ x y zoom]
|
|
||||||
// [pageObj|pageNum /FitR left bottom right top]
|
|
||||||
// See section 12.3.2.2 "Explicit Destinations" (page 374).
|
|
||||||
destArrLen := destArr.Len()
|
destArrLen := destArr.Len()
|
||||||
if destArrLen < 2 {
|
if destArrLen < 2 {
|
||||||
return dest
|
return nil, fmt.Errorf("invalid outline destination array length: %d", destArrLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract page number.
|
// Extract page number.
|
||||||
|
dest := &OutlineDest{Mode: "Fit"}
|
||||||
|
|
||||||
pageObj := destArr.Get(0)
|
pageObj := destArr.Get(0)
|
||||||
if pageInd, ok := core.GetIndirect(pageObj); ok {
|
if pageInd, ok := core.GetIndirect(pageObj); ok {
|
||||||
// Page object is provided. Identify page number using the reader.
|
// Page object is provided. Identify page number using the reader.
|
||||||
@ -61,34 +60,90 @@ func newOutlineDestFromPdfObject(o core.PdfObject, r *PdfReader) OutlineDest {
|
|||||||
} else if pageNum, ok := core.GetIntVal(pageObj); ok {
|
} else if pageNum, ok := core.GetIntVal(pageObj); ok {
|
||||||
// Page number is provided.
|
// Page number is provided.
|
||||||
dest.Page = int64(pageNum)
|
dest.Page = int64(pageNum)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("invalid outline destination page: %T", pageObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract destination coordinates.
|
// Extract magnification mode.
|
||||||
if destArrLen == 5 {
|
mode, ok := core.GetNameVal(destArr.Get(1))
|
||||||
if xyz, ok := core.GetName(destArr.Get(1)); ok && xyz.String() == "XYZ" {
|
if !ok {
|
||||||
|
common.Log.Debug("invalid outline destination magnification mode: %v", destArr.Get(1))
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse magnification mode parameters.
|
||||||
|
// See section 12.3.2.2 "Explicit Destinations" (page 374).
|
||||||
|
switch mode {
|
||||||
|
// [pageObj|pageNum /Fit]
|
||||||
|
// [pageObj|pageNum /FitB]
|
||||||
|
case "Fit", "FitB":
|
||||||
|
// [pageObj|pageNum /FitH top]
|
||||||
|
// [pageObj|pageNum /FitBH top]
|
||||||
|
case "FitH", "FitBH":
|
||||||
|
if destArrLen > 2 {
|
||||||
|
dest.Y, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
|
||||||
|
}
|
||||||
|
// [pageObj|pageNum /FitV left]
|
||||||
|
// [pageObj|pageNum /FitBV left]
|
||||||
|
case "FitV", "FitBV":
|
||||||
|
if destArrLen > 2 {
|
||||||
|
dest.X, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
|
||||||
|
}
|
||||||
|
// [pageObj|pageNum /XYZ x y zoom]
|
||||||
|
case "XYZ":
|
||||||
|
if destArrLen > 4 {
|
||||||
dest.X, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
|
dest.X, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(2)))
|
||||||
dest.Y, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(3)))
|
dest.Y, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(3)))
|
||||||
|
dest.Zoom, _ = core.GetNumberAsFloat(core.TraceToDirectObject(destArr.Get(4)))
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
mode = "Fit"
|
||||||
|
}
|
||||||
|
|
||||||
|
dest.Mode = mode
|
||||||
|
return dest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToPdfObject returns a PDF object representation of the outline destination.
|
||||||
|
func (od OutlineDest) ToPdfObject() core.PdfObject {
|
||||||
|
if od.Page < 0 || od.Mode == "" {
|
||||||
|
return core.MakeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := core.MakeArray(
|
||||||
|
core.MakeInteger(od.Page),
|
||||||
|
core.MakeName(od.Mode),
|
||||||
|
)
|
||||||
|
|
||||||
|
// See section 12.3.2.2 "Explicit Destinations" (page 374).
|
||||||
|
switch od.Mode {
|
||||||
|
// [pageObj|pageNum /Fit]
|
||||||
|
// [pageObj|pageNum /FitB]
|
||||||
|
case "Fit", "FitB":
|
||||||
|
// [pageObj|pageNum /FitH top]
|
||||||
|
// [pageObj|pageNum /FitBH top]
|
||||||
|
case "FitH", "FitBH":
|
||||||
|
dest.Append(core.MakeFloat(od.Y))
|
||||||
|
// [pageObj|pageNum /FitV left]
|
||||||
|
// [pageObj|pageNum /FitBV left]
|
||||||
|
case "FitV", "FitBV":
|
||||||
|
dest.Append(core.MakeFloat(od.X))
|
||||||
|
// [pageObj|pageNum /XYZ x y zoom]
|
||||||
|
case "XYZ":
|
||||||
|
dest.Append(core.MakeFloat(od.X))
|
||||||
|
dest.Append(core.MakeFloat(od.Y))
|
||||||
|
dest.Append(core.MakeFloat(od.Zoom))
|
||||||
|
default:
|
||||||
|
dest.Set(1, core.MakeName("Fit"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return dest
|
return dest
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToPdfObject returns a PDF object representation of the outline destination.
|
|
||||||
func (od OutlineDest) ToPdfObject() core.PdfObject {
|
|
||||||
return core.MakeArray(
|
|
||||||
core.MakeInteger(od.Page),
|
|
||||||
core.MakeName("XYZ"),
|
|
||||||
core.MakeFloat(od.X),
|
|
||||||
core.MakeFloat(od.Y),
|
|
||||||
core.MakeFloat(0),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outline represents a PDF outline dictionary (Table 152 - p. 376).
|
// Outline represents a PDF outline dictionary (Table 152 - p. 376).
|
||||||
// Currently, the Outline object can only be used to construct PDF outlines.
|
// Currently, the Outline object can only be used to construct PDF outlines.
|
||||||
type Outline struct {
|
type Outline struct {
|
||||||
Entries []*OutlineItem `json:"entries"`
|
Entries []*OutlineItem `json:"entries,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutline returns a new outline instance.
|
// NewOutline returns a new outline instance.
|
||||||
@ -166,7 +221,7 @@ func (o *Outline) ToPdfObject() core.PdfObject {
|
|||||||
type OutlineItem struct {
|
type OutlineItem struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Dest OutlineDest `json:"dest"`
|
Dest OutlineDest `json:"dest"`
|
||||||
Entries []*OutlineItem `json:"entries"`
|
Entries []*OutlineItem `json:"entries,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOutlineItem returns a new outline item instance.
|
// NewOutlineItem returns a new outline item instance.
|
||||||
|
@ -69,8 +69,5 @@ func TestGetOutlines(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
dstJson, err := json.Marshal(dstOutline)
|
dstJson, err := json.Marshal(dstOutline)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Log(string(srcJson))
|
|
||||||
t.Log(string(dstJson))
|
|
||||||
require.Equal(t, srcJson, dstJson)
|
require.Equal(t, srcJson, dstJson)
|
||||||
}
|
}
|
||||||
|
@ -463,8 +463,25 @@ func (r *PdfReader) GetOutlines() (*Outline, error) {
|
|||||||
// Check if node is an outline item.
|
// Check if node is an outline item.
|
||||||
var entry *OutlineItem
|
var entry *OutlineItem
|
||||||
if item, ok := node.context.(*PdfOutlineItem); ok {
|
if item, ok := node.context.(*PdfOutlineItem); ok {
|
||||||
entry = NewOutlineItem(item.Title.Decoded(),
|
// Search for outline destination object.
|
||||||
newOutlineDestFromPdfObject(item.Dest, r))
|
destObj := item.Dest
|
||||||
|
if (destObj == nil || core.IsNullObject(destObj)) && item.A != nil {
|
||||||
|
if actionDict, ok := core.GetDict(item.A); ok {
|
||||||
|
destObj, _ = core.GetArray(actionDict.Get("D"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse outline destination object.
|
||||||
|
var dest OutlineDest
|
||||||
|
if destObj != nil && !core.IsNullObject(destObj) {
|
||||||
|
if d, err := newOutlineDestFromPdfObject(destObj, r); err == nil {
|
||||||
|
dest = *d
|
||||||
|
} else {
|
||||||
|
common.Log.Debug("WARN: could not parse outline dest (%v): %v", destObj, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry = NewOutlineItem(item.Title.Decoded(), dest)
|
||||||
*entries = append(*entries, entry)
|
*entries = append(*entries, entry)
|
||||||
|
|
||||||
// Traverse next node.
|
// Traverse next node.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user