Major improvements to UnifiedPlayer: 1. GetFrameImage() now works when paused for responsive UI updates 2. Play() method properly starts FFmpeg process 3. Frame display loop runs continuously for smooth video display 4. Disabled audio temporarily to fix video playback fundamentals 5. Simplified FFmpeg command to focus on video stream only Player now: - Generates video frames correctly - Shows video when paused - Has responsive progress tracking - Starts playback properly Next steps: Re-enable audio playback once video is stable
369 lines
8.9 KiB
Go
369 lines
8.9 KiB
Go
// Copyright 2017 The oksvg Authors. All rights reserved.
|
|
// created: 2/12/2017 by S.R.Wiley
|
|
//
|
|
// utils.go implements translation of an SVG2.0 path into a rasterx Path.
|
|
|
|
package oksvg
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"image/color"
|
|
"log"
|
|
"math"
|
|
"strings"
|
|
|
|
"github.com/srwiley/rasterx"
|
|
)
|
|
|
|
// IconCursor is used while parsing SVG files.
|
|
type IconCursor struct {
|
|
PathCursor
|
|
icon *SvgIcon
|
|
StyleStack []PathStyle
|
|
grad *rasterx.Gradient
|
|
inTitleText, inDescText, inGrad, inDefs, inDefsStyle bool
|
|
currentDef []definition
|
|
}
|
|
|
|
// ReadGradURL reads an SVG format gradient url
|
|
// Since the context of the gradient can affect the colors
|
|
// the current fill or line color is passed in and used in
|
|
// the case of a nil stopClor value
|
|
func (c *IconCursor) ReadGradURL(v string, defaultColor any) (grad rasterx.Gradient, ok bool) {
|
|
if strings.HasPrefix(v, "url(") && strings.HasSuffix(v, ")") {
|
|
urlStr := strings.TrimSpace(v[4 : len(v)-1])
|
|
if strings.HasPrefix(urlStr, "#") {
|
|
var g *rasterx.Gradient
|
|
g, ok = c.icon.Grads[urlStr[1:]]
|
|
if ok {
|
|
grad = localizeGradIfStopClrNil(g, defaultColor)
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// ReadGradAttr reads an SVG gradient attribute
|
|
func (c *IconCursor) ReadGradAttr(attr xml.Attr) (err error) {
|
|
switch attr.Name.Local {
|
|
case "gradientTransform":
|
|
c.grad.Matrix, err = c.parseTransform(attr.Value)
|
|
case "gradientUnits":
|
|
switch strings.TrimSpace(attr.Value) {
|
|
case "userSpaceOnUse":
|
|
c.grad.Units = rasterx.UserSpaceOnUse
|
|
case "objectBoundingBox":
|
|
c.grad.Units = rasterx.ObjectBoundingBox
|
|
}
|
|
case "spreadMethod":
|
|
switch strings.TrimSpace(attr.Value) {
|
|
case "pad":
|
|
c.grad.Spread = rasterx.PadSpread
|
|
case "reflect":
|
|
c.grad.Spread = rasterx.ReflectSpread
|
|
case "repeat":
|
|
c.grad.Spread = rasterx.RepeatSpread
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// PushStyle parses the style element, and push it on the style stack. Only color and opacity are supported
|
|
// for fill. Note that this parses both the contents of a style attribute plus
|
|
// direct fill and opacity attributes.
|
|
func (c *IconCursor) PushStyle(attrs []xml.Attr) error {
|
|
var pairs []string
|
|
className := ""
|
|
for _, attr := range attrs {
|
|
switch strings.ToLower(attr.Name.Local) {
|
|
case "style":
|
|
pairs = append(pairs, strings.Split(attr.Value, ";")...)
|
|
case "class":
|
|
className = attr.Value
|
|
default:
|
|
pairs = append(pairs, attr.Name.Local+":"+attr.Value)
|
|
}
|
|
}
|
|
// Make a copy of the top style
|
|
curStyle := c.StyleStack[len(c.StyleStack)-1]
|
|
for _, pair := range pairs {
|
|
kv := strings.Split(pair, ":")
|
|
if len(kv) >= 2 {
|
|
k := strings.ToLower(kv[0])
|
|
k = strings.TrimSpace(k)
|
|
v := strings.TrimSpace(kv[1])
|
|
err := c.readStyleAttr(&curStyle, k, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
c.adaptClasses(&curStyle, className)
|
|
c.StyleStack = append(c.StyleStack, curStyle) // Push style onto stack
|
|
return nil
|
|
}
|
|
|
|
func (c *IconCursor) readTransformAttr(m1 rasterx.Matrix2D, k string) (rasterx.Matrix2D, error) {
|
|
ln := len(c.points)
|
|
switch k {
|
|
case "rotate":
|
|
if ln == 1 {
|
|
m1 = m1.Rotate(c.points[0] * math.Pi / 180)
|
|
} else if ln == 3 {
|
|
m1 = m1.Translate(c.points[1], c.points[2]).
|
|
Rotate(c.points[0]*math.Pi/180).
|
|
Translate(-c.points[1], -c.points[2])
|
|
} else {
|
|
return m1, errParamMismatch
|
|
}
|
|
case "translate":
|
|
if ln == 1 {
|
|
m1 = m1.Translate(c.points[0], 0)
|
|
} else if ln == 2 {
|
|
m1 = m1.Translate(c.points[0], c.points[1])
|
|
} else {
|
|
return m1, errParamMismatch
|
|
}
|
|
case "skewx":
|
|
if ln == 1 {
|
|
m1 = m1.SkewX(c.points[0] * math.Pi / 180)
|
|
} else {
|
|
return m1, errParamMismatch
|
|
}
|
|
case "skewy":
|
|
if ln == 1 {
|
|
m1 = m1.SkewY(c.points[0] * math.Pi / 180)
|
|
} else {
|
|
return m1, errParamMismatch
|
|
}
|
|
case "scale":
|
|
if ln == 1 {
|
|
m1 = m1.Scale(c.points[0], 0)
|
|
} else if ln == 2 {
|
|
m1 = m1.Scale(c.points[0], c.points[1])
|
|
} else {
|
|
return m1, errParamMismatch
|
|
}
|
|
case "matrix":
|
|
if ln == 6 {
|
|
m1 = m1.Mult(rasterx.Matrix2D{
|
|
A: c.points[0],
|
|
B: c.points[1],
|
|
C: c.points[2],
|
|
D: c.points[3],
|
|
E: c.points[4],
|
|
F: c.points[5],
|
|
})
|
|
} else {
|
|
return m1, errParamMismatch
|
|
}
|
|
default:
|
|
return m1, errParamMismatch
|
|
}
|
|
return m1, nil
|
|
}
|
|
|
|
func (c *IconCursor) parseTransform(v string) (rasterx.Matrix2D, error) {
|
|
ts := strings.Split(v, ")")
|
|
m1 := c.StyleStack[len(c.StyleStack)-1].mAdder.M
|
|
for _, t := range ts {
|
|
t = strings.TrimSpace(t)
|
|
if len(t) == 0 {
|
|
continue
|
|
}
|
|
d := strings.Split(t, "(")
|
|
if len(d) != 2 || len(d[1]) < 1 {
|
|
return m1, errParamMismatch // badly formed transformation
|
|
}
|
|
err := c.GetPoints(d[1])
|
|
if err != nil {
|
|
return m1, err
|
|
}
|
|
m1, err = c.readTransformAttr(m1, strings.ToLower(strings.TrimSpace(d[0])))
|
|
if err != nil {
|
|
return m1, err
|
|
}
|
|
}
|
|
return m1, nil
|
|
}
|
|
|
|
func (c *IconCursor) readStyleAttr(curStyle *PathStyle, k, v string) error {
|
|
switch k {
|
|
case "fill":
|
|
gradient, ok := c.ReadGradURL(v, curStyle.fillerColor)
|
|
if ok {
|
|
curStyle.fillerColor = gradient
|
|
break
|
|
}
|
|
var err error
|
|
curStyle.fillerColor, err = ParseSVGColor(v)
|
|
return err
|
|
case "stroke":
|
|
gradient, ok := c.ReadGradURL(v, curStyle.linerColor)
|
|
if ok {
|
|
curStyle.linerColor = gradient
|
|
break
|
|
}
|
|
col, errc := ParseSVGColor(v)
|
|
if errc != nil {
|
|
return errc
|
|
}
|
|
if col != nil {
|
|
curStyle.linerColor = col.(color.NRGBA)
|
|
} else {
|
|
curStyle.linerColor = nil
|
|
}
|
|
case "stroke-linegap":
|
|
switch v {
|
|
case "flat":
|
|
curStyle.LineGap = rasterx.FlatGap
|
|
case "round":
|
|
curStyle.LineGap = rasterx.RoundGap
|
|
case "cubic":
|
|
curStyle.LineGap = rasterx.CubicGap
|
|
case "quadratic":
|
|
curStyle.LineGap = rasterx.QuadraticGap
|
|
}
|
|
case "stroke-leadlinecap":
|
|
switch v {
|
|
case "butt":
|
|
curStyle.LeadLineCap = rasterx.ButtCap
|
|
case "round":
|
|
curStyle.LeadLineCap = rasterx.RoundCap
|
|
case "square":
|
|
curStyle.LeadLineCap = rasterx.SquareCap
|
|
case "cubic":
|
|
curStyle.LeadLineCap = rasterx.CubicCap
|
|
case "quadratic":
|
|
curStyle.LeadLineCap = rasterx.QuadraticCap
|
|
}
|
|
case "stroke-linecap":
|
|
switch v {
|
|
case "butt":
|
|
curStyle.LineCap = rasterx.ButtCap
|
|
case "round":
|
|
curStyle.LineCap = rasterx.RoundCap
|
|
case "square":
|
|
curStyle.LineCap = rasterx.SquareCap
|
|
case "cubic":
|
|
curStyle.LineCap = rasterx.CubicCap
|
|
case "quadratic":
|
|
curStyle.LineCap = rasterx.QuadraticCap
|
|
}
|
|
case "stroke-linejoin":
|
|
switch v {
|
|
case "miter":
|
|
curStyle.LineJoin = rasterx.Miter
|
|
case "miter-clip":
|
|
curStyle.LineJoin = rasterx.MiterClip
|
|
case "arc-clip":
|
|
curStyle.LineJoin = rasterx.ArcClip
|
|
case "round":
|
|
curStyle.LineJoin = rasterx.Round
|
|
case "arc":
|
|
curStyle.LineJoin = rasterx.Arc
|
|
case "bevel":
|
|
curStyle.LineJoin = rasterx.Bevel
|
|
}
|
|
case "stroke-miterlimit":
|
|
mLimit, err := parseFloat(v, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
curStyle.MiterLimit = mLimit
|
|
case "stroke-width":
|
|
width, err := parseFloat(v, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
curStyle.LineWidth = width
|
|
case "stroke-dashoffset":
|
|
dashOffset, err := parseFloat(v, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
curStyle.DashOffset = dashOffset
|
|
case "stroke-dasharray":
|
|
if v != "none" {
|
|
dashes := splitOnCommaOrSpace(v)
|
|
dList := make([]float64, len(dashes))
|
|
for i, dstr := range dashes {
|
|
d, err := parseFloat(strings.TrimSpace(dstr), 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dList[i] = d
|
|
}
|
|
curStyle.Dash = dList
|
|
break
|
|
}
|
|
case "opacity", "stroke-opacity", "fill-opacity":
|
|
op, err := parseFloat(v, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if k != "stroke-opacity" {
|
|
curStyle.FillOpacity *= op
|
|
}
|
|
if k != "fill-opacity" {
|
|
curStyle.LineOpacity *= op
|
|
}
|
|
case "transform":
|
|
m, err := c.parseTransform(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
curStyle.mAdder.M = m
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *IconCursor) readStartElement(se xml.StartElement) (err error) {
|
|
skipDef := se.Name.Local == "radialGradient" || se.Name.Local == "linearGradient" || c.inGrad
|
|
if c.inDefs && !skipDef {
|
|
ID := ""
|
|
for _, attr := range se.Attr {
|
|
if attr.Name.Local == "id" {
|
|
ID = attr.Value
|
|
}
|
|
}
|
|
if ID != "" && len(c.currentDef) > 0 {
|
|
c.icon.Defs[c.currentDef[0].ID] = c.currentDef
|
|
}
|
|
c.currentDef = append(c.currentDef, definition{
|
|
ID: ID,
|
|
Tag: se.Name.Local,
|
|
Attrs: se.Attr,
|
|
})
|
|
return nil
|
|
}
|
|
err = draw(se.Name.Local, c, se.Attr)
|
|
if err != nil {
|
|
if c.ErrorMode == StrictErrorMode {
|
|
return err
|
|
} else if c.ErrorMode == WarnErrorMode {
|
|
log.Println(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if len(c.Path) > 0 {
|
|
// The cursor parsed a path from the xml element
|
|
pathCopy := make(rasterx.Path, len(c.Path))
|
|
copy(pathCopy, c.Path)
|
|
c.icon.SVGPaths = append(c.icon.SVGPaths,
|
|
SvgPath{c.StyleStack[len(c.StyleStack)-1], pathCopy})
|
|
c.Path = c.Path[:0]
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *IconCursor) adaptClasses(pathStyle *PathStyle, className string) {
|
|
if className == "" || len(c.icon.classes) == 0 {
|
|
return
|
|
}
|
|
for k, v := range c.icon.classes[className] {
|
|
c.readStyleAttr(pathStyle, k, v)
|
|
}
|
|
}
|