Kaynağa Gözat

:art: 导出 PDF 时支持将资源文件作为附件嵌入 https://github.com/siyuan-note/siyuan/issues/7414

Liang Ding 2 yıl önce
ebeveyn
işleme
efc018a27f
3 değiştirilmiş dosya ile 196 ekleme ve 29 silme
  1. 1 1
      kernel/go.mod
  2. 2 2
      kernel/go.sum
  3. 193 26
      kernel/model/export.go

+ 1 - 1
kernel/go.mod

@@ -7,7 +7,7 @@ require (
 	github.com/88250/css v0.1.2
 	github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798
 	github.com/88250/lute v1.7.6-0.20230220030205-b0f64d7ba66e
-	github.com/88250/pdfcpu v0.3.13
+	github.com/88250/pdfcpu v0.3.14-0.20230222105639-68297f21b5d2
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
 	github.com/ClarkThan/ahocorasick v0.0.0-20230216061320-bccdb98581a3
 	github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732

+ 2 - 2
kernel/go.sum

@@ -10,8 +10,8 @@ github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798 h1:sR/s/Y9wyl79ZRCUER
 github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
 github.com/88250/lute v1.7.6-0.20230220030205-b0f64d7ba66e h1:7UgFzsksh+z6IX2z+BKG3tt1TU7LJNb0zOHDbhLEaUc=
 github.com/88250/lute v1.7.6-0.20230220030205-b0f64d7ba66e/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
-github.com/88250/pdfcpu v0.3.13 h1:touMWMZkCGalMIbEg9bxYp7rETM+zwb9hXjwhqi4I7Q=
-github.com/88250/pdfcpu v0.3.13/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
+github.com/88250/pdfcpu v0.3.14-0.20230222105639-68297f21b5d2 h1:M1JxCmcaLwI7qlQJD5UatAxaIT6tfLvq3GptOsMffn4=
+github.com/88250/pdfcpu v0.3.14-0.20230222105639-68297f21b5d2/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
 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/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=

+ 193 - 26
kernel/model/export.go

@@ -764,36 +764,203 @@ func AddPDFOutline(id, p string, merge bool) (err error) {
 		}
 	}
 
