feat(转换能力): 增强PDF字形与扫描件转换
build.yaml / build (push) Successful in 41s

This commit is contained in:
xiaoqidun committed 2026-09-25 00:31:52 +08:00
1 parent 2ae89e54b4
commit 786b1a41c2
14 files changed
+213 -60

No files matched your search

+4
View File
@@ -4047,6 +4047,10 @@ async function openDocument(options = {}) {
async function loadDocumentDetails(openSeq) {
try {
const details = await callWASM("ofdgoDocumentInfo");
if (openSeq !== state.openSeq) return;
Object.assign(state.doc, details, { detailsPending: false, detailsError: "" });
renderMeta();
while (openSeq === state.openSeq) {
await waitForPaint();
if (openSeq !== state.openSeq) return;
+1 -1
View File
@@ -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-20260924125905-097a17bdf501
github.com/xiaoqidun/pdfgo v0.0.0-20260924162208-698614ef984e
golang.org/x/crypto v0.57.0
golang.org/x/image v0.46.0
golang.org/x/text v0.42.0
+2 -2
View File
@@ -60,8 +60,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-20260924125905-097a17bdf501 h1:6kzh25IgKXVcAbigV1i3AO3pfpzAniC0F4mXwPGRTrY=
github.com/xiaoqidun/pdfgo v0.0.0-20260924125905-097a17bdf501/go.mod h1:/D0XHgfZ7xNhix95swpNaThpuqoJjVR5wIadogtR5pI=
github.com/xiaoqidun/pdfgo v0.0.0-20260924162208-698614ef984e h1:17a5JOpYM6gwXZnhapgrXA6H7LhtGVrF0rpeiEORYUA=
github.com/xiaoqidun/pdfgo v0.0.0-20260924162208-698614ef984e/go.mod h1:/D0XHgfZ7xNhix95swpNaThpuqoJjVR5wIadogtR5pI=
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=
+5 -2
View File
@@ -412,10 +412,13 @@ func documentInfo(args []js.Value) (any, error) {
if currentSession == nil {
return nil, fmt.Errorf("ofd document is not opened")
}
if len(args) > 0 && args[0].Bool() && !currentSession.scanFontInfoBatch() {
if len(args) > 0 && args[0].Bool() {
if !currentSession.scanFontInfoBatch() {
return map[string]bool{"detailsPending": true}, nil
}
return currentSession.Info(), nil
return map[string]any{"fonts": currentSession.fontInfos, "fontCount": len(currentSession.fontInfos), "detailsPending": false}, nil
}
return currentSession.Details(), nil
}
// exportAttachment 分块导出附件
+32 -20
View File
@@ -89,6 +89,17 @@ type DocumentInfo struct {
DetailsPending bool `json:"detailsPending,omitempty"`
}
// DocumentDetails 不含页面数组的文档补充信息
type DocumentDetails struct {
FontCount int `json:"fontCount"`
SignatureCount int `json:"signatureCount"`
SignatureError string `json:"signatureError,omitempty"`
AttachmentError string `json:"attachmentError,omitempty"`
Attachments []AttachmentInfo `json:"attachments,omitempty"`
Fonts []FontInfo `json:"fonts"`
Signatures []SignatureInfo `json:"signatures"`
}
// OutlineInfo 目录节点信息
type OutlineInfo = ofdgo.OutlineInfo
@@ -491,17 +502,10 @@ func (s *Session) pageInfos() []PageInfo {
return pages
}
// Info 获取完整文档信息
// 返回: DocumentInfo 文档信息
func (s *Session) Info() DocumentInfo {
info := DocumentInfo{
Encryption: s.encryptionInfo(),
Version: s.Reader.Version(),
DocType: s.Reader.DocType(),
PageCount: len(s.doc.Pages.Page),
Pages: s.pageInfos(),
Outlines: s.doc.OutlineInfos(),
}
// Details 获取无需逐页扫描的附件、签名及字体匹配结果
// 返回: DocumentDetails 文档补充信息
func (s *Session) Details() DocumentDetails {
info := DocumentDetails{}
if attachments, err := s.Reader.Attachments(); err == nil {
for _, attachment := range attachments {
if attachment.Visible {
@@ -523,20 +527,28 @@ func (s *Session) Info() DocumentInfo {
info.SignatureError = err.Error()
}
info.SignatureCount = len(info.Signatures)
if docInfo, err := s.Reader.DocInfo(); err == nil && docInfo != nil {
info.Title = docInfo.Title
info.Author = docInfo.Author
info.Subject = docInfo.Subject
if docInfo.CustomDatas != nil {
info.CustomData = append([]ofdgo.CustomData(nil), docInfo.CustomDatas.CustomData...)
}
info.CreationDate = docInfo.CreationDate
info.ModDate = docInfo.ModDate
if fonts, err := s.Renderer.DeclaredFontInfos(); err == nil {
info.Fonts = fonts
info.FontCount = len(fonts)
}
return info
}
// Info 获取完整文档信息及逐页字体用量
// 返回: DocumentInfo 文档信息
func (s *Session) Info() DocumentInfo {
info := s.Summary()
details := s.Details()
info.Attachments = details.Attachments
info.AttachmentError = details.AttachmentError
info.Signatures = details.Signatures
info.SignatureCount = details.SignatureCount
info.SignatureError = details.SignatureError
for !s.scanFontInfo() {
}
info.Fonts = s.fontInfos
info.FontCount = len(info.Fonts)
info.DetailsPending = false
return info
}
+22 -5
View File
@@ -503,7 +503,7 @@ func (e *Editor) nextID() string {
return strconv.Itoa(e.maxID)
}
// AddImage 注册PNG或JPEG图片,重复资源复用标识,引用后写入文档
// AddImage 注册PNG、JPEG或JBIG2图片,重复资源复用标识,引用后写入文档
// 不透明纯黑白PNG仅在无损JBIG2编码更小时转换,其他图片保留原始编码
// 入参: data 图片数据
// 返回: string 图片资源标识, error 错误信息
@@ -512,8 +512,8 @@ func (e *Editor) AddImage(data []byte) (string, error) {
if err != nil {
return "", err
}
if (format != "png" && format != "jpeg") || config.Width <= 0 || config.Height <= 0 {
return "", fmt.Errorf("only PNG and JPEG images are supported")
if (format != "png" && format != "jpeg" && format != "jbig2") || config.Width <= 0 || config.Height <= 0 {
return "", fmt.Errorf("only PNG, JPEG and JBIG2 images are supported")
}
key := editorResourceKey{checksum: sha256.Sum256(data), index: -1}
if id, ok := e.resourceID[key]; ok {
@@ -991,10 +991,21 @@ func (e *Editor) LayoutText(obj *TextObject, value string, options TextLayout) e
// 入参: obj 文字对象
// 返回: error 错误信息
func (e *Editor) prepareText(obj *TextObject) error {
sfnt, err := e.editorFont(obj.Font)
var sfnt FontMetrics
external := false
for _, resource := range e.resources {
if resource.font != nil && resource.font.ID == obj.Font && resource.font.FontFile == "" {
external = true
break
}
}
if !external {
var err error
sfnt, err = e.editorFont(obj.Font)
if err != nil {
return err
}
}
if !finite(obj.Size) || obj.Size <= 0 {
return fmt.Errorf("text requires a positive finite size")
}
@@ -1004,9 +1015,15 @@ func (e *Editor) prepareText(obj *TextObject) error {
if obj.VScale != 0 || obj.Decoration != "" {
return &EditError{Code: EditUnsupportedObject, Err: fmt.Errorf("text extensions are not supported for creation")}
}
if external {
if len(obj.CGTransform) != 0 {
return fmt.Errorf("external font text cannot use glyph indices")
}
} else {
if err := validateTextGlyphs(*obj, sfnt.NumGlyphs()); err != nil {
return err
}
}
if !finite(obj.HScale) || obj.HScale < 0 || !finite(obj.LineWidth) || obj.LineWidth < 0 || !finite(obj.MiterLimit) || obj.MiterLimit < 0 {
return fmt.Errorf("invalid text dimensions")
}
@@ -1045,7 +1062,7 @@ func (e *Editor) prepareText(obj *TextObject) error {
transform++
}
mapped := transform < len(obj.CGTransform) && position >= obj.CGTransform[transform].CodePosition
if !mapped && sfnt.GlyphIndex(char) == 0 {
if !external && !mapped && sfnt.GlyphIndex(char) == 0 {
missing = append(missing, char)
}
position++
+23 -1
View File
@@ -72,6 +72,28 @@ func (e *Editor) AddFont(file FontFile, index int) (string, error) {
return id, nil
}
// AddExternalFont 注册不含字体文件的名称引用,由阅读环境匹配
// 入参: name 字体名称
// 返回: string 字体资源标识, error 错误信息
func (e *Editor) AddExternalFont(name string) (string, error) {
name = strings.TrimSpace(name)
if name == "" {
return "", fmt.Errorf("empty external font name")
}
for _, resource := range e.resources {
if resource.font != nil && resource.font.FontFile == "" && resource.font.FontName == name {
return resource.font.ID, nil
}
}
if err := e.prepareSourceIDs(); err != nil {
return "", err
}
id := e.nextID()
e.resources = append(e.resources, editorResource{font: &Font{ID: id, FontName: name, FamilyName: name}})
e.fontRenderer = nil
return id, nil
}
// newEditorFontUsage 创建字体用字记录
// 返回: *editorFontUsage 用字记录
func newEditorFontUsage() *editorFontUsage {
@@ -133,7 +155,7 @@ func (e *Editor) subsetFonts(progress editorProgress) (map[string][]byte, error)
used := make(map[string]map[uint16]bool)
mapped := make(map[string]bool)
for _, resource := range e.resources {
if resource.font == nil {
if resource.font == nil || resource.font.FontFile == "" {
continue
}
resourceFont := e.fonts[resource.font.ID]
+15 -2
View File
@@ -781,13 +781,15 @@ func (e *Editor) editorFont(id string) (FontMetrics, error) {
return nil, &EditError{Code: EditFontUnavailable, Err: fmt.Errorf("fonts: %w", ErrBackendUnavailable)}
}
var data []byte
createdExternal := false
for _, resource := range e.resources {
if resource.font != nil && resource.font.ID == id {
data = resource.data
createdExternal = resource.font.FontFile == ""
break
}
}
if data == nil && e.source != nil {
if data == nil && e.source != nil && !createdExternal {
if e.fontRenderer == nil {
e.fontRenderer = e.newRenderer(e.source.reader)
}
@@ -797,6 +799,17 @@ func (e *Editor) editorFont(id string) (FontMetrics, error) {
}
data = resolved.Data
}
if data == nil && createdExternal {
reader, err := e.Reader()
if err != nil {
return nil, &EditError{Code: EditFontUnavailable, Err: err}
}
resolved, err := e.newRenderer(reader).ResolveFont(id, false)
if err != nil {
return nil, &EditError{Code: EditFontUnavailable, Err: err}
}
data = resolved.Data
}
if data == nil {
return nil, &EditError{Code: EditFontUnavailable, Err: fmt.Errorf("font %q not found", id)}
}
@@ -813,7 +826,7 @@ func (e *Editor) editorFont(id string) (FontMetrics, error) {
// 返回: []byte 独立字体数据, error 错误信息
func (e *Editor) FontData(id string) ([]byte, error) {
for _, resource := range e.resources {
if resource.font != nil && resource.font.ID == id {
if resource.font != nil && resource.font.ID == id && len(resource.data) != 0 {
return bytes.Clone(resource.data), nil
}
}
+23 -4
View File
@@ -176,6 +176,15 @@ func (r *Renderer) ScanFontInfos() (*FontInfoScanner, error) {
return &FontInfoScanner{renderer: r, doc: doc, usage: make(map[string]int)}, nil
}
// DeclaredFontInfos 获取已声明字体的匹配结果,不扫描页面用量
// 返回: []FontInfo 字体诊断列表, error 错误信息
func (r *Renderer) DeclaredFontInfos() ([]FontInfo, error) {
if r == nil || r.Reader == nil {
return nil, fmt.Errorf("ofd renderer is not initialized")
}
return r.fontInfos(nil)
}
// Next 统计下一页或模板的字体用量
// 返回: bool 是否处理了页面,false表示扫描结束
func (s *FontInfoScanner) Next() bool {
@@ -298,10 +307,17 @@ func (r *Renderer) fontInfo(font Font) FontInfo {
}
return info
}
if resolved, err := r.ResolveFont(font.ID, false); err == nil && resolved.Source != "" {
info.Matched = resolved.Source
info.MatchedFace = resolved.Face
if resolved.Exact {
if r.backends.Fonts != nil {
if source, metrics := r.fontSourceMatch(r.backends.Fonts, font.ID, &font, false); metrics != nil {
data, err := r.readFontSource(source, &font)
if err == nil {
faces, err := (FontFile{Data: data}).Faces()
if err == nil && len(faces) != 0 {
face := faces[0]
face.Index = source.face
info.Matched = source.name
info.MatchedFace = &face
if source.exact {
info.Status = FontStatusMatched
info.Detail = "使用外部字体文件"
} else {
@@ -310,6 +326,9 @@ func (r *Renderer) fontInfo(font Font) FontInfo {
}
return info
}
}
}
}
info.Status = FontStatusMissing
info.Detail = "可用字体文件缺失"
return info
+18 -6
View File
@@ -26,15 +26,15 @@ import (
"github.com/xiaoqidun/pdfgo"
)
// PDFImportOptions 指定PDF转换的运行时后端、进度通知与源文件检查策略
// Strict禁止恢复缺失的图形状态资源和无目标链接,默认恢复并在报告中记录警告
// PDFImportOptions 指定PDF转换的运行时后端、进度通知与检查策略
// Strict禁止恢复缺失资源或丢失内容语义,默认在报告中记录警告
type PDFImportOptions struct {
Backends *RenderBackends
Progress func(int) error
Strict bool
}
// PDFImportReport 汇总转换页数、对象数、链接数和源文件恢复警告
// PDFImportReport 汇总转换页数、对象数、链接数和转换警告
type PDFImportReport struct {
Pages int
TextObjects int
@@ -46,6 +46,7 @@ type PDFImportReport struct {
// pdfImporter 保存单次转换的对象与资源状态
type pdfImporter struct {
ctx context.Context
reader *pdfgo.Reader
editor *Editor
report PDFImportReport
@@ -60,6 +61,8 @@ type pdfImporter struct {
imageIDs map[*pdfgo.Stream]string
maskClips map[*pdfgo.SoftMask]pdfgo.Path
pageBox pdfgo.Rectangle
pageWidth float64
pageHeight float64
pendingPath *pdfgo.PathMark
}
@@ -78,7 +81,7 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
if options.Backends != nil {
editor.SetRenderBackends(*options.Backends)
}
importer := pdfImporter{reader: reader, editor: editor, fontIDs: map[*pdfgo.Font]string{}, fontMetrics: map[string]FontMetrics{}, pages: map[pdfgo.Reference]*pdfgo.Page{}, pageIDs: map[pdfgo.Reference]string{}}
importer := pdfImporter{ctx: ctx, reader: reader, editor: editor, fontIDs: map[*pdfgo.Font]string{}, fontMetrics: map[string]FontMetrics{}, pages: map[pdfgo.Reference]*pdfgo.Page{}, pageIDs: map[pdfgo.Reference]string{}}
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
_, width, height := pdfPageMatrix(page)
if _, err := editor.AddPage(width, height); err != nil {
@@ -92,10 +95,12 @@ func ImportPDF(ctx context.Context, source io.ReaderAt, size int64, options PDFI
return nil, PDFImportReport{}, err
}
err = reader.WalkPages(ctx, func(index int, page *pdfgo.Page) error {
matrix, _, _ := pdfPageMatrix(page)
matrix, width, height := pdfPageMatrix(page)
importer.matrix = matrix
importer.page = index
importer.pageBox = page.CropBox
importer.pageWidth = width
importer.pageHeight = height
importer.maskClips = make(map[*pdfgo.SoftMask]pdfgo.Path)
visitor := pdfgo.Visitor{Path: importer.path, Text: importer.text, Image: importer.image}
visitor.Group = func(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error {
@@ -322,7 +327,7 @@ func (p *pdfImporter) appendPath(mark pdfgo.PathMark) error {
if err != nil {
return err
}
if mark.Fill && mark.Style.FillOverprint && !pdfOpaqueBlack(mark.Style.Fill) || mark.Stroke && mark.Style.StrokeOverprint && !pdfOpaqueBlack(mark.Style.Stroke) {
if mark.Fill && mark.Style.FillOverprint && pdfOverprintNeedsSeparation(mark.Style.Fill) || mark.Stroke && mark.Style.StrokeOverprint && pdfOverprintNeedsSeparation(mark.Style.Stroke) {
return &pdfgo.UnsupportedError{Feature: "color separation overprint"}
}
var points []pdfgo.Point
@@ -392,3 +397,10 @@ func (p *pdfImporter) pathColor(paint pdfgo.Paint, box Box) *FillColor {
func pdfOpaqueBlack(paint pdfgo.Paint) bool {
return paint.Alpha == 1 && paint.CMYK != nil && paint.CMYK[3] == 1
}
// pdfOverprintNeedsSeparation 判断彩色CMYK套印是否依赖分色输出
// 入参: paint PDF颜色与不透明度
// 返回: bool 是否需要保留分色语义
func pdfOverprintNeedsSeparation(paint pdfgo.Paint) bool {
return paint.CMYK != nil && !pdfOpaqueBlack(paint)
}
+1 -1
View File
@@ -72,7 +72,7 @@ func (p *pdfImporter) group(mark pdfgo.GroupMark, walk func(pdfgo.Visitor) error
if stroke {
color, overprint = path.Style.Stroke, path.Style.StrokeOverprint
}
if color.Alpha != 1 || color.Axial != nil || overprint && !pdfOpaqueBlack(color) {
if color.Alpha != 1 || color.Axial != nil || overprint && pdfOverprintNeedsSeparation(color) {
return &pdfgo.UnsupportedError{Feature: "transparent or overprinted group content"}
}
if paint == nil {
+61 -16
View File
@@ -56,15 +56,23 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
id = ""
}
if id == "" {
jbig2Original, err := source.JBIG2File()
if err != nil {
return err
}
filter, err := p.reader.Resolve(source.Stream.Dictionary["Filter"])
if err != nil {
return err
}
jpegOriginal := filter == pdfgo.Name("DCTDecode") && len(source.Decode) == 0 && !source.ImageMask && source.ColorSpace != pdfgo.Name("DeviceCMYK") && source.Mask == nil && source.SoftMask == nil
decoded, err := source.DecodeImage()
space, _ := source.ColorSpace.(pdfgo.Name)
jpegOriginal := filter == pdfgo.Name("DCTDecode") && len(source.Decode) == 0 && !source.ImageMask && (space == "DeviceGray" || space == "DeviceRGB") && source.Mask == nil && source.SoftMask == nil
var decoded image.Image
if len(jbig2Original) == 0 {
decoded, err = source.DecodeImage()
if err != nil {
return err
}
}
if source.ImageMask {
if mark.Style.Fill.CMYK != nil {
return &pdfgo.UnsupportedError{Feature: "CMYK stencil image"}
@@ -85,7 +93,9 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
decoded = stencil
}
var encoded bytes.Buffer
if jpegOriginal {
if len(jbig2Original) != 0 {
encoded.Write(jbig2Original)
} else if jpegOriginal {
encoded.Write(source.Stream.Data)
} else {
if err := png.Encode(&encoded, decoded); err != nil {
@@ -112,10 +122,25 @@ func (p *pdfImporter) image(mark pdfgo.ImageMark) error {
return nil
}
// text 保留原字体、字形编号与逐字基线,不重新塑形
// text 保留内嵌字形及逐字基线,外部字体保留名称引用
// 入参: mark PDF文字绘制信息
// 返回: error 错误信息
func (p *pdfImporter) text(mark pdfgo.TextMark) error {
if mark.Font.Subtype == pdfgo.Name("Type3") {
if err := p.flushPath(); err != nil {
return err
}
visitor := pdfgo.Visitor{Path: p.path, Image: p.image}
visitor.Group = func(group pdfgo.GroupMark, walk func(pdfgo.Visitor) error) error {
return p.group(group, walk, visitor)
}
for index := range mark.Glyphs {
if err := p.reader.WalkType3Glyph(p.ctx, mark, index, visitor); err != nil {
return err
}
}
return p.flushPath()
}
if mark.Style.BlendMode == "Multiply" {
return &pdfgo.UnsupportedError{Feature: "multiply text"}
}
@@ -130,45 +155,51 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
if err != nil {
return err
}
if (mark.Mode == 0 || mark.Mode == 2) && mark.Style.FillOverprint && !pdfOpaqueBlack(mark.Style.Fill) || (mark.Mode == 1 || mark.Mode == 2) && mark.Style.StrokeOverprint && !pdfOpaqueBlack(mark.Style.Stroke) {
if (mark.Mode == 0 || mark.Mode == 2) && mark.Style.FillOverprint && pdfOverprintNeedsSeparation(mark.Style.Fill) || (mark.Mode == 1 || mark.Mode == 2) && mark.Style.StrokeOverprint && pdfOverprintNeedsSeparation(mark.Style.Stroke) {
return &pdfgo.UnsupportedError{Feature: "text color separation overprint"}
}
font := mark.Font
if len(font.Program) == 0 {
return &pdfgo.UnsupportedError{Feature: "text without embedded font"}
}
if font.ProgramType != "FontFile2" && font.ProgramType != "OpenType" && font.ProgramType != "Type1C" && font.ProgramType != "CIDFontType0C" {
embedded := len(font.Program) != 0
if embedded && font.ProgramType != "FontFile2" && font.ProgramType != "OpenType" && font.ProgramType != "Type1C" && font.ProgramType != "CIDFontType0C" {
return &pdfgo.UnsupportedError{Feature: "font program " + string(font.ProgramType)}
}
id := p.fontIDs[font]
if id == "" {
var err error
if embedded {
program, err := pdfFontProgram(font)
if err != nil {
return err
}
id, err = p.editor.AddFont(FontFile{Name: font.Name + ".ttf", Data: program}, 0)
} else {
id, err = p.editor.AddExternalFont(font.Name)
}
if err != nil {
return err
}
p.fontIDs[font] = id
}
metrics := p.fontMetrics[id]
var metrics FontMetrics
var outline FontOutlines
if embedded {
metrics = p.fontMetrics[id]
if metrics == nil {
if p.editor.backends.Fonts == nil {
return fmt.Errorf("PDF font backend unavailable")
}
var err error
metrics, err = p.editor.backends.Fonts.OpenFont(p.editor.fonts[id].Data)
if err != nil {
return err
}
p.fontMetrics[id] = metrics
}
outline, ok := metrics.(FontOutlines)
var ok bool
outline, ok = metrics.(FontOutlines)
if !ok {
return fmt.Errorf("PDF font backend does not provide glyph outlines")
}
}
const unit = 25.4 / 72
m := p.matrix.Mul(mark.Matrix).Mul(pdfgo.Matrix{1 / unit, 0, 0, -1 / unit, 0, 0})
var points []pdfgo.Point
@@ -183,7 +214,7 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
}
glyph.Text = string(character)
}
if !glyph.HasID {
if embedded && !glyph.HasID {
if utf8.RuneCountInString(glyph.Text) != 1 {
return &pdfgo.UnsupportedError{Feature: "simple font glyph ligature mapping"}
}
@@ -193,13 +224,18 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
return fmt.Errorf("missing PDF glyph for %U", char)
}
}
if gid >= metrics.NumGlyphs() {
if embedded && gid >= metrics.NumGlyphs() {
return fmt.Errorf("PDF glyph index outside embedded font")
}
if !embedded && glyph.Text == "" {
return &pdfgo.UnsupportedError{Feature: "unmapped external font character"}
}
origin := mark.Positions[n]
object.TextCode = append(object.TextCode, TextCode{X: pdfNumbers(origin.X * unit), Y: pdfNumbers(-origin.Y * unit), Value: glyph.Text})
object.CGTransform = append(object.CGTransform, CGTransform{CodePosition: position, CodeCount: utf8.RuneCountInString(glyph.Text), GlyphCount: 1, Glyphs: strconv.Itoa(int(gid))})
position += utf8.RuneCountInString(glyph.Text)
if embedded {
count := utf8.RuneCountInString(glyph.Text)
object.CGTransform = append(object.CGTransform, CGTransform{CodePosition: position, CodeCount: count, GlyphCount: 1, Glyphs: strconv.Itoa(int(gid))})
position += count
path, err := outline.GlyphOutline(gid, object.Size)
if err != nil {
return err
@@ -211,6 +247,12 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
for _, point := range []pdfgo.Point{{X: bounds.X, Y: bounds.Y}, {X: bounds.X + bounds.W, Y: bounds.Y}, {X: bounds.X, Y: bounds.Y + bounds.H}, {X: bounds.X + bounds.W, Y: bounds.Y + bounds.H}} {
points = append(points, m.Apply(pdfgo.Point{X: origin.X*unit + point.X*mark.HorizontalScale, Y: -origin.Y*unit + point.Y}))
}
} else {
width := glyph.Width / 1000 * object.Size * mark.HorizontalScale
for _, point := range []pdfgo.Point{{X: 0, Y: -object.Size}, {X: width, Y: -object.Size}, {X: 0, Y: object.Size / 4}, {X: width, Y: object.Size / 4}} {
points = append(points, m.Apply(pdfgo.Point{X: origin.X*unit + point.X, Y: -origin.Y*unit + point.Y}))
}
}
}
box := pdfBounds(points)
fill, stroke, visible := mark.Mode == 0 || mark.Mode == 2, mark.Mode == 1 || mark.Mode == 2, mark.Mode != 3
@@ -232,6 +274,9 @@ func (p *pdfImporter) text(mark pdfgo.TextMark) error {
}
}
object.Boundary = pdfBoundary(box)
if box.X >= p.pageWidth || box.Y >= p.pageHeight || box.X+box.W <= 0 || box.Y+box.H <= 0 {
visible = false
}
object.CTM = pdfNumbers(m[0], m[1], m[2], m[3], m[4]-box.X, m[5]-box.Y)
object.Fill = &fill
object.Stroke = &stroke
+3
View File
@@ -307,6 +307,9 @@ func (r *Reader) VerifyDocumentSignatures(index int, opts ...SignatureVerifyOpti
}
body := r.OFD.DocBody[index]
options.DocIndex, options.DocRoot = index, r.signatureCoveragePath(body.DocRoot)
if index == 0 && r.doc != nil && r.doc.Signatures == "" {
return nil, nil
}
data, err := r.readFile(body.DocRoot)
if err != nil {
return nil, err
+3
View File
@@ -330,6 +330,9 @@ func (e *Editor) writeParts(write func(string, []byte, bool) error, progress edi
}
for _, resources := range [][]editorResource{fonts, images} {
for _, resource := range resources {
if resource.name == "" {
continue
}
if err := write(resource.name, resource.data, resource.image != nil); err != nil {
return err
}