<commit_before>package de.markusfisch.android.binaryeye.zxing

import android.graphics.Bitmap
import com.google.zxing.*
import com.google.zxing.common.BitMatrix
import com.google.zxing.common.HybridBinarizer
import java.util.*

class Zxing(possibleResultPoint: ResultPointCallback? = null) {
	private val hints = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java)
	private val multiFormatReader = MultiFormatReader()

	init {
		possibleResultPoint?.let {
			hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = it
		}
	}

	fun updateHints(tryHarder: Boolean, formats: Set<String>) {
		if (tryHarder) {
			hints[DecodeHintType.TRY_HARDER] = java.lang.Boolean.TRUE
		} else {
			hints.remove(DecodeHintType.TRY_HARDER)
		}
		setDecodeFormats(formats)
	}

	private fun setDecodeFormats(formats: Set<String>) {
		hints[DecodeHintType.POSSIBLE_FORMATS] = EnumSet.noneOf(
			BarcodeFormat::class.java
		).apply {
			addAll(
				EnumSet.copyOf(
					formats.map { BarcodeFormat.valueOf(it) }
				)
			)
		}
	}

	fun decode(
		yuvData: ByteArray,
		width: Int,
		height: Int,
		invert: Boolean = false
	): Result? = decodeLuminanceSource(
		PlanarYUVLuminanceSource(
			yuvData,
			width,
			height,
			0,
			0,
			width,
			height,
			false
		),
		invert
	)

	fun decodePositiveNegative(bitmap: Bitmap): Result? =
		decode(bitmap, false) ?: decode(bitmap, true)

	private fun decode(
		bitmap: Bitmap,
		invert: Boolean = false
	): Result? {
		val width = bitmap.width
		val height = bitmap.height
		val pixels = IntArray(width * height)
		if (bitmap.config != Bitmap.Config.ARGB_8888) {
			bitmap.copy(Bitmap.Config.ARGB_8888, true)
		} else {
			bitmap
		}.getPixels(pixels, 0, width, 0, 0, width, height)
		return decodeLuminanceSource(
			RGBLuminanceSource(width, height, pixels),
			invert
		)
	}

	private fun decodeLuminanceSource(
		source: LuminanceSource,
		invert: Boolean
	): Result? = decodeLuminanceSource(
		if (invert) {
			source.invert()
		} else {
			source
		}
	)

	private fun decodeLuminanceSource(source: LuminanceSource): Result? = try {
		multiFormatReader.decode(
			BinaryBitmap(HybridBinarizer(source)),
			hints
		)
	} catch (e: Exception) {
		// Usually it's bad practice to blindly catch *all* exceptions
		// because this can hide problems we want to know about. But
		// since ZXing has some errors (like all software) and I don't
		// want this app to break in the hands of my users, which won't
		// see the stack trace anyway, it's better to just catch all
		// exceptions here and live on.
		null
	} finally {
		multiFormatReader.reset()
	}
}

private const val BLACK = 0xff000000.toInt()
private const val WHITE = 0xffffffff.toInt()

fun encodeAsBitmap(
	text: String,
	format: BarcodeFormat,
	width: Int,
	height: Int,
	hints: EnumMap<EncodeHintType, Any>? = null
): Bitmap? {
	val bitMatrix = encode(text, format, hints, width, height)
	val w = bitMatrix.width
	val h = bitMatrix.height
	val pixels = IntArray(w * h)
	var offset = 0
	for (y in 0 until h) {
		for (x in 0 until w) {
			pixels[offset + x] = if (bitMatrix.get(x, y)) {
				BLACK
			} else {
				WHITE
			}
		}
		offset += w
	}
	val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
	bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
	return bitmap
}

