1 /*
2  * Copyright (C) 2015-2017, by Laszlo Szeremi under the Boost license.
3  *
4  * Pixel Perfect Engine, graphics.draw module
5  */
6 
7 module PixelPerfectEngine.graphics.draw;
8 
9 import std.stdio;
10 import std.math;
11 import std.conv;
12 
13 import PixelPerfectEngine.graphics.bitmap;
14 import compose = CPUblit.composing;
15 import specblt = CPUblit.composing.specblt;
16 import draw = CPUblit.draw;
17 import bmfont;
18 public import PixelPerfectEngine.graphics.fontsets;
19 public import PixelPerfectEngine.graphics.common;
20 import PixelPerfectEngine.graphics.text : Text;
21 //import system.etc;
22 /**
23  * Draws into a 8bit bitmap.
24  */
25 public class BitmapDrawer{
26 	public Bitmap8Bit output;
27 	public ubyte brushTransparency;
28 	///Creates the object alongside its output.
29 	public this(int x, int y) pure {
30 		output = new Bitmap8Bit(x, y);
31 
32 	}
33 	///Draws a single line. DEPRECATED!
34 	deprecated public void drawLine(int xa, int xb, int ya, int yb, ubyte color) pure {
35 		draw.drawLine(xa, ya, xb, yb, color, output.getPtr(), output.width);
36 	}
37 	///Draws a single line.
38 	public void drawLine(Point from, Point to, ubyte color) pure {
39 		draw.drawLine(from.x, from.y, to.x, to.y, color, output.getPtr(), output.width);
40 	}
41 	///Draws a line with a pattern.
42 	public void drawLinePattern(Point from, Point to, ubyte[] pattern) pure {
43 		draw.drawLinePattern(from.x, from.y, to.x, to.y, pattern, output.getPtr(), output.width);
44 	}
45 	///Draws a box.
46 	public void drawBox(Coordinate target, ubyte color) pure {
47 		draw.drawLine(target.left, target.top, target.right, target.top, color, output.getPtr(), output.width);
48 		draw.drawLine(target.left, target.top, target.left, target.bottom, color, output.getPtr(), output.width);
49 		draw.drawLine(target.left, target.bottom, target.right, target.bottom, color, output.getPtr(), output.width);
50 		draw.drawLine(target.right, target.top, target.right, target.bottom, color, output.getPtr(), output.width);
51 	}
52 	///Draws a box with line pattern.
53 	public void drawBox(Coordinate target, ubyte[] pattern) pure {
54 		draw.drawLinePattern(target.left, target.top, target.right, target.top, pattern, output.getPtr(), output.width);
55 		draw.drawLinePattern(target.left, target.top, target.left, target.bottom, pattern, output.getPtr(), output.width);
56 		draw.drawLinePattern(target.left, target.bottom, target.right, target.bottom, pattern, output.getPtr(), output.width);
57 		draw.drawLinePattern(target.right, target.top, target.right, target.bottom, pattern, output.getPtr(), output.width);
58 	}
59 	///Draws a filled box.
60 	public void drawFilledBox(Coordinate target, ubyte color) pure {
61 		draw.drawFilledRectangle(target.left, target.top, target.right, target.bottom, color, output.getPtr(), output.width);
62 	}
63 	///Copies a bitmap to the canvas using 0th index transparency.
64 	public void bitBLT(Point target, Bitmap8Bit source) pure {
65 		ubyte* src = source.getPtr;
66 		ubyte* dest = output.getPtr + (output.width * target.y) + target.x;
67 		for (int y ; y < source.height ; y++){
68 			compose.blitter(src, dest, source.width);
69 			src += source.width;
70 			dest += output.width;
71 		}
72 	}
73 	///Copies a bitmap slice to the canvas using 0th index transparency.
74 	public void bitBLT(Point target, Bitmap8Bit source, Coordinate slice) pure {
75 		ubyte* src = source.getPtr + (source.width * slice.top) + slice.left;
76 		ubyte* dest = output.getPtr + (output.width * target.y) + target.x;
77 		for (int y ; y < slice.height ; y++){
78 			compose.blitter(src, dest, slice.width);
79 			src += source.width;
80 			dest += output.width;
81 		}
82 	}
83 	///Fills the area with a pattern.
84 	public void bitBLTPattern(Coordinate pos, Bitmap8Bit pattern) pure {
85 		const int targetX = pos.width / pattern.width;
86 		const int targetX0 = pos.width % pattern.width;
87 		const int targetY = pos.height / pattern.height;
88 		const int targetY0 = pos.height % pattern.height;
89 		for(int y ; y < targetY ; y++) {
90 			for(int x ; x < targetX; x++) 
91 				bitBLT(Point(pos.left + (x * pattern.width), pos.top + (y * pattern.height)), pattern);
92 			if(targetX0) 
93 				bitBLT(Point(pos.left + (pattern.width * targetX), pos.top + (y * pattern.height)), pattern,
94 						Coordinate(0, 0, targetX0, pattern.height));
95 		}
96 		if(targetY0) {
97 			for(int x ; x < targetX; x++) 
98 				bitBLT(Point(pos.left + (x * pattern.width), pos.top + (targetY * pattern.height)), pattern,
99 						Coordinate(0, 0, pattern.width, targetY0));
100 			if(targetX0) 
101 				bitBLT(Point(pos.left + (pattern.width * targetX), pos.top + (targetY * pattern.height)), pattern,
102 						Coordinate(0, 0, targetX0, targetY0));
103 		}
104 	}
105 	///XOR blits a repeated bitmap pattern over the specified area.
106 	public void xorBitBLT(Coordinate target, Bitmap8Bit pattern) pure {
107 		import CPUblit.composing.specblt;
108 		ubyte* dest = output.getPtr + target.left + (target.top * output.width);
109 		for (int y ; y < target.height ; y++) {
110 			for (int x ; x < target.width ; x += pattern.width) {
111 				const size_t l = x + pattern.width <= target.width ? pattern.width : target.width - pattern.width;
112 				const size_t lineNum = (y % pattern.height);
113 				xorBlitter(pattern.getPtr + pattern.width * lineNum, dest + output.width * y, l);
114 			}
115 		}
116 	}
117 	///XOR blits a color index over a specified area.
118 	public void xorBitBLT(Coordinate target, ubyte color) pure {
119 		import CPUblit.composing.specblt;
120 		ubyte* dest = output.getPtr + target.left + (target.top * output.width);
121 		for (int y ; y < target.height ; y++) {
122 			xorBlitter(dest + output.width * y, target.width, color);
123 		}
124 	}
125 	///Inserts a bitmap using blitter. DEPRECATED
126 	deprecated public void insertBitmap(int x, int y, Bitmap8Bit bitmap) pure {
127 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
128 		pdest += x + output.width * y;
129 		int length = bitmap.width;
130 		for(int iy ; iy < bitmap.height ; iy++){
131 			compose.blitter(psrc,pdest,length);
132 			psrc += length;
133 			pdest += output.width;
134 		}
135 	}
136 	///Inserts a color letter.
137 	public void insertColorLetter(int x, int y, Bitmap8Bit bitmap, ubyte color) pure {
138 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
139 		pdest += x + output.width * y;
140 		int length = bitmap.width;
141 		for(int iy ; iy < bitmap.height ; iy++){
142 			specblt.textBlitter(psrc,pdest,length,color);
143 			psrc += length;
144 			pdest += output.width;
145 		}
146 	}
147 	///Inserts a midsection of the bitmap defined by slice
148 	public void insertBitmapSlice(int x, int y, Bitmap8Bit bitmap, Coordinate slice) pure {
149 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
150 		pdest += x + output.width * y;
151 		int bmpWidth = bitmap.width;
152 		psrc += slice.left + bmpWidth * slice.top;
153 		int length = slice.width;
154 		for(int iy ; iy < slice.height ; iy++){
155 			compose.blitter(psrc,pdest,length);
156 			psrc += bmpWidth;
157 			pdest += output.width;
158 		}
159 	}
160 	///Inserts a midsection of the bitmap defined by slice as a color letter
161 	public void insertColorLetter(int x, int y, Bitmap8Bit bitmap, ubyte color, Coordinate slice) pure {
162 		if(slice.width - 1 <= 0) return;
163 		if(slice.height - 1 <= 0) return;
164 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
165 		pdest += x + output.width * y;
166 		const int bmpWidth = bitmap.width;
167 		psrc += slice.left + bmpWidth * slice.top;
168 		//int length = slice.width;
169 		for(int iy ; iy < slice.height ; iy++){
170 			specblt.textBlitter(psrc,pdest,slice.width,color);
171 			psrc += bmpWidth;
172 			pdest += output.width;
173 		}
174 	}
175 	///Draws a rectangle. DEPRECATED!
176 	deprecated public void drawRectangle(int xa, int xb, int ya, int yb, ubyte color) pure {
177 		drawLine(xa, xa, ya, yb, color);
178 		drawLine(xb, xb, ya, yb, color);
179 		drawLine(xa, xb, ya, ya, color);
180 		drawLine(xa, xb, yb, yb, color);
181 	}
182 	/+
183 	deprecated public void drawRectangle(int xa, int xb, int ya, int yb, Bitmap8Bit brush) pure {
184 		xa = xa + brush.width;
185 		ya = ya + brush.height;
186 		xb = xb - brush.width;
187 		yb = yb - brush.height;
188 		drawLine(xa, xa, ya, yb, brush);
189 		drawLine(xb, xb, ya, yb, brush);
190 		drawLine(xa, xb, ya, ya, brush);
191 		drawLine(xa, xb, yb, yb, brush);
192 	}+/
193 	///Draws a filled rectangle. DEPRECATED!
194 	deprecated public void drawFilledRectangle(int xa, int xb, int ya, int yb, ubyte color) pure {
195 		draw.drawFilledRectangle(xa, ya, xb, yb, color, output.getPtr(), output.width);
196 	}
197 	
198 	///Draws text to the given point. DEPRECATED!
199 	public void drawText(int x, int y, dstring text, Fontset!(Bitmap8Bit) fontset, uint style = 0) pure {
200 		const int length = fontset.getTextLength(text);
201 		//writeln(text);
202 		/+if(style == 0){
203 			x = x - (length / 2);
204 			y -= fontset.getSize() / 2;
205 		}else if(style == 2){
206 			y -= fontset.getSize();
207 		}+/
208 		if(style & FontFormat.HorizCentered)
209 			x = x - (length / 2);
210 		if(style & FontFormat.VertCentered)
211 			y -= fontset.size / 2;
212 		foreach(dchar c ; text){
213 			const Font.Char chinfo = fontset.chars(c);
214 			const Coordinate letterSlice = Coordinate(chinfo.x, chinfo.y, chinfo.x + chinfo.width, chinfo.y + chinfo.height);
215 			insertBitmapSlice(x + chinfo.xoffset, y + chinfo.yoffset, fontset.pages[chinfo.page], letterSlice);
216 			x += chinfo.xadvance;
217 		}
218 	}
219 	///Draws colored text from monocromatic font.
220 	public void drawColorText(int x, int y, dstring text, Fontset!(Bitmap8Bit) fontset, ubyte color, uint style = 0) pure {
221 		//color = 1;
222 		const int length = fontset.getTextLength(text);
223 		if(style & FontFormat.HorizCentered)
224 			x = x - (length / 2);
225 		if(style & FontFormat.VertCentered)
226 			y -= fontset.size / 2;
227 		if(style & FontFormat.RightJustified)
228 			x -= length;
229 		//int fontheight = fontset.getSize();
230 		foreach(dchar c ; text){
231 			const Font.Char chinfo = fontset.chars(c);
232 			const Coordinate letterSlice = Coordinate(chinfo.x, chinfo.y, chinfo.x + chinfo.width, chinfo.y + chinfo.height);
233 			insertColorLetter(x + chinfo.xoffset, y + chinfo.yoffset, fontset.pages[chinfo.page], color, letterSlice);
234 			x += chinfo.xadvance;
235 		}
236 	}
237 	/**
238 	 * Draws fully formatted text within a given prelimiter specified by pos.
239 	 * Offset specifies how much of the text is being obscured from the left hand side.
240 	 * lineOffset specifies how much lines in pixels are skipped on the top.
241 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
242 	 */
243 	public uint drawMultiLineText(Coordinate pos, Text text, int offset = 0, int lineOffset = 0) pure {
244 		int lineCount = lineOffset;
245 		const int maxLines = pos.height + lineOffset;
246 			if(lineCount >= lineOffset) {	//draw if linecount is greater or equal than offset
247 				//special
248 			}
249 		do {
250 
251 		} while(lineCount < maxLines);
252 		return 0;
253 	}
254 	/**
255 	 * Draws a single line fully formatted text within a given prelimiter specified by pos.
256 	 * Offset specifies how much of the text is being obscured from the left hand side.
257 	 * lineOffset specifies how much lines in pixels are skipped on the top.
258 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
259 	 */
260 	public uint drawSingleLineText(Box pos, Text text, int offset = 0, int lineOffset = 0) {
261 		uint status;						//contains status flags
262 		bool needsIcon; 					//set to true if icon is inserted or doesn't exist.
263 		dchar prevChar;						//previous character, used for kerning
264 
265 		const int textWidth = text.getWidth();	//Total with of the text
266 		if (textWidth < offset) return TextDrawStatus.TooMuchOffset;
267 		int pX = text.frontTab;							//The current position, where the first letter will be drawn
268 		
269 		//Currently it was chosen to use a workpad to make things simpler
270 		//TODO: modify the algorithm to work without a workpad
271 		Bitmap8Bit workPad = new Bitmap8Bit(textWidth + 1, text.font.size * 2);
272 		///Inserts a color letter.
273 		void _insertColorLetter(Point pos, Bitmap8Bit bitmap, ubyte color, Coordinate slice) pure nothrow {
274 			if(slice.width - 1 <= 0) return;
275 			if(slice.height - 1 <= 0) return;
276 			ubyte* psrc = bitmap.getPtr, pdest = workPad.getPtr;
277 			pdest += pos.x + workPad.width * pos.y;
278 			const int bmpWidth = bitmap.width;
279 			psrc += slice.left + bmpWidth * slice.top;
280 			//int length = slice.width;
281 			for(int iy ; iy < slice.height ; iy++){
282 				specblt.textBlitter(psrc,pdest,slice.width,color);
283 				psrc += bmpWidth;
284 				pdest += workPad.width;
285 			}
286 		}
287 		///Copies a bitmap to the canvas using 0th index transparency.
288 		void _bitBLT(Point target, Bitmap8Bit source) pure nothrow {
289 			ubyte* src = source.getPtr;
290 			ubyte* dest = workPad.getPtr + (workPad.width * target.y) + target.x;
291 			for (int y ; y < source.height ; y++){
292 				compose.blitter(src, dest, source.width);
293 				src += source.width;
294 				dest += workPad.width;
295 			}
296 		}
297 		//const int targetX = textWidth - offset > pos.width ? pos.right : pos.left + textWidth;
298 		Text currTextChunk = text;
299 		int currCharPos;// = text.offsetAmount(offset);
300 		if (currCharPos == 0 && currTextChunk.icon && offset < currTextChunk.icon.width + currTextChunk.iconOffsetX)
301 			needsIcon = true;
302 		//pX += currTextChunk.frontTab - offset > 0 ? currTextChunk.frontTab - offset : 0;
303 		/+int firstCharOffset = offset;// - text.getWidth(0, currCharPos);
304 
305 		if (currCharPos > 0)
306 			firstCharOffset -= text.getWidth(0, currCharPos);+/
307 		
308 		while (pX < textWidth) {	//Per character/symbol drawing
309 			if(needsIcon) {
310 				//if there's enough space for the icon, then draw it
311 				pX += currTextChunk.iconOffsetX >= 0 ? currTextChunk.iconOffsetX : 0;
312 				//if(pX + currTextChunk.icon.width < targetX) {
313 				//const int targetHeight = pos.height > currTextChunk.icon.height - lineOffset ? currTextChunk.icon.height : pos.height;
314 				_bitBLT(Point(pX, currTextChunk.iconOffsetY), currTextChunk.icon);
315 				pX += text.iconSpacing;
316 				needsIcon = false;
317 				//} else return status | TextDrawStatus.RHSOutOfBound;
318 			} else {
319 				//check if there's any characters left in the current text chunk, if not step onto the next one if any, if not then return
320 				if(currCharPos >= currTextChunk.text.length) {
321 					if(currTextChunk.next) currTextChunk = currTextChunk.next;
322 					else return status;
323 					if(currTextChunk.icon) needsIcon = true;
324 					else needsIcon = false;
325 					currCharPos = 0;
326 					pX += currTextChunk.frontTab;
327 				} else {
328 					//if there's enough space for the next character, then draw it
329 					const dchar chr = text.text[currCharPos];
330 					Font.Char chrInfo = text.font.chars(chr);
331 					//check if the character exists in the fontset, if not, then substitute it and set flag for missing character
332 					if(chrInfo.id == 0xFFFD && chr != 0xFFFD) status |= TextDrawStatus.CharacterNotFound;
333 					//const int chrAdvAmount = chrInfo.xadvance + currTextChunk.formatting.getKerning(prevChar, chr);
334 					/+if(pX < targetX) {
335 						//if(pX + chrInfo.xadvance >= offset) {
336 						Box letterSlice = Box(chrInfo.x, chrInfo.y, chrInfo.x + chrInfo.width, chrInfo.y + 
337 							chrInfo.height);
338 						const int xOffset = pX < offset ? offset - pX : 0;
339 						letterSlice.left += xOffset > chrInfo.xoffset ? xOffset : 0; //Draw portion of letter if it's partly obscured at the left
340 						letterSlice.top += lineOffset > chrInfo.yoffset ? chrInfo.yoffset - lineOffset : 0;
341 						if(pX + chrInfo.xoffset + chrAdvAmount >= targetX){//Draw portion of letter if it's partly obscured at the right
342 							letterSlice.right -= targetX - (pX + chrInfo.xoffset + chrInfo.width);
343 							status |= TextDrawStatus.RHSOutOfBound;
344 						}
345 						/+letterSlice.right -= pX + chrInfo.xoffset + chrInfo.width >= targetX ? 
346 								(pX - chrInfo.xoffset) - (targetX - chrInfo.xoffset) : 0;+/
347 						if (letterSlice.width > 0 && letterSlice.height > 0)
348 							insertColorLetter(pX + chrInfo.xoffset - xOffset, pos.top + chrInfo.yoffset - lineOffset, 
349 									text.font.pages[chrInfo.page], text.formatting.color, letterSlice);
350 						//}
351 						pX += chrAdvAmount;
352 						firstCharOffset = 0;
353 						currCharPos++;
354 						prevChar = chr;
355 					} else return status | TextDrawStatus.RHSOutOfBound;+/
356 					Box letterSrc = Box(chrInfo.x, chrInfo.y, chrInfo.x + chrInfo.width, chrInfo.y + 
357 							chrInfo.height);
358 					Point chrPos = Point (pX + chrInfo.xoffset, chrInfo.yoffset);
359 					_insertColorLetter(chrPos, currTextChunk.font.pages[chrInfo.page], currTextChunk.formatting.color, letterSrc);
360 					pX += chrInfo.xadvance + currTextChunk.formatting.getKerning(prevChar, chr);
361 					currCharPos++;
362 				}
363 			}
364 		}
365 		Point renderTarget = Point(pos.left,pos.top);
366 		//Offset text vertically in needed
367 		if (text.formatting.formatFlags & FormattingFlags.slHorizCenter) {
368 			renderTarget.y += (pos.height / 2) - (text.font.size);
369 		}
370 		//Offset text to the right if needed
371 		if (pos.width > textWidth) {
372 			switch (text.formatting.formatFlags & FormattingFlags.justifyMask) {
373 				case FormattingFlags.centerJustify:
374 					renderTarget.x += (pos.width - textWidth) / 2;
375 					break;
376 				case FormattingFlags.rightJustify:
377 					renderTarget.x += pos.width - textWidth;
378 					break;
379 				default: break;
380 			}
381 		}
382 		Box textSlice = Box(offset, lineOffset, workPad.width - 1, workPad.height - 1);
383 		if (textSlice.width > pos.width) textSlice.width = pos.width;	//clamp down text width
384 		if (textSlice.height > pos.height) textSlice.height = pos.height;	//clamp down text height
385 		if (textSlice.width > 0 && textSlice.height > 0)
386 			bitBLT(renderTarget, workPad, textSlice);
387 		return status;
388 	}
389 }
390 /**
391  * Font formatting flags.
392  */
393 enum FontFormat : uint {
394 	HorizCentered			=	0x1,
395 	VertCentered			=	0x2,
396 	RightJustified			=	0x10,
397 	SingleLine				=	0x100,	///Forces text as single line
398 	
399 }
400 /**
401  * Text drawing return flags.
402  */
403 enum TextDrawStatus : uint {
404 	CharacterNotFound		=	0x01_00,	///Set if there's any character that was not found in the character set
405 	LHSOutOfBound			=	0x00_01,
406 	RHSOutOfBound			=	0x00_02,
407 	TPOutOfBound			=	0x00_04,
408 	BPOutOfBound			=	0x00_08,
409 	TooMuchOffset			=	0x1_00_00,
410 }