239 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			239 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | package kha; | ||
|  | 
 | ||
|  | import haxe.ds.Vector; | ||
|  | import haxe.io.Bytes; | ||
|  | import kha.graphics2.truetype.StbTruetype; | ||
|  | import kha.graphics4.TextureFormat; | ||
|  | import kha.graphics4.Usage; | ||
|  | 
 | ||
|  | class AlignedQuad { | ||
|  | 	public function new() {} | ||
|  | 
 | ||
|  | 	// top-left | ||
|  | 	public var x0: Float; | ||
|  | 	public var y0: Float; | ||
|  | 	public var s0: Float; | ||
|  | 	public var t0: Float; | ||
|  | 
 | ||
|  | 	// bottom-right | ||
|  | 	public var x1: Float; | ||
|  | 	public var y1: Float; | ||
|  | 	public var s1: Float; | ||
|  | 	public var t1: Float; | ||
|  | 
 | ||
|  | 	public var xadvance: Float; | ||
|  | } | ||
|  | 
 | ||
|  | class KravurImage { | ||
|  | 	var mySize: Float; | ||
|  | 
 | ||
|  | 	var chars: Vector<Stbtt_bakedchar>; | ||
|  | 	var texture: Image; | ||
|  | 
 | ||
|  | 	public var width: Int; | ||
|  | 	public var height: Int; | ||
|  | 
 | ||
|  | 	var baseline: Float; | ||
|  | 
 | ||
|  | 	public static var charBlocks: Array<Int>; | ||
|  | 
 | ||
|  | 	public function new(size: Int, ascent: Int, descent: Int, lineGap: Int, width: Int, height: Int, chars: Vector<Stbtt_bakedchar>, pixels: Blob) { | ||
|  | 		mySize = size; | ||
|  | 		this.width = width; | ||
|  | 		this.height = height; | ||
|  | 		this.chars = chars; | ||
|  | 		baseline = ascent; | ||
|  | 		for (char in chars) { | ||
|  | 			char.yoff += baseline; | ||
|  | 		} | ||
|  | 		texture = Image.create(width, height, TextureFormat.L8); | ||
|  | 		var bytes = texture.lock(); | ||
|  | 		var pos: Int = 0; | ||
|  | 		for (y in 0...height) | ||
|  | 			for (x in 0...width) { | ||
|  | 				bytes.set(pos, pixels.readU8(pos)); | ||
|  | 				++pos; | ||
|  | 			} | ||
|  | 		texture.unlock(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function getTexture(): Image { | ||
|  | 		return texture; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function getBakedQuad(q: AlignedQuad, char_index: Int, xpos: Float, ypos: Float): AlignedQuad { | ||
|  | 		if (char_index >= chars.length) | ||
|  | 			return null; | ||
|  | 		var ipw: Float = 1.0 / width; | ||
|  | 		var iph: Float = 1.0 / height; | ||
|  | 		var b = chars[char_index]; | ||
|  | 		if (b == null) | ||
|  | 			return null; | ||
|  | 		var round_x: Int = Math.round(xpos + b.xoff); | ||
|  | 		var round_y: Int = Math.round(ypos + b.yoff); | ||
|  | 
 | ||
|  | 		q.x0 = round_x; | ||
|  | 		q.y0 = round_y; | ||
|  | 		q.x1 = round_x + b.x1 - b.x0; | ||
|  | 		q.y1 = round_y + b.y1 - b.y0; | ||
|  | 
 | ||
|  | 		q.s0 = b.x0 * ipw; | ||
|  | 		q.t0 = b.y0 * iph; | ||
|  | 		q.s1 = b.x1 * ipw; | ||
|  | 		q.t1 = b.y1 * iph; | ||
|  | 
 | ||
|  | 		q.xadvance = b.xadvance; | ||
|  | 
 | ||
|  | 		return q; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function getCharWidth(charIndex: Int): Float { | ||
|  | 		if (chars.length == 0) | ||
|  | 			return 0; | ||
|  | 		var offset = charBlocks[0]; | ||
|  | 		if (charIndex < offset) | ||
|  | 			return chars[0].xadvance; | ||
|  | 
 | ||
|  | 		for (i in 1...Std.int(charBlocks.length / 2)) { | ||
|  | 			var prevEnd = charBlocks[i * 2 - 1]; | ||
|  | 			var start = charBlocks[i * 2]; | ||
|  | 			if (charIndex > start - 1) | ||
|  | 				offset += start - 1 - prevEnd; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if (charIndex - offset >= chars.length) | ||
|  | 			return chars[0].xadvance; | ||
|  | 		return chars[charIndex - offset].xadvance; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function getHeight(): Float { | ||
|  | 		return mySize; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function stringWidth(str: String): Float { | ||
|  | 		var width: Float = 0; | ||
|  | 		for (c in 0...str.length) { | ||
|  | 			width += getCharWidth(str.charCodeAt(c)); | ||
|  | 		} | ||
|  | 		return width; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function charactersWidth(characters: Array<Int>, start: Int, length: Int): Float { | ||
|  | 		var width: Float = 0; | ||
|  | 		for (i in start...start + length) { | ||
|  | 			width += getCharWidth(characters[i]); | ||
|  | 		} | ||
|  | 		return width; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function getBaselinePosition(): Float { | ||
|  | 		return baseline; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | class Kravur implements Resource { | ||
|  | 	// Do not put static data in Kravur because it is overwritten | ||
|  | 	// when <canvas> is used - but it's still used by the overwriting class. | ||
|  | 	var oldGlyphs: Array<Int>; | ||
|  | 	var blob: Blob; | ||
|  | 	var images: Map<Int, KravurImage> = new Map(); | ||
|  | 	var fontIndex: Int; | ||
|  | 
 | ||
|  | 	public function new(blob: Blob, fontIndex: Int = 0) { | ||
|  | 		this.blob = blob; | ||
|  | 		this.fontIndex = fontIndex; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function fromBytes(bytes: Bytes, fontIndex: Int = 0): Kravur { | ||
|  | 		return new Kravur(Blob.fromBytes(bytes), fontIndex); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function _get(fontSize: Int): KravurImage { | ||
|  | 		var glyphs = kha.graphics2.Graphics.fontGlyphs; | ||
|  | 
 | ||
|  | 		if (glyphs != oldGlyphs) { | ||
|  | 			oldGlyphs = glyphs; | ||
|  | 			// save first/last chars of sequences | ||
|  | 			KravurImage.charBlocks = [glyphs[0]]; | ||
|  | 			var nextChar = KravurImage.charBlocks[0] + 1; | ||
|  | 			for (i in 1...glyphs.length) { | ||
|  | 				if (glyphs[i] != nextChar) { | ||
|  | 					KravurImage.charBlocks.push(glyphs[i - 1]); | ||
|  | 					KravurImage.charBlocks.push(glyphs[i]); | ||
|  | 					nextChar = glyphs[i] + 1; | ||
|  | 				} | ||
|  | 				else | ||
|  | 					nextChar++; | ||
|  | 			} | ||
|  | 			KravurImage.charBlocks.push(glyphs[glyphs.length - 1]); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		var imageIndex = fontIndex * 10000000 + fontSize * 10000 + glyphs.length; | ||
|  | 		if (!images.exists(imageIndex)) { | ||
|  | 			var width: Int = 64; | ||
|  | 			var height: Int = 32; | ||
|  | 			var baked = new Vector<Stbtt_bakedchar>(glyphs.length); | ||
|  | 			for (i in 0...baked.length) { | ||
|  | 				baked[i] = new Stbtt_bakedchar(); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			var pixels: Blob = null; | ||
|  | 
 | ||
|  | 			var offset = StbTruetype.stbtt_GetFontOffsetForIndex(blob, fontIndex); | ||
|  | 			if (offset == -1) { | ||
|  | 				offset = StbTruetype.stbtt_GetFontOffsetForIndex(blob, 0); | ||
|  | 			} | ||
|  | 			var status: Int = -1; | ||
|  | 			while (status <= 0) { | ||
|  | 				if (height < width) | ||
|  | 					height *= 2; | ||
|  | 				else | ||
|  | 					width *= 2; | ||
|  | 				pixels = Blob.alloc(width * height); | ||
|  | 				status = StbTruetype.stbtt_BakeFontBitmap(blob, offset, fontSize, pixels, width, height, glyphs, baked); | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// TODO: Scale pixels down if they exceed the supported texture size | ||
|  | 
 | ||
|  | 			var info = new Stbtt_fontinfo(); | ||
|  | 			StbTruetype.stbtt_InitFont(info, blob, offset); | ||
|  | 
 | ||
|  | 			var metrics = StbTruetype.stbtt_GetFontVMetrics(info); | ||
|  | 			var scale = StbTruetype.stbtt_ScaleForPixelHeight(info, fontSize); | ||
|  | 			var ascent = Math.round(metrics.ascent * scale); // equals baseline | ||
|  | 			var descent = Math.round(metrics.descent * scale); | ||
|  | 			var lineGap = Math.round(metrics.lineGap * scale); | ||
|  | 
 | ||
|  | 			var image = new KravurImage(Std.int(fontSize), ascent, descent, lineGap, width, height, baked, pixels); | ||
|  | 			images[imageIndex] = image; | ||
|  | 			return image; | ||
|  | 		} | ||
|  | 		return images[imageIndex]; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function height(fontSize: Int): Float { | ||
|  | 		return _get(fontSize).getHeight(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function width(fontSize: Int, str: String): Float { | ||
|  | 		return _get(fontSize).stringWidth(str); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function widthOfCharacters(fontSize: Int, characters: Array<Int>, start: Int, length: Int): Float { | ||
|  | 		return _get(fontSize).charactersWidth(characters, start, length); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function baseline(fontSize: Int): Float { | ||
|  | 		return _get(fontSize).getBaselinePosition(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function setFontIndex(fontIndex: Int): Void { | ||
|  | 		this.fontIndex = fontIndex; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public function unload(): Void { | ||
|  | 		blob = null; | ||
|  | 		images = null; | ||
|  | 	} | ||
|  | } |