mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
220 lines
7.4 KiB
Go
220 lines
7.4 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 (
|
|
codecpdf "github.com/xiaoqidun/ofdgo/internal/codec/pdf"
|
|
"github.com/xiaoqidun/pdfgo"
|
|
)
|
|
|
|
// 图案标记保留设备四色通道及非零对象形状
|
|
const (
|
|
pdfTransferChannels uint8 = 15
|
|
pdfTransferShape uint8 = 16
|
|
)
|
|
|
|
// resolveTransfer 无设备函数时保留无缓存快速路径,按需绑定单页解析缓存
|
|
// 入参: style 原始图形状态, model 原生设备颜色空间
|
|
// 返回: *pdfgo.TransferFunction 有效函数, error 网屏传递函数错误
|
|
func (p *pdfImporter) resolveTransfer(style pdfgo.Style, model pdfgo.Name) (*pdfgo.TransferFunction, error) {
|
|
if style.Halftone == nil && style.Transfer == nil {
|
|
return nil, nil
|
|
}
|
|
return p.compositingCache().transfers.Resolve(p.reader, style, model)
|
|
}
|
|
|
|
// compositeHasTransfer 检查最终设备使用的通道,不将备用通道误判为有效函数
|
|
// 入参: nodes 原始图元及透明组, model 原生设备颜色空间
|
|
// 返回: bool 是否存在非恒等函数, error 网屏函数错误
|
|
func (p *pdfImporter) compositeHasTransfer(nodes []pdfCompositeNode, model pdfgo.Name) (bool, error) {
|
|
for _, node := range nodes {
|
|
if node.group != nil {
|
|
if found, err := p.compositeHasTransfer(node.children, model); err != nil || found {
|
|
return found, err
|
|
}
|
|
} else {
|
|
style, _, _ := node.style()
|
|
transfer, err := p.resolveTransfer(style, model)
|
|
if err != nil || transfer != nil {
|
|
return transfer != nil, err
|
|
}
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// transferDevice 为四色叠印保留原生设备通道,其余页面使用连续RGB设备
|
|
// 入参: space 页面混合空间, nodes 参与合成的图元
|
|
// 返回: pdfgo.Name 原生设备空间, error 图案或图像解析错误
|
|
func (p *pdfImporter) transferDevice(space *pdfgo.ColorSpace, nodes []pdfCompositeNode) (pdfgo.Name, error) {
|
|
if space.Model == "DeviceCMYK" && !space.Calibrated() {
|
|
if found, err := p.processOverprint(nodes); err != nil {
|
|
return "", err
|
|
} else if found {
|
|
return "DeviceCMYK", nil
|
|
}
|
|
}
|
|
return "DeviceRGB", nil
|
|
}
|
|
|
|
// selectTransfer 按最上层基本图元及祖先的不透明状态选择最终设备函数
|
|
// 入参: node 基本图元, outline 是否描边
|
|
// 返回: *pdfgo.TransferFunction 当前函数或设备默认值, error 图案解析错误
|
|
func (c *pdfCompositor) selectTransfer(node pdfCompositeNode, outline bool) (*pdfgo.TransferFunction, error) {
|
|
style, _, _ := node.style()
|
|
paint := style.Fill
|
|
if outline {
|
|
paint = style.Stroke
|
|
}
|
|
if c.transferBlocked || paint.Alpha != 1 || style.SoftMask != nil || !codecpdf.NormalBlend(style.BlendMode) {
|
|
return nil, nil
|
|
}
|
|
transfer, err := c.importer.resolveTransfer(style, c.transferModel)
|
|
if err != nil || transfer == nil {
|
|
return transfer, err
|
|
}
|
|
if pattern := paint.Shading; pattern != nil && (node.image == nil || node.image.Image.ImageMask) {
|
|
inner := pattern.Style
|
|
if inner.Fill.Alpha != 1 || inner.SoftMask != nil || !codecpdf.NormalBlend(inner.BlendMode) {
|
|
return nil, nil
|
|
}
|
|
}
|
|
if node.image != nil {
|
|
soft, err := node.image.Image.HasSoftMask()
|
|
if err != nil || soft {
|
|
return nil, err
|
|
}
|
|
}
|
|
if paint.Tiling != nil && (node.image == nil || node.image.Image.ImageMask) {
|
|
pattern, err := c.importer.compositePattern(paint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opaque, err := pattern.opaqueTransfer()
|
|
if err != nil || !opaque {
|
|
return nil, err
|
|
}
|
|
}
|
|
return transfer, nil
|
|
}
|
|
|
|
// opaqueTransfer 复用图案不透明性检查,图案内部函数不提前作用于源颜色
|
|
// 返回: bool 是否允许使用外层设备函数, error 单元解析错误
|
|
func (p *pdfCompositePattern) opaqueTransfer() (bool, error) {
|
|
if !p.transferChecked {
|
|
for _, child := range p.nodes {
|
|
opaque, err := p.local.transferOpaque(child)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !opaque {
|
|
p.transferChecked = true
|
|
return false, nil
|
|
}
|
|
}
|
|
p.transferChecked, p.transferOpaque = true, true
|
|
}
|
|
return p.transferOpaque, nil
|
|
}
|
|
|
|
// transferOpaque 检查全部图案内容的不透明条件,不限制混合颜色空间
|
|
// 入参: node 单元图元或透明组
|
|
// 返回: bool 是否不透明, error 图案或图片遮罩引用错误
|
|
func (p *pdfImporter) transferOpaque(node pdfCompositeNode) (bool, error) {
|
|
if group := node.group; group != nil {
|
|
if group.Alpha != 1 || group.SoftMask != nil || !codecpdf.NormalBlend(group.BlendMode) {
|
|
return false, nil
|
|
}
|
|
for _, child := range node.children {
|
|
if opaque, err := p.transferOpaque(child); err != nil || !opaque {
|
|
return opaque, err
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
style, fill, stroke := node.style()
|
|
if style.SoftMask != nil || !codecpdf.NormalBlend(style.BlendMode) || fill && style.Fill.Alpha != 1 || stroke && style.Stroke.Alpha != 1 {
|
|
return false, nil
|
|
}
|
|
if node.image != nil {
|
|
soft, err := node.image.Image.HasSoftMask()
|
|
if err != nil || soft {
|
|
return false, err
|
|
}
|
|
if !node.image.Image.ImageMask {
|
|
return true, nil
|
|
}
|
|
}
|
|
for i, paint := range [2]pdfgo.Paint{style.Fill, style.Stroke} {
|
|
if pattern := paint.Shading; pattern != nil && (i == 0 && fill || i == 1 && stroke) {
|
|
inner := pattern.Style
|
|
if inner.Fill.Alpha != 1 || inner.SoftMask != nil || !codecpdf.NormalBlend(inner.BlendMode) {
|
|
return false, nil
|
|
}
|
|
}
|
|
if i == 0 && !fill || i == 1 && !stroke || paint.Tiling == nil {
|
|
continue
|
|
}
|
|
pattern, err := p.compositePattern(paint)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if opaque, err := pattern.opaqueTransfer(); err != nil || !opaque {
|
|
return opaque, err
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// halftone 检查连续色调转换,避免将打印网屏误用于屏幕显示
|
|
// 入参: style 图形状态
|
|
// 返回: error 严格模式无法保留设备网屏
|
|
func (p *pdfImporter) halftone(style pdfgo.Style) error {
|
|
h := style.Halftone
|
|
message := "PDF continuous-tone appearance retained; device halftone not transferred to OFD"
|
|
if h == nil || h.Type == 0 {
|
|
if style.HalftoneOrigin == nil {
|
|
return nil
|
|
}
|
|
message = "PDF continuous-tone appearance retained; device halftone origin not transferred to OFD"
|
|
}
|
|
if p.warning == nil {
|
|
return &pdfgo.UnsupportedError{Feature: "device halftone conversion"}
|
|
}
|
|
if p.halftoneWarnings == nil {
|
|
p.halftoneWarnings = make(map[*pdfgo.Halftone]bool)
|
|
}
|
|
if !p.halftoneWarnings[h] {
|
|
p.halftoneWarnings[h] = true
|
|
p.warning(pdfgo.Diagnostic{Message: message})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// pdfOpaqueBlack 判断不受底层分色影响的全黑设备色
|
|
// 入参: paint PDF颜色与不透明度
|
|
// 返回: bool 是否为不透明全黑CMYK颜色
|
|
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 {
|
|
_, process := paint.Process.CMYKMask()
|
|
return (paint.CMYK != nil || process) && !pdfOpaqueBlack(paint)
|
|
}
|