1 /**
2 Copyright: Copyright (c) 2017-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.graphics.font.bitmapfontloader;
7 
8 import dlib.image.io.png;
9 import voxelman.math;
10 import voxelman.graphics.image.crop;
11 import voxelman.graphics.bitmap;
12 import voxelman.graphics.font.font;
13 import voxelman.graphics.textureatlas;
14 
15 import std.stdio;
16 
17 void loadBitmapFont(Font* font, TextureAtlas texAtlas, in string chars)
18 {
19 	auto image = new Bitmap(loadPNG(font.filename));
20 	if (image.width == 0 || image.height == 0) return;
21 
22 	auto sentinelPixel = image[0,0];
23 	//writefln("sent %s", sentinelPixel);
24 
25 	ivec2 cursor;
26 
27 	int nextCursorX;
28 	// find a pixel past the end of current char
29 	void checkNextChar()
30 	{
31 		nextCursorX = cursor.x+1;
32 		while (nextCursorX < image.width && image[nextCursorX, cursor.y] != sentinelPixel)
33 		{
34 			++nextCursorX;
35 		}
36 	}
37 
38 	int nextLineY;
39 	void checkNextLine()
40 	{
41 		if (cursor.x == 0) // new line
42 		{
43 			nextLineY = cursor.y+1;
44 			while (nextLineY < image.height && image[0, nextLineY] != sentinelPixel)
45 			{
46 				++nextLineY;
47 			}
48 		}
49 	}
50 
51 	void advanceCursor()
52 	{
53 		if (nextCursorX >= image.width)
54 		{
55 			cursor.x = 0;
56 			cursor.y = nextLineY;
57 		}
58 		else
59 		{
60 			cursor.x = nextCursorX;
61 
62 			// check if end of line
63 			// either sentinel + end or two sentinels
64 			int rightPixel = cursor.x + 1;
65 			if (rightPixel == image.width || image[rightPixel, cursor.y] == sentinelPixel)
66 			{
67 				// newline
68 				cursor.x = 0;
69 				cursor.y = nextLineY;
70 			}
71 		}
72 	}
73 
74 	bool isValidCursor()
75 	{
76 		return cursor.x < image.width && cursor.y < image.height;
77 	}
78 
79 	int maxGlyphWidth = 0;
80 	int maxGlyphHeight = 0;
81 
82 	void loadGlyph(dchar glyph)
83 	{
84 		// load glyph
85 		auto glyphStart = cursor + ivec2(1,0);
86 		auto glyphStartCopy = glyphStart;
87 
88 		int glyphWidth = nextCursorX - cursor.x - 1;
89 		int glyphHeight = nextLineY - cursor.y;
90 		auto glyphSize = ivec2(glyphWidth, glyphHeight);
91 
92 		// crop all transparent pixels
93 		cropImage(image, glyphStart, glyphSize);
94 
95 		// write to texture
96 		ivec2 atlasPos = texAtlas.insert(image, irect(glyphStart, glyphSize));
97 
98 		ivec2 glyphOffset = glyphStart - glyphStartCopy;
99 
100 		int glyphAdvanceX = glyphSize.x + 1;
101 		int glyphAdvanceY = glyphSize.y + 1;
102 
103 		auto metrics = GlyphMetrics(
104 			glyphSize.x, glyphSize.y,
105 			glyphOffset.x, -glyphOffset.y,
106 			glyphAdvanceX, glyphAdvanceY);
107 
108 		maxGlyphWidth = max(maxGlyphWidth, glyphSize.x);
109 		// use uncropped height here since there can be no glyphs
110 		// that reach both top and bottom of line
111 		maxGlyphHeight = max(maxGlyphHeight, glyphHeight);
112 
113 		//writefln("glyph '%s' img pos %s size %s", glyph, glyphStart, glyphSize);
114 
115 		font.glyphs[glyph] = Glyph(atlasPos, metrics);
116 	}
117 
118 	import std.utf : byDchar;
119 	foreach (dchar glyph; chars.byDchar)
120 	{
121 		import std.uni : isWhite;
122 		if(isWhite(glyph)) continue;
123 
124 		checkNextChar();
125 		checkNextLine();
126 
127 		if (!isValidCursor()) break;
128 
129 		loadGlyph(glyph);
130 
131 		advanceCursor();
132 	}
133 
134 	font.metrics.width = maxGlyphWidth;
135 	font.metrics.height = maxGlyphHeight;
136 	font.metrics.advanceX = maxGlyphWidth + 1;
137 	font.metrics.advanceY = maxGlyphHeight + 1;
138 	//writefln("font %s", font.metrics);
139 }