mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
214 lines
7.5 KiB
Go
214 lines
7.5 KiB
Go
// 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 (
|
|
"math"
|
|
|
|
"github.com/xiaoqidun/pdfgo"
|
|
)
|
|
|
|
// pdfShadingSourceKey 区分图案的使用几何及填充或描边
|
|
type pdfShadingSourceKey struct {
|
|
pattern *pdfgo.ShadingPattern
|
|
geometry pdfCompositeKey
|
|
}
|
|
|
|
// compositeShadingPattern 在页面内复用图案内部图元,不缓存依赖背景的像素
|
|
// 入参: pattern 只读着色图案
|
|
// 返回: []pdfCompositeNode 隐式挖空组, error 遍历或资源错误
|
|
func (p *pdfImporter) compositeShadingPattern(pattern *pdfgo.ShadingPattern) ([]pdfCompositeNode, error) {
|
|
cache := p.compositingCache()
|
|
if nodes, found := cache.shadingPatterns[pattern]; found {
|
|
return nodes, nil
|
|
}
|
|
inverse, ok := p.matrix.Inverse()
|
|
if !ok {
|
|
return nil, &pdfgo.UnsupportedError{Feature: "shading pattern page transform"}
|
|
}
|
|
box := pdfBounds([]pdfgo.Point{
|
|
inverse.Apply(pdfgo.Point{}), inverse.Apply(pdfgo.Point{X: p.pageWidth}),
|
|
inverse.Apply(pdfgo.Point{X: p.pageWidth, Y: p.pageHeight}), inverse.Apply(pdfgo.Point{Y: p.pageHeight}),
|
|
})
|
|
nodes, err := p.collectCompositeNodes(func(visitor pdfgo.Visitor) error {
|
|
return pattern.Walk(p.ctx, pdfgo.Rectangle{XMin: box.X, YMin: box.Y, XMax: box.X + box.W, YMax: box.Y + box.H}, visitor)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cache.shadingPatterns == nil {
|
|
cache.shadingPatterns = make(map[*pdfgo.ShadingPattern][]pdfCompositeNode)
|
|
}
|
|
cache.shadingPatterns[pattern] = nodes
|
|
return nodes, nil
|
|
}
|
|
|
|
// shadingPattern 统一对象和图案的几何覆盖,按相交面积合成背景及内部着色
|
|
// 入参: pattern 着色图案, node 使用图元, outline 是否描边, backdrop 初始背景, space 父级混合空间, channels 可选源通道输出
|
|
// 返回: []pdfCompositePixel 源颜色、形状及不透明度, error 状态或合成错误
|
|
func (c *pdfCompositor) shadingPattern(pattern *pdfgo.ShadingPattern, node pdfCompositeNode, outline bool, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace, channels []uint8) ([]pdfCompositePixel, error) {
|
|
nodes, err := c.importer.shadingSources(pattern, node, outline)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
local := *c
|
|
local.transfers, local.shadingSource = nil, true
|
|
local.channels = channels
|
|
result, err := local.shadingSourcePixels(nodes[len(nodes)-1], backdrop, space)
|
|
if err != nil || len(nodes) == 1 {
|
|
return result, err
|
|
}
|
|
var backgroundChannels []uint8
|
|
if channels != nil {
|
|
backgroundChannels = c.acquireChannels(len(channels))
|
|
defer c.releaseChannels(backgroundChannels)
|
|
clear(backgroundChannels)
|
|
}
|
|
local.channels = backgroundChannels
|
|
background, err := local.shadingSourcePixels(nodes[0], backdrop, space)
|
|
if err != nil {
|
|
c.releasePixels(result)
|
|
return nil, err
|
|
}
|
|
defer c.releasePixels(background)
|
|
coverage, err := c.coverage(nodes[0], outline)
|
|
if err != nil {
|
|
c.releasePixels(result)
|
|
return nil, err
|
|
}
|
|
defer c.releaseCoverage(coverage)
|
|
step := 25.4 / c.importer.rasterDPI
|
|
for i := range result {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
c.releasePixels(result)
|
|
return nil, err
|
|
}
|
|
}
|
|
shape := 0.0
|
|
if coverage != nil {
|
|
a := imageAlphaAt(coverage, i%c.width, i/c.width)
|
|
shape = float64(a) / 65535
|
|
}
|
|
if nodes[0].image != nil && shape != 0 {
|
|
point := c.inverse.Apply(pdfgo.Point{X: c.box.X + (float64(i%c.width)+.5)*step, Y: c.box.Y + (float64(i/c.width)+.5)*step})
|
|
_, alpha, err := c.imageColor(nodes[0].image, point, space)
|
|
if err != nil {
|
|
c.releasePixels(result)
|
|
return nil, err
|
|
}
|
|
shape *= alpha
|
|
}
|
|
remaining := 1.0
|
|
if shape != 0 {
|
|
remaining -= math.Min(1, result[i].shape/shape)
|
|
}
|
|
alpha := result[i].alpha + background[i].alpha*remaining
|
|
if alpha != 0 {
|
|
for j := 0; j < space.Components(); j++ {
|
|
result[i].values[j] = (result[i].values[j]*result[i].alpha + background[i].values[j]*background[i].alpha*remaining) / alpha
|
|
}
|
|
}
|
|
result[i].alpha, result[i].effect = alpha, alpha
|
|
result[i].shape += background[i].shape * remaining
|
|
if channels != nil && background[i].shape*remaining > 0 {
|
|
channels[i] |= backgroundChannels[i]
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// shadingSources 将内部状态绑定到使用图元,几何和裁剪仅计算一次
|
|
// 入参: pattern 只读着色图案, node 使用图元, outline 是否描边
|
|
// 返回: []pdfCompositeNode 背景及着色图元, error 图案遍历错误
|
|
func (p *pdfImporter) shadingSources(pattern *pdfgo.ShadingPattern, node pdfCompositeNode, outline bool) ([]pdfCompositeNode, error) {
|
|
cache := p.compositingCache()
|
|
key := pdfShadingSourceKey{pattern, pdfCompositeKey{path: node.path, text: node.text, image: node.image, stroke: outline}}
|
|
if sources, ok := cache.shadingSources[key]; ok {
|
|
return sources, nil
|
|
}
|
|
nodes, err := p.compositeShadingPattern(pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outer, _, _ := node.style()
|
|
sources := make([]pdfCompositeNode, 0, len(nodes[0].children))
|
|
for _, child := range nodes[0].children {
|
|
style := child.path.Style
|
|
style.LineWidth, style.Cap, style.Join, style.MiterLimit = outer.LineWidth, outer.Cap, outer.Join, outer.MiterLimit
|
|
style.Dash, style.DashPhase, style.StrokeAdjust = outer.Dash, outer.DashPhase, outer.StrokeAdjust
|
|
style.Clips = append(append([]pdfgo.Path(nil), outer.Clips...), style.Clips...)
|
|
style.Stroke, style.StrokeOverprint = style.Fill, style.FillOverprint
|
|
source := node
|
|
if node.path != nil {
|
|
mark := *node.path
|
|
mark.Style, mark.Fill, mark.Stroke = style, !outline, outline
|
|
source.path = &mark
|
|
} else if node.text != nil {
|
|
mark := *node.text
|
|
mark.Style, mark.Mode = style, 0
|
|
if outline {
|
|
mark.Mode = 1
|
|
}
|
|
source.text = &mark
|
|
} else {
|
|
mark := *node.image
|
|
mark.Style = style
|
|
source.image = &mark
|
|
}
|
|
sources = append(sources, source)
|
|
}
|
|
if cache.shadingSources == nil {
|
|
cache.shadingSources = make(map[pdfShadingSourceKey][]pdfCompositeNode)
|
|
}
|
|
cache.shadingSources[key] = sources
|
|
return sources, nil
|
|
}
|
|
|
|
// shadingSourcePixels 求值单个内部图元并移除初始背景贡献
|
|
// 入参: node 内部图元, backdrop 初始背景, space 混合空间
|
|
// 返回: []pdfCompositePixel 源颜色和覆盖, error 合成或取消错误
|
|
func (c *pdfCompositor) shadingSourcePixels(node pdfCompositeNode, backdrop []pdfCompositePixel, space *pdfgo.ColorSpace) ([]pdfCompositePixel, error) {
|
|
result := c.acquirePixels(len(backdrop))
|
|
complete := false
|
|
defer func() {
|
|
if !complete {
|
|
c.releasePixels(result)
|
|
}
|
|
}()
|
|
for i, pixel := range backdrop {
|
|
result[i] = pdfCompositePixel{values: pixel.values, alpha: pixel.alpha}
|
|
}
|
|
if err := c.drawMark(node, result, space); err != nil {
|
|
return nil, err
|
|
}
|
|
for i := range result {
|
|
if i%pdfCompositeTileSize == 0 {
|
|
if err := c.importer.ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
pixel := &result[i]
|
|
if pixel.effect != 0 {
|
|
for j := 0; j < space.Components(); j++ {
|
|
pixel.values[j] = math.Max(0, math.Min(1, (pixel.values[j]*pixel.alpha-backdrop[i].values[j]*backdrop[i].alpha*(1-pixel.effect))/pixel.effect))
|
|
}
|
|
}
|
|
pixel.alpha = pixel.effect
|
|
}
|
|
complete = true
|
|
return result, nil
|
|
}
|