add png support
This commit is contained in:
		
							
								
								
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,6 +1,8 @@ | |||||||
| The MIT License (MIT) | The MIT License (MIT) | ||||||
|  |  | ||||||
| Copyright (c) 2014 markus.teich@stusta.mhn.de | Copyright (c) 2014 Markus Teich | ||||||
|  |  | ||||||
|  | png handling stuff adapted from meh by John Hawthorn | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | |||||||
| @ -12,7 +12,7 @@ X11LIB = /usr/X11R6/lib | |||||||
|  |  | ||||||
| # includes and libs | # includes and libs | ||||||
| INCS = -I. -I/usr/include -I${X11INC} | INCS = -I. -I/usr/include -I${X11INC} | ||||||
| LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lX11 | LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lX11 -lpng | ||||||
|  |  | ||||||
| # flags | # flags | ||||||
| CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_XOPEN_SOURCE=600 | CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_XOPEN_SOURCE=600 | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								example
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								example
									
									
									
									
									
								
							| @ -1,9 +1,11 @@ | |||||||
| sent | sent | ||||||
| why? | why? | ||||||
|  | @nyan.png | ||||||
| easy to use | easy to use | ||||||
| few dependencies (X11) | depends on Xlib, libpng | ||||||
| no bloat | no bloat | ||||||
| how? | how? | ||||||
| sent FILENAME | sent FILENAME | ||||||
| one slide per line | one slide per line | ||||||
|  |  @FILE.png | ||||||
| thanks / questions? | thanks / questions? | ||||||
|  | |||||||
							
								
								
									
										261
									
								
								sent.c
									
									
									
									
									
								
							
							
						
						
									
										261
									
								
								sent.c
									
									
									
									
									
								
							| @ -1,8 +1,10 @@ | |||||||
