mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 17:27:08 +08:00
1 parent
f39675233c
commit
773b4195f5
10 files changed
+217
-29
No files matched your search
@@ -57,7 +57,7 @@
|
||||
<option value="300" selected>300 dpi</option>
|
||||
<option value="600">600 dpi</option>
|
||||
</select><span id="dpiValue" aria-hidden="true">300</span></label>
|
||||
<label class="toolbar-select"><select id="outputCompression" class="format-select" title="压缩" aria-label="压缩" disabled><option value="0">默认</option><option value="1">无损</option><option value="2">有损</option></select><span id="compressionValue" data-short="默" aria-hidden="true">默认</span></label>
|
||||
<label class="toolbar-select"><select id="outputCompression" class="format-select" title="压缩" aria-label="压缩" disabled><option value="0">默认</option><option value="1">无损</option><option value="2">轻压</option><option value="3">中压</option><option value="4">强压</option></select><span id="compressionValue" data-short="默" aria-hidden="true">默认</span></label>
|
||||
<button id="exportPageButton" class="button" type="button" data-short="单" title="导出单页" aria-label="导出单页" disabled>单页</button>
|
||||
<button id="exportButton" class="button" type="button" data-short="多" title="导出文档" aria-label="导出文档" aria-haspopup="dialog" aria-controls="exportPanel" disabled>文档</button>
|
||||
</div>
|
||||
@@ -184,7 +184,7 @@
|
||||
<button id="convertAdd" class="button" type="button">添加</button><button id="convertClear" class="button" type="button">清空</button>
|
||||
<div class="convert-options">
|
||||
<div class="form-row"><label for="convertFormat">格式</label><select id="convertFormat" required></select></div>
|
||||
<div class="form-row"><label for="convertCompression">压缩</label><select id="convertCompression"><option value="0">默认</option><option value="1">无损</option><option value="2">有损</option></select></div>
|
||||
<div class="form-row"><label for="convertCompression">压缩</label><select id="convertCompression"><option value="0">默认</option><option value="1">无损</option><option value="2">轻压</option><option value="3">中压</option><option value="4">强压</option></select></div>
|
||||
<div id="convertDPIRow" class="form-row" hidden><label for="convertDPI">精度</label><select id="convertDPI"><option value="96">96 dpi</option><option value="150">150 dpi</option><option value="300" selected>300 dpi</option><option value="600">600 dpi</option></select></div>
|
||||
<div class="form-row"><label for="convertDestination">保存</label><select id="convertDestination"><option value="directory">目录</option><option value="archive">打包</option></select></div>
|
||||
</div>
|
||||
|
||||
@@ -961,9 +961,12 @@ button:not(:disabled):hover {
|
||||
}
|
||||
.convert-dialog .form-actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
row-gap: 12px;
|
||||
}
|
||||
.convert-dialog .form-actions > .button { grid-row: 2; }
|
||||
.convert-dialog .form-actions > .button { grid-row: 2; width: 100%; }
|
||||
#convertClose { grid-column: 3; }
|
||||
#convertCancel, #convertStart { grid-column: 4; }
|
||||
.convert-options {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 1;
|
||||
|
||||
@@ -1254,7 +1254,7 @@ document.addEventListener("selectionchange", syncSelection);
|
||||
el.exportFormat.addEventListener("change", () => updateDPIControl());
|
||||
el.outputCompression.addEventListener("change", () => {
|
||||
el.compressionValue.textContent = el.outputCompression.selectedOptions[0].textContent;
|
||||
el.compressionValue.dataset.short = ["默", "无", "有"][Number(el.outputCompression.value)];
|
||||
el.compressionValue.dataset.short = ["默", "无", "轻", "中", "强"][Number(el.outputCompression.value)];
|
||||
});
|
||||
el.exportPageButton.addEventListener("click", () => exportFile(false));
|
||||
el.exportButton.addEventListener("click", () => openExportPanel());
|
||||
@@ -4646,7 +4646,7 @@ async function signDocument(event) {
|
||||
pages:el.signPages.value.trim(), placement:el.signPlacement.value,
|
||||
x:Number(el.signX.value), y:Number(el.signY.value), width:Number(el.signWidth.value), height:Number(el.signHeight.value),
|
||||
mode:el.signMode.value, lock:el.signLock.checked,
|
||||
compression: { mode: Number(el.outputCompression.value) || 0 },
|
||||
compression: compressionOptions(el.outputCompression.value),
|
||||
};
|
||||
el.signKey.value = "";
|
||||
if (openSeq !== state.openSeq || state.signCanceled || !el.signPanel.open) return;
|
||||
@@ -4966,7 +4966,7 @@ async function runBatch() {
|
||||
if (!defaultFormat) return;
|
||||
const pageRange = item => item.pages.trim() === "全部" ? "" : item.pages.trim();
|
||||
const dpi = Number(batchElements.DPI.value);
|
||||
const compression = { mode: Number(batchElements.Compression.value) || 0 };
|
||||
const compression = compressionOptions(batchElements.Compression.value);
|
||||
const archive = batchElements.Destination.value === "archive";
|
||||
const backend = state.renderBackend;
|
||||
const fontKey = [fontManager.permission, fontManager.records().map(font => [font.id, font.name, font.enabled, font.checksum])];
|
||||
@@ -5092,12 +5092,17 @@ async function runBatch() {
|
||||
}
|
||||
}
|
||||
|
||||
function compressionOptions(value) {
|
||||
const preset = Number(value) || 0;
|
||||
return preset > 2 ? { mode: 2, level: preset - 2 } : { mode: preset };
|
||||
}
|
||||
|
||||
async function exportFile(whole, indices = null, value = el.exportFormat.value, encryption = null) {
|
||||
if (!state.doc || document.body.hasAttribute("aria-busy")) {
|
||||
return;
|
||||
}
|
||||
const saving = value === "ofd";
|
||||
const compression = { mode: Number(el.outputCompression.value) || 0 };
|
||||
const compression = compressionOptions(el.outputCompression.value);
|
||||
const outputOptions = compression.mode ? [compression] : [];
|
||||
if (saving && !state.editorInfo && !state.ofdBytes && !encryption) {
|
||||
return;
|
||||
|
||||
@@ -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-20260929115319-4a25c95d8fef
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260929132513-f5d1ff8ce60a
|
||||
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-20260929115319-4a25c95d8fef h1:AYQXIlR7DHcRaP6+ssVSRYMjmb5FhQkQZiex5P+P1so=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260929115319-4a25c95d8fef/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260929132513-f5d1ff8ce60a h1:+zzZs2zRkhs+HAX70k2ATDbmvEvV4feEJNavl7NCu80=
|
||||
github.com/xiaoqidun/pdfgo v0.0.0-20260929132513-f5d1ff8ce60a/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=
|
||||
|
||||
+40
-11
@@ -22,6 +22,7 @@ import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"hash/crc32"
|
||||
"image"
|
||||
"io"
|
||||
"maps"
|
||||
"slices"
|
||||
@@ -34,14 +35,20 @@ const (
|
||||
CompressionUnchanged = pdfgo.CompressionUnchanged
|
||||
CompressionLossless = pdfgo.CompressionLossless
|
||||
CompressionLossy = pdfgo.CompressionLossy
|
||||
CompressionLight = pdfgo.CompressionLight
|
||||
CompressionMedium = pdfgo.CompressionMedium
|
||||
CompressionStrong = pdfgo.CompressionStrong
|
||||
)
|
||||
|
||||
// CompressionMode 选择默认、无损或有损输出,不改变导出分辨率
|
||||
// CompressionMode 选择默认、无损或有损输出策略
|
||||
type CompressionMode = pdfgo.CompressionMode
|
||||
|
||||
// CompressionLevel 选择轻压、中压或强压,仅用于有损模式
|
||||
type CompressionLevel = pdfgo.CompressionLevel
|
||||
|
||||
// CompressionOptions 配置输出压缩,默认模式沿用现有输出策略,不额外优化
|
||||
// Mode零值CompressionUnchanged表示默认模式,Quality为有损质量1至100,0使用85
|
||||
// 无损表示不引入额外损失,不保证跨格式转换可逆;有损仅降低图片画质,不栅格化文字和矢量
|
||||
// Quality和MaxDPI为零时按Level使用预设,不改变页面尺寸或整页导出精度
|
||||
// 无损表示不引入额外损失,不保证跨格式转换可逆;有损不栅格化文字和矢量
|
||||
type CompressionOptions = pdfgo.CompressionOptions
|
||||
|
||||
// WriteOptions 配置OFD输出副本,不修改编辑资源和撤销记录
|
||||
@@ -52,6 +59,7 @@ type outputOptimization struct {
|
||||
ctx context.Context
|
||||
options CompressionOptions
|
||||
images map[string]bool
|
||||
sizes map[string]image.Point
|
||||
protected bool
|
||||
completed int
|
||||
deflater *flate.Writer
|
||||
@@ -135,21 +143,27 @@ func (e *Editor) outputSnapshot(ctx context.Context, options WriteOptions) (*Edi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
masks, err := reader.compressionMaskFiles(ctx, images)
|
||||
masks, geometry, err := reader.compressionImageSafety(ctx, images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for name := range masks {
|
||||
snapshot.output.images[name] = false
|
||||
}
|
||||
if geometry {
|
||||
snapshot.output.sizes, err = reader.compressionImageSizes(ctx, images, options.Compression.ImageDPI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
// compressionMaskFiles 查找包括模板和注释在内的独立蒙版资源,解析不完整时保留所有图片像素
|
||||
// compressionImageSafety 保护蒙版资源,识别不能可靠降采样的图案及隐藏内容
|
||||
// 入参: ctx 取消上下文, images 图片资源
|
||||
// 返回: map[string]bool 只能无损处理的资源路径, error 读取或取消错误
|
||||
func (r *Reader) compressionMaskFiles(ctx context.Context, images []ImageInfo) (map[string]bool, error) {
|
||||
// 返回: map[string]bool 只能无损处理的资源路径, bool 是否可分析尺寸, error 读取或取消错误
|
||||
func (r *Reader) compressionImageSafety(ctx context.Context, images []ImageInfo) (map[string]bool, bool, error) {
|
||||
names := make(map[string]bool)
|
||||
for name := range r.fileIndex {
|
||||
names[name] = true
|
||||
@@ -159,16 +173,17 @@ func (r *Reader) compressionMaskFiles(ctx context.Context, images []ImageInfo) (
|
||||
}
|
||||
masks := make(map[string]bool)
|
||||
safe := true
|
||||
geometry := len(r.OFD.DocBody) == 1
|
||||
for _, name := range slices.Sorted(maps.Keys(names)) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
if strings.HasSuffix(name, "/") {
|
||||
continue
|
||||
}
|
||||
input, err := r.openFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, false, err
|
||||
}
|
||||
buffer := bufio.NewReader(imageInput{ReadCloser: input, context: ctx})
|
||||
header, _ := buffer.Peek(512)
|
||||
@@ -185,11 +200,25 @@ func (r *Reader) compressionMaskFiles(ctx context.Context, images []ImageInfo) (
|
||||
break
|
||||
}
|
||||
if element, ok := token.(xml.StartElement); ok {
|
||||
if element.Name.Local == "Pattern" {
|
||||
geometry = false
|
||||
}
|
||||
resource, masked := "", false
|
||||
for _, attr := range element.Attr {
|
||||
if attr.Name.Local == "Visible" && (attr.Value == "false" || attr.Value == "0") {
|
||||
geometry = false
|
||||
}
|
||||
if attr.Name.Local == "ResourceID" {
|
||||
resource = strings.TrimSpace(attr.Value)
|
||||
}
|
||||
if attr.Name.Local == "ImageMask" {
|
||||
masks[strings.TrimSpace(attr.Value)] = true
|
||||
masked = true
|
||||
}
|
||||
}
|
||||
if masked {
|
||||
masks[resource] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,7 +233,7 @@ func (r *Reader) compressionMaskFiles(ctx context.Context, images []ImageInfo) (
|
||||
paths[strings.ToLower(cleanPackagePath(img.Location))] = true
|
||||
}
|
||||
}
|
||||
return paths, ctx.Err()
|
||||
return paths, safe && geometry, ctx.Err()
|
||||
}
|
||||
|
||||
// hasOutputSignatures 检查包中各文档,避免改写其他文档签名保护的共享资源
|
||||
@@ -251,7 +280,7 @@ func (e *Editor) writeOutputEntry(archive *zip.Writer, header zip.FileHeader, da
|
||||
if !lossy {
|
||||
options.Mode = CompressionLossless
|
||||
}
|
||||
candidate, err := pdfgo.OptimizeImage(e.output.ctx, data, options)
|
||||
candidate, err := pdfgo.OptimizeImageSize(e.output.ctx, data, options, e.output.sizes[strings.ToLower(cleanPackagePath(header.Name))])
|
||||
if err != nil {
|
||||
if e.output.ctx.Err() != nil {
|
||||
return e.output.ctx.Err()
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// compressionImages 记录图片各处使用的最大像素需求
|
||||
type compressionImages struct {
|
||||
ctx context.Context
|
||||
dpi int
|
||||
sizes map[string]image.Point
|
||||
}
|
||||
|
||||
// compressionImageSize 将毫米显示尺寸换算为像素,非法尺寸不降采样
|
||||
// 入参: width 显示宽度, height 显示高度, dpi 分辨率上限
|
||||
// 返回: image.Point 像素需求
|
||||
func compressionImageSize(width, height float64, dpi int) image.Point {
|
||||
w, h := math.Ceil(width*float64(dpi)/25.4), math.Ceil(height*float64(dpi)/25.4)
|
||||
if w <= 0 || h <= 0 || math.IsNaN(w) || math.IsNaN(h) || math.IsInf(w, 0) || math.IsInf(h, 0) || w > 1<<30 || h > 1<<30 {
|
||||
return image.Point{}
|
||||
}
|
||||
return image.Pt(int(w), int(h))
|
||||
}
|
||||
|
||||
// compressionImageSizes 按正文、模板及注解的实际变换汇总共享图片尺寸
|
||||
// 入参: ctx 取消上下文, images 图片资源, dpi 分辨率上限
|
||||
// 返回: map[string]image.Point 资源像素需求, error 取消错误
|
||||
func (r *Reader) compressionImageSizes(ctx context.Context, images []ImageInfo, dpi int) (map[string]image.Point, error) {
|
||||
if dpi == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
count, err := r.PageCount()
|
||||
if err != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
renderer := NewRenderer(r)
|
||||
defer renderer.ClearCache()
|
||||
visitor := &compressionImages{ctx: ctx, dpi: dpi, sizes: make(map[string]image.Point)}
|
||||
for index := 0; index < count; index++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
page, err := r.PageContentByIndex(index)
|
||||
if err != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
for _, ref := range page.Template {
|
||||
if renderer.loadTemplate(ref.TemplateID) == nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
if err := renderer.WalkPage(page, visitor); err != nil {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
result := make(map[string]image.Point)
|
||||
for _, img := range images {
|
||||
name := strings.ToLower(cleanPackagePath(img.Location))
|
||||
size := visitor.sizes[img.ID]
|
||||
old := result[name]
|
||||
result[name] = image.Pt(max(old.X, size.X), max(old.Y, size.Y))
|
||||
}
|
||||
return result, ctx.Err()
|
||||
}
|
||||
|
||||
// DrawObject 收集图片的完整显示尺寸,不使用裁剪后的局部面积
|
||||
// 入参: object 图形对象, state 继承状态
|
||||
// 返回: error 取消或几何错误
|
||||
func (v *compressionImages) DrawObject(object *GraphicObject, state RenderState) error {
|
||||
if err := v.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if object.Type != "ImageObject" {
|
||||
return nil
|
||||
}
|
||||
img := object.ImageObject
|
||||
box, err := ParseBox(img.Boundary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
local := NewMatrix(img.CTM)
|
||||
if img.CTM == "" {
|
||||
local = Matrix{a: box.W, d: box.H}
|
||||
}
|
||||
matrix, _ := renderObjectMatrix(img.Boundary, local, state)
|
||||
size := compressionImageSize(math.Hypot(matrix.a, matrix.b), math.Hypot(matrix.c, matrix.d), v.dpi)
|
||||
if size.X == 0 || size.Y == 0 {
|
||||
return fmt.Errorf("invalid image placement")
|
||||
}
|
||||
old := v.sizes[img.ResourceID]
|
||||
v.sizes[img.ResourceID] = image.Pt(max(old.X, size.X), max(old.Y, size.Y))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DrawStamp 保留签章内容,不参与图片降采样
|
||||
// 入参: stamp 印章
|
||||
// 返回: error 始终为空
|
||||
func (v *compressionImages) DrawStamp(stamp Stamp) error { return nil }
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/tdewolff/canvas"
|
||||
"github.com/tdewolff/canvas/renderers/ps"
|
||||
@@ -38,12 +39,24 @@ type epsImageRenderer struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// RenderImage 写出Flate或更小的JPEG图片,保留原尺寸与变换
|
||||
// RenderImage 写出Flate或更小的JPEG图片,保留显示尺寸与变换
|
||||
// 入参: img 图片, matrix 绘制变换
|
||||
func (r *epsImageRenderer) RenderImage(img image.Image, matrix canvas.Matrix) {
|
||||
if r.err != nil {
|
||||
return
|
||||
}
|
||||
if dpi := r.options.ImageDPI(); dpi > 0 {
|
||||
original := img.Bounds().Size()
|
||||
target := compressionImageSize(math.Hypot(matrix[0][0], matrix[1][0])*float64(original.X), math.Hypot(matrix[0][1], matrix[1][1])*float64(original.Y), dpi)
|
||||
img, r.err = pdfgo.ResizeImage(r.ctx, img, target)
|
||||
if r.err != nil {
|
||||
return
|
||||
}
|
||||
size := img.Bounds().Size()
|
||||
if size.X > 0 && size.Y > 0 {
|
||||
matrix = matrix.Scale(float64(original.X)/float64(size.X), float64(original.Y)/float64(size.Y))
|
||||
}
|
||||
}
|
||||
bounds := img.Bounds()
|
||||
width, height := bounds.Dx(), bounds.Dy()
|
||||
pixels := make([]byte, width*3)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/tdewolff/canvas"
|
||||
@@ -42,7 +43,7 @@ type svgResourceRenderer struct {
|
||||
writer io.Writer
|
||||
fonts []SVGFont
|
||||
images []SVGImage
|
||||
imageNames map[image.Image]string
|
||||
imageNames map[svgImageKey]string
|
||||
seen map[string]bool
|
||||
styles strings.Builder
|
||||
err error
|
||||
@@ -51,6 +52,12 @@ type svgResourceRenderer struct {
|
||||
embeddedFonts bool
|
||||
}
|
||||
|
||||
// svgImageKey 按原图及输出精度复用图片编码,避免不同显示尺寸共用低清结果
|
||||
type svgImageKey struct {
|
||||
image image.Image
|
||||
size image.Point
|
||||
}
|
||||
|
||||
// svgObjectGroup 记录编辑分组的稳定路径,不把底纹内部绘制当作成员
|
||||
type svgObjectGroup struct {
|
||||
path string
|
||||
@@ -61,7 +68,13 @@ type svgObjectGroup struct {
|
||||
// RenderImage 按输出策略编码图片,分离资源时保持尺寸和变换
|
||||
// 入参: img 图片对象, m 变换矩阵
|
||||
func (s *svgResourceRenderer) RenderImage(img image.Image, m canvas.Matrix) {
|
||||
name, ok := s.imageNames[img]
|
||||
var target image.Point
|
||||
if s.renderer != nil && s.renderer.Compression.ImageDPI() > 0 {
|
||||
bounds := img.Bounds()
|
||||
target = compressionImageSize(math.Hypot(m[0][0], m[1][0])*float64(bounds.Dx()), math.Hypot(m[0][1], m[1][1])*float64(bounds.Dy()), s.renderer.Compression.ImageDPI())
|
||||
}
|
||||
key := svgImageKey{image: img, size: target}
|
||||
name, ok := s.imageNames[key]
|
||||
var resource SVGImage
|
||||
if !ok {
|
||||
resource = SVGImage{MIME: "image/png"}
|
||||
@@ -76,7 +89,7 @@ func (s *svgResourceRenderer) RenderImage(img image.Image, m canvas.Matrix) {
|
||||
resource.Data = buffer.Bytes()
|
||||
}
|
||||
if s.renderer != nil && s.renderer.Compression.Mode != CompressionUnchanged {
|
||||
data, err := pdfgo.OptimizeImage(s.renderer.outputContext(), resource.Data, s.renderer.Compression)
|
||||
data, err := pdfgo.OptimizeImageSize(s.renderer.outputContext(), resource.Data, s.renderer.Compression, target)
|
||||
if err != nil {
|
||||
if s.err = s.renderer.outputContext().Err(); s.err != nil {
|
||||
return
|
||||
@@ -88,7 +101,7 @@ func (s *svgResourceRenderer) RenderImage(img image.Image, m canvas.Matrix) {
|
||||
if s.imageNames != nil {
|
||||
name = fmt.Sprintf("ofdgo-image-%x", sha256.Sum256(resource.Data))
|
||||
resource.Name = name
|
||||
s.imageNames[img] = name
|
||||
s.imageNames[key] = name
|
||||
if !s.seen[name] {
|
||||
s.seen[name] = true
|
||||
s.images = append(s.images, resource)
|
||||
@@ -174,7 +187,7 @@ func (r *Renderer) renderSVGResources(page *PageContent, writer io.Writer, image
|
||||
// 图片方向由文档变换决定,不再由浏览器应用EXIF方向。
|
||||
io.WriteString(buffer, `<g style="image-orientation:none">`)
|
||||
if images {
|
||||
s.imageNames = make(map[image.Image]string)
|
||||
s.imageNames = make(map[svgImageKey]string)
|
||||
}
|
||||
if objects {
|
||||
s.objects = make(map[*GraphicObject]string)
|
||||
|
||||
+11
-2
@@ -241,12 +241,21 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
||||
return nil, err
|
||||
}
|
||||
var masks map[string]bool
|
||||
geometry := false
|
||||
if options.Compression.Mode == CompressionLossy {
|
||||
masks, err = r.compressionMaskFiles(context.Background(), images)
|
||||
masks, geometry, err = r.compressionImageSafety(context.Background(), images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dpi := 0
|
||||
if geometry {
|
||||
dpi = options.Compression.ImageDPI()
|
||||
}
|
||||
sizes, err := r.compressionImageSizes(context.Background(), images, dpi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seen := make(map[string]bool)
|
||||
for i, img := range images {
|
||||
if err := options.progress.report("compress", i, len(images)); err != nil {
|
||||
@@ -261,7 +270,7 @@ func signatureWritePackage(parts map[string][]byte, options SignatureWriteOption
|
||||
if masks[strings.ToLower(cleanPackagePath(img.Location))] {
|
||||
compression.Mode = CompressionLossless
|
||||
}
|
||||
if optimized, err := pdfgo.OptimizeImage(context.Background(), data, compression); err == nil {
|
||||
if optimized, err := pdfgo.OptimizeImageSize(context.Background(), data, compression, sizes[strings.ToLower(cleanPackagePath(img.Location))]); err == nil {
|
||||
parts[img.Location] = optimized
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user