mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
b85b3bc27d
commit
bbc8d59bdb
11 files changed
+153
-27
No files matched your search
+20
-2
@@ -4996,6 +4996,13 @@ function destinationPoint(page, x, y) {
|
|||||||
return { x, y };
|
return { x, y };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sourcePoint(page, x, y) {
|
||||||
|
if (state.rotation === 90) return { x: y, y: page.height - x };
|
||||||
|
if (state.rotation === 180) return { x: page.width - x, y: page.height - y };
|
||||||
|
if (state.rotation === 270) return { x: page.width - y, y: x };
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
async function navigateDestination(dest) {
|
async function navigateDestination(dest) {
|
||||||
if (document.body.hasAttribute("aria-busy")) return;
|
if (document.body.hasAttribute("aria-busy")) return;
|
||||||
const index = state.doc.pages.findIndex(page => page.id === dest.pageID);
|
const index = state.doc.pages.findIndex(page => page.id === dest.pageID);
|
||||||
@@ -5008,11 +5015,22 @@ async function navigateDestination(dest) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const seq = state.openSeq, scale = state.scale;
|
const seq = state.openSeq, scale = state.scale;
|
||||||
|
let retained = null;
|
||||||
|
if (dest.omitLeft || dest.omitTop) {
|
||||||
|
const current = state.doc.pages[state.pageIndex], shell = pageShell(state.pageIndex)?.getBoundingClientRect();
|
||||||
|
if (current && shell) {
|
||||||
|
const viewer = el.viewerPanel.getBoundingClientRect();
|
||||||
|
retained = sourcePoint(current,
|
||||||
|
(viewer.left + pageSpace() - shell.left) / (MM_TO_PX * scale),
|
||||||
|
(viewer.top + pageBlockSpace() - shell.top) / (MM_TO_PX * scale));
|
||||||
|
}
|
||||||
|
}
|
||||||
await renderPage(index, { fit: false, scroll: false });
|
await renderPage(index, { fit: false, scroll: false });
|
||||||
if (seq !== state.openSeq || state.pageIndex !== index) return;
|
if (seq !== state.openSeq || state.pageIndex !== index) return;
|
||||||
const page = state.doc.pages[index], size = pageViewSize(page), space = pageSpace();
|
const page = state.doc.pages[index], size = pageViewSize(page), space = pageSpace();
|
||||||
const width = el.viewerPanel.clientWidth - space * 2, height = el.viewerPanel.clientHeight - space * 2;
|
const width = el.viewerPanel.clientWidth - space * 2, height = el.viewerPanel.clientHeight - space * 2;
|
||||||
let left = dest.left, top = dest.top;
|
let left = dest.omitLeft && retained ? retained.x : dest.left;
|
||||||
|
let top = dest.omitTop && retained ? retained.y : dest.top;
|
||||||
if (dest.type === "Fit") {
|
if (dest.type === "Fit") {
|
||||||
fitHeight(false);
|
fitHeight(false);
|
||||||
scrollToPage(index);
|
scrollToPage(index);
|
||||||
@@ -5028,7 +5046,7 @@ async function navigateDestination(dest) {
|
|||||||
if (dest.right <= dest.left || dest.bottom <= dest.top) return;
|
if (dest.right <= dest.left || dest.bottom <= dest.top) return;
|
||||||
setScale(Math.min(width / Math.abs(b.x - a.x), height / Math.abs(b.y - a.y)) / MM_TO_PX, false);
|
setScale(Math.min(width / Math.abs(b.x - a.x), height / Math.abs(b.y - a.y)) / MM_TO_PX, false);
|
||||||
} else {
|
} else {
|
||||||
setScale(dest.zoom > 0 ? dest.zoom : scale, false);
|
setScale(!dest.omitZoom && dest.zoom > 0 ? dest.zoom : scale, false);
|
||||||
}
|
}
|
||||||
const point = destinationPoint(page, left, top);
|
const point = destinationPoint(page, left, top);
|
||||||
if (dest.type === "FitR") {
|
if (dest.type === "FitR") {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ require (
|
|||||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1
|
github.com/xiaoqidun/jbig2 v1.1.1
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20260924162208-698614ef984e
|
github.com/xiaoqidun/pdfgo v0.0.0-20260925061819-c89bfccf349d
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/text v0.42.0
|
golang.org/x/text v0.42.0
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
|||||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20260924162208-698614ef984e h1:17a5JOpYM6gwXZnhapgrXA6H7LhtGVrF0rpeiEORYUA=
|
github.com/xiaoqidun/pdfgo v0.0.0-20260925061819-c89bfccf349d h1:hjdo01d4EsiNe9l0JNyc+Bqfrvdj9XGG786EYxAx3yw=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20260924162208-698614ef984e/go.mod h1:/D0XHgfZ7xNhix95swpNaThpuqoJjVR5wIadogtR5pI=
|
github.com/xiaoqidun/pdfgo v0.0.0-20260925061819-c89bfccf349d/go.mod h1:/D0XHgfZ7xNhix95swpNaThpuqoJjVR5wIadogtR5pI=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
@@ -529,7 +529,8 @@ func renderPage(args []js.Value) (any, error) {
|
|||||||
}
|
}
|
||||||
if dest := link.Dest; dest != nil {
|
if dest := link.Dest; dest != nil {
|
||||||
item["dest"] = map[string]any{"type": dest.Type, "pageID": dest.PageID, "left": dest.Left, "top": dest.Top,
|
item["dest"] = map[string]any{"type": dest.Type, "pageID": dest.PageID, "left": dest.Left, "top": dest.Top,
|
||||||
"right": dest.Right, "bottom": dest.Bottom, "zoom": dest.Zoom}
|
"right": dest.Right, "bottom": dest.Bottom, "zoom": dest.Zoom,
|
||||||
|
"omitLeft": dest.OmitLeft, "omitTop": dest.OmitTop, "omitZoom": dest.OmitZoom}
|
||||||
}
|
}
|
||||||
links[i] = item
|
links[i] = item
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ type Dest struct {
|
|||||||
Top float64 `xml:"Top"`
|
Top float64 `xml:"Top"`
|
||||||
Bottom float64 `xml:"Bottom"`
|
Bottom float64 `xml:"Bottom"`
|
||||||
Zoom float64 `xml:"Zoom"`
|
Zoom float64 `xml:"Zoom"`
|
||||||
|
OmitLeft bool `xml:"-"`
|
||||||
|
OmitTop bool `xml:"-"`
|
||||||
|
OmitZoom bool `xml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GotoBookmark 书签跳转目标
|
// GotoBookmark 书签跳转目标
|
||||||
@@ -151,6 +154,17 @@ func (dest *Dest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
}
|
}
|
||||||
dest.Type = attrValue(start, "Type")
|
dest.Type = attrValue(start, "Type")
|
||||||
dest.PageID = attrValue(start, "PageID")
|
dest.PageID = attrValue(start, "PageID")
|
||||||
|
dest.OmitLeft, dest.OmitTop, dest.OmitZoom = true, true, true
|
||||||
|
for _, attr := range start.Attr {
|
||||||
|
switch attr.Name.Local {
|
||||||
|
case "Left":
|
||||||
|
dest.OmitLeft = false
|
||||||
|
case "Top":
|
||||||
|
dest.OmitTop = false
|
||||||
|
case "Zoom":
|
||||||
|
dest.OmitZoom = false
|
||||||
|
}
|
||||||
|
}
|
||||||
dest.Left = actionFloatAttr(start, "Left")
|
dest.Left = actionFloatAttr(start, "Left")
|
||||||
dest.Right = actionFloatAttr(start, "Right")
|
dest.Right = actionFloatAttr(start, "Right")
|
||||||
dest.Top = actionFloatAttr(start, "Top")
|
dest.Top = actionFloatAttr(start, "Top")
|
||||||
@@ -161,18 +175,21 @@ func (dest *Dest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
}
|
}
|
||||||
if value.Left != nil {
|
if value.Left != nil {
|
||||||
dest.Left = *value.Left
|
dest.Left = *value.Left
|
||||||
|
dest.OmitLeft = false
|
||||||
}
|
}
|
||||||
if value.Right != nil {
|
if value.Right != nil {
|
||||||
dest.Right = *value.Right
|
dest.Right = *value.Right
|
||||||
}
|
}
|
||||||
if value.Top != nil {
|
if value.Top != nil {
|
||||||
dest.Top = *value.Top
|
dest.Top = *value.Top
|
||||||
|
dest.OmitTop = false
|
||||||
}
|
}
|
||||||
if value.Bottom != nil {
|
if value.Bottom != nil {
|
||||||
dest.Bottom = *value.Bottom
|
dest.Bottom = *value.Bottom
|
||||||
}
|
}
|
||||||
if value.Zoom != nil {
|
if value.Zoom != nil {
|
||||||
dest.Zoom = *value.Zoom
|
dest.Zoom = *value.Zoom
|
||||||
|
dest.OmitZoom = false
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -372,6 +372,12 @@ func annotationLinkXML(target AnnotationLink) ([]byte, error) {
|
|||||||
if !finite(value.value) {
|
if !finite(value.value) {
|
||||||
return nil, fmt.Errorf("destination coordinates must be finite")
|
return nil, fmt.Errorf("destination coordinates must be finite")
|
||||||
}
|
}
|
||||||
|
omit := dest.Type == "XYZ" && (value.name == "Left" && dest.OmitLeft || value.name == "Top" && dest.OmitTop || value.name == "Zoom" && dest.OmitZoom)
|
||||||
|
omit = omit || dest.Type == "FitH" && value.name == "Top" && dest.OmitTop
|
||||||
|
omit = omit || dest.Type == "FitV" && value.name == "Left" && dest.OmitLeft
|
||||||
|
if omit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if dest.Type == "XYZ" && slices.Contains([]string{"Left", "Top", "Zoom"}, value.name) || dest.Type == "FitH" && value.name == "Top" || dest.Type == "FitV" && value.name == "Left" || dest.Type == "FitR" && value.name != "Zoom" {
|
if dest.Type == "XYZ" && slices.Contains([]string{"Left", "Top", "Zoom"}, value.name) || dest.Type == "FitH" && value.name == "Top" || dest.Type == "FitV" && value.name == "Left" || dest.Type == "FitR" && value.name != "Zoom" {
|
||||||
attrs.add(value.name, ofdNumber(value.value))
|
attrs.add(value.name, ofdNumber(value.value))
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-4
@@ -378,8 +378,8 @@ func (p *pdfImporter) appendPath(mark pdfgo.PathMark) error {
|
|||||||
margin := mark.Style.LineWidth * scale / 2 * math.Max(1, mark.Style.MiterLimit)
|
margin := mark.Style.LineWidth * scale / 2 * math.Max(1, mark.Style.MiterLimit)
|
||||||
box = Box{box.X - margin, box.Y - margin, box.W + 2*margin, box.H + 2*margin}
|
box = Box{box.X - margin, box.Y - margin, box.W + 2*margin, box.H + 2*margin}
|
||||||
}
|
}
|
||||||
strokeColor := StrokeColor(*p.pathColor(mark.Style.Stroke, box))
|
strokeColor := StrokeColor(*p.paintColor(mark.Style.Stroke, box))
|
||||||
object := PathObject{Boundary: pdfBoundary(box), AbbreviatedData: p.pathData(mark.Path, box), Fill: &mark.Fill, Stroke: &mark.Stroke, FillColor: p.pathColor(mark.Style.Fill, box), StrokeColor: &strokeColor, LineWidth: mark.Style.LineWidth * scale, Cap: []string{"Butt", "Round", "Square"}[mark.Style.Cap], Join: []string{"Miter", "Round", "Bevel"}[mark.Style.Join], MiterLimit: mark.Style.MiterLimit, Clips: p.clips(mark.Style.Clips, box)}
|
object := PathObject{Boundary: pdfBoundary(box), AbbreviatedData: p.pathData(mark.Path, box), Fill: &mark.Fill, Stroke: &mark.Stroke, FillColor: p.paintColor(mark.Style.Fill, box), StrokeColor: &strokeColor, LineWidth: mark.Style.LineWidth * scale, Cap: []string{"Butt", "Round", "Square"}[mark.Style.Cap], Join: []string{"Miter", "Round", "Bevel"}[mark.Style.Join], MiterLimit: mark.Style.MiterLimit, Clips: p.clips(mark.Style.Clips, box)}
|
||||||
object.LineWidthSet = object.LineWidth == 0
|
object.LineWidthSet = object.LineWidth == 0
|
||||||
if mark.Path.EvenOdd {
|
if mark.Path.EvenOdd {
|
||||||
object.Rule = "Even-Odd"
|
object.Rule = "Even-Odd"
|
||||||
@@ -398,11 +398,30 @@ func (p *pdfImporter) appendPath(mark pdfgo.PathMark) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathColor 将页面渐变轴转换为对象局部坐标
|
// paintColor 将页面渐变转换为对象局部坐标
|
||||||
// 入参: paint PDF画刷, box 对象边界
|
// 入参: paint PDF画刷, box 对象边界
|
||||||
// 返回: *FillColor OFD颜色或渐变
|
// 返回: *FillColor OFD颜色或渐变
|
||||||
func (p *pdfImporter) pathColor(paint pdfgo.Paint, box Box) *FillColor {
|
func (p *pdfImporter) paintColor(paint pdfgo.Paint, box Box) *FillColor {
|
||||||
color := p.color(paint)
|
color := p.color(paint)
|
||||||
|
if paint.Radial != nil {
|
||||||
|
gradient := paint.Radial
|
||||||
|
start, end := p.matrix.Apply(gradient.Start), p.matrix.Apply(gradient.End)
|
||||||
|
scale := math.Hypot(p.matrix[0], p.matrix[1])
|
||||||
|
extend := 0
|
||||||
|
if gradient.Extend[0] {
|
||||||
|
extend |= 1
|
||||||
|
}
|
||||||
|
if gradient.Extend[1] {
|
||||||
|
extend |= 2
|
||||||
|
}
|
||||||
|
shading := &RadialShd{StartPoint: pdfNumbers(start.X-box.X, start.Y-box.Y), EndPoint: pdfNumbers(end.X-box.X, end.Y-box.Y), StartRadius: gradient.StartRadius * scale, EndRadius: gradient.EndRadius * scale, Extend: strconv.Itoa(extend)}
|
||||||
|
for _, stop := range gradient.Stops {
|
||||||
|
shading.Segment = append(shading.Segment, ShdSegment{Position: stop.Position, Color: ShdColor{Value: pdfNumbers(math.Round(stop.RGB[0]*255), math.Round(stop.RGB[1]*255), math.Round(stop.RGB[2]*255))}})
|
||||||
|
}
|
||||||
|
color.Value = ""
|
||||||
|
color.RadialShd = shading
|
||||||
|
return color
|
||||||
|
}
|
||||||
if paint.Axial == nil {
|
if paint.Axial == nil {
|
||||||
return color
|
return color
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -72,7 +72,7 @@ func (p *pdfImporter) group(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error
|
|||||||
if stroke {
|
if stroke {
|
||||||
color, overprint = path.Style.Stroke, path.Style.StrokeOverprint
|
color, overprint = path.Style.Stroke, path.Style.StrokeOverprint
|
||||||
}
|
}
|
||||||
if color.Alpha != 1 || color.Axial != nil || overprint && pdfOverprintNeedsSeparation(color) {
|
if color.Alpha != 1 || color.Axial != nil || color.Radial != nil || overprint && pdfOverprintNeedsSeparation(color) {
|
||||||
return &pdfgo.UnsupportedError{Feature: "transparent or overprinted group content"}
|
return &pdfgo.UnsupportedError{Feature: "transparent or overprinted group content"}
|
||||||
}
|
}
|
||||||
if paint == nil {
|
if paint == nil {
|
||||||
@@ -230,7 +230,7 @@ func (p *pdfImporter) maskClip(mask *pdfgo.SoftMask) (pdfgo.Path, error) {
|
|||||||
}
|
}
|
||||||
visitor.Path = func(mark pdfgo.PathMark) error {
|
visitor.Path = func(mark pdfgo.PathMark) error {
|
||||||
paint := mark.Style.Fill
|
paint := mark.Style.Fill
|
||||||
if !mark.Fill || mark.Stroke || paint.Alpha != 1 || paint.CMYK != nil || paint.Axial != nil || paint.RGB[0] != paint.RGB[1] || paint.RGB[1] != paint.RGB[2] || mark.Style.SoftMask != nil {
|
if !mark.Fill || mark.Stroke || paint.Alpha != 1 || paint.CMYK != nil || paint.Axial != nil || paint.Radial != nil || paint.RGB[0] != paint.RGB[1] || paint.RGB[1] != paint.RGB[2] || mark.Style.SoftMask != nil {
|
||||||
return &pdfgo.UnsupportedError{Feature: "nonbinary mask graphic"}
|
return &pdfgo.UnsupportedError{Feature: "nonbinary mask graphic"}
|
||||||
}
|
}
|
||||||
visible, err := opacity(paint.RGB[0])
|
visible, err := opacity(paint.RGB[0])
|
||||||
|
|||||||
+58
-4
@@ -41,7 +41,7 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
|||||||
if annotation.Subtype != "Link" {
|
if annotation.Subtype != "Link" {
|
||||||
return &pdfgo.UnsupportedError{Feature: "annotation " + string(annotation.Subtype)}
|
return &pdfgo.UnsupportedError{Feature: "annotation " + string(annotation.Subtype)}
|
||||||
}
|
}
|
||||||
for _, key := range []pdfgo.Name{"AP", "AA", "BS", "QuadPoints", "OC"} {
|
for _, key := range []pdfgo.Name{"AP", "AA", "BS", "OC"} {
|
||||||
if dict[key] != nil {
|
if dict[key] != nil {
|
||||||
return &pdfgo.UnsupportedError{Feature: fmt.Sprintf("link annotation field %q", key)}
|
return &pdfgo.UnsupportedError{Feature: fmt.Sprintf("link annotation field %q", key)}
|
||||||
}
|
}
|
||||||
@@ -127,6 +127,7 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
|||||||
dest := &Dest{Type: string(destination.Mode), PageID: p.pageIDs[destination.Page]}
|
dest := &Dest{Type: string(destination.Mode), PageID: p.pageIDs[destination.Page]}
|
||||||
if destination.Mode == "XYZ" {
|
if destination.Mode == "XYZ" {
|
||||||
values := [3]float64{}
|
values := [3]float64{}
|
||||||
|
retained := [3]bool{}
|
||||||
for n, v := range destination.Parameters {
|
for n, v := range destination.Parameters {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case pdfgo.Integer:
|
case pdfgo.Integer:
|
||||||
@@ -134,13 +135,16 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
|||||||
case pdfgo.Real:
|
case pdfgo.Real:
|
||||||
values[n] = float64(v)
|
values[n] = float64(v)
|
||||||
case nil:
|
case nil:
|
||||||
if n < 2 {
|
retained[n] = true
|
||||||
return &pdfgo.UnsupportedError{Feature: "retained destination coordinates"}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
point := matrix.Apply(pdfgo.Point{X: values[0], Y: values[1]})
|
point := matrix.Apply(pdfgo.Point{X: values[0], Y: values[1]})
|
||||||
dest.Left, dest.Top, dest.Zoom = point.X, point.Y, values[2]
|
dest.Left, dest.Top, dest.Zoom = point.X, point.Y, values[2]
|
||||||
|
dest.OmitLeft, dest.OmitTop = retained[0], retained[1]
|
||||||
|
if targetPage.Rotation == 90 || targetPage.Rotation == 270 {
|
||||||
|
dest.OmitLeft, dest.OmitTop = retained[1], retained[0]
|
||||||
|
}
|
||||||
|
dest.OmitZoom = retained[2] || values[2] == 0
|
||||||
} else if destination.Mode != "Fit" {
|
} else if destination.Mode != "Fit" {
|
||||||
return &pdfgo.UnsupportedError{Feature: "destination mode " + string(destination.Mode)}
|
return &pdfgo.UnsupportedError{Feature: "destination mode " + string(destination.Mode)}
|
||||||
}
|
}
|
||||||
@@ -154,9 +158,59 @@ func (p *pdfImporter) annotations(ctx context.Context, page *pdfgo.Page, strict
|
|||||||
}
|
}
|
||||||
no := false
|
no := false
|
||||||
object.Fill, object.Stroke = &no, &no
|
object.Fill, object.Stroke = &no, &no
|
||||||
|
action.Region, err = p.linkRegion(annotation, box)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
object.Actions = []Action{action}
|
object.Actions = []Action{action}
|
||||||
p.objects = append(p.objects, GraphicObject{Type: "PathObject", PathObject: object})
|
p.objects = append(p.objects, GraphicObject{Type: "PathObject", PathObject: object})
|
||||||
p.report.Links++
|
p.report.Links++
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// linkRegion 将PDF链接四边形映射为OFD动作区域,越出注解矩形时使用矩形
|
||||||
|
// 入参: annotation PDF链接注解, box OFD对象边界
|
||||||
|
// 返回: *Region 点击区域, error 解析错误
|
||||||
|
func (p *pdfImporter) linkRegion(annotation pdfgo.Annotation, box Box) (*Region, error) {
|
||||||
|
value, err := p.reader.Resolve(annotation.Dictionary["QuadPoints"])
|
||||||
|
if err != nil || value == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
array, ok := value.(pdfgo.Array)
|
||||||
|
if !ok || len(array) == 0 || len(array)%8 != 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
region := &Region{Area: make([]RegionArea, 0, len(array)/8)}
|
||||||
|
for start := 0; start < len(array); start += 8 {
|
||||||
|
var points [4]pdfgo.Point
|
||||||
|
for index := range points {
|
||||||
|
coordinates := [2]float64{}
|
||||||
|
for axis := range coordinates {
|
||||||
|
resolved, err := p.reader.Resolve(array[start+index*2+axis])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch number := resolved.(type) {
|
||||||
|
case pdfgo.Integer:
|
||||||
|
coordinates[axis] = float64(number)
|
||||||
|
case pdfgo.Real:
|
||||||
|
coordinates[axis] = float64(number)
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x, y := coordinates[0], coordinates[1]
|
||||||
|
if !finite(x) || !finite(y) || x < annotation.Rect.XMin || x > annotation.Rect.XMax || y < annotation.Rect.YMin || y > annotation.Rect.YMax {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
points[index] = p.matrix.Apply(pdfgo.Point{X: x, Y: y})
|
||||||
|
}
|
||||||
|
area := RegionArea{Start: pdfNumbers(points[0].X-box.X, points[0].Y-box.Y)}
|
||||||
|
for _, point := range points[1:] {
|
||||||
|
area.Command = append(area.Command, RegionCommand{Type: "Line", Point1: pdfNumbers(point.X-box.X, point.Y-box.Y)})
|
||||||
|
}
|
||||||
|
region.Area = append(region.Area, area)
|
||||||
|
}
|
||||||
|
return region, nil
|
||||||
|
}
|
||||||
+12
-11
@@ -39,7 +39,7 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
|
|||||||
if err := p.flushPath(); err != nil {
|
if err := p.flushPath(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mark.Image.ImageMask && mark.Style.Fill.Axial != nil {
|
if mark.Image.ImageMask && (mark.Style.Fill.Axial != nil || mark.Style.Fill.Radial != nil) {
|
||||||
return &pdfgo.UnsupportedError{Feature: "gradient stencil image"}
|
return &pdfgo.UnsupportedError{Feature: "gradient stencil image"}
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@@ -147,9 +147,6 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
if err := p.flushPath(); err != nil {
|
if err := p.flushPath(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mark.Style.Fill.Axial != nil || mark.Style.Stroke.Axial != nil {
|
|
||||||
return &pdfgo.UnsupportedError{Feature: "gradient text paint"}
|
|
||||||
}
|
|
||||||
var err error
|
var err error
|
||||||
mark.Style, err = p.maskStyle(mark.Style)
|
mark.Style, err = p.maskStyle(mark.Style)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -167,7 +164,8 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
if id == "" {
|
if id == "" {
|
||||||
var err error
|
var err error
|
||||||
if embedded {
|
if embedded {
|
||||||
program, err := pdfFontProgram(font)
|
var program []byte
|
||||||
|
program, err = pdfFontProgram(font)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -225,7 +223,7 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if embedded && gid >= metrics.NumGlyphs() {
|
if embedded && gid >= metrics.NumGlyphs() {
|
||||||
return fmt.Errorf("PDF glyph index outside embedded font")
|
return fmt.Errorf("PDF glyph index %d outside embedded font %s (%d glyphs)", gid, font.Name, metrics.NumGlyphs())
|
||||||
}
|
}
|
||||||
if !embedded && glyph.Text == "" {
|
if !embedded && glyph.Text == "" {
|
||||||
return &pdfgo.UnsupportedError{Feature: "unmapped external font character"}
|
return &pdfgo.UnsupportedError{Feature: "unmapped external font character"}
|
||||||
@@ -263,8 +261,6 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
}
|
}
|
||||||
margin := mark.Style.LineWidth * unit / 2 * math.Max(1, mark.Style.MiterLimit)
|
margin := mark.Style.LineWidth * unit / 2 * math.Max(1, mark.Style.MiterLimit)
|
||||||
box = Box{box.X - margin, box.Y - margin, box.W + 2*margin, box.H + 2*margin}
|
box = Box{box.X - margin, box.Y - margin, box.W + 2*margin, box.H + 2*margin}
|
||||||
color := StrokeColor(*p.color(mark.Style.Stroke))
|
|
||||||
object.StrokeColor = &color
|
|
||||||
object.LineWidth = mark.Style.LineWidth * unit / scale
|
object.LineWidth = mark.Style.LineWidth * unit / scale
|
||||||
object.LineWidthSet = object.LineWidth == 0
|
object.LineWidthSet = object.LineWidth == 0
|
||||||
object.Join = []string{"Miter", "Round", "Bevel"}[mark.Style.Join]
|
object.Join = []string{"Miter", "Round", "Bevel"}[mark.Style.Join]
|
||||||
@@ -274,6 +270,10 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
object.Boundary = pdfBoundary(box)
|
object.Boundary = pdfBoundary(box)
|
||||||
|
if stroke {
|
||||||
|
color := StrokeColor(*p.paintColor(mark.Style.Stroke, box))
|
||||||
|
object.StrokeColor = &color
|
||||||
|
}
|
||||||
if box.X >= p.pageWidth || box.Y >= p.pageHeight || box.X+box.W <= 0 || box.Y+box.H <= 0 {
|
if box.X >= p.pageWidth || box.Y >= p.pageHeight || box.X+box.W <= 0 || box.Y+box.H <= 0 {
|
||||||
visible = false
|
visible = false
|
||||||
}
|
}
|
||||||
@@ -281,7 +281,7 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
object.Fill = &fill
|
object.Fill = &fill
|
||||||
object.Stroke = &stroke
|
object.Stroke = &stroke
|
||||||
object.Visible = &visible
|
object.Visible = &visible
|
||||||
object.FillColor = p.color(mark.Style.Fill)
|
object.FillColor = p.paintColor(mark.Style.Fill, box)
|
||||||
object.Clips = p.clips(mark.Style.Clips, box)
|
object.Clips = p.clips(mark.Style.Clips, box)
|
||||||
p.objects = append(p.objects, GraphicObject{Type: "TextObject", TextObject: object})
|
p.objects = append(p.objects, GraphicObject{Type: "TextObject", TextObject: object})
|
||||||
p.report.TextObjects++
|
p.report.TextObjects++
|
||||||
@@ -293,7 +293,8 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
|
|||||||
// 返回: []byte 封装后的字体数据, error 错误信息
|
// 返回: []byte 封装后的字体数据, error 错误信息
|
||||||
func pdfFontProgram(source *pdfgo.Font) ([]byte, error) {
|
func pdfFontProgram(source *pdfgo.Font) ([]byte, error) {
|
||||||
program := source.Program
|
program := source.Program
|
||||||
if source.ProgramType == "Type1C" || source.ProgramType == "CIDFontType0C" {
|
bareCFF := len(program) >= 4 && program[0] == 1 && program[1] == 0 && program[2] >= 4 && program[3] >= 1 && program[3] <= 4
|
||||||
|
if source.ProgramType == "Type1C" || source.ProgramType == "CIDFontType0C" || bareCFF {
|
||||||
var err error
|
var err error
|
||||||
program, _, err = wrapCFFToOTF(source.Program)
|
program, _, err = wrapCFFToOTF(source.Program)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -377,7 +378,7 @@ func pdfFontProgram(source *pdfgo.Font) ([]byte, error) {
|
|||||||
tables["OS/2"] = buildOS2TableWithMetrics(int16(binary.BigEndian.Uint16(hhea[4:6])), int16(binary.BigEndian.Uint16(hhea[6:8])))
|
tables["OS/2"] = buildOS2TableWithMetrics(int16(binary.BigEndian.Uint16(hhea[4:6])), int16(binary.BigEndian.Uint16(hhea[6:8])))
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
if !changed {
|
if !changed && len(program)%4 == 0 {
|
||||||
return program, nil
|
return program, nil
|
||||||
}
|
}
|
||||||
return serializeOTF(tables)
|
return serializeOTF(tables)
|
||||||
|
|||||||
@@ -961,13 +961,23 @@ func (x *ofdXML) actions(actions []Action) {
|
|||||||
attrs := ofdAttrs{{Name: xml.Name{Local: "Type"}, Value: dest.Type}, {Name: xml.Name{Local: "PageID"}, Value: dest.PageID}}
|
attrs := ofdAttrs{{Name: xml.Name{Local: "Type"}, Value: dest.Type}, {Name: xml.Name{Local: "PageID"}, Value: dest.PageID}}
|
||||||
switch dest.Type {
|
switch dest.Type {
|
||||||
case "XYZ":
|
case "XYZ":
|
||||||
|
if !dest.OmitLeft {
|
||||||
attrs.add("Left", ofdNumber(dest.Left))
|
attrs.add("Left", ofdNumber(dest.Left))
|
||||||
|
}
|
||||||
|
if !dest.OmitTop {
|
||||||
attrs.add("Top", ofdNumber(dest.Top))
|
attrs.add("Top", ofdNumber(dest.Top))
|
||||||
|
}
|
||||||
|
if !dest.OmitZoom {
|
||||||
attrs.add("Zoom", ofdNumber(dest.Zoom))
|
attrs.add("Zoom", ofdNumber(dest.Zoom))
|
||||||
|
}
|
||||||
case "FitH":
|
case "FitH":
|
||||||
|
if !dest.OmitTop {
|
||||||
attrs.add("Top", ofdNumber(dest.Top))
|
attrs.add("Top", ofdNumber(dest.Top))
|
||||||
|
}
|
||||||
case "FitV":
|
case "FitV":
|
||||||
|
if !dest.OmitLeft {
|
||||||
attrs.add("Left", ofdNumber(dest.Left))
|
attrs.add("Left", ofdNumber(dest.Left))
|
||||||
|
}
|
||||||
case "FitR":
|
case "FitR":
|
||||||
attrs.add("Left", ofdNumber(dest.Left))
|
attrs.add("Left", ofdNumber(dest.Left))
|
||||||
attrs.add("Right", ofdNumber(dest.Right))
|
attrs.add("Right", ofdNumber(dest.Right))
|
||||||
|
|||||||
Reference in new issue
Block a user