mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
1 parent
245fffed84
commit
ee873443e0
10 files changed
+160
-13
No files matched your search
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
github.com/tdewolff/canvas v0.0.0-20260923214215-09804640d00c
|
||||||
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
github.com/tdewolff/font v0.0.0-20260913163313-54f98bb59ee6
|
||||||
github.com/xiaoqidun/jbig2 v1.1.1
|
github.com/xiaoqidun/jbig2 v1.1.1
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261008045044-ada80114c916
|
github.com/xiaoqidun/pdfgo v0.0.0-20261008072006-d519e7a38749
|
||||||
golang.org/x/crypto v0.57.0
|
golang.org/x/crypto v0.57.0
|
||||||
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
|
||||||
|
|||||||
@@ -62,8 +62,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/xiaoqidun/jbig2 v1.1.1 h1:SjliS9ZO0e9HX5oYUfBuXrfFsXhWricrSz+XY4zORDI=
|
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/jbig2 v1.1.1/go.mod h1:8QonVWtENmh2kc6l+leqeGl7psb79DWlHDkCpdChjfQ=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261008045044-ada80114c916 h1:SwXbZ6zLtTCxMeV7A5GBSxgNhyVfdzWLhPh38IdkVTc=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261008072006-d519e7a38749 h1:jWImuld2SXZp826sudLJJUUOEEMPj/Jy77ItAE778Sg=
|
||||||
github.com/xiaoqidun/pdfgo v0.0.0-20261008045044-ada80114c916/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
github.com/xiaoqidun/pdfgo v0.0.0-20261008072006-d519e7a38749/go.mod h1:4Pf0JO0BEf1uzRY+uZjlMjN/OPn3PYa0YMi6cqPPTLQ=
|
||||||
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.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
github.com/yuin/goldmark v1.8.5 h1:r6N5afV5qj/5S4UTch8agZHJ8UxNCMwX7WjkkJam2NA=
|
||||||
|
|||||||
+23
-1
@@ -15,6 +15,7 @@
|
|||||||
package ofdgo
|
package ofdgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
@@ -188,10 +189,31 @@ func WithRasterBackend(backend RasterBackend) RendererOption {
|
|||||||
// 入参: page 页面内容
|
// 入参: page 页面内容
|
||||||
// 返回: *RasterPage 只读绘制页面, error 编译错误
|
// 返回: *RasterPage 只读绘制页面, error 编译错误
|
||||||
func (r *Renderer) CompilePage(page *PageContent) (*RasterPage, error) {
|
func (r *Renderer) CompilePage(page *PageContent) (*RasterPage, error) {
|
||||||
|
return r.CompilePageContext(r.pageContext, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompilePageContext 编译页面并在对象遍历间响应取消,不返回未完成场景
|
||||||
|
// 单次后端操作完成后检查取消,nil使用后台上下文
|
||||||
|
// 入参: ctx 取消上下文, page 页面内容
|
||||||
|
// 返回: *RasterPage 只读绘制页面, error 编译或取消错误
|
||||||
|
func (r *Renderer) CompilePageContext(ctx context.Context, page *PageContent) (*RasterPage, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if r.backends.Compiler == nil {
|
if r.backends.Compiler == nil {
|
||||||
return nil, fmt.Errorf("page compiler: %w", ErrBackendUnavailable)
|
return nil, fmt.Errorf("page compiler: %w", ErrBackendUnavailable)
|
||||||
}
|
}
|
||||||
return r.backends.Compiler.CompilePage(r, page)
|
previous := r.pageContext
|
||||||
|
r.pageContext = ctx
|
||||||
|
defer func() { r.pageContext = previous }()
|
||||||
|
result, err := r.backends.Compiler.CompilePage(r, page)
|
||||||
|
if canceled := ctx.Err(); canceled != nil {
|
||||||
|
return nil, canceled
|
||||||
|
}
|
||||||
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// backendName 获取已配置的后端标识
|
// backendName 获取已配置的后端标识
|
||||||
|
|||||||
+60
-5
@@ -231,6 +231,38 @@ func (c *pdfCompositor) geometryBounds(node pdfCompositeNode, stroke bool) (Box,
|
|||||||
return geometry.box, err
|
return geometry.box, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// coverageBounds 合并实际覆盖边界并复用图元编译缓存,不采用字体估算范围
|
||||||
|
// 入参: node 图元或透明组
|
||||||
|
// 返回: Box 覆盖范围, error 编译或取消错误
|
||||||
|
func (c *pdfCompositor) coverageBounds(node pdfCompositeNode) (Box, error) {
|
||||||
|
if err := c.importer.ctx.Err(); err != nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
var box Box
|
||||||
|
if node.group != nil {
|
||||||
|
for _, child := range node.children {
|
||||||
|
bounds, err := c.coverageBounds(child)
|
||||||
|
if err != nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
box = unionTextBox(box, bounds)
|
||||||
|
}
|
||||||
|
return box, nil
|
||||||
|
}
|
||||||
|
_, fill, stroke := node.style()
|
||||||
|
for _, outline := range []bool{false, true} {
|
||||||
|
if outline && !stroke || !outline && !fill {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bounds, err := c.geometryBounds(node, outline)
|
||||||
|
if err != nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
box = unionTextBox(box, bounds)
|
||||||
|
}
|
||||||
|
return box, nil
|
||||||
|
}
|
||||||
|
|
||||||
// groupBounds 缓存组内实际覆盖范围,外部字体沿用描述符保守边界
|
// groupBounds 缓存组内实际覆盖范围,外部字体沿用描述符保守边界
|
||||||
// 入参: node 透明组
|
// 入参: node 透明组
|
||||||
// 返回: Box 组覆盖范围, error 几何编译错误
|
// 返回: Box 组覆盖范围, error 几何编译错误
|
||||||
@@ -749,12 +781,24 @@ func (n pdfCompositeNode) coverage(p *pdfImporter, stroke bool) error {
|
|||||||
return p.path(pdfgo.PathMark{Path: path, Style: style, Fill: true})
|
return p.path(pdfgo.PathMark{Path: path, Style: style, Fill: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
// compositeRegion 按原始背景计算局部效果,不将组外文字和图形栅格化
|
// compositeRegionBounds 复用内置后端的图元覆盖,自定义后端保留整组度量
|
||||||
// 入参: backdrop 前序图元, node 当前效果, space 页面混合空间, flatten 是否合并页面背景
|
// 入参: node 当前效果
|
||||||
// 返回: error 合成或保存错误
|
// 返回: Box 覆盖范围, error 编译或取消错误
|
||||||
func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompositeNode, space *pdfgo.ColorSpace, flatten bool) error {
|
func (p *pdfImporter) compositeRegionBounds(node pdfCompositeNode) (Box, error) {
|
||||||
if err := p.ctx.Err(); err != nil {
|
if err := p.ctx.Err(); err != nil {
|
||||||
return err
|
return Box{}, err
|
||||||
|
}
|
||||||
|
switch p.editor.Backends().Compiler.(type) {
|
||||||
|
case CanvasBackend, *CanvasBackend, OFDCompiler, *OFDCompiler:
|
||||||
|
geometry, err := p.renderer.Geometry()
|
||||||
|
if err != nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
switch geometry.(type) {
|
||||||
|
case CanvasBackend, *CanvasBackend:
|
||||||
|
c := pdfCompositor{importer: p, cache: p.compositingCache()}
|
||||||
|
return c.coverageBounds(node)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
scene, box, err := p.compileGroup(func(local *pdfImporter) error {
|
scene, box, err := p.compileGroup(func(local *pdfImporter) error {
|
||||||
if node.group != nil {
|
if node.group != nil {
|
||||||
@@ -772,6 +816,17 @@ func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompo
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil || scene == nil {
|
if err != nil || scene == nil {
|
||||||
|
return Box{}, err
|
||||||
|
}
|
||||||
|
return box, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compositeRegion 按原始背景计算局部效果,不将组外文字和图形栅格化
|
||||||
|
// 入参: backdrop 前序图元, node 当前效果, space 页面混合空间, flatten 是否合并页面背景
|
||||||
|
// 返回: error 合成或保存错误
|
||||||
|
func (p *pdfImporter) compositeRegion(backdrop []pdfCompositeNode, node pdfCompositeNode, space *pdfgo.ColorSpace, flatten bool) error {
|
||||||
|
box, err := p.compositeRegionBounds(node)
|
||||||
|
if err != nil || box.W <= 0 || box.H <= 0 {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
inverse, ok := p.matrix.Inverse()
|
inverse, ok := p.matrix.Inverse()
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (p *pdfImporter) destinationBounds(page *pdfgo.Page) (Box, error) {
|
|||||||
if renderer.backends.Fonts != nil {
|
if renderer.backends.Fonts != nil {
|
||||||
renderer.backends.Fonts = pdfDestinationFontBackend{renderer.backends.Fonts}
|
renderer.backends.Fonts = pdfDestinationFontBackend{renderer.backends.Fonts}
|
||||||
}
|
}
|
||||||
scene, err := renderer.CompilePage(&editor.pages[0])
|
scene, err := renderer.CompilePageContext(p.ctx, &editor.pages[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Box{}, err
|
return Box{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -393,7 +393,7 @@ func (p *pdfImporter) compileGroup(build func(*pdfImporter) error) (*RasterPage,
|
|||||||
page := &editor.pages[0]
|
page := &editor.pages[0]
|
||||||
renderer := p.renderer.childRenderer(reader)
|
renderer := p.renderer.childRenderer(reader)
|
||||||
renderer.TransparentBackground = true
|
renderer.TransparentBackground = true
|
||||||
scene, err := renderer.CompilePage(page)
|
scene, err := renderer.CompilePageContext(p.ctx, page)
|
||||||
if canceled := p.ctx.Err(); canceled != nil {
|
if canceled := p.ctx.Err(); canceled != nil {
|
||||||
return nil, box, canceled
|
return nil, box, canceled
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,11 @@ type PageGroups interface {
|
|||||||
// 入参: page 页面内容, visitor 页面访问器
|
// 入参: page 页面内容, visitor 页面访问器
|
||||||
// 返回: error 遍历或访问器错误
|
// 返回: error 遍历或访问器错误
|
||||||
func (r *Renderer) WalkPage(page *PageContent, visitor PageVisitor) error {
|
func (r *Renderer) WalkPage(page *PageContent, visitor PageVisitor) error {
|
||||||
|
if r.pageContext != nil {
|
||||||
|
if err := r.pageContext.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
for order := range 3 {
|
for order := range 3 {
|
||||||
if r.Reader.doc != nil {
|
if r.Reader.doc != nil {
|
||||||
for _, ref := range page.Template {
|
for _, ref := range page.Template {
|
||||||
@@ -83,6 +88,11 @@ func (r *Renderer) WalkPage(page *PageContent, visitor PageVisitor) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, stamp := range r.Reader.Stamps[page.ID] {
|
for _, stamp := range r.Reader.Stamps[page.ID] {
|
||||||
|
if r.pageContext != nil {
|
||||||
|
if err := r.pageContext.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if err := visitor.DrawStamp(stamp); err != nil {
|
if err := visitor.DrawStamp(stamp); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -160,6 +170,11 @@ func (r *Renderer) walkLayers(layers []Layer, order int, visitor PageVisitor) er
|
|||||||
// 入参: object 源对象, state 继承状态, visitor 页面访问器, references 当前资源引用链
|
// 入参: object 源对象, state 继承状态, visitor 页面访问器, references 当前资源引用链
|
||||||
// 返回: error 遍历或访问器错误
|
// 返回: error 遍历或访问器错误
|
||||||
func (r *Renderer) walkObject(object *GraphicObject, state RenderState, visitor PageVisitor, references map[string]bool) error {
|
func (r *Renderer) walkObject(object *GraphicObject, state RenderState, visitor PageVisitor, references map[string]bool) error {
|
||||||
|
if r.pageContext != nil {
|
||||||
|
if err := r.pageContext.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if groups, ok := visitor.(PageGroups); ok && groups.BeginObject(object) {
|
if groups, ok := visitor.(PageGroups); ok && groups.BeginObject(object) {
|
||||||
defer groups.EndObject()
|
defer groups.EndObject()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type Renderer struct {
|
|||||||
fontFS []fs.FS
|
fontFS []fs.FS
|
||||||
decodeImages bool
|
decodeImages bool
|
||||||
pageText *PageText
|
pageText *PageText
|
||||||
|
pageContext context.Context
|
||||||
textOnly bool
|
textOnly bool
|
||||||
backends RenderBackends
|
backends RenderBackends
|
||||||
backendStates map[any]any
|
backendStates map[any]any
|
||||||
@@ -107,6 +108,7 @@ func (r *Renderer) childRenderer(reader *Reader) *Renderer {
|
|||||||
renderer.fontMetrics = r.fontMetrics
|
renderer.fontMetrics = r.fontMetrics
|
||||||
renderer.fontSourcesCache = r.fontSourcesCache.child()
|
renderer.fontSourcesCache = r.fontSourcesCache.child()
|
||||||
renderer.decodeImages = r.decodeImages
|
renderer.decodeImages = r.decodeImages
|
||||||
|
renderer.pageContext = r.pageContext
|
||||||
renderer.TransparentBackground = r.TransparentBackground
|
renderer.TransparentBackground = r.TransparentBackground
|
||||||
return renderer
|
return renderer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,8 @@ func intersectClipPath(parent, current *canvas.Path) *canvas.Path {
|
|||||||
if slices.Equal(parent.Data(), current.Data()) {
|
if slices.Equal(parent.Data(), current.Data()) {
|
||||||
return parent
|
return parent
|
||||||
}
|
}
|
||||||
|
parent = pruneCanvasContours(parent, current)
|
||||||
|
current = pruneCanvasContours(current, parent)
|
||||||
parentRect, parentOK := rectangularPath(parent)
|
parentRect, parentOK := rectangularPath(parent)
|
||||||
currentRect, currentOK := rectangularPath(current)
|
currentRect, currentOK := rectangularPath(current)
|
||||||
if parentOK && currentOK {
|
if parentOK && currentOK {
|
||||||
@@ -295,6 +297,8 @@ func applyClipPath(path, clip *canvas.Path) *canvas.Path {
|
|||||||
if path.Empty() || clip.Empty() {
|
if path.Empty() || clip.Empty() {
|
||||||
return &canvas.Path{}
|
return &canvas.Path{}
|
||||||
}
|
}
|
||||||
|
path = pruneCanvasContours(path, clip)
|
||||||
|
clip = pruneCanvasContours(clip, path)
|
||||||
if rect, ok := rectangularPath(clip); ok {
|
if rect, ok := rectangularPath(clip); ok {
|
||||||
bounds := path.Bounds()
|
bounds := path.Bounds()
|
||||||
if rect.Contains(bounds) {
|
if rect.Contains(bounds) {
|
||||||
@@ -319,6 +323,37 @@ func applyClipPath(path, clip *canvas.Path) *canvas.Path {
|
|||||||
return path.And(clip)
|
return path.And(clip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pruneCanvasContours 排除与裁剪边界分离的完整轮廓,保留相交轮廓的曲线和绕向
|
||||||
|
// 入参: path 填充路径, clip 裁剪路径
|
||||||
|
// 返回: *canvas.Path 待求交路径
|
||||||
|
func pruneCanvasContours(path, clip *canvas.Path) *canvas.Path {
|
||||||
|
if path == nil || clip == nil || len(path.Data()) < 256 || !path.HasSubpaths() {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
bounds := clip.Bounds()
|
||||||
|
parts := path.Split()
|
||||||
|
count := 0
|
||||||
|
for _, part := range parts {
|
||||||
|
if !part.Closed() {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
box := part.Bounds()
|
||||||
|
if box.X1 < bounds.X0 || bounds.X1 < box.X0 || box.Y1 < bounds.Y0 || bounds.Y1 < box.Y0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts[count] = part
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if count == len(parts) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
result := &canvas.Path{}
|
||||||
|
for _, part := range parts[:count] {
|
||||||
|
result = result.Append(part)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// reduceRepeatedNonZeroPath 将完全相同的闭合轮廓约为一份,保持非零填充区域
|
// reduceRepeatedNonZeroPath 将完全相同的闭合轮廓约为一份,保持非零填充区域
|
||||||
// 入参: path 非零填充路径
|
// 入参: path 非零填充路径
|
||||||
// 返回: *canvas.Path 等价填充路径
|
// 返回: *canvas.Path 等价填充路径
|
||||||
|
|||||||
@@ -311,7 +311,7 @@ func geometryToCanvasPath(path *GeometryPath) (*canvas.Path, error) {
|
|||||||
if err := path.validate(); err != nil {
|
if err := path.validate(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p := &canvas.Path{}
|
p := canvas.NewPathFromData(make([]float64, 0, canvasPathCapacity(*path)))
|
||||||
for _, s := range *path {
|
for _, s := range *path {
|
||||||
switch s.Verb {
|
switch s.Verb {
|
||||||
case GeometryMove:
|
case GeometryMove:
|
||||||
@@ -337,7 +337,7 @@ func geometryToCanvasPath(path *GeometryPath) (*canvas.Path, error) {
|
|||||||
// 入参: path 独立路径
|
// 入参: path 独立路径
|
||||||
// 返回: *canvas.Path 适配路径, error 不支持的指令
|
// 返回: *canvas.Path 适配路径, error 不支持的指令
|
||||||
func canvasObjectPath(path GeometryPath) (*canvas.Path, error) {
|
func canvasObjectPath(path GeometryPath) (*canvas.Path, error) {
|
||||||
var data []float64
|
data := make([]float64, 0, canvasPathCapacity(path))
|
||||||
for _, s := range path {
|
for _, s := range path {
|
||||||
end := s.End
|
end := s.End
|
||||||
switch s.Verb {
|
switch s.Verb {
|
||||||
@@ -364,6 +364,24 @@ func canvasObjectPath(path GeometryPath) (*canvas.Path, error) {
|
|||||||
return canvas.NewPathFromData(data), nil
|
return canvas.NewPathFromData(data), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// canvasPathCapacity 按指令估算路径容量,避免长路径逐段扩容
|
||||||
|
// 入参: path 独立几何路径
|
||||||
|
// 返回: int 坐标存储容量
|
||||||
|
func canvasPathCapacity(path GeometryPath) int {
|
||||||
|
count := 0
|
||||||
|
for _, segment := range path {
|
||||||
|
switch segment.Verb {
|
||||||
|
case GeometryMove, GeometryLine, GeometryClose:
|
||||||
|
count += 4
|
||||||
|
case GeometryQuad:
|
||||||
|
count += 6
|
||||||
|
default:
|
||||||
|
count += 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
// geometryFromCanvasPath 将默认引擎路径转换为独立的页面坐标路径,保留曲线和椭圆弧
|
// geometryFromCanvasPath 将默认引擎路径转换为独立的页面坐标路径,保留曲线和椭圆弧
|
||||||
// 入参: path 默认引擎路径
|
// 入参: path 默认引擎路径
|
||||||
// 返回: *GeometryPath 独立路径,nil表示不裁剪
|
// 返回: *GeometryPath 独立路径,nil表示不裁剪
|
||||||
|
|||||||
Reference in new issue
Block a user