mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-08-30 12:12:40 +08:00
@@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
// FixFontDataAggressive 激进修复字体数据
|
// FixFontDataAggressive 激进修复字体数据
|
||||||
// 尝试修复缺失表(OS/2, cmap等)的TrueType字体或包装CFF裸数据
|
// 尝试修复缺失表(OS/2, cmap等)的TrueType字体或包装CFF裸数据
|
||||||
|
// 对显式字形映射补充私有字符cmap,便于按glyph id渲染
|
||||||
// 入参: data 原始字体数据, fixCmap 是否修复cmap, fixName 是否修复name
|
// 入参: data 原始字体数据, fixCmap 是否修复cmap, fixName 是否修复name
|
||||||
// 返回: bool 是否修复, []byte 修复后数据, map[rune]uint16 字符映射, bool 是否缺失cmap, error 错误信息
|
// 返回: bool 是否修复, []byte 修复后数据, map[rune]uint16 字符映射, bool 是否缺失cmap, error 错误信息
|
||||||
func FixFontDataAggressive(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uint16, bool, error) {
|
func FixFontDataAggressive(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uint16, bool, error) {
|
||||||
|
|||||||
+7
-6
@@ -120,7 +120,7 @@ func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uin
|
|||||||
if !missingPost && hasBadPostTable(existingTables["post"], numGlyphs, !isCFFSfnt) {
|
if !missingPost && hasBadPostTable(existingTables["post"], numGlyphs, !isCFFSfnt) {
|
||||||
missingPost = true
|
missingPost = true
|
||||||
}
|
}
|
||||||
if !missingHead && !missingMaxp && !missingHhea && !missingHmtx &&
|
if !fixCmap && !missingHead && !missingMaxp && !missingHhea && !missingHmtx &&
|
||||||
!missingOS2 && !missingCmap && !missingName && !missingPost && !malformedDirectory {
|
!missingOS2 && !missingCmap && !missingName && !missingPost && !malformedDirectory {
|
||||||
return false, data, nil, false, nil
|
return false, data, nil, false, nil
|
||||||
}
|
}
|
||||||
@@ -166,16 +166,17 @@ func fixTrueType(data []byte, fixCmap, fixName bool) (bool, []byte, map[rune]uin
|
|||||||
newTables["OS/2"] = buildOS2TableWithMetrics(ascender, descender)
|
newTables["OS/2"] = buildOS2TableWithMetrics(ascender, descender)
|
||||||
}
|
}
|
||||||
var mapping map[rune]uint16
|
var mapping map[rune]uint16
|
||||||
if missingCmap && fixCmap {
|
if fixCmap {
|
||||||
if isCFFSfnt {
|
if !missingCmap {
|
||||||
|
mapping = parseCmapMappings(newTables["cmap"])
|
||||||
|
}
|
||||||
|
if len(mapping) == 0 && isCFFSfnt {
|
||||||
mapping = getCmapFromCFF(newTables["CFF "], int(numGlyphs))
|
mapping = getCmapFromCFF(newTables["CFF "], int(numGlyphs))
|
||||||
}
|
}
|
||||||
if len(mapping) == 0 {
|
if len(mapping) == 0 {
|
||||||
mapping = make(map[rune]uint16)
|
mapping = make(map[rune]uint16)
|
||||||
for i := uint16(0); i < numGlyphs; i++ {
|
|
||||||
mapping[packedGlyphRune(i)] = i
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
addPackedGlyphMapping(mapping, numGlyphs)
|
||||||
newTables["cmap"] = buildCmapTable(numGlyphs, mapping)
|
newTables["cmap"] = buildCmapTable(numGlyphs, mapping)
|
||||||
}
|
}
|
||||||
if missingName && fixName {
|
if missingName && fixName {
|
||||||
|
|||||||
@@ -76,6 +76,165 @@ func checkMissingCmap(data []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseCmapMappings 解析 cmap 字符映射
|
||||||
|
// 入参: data cmap表数据
|
||||||
|
// 返回: map[rune]uint16 字符到字形映射
|
||||||
|
func parseCmapMappings(data []byte) map[rune]uint16 {
|
||||||
|
if len(data) < 4 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
numTables := int(binary.BigEndian.Uint16(data[2:4]))
|
||||||
|
result := make(map[rune]uint16)
|
||||||
|
for i := 0; i < numTables; i++ {
|
||||||
|
pos := 4 + i*8
|
||||||
|
if pos+8 > len(data) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
platformID := binary.BigEndian.Uint16(data[pos : pos+2])
|
||||||
|
if platformID != 0 && platformID != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
offset := int(binary.BigEndian.Uint32(data[pos+4 : pos+8]))
|
||||||
|
if offset+2 > len(data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch binary.BigEndian.Uint16(data[offset : offset+2]) {
|
||||||
|
case 0:
|
||||||
|
parseCmapFormat0(data[offset:], result)
|
||||||
|
case 4:
|
||||||
|
parseCmapFormat4(data[offset:], result)
|
||||||
|
case 6:
|
||||||
|
parseCmapFormat6(data[offset:], result)
|
||||||
|
case 12:
|
||||||
|
parseCmapFormat12(data[offset:], result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCmapFormat0 解析 cmap format 0
|
||||||
|
// 入参: data 子表数据, result 字符映射
|
||||||
|
func parseCmapFormat0(data []byte, result map[rune]uint16) {
|
||||||
|
if len(data) < 262 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < 256; i++ {
|
||||||
|
gid := uint16(data[6+i])
|
||||||
|
if gid != 0 {
|
||||||
|
result[rune(i)] = gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCmapFormat4 解析 cmap format 4
|
||||||
|
// 入参: data 子表数据, result 字符映射
|
||||||
|
func parseCmapFormat4(data []byte, result map[rune]uint16) {
|
||||||
|
if len(data) < 16 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length := int(binary.BigEndian.Uint16(data[2:4]))
|
||||||
|
if length > len(data) {
|
||||||
|
length = len(data)
|
||||||
|
}
|
||||||
|
segCount := int(binary.BigEndian.Uint16(data[6:8]) / 2)
|
||||||
|
endPos := 14
|
||||||
|
startPos := endPos + segCount*2 + 2
|
||||||
|
deltaPos := startPos + segCount*2
|
||||||
|
rangePos := deltaPos + segCount*2
|
||||||
|
if rangePos+segCount*2 > length {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < segCount; i++ {
|
||||||
|
end := binary.BigEndian.Uint16(data[endPos+i*2:])
|
||||||
|
start := binary.BigEndian.Uint16(data[startPos+i*2:])
|
||||||
|
delta := int16(binary.BigEndian.Uint16(data[deltaPos+i*2:]))
|
||||||
|
rangeOffsetPos := rangePos + i*2
|
||||||
|
rangeOffset := binary.BigEndian.Uint16(data[rangeOffsetPos:])
|
||||||
|
if start == 0xFFFF && end == 0xFFFF {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for c := start; c <= end; c++ {
|
||||||
|
var gid uint16
|
||||||
|
if rangeOffset == 0 {
|
||||||
|
gid = uint16(int(c) + int(delta))
|
||||||
|
} else {
|
||||||
|
gidPos := rangeOffsetPos + int(rangeOffset) + int(c-start)*2
|
||||||
|
if gidPos+2 > length {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gid = binary.BigEndian.Uint16(data[gidPos:])
|
||||||
|
if gid != 0 {
|
||||||
|
gid = uint16(int(gid) + int(delta))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if gid != 0 {
|
||||||
|
result[rune(c)] = gid
|
||||||
|
}
|
||||||
|
if c == 0xFFFF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCmapFormat6 解析 cmap format 6
|
||||||
|
// 入参: data 子表数据, result 字符映射
|
||||||
|
func parseCmapFormat6(data []byte, result map[rune]uint16) {
|
||||||
|
if len(data) < 10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
firstCode := int(binary.BigEndian.Uint16(data[6:8]))
|
||||||
|
entryCount := int(binary.BigEndian.Uint16(data[8:10]))
|
||||||
|
for i := 0; i < entryCount && 10+i*2+2 <= len(data); i++ {
|
||||||
|
gid := binary.BigEndian.Uint16(data[10+i*2:])
|
||||||
|
if gid != 0 {
|
||||||
|
result[rune(firstCode+i)] = gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCmapFormat12 解析 cmap format 12
|
||||||
|
// 入参: data 子表数据, result 字符映射
|
||||||
|
func parseCmapFormat12(data []byte, result map[rune]uint16) {
|
||||||
|
if len(data) < 16 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length := int(binary.BigEndian.Uint32(data[4:8]))
|
||||||
|
if length > len(data) {
|
||||||
|
length = len(data)
|
||||||
|
}
|
||||||
|
nGroups := int(binary.BigEndian.Uint32(data[12:16]))
|
||||||
|
for i := 0; i < nGroups; i++ {
|
||||||
|
pos := 16 + i*12
|
||||||
|
if pos+12 > length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
startChar := binary.BigEndian.Uint32(data[pos:])
|
||||||
|
endChar := binary.BigEndian.Uint32(data[pos+4:])
|
||||||
|
startGID := binary.BigEndian.Uint32(data[pos+8:])
|
||||||
|
for c := startChar; c <= endChar; c++ {
|
||||||
|
gid := startGID + c - startChar
|
||||||
|
if gid != 0 && gid <= 0xFFFF {
|
||||||
|
result[rune(c)] = uint16(gid)
|
||||||
|
}
|
||||||
|
if c == endChar {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addPackedGlyphMapping 添加字形ID私有映射
|
||||||
|
// 入参: mapping 字符映射, numGlyphs 字形数量
|
||||||
|
func addPackedGlyphMapping(mapping map[rune]uint16, numGlyphs uint16) {
|
||||||
|
for i := uint16(0); i < numGlyphs; i++ {
|
||||||
|
mapping[packedGlyphRune(i)] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// buildHeadTable 构建 head 表
|
// buildHeadTable 构建 head 表
|
||||||
// 入参: unitsPerEm 每em单位数
|
// 入参: unitsPerEm 每em单位数
|
||||||
// 返回: []byte head表数据
|
// 返回: []byte head表数据
|
||||||
|
|||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeImageData 解码图片数据
|
||||||
|
// 入参: data 图片数据
|
||||||
|
// 返回: image.Image 图片对象, string 图片格式, error 错误信息
|
||||||
|
func decodeImageData(data []byte) (image.Image, string, error) {
|
||||||
|
img, format, err := image.Decode(bytes.NewReader(data))
|
||||||
|
if err == nil {
|
||||||
|
return img, normalizeSealType(format), nil
|
||||||
|
}
|
||||||
|
if img, err := decodeBMPImage(data); err == nil {
|
||||||
|
return img, "bmp", nil
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeImageConfigData 解码图片尺寸
|
||||||
|
// 入参: data 图片数据
|
||||||
|
// 返回: image.Config 图片尺寸, string 图片格式, error 错误信息
|
||||||
|
func decodeImageConfigData(data []byte) (image.Config, string, error) {
|
||||||
|
cfg, format, err := image.DecodeConfig(bytes.NewReader(data))
|
||||||
|
if err == nil {
|
||||||
|
return cfg, normalizeSealType(format), nil
|
||||||
|
}
|
||||||
|
if cfg, err := decodeBMPConfig(data); err == nil {
|
||||||
|
return cfg, "bmp", nil
|
||||||
|
}
|
||||||
|
return image.Config{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeBMPConfig 解码 BMP 尺寸
|
||||||
|
// 入参: data 图片数据
|
||||||
|
// 返回: image.Config 图片尺寸, error 错误信息
|
||||||
|
func decodeBMPConfig(data []byte) (image.Config, error) {
|
||||||
|
offset, width, height, bpp, compression, err := parseBMPHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
return image.Config{}, err
|
||||||
|
}
|
||||||
|
if offset <= 0 || width <= 0 || height == 0 || compression != 0 {
|
||||||
|
return image.Config{}, fmt.Errorf("unsupported bmp")
|
||||||
|
}
|
||||||
|
if bpp != 16 && bpp != 24 && bpp != 32 {
|
||||||
|
return image.Config{}, fmt.Errorf("unsupported bmp")
|
||||||
|
}
|
||||||
|
if height < 0 {
|
||||||
|
height = -height
|
||||||
|
}
|
||||||
|
return image.Config{ColorModel: color.NRGBAModel, Width: width, Height: height}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeBMPImage 解码 BMP 图片
|
||||||
|
// 入参: data 图片数据
|
||||||
|
// 返回: image.Image 图片对象, error 错误信息
|
||||||
|
func decodeBMPImage(data []byte) (image.Image, error) {
|
||||||
|
offset, width, height, bpp, compression, err := parseBMPHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if compression != 0 {
|
||||||
|
return nil, fmt.Errorf("unsupported bmp compression")
|
||||||
|
}
|
||||||
|
topDown := height < 0
|
||||||
|
if topDown {
|
||||||
|
height = -height
|
||||||
|
}
|
||||||
|
if width <= 0 || height <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid bmp size")
|
||||||
|
}
|
||||||
|
rowStride := ((width*int(bpp) + 31) / 32) * 4
|
||||||
|
if offset+rowStride*height > len(data) {
|
||||||
|
return nil, fmt.Errorf("truncated bmp")
|
||||||
|
}
|
||||||
|
img := image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
srcY := y
|
||||||
|
if !topDown {
|
||||||
|
srcY = height - 1 - y
|
||||||
|
}
|
||||||
|
row := data[offset+srcY*rowStride:]
|
||||||
|
for x := 0; x < width; x++ {
|
||||||
|
switch bpp {
|
||||||
|
case 16:
|
||||||
|
v := binary.LittleEndian.Uint16(row[x*2:])
|
||||||
|
img.SetNRGBA(x, y, color.NRGBA{
|
||||||
|
R: uint8(((v >> 10) & 0x1F) * 255 / 31),
|
||||||
|
G: uint8(((v >> 5) & 0x1F) * 255 / 31),
|
||||||
|
B: uint8((v & 0x1F) * 255 / 31),
|
||||||
|
A: 255,
|
||||||
|
})
|
||||||
|
case 24:
|
||||||
|
p := x * 3
|
||||||
|
img.SetNRGBA(x, y, color.NRGBA{R: row[p+2], G: row[p+1], B: row[p], A: 255})
|
||||||
|
case 32:
|
||||||
|
p := x * 4
|
||||||
|
img.SetNRGBA(x, y, color.NRGBA{R: row[p+2], G: row[p+1], B: row[p], A: 255})
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported bmp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseBMPHeader 解析 BMP 文件头
|
||||||
|
// 入参: data 图片数据
|
||||||
|
// 返回: int 像素偏移, int 宽度, int 高度, uint16 位数, uint32 压缩方式, error 错误信息
|
||||||
|
func parseBMPHeader(data []byte) (int, int, int, uint16, uint32, error) {
|
||||||
|
if len(data) < 54 || string(data[:2]) != "BM" {
|
||||||
|
return 0, 0, 0, 0, 0, fmt.Errorf("not bmp")
|
||||||
|
}
|
||||||
|
offset := int(binary.LittleEndian.Uint32(data[10:14]))
|
||||||
|
dibSize := binary.LittleEndian.Uint32(data[14:18])
|
||||||
|
if dibSize < 40 {
|
||||||
|
return 0, 0, 0, 0, 0, fmt.Errorf("unsupported bmp")
|
||||||
|
}
|
||||||
|
width := int(int32(binary.LittleEndian.Uint32(data[18:22])))
|
||||||
|
height := int(int32(binary.LittleEndian.Uint32(data[22:26])))
|
||||||
|
planes := binary.LittleEndian.Uint16(data[26:28])
|
||||||
|
bpp := binary.LittleEndian.Uint16(data[28:30])
|
||||||
|
compression := binary.LittleEndian.Uint32(data[30:34])
|
||||||
|
if planes != 1 {
|
||||||
|
return 0, 0, 0, 0, 0, fmt.Errorf("invalid bmp")
|
||||||
|
}
|
||||||
|
return offset, width, height, bpp, compression, nil
|
||||||
|
}
|
||||||
+3
-2
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/tdewolff/canvas/renderers/pdf"
|
"github.com/tdewolff/canvas/renderers/pdf"
|
||||||
"github.com/tdewolff/canvas/renderers/rasterizer"
|
"github.com/tdewolff/canvas/renderers/rasterizer"
|
||||||
_ "github.com/xiaoqidun/jbig2"
|
_ "github.com/xiaoqidun/jbig2"
|
||||||
|
_ "golang.org/x/image/bmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Renderer 渲染器实现
|
// Renderer 渲染器实现
|
||||||
@@ -501,7 +502,7 @@ func (r *Renderer) renderImage(ctx *canvas.Context, obj ImageObject, pageH float
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
img, _, err := image.Decode(bytes.NewReader(imgData))
|
img, _, err := decodeImageData(imgData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1420,7 +1421,7 @@ func (r *Renderer) renderStamp(ctx *canvas.Context, s Stamp, pageH float64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.Data) > 0 {
|
if len(s.Data) > 0 {
|
||||||
img, _, err := image.Decode(bytes.NewReader(s.Data))
|
img, _, err := decodeImageData(s.Data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
img = stampImageWithTransparentWhite(img)
|
img = stampImageWithTransparentWhite(img)
|
||||||
ctx.Push()
|
ctx.Push()
|
||||||
|
|||||||
+4
-3
@@ -19,7 +19,6 @@ import (
|
|||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"image"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -220,6 +219,8 @@ func normalizeSealType(s string) string {
|
|||||||
switch s {
|
switch s {
|
||||||
case "jpg":
|
case "jpg":
|
||||||
return "jpeg"
|
return "jpeg"
|
||||||
|
case "bmp":
|
||||||
|
return "bmp"
|
||||||
case "jb2", "gbig2":
|
case "jb2", "gbig2":
|
||||||
return "jbig2"
|
return "jbig2"
|
||||||
default:
|
default:
|
||||||
@@ -232,7 +233,7 @@ func normalizeSealType(s string) string {
|
|||||||
// 返回: bool 是否可渲染
|
// 返回: bool 是否可渲染
|
||||||
func isSealMediaType(s string) bool {
|
func isSealMediaType(s string) bool {
|
||||||
switch s {
|
switch s {
|
||||||
case "png", "jpeg", "jbig2", "ofd":
|
case "png", "jpeg", "bmp", "jbig2", "ofd":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
@@ -286,7 +287,7 @@ func zipDataEnd(data []byte) int {
|
|||||||
// 入参: data 原始数据
|
// 入参: data 原始数据
|
||||||
// 返回: string 媒体类型, []byte 图片数据
|
// 返回: string 媒体类型, []byte 图片数据
|
||||||
func probeImageMedia(data []byte) (string, []byte) {
|
func probeImageMedia(data []byte) (string, []byte) {
|
||||||
_, format, err := image.DecodeConfig(bytes.NewReader(data))
|
_, format, err := decodeImageConfigData(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user