1 /*
2  * LavaLauncher - A simple launcher panel for Wayland
3  *
4  * Copyright (C) 2020 Leon Henrik Plickat
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #define _POSIX_C_SOURCE 200809L
21 
22 #include<stdio.h>
23 #include<stdlib.h>
24 #include<stdbool.h>
25 #include<stdint.h>
26 #include<unistd.h>
27 #include<string.h>
28 #include<errno.h>
29 #include<cairo/cairo.h>
30 
31 #if SVG_SUPPORT
32 #include<librsvg-2.0/librsvg/rsvg.h>
33 #endif
34 
35 #include"str.h"
36 #include"lavalauncher.h"
37 #include"types/image_t.h"
38 
39 /* Returns: -1 On error
40  *           0 If the file is not a PNG file
41  *           1 If the file is a PNG file
42  */
is_png_file(const char * path)43 static int is_png_file (const char *path)
44 {
45 	const size_t buffer_size = 8;
46 
47 	FILE *file;
48 	if ( NULL == (file = fopen(path, "r")) )
49 	{
50 		log_message(0, "ERROR: Can not open file: %s\n"
51 				"ERROR: fopen: %s\n", path, strerror(errno));
52 		return -1;
53 	}
54 
55 	unsigned char buffer[buffer_size];
56 	size_t ret = fread(buffer, sizeof(unsigned char), buffer_size, file);
57 	fclose(file);
58 
59 	if ( ret == 0 )
60 	{
61 		log_message(0, "ERROR: fread() failed when trying to fetch file magic.\n");
62 		return -1;
63 	}
64 
65 	unsigned char png_magic[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
66 	for (int i = 0; i < 8; i++) if ( buffer[i] != png_magic[i] )
67 		return 0;
68 
69 	return 1;
70 }
71 
load_image(image_t * image,const char * path)72 static bool load_image (image_t *image, const char *path)
73 {
74 	if (access(path, F_OK))
75 	{
76 		log_message(0, "ERROR: File does not exist: %s\n", path);
77 		return false;
78 	}
79 	if (access(path, R_OK))
80 	{
81 		log_message(0, "ERROR: File can not be read: %s\n"
82 				"INFO: Check the files permissions, owner and group.\n",
83 				path);
84 		return false;
85 	}
86 	if ( errno != 0 )
87 	{
88 		log_message(0, "ERROR: access: %s\n", strerror(errno));
89 		return false;
90 	}
91 
92 	/* PNG */
93 	int ret;
94 	ret = is_png_file(path);
95 	if ( ret == 1 )
96 	{
97 		if ( NULL == (image->cairo_surface = cairo_image_surface_create_from_png(path)) )
98 		{
99 			log_message(0, "ERROR: Failed loading image: %s\n"
100 					"ERROR: cairo_image_surface_create_from_png: %s\n",
101 					path, strerror(errno));
102 			return false;
103 		}
104 		else
105 			return true;
106 	}
107 	else if ( ret == -1 )
108 		return false;
109 
110 #if SVG_SUPPORT
111 	/* SVG */
112 	GError *gerror = NULL;
113 	if ( NULL != (image->rsvg_handle = rsvg_handle_new_from_file(path, &gerror)) )
114 		return true;
115 	else if ( gerror->domain != 123 )
116 	{
117 		/* The domain 123 is an XML parse error. Receiving it means that
118 		 * the file is likely not an SVG image, a case which must be
119 		 * handled differently than other errors.
120 		 */
121 		log_message(0, "ERROR: Failed to load image: %s\n"
122 				"ERROR: rsvg_handle_new_from_file: %d: %s\n",
123 				path, gerror->domain, gerror->message);
124 		return false;
125 	}
126 #endif
127 
128 	log_message(0, "ERROR: Unsupported file type: %s\n"
129 #if SVG_SUPPORT
130 			"INFO: LavaLauncher supports PNG and SVG images.\n",
131 #else
132 			"INFO: LavaLauncher supports PNG images.\n"
133 			"INFO: LavaLauncher has been compiled without SVG support.\n",
134 #endif
135 			path);
136 
137 	return false;
138 }
139 
image_t_create_from_file(const char * path)140 image_t *image_t_create_from_file (const char *path)
141 {
142 	TRY_NEW(image_t, image, NULL);
143 
144 	image->cairo_surface = NULL;
145 	image->references    = 1;
146 #if SVG_SUPPORT
147 	image->rsvg_handle   = NULL;
148 #endif
149 
150 	if (load_image(image, path))
151 		return image;
152 
153 	free(image);
154 	return NULL;
155 }
156 
image_t_reference(image_t * image)157 image_t *image_t_reference (image_t *image)
158 {
159 	image->references++;
160 	return image;
161 }
162 
image_t_destroy(image_t * image)163 void image_t_destroy (image_t *image)
164 {
165 	if ( --image->references > 0 )
166 		return;
167 
168 	if ( image->cairo_surface != NULL )
169 		cairo_surface_destroy(image->cairo_surface);
170 
171 #if SVG_SUPPORT
172 	if ( image->rsvg_handle != NULL )
173 		g_object_unref(image->rsvg_handle);
174 #endif
175 
176 	free(image);
177 }
178 
image_t_draw_to_cairo(cairo_t * cairo,image_t * image,uint32_t x,uint32_t y,uint32_t width,uint32_t height)179 void image_t_draw_to_cairo (cairo_t *cairo, image_t *image,
180 		uint32_t x, uint32_t y, uint32_t width, uint32_t height)
181 {
182 	cairo_save(cairo);
183 	cairo_translate(cairo, x, y);
184 
185 	if ( image->cairo_surface != NULL )
186 	{
187 		int sw = cairo_image_surface_get_width(image->cairo_surface);
188 		int sh = cairo_image_surface_get_height(image->cairo_surface);
189 
190 		cairo_scale(cairo, (float)width / (float)sw, (float)height / (float)sh);
191 		cairo_set_source_surface(cairo, image->cairo_surface, 0, 0);
192 		cairo_paint(cairo);
193 	}
194 #if SVG_SUPPORT
195 	else if ( image->rsvg_handle != NULL )
196 	{
197 		// TODO maybe set DPI?
198 		gboolean has_width, has_height, has_viewbox;
199 		RsvgLength rsvg_width, rsvg_height;
200 		RsvgRectangle viewbox;
201 		rsvg_handle_get_intrinsic_dimensions(image->rsvg_handle,
202 				&has_width, &rsvg_width, &has_height, &rsvg_height,
203 				&has_viewbox, &viewbox);
204 		if ( ! has_width || ! has_height )
205 			log_message(0, "ERROR: Can not render SVG image without width or height.\n");
206 		else if ( rsvg_width.length == 0 || rsvg_height.length == 0 )
207 			log_message(0, "ERROR: Can not render SVG image with width or height of 0.\n");
208 		else if ( rsvg_width.unit != RSVG_UNIT_PX || rsvg_height.unit != RSVG_UNIT_PX )
209 			log_message(0, "ERROR: Can not render SVG image whichs width or height are not defined in pixels.\n");
210 		else
211 		{
212 			cairo_scale(cairo, (float)width / rsvg_width.length,
213 					(float)width / rsvg_height.length);
214 			GError *gerror = NULL;
215 			rsvg_handle_render_document(image->rsvg_handle, cairo,
216 					&viewbox, &gerror);
217 			// TODO check value of gerror
218 		}
219 	}
220 #endif
221 
222 	cairo_restore(cairo);
223 }
224 
225