mirror of
https://github.com/sbrow/ps.git
synced 2025-12-29 18:47:38 -05:00
263 lines
5.9 KiB
Go
263 lines
5.9 KiB
Go
package ps
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Document represents a Photoshop document (PSD file).
|
|
type Document struct {
|
|
name string
|
|
fullName string
|
|
height int
|
|
width int
|
|
artLayers []*ArtLayer
|
|
layerSets []*LayerSet
|
|
}
|
|
|
|
// DocumentJSON is an exported version of Document that
|
|
// allows Documents to be saved to and loaded from JSON.
|
|
type DocumentJSON struct {
|
|
Name string
|
|
FullName string
|
|
Height int
|
|
Width int
|
|
ArtLayers []*ArtLayer
|
|
LayerSets []*LayerSet
|
|
}
|
|
|
|
// MarshalJSON returns the Document in JSON format.
|
|
func (d *Document) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(&DocumentJSON{Name: d.name, Height: d.height,
|
|
Width: d.width, ArtLayers: d.artLayers, LayerSets: d.layerSets})
|
|
}
|
|
|
|
// UnmarshalJSON loads JSON data into this Document.
|
|
func (d *Document) UnmarshalJSON(b []byte) error {
|
|
tmp := &DocumentJSON{}
|
|
if err := json.Unmarshal(b, &tmp); err != nil {
|
|
return err
|
|
}
|
|
d.name = tmp.Name
|
|
d.fullName = tmp.FullName
|
|
d.height = tmp.Height
|
|
d.width = tmp.Width
|
|
d.artLayers = tmp.ArtLayers
|
|
for _, lyr := range d.artLayers {
|
|
lyr.SetParent(d)
|
|
}
|
|
d.layerSets = tmp.LayerSets
|
|
for _, set := range d.layerSets {
|
|
set.SetParent(d)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Name returns the document's title.
|
|
// This fulfills the Group interface.
|
|
func (d *Document) Name() string {
|
|
return d.name
|
|
}
|
|
|
|
// FullName returns the absolute path to the current document file.
|
|
func (d *Document) FullName() string {
|
|
return d.fullName
|
|
}
|
|
|
|
// Parent returns the Group that contains d.
|
|
func (d *Document) Parent() Group {
|
|
return nil
|
|
}
|
|
|
|
// Height returns the height of the document, in pixels.
|
|
func (d *Document) Height() int {
|
|
return d.height
|
|
}
|
|
|
|
// ArtLayer returns the first top level ArtLayer matching
|
|
// the given name.
|
|
func (d *Document) ArtLayer(name string) *ArtLayer {
|
|
for _, lyr := range d.artLayers {
|
|
if lyr.name == name {
|
|
if Mode == 0 && !lyr.current {
|
|
err := lyr.Refresh()
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
}
|
|
return lyr
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ArtLayers returns this document's ArtLayers, if any.
|
|
func (d *Document) ArtLayers() []*ArtLayer {
|
|
return d.artLayers
|
|
}
|
|
|
|
// LayerSets returns all the document's top level LayerSets.
|
|
func (d *Document) LayerSets() []*LayerSet {
|
|
return d.layerSets
|
|
}
|
|
|
|
// LayerSet returns the first top level LayerSet matching
|
|
// the given name.
|
|
func (d *Document) LayerSet(name string) *LayerSet {
|
|
for _, set := range d.layerSets {
|
|
if set.name == name {
|
|
if Mode != Fast && !set.current {
|
|
if err := set.Refresh(); err != nil {
|
|
log.Panic(err)
|
|
}
|
|
}
|
|
return set
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ActiveDocument returns document currently focused in Photoshop.
|
|
//
|
|
// TODO(sbrow): Reduce cyclomatic complexity of ActiveDocument().
|
|
func ActiveDocument() (*Document, error) {
|
|
log.Println("Loading ActiveDocument")
|
|
d := &Document{}
|
|
byt, err := DoJS("activeDocFullName.jsx")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d.fullName = strings.TrimRight(string(byt), "\r\n")
|
|
if Mode != Safe {
|
|
err = d.Restore(d.DumpFile())
|
|
switch {
|
|
case os.IsNotExist(err):
|
|
log.Printf("Previous version not found: \"%s\"\n", d.DumpFile())
|
|
case err == nil:
|
|
return d, err
|
|
default:
|
|
return nil, err
|
|
|
|
}
|
|
}
|
|
log.Println("Loading manually (This could take awhile)")
|
|
byt, err = DoJS("getActiveDoc.jsx")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err = json.Unmarshal(byt, &d); err != nil {
|
|
d.Dump()
|
|
return nil, err
|
|
}
|
|
for _, lyr := range d.artLayers {
|
|
lyr.SetParent(d)
|
|
}
|
|
for i, set := range d.layerSets {
|
|
var s *LayerSet
|
|
if s, err = NewLayerSet(set.Path()+"/", d); err != nil {
|
|
return nil, err
|
|
}
|
|
d.layerSets[i] = s
|
|
s.SetParent(d)
|
|
}
|
|
d.Dump()
|
|
return d, err
|
|
}
|
|
|
|
// Restore loads document data from a JSON file.
|
|
func (d *Document) Restore(path string) error {
|
|
if path == "" {
|
|
path = d.DumpFile()
|
|
}
|
|
byt, err := ioutil.ReadFile(path)
|
|
if err == nil {
|
|
log.Println("Previous version found, loading")
|
|
err = json.Unmarshal(byt, &d)
|
|
if err == nil {
|
|
var byt []byte
|
|
byt, err = DoJS("activeDocFullName.jsx")
|
|
d.fullName = strings.TrimRight(string(byt), "\r\n")
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// SetParent does nothing, as the document is a top-level object
|
|
// and therefore can't have a parent group.
|
|
// The function is needed to implement the group interface.
|
|
func (d *Document) SetParent(g Group) {}
|
|
|
|
// Path returns the root path ("") for all the layers.
|
|
func (d *Document) Path() string {
|
|
return ""
|
|
}
|
|
|
|
// DumpFile returns the path to the json file where
|
|
// this document's data gets dumped. See Document.Dump
|
|
func (d *Document) DumpFile() string {
|
|
usr, err := user.Current()
|
|
if err != nil {
|
|
log.Println(err)
|
|
}
|
|
path := filepath.Join(strings.Replace(d.fullName, "~", usr.HomeDir, 1))
|
|
path = strings.Replace(path, `/`, `\`, -1)
|
|
path = strings.TrimPrefix(path, `\`)
|
|
reg := regexp.MustCompile(`(^)([a-zA-z])(:?\\)`)
|
|
path = reg.ReplaceAllString(path, `$2:\`)
|
|
return strings.Replace(path, ".psd", ".json", 1)
|
|
}
|
|
|
|
// Dump saves the document to disk in JSON format.
|
|
func (d *Document) Dump() {
|
|
log.Println("Dumping to disk")
|
|
log.Println(d.DumpFile())
|
|
defer d.Save()
|
|
f, err := os.Create(d.DumpFile())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer func() {
|
|
if err = f.Close(); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}()
|
|
byt, err := json.MarshalIndent(d, "", "\t")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if _, err = f.Write(byt); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
// MustExist returns a Layer from the set with the given name, and
|
|
// panics if it doesn't exist.
|
|
//
|
|
// If there is a LayerSet and an ArtLayer with the same name,
|
|
// it will return the LayerSet.
|
|
func (d *Document) MustExist(name string) Layer {
|
|
set := d.LayerSet(name)
|
|
if set == nil {
|
|
lyr := d.ArtLayer(name)
|
|
if lyr == nil {
|
|
log.Panicf("no Layer found at \"%s%s\"", d.Path(), name)
|
|
}
|
|
return lyr
|
|
}
|
|
return set
|
|
}
|
|
|
|
// Save saves the Document in place.
|
|
func (d *Document) Save() error {
|
|
js := fmt.Sprintf("var d=app.open(File('%s'));\nd.save();", d.FullName())
|
|
_, err := DoJS("compilejs", js)
|
|
return err
|
|
}
|