Use Georgia, but fall back to FreeSerif if there's a glyph we can't find.
[ccbs] / bigscreen / ccbs_bigscreen.cpp
1 #include <cstdio>
2 #include <cstring>
3 #include <iconv.h>
4 #include <unistd.h>
5 #include <pqxx/pqxx>
6 #include <ft2build.h>
7 #include FT_FREETYPE_H
8 #include <tinyptc.h>
9
10 iconv_t ucs4_iconv;
11
12 // UCS-4 string with support for getting from UTF-8
13 class widestring : public std::wstring
14 {
15 public:
16         void operator= (const char *from)
17         {
18                 unsigned bytes = std::strlen(from);
19                 char *from_buf = strdup(from);
20                 wchar_t *to_buf = new wchar_t[bytes + 1];
21
22                 char *inptr = from_buf, *outptr = reinterpret_cast<char *> (to_buf);
23
24                 size_t in_left = bytes;
25                 size_t out_left = bytes * sizeof(wchar_t);
26
27                 size_t ret = iconv(ucs4_iconv, NULL, NULL, &outptr, &out_left);
28                 if (ret == (size_t)(-1)) {
29                         throw std::runtime_error("Error in iconv during initialization");
30                 }
31
32                 ret = iconv(ucs4_iconv, &inptr, &in_left, &outptr, &out_left);
33                 if (ret == (size_t)(-1)) {
34                         perror("iconv");
35                         throw std::runtime_error("Error in iconv during conversion");
36                 }
37
38                 erase(begin(), end());
39                 std::copy(to_buf, reinterpret_cast<wchar_t *> (outptr), std::back_inserter(*this));
40
41                 free(from_buf);
42                 delete[] to_buf;
43         }
44 };
45
46 template<>
47 void pqxx::from_string<widestring>(const char *from, widestring &to)
48 {
49         to = from;
50 }
51
52 int my_draw_text(const widestring &str, unsigned char *buf, int xpos, int ypos, bool real_render, int r, int g, int b, FT_Face face, FT_Face symbolface);
53
54 class Tournament {
55 public:
56         int id;
57         widestring name;
58 };
59
60 Tournament active_tournament;
61 FT_Face font, symbolfont;
62
63 /* A trigger that sets a flag whenever it's trigged. */
64 class FlagTrigger : pqxx::trigger {
65 private:
66         bool flag;
67         
68 public:
69         FlagTrigger(pqxx::connection_base &conn, const PGSTD::string &name)
70                 : pqxx::trigger(conn, name), flag(false) {}
71         virtual ~FlagTrigger() throw () {}
72         
73         virtual void operator() (int pid)
74         {
75                 flag = true;
76                 std::fprintf(stderr, "Received a flag trigger from pid %u\n", pid);
77         }
78
79         bool get_flag() const
80         {
81                 return flag;
82         }
83
84         void reset_flag()
85         {
86                 flag = false;
87         }
88 };
89
90 /* A transactor that fetches the current tournament and some information about it. */
91 class FetchCurrentTournament : public pqxx::transactor<> {
92 private:
93         Tournament *tourn;
94
95 public:
96         FetchCurrentTournament(Tournament *tourn) : tourn(tourn) {}
97         void operator() (pqxx::transaction<> &t)
98         {
99                 pqxx::result res( t.exec("SELECT * FROM bigscreen.active_tournament NATURAL JOIN tournaments") );
100                 try {
101                         pqxx::result::tuple tournament = res.at(0);
102
103                         tourn->id = tournament["tournament"].as(tourn->id);
104                         tourn->name = tournament["tournamentname"].as(tourn->name);
105                 } catch (PGSTD::out_of_range &e) {
106                         tourn->id = -1;
107                         tourn->name = "";
108                 }
109         }
110 };
111
112 void init(pqxx::connection &conn)
113 {
114         conn.perform(FetchCurrentTournament(&active_tournament));
115
116         if (active_tournament.id == -1) {
117                 std::fprintf(stderr, "No active tournament\n");
118         } else {
119                 std::fprintf(stderr, "Current tournament is %d (name: '%s')\n",
120                         active_tournament.id, active_tournament.name.c_str());
121         }
122 }
123
124 unsigned char framebuf[800 * 600 * 4];
125
126 void main_loop(pqxx::connection &conn)
127 {
128         if (active_tournament.id == -1) {
129                 // No active tournament, sleep a second or so and exit
130                 sleep(1);
131                 return;
132         }
133
134         memset(framebuf, 0, 800*600*4);
135         
136         pqxx::work t(conn, "trx");
137
138         // fetch all songs
139         pqxx::result res( t.exec("SELECT * FROM songs WHERE title LIKE 'M%'") );
140         unsigned y = 0;
141         for (pqxx::result::const_iterator i = res.begin(); i != res.end(); ++i) {
142                 my_draw_text(i["title"].as(widestring()), framebuf, 0, y, 1, 255, 255, 255, font, symbolfont);
143                 y += 20;
144 //              std::fprintf(stderr, "%s\n", i["title"].c_str());
145         }
146         t.commit();
147
148         ptc_update(framebuf);
149         sleep(1);
150 }
151
152 void init_freetype()
153 {
154         FT_Library library;
155         if (FT_Init_FreeType(&library))
156                 throw std::logic_error("FreeType init failed.");
157         if (FT_New_Face(library, "/usr/share/fonts/truetype/msttcorefonts/Georgia.ttf", 0, &font))
158                 throw std::logic_error("Face opening failed.");
159         if (FT_New_Face(library, "/usr/share/fonts/truetype/freefont/FreeSerif.ttf", 0, &symbolfont))
160                 throw std::logic_error("Face opening failed.");
161         if (FT_Set_Char_Size(font, 0, 12 * 64, 96, 96))
162                 throw std::logic_error("Size set failed.");
163         if (FT_Set_Char_Size(symbolfont, 0, 12 * 64, 96, 96))
164                 throw std::logic_error("Size set failed.");
165 }
166
167 int my_draw_text(const widestring &str, unsigned char *buf, int xpos, int ypos, bool real_render, int r, int g, int b, FT_Face face, FT_Face symbolface)
168 {
169         FT_GlyphSlot slot;
170         int x = 0;
171
172         for (widestring::const_iterator i = str.begin(); i != str.end(); ++i) {
173                 // try the normal font first, fall back if there's some special character
174                 int glyph_index = FT_Get_Char_Index(face, *i);
175                 if (glyph_index == 0) {
176                         std::fprintf(stderr, "Couldn't find U+%x in primary face, falling back to symbol face\n",
177                                 *i);
178                         glyph_index = FT_Get_Char_Index(symbolface, *i);
179                         if (glyph_index == 0) {
180                                 std::fprintf(stderr, "Couldn't find U+%x in symbol face, ignoring\n", *i);
181                                 continue;
182                         }
183                         if (FT_Load_Glyph(symbolface, glyph_index, FT_LOAD_RENDER))
184                                 throw std::runtime_error("Couldn't load glyph from symbol face");
185                         slot = symbolface->glyph;
186                 } else {
187                         if (FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER))
188                                 throw std::runtime_error("Couldn't load glyph from primary face");
189                         slot = face->glyph;
190                 }
191
192                 if (real_render) {
193                         int y;
194                         FT_Bitmap *bm = &(slot->bitmap);
195                         for (y = 0; y < bm->rows; y++) {
196                                 int xx;
197                                 int dsty = ypos - slot->bitmap_top + y;
198                                 if (dsty < 0 || dsty > 599) continue;
199
200                                 unsigned char *dst = buf + dsty * 800*4 + (x + xpos + slot->bitmap_left)*4;
201                                 unsigned char *src = bm->buffer + y * bm->width;
202                                 for (xx = 0; xx < bm->width; xx++) {
203                                         *dst = (*dst * (256-*src) + r * *src) >> 8;
204                                         *dst++;
205                                         *dst = (*dst * (256-*src) + g * *src) >> 8;
206                                         *dst++;
207                                         *dst = (*dst * (256-*src) + b * *src) >> 8;
208                                         *dst++;
209                                         *dst++ = 0;
210                                         src++;
211                                 }
212                         }
213                 }
214
215                 x += slot->advance.x >> 6;
216         }
217
218         return x;
219 }
220
221
222 int main(int argc, char **argv)
223 {
224         ucs4_iconv = iconv_open("ucs-4le", "utf-8");   // FIXME: will be broken for big endian!
225         
226         ptc_open("CCBS bigscreen", 800, 600);
227         
228         try {
229                 init_freetype();
230                 pqxx::connection conn("dbname=ccbs host=altersex.samfundet.no user=ccbs password=GeT|>>B_");
231                 FlagTrigger tournament_changed(conn, "active_tournament");
232                 
233                 // when active_tournament is changed, we destroy everything and start from scratch
234                 for ( ;; ) {
235                         tournament_changed.reset_flag();
236                         init(conn);
237                         do {
238                                 main_loop(conn);
239                                 conn.get_notifs();
240                         } while (!tournament_changed.get_flag());
241                         std::fprintf(stderr, "active_tournament changed, resetting...\n");
242                 }
243         } catch (const std::exception &e) {
244                 std::fprintf(stderr, "Exception: %s\n", e.what());
245                 exit(1);
246         }
247         
248         return 0;
249 }