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 draw = CPUblit.drawing.line;
18 import draw0 = CPUblit.drawing.foodfill;
19 import bmfont;
20 public import pixelperfectengine.graphics.fontsets;
21 public import pixelperfectengine.graphics.common;
22 import pixelperfectengine.graphics.text : Text, isWhiteSpaceMB;
23 //import system.etc;
24 /**
25  * Draws into a 8bit bitmap.
26  */
27 public class BitmapDrawer{
28 	public Bitmap8Bit output;
29 	protected immutable ubyte[2] dottedLine = [0x00, 0xFF];
30 	protected immutable ubyte[8] stripesLine = [0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
31 	///Creates the object alongside its output.
32 	public this(int x, int y) pure {
33 		output = new Bitmap8Bit(x, y);
34 
35 	}
36 	/**
37 	 * Draws a single line.
38 	 * Parameters:
39 	 *  from = The beginning of the line.
40 	 *  to = The endpoint of the line.
41 	 *  color = The color index to be used for the line.
42 	 */
43 	public void drawLine(Point from, Point to, ubyte color) pure {
44 		draw.drawLine(from.x, from.y, to.x, to.y, color, output.pixels, output.width);
45 	}
46 	/**
47 	 * Draws a line with a pattern.
48 	 * Parameters:
49 	 *  from = The beginning of the line.
50 	 *  to = The end of the line.
51 	 *  pattern = Contains the color indexes for the line, to draw the pattern.
52 	 */
53 	public void drawLinePattern(Point from, Point to, ubyte[] pattern) pure {
54 		draw.drawLinePattern(from.x, from.y, to.x, to.y, pattern, output.pixels, output.width);
55 	}
56 	/**
57 	 * Draws a box.
58 	 * Parameters:
59 	 *  target = Containst the coordinates of the box to be drawn.
60 	 *  color = The color index which the box will be drawn.
61 	 */
62 	public void drawBox(Box target, ubyte color) pure {
63 		draw.drawLine(target.left, target.top, target.right, target.top, color, output.pixels, output.width);
64 		draw.drawLine(target.left, target.top, target.left, target.bottom, color, output.pixels, output.width);
65 		draw.drawLine(target.left, target.bottom, target.right, target.bottom, color, output.pixels, output.width);
66 		draw.drawLine(target.right, target.top, target.right, target.bottom, color, output.pixels, output.width);
67 	}
68 	/**
69 	 * Draws a box with the supplied pattern as the 
70 	 * Parameters:
71 	 *  target = Containst the coordinates of the box to be drawn.
72 	 *  color = The color index which the box will be drawn.
73 	 */
74 	public void drawBox(Coordinate target, ubyte[] pattern) pure {
75 		draw.drawLinePattern(target.left, target.top, target.right, target.top, pattern, output.pixels, output.width);
76 		draw.drawLinePattern(target.left, target.top, target.left, target.bottom, pattern, output.pixels, output.width);
77 		draw.drawLinePattern(target.left, target.bottom, target.right, target.bottom, pattern, output.pixels, output.width);
78 		draw.drawLinePattern(target.right, target.top, target.right, target.bottom, pattern, output.pixels, output.width);
79 	}
80 	/**
81 	 * Draws a filled box.
82 	 * Parameters:
83 	 *  target = The position of the box.
84 	 *  color = The color of the box (both the line and fill color).
85 	 */
86 	public void drawFilledBox(Coordinate target, ubyte color) pure {
87 		draw.drawFilledRectangle(target.left, target.top, target.right, target.bottom, color, output.pixels, 
88 				output.width);
89 	}
90 	public void floodFill(Point target, ubyte color, ubyte transparency = 0) pure {
91 		draw0.floodFill(target.x, target.y, color, output.pixels, output.width, transparency);
92 	}
93 	/**
94 	 * Copies a bitmap to the canvas using 0th index transparency.
95 	 * Parameters:
96 	 *  target = Where the top-left corner should fall.
97 	 *  source = The bitmap to be copied into the output.
98 	 */
99 	public void bitBLT(Point target, Bitmap8Bit source) pure {
100 		ubyte* src = source.getPtr;
101 		ubyte* dest = output.getPtr + (output.width * target.y) + target.x;
102 		for (int y ; y < source.height ; y++){
103 			compose.blitter(src, dest, source.width);
104 			src += source.width;
105 			dest += output.width;
106 		}
107 	}
108 	/**
109 	 * Copies a bitmap slice to the canvas using 0th index transparency.
110 	 * Parameters:
111 	 *  target = Where the top-left corner should fall.
112 	 *  source = The bitmap to be copied into the output.
113 	 *  slice = Defines what  part of the bitmap should be copied.
114 	 */
115 	public void bitBLT(Point target, Bitmap8Bit source, Coordinate slice) pure {
116 		ubyte* src = source.getPtr + (source.width * slice.top) + slice.left;
117 		ubyte* dest = output.getPtr + (output.width * target.y) + target.x;
118 		for (int y ; y < slice.height ; y++){
119 			compose.blitter(src, dest, slice.width);
120 			src += source.width;
121 			dest += output.width;
122 		}
123 	}
124 	/**
125 	 * Fills the specified area with a pattern.
126 	 * Parameters:
127 	 *  pos = The area that needs to be filled with the pattern.
128 	 *  pattern = The pattern to be used.
129 	 */
130 	public void bitBLTPattern(Box pos, Bitmap8Bit pattern) pure {
131 		const int targetX = pos.width / pattern.width;
132 		const int targetX0 = pos.width % pattern.width;
133 		const int targetY = pos.height / pattern.height;
134 		const int targetY0 = pos.height % pattern.height;
135 		for(int y ; y < targetY ; y++) {
136 			for(int x ; x < targetX; x++) 
137 				bitBLT(Point(pos.left + (x * pattern.width), pos.top + (y * pattern.height)), pattern);
138 			if(targetX0) 
139 				bitBLT(Point(pos.left + (pattern.width * targetX), pos.top + (y * pattern.height)), pattern,
140 						Box(0, 0, targetX0, pattern.height - 1));
141 		}
142 		if(targetY0) {
143 			for(int x ; x < targetX; x++) 
144 				bitBLT(Point(pos.left + (x * pattern.width), pos.top + (targetY * pattern.height)), pattern,
145 						Box(0, 0, pattern.width - 1, targetY0));
146 			if(targetX0) 
147 				bitBLT(Point(pos.left + (pattern.width * targetX), pos.top + (targetY * pattern.height)), pattern,
148 						Box(0, 0, targetX0, targetY0));
149 		}
150 	}
151 	/**
152 	 * XOR blits a repeated bitmap pattern over the specified area.
153 	 * Parameters:
154 	 *  target = The area to be XOR blitted.
155 	 *  pattern = Specifies the pattern to be used.
156 	 */
157 	public void xorBitBLT(Box target, Bitmap8Bit pattern) pure {
158 		import CPUblit.composing.specblt;
159 		ubyte* dest = output.getPtr + target.left + (target.top * output.width);
160 		for (int y ; y < target.height ; y++) {
161 			for (int x ; x < target.width ; x += pattern.width) {
162 				const size_t l = x + pattern.width <= target.width ? pattern.width : target.width - pattern.width;
163 				const size_t lineNum = (y % pattern.height);
164 				xorBlitter(pattern.getPtr + pattern.width * lineNum, dest + output.width * y, l);
165 			}
166 		}
167 	}
168 	/**
169 	 * XOR blits a color index over a specified area.
170 	 * Parameters:
171 	 *  target = The area to be XOR blitted.
172 	 *  color = The color index to be used.
173 	 */
174 	public void xorBitBLT(Coordinate target, ubyte color) pure {
175 		import CPUblit.composing.specblt;
176 		ubyte* dest = output.getPtr + target.left + (target.top * output.width);
177 		for (int y ; y < target.height ; y++) {
178 			xorBlitter(dest + output.width * y, target.width, color);
179 		}
180 	}
181 	
182 	///Inserts a color letter.
183 	public void insertColorLetter(int x, int y, Bitmap8Bit bitmap, ubyte color) pure {
184 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
185 		pdest += x + output.width * y;
186 		int length = bitmap.width;
187 		for(int iy ; iy < bitmap.height ; iy++){
188 			specblt.textBlitter(psrc,pdest,length,color);
189 			psrc += length;
190 			pdest += output.width;
191 		}
192 	}
193 	/* ///Inserts a midsection of the bitmap defined by slice (DEPRECATED)
194 	public deprecated void insertBitmapSlice(int x, int y, Bitmap8Bit bitmap, Coordinate slice) pure {
195 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
196 		pdest += x + output.width * y;
197 		int bmpWidth = bitmap.width;
198 		psrc += slice.left + bmpWidth * slice.top;
199 		int length = slice.width;
200 		for(int iy ; iy < slice.height ; iy++){
201 			compose.blitter(psrc,pdest,length);
202 			psrc += bmpWidth;
203 			pdest += output.width;
204 		}
205 	} */
206 	/* ///Inserts a midsection of the bitmap defined by slice as a color letter (DEPRECATED)
207 	public deprecated void insertColorLetter(int x, int y, Bitmap8Bit bitmap, ubyte color, Coordinate slice) pure {
208 		if(slice.width - 1 <= 0) return;
209 		if(slice.height - 1 <= 0) return;
210 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
211 		pdest += x + output.width * y;
212 		const int bmpWidth = bitmap.width;
213 		psrc += slice.left + bmpWidth * slice.top;
214 		//int length = slice.width;
215 		for(int iy ; iy < slice.height ; iy++){
216 			specblt.textBlitter(psrc,pdest,slice.width,color);
217 			psrc += bmpWidth;
218 			pdest += output.width;
219 		}
220 	} */
221 		
222 	/* ///Draws colored text from monocromatic font.
223 	public void drawColorText(int x, int y, dstring text, Fontset!(Bitmap8Bit) fontset, ubyte color, uint style = 0) pure {
224 		//color = 1;
225 		const int length = fontset.getTextLength(text);
226 		if(style & FontFormat.HorizCentered)
227 			x = x - (length / 2);
228 		if(style & FontFormat.VertCentered)
229 			y -= fontset.size / 2;
230 		if(style & FontFormat.RightJustified)
231 			x -= length;
232 		//int fontheight = fontset.getSize();
233 		foreach(dchar c ; text){
234 			const Font.Char chinfo = fontset.chars(c);
235 			const Coordinate letterSlice = Coordinate(chinfo.x, chinfo.y, chinfo.x + chinfo.width, chinfo.y + chinfo.height);
236 			insertColorLetter(x + chinfo.xoffset, y + chinfo.yoffset, fontset.pages[chinfo.page], color, letterSlice);
237 			x += chinfo.xadvance;
238 		}
239 	} */
240 	/**
241 	 * Draws fully formatted text within a given prelimiter specified by pos.
242 	 * Offset specifies how much of the text is being obscured from the left hand side.
243 	 * lineOffset specifies how much lines in pixels are skipped on the top.
244 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
245 	 */
246 	public uint drawMultiLineText(Box pos, Text text, int offset = 0, int lineOffset = 0) {
247 		Text[] lineChunks = text.breakTextIntoMultipleLines(pos.width);
248 		assert (lineChunks.length);
249 		return drawMultiLineText(pos, lineChunks, offset, lineOffset);
250 	}
251 	/**
252 	 * Draws fully formatted text within a given prelimiter specified by pos.
253 	 * Offset specifies how much of the text is being obscured from the left hand side.
254 	 * lineOffset specifies how much lines in pixels are skipped on the top.
255 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
256 	 */
257 	public uint drawMultiLineText(Box pos, Text[] lineChunks, int offset = 0, int lineOffset = 0) {
258 		//Text[] lineChunks = text.breakTextIntoMultipleLines(pos.width);
259 		int lineNum;
260 		const int maxLines = pos.height + lineOffset;
261 		/* if(lineCount >= lineOffset) {	//draw if linecount is greater or equal than offset
262 			//special
263 		} */
264 		int currLine = lineOffset * -1;
265 		
266 		while (currLine < maxLines && lineNum < lineChunks.length) {
267 			Box currPos = Box.bySize(0, currLine, pos.width, lineChunks[lineNum].getHeight);
268 			if (currPos.top >= 0) {
269 				drawSingleLineText(currPos, lineChunks[lineNum], 0, 0);
270 			} else if (currPos.bottom >= 0) {
271 				drawSingleLineText(currPos, lineChunks[lineNum], 0, currPos.top * -1);
272 			}
273 			currLine += lineChunks[lineNum].getHeight;
274 			lineNum++;
275 		}
276 		return 0;
277 	}
278 	/**
279 	 * Draws a single line fully formatted text within a given prelimiter specified by pos.
280 	 * Offset specifies how much of the text is being obscured from the left hand side.
281 	 * lineOffset specifies how much lines in pixels are skipped on the top.
282 	 * Return value contains state flags on wheter certain portions of the text were out of bound.
283 	 */
284 	public uint drawSingleLineText(Box pos, Text text, int offset = 0, int lineOffset = 0) {
285 		uint status;						//contains status flags
286 		bool needsIcon; 					//set to true if icon is inserted or doesn't exist.
287 		dchar prevChar;						//previous character, used for kerning
288 
289 		const int textWidth = text.getWidth();	//Total with of the text
290 		if (textWidth < offset) return TextDrawStatus.TooMuchOffset;
291 		if (text.font.size < lineOffset) return TextDrawStatus.TooMuchLineOffset;
292 		int pX = text.frontTab;							//The current position, where the first letter will be drawn
293 		
294 		//Currently it was chosen to use a workpad to make things simpler
295 		//TODO: modify the algorithm to work without a workpad
296 		Bitmap8Bit workPad = new Bitmap8Bit(textWidth + 1, text.font.size * 2);
297 		///Inserts a color letter.
298 		void _insertColorLetter(Point pos, Bitmap8Bit bitmap, ubyte color, Box slice, int italics) pure nothrow {
299 			if(slice.width <= 0) return;
300 			if(slice.height <= 0) return;
301 			ubyte* psrc = bitmap.getPtr, pdest = workPad.getPtr;
302 			pdest += pos.x + workPad.width * pos.y;
303 			const int bmpWidth = bitmap.width;
304 			psrc += slice.left + bmpWidth * slice.top;
305 			//int length = slice.width;
306 			for(int iy ; iy < slice.height ; iy++) {
307 				const int italicsOffset = italics != 1 ? (slice.height - iy) / italics : 0;
308 				specblt.textBlitter(psrc, pdest + italicsOffset, slice.width, color);
309 				psrc += bmpWidth;
310 				pdest += workPad.width;
311 			}
312 		}
313 		///Copies a bitmap to the canvas using 0th index transparency.
314 		void _bitBLT(Point target, Bitmap8Bit source) pure nothrow {
315 			ubyte* src = source.getPtr;
316 			ubyte* dest = workPad.getPtr + (workPad.width * target.y) + target.x;
317 			for (int y ; y < source.height ; y++){
318 				compose.blitter(src, dest, source.width);
319 				src += source.width;
320 				dest += workPad.width;
321 			}
322 		}
323 		void _drawUnderlineSegment(uint style, int vOffset, int from, int to, ubyte color) pure {
324 			switch (style & FormattingFlags.ulLineStyle) {
325 			case FormattingFlags.underlineDotted:
326 				for (int x = from ; x <= to ; x++) {
327 					workPad.writePixel(x, vOffset, dottedLine[x & 1] & color);
328 				}
329 				break;
330 			case FormattingFlags.underlineStripes:
331 				for (int x = from ; x <= to ; x++) {
332 					workPad.writePixel(x, vOffset, stripesLine[x & 7] & color);
333 				}
334 				break;
335 			default:
336 				/* for (int i = (style & FormattingFlags.ulLineMultiplier) ; i >= 0 ; i--){
337 					if (vOffset + ((i>>7) * 2) > workPad.height) continue; */
338 				for (int x = from ; x <= to ; x++) {
339 					workPad.writePixel(x, vOffset/*  + ((i>>7) * 2) */, color);
340 				}
341 				/* } */
342 				break;
343 			}	
344 		}
345 		void _drawOtherLines(uint style, int vOffset, int from, int to, ubyte color) pure {
346 			if (style & FormattingFlags.strikeThrough) {
347 				for (int x = from ; x <= to ; x++) {
348 					workPad.writePixel(x, vOffset / 2, color);
349 				}
350 			}
351 			if (style & FormattingFlags.overline) {
352 				for (int x = from ; x <= to ; x++) {
353 					workPad.writePixel(x, 0, color);
354 				}
355 			}
356 		}
357 		//const int targetX = textWidth - offset > pos.width ? pos.right : pos.left + textWidth;
358 		Text currTextChunk = text;
359 		int currCharPos;// = text.offsetAmount(offset);
360 		if (currCharPos == 0 && currTextChunk.icon && offset < currTextChunk.icon.width + currTextChunk.iconOffsetX)
361 			needsIcon = true;
362 		//pX += currTextChunk.frontTab - offset > 0 ? currTextChunk.frontTab - offset : 0;
363 		/+int firstCharOffset = offset;// - text.getWidth(0, currCharPos);
364 
365 		if (currCharPos > 0)
366 			firstCharOffset -= text.getWidth(0, currCharPos);+/
367 		
368 		while (pX < textWidth) {	//Per character/symbol drawing
369 			if(needsIcon) {
370 				//if there's enough space for the icon, then draw it
371 				pX += currTextChunk.iconOffsetX >= 0 ? currTextChunk.iconOffsetX : 0;
372 				//if(pX + currTextChunk.icon.width < targetX) {
373 				//const int targetHeight = pos.height > currTextChunk.icon.height - lineOffset ? currTextChunk.icon.height : pos.height;
374 				_bitBLT(Point(pX, currTextChunk.iconOffsetY), currTextChunk.icon);
375 				pX += text.iconSpacing;
376 				needsIcon = false;
377 				//} else return status | TextDrawStatus.RHSOutOfBound;
378 			} else {
379 				//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
380 				if(currCharPos >= currTextChunk.text.length) {
381 					if(currTextChunk.next) currTextChunk = currTextChunk.next;
382 					else return status;
383 					if(currTextChunk.icon) needsIcon = true;
384 					else needsIcon = false;
385 					currCharPos = 0;
386 					pX += currTextChunk.frontTab;
387 				} else {
388 					//if there's enough space for the next character, then draw it
389 					const dchar chr = currTextChunk.text[currCharPos];
390 					Font.Char chrInfo = text.font.chars(chr);
391 					//check if the character exists in the fontset, if not, then substitute it and set flag for missing character
392 					if(chrInfo.id == 0xFFFD && chr != 0xFFFD) status |= TextDrawStatus.CharacterNotFound;
393 					/* Box letterSrc = Box(chrInfo.x, chrInfo.y, chrInfo.x + chrInfo.width, chrInfo.y + 
394 							chrInfo.height); */
395 					Box letterSrc = Box.bySize(chrInfo.x, chrInfo.y, chrInfo.width, chrInfo.height);
396 					Point chrPos = Point (pX + chrInfo.xoffset, chrInfo.yoffset);
397 					_insertColorLetter(chrPos, currTextChunk.font.pages[chrInfo.page], currTextChunk.formatting.color, letterSrc, 
398 							currTextChunk.formatting.getItalicsAm);
399 					//draw underline if needed
400 					if ((currTextChunk.formatting.formatFlags & FormattingFlags.underline) && 
401 							!((currTextChunk.formatting.formatFlags & FormattingFlags.underlinePerWord) && isWhiteSpaceMB(chr)))
402 						_drawUnderlineSegment(currTextChunk.formatting.formatFlags, currTextChunk.formatting.font.size, pX, 
403 								pX + chrInfo.xadvance, currTextChunk.formatting.color);
404 					_drawOtherLines(currTextChunk.formatting.formatFlags, currTextChunk.formatting.font.size, pX, 
405 							pX + chrInfo.xadvance, currTextChunk.formatting.color);
406 					pX += chrInfo.xadvance + currTextChunk.formatting.getKerning(prevChar, chr);
407 					currCharPos++;
408 					prevChar = chr;
409 				}
410 			}
411 		}
412 		Point renderTarget = Point(pos.left,pos.top);
413 		//Offset text vertically in needed
414 		if (text.formatting.formatFlags & FormattingFlags.slHorizCenter) {
415 			renderTarget.y += (pos.height / 2) - (text.font.size);
416 		}
417 		//Offset text to the right if needed
418 		if (pos.width > textWidth) {
419 			switch (text.formatting.formatFlags & FormattingFlags.justifyMask) {
420 				case FormattingFlags.centerJustify:
421 					renderTarget.x += (pos.width - textWidth) / 2;
422 					break;
423 				case FormattingFlags.rightJustify:
424 					renderTarget.x += pos.width - textWidth;
425 					break;
426 				default: break;
427 			}
428 		}
429 		Box textSlice = Box(offset, lineOffset + text.formatting.offsetV, workPad.width - 1, workPad.height - 1);
430 		if (textSlice.width > pos.width) textSlice.width = pos.width;	//clamp down text width
431 		if (textSlice.height > pos.height) textSlice.height = pos.height;	//clamp down text height
432 		if (textSlice.width > 0 && textSlice.height > 0)
433 			bitBLT(renderTarget, workPad, textSlice);
434 		return status;
435 	}
436 	/* ///Draws text to the given point. DEPRECATED!
437 	deprecated public void drawText(int x, int y, dstring text, Fontset!(Bitmap8Bit) fontset, uint style = 0) pure {
438 		const int length = fontset.getTextLength(text);
439 		//writeln(text);
440 		/+if(style == 0){
441 			x = x - (length / 2);
442 			y -= fontset.getSize() / 2;
443 		}else if(style == 2){
444 			y -= fontset.getSize();
445 		}+/
446 		if(style & FontFormat.HorizCentered)
447 			x = x - (length / 2);
448 		if(style & FontFormat.VertCentered)
449 			y -= fontset.size / 2;
450 		foreach(dchar c ; text){
451 			const Font.Char chinfo = fontset.chars(c);
452 			const Coordinate letterSlice = Coordinate(chinfo.x, chinfo.y, chinfo.x + chinfo.width, chinfo.y + chinfo.height);
453 			insertBitmapSlice(x + chinfo.xoffset, y + chinfo.yoffset, fontset.pages[chinfo.page], letterSlice);
454 			x += chinfo.xadvance;
455 		}
456 	} */
457 	/* ///Inserts a bitmap using blitter. DEPRECATED
458 	deprecated public void insertBitmap(int x, int y, Bitmap8Bit bitmap) pure {
459 		ubyte* psrc = bitmap.getPtr, pdest = output.getPtr;
460 		pdest += x + output.width * y;
461 		int length = bitmap.width;
462 		for(int iy ; iy < bitmap.height ; iy++){
463 			compose.blitter(psrc,pdest,length);
464 			psrc += length;
465 			pdest += output.width;
466 		}
467 	} */
468 }
469 /**
470  * Font formatting flags.
471  */
472 enum FontFormat : uint {
473 	HorizCentered			=	0x1,
474 	VertCentered			=	0x2,
475 	RightJustified			=	0x10,
476 	SingleLine				=	0x100,	///Forces text as single line
477 	
478 }
479 /**
480  * Text drawing return flags.
481  */
482 enum TextDrawStatus : uint {
483 	CharacterNotFound		=	0x01_00,	///Set if there's any character that was not found in the character set
484 	LHSOutOfBound			=	0x00_01,	///Left hand side out of bound
485 	RHSOutOfBound			=	0x00_02,	///Right hand side out of bound
486 	TPOutOfBound			=	0x00_04,	///Top portion out of bound
487 	BPOutOfBound			=	0x00_08,	///Bottom portion out of bound
488 	TooMuchOffset			=	0x1_00_00,
489 	TooMuchLineOffset		=	0x1_00_01,
490 }