mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-11-01 20:51:18 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
283c8eea77 | ||
|
|
830378c6e4 | ||
|
|
0fd4954547 | ||
|
|
565b7f6db4 |
No files matched your search
+3
-12
@@ -53,7 +53,6 @@ const state = {
|
|||||||
pageObserver: null,
|
pageObserver: null,
|
||||||
visiblePages: new Set(),
|
visiblePages: new Set(),
|
||||||
scrollFrame: 0,
|
scrollFrame: 0,
|
||||||
thumbnailCache: new Map(),
|
|
||||||
thumbnailInFlight: new Set(),
|
thumbnailInFlight: new Set(),
|
||||||
thumbnailObserver: null,
|
thumbnailObserver: null,
|
||||||
visibleThumbnails: new Set(),
|
visibleThumbnails: new Set(),
|
||||||
@@ -1549,7 +1548,6 @@ async function processPageRenderQueue() {
|
|||||||
const page = await callWASM("ofdgoRenderPage", task.index);
|
const page = await callWASM("ofdgoRenderPage", task.index);
|
||||||
if (task.openSeq === state.openSeq) {
|
if (task.openSeq === state.openSeq) {
|
||||||
state.pageCache.set(task.index, page);
|
state.pageCache.set(task.index, page);
|
||||||
cacheThumbnail(task.index, page.svg);
|
|
||||||
}
|
}
|
||||||
task.resolve(page);
|
task.resolve(page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1809,7 +1807,7 @@ function renderPageList() {
|
|||||||
const thumb = document.createElement("span");
|
const thumb = document.createElement("span");
|
||||||
thumb.className = "thumb-paper";
|
thumb.className = "thumb-paper";
|
||||||
thumb.setAttribute("aria-hidden", "true");
|
thumb.setAttribute("aria-hidden", "true");
|
||||||
setThumbnailContent(thumb, state.thumbnailCache.get(page.index), page.index, openSeq);
|
setThumbnailContent(thumb, state.pageCache.get(page.index)?.svg, page.index, openSeq);
|
||||||
|
|
||||||
const label = document.createElement("span");
|
const label = document.createElement("span");
|
||||||
label.className = "thumb-label";
|
label.className = "thumb-label";
|
||||||
@@ -1831,7 +1829,6 @@ function renderPageList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resetThumbnails() {
|
function resetThumbnails() {
|
||||||
state.thumbnailCache.clear();
|
|
||||||
state.visibleThumbnails.clear();
|
state.visibleThumbnails.clear();
|
||||||
state.thumbnailInFlight.clear();
|
state.thumbnailInFlight.clear();
|
||||||
if (state.thumbnailObserver) {
|
if (state.thumbnailObserver) {
|
||||||
@@ -1840,12 +1837,6 @@ function resetThumbnails() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheThumbnail(index, svgText) {
|
|
||||||
if (typeof svgText === "string" && svgText) {
|
|
||||||
state.thumbnailCache.set(index, svgText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setThumbnailContent(container, svgText, index, openSeq = state.openSeq) {
|
function setThumbnailContent(container, svgText, index, openSeq = state.openSeq) {
|
||||||
const renderKey = `${openSeq}:${index}`;
|
const renderKey = `${openSeq}:${index}`;
|
||||||
if (svgText && container.dataset.renderKey === renderKey) {
|
if (svgText && container.dataset.renderKey === renderKey) {
|
||||||
@@ -1923,7 +1914,7 @@ async function renderThumbnail(index, openSeq = state.openSeq) {
|
|||||||
if (openSeq !== state.openSeq || !state.doc) {
|
if (openSeq !== state.openSeq || !state.doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.thumbnailCache.has(index)) {
|
if (state.pageCache.has(index)) {
|
||||||
updateThumbnail(index, openSeq);
|
updateThumbnail(index, openSeq);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1956,7 +1947,7 @@ function updateThumbnail(index, openSeq = state.openSeq) {
|
|||||||
}
|
}
|
||||||
const thumb = el.pageList.querySelector(`[data-page-index="${index}"] .thumb-paper`);
|
const thumb = el.pageList.querySelector(`[data-page-index="${index}"] .thumb-paper`);
|
||||||
if (thumb) {
|
if (thumb) {
|
||||||
setThumbnailContent(thumb, state.thumbnailCache.get(index), index, openSeq);
|
setThumbnailContent(thumb, state.pageCache.get(index)?.svg, index, openSeq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ go 1.26.0
|
|||||||
require (
|
require (
|
||||||
github.com/tdewolff/canvas v0.0.0-20260901160717-dae8cd8e19a7
|
github.com/tdewolff/canvas v0.0.0-20260901160717-dae8cd8e19a7
|
||||||
github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117
|
github.com/tdewolff/font v0.0.0-20260822205238-d0d2f004b117
|
||||||
github.com/xiaoqidun/jbig2 v0.0.0-20260911193906-4ff0e8c3778b
|
github.com/xiaoqidun/jbig2 v0.0.0-20260912052037-9a7cadfca98c
|
||||||
golang.org/x/image v0.46.0
|
golang.org/x/image v0.46.0
|
||||||
golang.org/x/text v0.42.0
|
golang.org/x/text v0.42.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ 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-20260911193906-4ff0e8c3778b h1:roazxr+FPYc+8H3M/GeJFZRdsKAbLaHyoAesXSHiNak=
|
github.com/xiaoqidun/jbig2 v0.0.0-20260912052037-9a7cadfca98c h1:ZnB6Js4yt9X3hcM3X+ddZUgHAD4VKoAO5EIwZxFET98=
|
||||||
github.com/xiaoqidun/jbig2 v0.0.0-20260911193906-4ff0e8c3778b/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
github.com/xiaoqidun/jbig2 v0.0.0-20260912052037-9a7cadfca98c/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||||
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=
|
||||||
|
|||||||
@@ -237,18 +237,15 @@ func (s *Session) Close() error {
|
|||||||
// 入参: fonts 字体文件列表
|
// 入参: fonts 字体文件列表
|
||||||
// 返回: error 错误信息
|
// 返回: error 错误信息
|
||||||
func (s *Session) SetFonts(fonts []FontFile) error {
|
func (s *Session) SetFonts(fonts []FontFile) error {
|
||||||
opts := []ofdgo.RendererOption{
|
|
||||||
ofdgo.WithDPI(s.Renderer.DPI),
|
|
||||||
ofdgo.WithAnnotations(s.Renderer.RenderAnnotations),
|
|
||||||
}
|
|
||||||
if len(fonts) > 0 {
|
if len(fonts) > 0 {
|
||||||
fontFS := ofdgo.NewFontFS(fonts)
|
fontFS := ofdgo.NewFontFS(fonts)
|
||||||
if fontFS.Len() == 0 {
|
if fontFS.Len() == 0 {
|
||||||
return fmt.Errorf("invalid font file")
|
return fmt.Errorf("invalid font file")
|
||||||
}
|
}
|
||||||
opts = append(opts, ofdgo.WithFontFS(fontFS))
|
s.Renderer.SetFontFS(fontFS)
|
||||||
|
} else {
|
||||||
|
s.Renderer.SetFontFS()
|
||||||
}
|
}
|
||||||
s.Renderer = ofdgo.NewRenderer(s.Reader, opts...)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
|
||||||
"github.com/tdewolff/canvas"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Open 打开OFD文件
|
// Open 打开OFD文件
|
||||||
@@ -71,18 +69,10 @@ func NewRenderer(reader *Reader, opts ...RendererOption) *Renderer {
|
|||||||
RenderAnnotations: true,
|
RenderAnnotations: true,
|
||||||
DrawParams: reader.drawParamCache,
|
DrawParams: reader.drawParamCache,
|
||||||
CompositeGraphicUnits: reader.compositeGraphicUnitCache,
|
CompositeGraphicUnits: reader.compositeGraphicUnitCache,
|
||||||
FontMap: make(map[string]*canvas.FontFamily),
|
|
||||||
FontGIDMap: make(map[string]map[uint16]rune),
|
|
||||||
FontCIDMap: make(map[string]map[uint16]rune),
|
|
||||||
fontCache: make(map[fontCacheKey]*canvas.FontFamily),
|
|
||||||
fontSourceCache: make(map[string][]fontSource),
|
|
||||||
fontSourceUsed: make(map[string]fontSource),
|
|
||||||
fontDirCandidates: make(map[string][]fontFileCandidate),
|
|
||||||
fontFSCandidates: make(map[int][]fontFileCandidate),
|
|
||||||
textGlyphPathCache: make(map[textGlyphPathCacheKey]textGlyphPathCacheValue),
|
|
||||||
templatePageCache: make(map[string]*PageContent),
|
templatePageCache: make(map[string]*PageContent),
|
||||||
imageCache: make(map[string]image.Image),
|
imageCache: make(map[string]image.Image),
|
||||||
}
|
}
|
||||||
|
r.resetFontCache()
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(r)
|
opt(r)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -318,8 +318,11 @@ func fontDefaultSystemNames() []string {
|
|||||||
func fontNoSyntheticBold(names ...string) bool {
|
func fontNoSyntheticBold(names ...string) bool {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
for _, rule := range fontMatchRules {
|
for _, rule := range fontMatchRules {
|
||||||
|
if !rule.NoSyntheticBold {
|
||||||
|
continue
|
||||||
|
}
|
||||||
level := fontRuleMatchLevel(rule, name)
|
level := fontRuleMatchLevel(rule, name)
|
||||||
if rule.NoSyntheticBold && level >= fontMatchExact && level <= fontMatchPartial {
|
if level >= fontMatchExact && level <= fontMatchPartial {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-7
@@ -17,6 +17,7 @@ package ofdgo
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@@ -30,17 +31,21 @@ type Box struct {
|
|||||||
// 入参: s 字符串
|
// 入参: s 字符串
|
||||||
// 返回: Box 矩形对象, error 错误信息
|
// 返回: Box 矩形对象, error 错误信息
|
||||||
func ParseBox(s string) (Box, error) {
|
func ParseBox(s string) (Box, error) {
|
||||||
parts := strings.Fields(s)
|
|
||||||
if len(parts) != 4 {
|
|
||||||
return Box{}, fmt.Errorf("invalid box: %s", s)
|
|
||||||
}
|
|
||||||
var values [4]float64
|
var values [4]float64
|
||||||
for i, part := range parts {
|
i := 0
|
||||||
|
for part := range strings.FieldsSeq(s) {
|
||||||
|
if i == len(values) {
|
||||||
|
return Box{}, fmt.Errorf("invalid box: %s", s)
|
||||||
|
}
|
||||||
value, err := strconv.ParseFloat(part, 64)
|
value, err := strconv.ParseFloat(part, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Box{}, fmt.Errorf("invalid box: %s", s)
|
return Box{}, fmt.Errorf("invalid box: %s", s)
|
||||||
}
|
}
|
||||||
values[i] = value
|
values[i] = value
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i != len(values) {
|
||||||
|
return Box{}, fmt.Errorf("invalid box: %s", s)
|
||||||
}
|
}
|
||||||
return Box{X: values[0], Y: values[1], W: values[2], H: values[3]}, nil
|
return Box{X: values[0], Y: values[1], W: values[2], H: values[3]}, nil
|
||||||
}
|
}
|
||||||
@@ -146,11 +151,10 @@ func parseFloats(s string) []float64 {
|
|||||||
// 入参: s 字符串
|
// 入参: s 字符串
|
||||||
// 返回: []float64 浮点数数组
|
// 返回: []float64 浮点数数组
|
||||||
func parseFloatsWithG(s string) []float64 {
|
func parseFloatsWithG(s string) []float64 {
|
||||||
parts := strings.Fields(s)
|
|
||||||
var result []float64
|
var result []float64
|
||||||
gFlag := false
|
gFlag := false
|
||||||
gCount := 0
|
gCount := 0
|
||||||
for _, p := range parts {
|
for p := range strings.FieldsSeq(s) {
|
||||||
if p == "g" {
|
if p == "g" {
|
||||||
gFlag = true
|
gFlag = true
|
||||||
continue
|
continue
|
||||||
@@ -166,6 +170,7 @@ func parseFloatsWithG(s string) []float64 {
|
|||||||
}
|
}
|
||||||
if gCount > 0 {
|
if gCount > 0 {
|
||||||
v, _ := strconv.ParseFloat(p, 64)
|
v, _ := strconv.ParseFloat(p, 64)
|
||||||
|
result = slices.Grow(result, gCount)
|
||||||
for j := 0; j < gCount; j++ {
|
for j := 0; j < gCount; j++ {
|
||||||
result = append(result, v)
|
result = append(result, v)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,26 @@ type Renderer struct {
|
|||||||
// RendererOption 渲染器配置选项
|
// RendererOption 渲染器配置选项
|
||||||
type RendererOption func(*Renderer)
|
type RendererOption func(*Renderer)
|
||||||
|
|
||||||
|
// SetFontFS 替换外部字体文件系统并重置字体缓存
|
||||||
|
// 入参: fsys 字体文件系统
|
||||||
|
func (r *Renderer) SetFontFS(fsys ...fs.FS) {
|
||||||
|
r.fontFS = append([]fs.FS(nil), fsys...)
|
||||||
|
r.resetFontCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetFontCache 重置字体匹配、加载和字形缓存
|
||||||
|
func (r *Renderer) resetFontCache() {
|
||||||
|
r.FontMap = make(map[string]*canvas.FontFamily)
|
||||||
|
r.FontGIDMap = make(map[string]map[uint16]rune)
|
||||||
|
r.FontCIDMap = make(map[string]map[uint16]rune)
|
||||||
|
r.fontCache = make(map[fontCacheKey]*canvas.FontFamily)
|
||||||
|
r.fontSourceCache = make(map[string][]fontSource)
|
||||||
|
r.fontSourceUsed = make(map[string]fontSource)
|
||||||
|
r.fontDirCandidates = make(map[string][]fontFileCandidate)
|
||||||
|
r.fontFSCandidates = make(map[int][]fontFileCandidate)
|
||||||
|
r.textGlyphPathCache = make(map[textGlyphPathCacheKey]textGlyphPathCacheValue)
|
||||||
|
}
|
||||||
|
|
||||||
// RenderPage 渲染特定页面内容
|
// RenderPage 渲染特定页面内容
|
||||||
// 入参: page 页面内容
|
// 入参: page 页面内容
|
||||||
// 返回: *canvas.Canvas 画布实例, error 错误信息
|
// 返回: *canvas.Canvas 画布实例, error 错误信息
|
||||||
|
|||||||
@@ -140,6 +140,18 @@ func imageWithMask(img, mask image.Image) image.Image {
|
|||||||
maskBounds = bounds
|
maskBounds = bounds
|
||||||
}
|
}
|
||||||
out := image.NewNRGBA(bounds)
|
out := image.NewNRGBA(bounds)
|
||||||
|
if src, ok := source.(*image.NRGBA); ok {
|
||||||
|
for y := 0; y < bounds.Dy(); y++ {
|
||||||
|
offset := src.PixOffset(bounds.Min.X, bounds.Min.Y+y)
|
||||||
|
row := out.Pix[y*out.Stride : (y+1)*out.Stride]
|
||||||
|
copy(row, src.Pix[offset:offset+len(row)])
|
||||||
|
for x := 3; x < len(row); x += 4 {
|
||||||
|
a := imageGrayAt(opacity, maskBounds.Min.X+x/4, maskBounds.Min.Y+y).Y
|
||||||
|
row[x] = uint8(int(row[x]) * int(a) / 255)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||||
mx := maskBounds.Min.X + x - bounds.Min.X
|
mx := maskBounds.Min.X + x - bounds.Min.X
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ func (r *Renderer) renderText(ctx *canvas.Context, obj TextObject, pageH float64
|
|||||||
embeddedFont := false
|
embeddedFont := false
|
||||||
if of, ok := r.Reader.fontCache[fontID]; ok {
|
if of, ok := r.Reader.fontCache[fontID]; ok {
|
||||||
embeddedFont = of.FontFile != ""
|
embeddedFont = of.FontFile != ""
|
||||||
if !embeddedFont && fontNoSyntheticBold(of.FontName, of.FamilyName) {
|
if syntheticBold && !embeddedFont && fontNoSyntheticBold(of.FontName, of.FamilyName) {
|
||||||
syntheticBold = false
|
syntheticBold = false
|
||||||
}
|
}
|
||||||
if of.Bold {
|
if of.Bold {
|
||||||
|
|||||||
Reference in new issue
Block a user