fun encodeAsSvg(
	text: String,
	format: BarcodeFormat,
	hints: EnumMap<EncodeHintType, Any>? = null
): String {
	val bitMatrix = encode(text, format, hints)
	val sb = StringBuilder()
	val w = bitMatrix.width
	var h = bitMatrix.height
	val moduleHeight = if (h == 1) w / 2 else 1
	for (y in 0 until h) {
		for (x in 0 until w) {
			if (bitMatrix.get(x, y)) {
				sb.append(" M${x},${y}h1v${moduleHeight}h-1z")
			}
		}
	}
	h *= moduleHeight
	return """<svg width="$w" height="$h"
viewBox="0 0 $w $h"
xmlns="http://www.w3.org/2000/svg">
<path d="$sb"/>
</svg>
"""
}

fun encodeAsText(
	text: String,
	format: BarcodeFormat,
	hints: EnumMap<EncodeHintType, Any>? = null
): String {
	val bitMatrix = encode(text, format, hints)
	val w = bitMatrix.width
	val h = bitMatrix.height
	val sb = StringBuilder()
	for (y in 0 until h) {
		for (x in 0 until w) {
			sb.append(if (bitMatrix.get(x, y)) "█" else " ")
		}
		sb.append("\n")
	}
	return sb.toString()
}

private fun encode(
	text: String,
	format: BarcodeFormat,
	encodeHints: EnumMap<EncodeHintType, Any>? = null,
	width: Int = 0,
	height: Int = 0
): BitMatrix = MultiFormatWriter().encode(
	text, format, width, height,
	(encodeHints ?: EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)).apply {
		if (!contains(EncodeHintType.CHARACTER_SET)) {
			this[EncodeHintType.CHARACTER_SET] = "utf-8"
		}
	}
)<commit_msg>Improve plain text output of generated barcodes<commit_after>package de.markusfisch.android.binaryeye.zxing

import android.graphics.Bitmap
import com.google.zxing.*
import com.google.zxing.common.BitMatrix
import com.google.zxing.common.HybridBinarizer
import java.util.*

class Zxing(possibleResultPoint: ResultPointCallback? = null) {
	private val hints = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java)
	private val multiFormatReader = MultiFormatReader()

	init {
		possibleResultPoint?.let {
			hints[DecodeHintType.NEED_RESULT_POINT_CALLBACK] = it
		}
	}

	fun updateHints(tryHarder: Boolean, formats: Set<String>) {
		if (tryHarder) {
			hints[DecodeHintType.TRY_HARDER] = java.lang.Boolean.TRUE
		} else {
			hints.remove(DecodeHintType.TRY_HARDER)
		}
		setDecodeFormats(formats)
	}

	private fun setDecodeFormats(formats: Set<String>) {
		hints[DecodeHintType.POSSIBLE_FORMATS] = EnumSet.noneOf(
			BarcodeFormat::class.java
		).apply {
			addAll(
				EnumSet.copyOf(
					formats.map { BarcodeFormat.valueOf(it) }
				)
			)
		}
	}

	fun decode(
		yuvData: ByteArray,
		width: Int,
		height: Int,
		invert: Boolean = false
	): Result? = decodeLuminanceSource(
		PlanarYUVLuminanceSource(
			yuvData,
			width,
			height,
			0,
			0,
			width,
			height,
			false
		),
		invert
	)

	fun decodePositiveNegative(bitmap: Bitmap): Result? =
		decode(bitmap, false) ?: decode(bitmap, true)

	private fun decode(
		bitmap: Bitmap,
		invert: Boolean = false
	): Result? {
		val width = bitmap.width
		val height = bitmap.height
		val pixels = IntArray(width * height)
		if (bitmap.config != Bitmap.Config.ARGB_8888) {
			bitmap.copy(Bitmap.Config.ARGB_8888, true)
		} else {
			bitmap
		}.getPixels(pixels, 0, width, 0, 0, width, height)
		return decodeLuminanceSource(
			RGBLuminanceSource(width, height, pixels),
			invert
		)
	}

	private fun decodeLuminanceSource(
		source: LuminanceSource,
		invert: Boolean
	): Result? = decodeLuminanceSource(
		if (invert) {
			source.invert()
		} else {
			source
		}
	)

	private fun decodeLuminanceSource(source: LuminanceSource): Result? = try {
		multiFormatReader.decode(
			BinaryBitmap(HybridBinarizer(source)),
			hints
		)
	} catch (e: Exception) {
		// Usually it's bad practice to blindly catch *all* exceptions
		// because this can hide problems we want to know about. But
		// since ZXing has some errors (like all software) and I don't
		// want this app to break in the hands of my users, which won't
		// see the stack trace anyway, it's better to just catch all
		// exceptions here and live on.
		null
	} finally {
		multiFormatReader.reset()
	}
}