-	//var assetAbsPaths []string
-	//for _, dest := range assetDests {
-	//	absPath, _ := GetAssetAbsPath(dest)
-	//	if "" != absPath {
-	//		assetAbsPaths = append(assetAbsPaths, absPath)
-	//	}
-	//}
-	//
-	//if 0 < len(assetAbsPaths) {
-	//	outFile := inFile + ".tmp"
-	//	err = api.AddAttachmentsFile(inFile, outFile, assetAbsPaths, false, nil)
-	//	if nil != err {
-	//		logging.LogErrorf("add attachment failed: %s", err)
-	//		return
-	//	}
-	//
-	//	err = os.Rename(outFile, inFile)
-	//	if nil != err {
-	//		return
-	//	}
-	//}
-	//
-	//assetLinks, err := api.ListAssetLinks(inFile)
-	//if nil == err {
-	//	logging.LogInfof("pdf annotation: %+v", assetLinks)
-	//}
+	var assetAbsPaths []string
+	for _, dest := range assetDests {
+		absPath, _ := GetAssetAbsPath(dest)
+		if "" != absPath {
+			assetAbsPaths = append(assetAbsPaths, absPath)
+		}
+	}
+
+	if 0 < len(assetAbsPaths) {
+		//outFile := inFile + ".tmp"
+		//err = api.AddAttachmentsFile(inFile, outFile, assetAbsPaths, false, nil)
+		//if nil != err {
+		//	logging.LogErrorf("add attachment failed: %s", err)
+		//	return
+		//}
+		//
+		//err = os.Rename(outFile, inFile)
+		//if nil != err {
+		//	return
+		//}
+
+		assetLinks, listErr := api.ListAssetLinks(inFile)
+		if nil != listErr {
+			logging.LogErrorf("list asset links failed: %s", listErr)
+			return
+		}
+
+		pdfCtx, ctxErr := api.ReadContextFile(inFile)
+		if nil != ctxErr {
+			logging.LogErrorf("read pdf context failed: %s", ctxErr)
+			return
+		}
+
+		linkMap := map[int][]*pdfcpu.IndirectRef{}
+		//pdfCtx.RemoveAnnotations(nil, nil, nil, false)
+
+		for i, link := range assetLinks {
+			link.URI = strings.ReplaceAll(link.URI, "http://127.0.0.1:6806/export/temp/", "")
+			//if 1 > len(linkMap[link.Page]) {
+			//	linkMap[link.Page] = []pdfcpu.Annotation{link}
+			//} else {
+			//	linkMap[link.Page] = append(linkMap[link.Page], link)
+			//}
+
+			absPath, getErr := GetAssetAbsPath(link.URI)
+			if nil != getErr {
+				continue
+			}
+
+			ir, newErr := pdfCtx.XRefTable.NewEmbeddedFileStreamDict(absPath)
+			if nil != newErr {
+				logging.LogWarnf("new embedded file stream dict failed: %s", newErr)
+				continue
+			}
+
+			fn := filepath.Base(absPath)
+			fileSpecDict, newErr := pdfCtx.XRefTable.NewFileSpecDict(fn, pdfcpu.EncodeUTF16String(fn), "attached by SiYuan", *ir)
+			if nil != newErr {
+				logging.LogWarnf("new file spec dict failed: %s", newErr)
+				continue
+			}
+
+			ir, indErr := pdfCtx.XRefTable.IndRefForNewObject(fileSpecDict)
+			if nil != indErr {
+				logging.LogWarnf("ind ref for new object failed: %s", indErr)
+				continue
+			}
+
+			now := pdfcpu.StringLiteral(pdfcpu.DateString(time.Now()))
+			mediaBox := pdfcpu.RectForFormat("A4")
+			r := annotRect(i, mediaBox.Width(), mediaBox.Height(), 30, 80)
+			d := pdfcpu.Dict(
+				map[string]pdfcpu.Object{
+					"Type":         pdfcpu.Name("Annot"),
+					"Subtype":      pdfcpu.Name("FileAttachment"),
+					"Contents":     pdfcpu.StringLiteral("FileAttachment Annotation"),
+					"Rect":         r.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),
+					"CreationDate": now,
+					"Name":         pdfcpu.Name("FileAttachment"),
+					"FS":           *ir,
+					"NM":           pdfcpu.StringLiteral("SoundFileAttachmentAnnot"),
+				},
+			)
+
+			ann, indErr := pdfCtx.XRefTable.IndRefForNewObject(d)
+			if nil != indErr {
+				logging.LogWarnf("ind ref for new object failed: %s", indErr)
+				continue
+			}
+
+			pageDictIndRef, pageErr := pdfCtx.PageDictIndRef(link.Page)
+			if nil != pageErr {
+				logging.LogWarnf("page dict ind ref failed: %s", pageErr)
+				continue
+			}
+
+			d, defErr := pdfCtx.DereferenceDict(*pageDictIndRef)
+			if nil != defErr {
+				logging.LogWarnf("dereference dict failed: %s", defErr)
+				continue
+			}
+
+			if 1 > len(linkMap[link.Page]) {
+				linkMap[link.Page] = []*pdfcpu.IndirectRef{ann}
+			} else {
+				linkMap[link.Page] = append(linkMap[link.Page], ann)
+			}
+		}
+
+		for page, anns := range linkMap {
+			pageDictIndRef, pageErr := pdfCtx.PageDictIndRef(page)
+			if nil != pageErr {
+				logging.LogWarnf("page dict ind ref failed: %s", pageErr)
+				continue
+			}
+
+			pageDict, defErr := pdfCtx.DereferenceDict(*pageDictIndRef)
+			if nil != defErr {
+				logging.LogWarnf("dereference dict failed: %s", defErr)
+				continue
+			}
+
+			array := pdfcpu.Array{}
+			for _, ann := range anns {
+				array = append(array, *ann)
+			}
+
+			obj, found := pageDict.Find("Annots")
+			if !found {
+				pageDict.Insert("Annots", array)
+
+				pdfCtx.EnsureVersionForWriting()
+				continue
+			}
+
+			ir, ok := obj.(pdfcpu.IndirectRef)
+			if !ok {
+				pageDict.Update("Annots", append(obj.(pdfcpu.Array), array...))
+				pdfCtx.EnsureVersionForWriting()
+				continue
+			}
+
+			// Annots array is an IndirectReference.
+
+			o, err := pdfCtx.Dereference(ir)
+			if err != nil || o == nil {
+				continue
+			}
+
+			annots, _ := o.(pdfcpu.Array)
+			entry, ok := pdfCtx.FindTableEntryForIndRef(&ir)
+			if !ok {
+				continue
+			}
+			entry.Object = append(annots, array...)
+			pdfCtx.EnsureVersionForWriting()
+
+			//d.Insert("Annots", array)
+		}
+
+		if writeErr := api.WriteContextFile(pdfCtx, inFile); nil != writeErr {
+			logging.LogErrorf("write pdf context failed: %s", writeErr)
+			return
+		}
+	}
 
 	return
 }
 
+func annotRect(i int, w, h, d, l float64) *pdfcpu.Rectangle {
+	// d..distance between annotation rectangles
+	// l..side length of rectangle
+
+	// max number of rectangles fitting into w
+	xmax := int((w - d) / (l + d))
+
+	// max number of rectangles fitting into h
+	ymax := int((h - d) / (l + d))
+
+	col := float64(i % xmax)
+	row := float64(i / xmax % ymax)
+
+	llx := d + col*(l+d)
+	lly := d + row*(l+d)
+
+	urx := llx + l
+	ury := lly + l
+
+	return pdfcpu.Rect(llx, lly, urx, ury)
+}
+
 func ExportStdMarkdown(id string) string {
 	tree, err := loadTreeByBlockID(id)
 	if nil != err {