1// Aseprite
2// Copyright (C) 2019-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 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/app.h"
13#include "app/commands/command.h"
14#include "app/console.h"
15#include "app/context.h"
16#include "app/file_selector.h"
17#include "app/modules/editors.h"
18#include "app/pref/preferences.h"
19#include "app/ui/drop_down_button.h"
20#include "app/ui/editor/editor.h"
21#include "app/ui/font_popup.h"
22#include "app/ui/timeline/timeline.h"
23#include "app/util/freetype_utils.h"
24#include "base/fs.h"
25#include "base/string.h"
26#include "doc/image.h"
27#include "doc/image_ref.h"
28#include "render/dithering.h"
29#include "render/ordered_dither.h"
30#include "render/quantization.h"
31#include "ui/manager.h"
32
33#include "paste_text.xml.h"
34
35namespace app {
36
37static std::string last_text_used;
38
39class PasteTextCommand : public Command {
40public:
41 PasteTextCommand();
42
43protected:
44 bool onEnabled(Context* ctx) override;
45 void onExecute(Context* ctx) override;
46};
47
48PasteTextCommand::PasteTextCommand()
49 : Command(CommandId::PasteText(), CmdUIOnlyFlag)
50{
51}
52
53bool PasteTextCommand::onEnabled(Context* ctx)
54{
55 return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
56 ContextFlags::ActiveLayerIsEditable);
57}
58
59class PasteTextWindow : public app::gen::PasteText {
60public:
61 PasteTextWindow(const std::string& face, int size,
62 bool antialias,
63 const app::Color& color)
64 : m_face(face) {
65 ok()->setEnabled(!m_face.empty());
66 if (!m_face.empty())
67 updateFontFaceButton();
68
69 fontSize()->setTextf("%d", size);
70 fontFace()->Click.connect([this]{ onSelectFontFile(); });
71 fontFace()->DropDownClick.connect([this]{ onSelectSystemFont(); });
72 fontColor()->setColor(color);
73 this->antialias()->setSelected(antialias);
74 }
75
76 std::string faceValue() const {
77 return m_face;
78 }
79
80 int sizeValue() const {
81 int size = fontSize()->textInt();
82 size = std::clamp(size, 1, 5000);
83 return size;
84 }
85
86private:
87 void updateFontFaceButton() {
88 fontFace()->mainButton()
89 ->setTextf("Select Font: %s",
90 base::get_file_title(m_face).c_str());
91 }
92
93 void onSelectFontFile() {
94 base::paths exts = { "ttf", "ttc", "otf", "dfont" };
95 base::paths face;
96 if (!show_file_selector(
97 "Select a TrueType Font",
98 m_face, exts,
99 FileSelectorType::Open, face))
100 return;
101
102 ASSERT(!face.empty());
103 setFontFace(face.front());
104 }
105
106 void setFontFace(const std::string& face) {
107 m_face = face;
108 ok()->setEnabled(true);
109 updateFontFaceButton();
110 }
111
112 void onSelectSystemFont() {
113 if (!m_fontPopup) {
114 try {
115 m_fontPopup.reset(new FontPopup());
116 m_fontPopup->Load.connect(&PasteTextWindow::setFontFace, this);
117 m_fontPopup->Close.connect([this]{ onCloseFontPopup(); });
118 }
119 catch (const std::exception& ex) {
120 Console::showException(ex);
121 return;
122 }
123 }
124
125 if (!m_fontPopup->isVisible()) {
126 m_fontPopup->showPopup(display(), fontFace()->bounds());
127 }
128 else {
129 m_fontPopup->closeWindow(NULL);
130 }
131 }
132
133 void onCloseFontPopup() {
134 fontFace()->dropDown()->requestFocus();
135 }
136
137 std::string m_face;
138 std::unique_ptr<FontPopup> m_fontPopup;
139};
140
141void PasteTextCommand::onExecute(Context* ctx)
142{
143 Editor* editor = current_editor;
144 if (editor == NULL)
145 return;
146
147 Preferences& pref = Preferences::instance();
148 PasteTextWindow window(pref.textTool.fontFace(),
149 pref.textTool.fontSize(),
150 pref.textTool.antialias(),
151 pref.colorBar.fgColor());
152
153 window.userText()->setText(last_text_used);
154
155 window.openWindowInForeground();
156 if (window.closer() != window.ok())
157 return;
158
159 last_text_used = window.userText()->text();
160
161 bool antialias = window.antialias()->isSelected();
162 std::string faceName = window.faceValue();
163 int size = window.sizeValue();
164 size = std::clamp(size, 1, 999);
165 pref.textTool.fontFace(faceName);
166 pref.textTool.fontSize(size);
167 pref.textTool.antialias(antialias);
168
169 try {
170 std::string text = window.userText()->text();
171 app::Color appColor = window.fontColor()->getColor();
172 doc::color_t color = doc::rgba(appColor.getRed(),
173 appColor.getGreen(),
174 appColor.getBlue(),
175 appColor.getAlpha());
176
177 doc::ImageRef image(render_text(faceName, size, text, color, antialias));
178 if (image) {
179 Sprite* sprite = editor->sprite();
180 if (image->pixelFormat() != sprite->pixelFormat()) {
181 RgbMap* rgbmap = sprite->rgbMap(editor->frame());
182 image.reset(
183 render::convert_pixel_format(
184 image.get(), NULL, sprite->pixelFormat(),
185 render::Dithering(),
186 rgbmap, sprite->palette(editor->frame()),
187 false,
188 sprite->transparentColor()));
189 }
190
191 // TODO we don't support pasting text in multiple cels at the
192 // moment, so we clear the range here (same as in
193 // clipboard::paste())
194 if (auto timeline = App::instance()->timeline())
195 timeline->clearAndInvalidateRange();
196
197 editor->pasteImage(image.get());
198 }
199 }
200 catch (const std::exception& ex) {
201 Console::showException(ex);
202 }
203}
204
205Command* CommandFactory::createPasteTextCommand()
206{
207 return new PasteTextCommand;
208}
209
210} // namespace app
211