mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
299c2cbfdc
commit
8ddc2fdeed
7 files changed
+192
-109
No files matched your search
@@ -663,9 +663,19 @@ button.outline-link:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.page-surface {
|
.page-surface {
|
||||||
|
position: relative;
|
||||||
transform-origin: 0 0;
|
transform-origin: 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link:hover,
|
||||||
|
.page-link:focus-visible {
|
||||||
|
background: rgba(15, 123, 104, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.page-placeholder {
|
.page-placeholder {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
@@ -1591,6 +1591,29 @@ function mountPageSVG(index, page, openSeq = state.openSeq) {
|
|||||||
const svg = parseSVG(page.svg, `p${openSeq}-${index}`);
|
const svg = parseSVG(page.svg, `p${openSeq}-${index}`);
|
||||||
svg.classList.add("ofd-svg");
|
svg.classList.add("ofd-svg");
|
||||||
surface.replaceChildren(svg);
|
surface.replaceChildren(svg);
|
||||||
|
for (const link of page.links) {
|
||||||
|
let url;
|
||||||
|
try {
|
||||||
|
url = new URL(link.uri);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const anchor = document.createElement("a");
|
||||||
|
anchor.className = "page-link";
|
||||||
|
anchor.href = url.href;
|
||||||
|
anchor.target = "_blank";
|
||||||
|
anchor.rel = "noopener noreferrer";
|
||||||
|
anchor.title = url.href;
|
||||||
|
anchor.setAttribute("aria-label", url.href);
|
||||||
|
anchor.style.left = `${link.x / page.width * 100}%`;
|
||||||
|
anchor.style.top = `${link.y / page.height * 100}%`;
|
||||||
|
anchor.style.width = `${link.width / page.width * 100}%`;
|
||||||
|
anchor.style.height = `${link.height / page.height * 100}%`;
|
||||||
|
surface.append(anchor);
|
||||||
|
}
|
||||||
shell.classList.add("rendered");
|
shell.classList.add("rendered");
|
||||||
if (state.doc?.pages?.[index]) {
|
if (state.doc?.pages?.[index]) {
|
||||||
layoutPageShell(shell, state.doc.pages[index]);
|
layoutPageShell(shell, state.doc.pages[index]);
|
||||||
|
|||||||
@@ -160,6 +160,16 @@ func renderPage(args []js.Value) (any, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
links := make([]any, len(page.Links))
|
||||||
|
for i, link := range page.Links {
|
||||||
|
links[i] = map[string]any{
|
||||||
|
"uri": link.URI,
|
||||||
|
"x": link.Box.X,
|
||||||
|
"y": link.Box.Y,
|
||||||
|
"width": link.Box.W,
|
||||||
|
"height": link.Box.H,
|
||||||
|
}
|
||||||
|
}
|
||||||
return successResult(map[string]any{
|
return successResult(map[string]any{
|
||||||
"index": page.Index,
|
"index": page.Index,
|
||||||
"number": page.Number,
|
"number": page.Number,
|
||||||
@@ -167,6 +177,7 @@ func renderPage(args []js.Value) (any, error) {
|
|||||||
"width": page.Width,
|
"width": page.Width,
|
||||||
"height": page.Height,
|
"height": page.Height,
|
||||||
"svg": page.SVG,
|
"svg": page.SVG,
|
||||||
|
"links": links,
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-7
@@ -156,12 +156,13 @@ type PageInfo struct {
|
|||||||
|
|
||||||
// PageSVG 页面SVG结果
|
// PageSVG 页面SVG结果
|
||||||
type PageSVG struct {
|
type PageSVG struct {
|
||||||
Index int `json:"index"`
|
Index int `json:"index"`
|
||||||
Number int `json:"number"`
|
Number int `json:"number"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Width float64 `json:"width"`
|
Width float64 `json:"width"`
|
||||||
Height float64 `json:"height"`
|
Height float64 `json:"height"`
|
||||||
SVG string `json:"svg"`
|
SVG string `json:"svg"`
|
||||||
|
Links []ofdgo.PageLink `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportFormat 导出格式
|
// ExportFormat 导出格式
|
||||||
@@ -382,7 +383,11 @@ func (s *Session) RenderPageSVG(index int) (PageSVG, error) {
|
|||||||
if err := s.Renderer.RenderToSVG(page, &buf); err != nil {
|
if err := s.Renderer.RenderToSVG(page, &buf); err != nil {
|
||||||
return PageSVG{}, err
|
return PageSVG{}, err
|
||||||
}
|
}
|
||||||
return PageSVG{Index: index, Number: index + 1, ID: pageRef.ID, Width: box.W, Height: box.H, SVG: buf.String()}, nil
|
links, err := s.Renderer.PageLinks(page)
|
||||||
|
if err != nil {
|
||||||
|
return PageSVG{}, err
|
||||||
|
}
|
||||||
|
return PageSVG{Index: index, Number: index + 1, ID: pageRef.ID, Width: box.W, Height: box.H, SVG: buf.String(), Links: links}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportPage 导出单页
|
// ExportPage 导出单页
|
||||||
|
|||||||
+109
@@ -16,6 +16,7 @@ package ofdgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,6 +59,12 @@ type URI struct {
|
|||||||
Base string `xml:"Base,attr"`
|
Base string `xml:"Base,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PageLink 页面矩形外链,Box使用页面毫米坐标
|
||||||
|
type PageLink struct {
|
||||||
|
URI string
|
||||||
|
Box Box
|
||||||
|
}
|
||||||
|
|
||||||
// GotoA 附件动作
|
// GotoA 附件动作
|
||||||
type GotoA struct {
|
type GotoA struct {
|
||||||
AttachID string `xml:"AttachID,attr"`
|
AttachID string `xml:"AttachID,attr"`
|
||||||
@@ -102,6 +109,12 @@ type RegionCommand struct {
|
|||||||
EndPoint string
|
EndPoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// actionSource 动作来源
|
||||||
|
type actionSource struct {
|
||||||
|
Box Box
|
||||||
|
Actions []Action
|
||||||
|
}
|
||||||
|
|
||||||
// gotoDest 获取文档内跳转目标
|
// gotoDest 获取文档内跳转目标
|
||||||
// 入参: action 跳转动作, bookmarks 书签
|
// 入参: action 跳转动作, bookmarks 书签
|
||||||
// 返回: *Dest 跳转目标
|
// 返回: *Dest 跳转目标
|
||||||
@@ -211,3 +224,99 @@ func actionFloatAttr(start xml.StartElement, name string) float64 {
|
|||||||
value, _ := strconv.ParseFloat(attrValue(start, name), 64)
|
value, _ := strconv.ParseFloat(attrValue(start, name), 64)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pageActionSources 获取页面动作来源
|
||||||
|
// 入参: page 页面内容, box 页面区域
|
||||||
|
// 返回: []actionSource 动作来源
|
||||||
|
func pageActionSources(page *PageContent, box Box) []actionSource {
|
||||||
|
sources := make([]actionSource, 0)
|
||||||
|
if len(page.Actions) > 0 {
|
||||||
|
sources = append(sources, actionSource{
|
||||||
|
Box: Box{W: box.W, H: box.H},
|
||||||
|
Actions: page.Actions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, layer := range page.Content.Layer {
|
||||||
|
for _, object := range layer.Objects {
|
||||||
|
sources = appendGraphicActionSources(sources, object, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
// annotationActionSources 获取注释动作来源
|
||||||
|
// 入参: annotations 页面注释
|
||||||
|
// 返回: []actionSource 动作来源
|
||||||
|
func annotationActionSources(annotations []Annotation) []actionSource {
|
||||||
|
sources := make([]actionSource, 0)
|
||||||
|
for _, annotation := range annotations {
|
||||||
|
if annotation.Visible != nil && !*annotation.Visible {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
box, err := ParseBox(annotation.Appearance.Boundary)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, object := range annotation.Appearance.Objects {
|
||||||
|
sources = appendGraphicActionSources(sources, object, &box)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendGraphicActionSources 添加图形对象动作来源
|
||||||
|
// 入参: sources 动作来源, object 图形对象, box 指定动作区域
|
||||||
|
// 返回: []actionSource 动作来源
|
||||||
|
func appendGraphicActionSources(sources []actionSource, object GraphicObject, box *Box) []actionSource {
|
||||||
|
var boundary string
|
||||||
|
var actions []Action
|
||||||
|
var children []GraphicObject
|
||||||
|
switch object.Type {
|
||||||
|
case "TextObject":
|
||||||
|
boundary = object.TextObject.Boundary
|
||||||
|
actions = object.TextObject.Actions
|
||||||
|
case "PathObject":
|
||||||
|
boundary = object.PathObject.Boundary
|
||||||
|
actions = object.PathObject.Actions
|
||||||
|
case "ImageObject":
|
||||||
|
boundary = object.ImageObject.Boundary
|
||||||
|
actions = object.ImageObject.Actions
|
||||||
|
case "CompositeGraphicUnit", "CompositeObject":
|
||||||
|
boundary = object.CompositeGraphicUnit.Boundary
|
||||||
|
actions = object.CompositeGraphicUnit.Actions
|
||||||
|
children = object.CompositeGraphicUnit.Objects
|
||||||
|
}
|
||||||
|
if len(actions) > 0 {
|
||||||
|
sourceBox := box
|
||||||
|
if sourceBox == nil && boundary != "" {
|
||||||
|
if value, err := ParseBox(boundary); err == nil {
|
||||||
|
sourceBox = &value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sourceBox != nil {
|
||||||
|
sources = append(sources, actionSource{Box: *sourceBox, Actions: actions})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, child := range children {
|
||||||
|
sources = appendGraphicActionSources(sources, child, box)
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveActionURI 解析URI动作地址
|
||||||
|
// 入参: action URI动作
|
||||||
|
// 返回: string URI地址
|
||||||
|
func resolveActionURI(action URI) string {
|
||||||
|
if action.Base == "" {
|
||||||
|
return action.URI
|
||||||
|
}
|
||||||
|
base, err := url.Parse(action.Base)
|
||||||
|
if err != nil {
|
||||||
|
return action.URI
|
||||||
|
}
|
||||||
|
target, err := url.Parse(action.URI)
|
||||||
|
if err != nil {
|
||||||
|
return action.URI
|
||||||
|
}
|
||||||
|
return base.ResolveReference(target).String()
|
||||||
|
}
|
||||||
@@ -164,6 +164,32 @@ func (r *Renderer) renderPageToContext(ctx *canvas.Context, page *PageContent, d
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PageLinks 获取页面及可见注释的矩形外链,不包含自定义Region
|
||||||
|
// 入参: page 页面内容
|
||||||
|
// 返回: []PageLink 页面外链, error 错误信息
|
||||||
|
func (r *Renderer) PageLinks(page *PageContent) ([]PageLink, error) {
|
||||||
|
box, err := r.GetPageBox(page)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sources := pageActionSources(page, box)
|
||||||
|
if r.RenderAnnotations {
|
||||||
|
sources = append(sources, annotationActionSources(r.Reader.Annots[page.ID])...)
|
||||||
|
}
|
||||||
|
var links []PageLink
|
||||||
|
for _, source := range sources {
|
||||||
|
if source.Box.W <= 0 || source.Box.H <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, action := range source.Actions {
|
||||||
|
if action.Event == "CLICK" && action.URI != nil && action.URI.URI != "" && action.Region == nil {
|
||||||
|
links = append(links, PageLink{URI: resolveActionURI(*action.URI), Box: source.Box})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RenderPageByIndex 按索引渲染页面
|
// RenderPageByIndex 按索引渲染页面
|
||||||
// 入参: index 页面索引
|
// 入参: index 页面索引
|
||||||
// 返回: *canvas.Canvas 画布实例, error 错误信息
|
// 返回: *canvas.Canvas 画布实例, error 错误信息
|
||||||
|
|||||||
+1
-102
@@ -16,7 +16,6 @@ package ofdgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
"github.com/tdewolff/canvas"
|
||||||
"github.com/tdewolff/canvas/renderers/pdf"
|
"github.com/tdewolff/canvas/renderers/pdf"
|
||||||
@@ -55,12 +54,6 @@ type pdfOutline struct {
|
|||||||
Y float64
|
Y float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// pdfActionSource PDF动作来源
|
|
||||||
type pdfActionSource struct {
|
|
||||||
Box Box
|
|
||||||
Actions []Action
|
|
||||||
}
|
|
||||||
|
|
||||||
// newPDFNavigation 创建PDF导航信息
|
// newPDFNavigation 创建PDF导航信息
|
||||||
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
|
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
|
||||||
// 返回: *pdfNavigation PDF导航信息
|
// 返回: *pdfNavigation PDF导航信息
|
||||||
@@ -81,7 +74,7 @@ func newPDFNavigation(renderer *Renderer, doc *Document, pages []pdfPage) *pdfNa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, page := range pages {
|
for i, page := range pages {
|
||||||
sources := pageActionSources(page)
|
sources := pageActionSources(page.Content, page.Box)
|
||||||
if renderer.RenderAnnotations {
|
if renderer.RenderAnnotations {
|
||||||
sources = append(sources, annotationActionSources(renderer.Reader.Annots[page.Content.ID])...)
|
sources = append(sources, annotationActionSources(renderer.Reader.Annots[page.Content.ID])...)
|
||||||
}
|
}
|
||||||
@@ -159,82 +152,6 @@ func (n *pdfNavigation) apply(renderer *pdf.PDF, page int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pageActionSources 获取页面动作来源
|
|
||||||
// 入参: page 页面数据
|
|
||||||
// 返回: []pdfActionSource 动作来源
|
|
||||||
func pageActionSources(page pdfPage) []pdfActionSource {
|
|
||||||
sources := make([]pdfActionSource, 0)
|
|
||||||
if len(page.Content.Actions) > 0 {
|
|
||||||
sources = append(sources, pdfActionSource{
|
|
||||||
Box: Box{W: page.Box.W, H: page.Box.H},
|
|
||||||
Actions: page.Content.Actions,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, layer := range page.Content.Content.Layer {
|
|
||||||
for _, object := range layer.Objects {
|
|
||||||
sources = appendGraphicActionSources(sources, object, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources
|
|
||||||
}
|
|
||||||
|
|
||||||
// annotationActionSources 获取注释动作来源
|
|
||||||
// 入参: annotations 页面注释
|
|
||||||
// 返回: []pdfActionSource 动作来源
|
|
||||||
func annotationActionSources(annotations []Annotation) []pdfActionSource {
|
|
||||||
sources := make([]pdfActionSource, 0)
|
|
||||||
for _, annotation := range annotations {
|
|
||||||
if annotation.Visible != nil && !*annotation.Visible {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
box, err := ParseBox(annotation.Appearance.Boundary)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, object := range annotation.Appearance.Objects {
|
|
||||||
sources = appendGraphicActionSources(sources, object, &box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendGraphicActionSources 添加图形对象动作来源
|
|
||||||
// 入参: sources 动作来源, object 图形对象, box 指定动作区域
|
|
||||||
// 返回: []pdfActionSource 动作来源
|
|
||||||
func appendGraphicActionSources(sources []pdfActionSource, object GraphicObject, box *Box) []pdfActionSource {
|
|
||||||
var boundary string
|
|
||||||
var actions []Action
|
|
||||||
var children []GraphicObject
|
|
||||||
switch object.Type {
|
|
||||||
case "TextObject":
|
|
||||||
boundary = object.TextObject.Boundary
|
|
||||||
actions = object.TextObject.Actions
|
|
||||||
case "PathObject":
|
|
||||||
boundary = object.PathObject.Boundary
|
|
||||||
actions = object.PathObject.Actions
|
|
||||||
case "ImageObject":
|
|
||||||
boundary = object.ImageObject.Boundary
|
|
||||||
actions = object.ImageObject.Actions
|
|
||||||
case "CompositeGraphicUnit", "CompositeObject":
|
|
||||||
boundary = object.CompositeGraphicUnit.Boundary
|
|
||||||
actions = object.CompositeGraphicUnit.Actions
|
|
||||||
children = object.CompositeGraphicUnit.Objects
|
|
||||||
}
|
|
||||||
sourceBox := box
|
|
||||||
if sourceBox == nil && boundary != "" {
|
|
||||||
if value, err := ParseBox(boundary); err == nil {
|
|
||||||
sourceBox = &value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if sourceBox != nil && len(actions) > 0 {
|
|
||||||
sources = append(sources, pdfActionSource{Box: *sourceBox, Actions: actions})
|
|
||||||
}
|
|
||||||
for _, child := range children {
|
|
||||||
sources = appendGraphicActionSources(sources, child, box)
|
|
||||||
}
|
|
||||||
return sources
|
|
||||||
}
|
|
||||||
|
|
||||||
// outlineDest 获取大纲跳转目标
|
// outlineDest 获取大纲跳转目标
|
||||||
// 入参: outline 大纲节点, bookmarks 书签
|
// 入参: outline 大纲节点, bookmarks 书签
|
||||||
// 返回: *Dest 跳转目标
|
// 返回: *Dest 跳转目标
|
||||||
@@ -254,24 +171,6 @@ func outlineDest(outline OutlineElem, bookmarks map[string]Dest) *Dest {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveActionURI 解析URI动作地址
|
|
||||||
// 入参: action URI动作
|
|
||||||
// 返回: string URI地址
|
|
||||||
func resolveActionURI(action URI) string {
|
|
||||||
if action.Base == "" {
|
|
||||||
return action.URI
|
|
||||||
}
|
|
||||||
base, err := url.Parse(action.Base)
|
|
||||||
if err != nil {
|
|
||||||
return action.URI
|
|
||||||
}
|
|
||||||
target, err := url.Parse(action.URI)
|
|
||||||
if err != nil {
|
|
||||||
return action.URI
|
|
||||||
}
|
|
||||||
return base.ResolveReference(target).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdfSourceRect 转换PDF动作区域
|
// pdfSourceRect 转换PDF动作区域
|
||||||
// 入参: box OFD区域, pageH 页面高度
|
// 入参: box OFD区域, pageH 页面高度
|
||||||
// 返回: canvas.Rect PDF区域
|
// 返回: canvas.Rect PDF区域
|
||||||
|
|||||||
Reference in new issue
Block a user