From 9cbf0e9b92cd2a3186ce92f112227cef7e808284 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 8 Mar 2018 11:38:00 -0500 Subject: [PATCH] Added functionality lib - Save - DoAction - ApplyDataset - GetLayer(s) main - do actions from commandline Improved test cases! --- cmd/ps/main.go | 27 ++++++++++++ ps.go | 90 ++++++++++++++++++++++++++++--------- ps_test.go | 95 ++++++++++++++++++++++++++++++++++------ scripts/action.vbs | 10 +++++ scripts/applyDataset.jsx | 15 +++++++ scripts/getLayers.jsx | 32 ++++++++++++++ scripts/save.vbs | 12 +++++ scripts/skirmish.jsx | 72 ++++++++++++++++++++++++++++++ structs.go | 20 +++++++++ 9 files changed, 339 insertions(+), 34 deletions(-) create mode 100644 cmd/ps/main.go create mode 100644 scripts/action.vbs create mode 100644 scripts/applyDataset.jsx create mode 100644 scripts/getLayers.jsx create mode 100644 scripts/save.vbs create mode 100644 scripts/skirmish.jsx create mode 100644 structs.go diff --git a/cmd/ps/main.go b/cmd/ps/main.go new file mode 100644 index 0000000..b1d0fa9 --- /dev/null +++ b/cmd/ps/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/sbrow/ps" + "os" +) + +func main() { + args := []string{} + cmd := "" + switch { + case len(os.Args) > 1: + args = os.Args[2:] + fallthrough + case len(os.Args) > 0: + cmd = os.Args[1] + } + + fmt.Println(os.Args, cmd, args) + if cmd == "action" { + err := ps.DoAction(args[0], args[1]) + if err != nil { + panic(err) + } + } +} diff --git a/ps.go b/ps.go index e0bd288..4db93a1 100644 --- a/ps.go +++ b/ps.go @@ -1,11 +1,14 @@ -// Package ps creates an interface between Adobe Photoshop (CS5) and go. -// This is primarily done by calling VBS/Applescript files. +// Package ps is a rudimentary API between Adobe Photoshop and go. // -// Currently only works on windows. +// Most of the interaction between the two is implemented via +// javascript and/or VBS/Applescript. +// +// Currently only works with CS5 on Windows. package ps import ( "bytes" + "encoding/json" "errors" "fmt" "io/ioutil" @@ -32,25 +35,25 @@ func init() { } } -// Open photoshop. +// Start opens Photoshop. func Start() error { _, err := run("start") return err } -// Open a file. -func Open(path string) error { - _, err := run("open", path) - return err -} - -// Close the active document. +// Close closes the active document. func Close() error { _, err := run("close") return err } -// Quits photoshop. +// Open opens a file with the specified path. +func Open(path string) error { + _, err := run("open", path) + return err +} + +// Quit exits Photoshop. // // There are 3 valid values for save: 1 (psSaveChanges), 2 (psDoNotSaveChanges), // 3 (psPromptToSaveChanges). @@ -59,9 +62,14 @@ func Quit(save int) error { return err } -func Js(path string, args ...string) ([]byte, error) { +// DoJs runs a Photoshop javascript script file (.jsx) from the specified location. +// It can't directly return output, so instead the scripts write their output to +// a temporary file. +func DoJs(path string, args ...string) ([]byte, error) { // Temp file for js to output to. outpath := filepath.Join(os.Getenv("TEMP"), "js_out.txt") + defer os.Remove(outpath) + args = append([]string{outpath}, args...) // If passed a script by name, assume it's in the default folder. @@ -76,28 +84,30 @@ func Js(path string, args ...string) ([]byte, error) { } file, err := ioutil.ReadFile(outpath) if err != nil { - // fmt.Println(cmd) return cmd, err } cmd = append(cmd, file...) - // os.Remove(outpath) return cmd, err } -// Wait provides the user a message, and halts operation until the user +// Wait prints a message to the console and halts operation until the user // signals that they are ready (by pushing enter). // // Useful for when you need to do something by hand in the middle of an -// automated process. +// otherwise automated process. func Wait(msg string) { + fmt.Println() fmt.Print(msg) var input string fmt.Scanln(&input) + fmt.Println() } +// run handles running the script files, returning output, and displaying errors. func run(name string, args ...string) ([]byte, error) { var ext string var out bytes.Buffer + var errs bytes.Buffer switch runtime.GOOS { case "windows": @@ -117,14 +127,52 @@ func run(name string, args ...string) ([]byte, error) { } else { args = append([]string{Opts, filepath.Join(pkgpath, "scripts", name)}, args...) } - cmd := exec.Command(Cmd, args...) cmd.Stdout = &out - cmd.Stderr = &out + cmd.Stderr = &errs err := cmd.Run() if err != nil { - return []byte{}, errors.New(string(out.Bytes())) - } else { return out.Bytes(), err + // return append(out.Bytes(), errs.Bytes()...), err } + if len(errs.Bytes()) != 0 { + return out.Bytes(), errors.New(string(errs.Bytes())) + } + return out.Bytes(), nil +} + +// DoAction runs a Photoshop action with name from set. +func DoAction(set, name string) error { + _, err := run("action", set, name) + return err +} + +// SaveAs saves the Photoshop document file to the given location. +func SaveAs(path string) error { + _, err := run("save", path) + return err +} + +// Layers returns an array of ArtLayers from the active document +// based on the given path string. +func Layers(path string) ([]ArtLayer, error) { + byt, err := DoJs("getLayers.jsx", path) + var out []ArtLayer + err = json.Unmarshal(byt, &out) + if err != nil { + return []ArtLayer{}, err + } + return out, err +} + +// Layer returns an ArtLayer from the active document given a specified +// path string. Layer calls Layers() and returns the first result. +func Layer(path string) (ArtLayer, error) { + lyrs, err := Layers(path) + return lyrs[0], err +} + +// ApplyDataset fills out a template file with information from a given dataset (csv) file. +func ApplyDataset(name string) ([]byte, error) { + return DoJs("applyDataset.jsx", name) } diff --git a/ps_test.go b/ps_test.go index e6ca08b..8bcae3c 100644 --- a/ps_test.go +++ b/ps_test.go @@ -2,6 +2,7 @@ package ps import ( "fmt" + // "log" "os" "path/filepath" _ "strings" @@ -15,16 +16,44 @@ func TestPkgPath(t *testing.T) { } } +func TestStart(t *testing.T) { + Start() +} + func TestOpen(t *testing.T) { if testing.Short() { t.Skip("Skipping \"TestOpen\"") } - _, err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") if err != nil { t.Fatal(err) } } +func TestClose(t *testing.T) { + Close() +} + +func TestQuit(t *testing.T) { + if testing.Short() { + t.Skip("Skipping \"TestQuit\"") + } + Quit(2) +} + +func TestDoJs(t *testing.T) { + out := []byte("F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n") + script := "test.jsx" + ret, err := DoJs(script, "arg", "args") + if err != nil { + t.Fatal(err) + } + if string(ret) != string(out) { + fail := fmt.Sprintf("TestJS failed.\ngot:\t\"%s\"\nwant:\t\"%s\"", ret, out) + t.Fatal(fail) + } +} + func TestRun(t *testing.T) { out := []byte("hello,\r\nworld!\r\n") msg, err := run("test", "hello,", "world!") @@ -37,22 +66,62 @@ func TestRun(t *testing.T) { } } -func TestQuit(t *testing.T) { - if testing.Short() { - t.Skip("Skipping \"TestQuit\"") - } - Quit(2) -} - func TestWait(t *testing.T) { Wait("Waiting...") - fmt.Println() } -func TestJS(t *testing.T) { - out := []byte("F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n") - script := "test.jsx" - ret, err := Js(script, "arg", "args") +func TestDoAction_Crop(t *testing.T) { + err := Open("F:\\GitLab\\dreamkeepers-psd\\Template009.1.psd") + if err != nil { + t.Fatal(err) + } + err = DoAction("DK", "Crop") + if err != nil { + t.Fatal(err) + } +} + +func TestDoAction_Undo(t *testing.T) { + err := DoAction("DK", "Undo") + if err != nil { + t.Fatal(err) + } +} + +func TestSaveAs(t *testing.T) { + err := SaveAs("F:\\TEMP\\test.png") + if err != nil { + t.Fatal(err) + } +} + +func TestLayers(t *testing.T) { + byt, err := Layers("Areas/TitleBackground") + // _, err := Layers("Areas/TitleBackground") + if err != nil { + t.Fatal(err) + } + for _, lyr := range byt { + fmt.Println(lyr.Name) + fmt.Println(lyr.Bounds) + } + +} + +func TestLayer(t *testing.T) { + // lyr, err := Layer("Areas/TitleBackground") + _, err := Layer("Areas/TitleBackground") + if err != nil { + t.Fatal(err) + } + /* fmt.Println(lyr.Name) + fmt.Println(lyr.Bounds) + */ +} + +func TestApplyDataset(t *testing.T) { + out := []byte("done!\r\n") + ret, err := ApplyDataset("Anger") if err != nil { t.Fatal(err) } diff --git a/scripts/action.vbs b/scripts/action.vbs new file mode 100644 index 0000000..231e2ce --- /dev/null +++ b/scripts/action.vbs @@ -0,0 +1,10 @@ +set appRef = CreateObject("Photoshop.Application") +' No dialogs' +dlgMode = 3 + +set desc = CreateObject( "Photoshop.ActionDescriptor" ) +set ref = CreateObject( "Photoshop.ActionReference" ) +Call ref.PutName(appRef.CharIDToTypeID("Actn"), wScript.Arguments(1)) +Call ref.PutName(appRef.CharIDToTypeID("ASet"), wScript.Arguments(0)) +Call desc.PutReference(appRef.CharIDToTypeID("null"), ref) +Call appRef.ExecuteAction(appRef.CharIDToTypeID("Ply "), desc, dlgMode) \ No newline at end of file diff --git a/scripts/applyDataset.jsx b/scripts/applyDataset.jsx new file mode 100644 index 0000000..2e35e13 --- /dev/null +++ b/scripts/applyDataset.jsx @@ -0,0 +1,15 @@ +var saveFile = File(arguments[0]); +if(saveFile.exists) + saveFile.remove(); +var idAply = charIDToTypeID("Aply"); +var desc1 = new ActionDescriptor(); +var idnull = charIDToTypeID("null"); +var ref1 = new ActionReference(); +var iddataSetClass = stringIDToTypeID("dataSetClass"); +ref1.putName(iddataSetClass, arguments[1]); +desc1.putReference(idnull, ref1); +executeAction(idAply, desc1, DialogModes.NO); +saveFile.encoding = "UTF8"; +saveFile.open("e", "TEXT", "????"); +saveFile.writeln("done!"); +saveFile.close(); \ No newline at end of file diff --git a/scripts/getLayers.jsx b/scripts/getLayers.jsx new file mode 100644 index 0000000..b58a834 --- /dev/null +++ b/scripts/getLayers.jsx @@ -0,0 +1,32 @@ +//arguments = [ 'F:\\TEMP\\js_out.txt\r\narg\r\nargs\r\n', 'Areas/TitleBackground'] +var saveFile = File(arguments[0]) +if(saveFile.exists) + saveFile.remove() +saveFile.encoding = "UTF8" +saveFile.open("e", "TEXT", "????") +try { + var doc = app.activeDocument + var splitPath = arguments[1].split('/') + var bottomLayerSet = doc.layerSets.getByName(splitPath[0]) + for (var i = 1; i < splitPath.length; i++) { + try {bottomLayerSet = bottomLayerSet.layerSets.getByName(splitPath[i])} + catch (e) {bottomLayerSet = { layers: [bottomLayerSet.layers.getByName(splitPath[i])] }} + } + saveFile.writeln('['); + for (var l = 0; l < bottomLayerSet.layers.length; l++) { + var lyr = bottomLayerSet.layers[l] + saveFile.write('{"Name":"' + lyr.name + '", "Bounds": [["' + lyr.bounds[0] + '","' + + lyr.bounds[1] + '","' + lyr.bounds[2] + '"],["' + lyr.bounds[3] + '"]]}'); + if (l != bottomLayerSet.layers.length - 1) + saveFile.write(','); + saveFile.writeln(); + + } + saveFile.writeln(']'); +} catch (e) { + if (e.message.indexOf('User') == -1) + alert('ERROR: ' + e.message + ' at ' + e.fileName + ':' + e.line); + else + throw new Exception('User cancelled the operation'); +} +saveFile.close(); \ No newline at end of file diff --git a/scripts/save.vbs b/scripts/save.vbs new file mode 100644 index 0000000..e23f1ac --- /dev/null +++ b/scripts/save.vbs @@ -0,0 +1,12 @@ +Set appRef = CreateObject("Photoshop.Application") +dlgMode = 3 'No dialog +set d = CreateObject( "Photoshop.ActionDescriptor" ) +Call d.PutEnumerated(appRef.CharIDToTypeID("PGIT"), appRef.CharIDToTypeID("PGIT"), appRef.CharIDToTypeID("PGIN")) +Call d.PutEnumerated(appRef.CharIDToTypeID("PNGf"), appRef.CharIDToTypeID("PNGf"), appRef.CharIDToTypeID("PGAd")) + +SET desc = CreateObject( "Photoshop.ActionDescriptor" ) +Call desc.PutObject( appRef.CharIDToTypeID("As "), appRef.CharIDToTypeID("PNGF"), d) +Call desc.PutPath( appRef.CharIDToTypeID("In "), wScript.Arguments(0)) +Call desc.PutBoolean( appRef.CharIDToTypeID("Cpy "), True ) + +Call appRef.ExecuteAction(appRef.CharIDToTypeID("save"), desc, dlgMode) \ No newline at end of file diff --git a/scripts/skirmish.jsx b/scripts/skirmish.jsx new file mode 100644 index 0000000..2f4b643 --- /dev/null +++ b/scripts/skirmish.jsx @@ -0,0 +1,72 @@ +function setTitle(title) { + var nameLayer = this.textLayers.getByName('name'); + var found = false; + for (var i = 0; i < this.titleBackgrounds.length; i++) { + if (!found && (nameLayer.bounds[2] + this.tolerance.title) < this.titleBackgrounds[i].bounds[2]) { + this.log.log('"{0}" is long enough'.format(this.titleBackgrounds[i].name), '-'); + this.titleBackgrounds[i].visible = true; + found = true; + } else { + this.log.log('"{0}" is too short'.format(this.titleBackgrounds[i].name),'-') + this.titleBackgrounds[i].visible = false; + } + } + +} + +function main() { + setTitle() + if ((this.Type).indexOf("Channel") != -2) { + this.changeColor(this.resolveBanner.normal, this.colors.Rarity); + } else { + this.changeColor(this.resolveBanner.normal, [128, 128, 128]); + } + formatText() +} + +DeckCardPSD.prototype.formatText = function() { + var speed = this.textLayers.getByName('speed'); + if (speed.visible) { + this.changeStroke(speed, (speed.textItem.contents == 1) ? [128, 128, 128] : [255, 255, 255], + this.colors.banner) + } + /** + * The lowest we allow a text layer to go. + * @type {int} + */ + var bottom = this.doc.height-this.tolerance.flavor_text + + // Get our text layers. + var short_text = this.setTextLayer('short_text', undefined, null, 'Arial', 'Regular',[this.bold_words, "Bold"]); + var long_text = this.textLayers.getByName('long_text'); + var flavor_text = this.textLayers.getByName('flavor_text'); + + // Position the layers. + positionLayer(this.short_textBackground, this.short_textBackground.bounds[0], short_text.bounds[3] + this.tolerance.short_text, 'bottom'); + positionLayer(long_text, long_text.bounds[0], this.short_textBackground.bounds[3] + this.tolerance.long_text, 'top'); + positionLayer(flavor_text, flavor_text.bounds[0], bottom, 'bottom'); + + /** + * Make our layers visible + * @todo hack, fix. + */ + short_text.visible = short_text.textItem.contents != "“"; + long_text.visible = long_text.textItem.contents != "“"; + flavor_text.visible = flavor_text.textItem.contents != "“"; + + + + //Hide long_text if too long. + if (long_text.bounds[3] > this.doc.height - bottom) { + long_text.visible == false; + } + + this.log.debug(short_text.bounds) + this.log.debug(long_text.bounds) + this.log.debug(flavor_text.bounds) + //Hide flavor text if too long. + if ( (long_text.visible && flavor_text.bounds[1] < long_text.bounds[3]) + || (short_text.visible && flavor_text.bounds[1] < short_text.bounds[3])) { + flavor_text.visible = false; + } +}; \ No newline at end of file diff --git a/structs.go b/structs.go new file mode 100644 index 0000000..9d38402 --- /dev/null +++ b/structs.go @@ -0,0 +1,20 @@ +package ps + +// type layer interface { +// Name() string +// TextItem() []string +// } + +type ArtLayer struct { + Name string + TextItem string + Bounds [2][2]string +} + +// func (a *ArtLayer) Name() string { +// return a.name +// } + +// func (a *ArtLayer) TextItem() string { +// return a.textItem +// }