Properly implement morphing of shapes, gradients, styles, fix KERNINGRECORD / PlaceObject, avoid panic on invalid PlaceObject of font object
This commit is contained in:
parent
ce5902c4b8
commit
c09f81ee4e
|
@ -6,10 +6,10 @@ import (
|
|||
)
|
||||
|
||||
type KERNINGRECORD struct {
|
||||
KerningCodeLeft8 uint8 `swfCond:"!Flag.WideCodes"`
|
||||
KerningCodeLeft16 uint16 `swfCond:"Flag.WideCodes"`
|
||||
KerningCodeRight8 uint8 `swfCond:"!Flag.WideCodes"`
|
||||
KerningCodeRight16 uint16 `swfCond:"Flag.WideCodes"`
|
||||
KerningCodeLeft8 uint8 `swfCondition:"!Flag.WideCodes"`
|
||||
KerningCodeLeft16 uint16 `swfCondition:"Flag.WideCodes"`
|
||||
KerningCodeRight8 uint8 `swfCondition:"!Flag.WideCodes"`
|
||||
KerningCodeRight16 uint16 `swfCondition:"Flag.WideCodes"`
|
||||
KerningAdjustment int16
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package types
|
|||
|
||||
import (
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
|
||||
math2 "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/records"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/shapes"
|
||||
"math"
|
||||
|
@ -35,36 +34,7 @@ func (d *MorphShapeDefinition) GetShapeList(p shapes.ObjectProperties) (list sha
|
|||
var shape shapes.Shape
|
||||
|
||||
for _, recordPair := range shapes.IterateMorphShape(c1.Commands, c2.Commands) {
|
||||
startEdge := recordPair[0]
|
||||
endEdge := recordPair[1]
|
||||
|
||||
//No need to convert types!
|
||||
aLineRecord, aIsLineRecord := startEdge.(*records.LineRecord)
|
||||
aMoveRecord, aIsMoveRecord := startEdge.(*records.MoveRecord)
|
||||
aQuadraticCurveRecord, aIsQuadraticCurveRecord := startEdge.(*records.QuadraticCurveRecord)
|
||||
bLineRecord, bIsLineRecord := endEdge.(*records.LineRecord)
|
||||
bMoveRecord, bIsMoveRecord := endEdge.(*records.MoveRecord)
|
||||
bQuadraticCurveRecord, bIsQuadraticCurveRecord := endEdge.(*records.QuadraticCurveRecord)
|
||||
|
||||
if aIsLineRecord && bIsLineRecord {
|
||||
shape.AddRecord(&records.LineRecord{
|
||||
To: math2.LerpVector2(aLineRecord.To, bLineRecord.To, p.Ratio),
|
||||
Start: math2.LerpVector2(aLineRecord.Start, bLineRecord.Start, p.Ratio),
|
||||
})
|
||||
} else if aIsQuadraticCurveRecord && bIsQuadraticCurveRecord {
|
||||
shape.AddRecord(&records.QuadraticCurveRecord{
|
||||
Control: math2.LerpVector2(aQuadraticCurveRecord.Control, bQuadraticCurveRecord.Control, p.Ratio),
|
||||
Anchor: math2.LerpVector2(aQuadraticCurveRecord.Anchor, bQuadraticCurveRecord.Anchor, p.Ratio),
|
||||
Start: math2.LerpVector2(aQuadraticCurveRecord.Start, bQuadraticCurveRecord.Start, p.Ratio),
|
||||
})
|
||||
} else if aIsMoveRecord && bIsMoveRecord {
|
||||
shape.AddRecord(&records.MoveRecord{
|
||||
To: math2.LerpVector2(aMoveRecord.To, bMoveRecord.To, p.Ratio),
|
||||
Start: math2.LerpVector2(aMoveRecord.Start, bMoveRecord.Start, p.Ratio),
|
||||
})
|
||||
} else {
|
||||
panic("unsupported")
|
||||
}
|
||||
shape.AddRecord(records.LerpRecord(recordPair[0], recordPair[1], p.Ratio))
|
||||
}
|
||||
|
||||
//TODO: morph styles properly
|
||||
|
@ -74,26 +44,9 @@ func (d *MorphShapeDefinition) GetShapeList(p shapes.ObjectProperties) (list sha
|
|||
c2LineStyle, c2IsLineStyle := c2.Style.(*shapes.LineStyleRecord)
|
||||
|
||||
if c1IsFillStyle && c2IsFillStyle {
|
||||
if c1Color, ok := c1FillStyle.Fill.(math2.Color); ok {
|
||||
list = append(list, shapes.DrawPathFill(&shapes.FillStyleRecord{
|
||||
Fill: math2.LerpColor(c1Color, c2FillStyle.Fill.(math2.Color), p.Ratio),
|
||||
Border: c1FillStyle.Border,
|
||||
}, &shape, nil))
|
||||
} else if c1Gradient, ok := c1FillStyle.Fill.(shapes.Gradient); ok {
|
||||
//TODO: proper gradients
|
||||
list = append(list, shapes.DrawPathFill(&shapes.FillStyleRecord{
|
||||
Fill: math2.LerpColor(c1Gradient.GetItems()[0].Color, c2FillStyle.Fill.(shapes.Gradient).GetItems()[0].Color, p.Ratio),
|
||||
Border: c1FillStyle.Border,
|
||||
}, &shape, nil))
|
||||
} else {
|
||||
panic("unsupported")
|
||||
}
|
||||
list = append(list, shapes.DrawPathFill(shapes.LerpFillStyle(c1FillStyle, c2FillStyle, p.Ratio), &shape, c1.Clip))
|
||||
} else if c1IsLineStyle && c2IsLineStyle {
|
||||
list = append(list, shapes.DrawPathStroke(&shapes.LineStyleRecord{
|
||||
Width: math2.Lerp(c1LineStyle.Width, c2LineStyle.Width, p.Ratio),
|
||||
Color: math2.LerpColor(c1LineStyle.Color, c2LineStyle.Color, p.Ratio),
|
||||
//TODO: blur
|
||||
}, &shape, nil))
|
||||
list = append(list, shapes.DrawPathStroke(shapes.LerpLineStyle(c1LineStyle, c2LineStyle, p.Ratio), &shape, c1.Clip))
|
||||
} else {
|
||||
panic("unsupported")
|
||||
}
|
||||
|
@ -109,10 +62,7 @@ func (d *MorphShapeDefinition) GetSafeObject() shapes.ObjectDefinition {
|
|||
func MorphShapeDefinitionFromSWF(collection shapes.ObjectCollection, shapeId uint16, startBounds, endBounds shapes.Rectangle[float64], startRecords, endRecords subtypes.SHAPERECORDS, fillStyles subtypes.MORPHFILLSTYLEARRAY, lineStyles subtypes.MORPHLINESTYLEARRAY) *MorphShapeDefinition {
|
||||
startStyles, endStyles := shapes.StyleListFromSWFMorphItems(collection, fillStyles, lineStyles)
|
||||
|
||||
start := shapes.DrawPathListFromSWFMorph(collection, startRecords, endRecords, startStyles, false)
|
||||
//TODO: morph styles properly
|
||||
_ = endStyles
|
||||
end := shapes.DrawPathListFromSWFMorph(collection, startRecords, endRecords, startStyles, true)
|
||||
start, end := shapes.DrawPathListFromSWFMorph(collection, startRecords, endRecords, startStyles, endStyles)
|
||||
|
||||
if len(start) != len(end) {
|
||||
panic("length does not match")
|
||||
|
|
|
@ -417,9 +417,7 @@ func (p *SWFTreeProcessor) process(actions ActionList) (tag swftag.Tag, newActio
|
|||
|
||||
case *swftag.PlaceObject:
|
||||
var object shapes.ObjectDefinition
|
||||
if vl := p.Layout.Get(node.Depth); vl != nil {
|
||||
object = vl.Object
|
||||
}
|
||||
p.Objects.Get(node.CharacterId)
|
||||
|
||||
var transform *math.MatrixTransform
|
||||
if t := math.MatrixTransformFromSWF(node.Matrix); !t.IsIdentity() {
|
||||
|
@ -436,7 +434,7 @@ func (p *SWFTreeProcessor) process(actions ActionList) (tag swftag.Tag, newActio
|
|||
case *swftag.PlaceObject2:
|
||||
var object shapes.ObjectDefinition
|
||||
if node.Flag.HasCharacter {
|
||||
object = p.Objects[node.CharacterId]
|
||||
object = p.Objects.Get(node.CharacterId)
|
||||
} else if vl := p.Layout.Get(node.Depth); vl != nil {
|
||||
object = vl.Object
|
||||
}
|
||||
|
@ -458,7 +456,7 @@ func (p *SWFTreeProcessor) process(actions ActionList) (tag swftag.Tag, newActio
|
|||
//TODO: handle extra properties
|
||||
var object shapes.ObjectDefinition
|
||||
if node.Flag.HasCharacter {
|
||||
object = p.Objects[node.CharacterId]
|
||||
object = p.Objects.Get(node.CharacterId)
|
||||
} else {
|
||||
object = p.Layout.Get(node.Depth).Object
|
||||
}
|
||||
|
|
58
types/records/lerp.go
Normal file
58
types/records/lerp.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package records
|
||||
|
||||
import "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
|
||||
func LerpRecord(start, end Record, ratio float64) Record {
|
||||
if start.SameType(end) {
|
||||
switch s := start.(type) {
|
||||
case *LineRecord:
|
||||
return &LineRecord{
|
||||
To: math.LerpVector2(s.To, end.(*LineRecord).To, ratio),
|
||||
Start: math.LerpVector2(s.Start, end.(*LineRecord).Start, ratio),
|
||||
}
|
||||
case *MoveRecord:
|
||||
return &MoveRecord{
|
||||
To: math.LerpVector2(s.To, end.(*MoveRecord).To, ratio),
|
||||
Start: math.LerpVector2(s.Start, end.(*MoveRecord).Start, ratio),
|
||||
}
|
||||
case *QuadraticCurveRecord:
|
||||
return &QuadraticCurveRecord{
|
||||
Control: math.LerpVector2(s.Control, end.(*QuadraticCurveRecord).Control, ratio),
|
||||
Anchor: math.LerpVector2(s.Anchor, end.(*QuadraticCurveRecord).Anchor, ratio),
|
||||
Start: math.LerpVector2(s.Start, end.(*QuadraticCurveRecord).Start, ratio),
|
||||
}
|
||||
case *CubicCurveRecord:
|
||||
return &CubicCurveRecord{
|
||||
Control1: math.LerpVector2(s.Control1, end.(*CubicCurveRecord).Control1, ratio),
|
||||
Control2: math.LerpVector2(s.Control2, end.(*CubicCurveRecord).Control2, ratio),
|
||||
Anchor: math.LerpVector2(s.Anchor, end.(*CubicCurveRecord).Anchor, ratio),
|
||||
Start: math.LerpVector2(s.Start, end.(*CubicCurveRecord).Start, ratio),
|
||||
}
|
||||
default:
|
||||
panic("not supported")
|
||||
}
|
||||
} else {
|
||||
startLine, startLineOk := start.(*LineRecord)
|
||||
startQuadratic, startQuadraticOk := start.(*QuadraticCurveRecord)
|
||||
endLine, endLineOk := end.(*LineRecord)
|
||||
endQuadratic, endQuadraticOk := end.(*QuadraticCurveRecord)
|
||||
|
||||
if startLineOk && endQuadraticOk {
|
||||
startQuadratic = QuadraticCurveFromLineRecord(startLine)
|
||||
return &QuadraticCurveRecord{
|
||||
Control: math.LerpVector2(startQuadratic.Control, endQuadratic.Control, ratio),
|
||||
Anchor: math.LerpVector2(startQuadratic.Anchor, endQuadratic.Anchor, ratio),
|
||||
Start: math.LerpVector2(startQuadratic.Start, endQuadratic.Start, ratio),
|
||||
}
|
||||
} else if startQuadraticOk && endLineOk {
|
||||
endQuadratic = QuadraticCurveFromLineRecord(endLine)
|
||||
return &QuadraticCurveRecord{
|
||||
Control: math.LerpVector2(startQuadratic.Control, endQuadratic.Control, ratio),
|
||||
Anchor: math.LerpVector2(startQuadratic.Anchor, endQuadratic.Anchor, ratio),
|
||||
Start: math.LerpVector2(startQuadratic.Start, endQuadratic.Start, ratio),
|
||||
}
|
||||
} else {
|
||||
panic("not supported")
|
||||
}
|
||||
}
|
||||
}
|
32
types/shapes/Bitmap.go
Normal file
32
types/shapes/Bitmap.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package shapes
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
|
||||
math2 "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
)
|
||||
|
||||
type Bitmap struct {
|
||||
List DrawPathList
|
||||
|
||||
Transform math2.MatrixTransform
|
||||
}
|
||||
|
||||
func (b Bitmap) ApplyColorTransform(transform math2.ColorTransform) Fillable {
|
||||
b2 := b
|
||||
b2.List = b.List.ApplyColorTransform(transform).(DrawPathList)
|
||||
b2.Transform = b.Transform
|
||||
return b2
|
||||
}
|
||||
|
||||
func (b Bitmap) Fill(shape *Shape) DrawPathList {
|
||||
return b.List.Fill(shape)
|
||||
}
|
||||
|
||||
func BitmapFillFromSWF(l DrawPathList, transform types.MATRIX) Bitmap {
|
||||
// shape is already in pixel world, but matrix comes as twip
|
||||
baseScale := math2.ScaleTransform(math2.NewVector2[float64](1./types.TwipFactor, 1./types.TwipFactor))
|
||||
return Bitmap{
|
||||
List: l,
|
||||
Transform: math2.MatrixTransformFromSWF(transform).Multiply(baseScale),
|
||||
}
|
||||
}
|
|
@ -2,7 +2,6 @@ package shapes
|
|||
|
||||
import (
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
)
|
||||
|
||||
|
@ -76,23 +75,13 @@ func (l DrawPathList) ApplyMatrixTransform(transform math.MatrixTransform, apply
|
|||
return r
|
||||
}
|
||||
|
||||
func DrawPathListFillFromSWF(l DrawPathList, transform types.MATRIX) DrawPathList {
|
||||
// shape is already in pixel world, but matrix comes as twip
|
||||
baseScale := math.ScaleTransform(math.NewVector2[float64](1./types.TwipFactor, 1./types.TwipFactor))
|
||||
t := math.MatrixTransformFromSWF(transform).Multiply(baseScale)
|
||||
return l.ApplyMatrixTransform(t, true)
|
||||
}
|
||||
|
||||
func DrawPathListFromSWF(collection ObjectCollection, records subtypes.SHAPERECORDS, styles StyleList) DrawPathList {
|
||||
converter := NewShapeConverter(collection, records, styles)
|
||||
converter.Convert(false)
|
||||
|
||||
return converter.Commands
|
||||
return NewShapeConverter(collection, styles).Convert(records)
|
||||
}
|
||||
|
||||
func DrawPathListFromSWFMorph(collection ObjectCollection, startRecords, endRecords subtypes.SHAPERECORDS, styles StyleList, flip bool) DrawPathList {
|
||||
converter := NewMorphShapeConverter(collection, startRecords, endRecords, styles)
|
||||
converter.Convert(flip)
|
||||
func DrawPathListFromSWFMorph(collection ObjectCollection, startRecords, endRecords subtypes.SHAPERECORDS, startStyles, endStyles StyleList) (DrawPathList, DrawPathList) {
|
||||
converter := NewMorphShapeConverter(collection, startStyles)
|
||||
startRecords, endRecords = converter.ConvertMorph(startRecords, endRecords)
|
||||
|
||||
return converter.Commands
|
||||
return converter.Convert(startRecords), NewMorphShapeConverter(collection, endStyles).Convert(endRecords)
|
||||
}
|
||||
|
|
|
@ -9,14 +9,50 @@ import (
|
|||
"slices"
|
||||
)
|
||||
|
||||
type Gradient interface {
|
||||
Fillable
|
||||
GetSpreadMode() swfsubtypes.GradientSpreadMode
|
||||
GetInterpolationMode() swfsubtypes.GradientInterpolationMode
|
||||
GetItems() []GradientItem
|
||||
GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList
|
||||
GetMatrixTransform() math2.MatrixTransform
|
||||
ApplyColorTransform(transform math2.ColorTransform) Fillable
|
||||
type Gradient struct {
|
||||
Records []GradientItem
|
||||
|
||||
Transform math2.MatrixTransform
|
||||
SpreadMode swfsubtypes.GradientSpreadMode
|
||||
InterpolationMode swfsubtypes.GradientInterpolationMode
|
||||
|
||||
Interpolation func(self Gradient, overlap, blur float64, gradientSlices int) DrawPathList
|
||||
}
|
||||
|
||||
func (g Gradient) GetSpreadMode() swfsubtypes.GradientSpreadMode {
|
||||
return g.SpreadMode
|
||||
}
|
||||
|
||||
func (g Gradient) GetInterpolationMode() swfsubtypes.GradientInterpolationMode {
|
||||
return g.InterpolationMode
|
||||
}
|
||||
|
||||
func (g Gradient) GetItems() []GradientItem {
|
||||
return g.Records
|
||||
}
|
||||
|
||||
func (g Gradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList {
|
||||
return g.Interpolation(g, overlap, blur, gradientSlices)
|
||||
}
|
||||
|
||||
func (g Gradient) GetMatrixTransform() math2.MatrixTransform {
|
||||
return g.Transform
|
||||
}
|
||||
|
||||
func (g Gradient) ApplyColorTransform(transform math2.ColorTransform) Fillable {
|
||||
g2 := g
|
||||
g2.Records = slices.Clone(g2.Records)
|
||||
for i, g := range g2.Records {
|
||||
g2.Records[i] = GradientItem{
|
||||
Ratio: g.Ratio,
|
||||
Color: transform.ApplyToColor(g.Color),
|
||||
}
|
||||
}
|
||||
return &g2
|
||||
}
|
||||
|
||||
func (g Gradient) Fill(shape *Shape) DrawPathList {
|
||||
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices).Fill(shape)
|
||||
}
|
||||
|
||||
type GradientItem struct {
|
||||
|
@ -39,7 +75,7 @@ var GradientBounds = Rectangle[float64]{
|
|||
|
||||
const GradientRatioDivisor = math.MaxUint8
|
||||
|
||||
func LerpGradient(gradient Gradient, gradientSlices int) (result []GradientSlice) {
|
||||
func InterpolateGradient(gradient Gradient, gradientSlices int) (result []GradientSlice) {
|
||||
items := gradient.GetItems()
|
||||
//TODO: spread modes
|
||||
|
||||
|
|
|
@ -1,77 +1,12 @@
|
|||
package shapes
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/settings"
|
||||
swfsubtypes "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type LinearGradient struct {
|
||||
Colors []GradientItem
|
||||
|
||||
Transform math.MatrixTransform
|
||||
SpreadMode swfsubtypes.GradientSpreadMode
|
||||
InterpolationMode swfsubtypes.GradientInterpolationMode
|
||||
}
|
||||
|
||||
func (g LinearGradient) GetSpreadMode() swfsubtypes.GradientSpreadMode {
|
||||
return g.SpreadMode
|
||||
}
|
||||
|
||||
func (g LinearGradient) GetInterpolationMode() swfsubtypes.GradientInterpolationMode {
|
||||
return g.InterpolationMode
|
||||
}
|
||||
|
||||
func (g LinearGradient) GetItems() []GradientItem {
|
||||
return g.Colors
|
||||
}
|
||||
|
||||
func (g LinearGradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList {
|
||||
//items is max size 8 to 15 depending on SWF version
|
||||
size := GradientBounds.Width()
|
||||
|
||||
//TODO spreadMode
|
||||
|
||||
var paths DrawPathList
|
||||
for _, item := range LerpGradient(g, gradientSlices) {
|
||||
paths = append(paths, DrawPathFill(
|
||||
&FillStyleRecord{
|
||||
Fill: item.Color,
|
||||
Blur: blur,
|
||||
},
|
||||
NewShape(Rectangle[float64]{
|
||||
TopLeft: math.NewVector2(GradientBounds.TopLeft.X+item.Start*size-overlap/2, GradientBounds.TopLeft.Y),
|
||||
BottomRight: math.NewVector2(GradientBounds.TopLeft.X+item.End*size+overlap/2, GradientBounds.BottomRight.Y),
|
||||
}.Draw()),
|
||||
nil, //TODO: clip here instead of outside
|
||||
).ApplyMatrixTransform(g.Transform, true))
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
func (g LinearGradient) GetMatrixTransform() math.MatrixTransform {
|
||||
return g.Transform
|
||||
}
|
||||
|
||||
func (g LinearGradient) ApplyColorTransform(transform math.ColorTransform) Fillable {
|
||||
g2 := g
|
||||
g2.Colors = slices.Clone(g2.Colors)
|
||||
for i, g := range g2.Colors {
|
||||
g2.Colors[i] = GradientItem{
|
||||
Ratio: g.Ratio,
|
||||
Color: transform.ApplyToColor(g.Color),
|
||||
}
|
||||
}
|
||||
return &g2
|
||||
}
|
||||
|
||||
func (g LinearGradient) Fill(shape *Shape) DrawPathList {
|
||||
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices).Fill(shape)
|
||||
}
|
||||
|
||||
func LinearGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) DrawPathList {
|
||||
func LinearGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) Gradient {
|
||||
items := make([]GradientItem, 0, len(records))
|
||||
for _, r := range records {
|
||||
items = append(items, GradientItemFromSWF(r.Ratio, r.Color))
|
||||
|
@ -79,11 +14,33 @@ func LinearGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MAT
|
|||
|
||||
//TODO: interpolationMode, spreadMode
|
||||
|
||||
return LinearGradient{
|
||||
Colors: items,
|
||||
return Gradient{
|
||||
Records: items,
|
||||
//TODO: do we need to scale this to pixel world from twips?
|
||||
Transform: math.MatrixTransformFromSWF(transform),
|
||||
SpreadMode: spreadMode,
|
||||
InterpolationMode: interpolationMode,
|
||||
}.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices)
|
||||
Interpolation: func(self Gradient, overlap, blur float64, gradientSlices int) DrawPathList {
|
||||
//items is max size 8 to 15 depending on SWF version
|
||||
size := GradientBounds.Width()
|
||||
|
||||
//TODO spreadMode
|
||||
|
||||
var paths DrawPathList
|
||||
for _, item := range InterpolateGradient(self, gradientSlices) {
|
||||
paths = append(paths, DrawPathFill(
|
||||
&FillStyleRecord{
|
||||
Fill: item.Color,
|
||||
Blur: blur,
|
||||
},
|
||||
NewShape(Rectangle[float64]{
|
||||
TopLeft: math.NewVector2(GradientBounds.TopLeft.X+item.Start*size-overlap/2, GradientBounds.TopLeft.Y),
|
||||
BottomRight: math.NewVector2(GradientBounds.TopLeft.X+item.End*size+overlap/2, GradientBounds.BottomRight.Y),
|
||||
}.Draw()),
|
||||
nil, //TODO: clip here instead of outside
|
||||
).ApplyMatrixTransform(self.Transform, true))
|
||||
}
|
||||
return paths
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,85 +1,12 @@
|
|||
package shapes
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/settings"
|
||||
swfsubtypes "git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type RadialGradient struct {
|
||||
Colors []GradientItem
|
||||
|
||||
Transform math.MatrixTransform
|
||||
SpreadMode swfsubtypes.GradientSpreadMode
|
||||
InterpolationMode swfsubtypes.GradientInterpolationMode
|
||||
}
|
||||
|
||||
func (g RadialGradient) GetSpreadMode() swfsubtypes.GradientSpreadMode {
|
||||
return g.SpreadMode
|
||||
}
|
||||
|
||||
func (g RadialGradient) GetInterpolationMode() swfsubtypes.GradientInterpolationMode {
|
||||
return g.InterpolationMode
|
||||
}
|
||||
|
||||
func (g RadialGradient) GetItems() []GradientItem {
|
||||
return g.Colors
|
||||
}
|
||||
|
||||
func (g RadialGradient) GetInterpolatedDrawPaths(overlap, blur float64, gradientSlices int) DrawPathList {
|
||||
//items is max size 8 to 15 depending on SWF version
|
||||
size := GradientBounds.Width()
|
||||
|
||||
//TODO spreadMode
|
||||
|
||||
var paths DrawPathList
|
||||
for _, item := range LerpGradient(g, gradientSlices) {
|
||||
//Create concentric circles to cut out a shape
|
||||
var shape Shape
|
||||
radiusStart := (item.Start*size)/2 - overlap/4
|
||||
radiusEnd := (item.End*size)/2 + overlap/4
|
||||
start := NewCircle(math.NewVector2[float64](0, 0), radiusStart).Draw()
|
||||
if radiusStart <= 0 {
|
||||
start = nil
|
||||
}
|
||||
end := NewCircle(math.NewVector2[float64](0, 0), radiusEnd).Draw()
|
||||
shape.Edges = append(shape.Edges, end...)
|
||||
shape.Edges = append(shape.Edges, NewShape(start).Reverse().Edges...)
|
||||
paths = append(paths, DrawPathFill(
|
||||
&FillStyleRecord{
|
||||
Fill: item.Color,
|
||||
Blur: blur,
|
||||
},
|
||||
&shape,
|
||||
nil, //TODO: clip here instead of outside
|
||||
))
|
||||
}
|
||||
return paths.ApplyMatrixTransform(g.Transform, true)
|
||||
}
|
||||
|
||||
func (g RadialGradient) GetMatrixTransform() math.MatrixTransform {
|
||||
return g.Transform
|
||||
}
|
||||
|
||||
func (g RadialGradient) ApplyColorTransform(transform math.ColorTransform) Fillable {
|
||||
g2 := g
|
||||
g2.Colors = slices.Clone(g2.Colors)
|
||||
for i, g := range g2.Colors {
|
||||
g2.Colors[i] = GradientItem{
|
||||
Ratio: g.Ratio,
|
||||
Color: transform.ApplyToColor(g.Color),
|
||||
}
|
||||
}
|
||||
return &g2
|
||||
}
|
||||
|
||||
func (g RadialGradient) Fill(shape *Shape) DrawPathList {
|
||||
return g.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices).Fill(shape)
|
||||
}
|
||||
|
||||
func RadialGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) DrawPathList {
|
||||
func RadialGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MATRIX, spreadMode swfsubtypes.GradientSpreadMode, interpolationMode swfsubtypes.GradientInterpolationMode) Gradient {
|
||||
items := make([]GradientItem, 0, len(records))
|
||||
for _, r := range records {
|
||||
items = append(items, GradientItemFromSWF(r.Ratio, r.Color))
|
||||
|
@ -87,11 +14,41 @@ func RadialGradientFromSWF(records []swfsubtypes.GRADRECORD, transform types.MAT
|
|||
|
||||
//TODO: interpolationMode, spreadMode
|
||||
|
||||
return RadialGradient{
|
||||
Colors: items,
|
||||
return Gradient{
|
||||
Records: items,
|
||||
//TODO: do we need to scale this to pixel world from twips?
|
||||
Transform: math.MatrixTransformFromSWF(transform),
|
||||
SpreadMode: spreadMode,
|
||||
InterpolationMode: interpolationMode,
|
||||
}.GetInterpolatedDrawPaths(settings.GlobalSettings.GradientOverlap, settings.GlobalSettings.GradientBlur, settings.GlobalSettings.GradientSlices)
|
||||
Interpolation: func(self Gradient, overlap, blur float64, gradientSlices int) DrawPathList {
|
||||
//items is max size 8 to 15 depending on SWF version
|
||||
size := GradientBounds.Width()
|
||||
|
||||
//TODO spreadMode
|
||||
|
||||
var paths DrawPathList
|
||||
for _, item := range InterpolateGradient(self, gradientSlices) {
|
||||
//Create concentric circles to cut out a shape
|
||||
var shape Shape
|
||||
radiusStart := (item.Start*size)/2 - overlap/4
|
||||
radiusEnd := (item.End*size)/2 + overlap/4
|
||||
start := NewCircle(math.NewVector2[float64](0, 0), radiusStart).Draw()
|
||||
if radiusStart <= 0 {
|
||||
start = nil
|
||||
}
|
||||
end := NewCircle(math.NewVector2[float64](0, 0), radiusEnd).Draw()
|
||||
shape.Edges = append(shape.Edges, end...)
|
||||
shape.Edges = append(shape.Edges, NewShape(start).Reverse().Edges...)
|
||||
paths = append(paths, DrawPathFill(
|
||||
&FillStyleRecord{
|
||||
Fill: item.Color,
|
||||
Blur: blur,
|
||||
},
|
||||
&shape,
|
||||
nil, //TODO: clip here instead of outside
|
||||
))
|
||||
}
|
||||
return paths.ApplyMatrixTransform(self.Transform, true)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,159 +23,139 @@ type ShapeConverter struct {
|
|||
Commands DrawPathList
|
||||
|
||||
Finished bool
|
||||
|
||||
FirstElement, SecondElement subtypes.SHAPERECORDS
|
||||
}
|
||||
|
||||
func NewShapeConverter(collection ObjectCollection, element subtypes.SHAPERECORDS, styles StyleList) *ShapeConverter {
|
||||
func NewShapeConverter(collection ObjectCollection, styles StyleList) *ShapeConverter {
|
||||
return &ShapeConverter{
|
||||
Collection: collection,
|
||||
Styles: styles,
|
||||
Position: math.NewVector2[types.Twip](0, 0),
|
||||
Fills: make(PendingPathMap),
|
||||
Strokes: make(PendingPathMap),
|
||||
FirstElement: element,
|
||||
Collection: collection,
|
||||
Styles: styles,
|
||||
Position: math.NewVector2[types.Twip](0, 0),
|
||||
Fills: make(PendingPathMap),
|
||||
Strokes: make(PendingPathMap),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMorphShapeConverter(collection ObjectCollection, firstElement, secondElement subtypes.SHAPERECORDS, styles StyleList) *ShapeConverter {
|
||||
func NewMorphShapeConverter(collection ObjectCollection, styles StyleList) *ShapeConverter {
|
||||
return &ShapeConverter{
|
||||
Collection: collection,
|
||||
Styles: styles,
|
||||
Position: math.NewVector2[types.Twip](0, 0),
|
||||
Fills: make(PendingPathMap),
|
||||
Strokes: make(PendingPathMap),
|
||||
FirstElement: firstElement,
|
||||
SecondElement: secondElement,
|
||||
Collection: collection,
|
||||
Styles: styles,
|
||||
Position: math.NewVector2[types.Twip](0, 0),
|
||||
Fills: make(PendingPathMap),
|
||||
Strokes: make(PendingPathMap),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ShapeConverter) Convert(flipElements bool) {
|
||||
func (c *ShapeConverter) Convert(elements subtypes.SHAPERECORDS) DrawPathList {
|
||||
if c.Finished {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
firstElement := c.FirstElement
|
||||
secondElement := c.SecondElement
|
||||
|
||||
for {
|
||||
var a, b subtypes.SHAPERECORD
|
||||
if len(secondElement) > 0 {
|
||||
b = secondElement[0]
|
||||
}
|
||||
|
||||
if len(firstElement) > 0 {
|
||||
a = firstElement[0]
|
||||
} else {
|
||||
if b != nil {
|
||||
panic("a finished, b did not")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
if flipElements {
|
||||
panic("b finished, a did not")
|
||||
}
|
||||
c.HandleNode(a)
|
||||
firstElement = firstElement[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if c.Finished {
|
||||
panic("more paths after end")
|
||||
}
|
||||
|
||||
if a.RecordType() == b.RecordType() {
|
||||
switch a := a.(type) {
|
||||
case *subtypes.StyleChangeRecord:
|
||||
bCopy := *b.(*subtypes.StyleChangeRecord)
|
||||
aCopy := *a
|
||||
|
||||
if aCopy.Flag.NewStyles {
|
||||
bCopy.Flag.NewStyles = aCopy.Flag.NewStyles
|
||||
bCopy.FillStyles = aCopy.FillStyles
|
||||
bCopy.LineStyles = aCopy.LineStyles
|
||||
}
|
||||
if aCopy.Flag.LineStyle {
|
||||
bCopy.Flag.LineStyle = aCopy.Flag.LineStyle
|
||||
bCopy.LineStyle = aCopy.LineStyle
|
||||
}
|
||||
if aCopy.Flag.FillStyle0 {
|
||||
bCopy.Flag.FillStyle0 = aCopy.Flag.FillStyle0
|
||||
bCopy.FillStyle0 = aCopy.FillStyle0
|
||||
}
|
||||
if aCopy.Flag.FillStyle1 {
|
||||
bCopy.Flag.FillStyle1 = aCopy.Flag.FillStyle1
|
||||
bCopy.FillStyle1 = aCopy.FillStyle1
|
||||
}
|
||||
|
||||
if !flipElements && !aCopy.Flag.MoveTo && bCopy.Flag.MoveTo {
|
||||
aCopy.Flag.MoveTo = bCopy.Flag.MoveTo
|
||||
aCopy.MoveDeltaX = c.Position.X
|
||||
aCopy.MoveDeltaY = c.Position.Y
|
||||
}
|
||||
|
||||
if flipElements && aCopy.Flag.MoveTo && !bCopy.Flag.MoveTo {
|
||||
bCopy.Flag.MoveTo = aCopy.Flag.MoveTo
|
||||
bCopy.MoveDeltaX = c.Position.X
|
||||
bCopy.MoveDeltaY = c.Position.Y
|
||||
}
|
||||
|
||||
if flipElements {
|
||||
c.HandleNode(&bCopy)
|
||||
} else {
|
||||
c.HandleNode(&aCopy)
|
||||
}
|
||||
}
|
||||
|
||||
firstElement = firstElement[1:]
|
||||
secondElement = secondElement[1:]
|
||||
} else if aStyleChange, ok := a.(*subtypes.StyleChangeRecord); ok {
|
||||
bCopy := *aStyleChange
|
||||
|
||||
if bCopy.Flag.MoveTo {
|
||||
bCopy.MoveDeltaX = c.Position.X
|
||||
bCopy.MoveDeltaY = c.Position.Y
|
||||
}
|
||||
|
||||
if flipElements {
|
||||
c.HandleNode(&bCopy)
|
||||
} else {
|
||||
c.HandleNode(aStyleChange)
|
||||
}
|
||||
|
||||
firstElement = firstElement[1:]
|
||||
} else if bStyleChange, ok := b.(*subtypes.StyleChangeRecord); ok {
|
||||
aCopy := *bStyleChange
|
||||
|
||||
if aCopy.Flag.MoveTo {
|
||||
aCopy.MoveDeltaX = c.Position.X
|
||||
aCopy.MoveDeltaY = c.Position.Y
|
||||
}
|
||||
|
||||
if flipElements {
|
||||
c.HandleNode(bStyleChange)
|
||||
} else {
|
||||
c.HandleNode(&aCopy)
|
||||
}
|
||||
|
||||
secondElement = secondElement[1:]
|
||||
} else {
|
||||
//Curve/line records can differ
|
||||
|
||||
if flipElements {
|
||||
c.HandleNode(b)
|
||||
} else {
|
||||
c.HandleNode(a)
|
||||
}
|
||||
|
||||
firstElement = firstElement[1:]
|
||||
secondElement = secondElement[1:]
|
||||
}
|
||||
for _, e := range elements {
|
||||
c.HandleNode(e)
|
||||
}
|
||||
|
||||
c.FlushLayer()
|
||||
|
||||
return c.Commands
|
||||
}
|
||||
|
||||
// ConvertMorph
|
||||
// We step through both the start records and end records, interpolating edges pairwise.
|
||||
// Fill style/line style changes should only appear in the start records.
|
||||
// However, StyleChangeRecord move_to can appear it both start and end records,
|
||||
// and not necessarily in matching pairs; therefore, we have to keep track of the pen position
|
||||
// in case one side is missing a move_to; it will implicitly use the last pen position.
|
||||
func (c *ShapeConverter) ConvertMorph(start, end subtypes.SHAPERECORDS) (startList, endList subtypes.SHAPERECORDS) {
|
||||
if c.Finished {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var startPos, endPos math.Vector2[types.Twip]
|
||||
|
||||
updatePos := func(v math.Vector2[types.Twip], s subtypes.SHAPERECORD) math.Vector2[types.Twip] {
|
||||
switch s := s.(type) {
|
||||
case *subtypes.StraightEdgeRecord:
|
||||
v = v.AddVector(math.NewVector2(s.DeltaX, s.DeltaY))
|
||||
case *subtypes.CurvedEdgeRecord:
|
||||
v = v.AddVector(math.NewVector2(s.ControlDeltaX+s.AnchorDeltaX, s.ControlDeltaY+s.AnchorDeltaY))
|
||||
case *subtypes.StyleChangeRecord:
|
||||
if s.Flag.MoveTo {
|
||||
v = math.NewVector2(s.MoveDeltaX, s.MoveDeltaY)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
for len(start) > 0 {
|
||||
startPtr := start[0]
|
||||
endPtr := end[0]
|
||||
|
||||
if startPtr.RecordType() == endPtr.RecordType() {
|
||||
switch s := startPtr.(type) {
|
||||
case *subtypes.StyleChangeRecord:
|
||||
if s.Flag.MoveTo {
|
||||
startPos = math.NewVector2(s.MoveDeltaX, s.MoveDeltaY)
|
||||
}
|
||||
e := endPtr.(*subtypes.StyleChangeRecord)
|
||||
endRecord := *s
|
||||
endRecord.Flag.MoveTo = e.Flag.MoveTo
|
||||
endRecord.MoveDeltaX = e.MoveDeltaX
|
||||
endRecord.MoveDeltaY = e.MoveDeltaY
|
||||
if e.Flag.MoveTo {
|
||||
endPos = math.NewVector2(e.MoveDeltaX, e.MoveDeltaY)
|
||||
}
|
||||
startList = append(startList, s)
|
||||
endList = append(endList, &endRecord)
|
||||
|
||||
start = start[1:]
|
||||
end = end[1:]
|
||||
default:
|
||||
startList = append(startList, startPtr)
|
||||
endList = append(endList, endPtr)
|
||||
|
||||
start = start[1:]
|
||||
end = end[1:]
|
||||
}
|
||||
} else {
|
||||
if s, ok := startPtr.(*subtypes.StyleChangeRecord); ok {
|
||||
endRecord := *s
|
||||
if s.Flag.MoveTo {
|
||||
startPos = math.NewVector2(s.MoveDeltaX, s.MoveDeltaY)
|
||||
endRecord.MoveDeltaX = endPos.X
|
||||
endRecord.MoveDeltaY = endPos.Y
|
||||
}
|
||||
startList = append(startList, startPtr)
|
||||
endList = append(endList, &endRecord)
|
||||
startPos = updatePos(startPos, startPtr)
|
||||
start = start[1:]
|
||||
} else if e, ok := endPtr.(*subtypes.StyleChangeRecord); ok {
|
||||
startRecord := *e
|
||||
if e.Flag.MoveTo {
|
||||
endPos = math.NewVector2(e.MoveDeltaX, e.MoveDeltaY)
|
||||
startRecord.MoveDeltaX = startPos.X
|
||||
startRecord.MoveDeltaY = startPos.Y
|
||||
}
|
||||
startList = append(startList, &startRecord)
|
||||
endList = append(endList, endPtr)
|
||||
endPos = updatePos(endPos, startPtr)
|
||||
end = end[1:]
|
||||
} else {
|
||||
startList = append(startList, startPtr)
|
||||
endList = append(endList, endPtr)
|
||||
startPos = updatePos(startPos, startPtr)
|
||||
endPos = updatePos(endPos, endPtr)
|
||||
|
||||
start = start[1:]
|
||||
end = end[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(end) > 0 {
|
||||
panic("did not complete")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ShapeConverter) HandleNode(node subtypes.SHAPERECORD) {
|
||||
|
|
|
@ -140,7 +140,7 @@ func FillStyleRecordFromSWF(collection ObjectCollection, fillType swfsubtypes.Fi
|
|||
blurFactor := 1.0
|
||||
//TODO: extend color
|
||||
return &FillStyleRecord{
|
||||
Fill: DrawPathListFillFromSWF(bitmap.GetShapeList(ObjectProperties{}).ApplyFunction(func(p DrawPath) DrawPath {
|
||||
Fill: BitmapFillFromSWF(bitmap.GetShapeList(ObjectProperties{}).ApplyFunction(func(p DrawPath) DrawPath {
|
||||
if fillStyle, ok := p.Style.(*FillStyleRecord); ok {
|
||||
return DrawPathFill(&FillStyleRecord{
|
||||
Fill: fillStyle.Fill,
|
||||
|
@ -176,7 +176,7 @@ func FillStyleRecordFromSWF(collection ObjectCollection, fillType swfsubtypes.Fi
|
|||
}
|
||||
//TODO: extend color
|
||||
return &FillStyleRecord{
|
||||
Fill: DrawPathListFillFromSWF(bitmap.GetShapeList(ObjectProperties{}), bitmapMatrix),
|
||||
Fill: BitmapFillFromSWF(bitmap.GetShapeList(ObjectProperties{}), bitmapMatrix),
|
||||
}
|
||||
//TODO other styles
|
||||
}
|
||||
|
|
74
types/shapes/lerp.go
Normal file
74
types/shapes/lerp.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package shapes
|
||||
|
||||
import (
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
)
|
||||
|
||||
func LerpFillStyle(start, end *FillStyleRecord, ratio float64) *FillStyleRecord {
|
||||
if start == nil || end == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &FillStyleRecord{
|
||||
Fill: LerpFillable(start.Fill, end.Fill, ratio),
|
||||
Border: LerpLineStyle(start.Border, end.Border, ratio),
|
||||
Blur: math.Lerp(start.Blur, end.Blur, ratio),
|
||||
}
|
||||
}
|
||||
|
||||
func LerpLineStyle(start, end *LineStyleRecord, ratio float64) *LineStyleRecord {
|
||||
if start == nil || end == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &LineStyleRecord{
|
||||
Width: math.Lerp(start.Width, end.Width, ratio),
|
||||
Color: math.LerpColor(start.Color, end.Color, ratio),
|
||||
Blur: math.Lerp(start.Blur, end.Blur, ratio),
|
||||
}
|
||||
}
|
||||
|
||||
func LerpFillable(start, end any, ratio float64) any {
|
||||
switch s := start.(type) {
|
||||
case math.Color:
|
||||
return math.LerpColor(s, end.(math.Color), ratio)
|
||||
case Bitmap:
|
||||
return Bitmap{
|
||||
List: s.List,
|
||||
Transform: math.LerpMatrix(s.Transform, end.(Bitmap).Transform, ratio),
|
||||
}
|
||||
case Gradient:
|
||||
return LerpGradient(s, end.(Gradient), ratio)
|
||||
case DrawPathList:
|
||||
return start
|
||||
//TODO: focal gradient
|
||||
default:
|
||||
panic("not supported")
|
||||
}
|
||||
}
|
||||
|
||||
func LerpGradient(start, end Gradient, ratio float64) Gradient {
|
||||
|
||||
startRecords := start.GetItems()
|
||||
endRecords := end.GetItems()
|
||||
|
||||
if len(startRecords) != len(endRecords) {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
records := make([]GradientItem, 0, len(startRecords))
|
||||
for i := range startRecords {
|
||||
records = append(records, GradientItem{
|
||||
Ratio: math.Lerp(startRecords[i].Ratio, endRecords[i].Ratio, ratio),
|
||||
Color: math.LerpColor(startRecords[i].Color, endRecords[i].Color, ratio),
|
||||
})
|
||||
}
|
||||
|
||||
return Gradient{
|
||||
Records: records,
|
||||
Transform: math.LerpMatrix(start.Transform, end.Transform, ratio),
|
||||
SpreadMode: start.SpreadMode,
|
||||
InterpolationMode: start.InterpolationMode,
|
||||
Interpolation: start.Interpolation,
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/tag/subtypes"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/swf/types"
|
||||
"git.gammaspectra.live/WeebDataHoarder/swf2ass-go/types/math"
|
||||
|
@ -23,7 +24,7 @@ func (d *FontDefinition) GetObjectId() uint16 {
|
|||
}
|
||||
|
||||
func (d *FontDefinition) GetShapeList(p shapes.ObjectProperties) (list shapes.DrawPathList) {
|
||||
panic("todo")
|
||||
fmt.Printf("something is trying to place a Font as a character!!!!!!")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue