Compare commits

..
8 Commits
Author SHA1 Message Date
xiaoqidun 0887ff1a04 feat(前端界面): 优化单页适高滚动条
build.yaml / build (push) Successful in 47s
2026-08-24 09:37:21 +08:00
xiaoqidun 39f4fffeb7 feat(更新依赖): 更新jbig2、canvas
build.yaml / build (push) Successful in 44s
2026-08-23 21:25:54 +08:00
xiaoqidun e47d9bdffc feat(升级依赖): 升级jbig2、canvas
build.yaml / build (push) Successful in 3m26s
2026-08-21 23:00:56 +08:00
xiaoqidun 3d03448b6c feat(升级依赖): 更新jbig2和canvas
build.yaml / build (push) Successful in 41s
2026-07-28 11:52:42 +08:00
xiaoqidun 9462425a25 refactor(重构代码): 专注于性能提升 2026-07-28 11:12:21 +08:00
xiaoqidun efe3324d4b feat(系统字体): 添加android系统
build.yaml / build (push) Successful in 40s
2026-07-24 17:22:20 +08:00
xiaoqidun 78b66f85fe style(代码风格): 微调代码风格
build.yaml / build (push) Successful in 38s
2026-07-23 20:36:26 +08:00
xiaoqidun 0784a404f8 refactor(重构代码): 提升代码质量 2026-07-23 20:22:10 +08:00
19 changed files with 1315 additions and 474 deletions
+4
View File
@@ -401,6 +401,10 @@ body[data-hide-meta] .meta-panel {
background-size: 24px 24px; background-size: 24px 24px;
} }
.viewer-panel.single-page-fits-height {
overflow-y: hidden;
}
.empty-state { .empty-state {
position: absolute; position: absolute;
inset: 0; inset: 0;
+2
View File
@@ -2110,6 +2110,7 @@ function scrollbarWidth() {
function updateFitSpace() { function updateFitSpace() {
const page = currentPageInfo(); const page = currentPageInfo();
if (!page || state.fitMode === "free") { if (!page || state.fitMode === "free") {
el.viewerPanel.classList.remove("single-page-fits-height");
el.pageFrame.style.removeProperty("--fit-space"); el.pageFrame.style.removeProperty("--fit-space");
el.pageFrame.style.removeProperty("--fit-gap"); el.pageFrame.style.removeProperty("--fit-gap");
return; return;
@@ -2117,6 +2118,7 @@ function updateFitSpace() {
const shell = pageShell(state.pageIndex); const shell = pageShell(state.pageIndex);
const height = shell ? shell.getBoundingClientRect().height : Math.max(1, page.height * MM_TO_PX * state.scale); const height = shell ? shell.getBoundingClientRect().height : Math.max(1, page.height * MM_TO_PX * state.scale);
const base = pageSpace(); const base = pageSpace();
el.viewerPanel.classList.toggle("single-page-fits-height", state.doc.pageCount === 1 && height <= el.viewerPanel.clientHeight - base * 2);
const space = Math.max(base, (el.viewerPanel.clientHeight - height) / 2); const space = Math.max(base, (el.viewerPanel.clientHeight - height) / 2);
const gap = space > base ? space + 1 : space; const gap = space > base ? space + 1 : space;
el.pageFrame.style.setProperty("--fit-space", `${space}px`); el.pageFrame.style.setProperty("--fit-space", `${space}px`);
+13 -12
View File
@@ -1,13 +1,13 @@
module github.com/xiaoqidun/ofdgo module github.com/xiaoqidun/ofdgo
go 1.25.0 go 1.26.0
require ( require (
github.com/tdewolff/canvas v0.0.0-20260708151538-b3beae08c578 github.com/tdewolff/canvas v0.0.0-20260822211237-56e622de465a
github.com/tdewolff/font v0.0.0-20260527091451-1663e68cb8a4 github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117
github.com/xiaoqidun/jbig2 v0.0.0-20260714021516-ffe617d6fbc8 github.com/xiaoqidun/jbig2 v0.0.0-20260823111534-2e67bcec7059
golang.org/x/image v0.44.0 golang.org/x/image v0.45.0
golang.org/x/text v0.40.0 golang.org/x/text v0.41.0
) )
require ( require (
@@ -19,7 +19,7 @@ require (
github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect
github.com/Kagami/go-avif v0.1.0 // indirect github.com/Kagami/go-avif v0.1.0 // indirect
github.com/andybalholm/brotli v1.2.1 // indirect github.com/andybalholm/brotli v1.2.2 // indirect
github.com/benoitkugler/textlayout v0.3.2 // indirect github.com/benoitkugler/textlayout v0.3.2 // indirect
github.com/benoitkugler/textprocessing v0.0.6 // indirect github.com/benoitkugler/textprocessing v0.0.6 // indirect
github.com/go-fonts/latin-modern v0.3.3 // indirect github.com/go-fonts/latin-modern v0.3.3 // indirect
@@ -28,13 +28,14 @@ require (
github.com/kolesa-team/go-webp v1.0.5 // indirect github.com/kolesa-team/go-webp v1.0.5 // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // indirect github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 // indirect
github.com/tdewolff/minify/v2 v2.24.13 // indirect github.com/tdewolff/minify/v2 v2.24.17 // indirect
github.com/tdewolff/parse/v2 v2.8.13 // indirect github.com/tdewolff/parse/v2 v2.8.16 // indirect
github.com/wcharczuk/go-chart/v2 v2.1.2 // indirect github.com/wcharczuk/go-chart/v2 v2.1.2 // indirect
github.com/yuin/goldmark v1.8.2 // indirect github.com/yuin/goldmark v1.8.5 // indirect
golang.org/x/net v0.55.0 // indirect golang.org/x/net v0.58.0 // indirect
golang.org/x/sys v0.47.0 // indirect
gonum.org/v1/plot v0.17.0 // indirect gonum.org/v1/plot v0.17.0 // indirect
modernc.org/knuth v0.5.5 // indirect modernc.org/knuth v0.6.0 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
star-tex.org/x/tex v0.7.1 // indirect star-tex.org/x/tex v0.7.1 // indirect
) )
+32 -32
View File
@@ -1,15 +1,15 @@
codeberg.org/go-fonts/dejavu v0.4.0 h1:2yn58Vkh4CFK3ipacWUAIE3XVBGNa0y1bc95Bmfx91I= codeberg.org/go-fonts/dejavu v0.4.0 h1:2yn58Vkh4CFK3ipacWUAIE3XVBGNa0y1bc95Bmfx91I=
codeberg.org/go-fonts/dejavu v0.4.0/go.mod h1:abni088lmhQJvso2Lsb7azCKzwkfcnttl6tL1UTWKzg= codeberg.org/go-fonts/dejavu v0.4.0/go.mod h1:abni088lmhQJvso2Lsb7azCKzwkfcnttl6tL1UTWKzg=
codeberg.org/go-fonts/liberation v0.5.0 h1:SsKoMO1v1OZmzkG2DY+7ZkCL9U+rrWI09niOLfQ5Bo0= codeberg.org/go-fonts/liberation v0.6.0 h1:15Gh6SdwYve22CWCm9jYpVpRuaTh726av2TgHTHvAtQ=
codeberg.org/go-fonts/liberation v0.5.0/go.mod h1:zS/2e1354/mJ4pGzIIaEtm/59VFCFnYC7YV6YdGl5GU= codeberg.org/go-fonts/liberation v0.6.0/go.mod h1:J15VAa+lyxdcI/Je7lDDDl6QOhLk9feNBnnwXqEHXOk=
codeberg.org/go-latex/latex v0.3.0 h1:LKTaDHFbEC2PH1sh0sYv6PZ1pzs/g2aoeV1HItWj/bg= codeberg.org/go-latex/latex v0.3.0 h1:LKTaDHFbEC2PH1sh0sYv6PZ1pzs/g2aoeV1HItWj/bg=
codeberg.org/go-latex/latex v0.3.0/go.mod h1:8ETijTpK2bFtwRAXLXe1RZJrYxnc5pibibZfQBj+Lk4= codeberg.org/go-latex/latex v0.3.0/go.mod h1:8ETijTpK2bFtwRAXLXe1RZJrYxnc5pibibZfQBj+Lk4=
codeberg.org/go-pdf/fpdf v0.12.0 h1:g8E/1VqGqB2lZUUaqQrrTnA0IEJLPTTX1DZ0qS/ZmhU= codeberg.org/go-pdf/fpdf v0.12.0 h1:g8E/1VqGqB2lZUUaqQrrTnA0IEJLPTTX1DZ0qS/ZmhU=
codeberg.org/go-pdf/fpdf v0.12.0/go.mod h1:WJNJ2bvCj81rZBdhOf7lKOGoSl+OKMXcIcXqDcP8r5Y= codeberg.org/go-pdf/fpdf v0.12.0/go.mod h1:WJNJ2bvCj81rZBdhOf7lKOGoSl+OKMXcIcXqDcP8r5Y=
git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo=
git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE=
git.sr.ht/~sbinet/gg v0.7.0 h1:YmNf7YKd7diDMTPm86hZa1EM3pbkOyD/zzjl0LZUdNM= git.sr.ht/~sbinet/gg v0.8.0 h1:PjQ4AgUWRz7Dy6PVBKMLLg96eRCos1U+H6F3FJuKWHo=
git.sr.ht/~sbinet/gg v0.7.0/go.mod h1:VYeli15tpMM4EvqlivlVbbyvWZlOU+EZn4XZmfBGUdM= git.sr.ht/~sbinet/gg v0.8.0/go.mod h1:XhlGCvSXts+BaZ1XMZLxLSFUDvwsJ/2El5F6XzPep6o=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g= github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=
@@ -24,8 +24,8 @@ github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w=
github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA= github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= github.com/andybalholm/brotli v1.2.2 h1:HzTuoo2ErYQqf5qvcJInB8uvqSVxRttzkFexPWtnceM=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/brotli v1.2.2/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/benoitkugler/textlayout v0.3.2 h1:Y/LTkwpQ9G0Fia9yhPmZA9IR5AnE8Cq30j3C+Gx5/IE= github.com/benoitkugler/textlayout v0.3.2 h1:Y/LTkwpQ9G0Fia9yhPmZA9IR5AnE8Cq30j3C+Gx5/IE=
github.com/benoitkugler/textlayout v0.3.2/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= github.com/benoitkugler/textlayout v0.3.2/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
@@ -33,8 +33,6 @@ github.com/benoitkugler/textlayout-testdata v0.1.1/go.mod h1:i/qZl09BbUOtd7Bu/W1
github.com/benoitkugler/textprocessing v0.0.6 h1:obkMyj62GEPg3xUVYqROlCN22z1OleuZm6ULqX9Om1g= github.com/benoitkugler/textprocessing v0.0.6 h1:obkMyj62GEPg3xUVYqROlCN22z1OleuZm6ULqX9Om1g=
github.com/benoitkugler/textprocessing v0.0.6/go.mod h1:Io0gN08/PXEzrSOWFa88xHx2Xv3VjvLMY7H76YoI23A= github.com/benoitkugler/textprocessing v0.0.6/go.mod h1:Io0gN08/PXEzrSOWFa88xHx2Xv3VjvLMY7H76YoI23A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-fonts/latin-modern v0.3.3 h1:g2xNgI8yzdNzIVm+qvbMryB6yGPe0pSMss8QT3QwlJ0= github.com/go-fonts/latin-modern v0.3.3 h1:g2xNgI8yzdNzIVm+qvbMryB6yGPe0pSMss8QT3QwlJ0=
github.com/go-fonts/latin-modern v0.3.3/go.mod h1:tHaiWDGze4EPB0Go4cLT5M3QzRY3peya09Z/8KSCrpY= github.com/go-fonts/latin-modern v0.3.3/go.mod h1:tHaiWDGze4EPB0Go4cLT5M3QzRY3peya09Z/8KSCrpY=
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU= github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
@@ -59,27 +57,29 @@ github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388 h1:ZdkidVdpLW13BQ9a+
github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388/go.mod h1:C/WY5lmWfMtPFYYBTd3Lzdn4FTLr+RxlIeiBNye+/os= github.com/srwiley/scanx v0.0.0-20190309010443-e94503791388/go.mod h1:C/WY5lmWfMtPFYYBTd3Lzdn4FTLr+RxlIeiBNye+/os=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/tdewolff/canvas v0.0.0-20260708151538-b3beae08c578 h1:OKVthD6KmBTDWsxnPkmDLW35yp+lvoqWsMeteXOZW+4= github.com/tdewolff/canvas v0.0.0-20260822211237-56e622de465a h1:riaFBiYaIv1gM31xdcU0Yo7bdp3WebF4dggO/JK0g/8=
github.com/tdewolff/canvas v0.0.0-20260708151538-b3beae08c578/go.mod h1:Lz12ClttuKvo2jb1UxBfJJWmKV0LmEc8IRo/30R7z+k= github.com/tdewolff/canvas v0.0.0-20260822211237-56e622de465a/go.mod h1:K9Ux3cwFtyAFRITZ46g7v+ORXgI3ENtQa5rL9yjNz5Y=
github.com/tdewolff/font v0.0.0-20260527091451-1663e68cb8a4 h1:QmSG/RtOlLPJDLJ24XHSDEu4iSgcG+s9sdRg7wTdfpk= github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117 h1:5vSJjsbV9AH0U+Ex0uej8ROrpOBlOVBrw0mGlVef7oo=
github.com/tdewolff/font v0.0.0-20260527091451-1663e68cb8a4/go.mod h1:tQzLTuJVLs4Pp6Vxdn3PJ7sAe4gIuph3SBm/BmdlmR0= github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117/go.mod h1:ipESUihcEdgRBhIwTRHmBTOMamccMQrp4iCxfa1aFws=
github.com/tdewolff/minify/v2 v2.24.13 h1:xrcF7gKDnUszseEY9WX9mUlZII2v2Go/QAcAwRASw58= github.com/tdewolff/minify/v2 v2.24.17 h1:6AbitfVyq0M7aW6i+XL7+49DeTQZwloOMs9O574arBg=
github.com/tdewolff/minify/v2 v2.24.13/go.mod h1:emvwoYeIl8bfAKqRU5ww95LX9Gpggpqv/naal9a8Yq0= github.com/tdewolff/minify/v2 v2.24.17/go.mod h1:kVqn9vxXUKtlHexSNrWbYePqioOT5mc4ou/KVSMpfCM=
github.com/tdewolff/parse/v2 v2.8.13 h1:si/8rLw5BZZTWCCiMm9A3f6x+RmqYfrkEeXCgpX5ick= github.com/tdewolff/parse/v2 v2.8.16 h1:bLk5svUOQRkW/Y2SJ+DeENSIkZBcTIkq+Atyv5D8feI=
github.com/tdewolff/parse/v2 v2.8.13/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ= github.com/tdewolff/parse/v2 v2.8.16/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ=
github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= 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/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
github.com/xiaoqidun/jbig2 v0.0.0-20260714021516-ffe617d6fbc8 h1:HSruvQiN+t72xcn4VQyvB4GRCyX0dnu3+6k4DFnmPvM= github.com/xiaoqidun/jbig2 v0.0.0-20260823111534-2e67bcec7059 h1:/JXxWbNTePSVfshS7BVRm75Kh1/5rixvLpgTRw7MMv8=
github.com/xiaoqidun/jbig2 v0.0.0-20260714021516-ffe617d6fbc8/go.mod h1:4ieZvd1EYR3zUzaAT4H8vLDb6XO+yjnxuh33So/5iC4= github.com/xiaoqidun/jbig2 v0.0.0-20260823111534-2e67bcec7059/go.mod h1:mdoUQnVPIA4yS0/+ZrtxJNiDdbphI0RMu+DPfxXfVhE=
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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark v1.8.5/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
@@ -87,8 +87,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -103,8 +103,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -122,6 +122,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -140,8 +142,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -153,10 +155,8 @@ gonum.org/v1/plot v0.17.0 h1:d0DwPVBe9jnEGqQBoZGl/P2M9WciJbG2CnV59C9QBT4=
gonum.org/v1/plot v0.17.0/go.mod h1:ipt2GUN1oqzr2O7wCjLDtw1ShfIYYNBp4o0O1Ez5B3Y= gonum.org/v1/plot v0.17.0/go.mod h1:ipt2GUN1oqzr2O7wCjLDtw1ShfIYYNBp4o0O1Ez5B3Y=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= modernc.org/knuth v0.6.0 h1:zLv0hzgdcpcCUB/iMwpQvn9m/iciqO/hyNXlpWymMKU=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/knuth v0.6.0/go.mod h1:ISAZV+URZIlE8h1BwNVg1NzpwHKndV5lQ13NPPj3tds=
modernc.org/knuth v0.5.5 h1:6lap2U/ISm8aC/4NU58ALFCRllNPaK0EZcIGY/oDgUg=
modernc.org/knuth v0.5.5/go.mod h1:e5SBb35HQBj2aFwbBO3ClPcViLY3Wi0LzaOd7c/3qMk=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
+1
View File
@@ -76,6 +76,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
fontCache: make(map[fontCacheKey]*canvas.FontFamily), fontCache: make(map[fontCacheKey]*canvas.FontFamily),
fontSourceCache: make(map[string][]fontSource), fontSourceCache: make(map[string][]fontSource),
fontSourceUsed: make(map[string]fontSource), fontSourceUsed: make(map[string]fontSource),
fontDirCandidates: make(map[string][]fontFileCandidate),
textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue), textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue),
templatePageCache: make(map[string]*PageContent), templatePageCache: make(map[string]*PageContent),
} }
+198
View File
@@ -0,0 +1,198 @@
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"encoding/xml"
"strconv"
)
// Action 动作
type Action struct {
Event string `xml:"Event,attr"`
Region *Region `xml:"Region"`
Goto *Goto `xml:"Goto"`
URI *URI `xml:"URI"`
GotoA *GotoA `xml:"GotoA"`
Sound *Sound `xml:"Sound"`
Movie *Movie `xml:"Movie"`
}
// Goto 文档内跳转动作
type Goto struct {
Dest *Dest `xml:"Dest"`
Bookmark *GotoBookmark `xml:"Bookmark"`
}
// Dest 文档内跳转目标
type Dest struct {
Type string `xml:"Type,attr"`
PageID string `xml:"PageID,attr"`
Left float64 `xml:"Left"`
Right float64 `xml:"Right"`
Top float64 `xml:"Top"`
Bottom float64 `xml:"Bottom"`
Zoom float64 `xml:"Zoom"`
}
// GotoBookmark 书签跳转目标
type GotoBookmark struct {
Name string `xml:"Name,attr"`
}
// URI URI动作
type URI struct {
URI string `xml:"URI,attr"`
Base string `xml:"Base,attr"`
}
// GotoA 附件动作
type GotoA struct {
AttachID string `xml:"AttachID,attr"`
NewWindow *bool `xml:"NewWindow,attr"`
}
// Sound 音频动作
type Sound struct {
ResourceID string `xml:"ResourceID,attr"`
Volume *int `xml:"Volume,attr"`
Repeat bool `xml:"Repeat,attr"`
Synchronous bool `xml:"Synchronous,attr"`
}
// Movie 视频动作
type Movie struct {
ResourceID string `xml:"ResourceID,attr"`
Operator string `xml:"Operator,attr"`
}
// Region 动作区域
type Region struct {
Area []RegionArea `xml:"Area"`
}
// RegionArea 动作区域分路径
type RegionArea struct {
Start string `xml:"Start,attr"`
Command []RegionCommand `xml:"-"`
}
// RegionCommand 动作区域绘制指令
type RegionCommand struct {
Type string
Point1 string
Point2 string
Point3 string
EllipseSize string
RotationAngle string
LargeArc string
SweepDirection string
EndPoint string
}
// UnmarshalXML 解析跳转目标
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (dest *Dest) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value struct {
Left *float64 `xml:"Left"`
Right *float64 `xml:"Right"`
Top *float64 `xml:"Top"`
Bottom *float64 `xml:"Bottom"`
Zoom *float64 `xml:"Zoom"`
}
dest.Type = attrValue(start, "Type")
dest.PageID = attrValue(start, "PageID")
dest.Left = actionFloatAttr(start, "Left")
dest.Right = actionFloatAttr(start, "Right")
dest.Top = actionFloatAttr(start, "Top")
dest.Bottom = actionFloatAttr(start, "Bottom")
dest.Zoom = actionFloatAttr(start, "Zoom")
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
if value.Left != nil {
dest.Left = *value.Left
}
if value.Right != nil {
dest.Right = *value.Right
}
if value.Top != nil {
dest.Top = *value.Top
}
if value.Bottom != nil {
dest.Bottom = *value.Bottom
}
if value.Zoom != nil {
dest.Zoom = *value.Zoom
}
return nil
}
// UnmarshalXML 解析视频动作并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (m *Movie) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type movie Movie
value := movie{Operator: "Play"}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*m = Movie(value)
return nil
}
// UnmarshalXML 解析动作区域分路径并保留指令顺序
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *RegionArea) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*a = RegionArea{Start: attrValue(start, "Start")}
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
command := RegionCommand{
Type: node.Name.Local,
Point1: attrValue(node, "Point1"),
Point2: attrValue(node, "Point2"),
Point3: attrValue(node, "Point3"),
EllipseSize: attrValue(node, "EllipseSize"),
RotationAngle: attrValue(node, "RotationAngle"),
LargeArc: attrValue(node, "LargeArc"),
SweepDirection: attrValue(node, "SweepDirection"),
EndPoint: attrValue(node, "EndPoint"),
}
a.Command = append(a.Command, command)
if err := d.Skip(); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
}
// actionFloatAttr 获取动作浮点属性
// 入参: start 起始节点, name 属性名
// 返回: float64 属性值
func actionFloatAttr(start xml.StartElement, name string) float64 {
value, _ := strconv.ParseFloat(attrValue(start, name), 64)
return value
}
+196 -17
View File
@@ -23,26 +23,32 @@ type Document struct {
Pages Pages `xml:"Pages"` Pages Pages `xml:"Pages"`
Outlines Outlines `xml:"Outlines"` Outlines Outlines `xml:"Outlines"`
Permissions Permissions `xml:"Permissions"` Permissions Permissions `xml:"Permissions"`
Actions []Action `xml:"Actions>Action"`
Bookmarks Bookmarks `xml:"Bookmarks"`
Annotations string `xml:"Annotations"` Annotations string `xml:"Annotations"`
Signatures string `xml:"Signatures"` Signatures string `xml:"Signatures"`
Attachments Attachments `xml:"Attachments"` Attachments Attachments `xml:"Attachments"`
CustomTags CustomTags `xml:"CustomTags"`
Extensions Extensions `xml:"Extensions"` Extensions Extensions `xml:"Extensions"`
} }
// Extensions 扩展集合 // Extensions 扩展集合
type Extensions struct { type Extensions struct {
XMLName xml.Name `xml:"Extensions"`
Path string `xml:",chardata"`
Extension []Extension `xml:"Extension"` Extension []Extension `xml:"Extension"`
} }
// Extension 扩展信息 // Extension 扩展信息
type Extension struct { type Extension struct {
AppName string `xml:"AppName,attr"` AppName string `xml:"AppName,attr"`
Company string `xml:"Company,attr"` Company string `xml:"Company,attr"`
AppVersion string `xml:"AppVersion,attr"` AppVersion string `xml:"AppVersion,attr"`
Date string `xml:"Date,attr"` Date string `xml:"Date,attr"`
RefID string `xml:"RefId,attr"` RefID string `xml:"RefId,attr"`
Property []Property `xml:"Property"` Property []Property `xml:"Property"`
Data string `xml:"Data"` ExtendData []string `xml:"ExtendData"`
Data []ExtensionData `xml:"Data"`
} }
// Property 扩展属性 // Property 扩展属性
@@ -52,16 +58,45 @@ type Property struct {
Type string `xml:"Type,attr"` Type string `xml:"Type,attr"`
} }
// ExtensionData 扩展数据
type ExtensionData struct {
Attr []xml.Attr `xml:",any,attr"`
Content string `xml:",innerxml"`
}
// Attachments 附件集合 // Attachments 附件集合
type Attachments struct { type Attachments struct {
XMLName xml.Name `xml:"Attachments"`
Path string `xml:",chardata"`
Attachment []Attachment `xml:"Attachment"` Attachment []Attachment `xml:"Attachment"`
} }
// Attachment 附件信息 // Attachment 附件信息
type Attachment struct { type Attachment struct {
Name string `xml:"Name,attr"` ID string `xml:"ID,attr"`
File string `xml:"File,attr"` Name string `xml:"Name,attr"`
ID string `xml:"ID,attr"` Format string `xml:"Format,attr"`
CreationDate string `xml:"CreationDate,attr"`
ModDate string `xml:"ModDate,attr"`
Size *float64 `xml:"Size,attr"`
Visible bool `xml:"Visible,attr"`
Usage string `xml:"Usage,attr"`
FileLoc string `xml:"FileLoc"`
}
// CustomTags 自定义标引集合
type CustomTags struct {
XMLName xml.Name `xml:"CustomTags"`
Path string `xml:",chardata"`
CustomTag []CustomTag `xml:"CustomTag"`
}
// CustomTag 自定义标引
type CustomTag struct {
TypeID string `xml:"TypeID,attr"`
NameSpace string `xml:"NameSpace,attr"`
SchemaLoc string `xml:"SchemaLoc"`
FileLoc string `xml:"FileLoc"`
} }
// CommonData 文档公共数据 // CommonData 文档公共数据
@@ -101,6 +136,17 @@ type TemplatePage struct {
ZOrder string `xml:"ZOrder,attr"` ZOrder string `xml:"ZOrder,attr"`
} }
// Bookmarks 书签集合
type Bookmarks struct {
Bookmark []Bookmark `xml:"Bookmark"`
}
// Bookmark 书签
type Bookmark struct {
Name string `xml:"Name,attr"`
Dest Dest `xml:"Dest"`
}
// Outlines 大纲集合 // Outlines 大纲集合
type Outlines struct { type Outlines struct {
OutlineElem []OutlineElem `xml:"OutlineElem"` OutlineElem []OutlineElem `xml:"OutlineElem"`
@@ -108,15 +154,148 @@ type Outlines struct {
// OutlineElem 大纲节点 // OutlineElem 大纲节点
type OutlineElem struct { type OutlineElem struct {
Title string `xml:"Title,attr"` Title string `xml:"Title,attr"`
Count int `xml:"Count,attr"` Count int `xml:"Count,attr"`
Actions string `xml:"Actions"` Expanded bool `xml:"Expanded,attr"`
Actions []Action `xml:"Actions>Action"`
OutlineElem []OutlineElem `xml:"OutlineElem"`
} }
// Permissions 权限声明 // Permissions 权限声明
type Permissions struct { type Permissions struct {
Edit bool `xml:"Edit"` Edit bool `xml:"Edit"`
Print bool `xml:"Print"` Annot bool `xml:"Annot"`
Export bool `xml:"Export"` Export bool `xml:"Export"`
Copy bool `xml:"Copy"` Signature bool `xml:"Signature"`
Watermark bool `xml:"Watermark"`
PrintScreen bool `xml:"PrintScreen"`
Print bool `xml:"-"`
Copies int `xml:"-"`
Copy bool `xml:"CopyText"`
ContentRegist bool `xml:"ContentRegist"`
ValidPeriod *ValidPeriod `xml:"ValidPeriod"`
}
// ValidPeriod 文档访问有效期
type ValidPeriod struct {
StartDate string `xml:"StartDate,attr"`
EndDate string `xml:"EndDate,attr"`
}
// UnmarshalXML 解析文档并应用权限默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (doc *Document) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type document Document
value := document{Permissions: defaultPermissions()}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*doc = Document(value)
return nil
}
// UnmarshalXML 解析附件并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *Attachment) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type attachment Attachment
value := attachment{Visible: true, Usage: "none"}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*a = Attachment(value)
return nil
}
// UnmarshalXML 解析大纲节点并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (o *OutlineElem) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type outlineElem OutlineElem
value := outlineElem{Expanded: true}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*o = OutlineElem(value)
return nil
}
// UnmarshalXML 解析文档权限并应用默认值
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (p *Permissions) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var value struct {
Edit *bool `xml:"Edit"`
Annot *bool `xml:"Annot"`
Export *bool `xml:"Export"`
Signature *bool `xml:"Signature"`
Watermark *bool `xml:"Watermark"`
PrintScreen *bool `xml:"PrintScreen"`
Copy *bool `xml:"CopyText"`
ContentRegist *bool `xml:"ContentRegist"`
Print *print `xml:"Print"`
ValidPeriod *ValidPeriod `xml:"ValidPeriod"`
}
if err := d.DecodeElement(&value, &start); err != nil {
return err
}
*p = defaultPermissions()
if value.Edit != nil {
p.Edit = *value.Edit
}
if value.Annot != nil {
p.Annot = *value.Annot
}
if value.Export != nil {
p.Export = *value.Export
}
if value.Signature != nil {
p.Signature = *value.Signature
}
if value.Watermark != nil {
p.Watermark = *value.Watermark
}
if value.PrintScreen != nil {
p.PrintScreen = *value.PrintScreen
}
if value.Copy != nil {
p.Copy = *value.Copy
}
if value.ContentRegist != nil {
p.ContentRegist = *value.ContentRegist
}
if value.Print != nil {
if value.Print.Printable != nil {
p.Print = *value.Print.Printable
}
if value.Print.Copies != nil {
p.Copies = *value.Print.Copies
}
}
p.ValidPeriod = value.ValidPeriod
return nil
}
// print 打印权限节点
type print struct {
Printable *bool `xml:"Printable,attr"`
Copies *int `xml:"Copies,attr"`
}
// defaultPermissions 获取默认文档权限
// 返回: Permissions 文档权限
func defaultPermissions() Permissions {
return Permissions{
Edit: true,
Annot: true,
Export: true,
Signature: true,
Watermark: true,
PrintScreen: true,
Print: true,
Copies: -1,
Copy: true,
ContentRegist: true,
}
} }
+23 -12
View File
@@ -162,10 +162,14 @@ func (r *Renderer) fontSources(fontID string, font *Font, style canvas.FontStyle
r.fontSourceCache[fontID] = sources r.fontSourceCache[fontID] = sources
return sources return sources
} }
systemDirs := systemFontDirs()
for _, name := range []string{font.FamilyName, font.FontName} { for _, name := range []string{font.FamilyName, font.FontName} {
for _, systemName := range fontSystemNames(name) { for _, systemName := range fontSystemNames(name) {
for _, match := range r.matchFontFiles(systemFontDir(), fontFilePatterns(systemName), bold, italic) { systemPatterns := fontFilePatterns(systemName)
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match, exact: true}) for _, dir := range systemDirs {
for _, match := range r.matchFontFiles(dir, systemPatterns, bold, italic) {
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceFile, name: match, exact: true})
}
} }
sources = appendFontSource(sources, seen, fontSource{kind: fontSourceSystem, name: systemName, exact: true}) sources = appendFontSource(sources, seen, fontSource{kind: fontSourceSystem, name: systemName, exact: true})
} }
@@ -246,8 +250,12 @@ func (r *Renderer) loadFontSource(family *canvas.FontFamily, source fontSource,
// 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体 // 入参: dir 目录, patterns 模式列表, bold 是否粗体, italic 是否斜体
// 返回: []string 文件列表 // 返回: []string 文件列表
func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string { func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bool) []string {
files, _ := filepath.Glob(filepath.Join(dir, "*")) candidates, ok := r.fontDirCandidates[dir]
candidates := fontFileCandidates(files, filepath.Base) if !ok {
files, _ := filepath.Glob(filepath.Join(dir, "*"))
candidates = fontFileCandidates(files, filepath.Base)
r.fontDirCandidates[dir] = candidates
}
matches := make([]fontFileMatch, 0, len(candidates)) matches := make([]fontFileMatch, 0, len(candidates))
index := make(map[string]int, len(candidates)) index := make(map[string]int, len(candidates))
for _, matcher := range newFontPatternMatchers(patterns) { for _, matcher := range newFontPatternMatchers(patterns) {
@@ -260,15 +268,18 @@ func (r *Renderer) matchFontFiles(dir string, patterns []string, bold, italic bo
return fontFileMatchNames(matches) return fontFileMatchNames(matches)
} }
// systemFontDir 获取系统字体目录 // systemFontDirs 获取系统字体目录
// 返回: string 字体目录 // 返回: []string 字体目录列表
func systemFontDir() string { func systemFontDirs() []string {
switch runtime.GOOS { switch runtime.GOOS {
case "linux": case "android":
return `/usr/share/fonts` return []string{"/product/fonts", "/system/fonts"}
case "darwin": case "darwin":
return `/Library/Fonts` return []string{`/Library/Fonts`}
default: case "linux":
return `C:\Windows\Fonts` return []string{`/usr/share/fonts`}
case "windows":
return []string{`C:\Windows\Fonts`}
} }
return nil
} }
+129 -280
View File
@@ -19,6 +19,76 @@ import (
"strconv" "strconv"
) )
// graphicObjectTarget 图形对象集合
type graphicObjectTarget struct {
objects *[]GraphicObject
text *[]TextObject
path *[]PathObject
image *[]ImageObject
composite *[]CompositeGraphicUnit
}
// decodeGraphicObject 解析图形对象
// 入参: d XML解码器, start 起始节点, target 图形对象集合
// 返回: bool 是否为图形对象, error 错误信息
func decodeGraphicObject(d *xml.Decoder, start xml.StartElement, target graphicObjectTarget) (bool, error) {
switch start.Name.Local {
case "TextObject":
var obj TextObject
if err := d.DecodeElement(&obj, &start); err != nil {
return true, err
}
*target.text = append(*target.text, obj)
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
case "PathObject":
var obj PathObject
if err := d.DecodeElement(&obj, &start); err != nil {
return true, err
}
*target.path = append(*target.path, obj)
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
case "ImageObject":
var obj ImageObject
if err := d.DecodeElement(&obj, &start); err != nil {
return true, err
}
*target.image = append(*target.image, obj)
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
case "CompositeGraphicUnit", "CompositeObject":
var obj CompositeGraphicUnit
if err := d.DecodeElement(&obj, &start); err != nil {
return true, err
}
*target.composite = append(*target.composite, obj)
*target.objects = append(*target.objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
default:
return false, nil
}
return true, nil
}
// decodeObjectContainer 解析图形对象容器
// 入参: d XML解码器, start 起始节点, decode 对象解码函数
// 返回: error 错误信息
func decodeObjectContainer(d *xml.Decoder, start xml.StartElement, decode func(*xml.Decoder, xml.StartElement) error) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := decode(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
}
// UnmarshalXML 解析图层并保留对象顺序 // UnmarshalXML 解析图层并保留对象顺序
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
@@ -26,85 +96,27 @@ func (l *Layer) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*l = Layer{} *l = Layer{}
l.ID = attrValue(start, "ID") l.ID = attrValue(start, "ID")
l.DrawParam = attrValue(start, "DrawParam") l.DrawParam = attrValue(start, "DrawParam")
for { return decodeObjectContainer(d, start, l.decodeObject)
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := l.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
} }
// decodeObject 解析图层子对象 // decodeObject 解析图层子对象
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
func (l *Layer) decodeObject(d *xml.Decoder, start xml.StartElement) error { func (l *Layer) decodeObject(d *xml.Decoder, start xml.StartElement) error {
switch start.Name.Local { target := graphicObjectTarget{
case "TextObject": objects: &l.Objects,
var obj TextObject text: &l.TextObject,
if err := d.DecodeElement(&obj, &start); err != nil { path: &l.PathObject,
return err image: &l.ImageObject,
} composite: &l.CompositeGraphicUnit,
l.TextObject = append(l.TextObject, obj)
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
case "PathObject":
var obj PathObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
l.PathObject = append(l.PathObject, obj)
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
case "ImageObject":
var obj ImageObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
l.ImageObject = append(l.ImageObject, obj)
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
case "CompositeGraphicUnit", "CompositeObject":
var obj CompositeGraphicUnit
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
l.CompositeGraphicUnit = append(l.CompositeGraphicUnit, obj)
l.Objects = append(l.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
case "PageBlock":
return l.decodePageBlock(d, start)
default:
return d.Skip()
} }
return nil if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
} return err
// decodePageBlock 解析页块子对象
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (l *Layer) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := l.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
} }
if start.Name.Local == "PageBlock" {
return decodeObjectContainer(d, start, l.decodeObject)
}
return d.Skip()
} }
// UnmarshalXML 解析复合图元并保留对象顺序 // UnmarshalXML 解析复合图元并保留对象顺序
@@ -128,178 +140,73 @@ func (c *CompositeGraphicUnit) UnmarshalXML(d *xml.Decoder, start xml.StartEleme
c.Visible = &visible c.Visible = &visible
} }
} }
for { return decodeObjectContainer(d, start, c.decodeObject)
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := c.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
} }
// decodeObject 解析复合图元子对象 // decodeObject 解析复合图元子对象
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
func (c *CompositeGraphicUnit) decodeObject(d *xml.Decoder, start xml.StartElement) error { func (c *CompositeGraphicUnit) decodeObject(d *xml.Decoder, start xml.StartElement) error {
target := graphicObjectTarget{
objects: &c.Objects,
text: &c.TextObject,
path: &c.PathObject,
image: &c.ImageObject,
composite: &c.CompositeGraphicUnit,
}
if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
return err
}
switch start.Name.Local { switch start.Name.Local {
case "TextObject":
var obj TextObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
c.TextObject = append(c.TextObject, obj)
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
case "PathObject":
var obj PathObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
c.PathObject = append(c.PathObject, obj)
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
case "ImageObject":
var obj ImageObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
c.ImageObject = append(c.ImageObject, obj)
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
case "CompositeGraphicUnit", "CompositeObject":
var obj CompositeGraphicUnit
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
c.CompositeGraphicUnit = append(c.CompositeGraphicUnit, obj)
c.Objects = append(c.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
case "Clips": case "Clips":
var clips Clips var clips Clips
if err := d.DecodeElement(&clips, &start); err != nil { if err := d.DecodeElement(&clips, &start); err != nil {
return err return err
} }
c.Clips = &clips c.Clips = &clips
case "Actions":
var actions struct {
Action []Action `xml:"Action"`
}
if err := d.DecodeElement(&actions, &start); err != nil {
return err
}
c.Actions = actions.Action
case "Content", "PageBlock": case "Content", "PageBlock":
return c.decodePageBlock(d, start) return decodeObjectContainer(d, start, c.decodeObject)
default: default:
return d.Skip() return d.Skip()
} }
return nil return nil
} }
// decodePageBlock 解析页块子对象
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (c *CompositeGraphicUnit) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := c.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
}
// UnmarshalXML 解析注释外观并保留对象顺序 // UnmarshalXML 解析注释外观并保留对象顺序
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (a *Appearance) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*a = Appearance{} *a = Appearance{}
a.Boundary = attrValue(start, "Boundary") a.Boundary = attrValue(start, "Boundary")
for { return decodeObjectContainer(d, start, a.decodeObject)
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := a.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
} }
// decodeObject 解析注释外观子对象 // decodeObject 解析注释外观子对象
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
func (a *Appearance) decodeObject(d *xml.Decoder, start xml.StartElement) error { func (a *Appearance) decodeObject(d *xml.Decoder, start xml.StartElement) error {
switch start.Name.Local { target := graphicObjectTarget{
case "TextObject": objects: &a.Objects,
var obj TextObject text: &a.TextObject,
if err := d.DecodeElement(&obj, &start); err != nil { path: &a.PathObject,
return err image: &a.ImageObject,
} composite: &a.CompositeGraphicUnit,
a.TextObject = append(a.TextObject, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
case "PathObject":
var obj PathObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.PathObject = append(a.PathObject, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
case "ImageObject":
var obj ImageObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.ImageObject = append(a.ImageObject, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
case "CompositeGraphicUnit", "CompositeObject":
var obj CompositeGraphicUnit
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
a.CompositeGraphicUnit = append(a.CompositeGraphicUnit, obj)
a.Objects = append(a.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
case "PageBlock":
return a.decodePageBlock(d, start)
default:
return d.Skip()
} }
return nil if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
} return err
// decodePageBlock 解析注释外观页块子对象
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (a *Appearance) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := a.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
} }
if start.Name.Local == "PageBlock" {
return decodeObjectContainer(d, start, a.decodeObject)
}
return d.Skip()
} }
// UnmarshalXML 解析图案单元内容并保留对象顺序 // UnmarshalXML 解析图案单元内容并保留对象顺序
@@ -307,85 +214,27 @@ func (a *Appearance) decodePageBlock(d *xml.Decoder, start xml.StartElement) err
// 返回: error 错误信息 // 返回: error 错误信息
func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { func (p *PatternContent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
*p = PatternContent{} *p = PatternContent{}
for { return decodeObjectContainer(d, start, p.decodeObject)
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := p.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
}
} }
// decodeObject 解析图案单元内容子对象 // decodeObject 解析图案单元内容子对象
// 入参: d XML解码器, start 起始节点 // 入参: d XML解码器, start 起始节点
// 返回: error 错误信息 // 返回: error 错误信息
func (p *PatternContent) decodeObject(d *xml.Decoder, start xml.StartElement) error { func (p *PatternContent) decodeObject(d *xml.Decoder, start xml.StartElement) error {
switch start.Name.Local { target := graphicObjectTarget{
case "TextObject": objects: &p.Objects,
var obj TextObject text: &p.TextObject,
if err := d.DecodeElement(&obj, &start); err != nil { path: &p.PathObject,
return err image: &p.ImageObject,
} composite: &p.CompositeGraphicUnit,
p.TextObject = append(p.TextObject, obj)
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, TextObject: obj})
case "PathObject":
var obj PathObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
p.PathObject = append(p.PathObject, obj)
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, PathObject: obj})
case "ImageObject":
var obj ImageObject
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
p.ImageObject = append(p.ImageObject, obj)
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, ImageObject: obj})
case "CompositeGraphicUnit", "CompositeObject":
var obj CompositeGraphicUnit
if err := d.DecodeElement(&obj, &start); err != nil {
return err
}
p.CompositeGraphicUnit = append(p.CompositeGraphicUnit, obj)
p.Objects = append(p.Objects, GraphicObject{Type: start.Name.Local, CompositeGraphicUnit: obj})
case "PageBlock":
return p.decodePageBlock(d, start)
default:
return d.Skip()
} }
return nil if decoded, err := decodeGraphicObject(d, start, target); decoded || err != nil {
} return err
// decodePageBlock 解析图案单元内容页块子对象
// 入参: d XML解码器, start 起始节点
// 返回: error 错误信息
func (p *PatternContent) decodePageBlock(d *xml.Decoder, start xml.StartElement) error {
for {
tok, err := d.Token()
if err != nil {
return err
}
switch node := tok.(type) {
case xml.StartElement:
if err := p.decodeObject(d, node); err != nil {
return err
}
case xml.EndElement:
if node.Name.Local == start.Name.Local {
return nil
}
}
} }
if start.Name.Local == "PageBlock" {
return decodeObjectContainer(d, start, p.decodeObject)
}
return d.Skip()
} }
// attrValue 获取XML属性值 // attrValue 获取XML属性值
+12 -8
View File
@@ -23,6 +23,7 @@ type PageContent struct {
Area PageArea `xml:"Area"` Area PageArea `xml:"Area"`
Template []Template `xml:"Template"` Template []Template `xml:"Template"`
Content Content `xml:"Content"` Content Content `xml:"Content"`
Actions []Action `xml:"Actions>Action"`
} }
// Template 页面模板引用 // Template 页面模板引用
@@ -97,6 +98,7 @@ type TextObject struct {
CGTransform []CGTransform `xml:"CGTransform"` CGTransform []CGTransform `xml:"CGTransform"`
TextCode []TextCode `xml:"TextCode"` TextCode []TextCode `xml:"TextCode"`
Clips *Clips `xml:"Clips"` Clips *Clips `xml:"Clips"`
Actions []Action `xml:"Actions>Action"`
} }
// FillColor 填充颜色 // FillColor 填充颜色
@@ -165,6 +167,7 @@ type PathObject struct {
FillColor *FillColor `xml:"FillColor"` FillColor *FillColor `xml:"FillColor"`
AbbreviatedData string `xml:"AbbreviatedData"` AbbreviatedData string `xml:"AbbreviatedData"`
Clips *Clips `xml:"Clips"` Clips *Clips `xml:"Clips"`
Actions []Action `xml:"Actions>Action"`
} }
// StrokeColor 勾边颜色 // StrokeColor 勾边颜色
@@ -207,12 +210,13 @@ type ShdColor struct {
// ImageObject 图片对象 // ImageObject 图片对象
type ImageObject struct { type ImageObject struct {
ID string `xml:"ID,attr"` ID string `xml:"ID,attr"`
Boundary string `xml:"Boundary,attr"` Boundary string `xml:"Boundary,attr"`
ResourceID string `xml:"ResourceID,attr"` ResourceID string `xml:"ResourceID,attr"`
ImageMask string `xml:"ImageMask,attr"` ImageMask string `xml:"ImageMask,attr"`
CTM string `xml:"CTM,attr"` CTM string `xml:"CTM,attr"`
Alpha *int `xml:"Alpha,attr"` Alpha *int `xml:"Alpha,attr"`
Visible *bool `xml:"Visible,attr"` Visible *bool `xml:"Visible,attr"`
Clips *Clips `xml:"Clips"` Clips *Clips `xml:"Clips"`
Actions []Action `xml:"Actions>Action"`
} }
+46 -64
View File
@@ -142,10 +142,10 @@ func parseFillColor(fillColor *FillColor) color.Color {
return nil return nil
} }
if fillColor.AxialShd != nil { if fillColor.AxialShd != nil {
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha) return parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha)
} }
if fillColor.RadialShd != nil { if fillColor.RadialShd != nil {
return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha) return parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha)
} }
if strings.TrimSpace(fillColor.Value) != "" { if strings.TrimSpace(fillColor.Value) != "" {
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
@@ -170,10 +170,10 @@ func parseFillPaint(fillColor *FillColor, x, y, pageH, originX, originY float64)
return gradient return gradient
} }
if fillColor.AxialShd != nil { if fillColor.AxialShd != nil {
return parseAxialShdColor(fillColor.AxialShd, fillColor.Alpha) return parseShdColor(fillColor.AxialShd.Segment, fillColor.Alpha)
} }
if fillColor.RadialShd != nil { if fillColor.RadialShd != nil {
return parseRadialShdColor(fillColor.RadialShd, fillColor.Alpha) return parseShdColor(fillColor.RadialShd.Segment, fillColor.Alpha)
} }
if strings.TrimSpace(fillColor.Value) != "" { if strings.TrimSpace(fillColor.Value) != "" {
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
@@ -189,10 +189,10 @@ func parseStrokeColor(strokeColor *StrokeColor) color.Color {
return nil return nil
} }
if strokeColor.AxialShd != nil { if strokeColor.AxialShd != nil {
return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha) return parseShdColor(strokeColor.AxialShd.Segment, strokeColor.Alpha)
} }
if strokeColor.RadialShd != nil { if strokeColor.RadialShd != nil {
return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha) return parseShdColor(strokeColor.RadialShd.Segment, strokeColor.Alpha)
} }
if strings.TrimSpace(strokeColor.Value) != "" { if strings.TrimSpace(strokeColor.Value) != "" {
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha) return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
@@ -214,10 +214,10 @@ func parseStrokePaint(strokeColor *StrokeColor, x, y, pageH, originX, originY fl
return gradient return gradient
} }
if strokeColor.AxialShd != nil { if strokeColor.AxialShd != nil {
return parseAxialShdColor(strokeColor.AxialShd, strokeColor.Alpha) return parseShdColor(strokeColor.AxialShd.Segment, strokeColor.Alpha)
} }
if strokeColor.RadialShd != nil { if strokeColor.RadialShd != nil {
return parseRadialShdColor(strokeColor.RadialShd, strokeColor.Alpha) return parseShdColor(strokeColor.RadialShd.Segment, strokeColor.Alpha)
} }
if strings.TrimSpace(strokeColor.Value) != "" { if strings.TrimSpace(strokeColor.Value) != "" {
return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha) return parseColorWithAlpha(strokeColor.Value, strokeColor.Alpha)
@@ -235,24 +235,44 @@ func patternColor(fillColor *FillColor) color.Color {
return parseColorWithAlpha(fillColor.Value, fillColor.Alpha) return parseColorWithAlpha(fillColor.Value, fillColor.Alpha)
} }
// parseAxialShdColor 解析轴向渐变颜色 // parseShdColor 解析渐变颜色
// 入参: axialShd 轴向渐变节点, alpha 透明度 // 入参: segments 渐变分段, alpha 透明度
// 返回: color.Color 颜色对象 // 返回: color.Color 颜色对象
func parseAxialShdColor(axialShd *AxialShd, alpha *int) color.Color { func parseShdColor(segments []ShdSegment, alpha *int) color.Color {
if axialShd != nil { for _, segment := range segments {
for _, segment := range axialShd.Segment { if strings.TrimSpace(segment.Color.Value) == "" {
if strings.TrimSpace(segment.Color.Value) == "" { continue
continue
}
if alpha == nil {
alpha = segment.Color.Alpha
}
return parseColorWithAlpha(segment.Color.Value, alpha)
} }
segmentAlpha := alpha
if segmentAlpha == nil {
segmentAlpha = segment.Color.Alpha
}
return parseColorWithAlpha(segment.Color.Value, segmentAlpha)
} }
return color.Black return color.Black
} }
// parseShdSegments 解析渐变分段
// 入参: segments 渐变分段, alpha 透明度
// 返回: canvas.Grad 渐变分段
func parseShdSegments(segments []ShdSegment, alpha *int) canvas.Grad {
gradient := canvas.NewGradient()
for _, segment := range segments {
if strings.TrimSpace(segment.Color.Value) == "" {
continue
}
segmentAlpha := alpha
if segmentAlpha == nil {
segmentAlpha = segment.Color.Alpha
}
gradient.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
}
if len(gradient) == 0 {
return nil
}
return gradient
}
// parseAxialShdGradient 解析轴向渐变 // parseAxialShdGradient 解析轴向渐变
// 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标 // 入参: axialShd 轴向渐变节点, alpha 透明度, x X坐标, y Y坐标, pageH 页面高度, originX 原点X坐标, originY 原点Y坐标
// 返回: canvas.Gradient 渐变对象 // 返回: canvas.Gradient 渐变对象
@@ -265,18 +285,8 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX,
if len(start) < 2 || len(end) < 2 { if len(start) < 2 || len(end) < 2 {
return nil return nil
} }
grad := canvas.NewGradient() gradient := parseShdSegments(axialShd.Segment, alpha)
for _, segment := range axialShd.Segment { if gradient == nil {
if strings.TrimSpace(segment.Color.Value) == "" {
continue
}
segmentAlpha := alpha
if segmentAlpha == nil {
segmentAlpha = segment.Color.Alpha
}
grad.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
}
if len(grad) == 0 {
return nil return nil
} }
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY} startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
@@ -284,25 +294,7 @@ func parseAxialShdGradient(axialShd *AxialShd, alpha *int, x, y, pageH, originX,
if startPoint.Equals(endPoint) { if startPoint.Equals(endPoint) {
return nil return nil
} }
return grad.ToLinear(startPoint, endPoint) return gradient.ToLinear(startPoint, endPoint)
}
// parseRadialShdColor 解析径向渐变颜色
// 入参: radialShd 径向渐变节点, alpha 透明度
// 返回: color.Color 颜色对象
func parseRadialShdColor(radialShd *RadialShd, alpha *int) color.Color {
if radialShd != nil {
for _, segment := range radialShd.Segment {
if strings.TrimSpace(segment.Color.Value) == "" {
continue
}
if alpha == nil {
alpha = segment.Color.Alpha
}
return parseColorWithAlpha(segment.Color.Value, alpha)
}
}
return color.Black
} }
// parseRadialShdGradient 解析径向渐变 // parseRadialShdGradient 解析径向渐变
@@ -317,23 +309,13 @@ func parseRadialShdGradient(radialShd *RadialShd, alpha *int, x, y, pageH, origi
if len(start) < 2 || len(end) < 2 { if len(start) < 2 || len(end) < 2 {
return nil return nil
} }
grad := canvas.NewGradient() gradient := parseShdSegments(radialShd.Segment, alpha)
for _, segment := range radialShd.Segment { if gradient == nil {
if strings.TrimSpace(segment.Color.Value) == "" {
continue
}
segmentAlpha := alpha
if segmentAlpha == nil {
segmentAlpha = segment.Color.Alpha
}
grad.Add(segment.Position, colorToRGBA(parseColorWithAlpha(segment.Color.Value, segmentAlpha)))
}
if len(grad) == 0 {
return nil return nil
} }
startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY} startPoint := canvas.Point{X: x + start[0] - originX, Y: pageH - (y + start[1]) - originY}
endPoint := canvas.Point{X: x + end[0] - originX, Y: pageH - (y + end[1]) - originY} endPoint := canvas.Point{X: x + end[0] - originX, Y: pageH - (y + end[1]) - originY}
return grad.ToRadial(startPoint, radialShd.StartRadius, endPoint, radialShd.EndRadius) return gradient.ToRadial(startPoint, radialShd.StartRadius, endPoint, radialShd.EndRadius)
} }
// colorToRGBA 转换颜色对象 // colorToRGBA 转换颜色对象
+94 -1
View File
@@ -279,6 +279,21 @@ func (r *Reader) ResData(resLink string) ([]byte, error) {
return r.readFile(fullPath) return r.readFile(fullPath)
} }
// readDocumentPart 读取文档关联文件
// 入参: loc 文件位置, value 文档结构
// 返回: string 文件路径, error 错误信息
func (r *Reader) readDocumentPart(loc string, value any) (string, error) {
fullPath := r.ResPath(strings.TrimSpace(loc))
data, err := r.readFile(fullPath)
if err != nil {
return "", err
}
if err := xml.Unmarshal(data, value); err != nil {
return "", fmt.Errorf("failed to unmarshal %s: %w", path.Base(fullPath), err)
}
return fullPath, nil
}
// Version 获取OFD版本号 // Version 获取OFD版本号
// 返回: string 版本号 // 返回: string 版本号
func (r *Reader) Version() string { func (r *Reader) Version() string {
@@ -326,6 +341,26 @@ func (r *Reader) Outlines() ([]OutlineElem, error) {
return doc.Outlines.OutlineElem, nil return doc.Outlines.OutlineElem, nil
} }
// Actions 获取文档动作
// 返回: []Action 动作列表, error 错误信息
func (r *Reader) Actions() ([]Action, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Actions, nil
}
// Bookmarks 获取文档书签
// 返回: []Bookmark 书签列表, error 错误信息
func (r *Reader) Bookmarks() ([]Bookmark, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
return doc.Bookmarks.Bookmark, nil
}
// Attachments 获取附件列表 // Attachments 获取附件列表
// 返回: []Attachment 附件列表, error 错误信息 // 返回: []Attachment 附件列表, error 错误信息
func (r *Reader) Attachments() ([]Attachment, error) { func (r *Reader) Attachments() ([]Attachment, error) {
@@ -333,9 +368,50 @@ func (r *Reader) Attachments() ([]Attachment, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if strings.TrimSpace(doc.Attachments.Path) != "" && doc.Attachments.Attachment == nil {
var attachments Attachments
partPath, err := r.readDocumentPart(doc.Attachments.Path, &attachments)
if err != nil {
return nil, err
}
for i := range attachments.Attachment {
attachment := &attachments.Attachment[i]
attachment.FileLoc = resolveResourcePath(partPath, "", attachment.FileLoc)
}
if attachments.Attachment == nil {
attachments.Attachment = []Attachment{}
}
doc.Attachments.Attachment = attachments.Attachment
}
return doc.Attachments.Attachment, nil return doc.Attachments.Attachment, nil
} }
// CustomTags 获取自定义标引
// 返回: []CustomTag 自定义标引列表, error 错误信息
func (r *Reader) CustomTags() ([]CustomTag, error) {
doc, err := r.Doc()
if err != nil {
return nil, err
}
if strings.TrimSpace(doc.CustomTags.Path) != "" && doc.CustomTags.CustomTag == nil {
var customTags CustomTags
partPath, err := r.readDocumentPart(doc.CustomTags.Path, &customTags)
if err != nil {
return nil, err
}
for i := range customTags.CustomTag {
tag := &customTags.CustomTag[i]
tag.SchemaLoc = resolveResourcePath(partPath, "", tag.SchemaLoc)
tag.FileLoc = resolveResourcePath(partPath, "", tag.FileLoc)
}
if customTags.CustomTag == nil {
customTags.CustomTag = []CustomTag{}
}
doc.CustomTags.CustomTag = customTags.CustomTag
}
return doc.CustomTags.CustomTag, nil
}
// CustomDatas 获取自定义数据 // CustomDatas 获取自定义数据
// 返回: []CustomData 自定义数据列表, error 错误信息 // 返回: []CustomData 自定义数据列表, error 错误信息
func (r *Reader) CustomDatas() ([]CustomData, error) { func (r *Reader) CustomDatas() ([]CustomData, error) {
@@ -349,12 +425,29 @@ func (r *Reader) CustomDatas() ([]CustomData, error) {
return info.CustomDatas.CustomData, nil return info.CustomDatas.CustomData, nil
} }
// Extensions 获取分列扩展项 // Extensions 获取扩展项
// 返回: []Extension 扩展项列表, error 错误信息 // 返回: []Extension 扩展项列表, error 错误信息
func (r *Reader) Extensions() ([]Extension, error) { func (r *Reader) Extensions() ([]Extension, error) {
doc, err := r.Doc() doc, err := r.Doc()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if strings.TrimSpace(doc.Extensions.Path) != "" && doc.Extensions.Extension == nil {
var extensions Extensions
partPath, err := r.readDocumentPart(doc.Extensions.Path, &extensions)
if err != nil {
return nil, err
}
for i := range extensions.Extension {
extension := &extensions.Extension[i]
for j := range extension.ExtendData {
extension.ExtendData[j] = resolveResourcePath(partPath, "", extension.ExtendData[j])
}
}
if extensions.Extension == nil {
extensions.Extension = []Extension{}
}
doc.Extensions.Extension = extensions.Extension
}
return doc.Extensions.Extension, nil return doc.Extensions.Extension, nil
} }
+1
View File
@@ -36,6 +36,7 @@ type Renderer struct {
fontCache map[fontCacheKey]*canvas.FontFamily fontCache map[fontCacheKey]*canvas.FontFamily
fontSourceCache map[string][]fontSource fontSourceCache map[string][]fontSource
fontSourceUsed map[string]fontSource fontSourceUsed map[string]fontSource
fontDirCandidates map[string][]fontFileCandidate
textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue
templatePageCache map[string]*PageContent templatePageCache map[string]*PageContent
fontDirs []string fontDirs []string
+29 -11
View File
@@ -266,28 +266,46 @@ func imageWithTransparentEdge(img image.Image) (image.Image, int) {
if opaque, ok := source.(interface{ Opaque() bool }); ok && opaque.Opaque() { if opaque, ok := source.(interface{ Opaque() bool }); ok && opaque.Opaque() {
return img, 0 return img, 0
} }
src := image.NewNRGBA(image.Rect(0, 0, w, h))
hasZero, hasVisible := false, false hasZero, hasVisible := false, false
for y := 0; y < h; y++ { src, ok := source.(*image.NRGBA)
for x := 0; x < w; x++ { if ok {
c := imageNRGBAAt(source, bounds.Min.X+x, bounds.Min.Y+y) srcBounds := src.Bounds()
src.SetNRGBA(x, y, c) for y := srcBounds.Min.Y; y < srcBounds.Max.Y; y++ {
if c.A == 0 { offset := src.PixOffset(srcBounds.Min.X, y) + 3
hasZero = true for x := 0; x < w; x++ {
} else { if src.Pix[offset] == 0 {
hasVisible = true hasZero = true
} else {
hasVisible = true
}
offset += 4
}
}
} else {
src = image.NewNRGBA(image.Rect(0, 0, w, h))
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
c := imageNRGBAAt(source, bounds.Min.X+x, bounds.Min.Y+y)
src.SetNRGBA(x, y, c)
if c.A == 0 {
hasZero = true
} else {
hasVisible = true
}
} }
} }
} }
if !hasZero || !hasVisible { if !hasZero || !hasVisible {
return img, 0 return img, 0
} }
srcBounds := src.Bounds()
out := image.NewNRGBA(image.Rect(0, 0, w+2, h+2)) out := image.NewNRGBA(image.Rect(0, 0, w+2, h+2))
for y := 0; y < h; y++ { for y := 0; y < h; y++ {
for x := 0; x < w; x++ { for x := 0; x < w; x++ {
c := src.NRGBAAt(x, y) sx, sy := srcBounds.Min.X+x, srcBounds.Min.Y+y
c := src.NRGBAAt(sx, sy)
if c.A == 0 { if c.A == 0 {
if edge, ok := transparentEdgeColor(src, x, y); ok { if edge, ok := transparentEdgeColor(src, sx, sy); ok {
c = edge c = edge
} }
} }
+27 -15
View File
@@ -71,9 +71,16 @@ func (r *Renderer) RenderToPDF(page *PageContent, writer io.Writer) error {
if err != nil { if err != nil {
return err return err
} }
box, err := r.GetPageBox(page)
if err != nil {
return err
}
pages := []pdfPage{{Content: page, Box: box}}
navigation := newPDFNavigation(r, r.Reader.doc, pages)
var buf bytes.Buffer var buf bytes.Buffer
p := pdf.New(&buf, c.W, c.H, nil) p := pdf.New(&buf, c.W, c.H, nil)
p.SetInfo("", "", "", "", "xiaoqidun/ofdgo") p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
navigation.apply(p, 0)
c.RenderTo(p) c.RenderTo(p)
if err := p.Close(); err != nil { if err := p.Close(); err != nil {
return err return err
@@ -104,28 +111,33 @@ func (r *Renderer) RenderToMultiPagePDF(writer io.Writer) error {
if len(doc.Pages.Page) == 0 { if len(doc.Pages.Page) == 0 {
return fmt.Errorf("no pages found") return fmt.Errorf("no pages found")
} }
pages := make([]pdfPage, len(doc.Pages.Page))
for i, pageRef := range doc.Pages.Page {
page, err := r.Reader.PageContent(pageRef)
if err != nil {
return fmt.Errorf("failed to read page %d: %w", i+1, err)
}
box, err := r.GetPageBox(page)
if err != nil {
return fmt.Errorf("failed to read page %d area: %w", i+1, err)
}
pages[i] = pdfPage{Content: page, Box: box}
}
navigation := newPDFNavigation(r, doc, pages)
var buf bytes.Buffer var buf bytes.Buffer
var p *pdf.PDF p := pdf.New(&buf, pages[0].Box.W, pages[0].Box.H, nil)
for _, pgRef := range doc.Pages.Page { p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
page, err := r.Reader.PageContent(pgRef) for i, page := range pages {
c, err := r.renderPage(page.Content)
if err != nil { if err != nil {
continue return fmt.Errorf("failed to render page %d: %w", i+1, err)
} }
c, err := r.renderPage(page) if i > 0 {
if err != nil {
continue
}
if p == nil {
p = pdf.New(&buf, c.W, c.H, nil)
p.SetInfo("", "", "", "", "xiaoqidun/ofdgo")
} else {
p.NewPage(c.W, c.H) p.NewPage(c.W, c.H)
} }
navigation.apply(p, i)
c.RenderTo(p) c.RenderTo(p)
} }
if p == nil {
return fmt.Errorf("failed to render any page")
}
if err := p.Close(); err != nil { if err := p.Close(); err != nil {
return err return err
} }
+322
View File
@@ -0,0 +1,322 @@
// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"fmt"
"net/url"
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers/pdf"
)
// pdfPage PDF页面数据
type pdfPage struct {
Content *PageContent
Box Box
}
// pdfNavigation PDF导航信息
type pdfNavigation struct {
Anchor map[int][]pdfAnchor
Link map[int][]pdfLink
Outline map[int][]pdfOutline
nextID int
}
// pdfAnchor PDF跳转目标
type pdfAnchor struct {
Name string
Rect canvas.Rect
}
// pdfLink PDF链接
type pdfLink struct {
URI string
Rect canvas.Rect
}
// pdfOutline PDF大纲
type pdfOutline struct {
Name string
Level int
Y float64
}
// pdfActionSource PDF动作来源
type pdfActionSource struct {
Box Box
Actions []Action
}
// newPDFNavigation 创建PDF导航信息
// 入参: renderer 渲染器, doc 文档结构, pages 页面数据
// 返回: *pdfNavigation PDF导航信息
func newPDFNavigation(renderer *Renderer, doc *Document, pages []pdfPage) *pdfNavigation {
navigation := &pdfNavigation{
Anchor: make(map[int][]pdfAnchor),
Link: make(map[int][]pdfLink),
Outline: make(map[int][]pdfOutline),
}
pageIndex := make(map[string]int, len(pages))
for i, page := range pages {
pageIndex[page.Content.ID] = i
}
bookmarks := make(map[string]Dest)
if doc != nil {
for _, bookmark := range doc.Bookmarks.Bookmark {
bookmarks[bookmark.Name] = bookmark.Dest
}
}
for i, page := range pages {
sources := pageActionSources(page)
if renderer.RenderAnnotations {
sources = append(sources, annotationActionSources(renderer.Reader.Annots[page.Content.ID])...)
}
for _, source := range sources {
rect := pdfSourceRect(source.Box, page.Box.H)
for _, action := range source.Actions {
if action.Event != "CLICK" {
continue
}
navigation.addAction(i, rect, action, bookmarks, pageIndex, pages)
}
}
}
if doc != nil {
navigation.addOutlines(doc.Outlines.OutlineElem, 0, bookmarks, pageIndex, pages)
}
return navigation
}
// addAction 添加PDF动作
// 入参: page 页面索引, rect 动作区域, action 动作, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
func (n *pdfNavigation) addAction(page int, rect canvas.Rect, action Action, bookmarks map[string]Dest, pageIndex map[string]int, pages []pdfPage) {
if action.Goto != nil {
dest := gotoDest(action.Goto, bookmarks)
if dest == nil {
return
}
target, ok := pageIndex[dest.PageID]
if !ok {
return
}
targetRect, ok := pdfDestRect(*dest, pages[target].Box.H)
if !ok {
return
}
name := fmt.Sprintf("ofdgo-dest-%d", n.nextID)
n.nextID++
n.Anchor[target] = append(n.Anchor[target], pdfAnchor{Name: name, Rect: targetRect})
n.Link[page] = append(n.Link[page], pdfLink{URI: "#" + name, Rect: rect})
return
}
if action.URI != nil && action.URI.URI != "" {
n.Link[page] = append(n.Link[page], pdfLink{URI: resolveActionURI(*action.URI), Rect: rect})
}
}
// addOutlines 添加PDF大纲
// 入参: outlines 大纲节点, level 节点层级, bookmarks 书签, pageIndex 页面索引表, pages 页面数据
func (n *pdfNavigation) addOutlines(outlines []OutlineElem, level int, bookmarks map[string]Dest, pageIndex map[string]int, pages []pdfPage) {
for _, outline := range outlines {
if dest := outlineDest(outline, bookmarks); dest != nil {
if page, ok := pageIndex[dest.PageID]; ok {
n.Outline[page] = append(n.Outline[page], pdfOutline{
Name: outline.Title,
Level: level,
Y: pdfDestY(*dest, pages[page].Box.H),
})
}
}
n.addOutlines(outline.OutlineElem, level+1, bookmarks, pageIndex, pages)
}
}
// apply 应用PDF导航信息
// 入参: renderer PDF渲染器, page 页面索引
func (n *pdfNavigation) apply(renderer *pdf.PDF, page int) {
for _, anchor := range n.Anchor[page] {
renderer.AddAnchor(anchor.Name, anchor.Rect)
}
for _, link := range n.Link[page] {
renderer.AddLink(link.URI, link.Rect)
}
for _, outline := range n.Outline[page] {
renderer.AddOutline(outline.Name, outline.Level, outline.Y)
}
}
// 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 {
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
}
// gotoDest 获取文档内跳转目标
// 入参: action 跳转动作, bookmarks 书签
// 返回: *Dest 跳转目标
func gotoDest(action *Goto, bookmarks map[string]Dest) *Dest {
if action.Dest != nil {
return action.Dest
}
if action.Bookmark != nil {
if dest, ok := bookmarks[action.Bookmark.Name]; ok {
return &dest
}
}
return nil
}
// outlineDest 获取大纲跳转目标
// 入参: outline 大纲节点, bookmarks 书签
// 返回: *Dest 跳转目标
func outlineDest(outline OutlineElem, bookmarks map[string]Dest) *Dest {
for _, action := range outline.Actions {
if action.Goto != nil {
if dest := gotoDest(action.Goto, bookmarks); dest != nil {
return dest
}
}
}
for _, child := range outline.OutlineElem {
if dest := outlineDest(child, bookmarks); dest != nil {
return dest
}
}
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动作区域
// 入参: box OFD区域, pageH 页面高度
// 返回: canvas.Rect PDF区域
func pdfSourceRect(box Box, pageH float64) canvas.Rect {
return canvas.RectFromSize(box.X, pageH-box.Y-box.H, box.W, box.H)
}
// pdfDestRect 转换PDF跳转目标
// 入参: dest OFD跳转目标, pageH 页面高度
// 返回: canvas.Rect PDF目标区域, bool 是否支持
func pdfDestRect(dest Dest, pageH float64) (canvas.Rect, bool) {
switch dest.Type {
case "XYZ":
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Top, X1: dest.Left, Y1: pageH - dest.Top}, true
case "Fit":
return canvas.Rect{}, true
case "FitH":
return canvas.Rect{Y0: pageH - dest.Top, Y1: pageH - dest.Top}, true
case "FitV":
return canvas.Rect{X0: dest.Left, X1: dest.Left}, true
case "FitR":
return canvas.Rect{X0: dest.Left, Y0: pageH - dest.Bottom, X1: dest.Right, Y1: pageH - dest.Top}, true
}
return canvas.Rect{}, false
}
// pdfDestY 获取PDF大纲目标位置
// 入参: dest OFD跳转目标, pageH 页面高度
// 返回: float64 PDF纵坐标
func pdfDestY(dest Dest, pageH float64) float64 {
switch dest.Type {
case "XYZ", "FitH", "FitR":
return pageH - dest.Top
}
return 0
}
+1
View File
@@ -104,4 +104,5 @@ type CompositeGraphicUnit struct {
ImageObject []ImageObject `xml:"ImageObject"` ImageObject []ImageObject `xml:"ImageObject"`
CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"` CompositeGraphicUnit []CompositeGraphicUnit `xml:"CompositeGraphicUnit"`
Clips *Clips `xml:"Clips"` Clips *Clips `xml:"Clips"`
Actions []Action `xml:"Actions>Action"`
} }
+1 -1
View File
@@ -36,7 +36,7 @@ const (
// Signatures 签名列表 // Signatures 签名列表
type Signatures struct { type Signatures struct {
XMLName xml.Name `xml:"Signatures"` XMLName xml.Name `xml:"Signatures"`
MaxSignId string `xml:"MaxSignId"` MaxSignID string `xml:"MaxSignId"`
List []Signature `xml:"Signature"` List []Signature `xml:"Signature"`
} }
+184 -21
View File
@@ -15,7 +15,6 @@
package ofdgo package ofdgo
import ( import (
"crypto/elliptic"
"encoding/asn1" "encoding/asn1"
"encoding/binary" "encoding/binary"
"math/big" "math/big"
@@ -31,17 +30,32 @@ type sm2PublicKey struct {
Y *big.Int Y *big.Int
} }
// sm2Curve SM2椭圆曲线
type sm2Curve struct {
P *big.Int
N *big.Int
B *big.Int
Gx *big.Int
Gy *big.Int
}
// sm2Point SM2雅可比坐标点
type sm2Point struct {
X *big.Int
Y *big.Int
Z *big.Int
}
// newSM2P256 创建SM2椭圆曲线 // newSM2P256 创建SM2椭圆曲线
// 返回: elliptic.Curve SM2椭圆曲线 // 返回: *sm2Curve SM2椭圆曲线
func newSM2P256() elliptic.Curve { func newSM2P256() *sm2Curve {
c := &elliptic.CurveParams{Name: "SM2-P-256"} return &sm2Curve{
c.P = sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF") P: sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"),
c.N = sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123") N: sm2Big("FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"),
c.B = sm2Big("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93") B: sm2Big("28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"),
c.Gx = sm2Big("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7") Gx: sm2Big("32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7"),
c.Gy = sm2Big("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0") Gy: sm2Big("BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"),
c.BitSize = 256 }
return c
} }
// sm2Big 解析SM2大整数常量 // sm2Big 解析SM2大整数常量
@@ -85,11 +99,11 @@ func parseSM2Signature(sig []byte) (*big.Int, *big.Int, bool) {
// 入参: pub 公钥, userID 用户标识, msg 原文, r R值, s S值 // 入参: pub 公钥, userID 用户标识, msg 原文, r R值, s S值
// 返回: bool 是否验证通过 // 返回: bool 是否验证通过
func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool { func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool {
n := sm2P256.Params().N n := sm2P256.N
if r.Sign() <= 0 || s.Sign() <= 0 || r.Cmp(n) >= 0 || s.Cmp(n) >= 0 { if r.Sign() <= 0 || s.Sign() <= 0 || r.Cmp(n) >= 0 || s.Cmp(n) >= 0 {
return false return false
} }
if pub.X == nil || pub.Y == nil || !sm2P256.IsOnCurve(pub.X, pub.Y) { if pub.X == nil || pub.Y == nil || !sm2P256.isOnCurve(pub.X, pub.Y) {
return false return false
} }
e := new(big.Int).SetBytes(sm2MessageDigest(pub, userID, msg)) e := new(big.Int).SetBytes(sm2MessageDigest(pub, userID, msg))
@@ -98,10 +112,8 @@ func sm2Verify(pub sm2PublicKey, userID, msg []byte, r, s *big.Int) bool {
if t.Sign() == 0 { if t.Sign() == 0 {
return false return false
} }
x1, y1 := sm2P256.ScalarBaseMult(s.Bytes()) x, ok := sm2P256.combinedMult(pub.X, pub.Y, s, t)
x2, y2 := sm2P256.ScalarMult(pub.X, pub.Y, t.Bytes()) if !ok {
x, _ := sm2P256.Add(x1, y1, x2, y2)
if x == nil {
return false return false
} }
v := new(big.Int).Add(e, x) v := new(big.Int).Add(e, x)
@@ -132,9 +144,9 @@ func sm2ZA(pub sm2PublicKey, userID []byte) []byte {
h.Write(entl[:]) h.Write(entl[:])
h.Write(userID) h.Write(userID)
h.Write(sm2Fixed(sm2A())) h.Write(sm2Fixed(sm2A()))
h.Write(sm2Fixed(sm2P256.Params().B)) h.Write(sm2Fixed(sm2P256.B))
h.Write(sm2Fixed(sm2P256.Params().Gx)) h.Write(sm2Fixed(sm2P256.Gx))
h.Write(sm2Fixed(sm2P256.Params().Gy)) h.Write(sm2Fixed(sm2P256.Gy))
h.Write(sm2Fixed(pub.X)) h.Write(sm2Fixed(pub.X))
h.Write(sm2Fixed(pub.Y)) h.Write(sm2Fixed(pub.Y))
return h.Sum(nil) return h.Sum(nil)
@@ -143,7 +155,158 @@ func sm2ZA(pub sm2PublicKey, userID []byte) []byte {
// sm2A 获取SM2曲线A参数 // sm2A 获取SM2曲线A参数
// 返回: *big.Int 曲线A参数 // 返回: *big.Int 曲线A参数
func sm2A() *big.Int { func sm2A() *big.Int {
return new(big.Int).Sub(sm2P256.Params().P, big.NewInt(3)) return new(big.Int).Sub(sm2P256.P, big.NewInt(3))
}
// isOnCurve 判断点是否位于SM2曲线
// 入参: x X坐标, y Y坐标
// 返回: bool 是否位于曲线
func (c *sm2Curve) isOnCurve(x, y *big.Int) bool {
if x.Sign() < 0 || y.Sign() < 0 || x.Cmp(c.P) >= 0 || y.Cmp(c.P) >= 0 {
return false
}
left := c.fieldSquare(y)
right := c.fieldAdd(c.fieldSub(c.fieldMul(c.fieldSquare(x), x), c.fieldScale(x, 3)), c.B)
return left.Cmp(right) == 0
}
// combinedMult 计算sG+tP
// 入参: x 公钥X坐标, y 公钥Y坐标, s 标量S, t 标量T
// 返回: *big.Int 结果X坐标, bool 是否计算成功
func (c *sm2Curve) combinedMult(x, y, s, t *big.Int) (*big.Int, bool) {
base := c.scalarMult(c.Gx, c.Gy, s.Bytes())
public := c.scalarMult(x, y, t.Bytes())
result := c.add(base, public)
affineX, _, ok := c.affine(result)
return affineX, ok
}
// scalarMult 计算椭圆曲线标量乘法
// 入参: x 点X坐标, y 点Y坐标, scalar 标量
// 返回: sm2Point 雅可比坐标点
func (c *sm2Curve) scalarMult(x, y *big.Int, scalar []byte) sm2Point {
result := c.infinity()
point := sm2Point{X: new(big.Int).Set(x), Y: new(big.Int).Set(y), Z: big.NewInt(1)}
for _, value := range scalar {
for bit := 7; bit >= 0; bit-- {
result = c.double(result)
if value&(1<<uint(bit)) != 0 {
result = c.add(result, point)
}
}
}
return result
}
// add 计算椭圆曲线点加法
// 入参: p 点P, q 点Q
// 返回: sm2Point 结果点
func (c *sm2Curve) add(p, q sm2Point) sm2Point {
if p.Z.Sign() == 0 {
return q
}
if q.Z.Sign() == 0 {
return p
}
z1z1 := c.fieldSquare(p.Z)
z2z2 := c.fieldSquare(q.Z)
u1 := c.fieldMul(p.X, z2z2)
u2 := c.fieldMul(q.X, z1z1)
s1 := c.fieldMul(p.Y, c.fieldMul(q.Z, z2z2))
s2 := c.fieldMul(q.Y, c.fieldMul(p.Z, z1z1))
if u1.Cmp(u2) == 0 {
if s1.Cmp(s2) != 0 {
return c.infinity()
}
return c.double(p)
}
h := c.fieldSub(u2, u1)
i := c.fieldSquare(c.fieldScale(h, 2))
j := c.fieldMul(h, i)
r := c.fieldScale(c.fieldSub(s2, s1), 2)
v := c.fieldMul(u1, i)
x := c.fieldSub(c.fieldSub(c.fieldSquare(r), j), c.fieldScale(v, 2))
y := c.fieldSub(c.fieldMul(r, c.fieldSub(v, x)), c.fieldScale(c.fieldMul(s1, j), 2))
z := c.fieldMul(c.fieldSub(c.fieldSub(c.fieldSquare(c.fieldAdd(p.Z, q.Z)), z1z1), z2z2), h)
return sm2Point{X: x, Y: y, Z: z}
}
// double 计算椭圆曲线点倍加
// 入参: p 点P
// 返回: sm2Point 结果点
func (c *sm2Curve) double(p sm2Point) sm2Point {
if p.Z.Sign() == 0 || p.Y.Sign() == 0 {
return c.infinity()
}
delta := c.fieldSquare(p.Z)
gamma := c.fieldSquare(p.Y)
beta := c.fieldMul(p.X, gamma)
alpha := c.fieldScale(c.fieldMul(c.fieldSub(p.X, delta), c.fieldAdd(p.X, delta)), 3)
x := c.fieldSub(c.fieldSquare(alpha), c.fieldScale(beta, 8))
z := c.fieldSub(c.fieldSub(c.fieldSquare(c.fieldAdd(p.Y, p.Z)), gamma), delta)
y := c.fieldSub(c.fieldMul(alpha, c.fieldSub(c.fieldScale(beta, 4), x)), c.fieldScale(c.fieldSquare(gamma), 8))
return sm2Point{X: x, Y: y, Z: z}
}
// affine 将雅可比坐标转换为仿射坐标
// 入参: p 雅可比坐标点
// 返回: *big.Int X坐标, *big.Int Y坐标, bool 是否转换成功
func (c *sm2Curve) affine(p sm2Point) (*big.Int, *big.Int, bool) {
if p.Z.Sign() == 0 {
return nil, nil, false
}
z := new(big.Int).ModInverse(p.Z, c.P)
if z == nil {
return nil, nil, false
}
z2 := c.fieldSquare(z)
x := c.fieldMul(p.X, z2)
y := c.fieldMul(p.Y, c.fieldMul(z2, z))
return x, y, true
}
// infinity 获取无穷远点
// 返回: sm2Point 无穷远点
func (c *sm2Curve) infinity() sm2Point {
return sm2Point{X: new(big.Int), Y: new(big.Int), Z: new(big.Int)}
}
// fieldAdd 计算有限域加法
// 入参: x 左操作数, y 右操作数
// 返回: *big.Int 计算结果
func (c *sm2Curve) fieldAdd(x, y *big.Int) *big.Int {
value := new(big.Int).Add(x, y)
return value.Mod(value, c.P)
}
// fieldSub 计算有限域减法
// 入参: x 左操作数, y 右操作数
// 返回: *big.Int 计算结果
func (c *sm2Curve) fieldSub(x, y *big.Int) *big.Int {
value := new(big.Int).Sub(x, y)
return value.Mod(value, c.P)
}
// fieldMul 计算有限域乘法
// 入参: x 左操作数, y 右操作数
// 返回: *big.Int 计算结果
func (c *sm2Curve) fieldMul(x, y *big.Int) *big.Int {
value := new(big.Int).Mul(x, y)
return value.Mod(value, c.P)
}
// fieldSquare 计算有限域平方
// 入参: x 操作数
// 返回: *big.Int 计算结果
func (c *sm2Curve) fieldSquare(x *big.Int) *big.Int {
return c.fieldMul(x, x)
}
// fieldScale 计算有限域整数倍
// 入参: x 操作数, scale 倍数
// 返回: *big.Int 计算结果
func (c *sm2Curve) fieldScale(x *big.Int, scale int64) *big.Int {
return c.fieldMul(x, big.NewInt(scale))
} }
// sm2Fixed 转换为SM2固定长度字节 // sm2Fixed 转换为SM2固定长度字节