diff --git a/kernel/go.mod b/kernel/go.mod index b25ea45d9..04f2cc6bf 100644 --- a/kernel/go.mod +++ b/kernel/go.mod @@ -11,7 +11,6 @@ require ( github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7 github.com/88250/gulu v1.2.3-0.20241127120230-1ae6a9868a2d github.com/88250/lute v1.7.7-0.20241127031345-f772b0ee2be8 - github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4 github.com/ConradIrwin/font v0.0.0-20240627033111-8567075b2bfe @@ -51,6 +50,7 @@ require ( github.com/open-spaced-repetition/go-fsrs/v3 v3.2.0 github.com/panjf2000/ants/v2 v2.10.0 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pdfcpu/pdfcpu v0.9.1 github.com/radovskyb/watcher v1.0.7 github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 @@ -70,7 +70,7 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 github.com/xuri/excelize/v2 v2.8.1 - golang.org/x/image v0.20.0 + golang.org/x/image v0.21.0 golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b golang.org/x/mod v0.22.0 golang.org/x/net v0.31.0 @@ -177,12 +177,14 @@ require ( replace github.com/mattn/go-sqlite3 => github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950 +replace github.com/pdfcpu/pdfcpu => github.com/88250/pdfcpu v0.3.14-0.20241128120530-05a73e90be3e + //replace github.com/88250/lute => F:\golang\gopath\src\github.com\88250\lute //replace github.com/siyuan-note/dejavu => D:\88250\dejavu //replace github.com/siyuan-note/riff => D:\88250\riff //replace github.com/siyuan-note/httpclient => D:\88250\httpclient //replace github.com/siyuan-note/filelock => D:\88250\filelock -//replace github.com/88250/pdfcpu => D:\88250\pdfcpu +//replace github.com/pdfcpu/pdfcpu => D:\pdfcpu //replace github.com/88250/gulu => D:\88250\gulu //replace github.com/mattn/go-sqlite3 => D:\88250\go-sqlite3 //replace github.com/88250/epub => D:\88250\epub diff --git a/kernel/go.sum b/kernel/go.sum index 1b21baece..c4214b72c 100644 --- a/kernel/go.sum +++ b/kernel/go.sum @@ -16,8 +16,8 @@ github.com/88250/gulu v1.2.3-0.20241127120230-1ae6a9868a2d h1:dexFyk3UkR4c2xpyrC github.com/88250/gulu v1.2.3-0.20241127120230-1ae6a9868a2d/go.mod h1:MUfzyfmbPrRDZLqxc7aPrVYveatTHRfoUa5TynPS0i8= github.com/88250/lute v1.7.7-0.20241127031345-f772b0ee2be8 h1:1gWOQT9m2o3E6X//wvI+bexbXgdHheN6YUZcABHMm4k= github.com/88250/lute v1.7.7-0.20241127031345-f772b0ee2be8/go.mod h1:VDAzL8b+oCh+e3NAlmwwLzC53ten0rZlS8NboB7ljtk= -github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c h1:Dl/8S9iLyPMTElnWIBxmjaLiWrkI5P4a21ivwAn5pU0= -github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4= +github.com/88250/pdfcpu v0.3.14-0.20241128120530-05a73e90be3e h1:juhQRP4rYNB1mVySivwuUaa1sIF10LQkbOmNPnCNjwA= +github.com/88250/pdfcpu v0.3.14-0.20241128120530-05a73e90be3e/go.mod h1:fVfOloBzs2+W2VJCCbq60XIxc3yJHAZ0Gahv1oO0gyI= github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY= github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1/go.mod h1:U3pckKQIgxxkmZjV5yXQjHdGxQK0o/vEZeZ6cQsxfHw= github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4 h1:r10k4+Lu1mDpiCKa1liAdGJUhB4BJHHJnMvtoli6Hts= @@ -428,8 +428,8 @@ golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgR golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= -golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw= -golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM= +golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= +golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0= golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySFVJb9rsODuiO+WXu8HrUuySb7nYFw= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= diff --git a/kernel/model/export.go b/kernel/model/export.go index 3bc0511e4..9e4334491 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -41,12 +41,14 @@ import ( "github.com/88250/lute/lex" "github.com/88250/lute/parse" "github.com/88250/lute/render" - "github.com/88250/pdfcpu/pkg/api" - "github.com/88250/pdfcpu/pkg/font" - "github.com/88250/pdfcpu/pkg/pdfcpu" "github.com/emirpasic/gods/sets/hashset" "github.com/emirpasic/gods/stacks/linkedliststack" "github.com/imroc/req/v3" + "github.com/pdfcpu/pdfcpu/pkg/api" + "github.com/pdfcpu/pdfcpu/pkg/font" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types" "github.com/siyuan-note/filelock" "github.com/siyuan-note/httpclient" "github.com/siyuan-note/logging" @@ -1003,7 +1005,7 @@ func ProcessPDF(id, p string, merge, removeAssets, watermark bool) (err error) { return ast.WalkContinue }) - pdfcpu.ConfigPath = "disable" + api.DisableConfigDir() font.UserFontDir = filepath.Join(util.HomeDir, ".config", "siyuan", "fonts") if mkdirErr := os.MkdirAll(font.UserFontDir, 0755); nil != mkdirErr { logging.LogErrorf("mkdir [%s] failed: %s", font.UserFontDir, mkdirErr) @@ -1019,7 +1021,8 @@ func ProcessPDF(id, p string, merge, removeAssets, watermark bool) (err error) { processPDFLinkEmbedAssets(pdfCtx, assetDests, removeAssets) processPDFWatermark(pdfCtx, watermark) - pdfcpu.VersionStr = "SiYuan v" + util.Ver + pdfcpuVer := model.VersionStr + model.VersionStr = "SiYuan v" + util.Ver + " (" + pdfcpuVer + ")" if writeErr := api.WriteContextFile(pdfCtx, p); nil != writeErr { logging.LogErrorf("write pdf context failed: %s", writeErr) return @@ -1027,7 +1030,7 @@ func ProcessPDF(id, p string, merge, removeAssets, watermark bool) (err error) { return } -func processPDFWatermark(pdfCtx *pdfcpu.Context, watermark bool) { +func processPDFWatermark(pdfCtx *model.Context, watermark bool) { // Support adding the watermark on export PDF https://github.com/siyuan-note/siyuan/issues/9961 // https://pdfcpu.io/core/watermark @@ -1085,15 +1088,15 @@ func processPDFWatermark(pdfCtx *pdfcpu.Context, watermark bool) { logging.LogInfof("add PDF watermark [mode=%s, str=%s, desc=%s]", mode, str, desc) - var wm *pdfcpu.Watermark + var wm *model.Watermark var err error switch mode { case "text": - wm, err = pdfcpu.ParseTextWatermarkDetails(str, desc, false, pdfcpu.POINTS) + wm, err = pdfcpu.ParseTextWatermarkDetails(str, desc, false, types.POINTS) case "image": - wm, err = pdfcpu.ParseImageWatermarkDetails(str, desc, false, pdfcpu.POINTS) + wm, err = pdfcpu.ParseImageWatermarkDetails(str, desc, false, types.POINTS) case "pdf": - wm, err = pdfcpu.ParsePDFWatermarkDetails(str, desc, false, pdfcpu.POINTS) + wm, err = pdfcpu.ParsePDFWatermarkDetails(str, desc, false, types.POINTS) } if err != nil { @@ -1103,15 +1106,15 @@ func processPDFWatermark(pdfCtx *pdfcpu.Context, watermark bool) { } wm.OnTop = true // Export PDF and add watermarks no longer covered by images https://github.com/siyuan-note/siyuan/issues/10818 - err = pdfCtx.AddWatermarks(nil, wm) + err = pdfcpu.AddWatermarks(pdfCtx, nil, wm) if err != nil { logging.LogErrorf("add watermark failed: %s", err) return } } -func processPDFBookmarks(pdfCtx *pdfcpu.Context, headings []*ast.Node) { - links, err := api.ListToCLinks(pdfCtx) +func processPDFBookmarks(pdfCtx *model.Context, headings []*ast.Node) { + links, err := PdfListToCLinks(pdfCtx) if err != nil { return } @@ -1120,7 +1123,7 @@ func processPDFBookmarks(pdfCtx *pdfcpu.Context, headings []*ast.Node) { return links[i].Page < links[j].Page }) - bms := map[string]*pdfcpu.Bookmark{} + bms := map[string]pdfcpu.Bookmark{} for _, link := range links { linkID := link.URI[strings.LastIndex(link.URI, "/")+1:] b := sql.GetBlock(linkID) @@ -1130,7 +1133,7 @@ func processPDFBookmarks(pdfCtx *pdfcpu.Context, headings []*ast.Node) { } title := b.Content title, _ = url.QueryUnescape(title) - bm := &pdfcpu.Bookmark{ + bm := pdfcpu.Bookmark{ Title: title, PageFrom: link.Page, AbsPos: link.Rect.UR.Y, @@ -1142,15 +1145,15 @@ func processPDFBookmarks(pdfCtx *pdfcpu.Context, headings []*ast.Node) { return } - var topBms []*pdfcpu.Bookmark + var topBms []pdfcpu.Bookmark stack := linkedliststack.New() for _, h := range headings { L: for ; ; stack.Pop() { cur, ok := stack.Peek() if !ok { - bm := bms[h.ID] - if nil == bm { + bm, ok := bms[h.ID] + if !ok { break L } bm.Level = h.HeadingLevel @@ -1159,19 +1162,19 @@ func processPDFBookmarks(pdfCtx *pdfcpu.Context, headings []*ast.Node) { break L } - tip := cur.(*pdfcpu.Bookmark) + tip := cur.(pdfcpu.Bookmark) if tip.Level < h.HeadingLevel { bm := bms[h.ID] bm.Level = h.HeadingLevel - bm.Parent = tip - tip.Children = append(tip.Children, bm) + bm.Parent = &tip + tip.Kids = append(tip.Kids, bm) stack.Push(bm) break L } } } - err = pdfCtx.AddBookmarks(topBms) + err = pdfcpu.AddBookmarks(pdfCtx, topBms, true) if err != nil { logging.LogErrorf("add bookmark failed: %s", err) return @@ -1180,7 +1183,7 @@ func processPDFBookmarks(pdfCtx *pdfcpu.Context, headings []*ast.Node) { // processPDFLinkEmbedAssets 处理资源文件超链接,根据 removeAssets 参数决定是否将资源文件嵌入到 PDF 中。 // 导出 PDF 时支持将资源文件作为附件嵌入 https://github.com/siyuan-note/siyuan/issues/7414 -func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, removeAssets bool) { +func processPDFLinkEmbedAssets(pdfCtx *model.Context, assetDests []string, removeAssets bool) { var assetAbsPaths []string for _, dest := range assetDests { if absPath, _ := GetAssetAbsPath(dest); "" != absPath { @@ -1192,28 +1195,28 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo return } - assetLinks, otherLinks, listErr := api.ListLinks(pdfCtx) + assetLinks, otherLinks, listErr := PdfListLinks(pdfCtx) if nil != listErr { logging.LogErrorf("list asset links failed: %s", listErr) return } - if _, removeErr := pdfCtx.RemoveAnnotations(nil, nil, nil, false); nil != removeErr { + if _, removeErr := pdfcpu.RemoveAnnotations(pdfCtx, nil, nil, nil, false); nil != removeErr { logging.LogWarnf("remove annotations failed: %s", removeErr) } - linkMap := map[int][]pdfcpu.AnnotationRenderer{} + linkMap := map[int][]model.AnnotationRenderer{} for _, link := range otherLinks { link.URI, _ = url.PathUnescape(link.URI) if 1 > len(linkMap[link.Page]) { - linkMap[link.Page] = []pdfcpu.AnnotationRenderer{link} + linkMap[link.Page] = []model.AnnotationRenderer{link} } else { linkMap[link.Page] = append(linkMap[link.Page], link) } } - attachmentMap := map[int][]*pdfcpu.IndirectRef{} - now := pdfcpu.StringLiteral(pdfcpu.DateString(time.Now())) + attachmentMap := map[int][]*types.IndirectRef{} + now := types.StringLiteral(types.DateString(time.Now())) for _, link := range assetLinks { link.URI = strings.ReplaceAll(link.URI, "http://"+util.LocalHost+":"+util.ServerPort+"/export/temp/", "") link.URI = strings.ReplaceAll(link.URI, "http://"+util.LocalHost+":"+util.ServerPort+"/", "") // Exporting PDF embedded asset files as attachments fails https://github.com/siyuan-note/siyuan/issues/7414#issuecomment-1704573557 @@ -1225,7 +1228,7 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo if !removeAssets { // 不移除资源文件夹的话将超链接指向资源文件夹 if 1 > len(linkMap[link.Page]) { - linkMap[link.Page] = []pdfcpu.AnnotationRenderer{link} + linkMap[link.Page] = []model.AnnotationRenderer{link} } else { linkMap[link.Page] = append(linkMap[link.Page], link) } @@ -1247,7 +1250,7 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo } fn := filepath.Base(absPath) - fileSpecDict, newErr := pdfCtx.XRefTable.NewFileSpecDict(fn, pdfcpu.EncodeUTF16String(fn), "attached by SiYuan", *ir) + fileSpecDict, newErr := pdfCtx.XRefTable.NewFileSpecDict(fn, types.EncodeUTF16String(fn), "attached by SiYuan", *ir) if nil != newErr { logging.LogWarnf("new file spec dict failed: %s", newErr) continue @@ -1264,22 +1267,22 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo ux := lx + link.Rect.Height()/2 uy := ly + link.Rect.Height()/2 - d := pdfcpu.Dict( - map[string]pdfcpu.Object{ - "Type": pdfcpu.Name("Annot"), - "Subtype": pdfcpu.Name("FileAttachment"), - "Contents": pdfcpu.StringLiteral(""), - "Rect": pdfcpu.Rect(lx, ly, ux, uy).Array(), + d := types.Dict( + map[string]types.Object{ + "Type": types.Name("Annot"), + "Subtype": types.Name("FileAttachment"), + "Contents": types.StringLiteral(""), + "Rect": types.RectForWidthAndHeight(lx, ly, ux, uy).Array(), "P": link.P, "M": now, - "F": pdfcpu.Integer(0), - "Border": pdfcpu.NewIntegerArray(0, 0, 1), - "C": pdfcpu.NewNumberArray(0.5, 0.0, 0.5), - "CA": pdfcpu.Float(0.95), + "F": types.Integer(0), + "Border": types.NewIntegerArray(0, 0, 1), + "C": types.NewNumberArray(0.5, 0.0, 0.5), + "CA": types.Float(0.95), "CreationDate": now, - "Name": pdfcpu.Name("FileAttachment"), + "Name": types.Name("FileAttachment"), "FS": *ir, - "NM": pdfcpu.StringLiteral(""), + "NM": types.StringLiteral(""), }, ) @@ -1302,14 +1305,14 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo } if 1 > len(attachmentMap[link.Page]) { - attachmentMap[link.Page] = []*pdfcpu.IndirectRef{ann} + attachmentMap[link.Page] = []*types.IndirectRef{ann} } else { attachmentMap[link.Page] = append(attachmentMap[link.Page], ann) } } if 0 < len(linkMap) { - if _, addErr := pdfCtx.AddAnnotationsMap(linkMap, false); nil != addErr { + if _, addErr := pdfcpu.AddAnnotationsMap(pdfCtx, linkMap, false); nil != addErr { logging.LogErrorf("add annotations map failed: %s", addErr) } } @@ -1328,7 +1331,7 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo continue } - array := pdfcpu.Array{} + array := types.Array{} for _, ann := range anns { array = append(array, *ann) } @@ -1340,9 +1343,9 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo continue } - ir, ok := obj.(pdfcpu.IndirectRef) + ir, ok := obj.(types.IndirectRef) if !ok { - pageDict.Update("Annots", append(obj.(pdfcpu.Array), array...)) + pageDict.Update("Annots", append(obj.(types.Array), array...)) pdfCtx.EnsureVersionForWriting() continue } @@ -1354,7 +1357,7 @@ func processPDFLinkEmbedAssets(pdfCtx *pdfcpu.Context, assetDests []string, remo continue } - annots, _ := o.(pdfcpu.Array) + annots, _ := o.(types.Array) entry, ok := pdfCtx.FindTableEntryForIndRef(&ir) if !ok { continue diff --git a/kernel/model/pdf.go b/kernel/model/pdf.go new file mode 100644 index 000000000..73fa0c710 --- /dev/null +++ b/kernel/model/pdf.go @@ -0,0 +1,61 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package model + +import ( + "strings" + + "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" +) + +func PdfListLinks(ctx *model.Context) (assets, others []model.LinkAnnotation, err error) { + for pg, annos := range ctx.PageAnnots { + for k, v := range annos { + if model.AnnLink == k { + for _, va := range v.Map { + link := va.ContentString() + l := va.(model.LinkAnnotation) + l.Page = pg + if strings.HasPrefix(link, "http://127.0.0.1:") && strings.Contains(link, "/assets/") { + assets = append(assets, l) + } else { + others = append(others, l) + } + } + } + } + } + return +} + +func PdfListToCLinks(ctx *model.Context) (ret []model.LinkAnnotation, err error) { + for pg, annos := range ctx.PageAnnots { + for k, v := range annos { + if model.AnnLink == k { + for _, va := range v.Map { + link := va.ContentString() + if strings.HasPrefix(link, "pdf-outline://") { + l := va.(model.LinkAnnotation) + l.Page = pg + ret = append(ret, l) + } + } + } + } + } + return +}