1// Aseprite
2// Copyright (C) 2022 Igara Studio S.A.
3// Copyright (C) 2001-2017 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/filename_formatter.h"
13
14#include "app/file/file.h"
15#include "app/file/split_filename.h"
16#include "base/convert_to.h"
17#include "base/fs.h"
18#include "base/replace_string.h"
19
20#include <cstdlib>
21#include <cstring>
22#include <vector>
23
24namespace app {
25
26static bool replace_frame(const char* frameKey, // E.g. = "{frame"
27 int frameBase,
28 std::string& str)
29{
30 size_t i = str.find(frameKey);
31 if (i != std::string::npos) {
32 int keyLen = std::strlen(frameKey);
33
34 size_t j = str.find("}", i+keyLen);
35 if (j != std::string::npos) {
36 std::string from = str.substr(i, j - i + 1);
37 if (frameBase >= 0) {
38 std::vector<char> to(32);
39 int offset = std::strtol(from.c_str()+keyLen, NULL, 10);
40
41 std::sprintf(&to[0], "%0*d", (int(j)-int(i+keyLen)), frameBase + offset);
42 base::replace_string(str, from, &to[0]);
43 }
44 else
45 base::replace_string(str, from, "");
46 }
47 return true;
48 }
49 else
50 return false;
51}
52
53bool get_frame_info_from_filename_format(
54 const std::string& format, int* frameBase, int* width)
55{
56 const char* frameKey = "{frame";
57 size_t i = format.find(frameKey);
58 if (i != std::string::npos) {
59 int keyLen = std::strlen(frameKey);
60
61 size_t j = format.find("}", i+keyLen);
62 if (j != std::string::npos) {
63 std::string frameStr = format.substr(i, j - i + 1);
64
65 if (frameBase)
66 *frameBase = std::strtol(frameStr.c_str()+keyLen, NULL, 10);
67
68 if (width)
69 *width = (int(j) - int(i+keyLen));
70 }
71 return true;
72 }
73 else
74 return false;
75}
76
77bool is_tag_in_filename_format(const std::string& format)
78{
79 return (format.find("{tag}") != std::string::npos);
80}
81
82bool is_layer_in_filename_format(const std::string& format)
83{
84 return (format.find("{layer}") != std::string::npos);
85}
86
87bool is_group_in_filename_format(const std::string& format)
88{
89 return (format.find("{group}") != std::string::npos);
90}
91
92bool is_slice_in_filename_format(const std::string& format)
93{
94 return (format.find("{slice}") != std::string::npos);
95}
96
97std::string filename_formatter(
98 const std::string& format,
99 FilenameInfo& info,
100 const bool replaceFrame)
101{
102 const std::string& filename = info.filename();
103 std::string path = base::get_file_path(filename);
104 if (path.empty())
105 path = ".";
106
107 std::string output = format;
108 base::replace_string(output, "{fullname}", filename);
109 base::replace_string(output, "{path}", path);
110 base::replace_string(output, "{name}", base::get_file_name(filename));
111 base::replace_string(output, "{title}", base::get_file_title(filename));
112 base::replace_string(output, "{extension}", base::get_file_extension(filename));
113 base::replace_string(output, "{layer}", info.layerName());
114 base::replace_string(output, "{group}", info.groupName());
115 base::replace_string(output, "{slice}", info.sliceName());
116
117 if (replaceFrame) {
118 base::replace_string(output, "{tag}", info.innerTagName());
119 base::replace_string(output, "{innertag}", info.innerTagName());
120 base::replace_string(output, "{outertag}", info.outerTagName());
121 base::replace_string(output, "{duration}", std::to_string(info.duration()));
122 replace_frame("{frame", info.frame(), output);
123 replace_frame("{tagframe", info.tagFrame(), output);
124 }
125
126 return output;
127}
128
129std::string get_default_filename_format(
130 std::string& filename,
131 const bool withPath,
132 const bool hasFrames,
133 const bool hasLayer,
134 const bool hasTag)
135{
136 std::string format;
137
138 if (withPath)
139 format += "{path}/";
140
141 format += "{title}";
142
143 if (hasLayer)
144 format += " ({layer})";
145
146 if (hasTag)
147 format += " #{tag}";
148
149 if (hasFrames && is_static_image_format(filename) &&
150 filename.find("{frame") == std::string::npos &&
151 filename.find("{tagframe") == std::string::npos) {
152 const bool autoFrameFromLastDigit =
153 (!hasLayer &&
154 !hasTag);
155
156 // Check if we already have a frame number at the end of the
157 // filename (e.g. output01.png)
158 int frameBase = -1, frameWidth = 0;
159 std::string left, right;
160 if (autoFrameFromLastDigit)
161 frameBase = split_filename(filename, left, right, frameWidth);
162 if (frameBase >= 0) {
163 std::vector<char> buf(32);
164 std::sprintf(&buf[0], "{frame%0*d}", frameWidth, frameBase);
165
166 if (hasLayer || hasTag)
167 format += " ";
168 format += &buf[0];
169
170 // Remove the frame number from the filename part.
171 filename = left;
172 filename += right;
173 }
174 // Check if there is already a {frame} tag in the filename
175 else if (get_frame_info_from_filename_format(filename, &frameBase, &frameWidth)) {
176 // Do nothing
177 }
178 else {
179 if (hasLayer || hasTag)
180 format += " {frame}";
181 else
182 format += "{frame1}";
183 }
184 }
185
186 format += ".{extension}";
187 return format;
188}
189
190std::string get_default_filename_format_for_sheet(
191 const std::string& filename,
192 const bool hasFrames,
193 const bool hasLayer,
194 const bool hasTag)
195{
196 std::string format = "{title}";
197
198 if (hasLayer)
199 format += " ({layer})";
200
201 if (hasTag)
202 format += " #{tag}";
203
204 if (hasFrames) {
205 int frameBase, frameWidth;
206
207 // Check if there is already a {frame} tag in the filename
208 if (get_frame_info_from_filename_format(filename, &frameBase, &frameWidth)) {
209 // Do nothing
210 }
211 else {
212 format += " {frame}";
213 }
214 }
215
216 format += ".{extension}";
217 return format;
218}
219
220} // namespace app
221