mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
4d0a909de5
commit
b85d7457c1
13 files changed
+98
-42
No files matched your search
@@ -48,6 +48,12 @@ select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
select,
|
||||
option {
|
||||
text-align: left;
|
||||
text-align-last: left;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid var(--line-strong);
|
||||
background: #ffffff;
|
||||
@@ -135,7 +141,6 @@ a:focus-visible {
|
||||
border-radius: 0;
|
||||
background: #fff;
|
||||
color: #1f2428;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.editor-paragraph > .editor-field,
|
||||
@@ -650,7 +655,8 @@ button:not(:disabled):hover {
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
padding-left: 4px;
|
||||
padding-right: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -789,7 +795,6 @@ button:not(:disabled):hover {
|
||||
height: var(--control-height);
|
||||
padding: 0 6px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
font: inherit;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 0;
|
||||
@@ -802,6 +807,7 @@ button:not(:disabled):hover {
|
||||
}
|
||||
|
||||
.convert-table input {
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
|
||||
@@ -2912,7 +2912,7 @@ function releasePageResources() {
|
||||
const images = new Set(), fonts = new Set();
|
||||
for (const page of state.pageCache.values()) {
|
||||
for (const name of page.imageNames) images.add(name);
|
||||
for (const font of page.fonts) fonts.add(font.name);
|
||||
for (const font of page.fonts || []) fonts.add(font.name);
|
||||
}
|
||||
for (const [name, image] of state.svgImages) {
|
||||
if (!images.has(name)) {
|
||||
@@ -5495,6 +5495,7 @@ function trimPageCache() {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!state.pageInFlight.size) releasePageResources();
|
||||
}
|
||||
|
||||
async function loadSVGFonts(fonts, openSeq) {
|
||||
|
||||
@@ -9,7 +9,7 @@ require (
|
||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||
github.com/xiaoqidun/jbig2 v1.1.1
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928145637-87d467fc8ef1
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e
|
||||
golang.org/x/crypto v0.57.0
|
||||
golang.org/x/image v0.46.0
|
||||
golang.org/x/text v0.42.0
|
||||
|
||||
@@ -62,8 +62,8 @@ github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk=
|
||||
github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
||||
github.com/xiaoqidun/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928145637-87d467fc8ef1 h1:meZvn/eb/AAbr6qfsP41UZZsW/g1v67a2CUYGzoJpS0=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928145637-87d467fc8ef1/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e h1:yc6JbpAQG36mxW6R+mNoAib9tbqTUAQxRcXjGLy6GRA=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260928160629-4a36d082a27e/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||
|
||||
@@ -72,7 +72,7 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
|
||||
DrawParams: reader.drawParamCache,
|
||||
CompositeGraphicUnits: reader.compositeGraphicUnitCache,
|
||||
templatePageCache: make(map[string]*PageContent),
|
||||
imageCache: make(map[string]image.Image),
|
||||
imageCache: renderCache[string, image.Image]{limit: imageCacheLimit},
|
||||
backends: defaultRenderBackends(),
|
||||
backendStates: make(map[any]any),
|
||||
}
|
||||
|
||||
+24
-5
@@ -25,6 +25,9 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// imageCacheLimit 限制图片缓存的编码数据及预计解码内存
|
||||
const imageCacheLimit = 64 << 20
|
||||
|
||||
// EncodedImage 保留PNG或JPEG原始编码,首次读取像素时解码
|
||||
// Bytes返回只读数据,Image返回共享只读像素,解码错误由Image返回
|
||||
type EncodedImage struct {
|
||||
@@ -110,21 +113,37 @@ func newEncodedImage(data []byte) (*EncodedImage, error) {
|
||||
// 返回: image.Image 图片对象, error 读取错误
|
||||
func (r *Renderer) cachedImageResource(resPath string) (image.Image, error) {
|
||||
resPath = cleanPackagePath(r.Reader.ResPath(resPath))
|
||||
img, ok := r.imageCache[resPath]
|
||||
img, ok := r.imageCache.get(resPath)
|
||||
if !ok {
|
||||
var err error
|
||||
img, err = r.readImageResource(resPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r.imageCache == nil {
|
||||
r.imageCache = make(map[string]image.Image)
|
||||
}
|
||||
r.imageCache[resPath] = img
|
||||
r.imageCache.put(resPath, img, imageResourceCost(img, r.imageCache.limit))
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// imageResourceCost 预留十六位像素展开空间,超预算图片不进入缓存
|
||||
// 入参: img 图片资源, limit 缓存预算
|
||||
// 返回: int 估算字节数或超预算标记
|
||||
func imageResourceCost(img image.Image, limit int) int {
|
||||
cost := 256
|
||||
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
||||
if limit < cost || w <= 0 || h <= 0 || w > (limit-cost)/8/h {
|
||||
return limit + 1
|
||||
}
|
||||
cost += w * h * 8
|
||||
if encoded, ok := img.(*EncodedImage); ok {
|
||||
if len(encoded.data) > limit-cost {
|
||||
return limit + 1
|
||||
}
|
||||
cost += len(encoded.data)
|
||||
}
|
||||
return cost
|
||||
}
|
||||
|
||||
// decodeImageResource 读取或复用图片资源,按输出需要解码像素
|
||||
// 入参: resPath 图片资源路径
|
||||
// 返回: image.Image 图片对象, error 读取或解码错误
|
||||
|
||||
+20
-7
@@ -15,6 +15,7 @@
|
||||
package ofdgo
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -25,7 +26,7 @@ import (
|
||||
// 入参: value 页码表达式,如1-6,7,8-10, count 文档总页数
|
||||
// 返回: []int 页面索引, error 错误信息
|
||||
func ParsePageRange(value string, count int) ([]int, error) {
|
||||
selected := make(map[int]bool)
|
||||
var intervals [][2]int
|
||||
for _, part := range strings.Split(strings.ReplaceAll(value, ",", ","), ",") {
|
||||
first, last, interval := strings.Cut(strings.TrimSpace(part), "-")
|
||||
start, err := strconv.Atoi(strings.TrimSpace(first))
|
||||
@@ -42,15 +43,27 @@ func ParsePageRange(value string, count int) ([]int, error) {
|
||||
if start < 1 || end < start || end > count {
|
||||
return nil, fmt.Errorf("page range %q outside 1-%d", part, count)
|
||||
}
|
||||
for index := start - 1; index < end; index++ {
|
||||
selected[index] = true
|
||||
intervals = append(intervals, [2]int{start - 1, end})
|
||||
}
|
||||
slices.SortFunc(intervals, func(a, b [2]int) int { return cmp.Compare(a[0], b[0]) })
|
||||
merged := intervals[:0]
|
||||
for _, interval := range intervals {
|
||||
if len(merged) != 0 && interval[0] <= merged[len(merged)-1][1] {
|
||||
merged[len(merged)-1][1] = max(merged[len(merged)-1][1], interval[1])
|
||||
} else {
|
||||
merged = append(merged, interval)
|
||||
}
|
||||
}
|
||||
indices := make([]int, 0, len(selected))
|
||||
for index := range selected {
|
||||
indices = append(indices, index)
|
||||
length := 0
|
||||
for _, interval := range merged {
|
||||
length += interval[1] - interval[0]
|
||||
}
|
||||
indices := make([]int, 0, length)
|
||||
for _, interval := range merged {
|
||||
for index := interval[0]; index < interval[1]; index++ {
|
||||
indices = append(indices, index)
|
||||
}
|
||||
}
|
||||
slices.Sort(indices)
|
||||
return indices, nil
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -55,11 +55,13 @@ type Reader struct {
|
||||
encryption *encryptionState
|
||||
}
|
||||
|
||||
// Close 关闭阅读器
|
||||
// Close 关闭阅读器持有的文件,重复调用不再关闭底层文件
|
||||
// 返回: error 错误信息
|
||||
func (r *Reader) Close() error {
|
||||
if r.Closer != nil {
|
||||
return r.Closer.Close()
|
||||
closer := r.Closer
|
||||
r.Closer = nil
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ type Renderer struct {
|
||||
DrawParams map[string]*DrawParam
|
||||
CompositeGraphicUnits map[string]*CompositeGraphicUnit
|
||||
templatePageCache map[string]*PageContent
|
||||
imageCache map[string]image.Image
|
||||
imageCache renderCache[string, image.Image]
|
||||
fontDirs []string
|
||||
fontFS []fs.FS
|
||||
decodeImages bool
|
||||
@@ -57,7 +57,7 @@ type RendererOption func(*Renderer)
|
||||
// ClearCache 释放派生字体、图片、模板与后端缓存,不改变文档和渲染配置
|
||||
func (r *Renderer) ClearCache() {
|
||||
r.backendStates = make(map[any]any)
|
||||
r.imageCache = make(map[string]image.Image)
|
||||
r.imageCache = renderCache[string, image.Image]{limit: imageCacheLimit}
|
||||
r.templatePageCache = make(map[string]*PageContent)
|
||||
r.resetFontCache()
|
||||
}
|
||||
|
||||
@@ -74,14 +74,7 @@ func textGlyphPath(face *canvas.FontFace, glyph textGlyph, outlines *sfntOutline
|
||||
width = face.MmPerEm * float64(face.Font.GlyphAdvance(glyphID))
|
||||
}
|
||||
if face.FauxBold != 0 {
|
||||
d := face.FauxBold * face.Size
|
||||
if face.Font.IsTrueType {
|
||||
d = -d
|
||||
}
|
||||
origFastStroke := canvas.FastStroke
|
||||
canvas.FastStroke = true
|
||||
p = p.Offset(d, canvas.Tolerance)
|
||||
canvas.FastStroke = origFastStroke
|
||||
p = p.Offset(face.FauxBold*face.Size, canvas.Tolerance)
|
||||
}
|
||||
if face.FauxItalic != 0 {
|
||||
p = p.Transform(canvas.Identity.Shear(face.FauxItalic, 0))
|
||||
@@ -105,7 +98,7 @@ func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (
|
||||
direction: face.Direction,
|
||||
glyph: glyph,
|
||||
}
|
||||
if cached, ok := r.canvasState().textGlyphPathCache[key]; ok {
|
||||
if cached, ok := r.canvasState().textGlyphPathCache.get(key); ok {
|
||||
return cached.path, cached.width
|
||||
}
|
||||
outlines := r.canvasState().fontOutlines[face.Font]
|
||||
@@ -118,7 +111,7 @@ func (r *Renderer) cachedTextGlyphPath(face *canvas.FontFace, glyph textGlyph) (
|
||||
r.renderError = err
|
||||
return &canvas.Path{}, 0
|
||||
}
|
||||
r.canvasState().textGlyphPathCache[key] = textGlyphPathCacheValue{path: path, width: width}
|
||||
r.canvasState().textGlyphPathCache.put(key, textGlyphPathCacheValue{path: path, width: width}, 256+len(path.Data())*8+len(glyph.Text)+len(face.Language))
|
||||
return path, width
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ func (r *Renderer) canvasEncodedImage(img image.Image) image.Image {
|
||||
return img
|
||||
}
|
||||
state := r.canvasState()
|
||||
if encoded, ok := state.images[source]; ok {
|
||||
if encoded, ok := state.images.get(source); ok {
|
||||
return encoded
|
||||
}
|
||||
var encoded *canvasimage.Image
|
||||
@@ -148,7 +148,7 @@ func (r *Renderer) canvasEncodedImage(img image.Image) image.Image {
|
||||
encoded, _ = canvasimage.NewPNGImage(bytes.NewReader(source.Bytes()))
|
||||
}
|
||||
encoded.Bytes = source.Bytes()
|
||||
state.images[source] = encoded
|
||||
state.images.put(source, encoded, 2*imageResourceCost(source, state.images.limit/2))
|
||||
return encoded
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ import (
|
||||
|
||||
// canvasBackendState 保存Canvas字体、字形和编码图片适配缓存
|
||||
type canvasBackendState struct {
|
||||
images map[*EncodedImage]image.Image
|
||||
images renderCache[*EncodedImage, image.Image]
|
||||
fontMap map[string]*canvas.FontFamily
|
||||
svgFontCache map[*canvas.Font]SVGFont
|
||||
textGlyphPathCache map[textGlyphPathCacheKey]textGlyphPathCacheValue
|
||||
textGlyphPathCache renderCache[textGlyphPathCacheKey, textGlyphPathCacheValue]
|
||||
fontOutlines map[*canvas.Font]*sfntOutliner
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ func (r *Renderer) canvasState() *canvasBackendState {
|
||||
return state.(*canvasBackendState)
|
||||
}
|
||||
state := &canvasBackendState{
|
||||
images: make(map[*EncodedImage]image.Image),
|
||||
images: renderCache[*EncodedImage, image.Image]{limit: imageCacheLimit},
|
||||
}
|
||||
state.resetFonts()
|
||||
r.backendStates[key] = state
|
||||
@@ -48,6 +48,6 @@ func (r *Renderer) canvasState() *canvasBackendState {
|
||||
func (s *canvasBackendState) resetFonts() {
|
||||
s.fontMap = make(map[string]*canvas.FontFamily)
|
||||
s.svgFontCache = make(map[*canvas.Font]SVGFont)
|
||||
s.textGlyphPathCache = make(map[textGlyphPathCacheKey]textGlyphPathCacheValue)
|
||||
s.textGlyphPathCache = renderCache[textGlyphPathCacheKey, textGlyphPathCacheValue]{limit: 16 << 20}
|
||||
s.fontOutlines = make(map[*canvas.Font]*sfntOutliner)
|
||||
}
|
||||
+24
-2
@@ -29,6 +29,7 @@ import (
|
||||
"encoding/pem"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math/big"
|
||||
"path"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/emmansun/gmsm/sm2"
|
||||
"github.com/emmansun/gmsm/sm3"
|
||||
"github.com/emmansun/gmsm/smx509"
|
||||
)
|
||||
|
||||
@@ -489,16 +491,36 @@ func (r *Reader) verifySignatureReference(sigPath, method string, ref SignatureR
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
data, err := r.readFile(refPath)
|
||||
var digest hash.Hash
|
||||
switch {
|
||||
case strings.TrimSpace(method) == "":
|
||||
digest = md5.New()
|
||||
case isSM3DigestMethod(method):
|
||||
digest = sm3.New()
|
||||
default:
|
||||
algorithm, ok := signatureDigestHash(method)
|
||||
if !ok {
|
||||
result.Error = fmt.Sprintf("unsupported digest method: %s", method)
|
||||
return result
|
||||
}
|
||||
digest = algorithm.New()
|
||||
}
|
||||
input, err := r.openFile(refPath)
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
actual, err := signatureDigest(method, data)
|
||||
_, err = io.Copy(digest, input)
|
||||
closeErr := input.Close()
|
||||
if err != nil {
|
||||
result.Error = err.Error()
|
||||
return result
|
||||
}
|
||||
if closeErr != nil {
|
||||
result.Error = closeErr.Error()
|
||||
return result
|
||||
}
|
||||
actual := digest.Sum(nil)
|
||||
result.CheckValue = checkValue
|
||||
result.Actual = actual
|
||||
result.Checked = true
|
||||
|
||||
Reference in new issue
Block a user