private const val BLACK = 0xff000000.toInt()
private const val WHITE = 0xffffffff.toInt()

fun encodeAsBitmap(
	text: String,
	format: BarcodeFormat,
	width: Int,
	height: Int,
	hints: EnumMap<EncodeHintType, Any>? = null
): Bitmap? {
	val bitMatrix = encode(text, format, hints, width, height)
	val w = bitMatrix.width
	val h = bitMatrix.height
	val pixels = IntArray(w * h)
	var offset = 0
	for (y in 0 until h) {
		for (x in 0 until w) {
			pixels[offset + x] = if (bitMatrix.get(x, y)) {
				BLACK
			} else {
				WHITE
			}
		}
		offset += w
	}
	val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
	bitmap.setPixels(pixels, 0, w, 0, 0, w, h)
	return bitmap
}

fun encodeAsSvg(
	text: String,
	format: BarcodeFormat,
	hints: EnumMap<EncodeHintType, Any>? = null
): String {
	val bitMatrix = encode(text, format, hints)
	val sb = StringBuilder()
	val w = bitMatrix.width
	var h = bitMatrix.height
	val moduleHeight = if (h == 1) w / 2 else 1
	for (y in 0 until h) {
		for (x in 0 until w) {
			if (bitMatrix.get(x, y)) {
				sb.append(" M${x},${y}h1v${moduleHeight}h-1z")
			}
		}
	}
	h *= moduleHeight
	return """<svg width="$w" height="$h"
viewBox="0 0 $w $h"
xmlns="http://www.w3.org/2000/svg">
<path d="$sb"/>
</svg>
"""
}

fun encodeAsText(
	text: String,
	format: BarcodeFormat,
	hints: EnumMap<EncodeHintType, Any>? = null
): String {
	val bitMatrix = encode(text, format, hints)
	val w = bitMatrix.width
	val h = bitMatrix.height
	val sb = StringBuilder()
    for (y in 0 until h step 2) {
        for (x in 0 until w) {
            val tp = bitMatrix.get(y, x)
            var bt = y + 1 >= h || bitMatrix.get(y + 1, x);
            if (tp && bt) {
                sb.append(' ')//'\u0020'
            } else if (tp) {
                sb.append('▄')//'\u2584'
            } else if (bt) {
                sb.append('▀')//'\u2580'
            } else {
                sb.append('█')//'\u2588'
            }
        }
        sb.append('\n')
    }
	return sb.toString()
}

private fun encode(
	text: String,
	format: BarcodeFormat,
	encodeHints: EnumMap<EncodeHintType, Any>? = null,
	width: Int = 0,
	height: Int = 0
): BitMatrix = MultiFormatWriter().encode(
	text, format, width, height,
	(encodeHints ?: EnumMap<EncodeHintType, Any>(EncodeHintType::class.java)).apply {
		if (!contains(EncodeHintType.CHARACTER_SET)) {
			this[EncodeHintType.CHARACTER_SET] = "utf-8"
		}
	}
)
{'fraction_non_alphanumeric': 0.12679995856210505, 'fraction_numerical': 0.012327773749093546, 'mean_word_length': 3.479814385150812, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2143571', 'n_tokens_mistral': 3841, 'n_tokens_neox': 3483, 'n_words': 896}