| /* See LICENSE for licence details. */ | /* See LICENSE for licence details. */ | ||||||
| #include <errno.h> | #include <errno.h> | ||||||
| #include <math.h> | #include <math.h> | ||||||
|  | #include <png.h> | ||||||
| #include <stdarg.h> | #include <stdarg.h> | ||||||
| #include <stdio.h> | #include <stdio.h> | ||||||
|  | #include <stdint.h> | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <X11/keysym.h> | #include <X11/keysym.h> | ||||||
| @ -19,8 +21,27 @@ char *argv0; | |||||||
| #define LEN(a)     (sizeof(a) / sizeof(a)[0]) | #define LEN(a)     (sizeof(a) / sizeof(a)[0]) | ||||||
| #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) | #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) | ||||||
|  |  | ||||||
|  | typedef enum { | ||||||
|  | 	NONE = 0, | ||||||
|  | 	LOADED = 1, | ||||||
|  | 	SCALED = 2, | ||||||
|  | 	DRAWN = 4 | ||||||
|  | } imgstate; | ||||||
|  |  | ||||||
|  | struct image { | ||||||
|  | 	unsigned char *buf; | ||||||
|  | 	unsigned int bufwidth, bufheight; | ||||||
|  | 	imgstate state; | ||||||
|  | 	XImage *ximg; | ||||||
|  | 	FILE *f; | ||||||
|  | 	png_structp png_ptr; | ||||||
|  | 	png_infop info_ptr; | ||||||
|  | 	int numpasses; | ||||||
|  | }; | ||||||
|  |  | ||||||
| typedef struct { | typedef struct { | ||||||
| 	char *text; | 	char *text; | ||||||
|  | 	struct image *img; | ||||||
| } Slide; | } Slide; | ||||||
|  |  | ||||||
| /* Purely graphic info */ | /* Purely graphic info */ | ||||||
| @ -32,6 +53,7 @@ typedef struct { | |||||||
| 	XSetWindowAttributes attrs; | 	XSetWindowAttributes attrs; | ||||||
| 	int scr; | 	int scr; | ||||||
| 	int w, h; | 	int w, h; | ||||||
|  | 	int uw, uh; /* usable dimensions for drawing text and images */ | ||||||
| } XWindow; | } XWindow; | ||||||
|  |  | ||||||
| /* Drawing Context linked list*/ | /* Drawing Context linked list*/ | ||||||
| @ -60,12 +82,11 @@ typedef struct { | |||||||
| 	const Arg arg; | 	const Arg arg; | ||||||
| } Shortcut; | } Shortcut; | ||||||
|  |  | ||||||
| /* function definitions used in config.h */ | static struct image *pngopen(char *filename); | ||||||
| static void advance(const Arg *); | static int pngread(struct image *img); | ||||||
| static void quit(const Arg *); | static int pngprepare(struct image *img); | ||||||
|  | static void pngscale(struct image *img); | ||||||
| /* config.h for applying patches and the configuration. */ | static void pngdraw(struct image *img); | ||||||
| #include "config.h" |  | ||||||
|  |  | ||||||
| static Bool xfontisscalable(char *name); | static Bool xfontisscalable(char *name); | ||||||
| static XFontStruct *xloadqueryscalablefont(char *name, int size); | static XFontStruct *xloadqueryscalablefont(char *name, int size); | ||||||
| @ -75,6 +96,7 @@ static void eprintf(const char *, ...); | |||||||
| static void load(FILE *fp); | static void load(FILE *fp); | ||||||
| static void advance(const Arg *arg); | static void advance(const Arg *arg); | ||||||
| static void quit(const Arg *arg); | static void quit(const Arg *arg); | ||||||
|  | static void resize(int width, int height); | ||||||
| static void run(); | static void run(); | ||||||
| static void usage(); | static void usage(); | ||||||
| static void xdraw(); | static void xdraw(); | ||||||
| @ -86,7 +108,10 @@ static void bpress(XEvent *); | |||||||
| static void cmessage(XEvent *); | static void cmessage(XEvent *); | ||||||
| static void expose(XEvent *); | static void expose(XEvent *); | ||||||
| static void kpress(XEvent *); | static void kpress(XEvent *); | ||||||
| static void resize(XEvent *); | static void configure(XEvent *); | ||||||
|  |  | ||||||
|  | /* config.h for applying patches and the configuration. */ | ||||||
|  | #include "config.h" | ||||||
|  |  | ||||||
| /* Globals */ | /* Globals */ | ||||||
| static Slide *slides = NULL; | static Slide *slides = NULL; | ||||||
| @ -100,11 +125,193 @@ static char *opt_font = NULL; | |||||||
| static void (*handler[LASTEvent])(XEvent *) = { | static void (*handler[LASTEvent])(XEvent *) = { | ||||||
| 	[ButtonPress] = bpress, | 	[ButtonPress] = bpress, | ||||||
| 	[ClientMessage] = cmessage, | 	[ClientMessage] = cmessage, | ||||||
| 	[ConfigureNotify] = resize, | 	[ConfigureNotify] = configure, | ||||||
| 	[Expose] = expose, | 	[Expose] = expose, | ||||||
| 	[KeyPress] = kpress, | 	[KeyPress] = kpress, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct image *pngopen(char *filename) | ||||||
|  | { | ||||||
|  | 	FILE *f; | ||||||
|  | 	unsigned char buf[8]; | ||||||
|  | 	struct image *img; | ||||||
|  |  | ||||||
|  | 	if (!(f = fopen(filename, "rb"))) { | ||||||
|  | 		eprintf("could not open file %s:", filename); | ||||||
|  | 		return NULL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (fread(buf, 1, 8, f) != 8 || png_sig_cmp(buf, 1, 8)) | ||||||
|  | 		return NULL; | ||||||
|  |  | ||||||
|  | 	img = malloc(sizeof(struct image)); | ||||||
|  | 	if (!(img->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, | ||||||
|  | 					NULL, NULL))) { | ||||||
|  | 		free(img); | ||||||
|  | 		return NULL; | ||||||
|  | 	} | ||||||
|  | 	if (!(img->info_ptr = png_create_info_struct(img->png_ptr))) { | ||||||
|  | 		png_destroy_read_struct(&img->png_ptr, NULL, NULL); | ||||||
|  | 		free(img); | ||||||
|  | 		return NULL; | ||||||
|  | 	} | ||||||
|  | 	if (setjmp(png_jmpbuf(img->png_ptr))) { | ||||||
|  | 		png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL); | ||||||
|  | 		free(img); | ||||||
|  | 		return NULL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	img->f = f; | ||||||
|  | 	rewind(f); | ||||||
|  | 	png_init_io(img->png_ptr, f); | ||||||
|  | 	png_read_info(img->png_ptr, img->info_ptr); | ||||||
|  | 	img->bufwidth = png_get_image_width(img->png_ptr, img->info_ptr); | ||||||
|  | 	img->bufheight = png_get_image_height(img->png_ptr, img->info_ptr); | ||||||
|  |  | ||||||
|  | 	return img; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int pngread(struct image *img) | ||||||
|  | { | ||||||
|  | 	unsigned int y; | ||||||
|  | 	png_bytepp row_pointers; | ||||||
|  |  | ||||||
|  | 	if (!img) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	if (img->state & LOADED) | ||||||
|  | 		return 2; | ||||||
|  |  | ||||||
|  | 	if (img->buf) | ||||||
|  | 		free(img->buf); | ||||||
|  | 	if (!(img->buf = malloc(3 * img->bufwidth * img->bufheight))) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	if (setjmp(png_jmpbuf(img->png_ptr))) { | ||||||
|  | 		png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	{ | ||||||
|  | 		int color_type = png_get_color_type(img->png_ptr, img->info_ptr); | ||||||
|  | 		int bit_depth = png_get_bit_depth(img->png_ptr, img->info_ptr); | ||||||
|  | 		if (color_type == PNG_COLOR_TYPE_PALETTE) | ||||||
|  | 			png_set_expand(img->png_ptr); | ||||||
|  | 		if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) | ||||||
|  | 			png_set_expand(img->png_ptr); | ||||||
|  | 		if (png_get_valid(img->png_ptr, img->info_ptr, PNG_INFO_tRNS)) | ||||||
|  | 			png_set_expand(img->png_ptr); | ||||||
|  | 		if (bit_depth == 16) | ||||||
|  | 			png_set_strip_16(img->png_ptr); | ||||||
|  | 		if (color_type == PNG_COLOR_TYPE_GRAY | ||||||
|  | 				|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA) | ||||||
|  | 			png_set_gray_to_rgb(img->png_ptr); | ||||||
|  |  | ||||||
|  | 		png_color_16 my_background = {.red = 0xff, .green = 0xff, .blue = 0xff}; | ||||||
|  | 		png_color_16p image_background; | ||||||
|  |  | ||||||
|  | 		if (png_get_bKGD(img->png_ptr, img->info_ptr, &image_background)) | ||||||
|  | 			png_set_background(img->png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); | ||||||
|  | 		else | ||||||
|  | 			png_set_background(img->png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 2, 1.0); | ||||||
|  |  | ||||||
|  | 		if (png_get_interlace_type(img->png_ptr, img->info_ptr) == PNG_INTERLACE_ADAM7) | ||||||
|  | 			img->numpasses = png_set_interlace_handling(img->png_ptr); | ||||||
|  | 		else | ||||||
|  | 			img->numpasses = 1; | ||||||
|  | 		png_read_update_info(img->png_ptr, img->info_ptr); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	row_pointers = (png_bytepp)malloc(img->bufheight * sizeof(png_bytep)); | ||||||
|  | 	for (y = 0; y < img->bufheight; y++) | ||||||
|  | 		row_pointers[y] = img->buf + y * img->bufwidth * 3; | ||||||
|  |  | ||||||
|  | 	png_read_image(img->png_ptr, row_pointers); | ||||||
|  | 	free(row_pointers); | ||||||
|  |  | ||||||
|  | 	png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL); | ||||||
|  | 	fclose(img->f); | ||||||
|  | 	img->state |= LOADED; | ||||||
|  |  | ||||||
|  | 	return 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int pngprepare(struct image *img) | ||||||
|  | { | ||||||
|  | 	int depth = DefaultDepth(xw.dpy, xw.scr); | ||||||
|  | 	int width = xw.uw; | ||||||
|  | 	int height = xw.uh; | ||||||
|  |  | ||||||
|  | 	if (xw.uw * img->bufheight > xw.uh * img->bufwidth) | ||||||
|  | 		width = img->bufwidth * xw.uh / img->bufheight; | ||||||
|  | 	else | ||||||
|  | 		height = img->bufheight * xw.uw / img->bufwidth; | ||||||
|  |  | ||||||
|  | 	if (depth < 24) { | ||||||
|  | 		eprintf("display depths <24 not supported."); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0, | ||||||
|  | 				NULL, width, height, 32, 0))) { | ||||||
|  | 		eprintf("could not create XImage"); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!(img->ximg->data = malloc(img->ximg->bytes_per_line * height))) { | ||||||
|  | 		eprintf("could not alloc data section for XImage"); | ||||||
|  | 		XDestroyImage(img->ximg); | ||||||
|  | 		img->ximg = NULL; | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!XInitImage(img->ximg)) { | ||||||
|  | 		eprintf("could not init XImage"); | ||||||
|  | 		free(img->ximg->data); | ||||||
|  | 		XDestroyImage(img->ximg); | ||||||
|  | 		img->ximg = NULL; | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	pngscale(img); | ||||||
|  | 	img->state |= SCALED; | ||||||
|  | 	return 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void pngscale(struct image *img) | ||||||
|  | { | ||||||
|  | 	unsigned int x, y; | ||||||
|  | 	unsigned int width = img->ximg->width; | ||||||
|  | 	unsigned int height = img->ximg->height; | ||||||
|  | 	char* __restrict__ newBuf = img->ximg->data; | ||||||
|  | 	unsigned char * __restrict__ ibuf; | ||||||
|  | 	unsigned int jdy = img->ximg->bytes_per_line / 4 - width; | ||||||
|  | 	unsigned int dx = (img->bufwidth << 10) / width; | ||||||
|  |  | ||||||
|  | 	for (y = 0; y < height; y++) { | ||||||
|  | 		unsigned int bufx = img->bufwidth / width; | ||||||
|  | 		ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3]; | ||||||
|  |  | ||||||
|  | 		for (x = 0; x < width; x++) { | ||||||
|  | 			*newBuf++ = (ibuf[(bufx >> 10)*3+2]); | ||||||
|  | 			*newBuf++ = (ibuf[(bufx >> 10)*3+1]); | ||||||
|  | 			*newBuf++ = (ibuf[(bufx >> 10)*3+0]); | ||||||
|  | 			newBuf++; | ||||||
|  | 			bufx += dx; | ||||||
|  | 		} | ||||||
|  | 		newBuf += jdy; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void pngdraw(struct image *img) | ||||||
|  | { | ||||||
|  | 	int xoffset = (xw.w - img->ximg->width) / 2; | ||||||
|  | 	int yoffset = (xw.h - img->ximg->height) / 2; | ||||||
|  | 	XPutImage(xw.dpy, xw.win, dc.gc, img->ximg, 0, 0, | ||||||
|  | 			xoffset, yoffset, img->ximg->width, img->ximg->height); | ||||||
|  | 	XFlush(xw.dpy); | ||||||
|  | 	img->state |= DRAWN; | ||||||
|  | } | ||||||
|  |  | ||||||
| Bool xfontisscalable(char *name) | Bool xfontisscalable(char *name) | ||||||
| { | { | ||||||
| @ -177,8 +384,7 @@ struct DC *getfontsize(char *str, size_t len, int *width, int *height) | |||||||
|  |  | ||||||
| 	do { | 	do { | ||||||
| 		XTextExtents(cur->font, str, len, &unused, &unused, &unused, &info); | 		XTextExtents(cur->font, str, len, &unused, &unused, &unused, &info); | ||||||
| 		if (info.width > usablewidth * xw.w | 		if (info.width > xw.uw || info.ascent + info.descent > xw.uh) | ||||||
| 				|| info.ascent + info.descent > usableheight * xw.h) |  | ||||||
| 			break; | 			break; | ||||||
| 		pre = cur; | 		pre = cur; | ||||||
| 	} while ((cur = cur->next)); | 	} while ((cur = cur->next)); | ||||||
| @ -245,6 +451,8 @@ void load(FILE *fp) | |||||||
| 			*p = '\0'; | 			*p = '\0'; | ||||||
| 		if (!(slides[i].text = strdup(buf))) | 		if (!(slides[i].text = strdup(buf))) | ||||||
| 			eprintf("cannot strdup %u bytes:", strlen(buf)+1); | 			eprintf("cannot strdup %u bytes:", strlen(buf)+1); | ||||||
|  | 		if (slides[i].text[0] == '@') | ||||||
|  | 			slides[i].img = pngopen(slides[i].text + 1); | ||||||
| 	} | 	} | ||||||
| 	if (slides) | 	if (slides) | ||||||
| 		slides[i].text = NULL; | 		slides[i].text = NULL; | ||||||
| @ -256,8 +464,14 @@ void advance(const Arg *arg) | |||||||
| 	int new_idx = idx + arg->i; | 	int new_idx = idx + arg->i; | ||||||
| 	LIMIT(new_idx, 0, slidecount-1); | 	LIMIT(new_idx, 0, slidecount-1); | ||||||
| 	if (new_idx != idx) { | 	if (new_idx != idx) { | ||||||
|  | 		if (slides[idx].img) | ||||||
|  | 			slides[idx].img->state &= ~(DRAWN | SCALED); | ||||||
| 		idx = new_idx; | 		idx = new_idx; | ||||||
| 		xdraw(); | 		xdraw(); | ||||||
|  | 		if (slidecount > idx + 1 && slides[idx + 1].img && !pngread(slides[idx + 1].img)) | ||||||
|  | 			eprintf("could not read image %s", slides[idx + 1].text + 1); | ||||||
|  | 		if (0 < idx && slides[idx - 1].img && !pngread(slides[idx - 1].img)) | ||||||
|  | 			eprintf("could not read image %s", slides[idx - 1].text + 1); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -266,6 +480,14 @@ void quit(const Arg *arg) | |||||||
| 	running = 0; | 	running = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void resize(int width, int height) | ||||||
|  | { | ||||||
|  | 	xw.w = width; | ||||||
|  | 	xw.h = height; | ||||||
|  | 	xw.uw = usablewidth * width; | ||||||
|  | 	xw.uh = usableheight * height; | ||||||
|  | } | ||||||
|  |  | ||||||
| void run() | void run() | ||||||
| { | { | ||||||
| 	XEvent ev; | 	XEvent ev; | ||||||
| @ -274,8 +496,7 @@ void run() | |||||||
| 	while (1) { | 	while (1) { | ||||||
| 		XNextEvent(xw.dpy, &ev); | 		XNextEvent(xw.dpy, &ev); | ||||||
| 		if (ev.type == ConfigureNotify) { | 		if (ev.type == ConfigureNotify) { | ||||||
| 			xw.w = ev.xconfigure.width; | 			resize(ev.xconfigure.width, ev.xconfigure.height); | ||||||
| 			xw.h = ev.xconfigure.height; |  | ||||||
| 		} else if (ev.type == MapNotify) { | 		} else if (ev.type == MapNotify) { | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
| @ -300,10 +521,19 @@ void xdraw() | |||||||
| 	int height; | 	int height; | ||||||
| 	int width; | 	int width; | ||||||
| 	struct DC *dc = getfontsize(slides[idx].text, line_len, &width, &height); | 	struct DC *dc = getfontsize(slides[idx].text, line_len, &width, &height); | ||||||
|  | 	struct image *im = slides[idx].img; | ||||||
|  |  | ||||||
| 	XClearWindow(xw.dpy, xw.win); | 	XClearWindow(xw.dpy, xw.win); | ||||||
|  |  | ||||||
|  | 	if (!im) | ||||||
| 		XDrawString(xw.dpy, xw.win, dc->gc, (xw.w - width)/2, (xw.h + height)/2, | 		XDrawString(xw.dpy, xw.win, dc->gc, (xw.w - width)/2, (xw.h + height)/2, | ||||||
| 				slides[idx].text, line_len); | 				slides[idx].text, line_len); | ||||||
|  | 	else if (!(im->state & LOADED) && !pngread(im)) | ||||||
|  | 		eprintf("could not read image %s", slides[idx].text + 1); | ||||||
|  | 	else if (!(im->state & SCALED) && !pngprepare(im)) | ||||||
|  | 		eprintf("could not prepare image %s for drawing", slides[idx].text + 1); | ||||||
|  | 	else if (!(im->state & DRAWN)) | ||||||
|  | 		pngdraw(im); | ||||||
| } | } | ||||||
|  |  | ||||||
| void xhints() | void xhints() | ||||||
| @ -427,10 +657,11 @@ void kpress(XEvent *e) | |||||||
| 			shortcuts[i].func(&(shortcuts[i].arg)); | 			shortcuts[i].func(&(shortcuts[i].arg)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void resize(XEvent *e) | void configure(XEvent *e) | ||||||
| { | { | ||||||
| 	xw.w = e->xconfigure.width; | 	resize(e->xconfigure.width, e->xconfigure.height); | ||||||
| 	xw.h = e->xconfigure.height; | 	if (slides[idx].img) | ||||||
|  | 		slides[idx].img->state &= ~(DRAWN | SCALED); | ||||||
| 	xdraw(); | 	xdraw(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user