본문 바로가기
작디 작은 나만의 라이브러리/EPSON 써멀 프린터 라이브러리

[신입 개발자의 '0' 번째 라이브러리] 프린터 출력 기능 구현. Text, Barcode, QR Code, Image - EPSON 써멀 프린터 라이브러리 제작기(3)

by 시니성 2024. 12. 16.
728x90

들어가며

지난 글에서는 EPSON 써멀 프린터 라이브러리의 각 연결 방식별 구현에 대해 살펴보았습니다. 이번 글에서는 프린터의 핵심 출력 기능인 텍스트, 바코드, QR 코드, 이미지 출력 기능의 구현을 상세히 분석해보겠습니다.

프린터의 출력 기능은 크게 두 가지 계층으로 구현되어 있습니다:

  1. PrinterDialect: 각 기능별 바이트 코드 생성
  2. T83PrinterAdaptor: 생성된 바이트 코드를 실제 프린터로 전송

이러한 계층 구조는 명령어 생성과 전송을 분리하여 유지보수성과 확장성을 높여줍니다.

1. 텍스트 출력 구현

PrinterDialect의 텍스트 관련 명령어

object PrinterDialect {
    private val FONT_SIZE_CMD: ByteArray = byteArrayOf(GS, '!'.code.toByte())
    private val REVERSE_WB_CMD: ByteArray = byteArrayOf(GS, 'B'.code.toByte())
    private val FONT_EMPHASIZE_CMD: ByteArray = byteArrayOf(ESC, 'E'.code.toByte())
    private val FONT_UNDERLINE_CMD: ByteArray = byteArrayOf(ESC, '-'.code.toByte())
    private val ALIGNMENT_CMD: ByteArray = byteArrayOf(ESC, 'a'.code.toByte())

    internal fun createFontSizeCode(width: Int = 0, height: Int = 0): ByteArray =
        FONT_SIZE_CMD + byteArrayOf((height + width).toByte())

    internal fun createFontReverseWbCode(flag: Int = OFF_FLAG): ByteArray =
        REVERSE_WB_CMD + byteArrayOf(flag.toByte())

    internal fun createFontEmphasizeCode(flag: Int = OFF_FLAG): ByteArray =
        FONT_EMPHASIZE_CMD + byteArrayOf(flag.toByte())

    internal fun createFontUnderlineCode(flag: Int = OFF_FLAG): ByteArray =
        FONT_UNDERLINE_CMD + byteArrayOf(flag.toByte())
}

텍스트 출력과 관련된 모든 명령어는 PrinterDialect에 정의되어 있습니다. 각 명령어는:

  • 글자 크기 조절
  • 반전 효과
  • 강조(볼드) 효과
  • 밑줄 효과
  • 정렬

등의 기능을 제공합니다.

T83PrinterAdaptor의 텍스트 출력 구현

abstract class T83PrinterAdaptor : EpsonPrinterAdaptor {
    internal fun printText(
        data: String,
        alignment: Alignment,
        options: Set<TextStyle>,
        textHeight: Int,
        textWidth: Int
    ) {
        outputWithResetStyle(
            PrinterDialect.createAlignmentCode(alignment.code)
            + options.map { it.code }.flatMap { it.toList() }.toByteArray()
            + TextSize(textWidth, textHeight).code
            + data.toEucKrByteArray()
        )
    }
}

텍스트 출력 구현의 특징:

  1. 스타일 초기화 후 출력
  2. EUC-KR 인코딩을 사용한 한글 지원
  3. TextSize 클래스를 통한 크기 유효성 검사

2. 바코드 출력 구현

PrinterDialect의 바코드 관련 명령어

object PrinterDialect {
    private val PRINT_BARCODE_CMD: ByteArray = byteArrayOf(GS, 'k'.code.toByte())
    private val BARCODE_HEIGHT_CMD: ByteArray = byteArrayOf(GS, 'h'.code.toByte())
    private val BARCODE_WIDTH_CMD: ByteArray = byteArrayOf(GS, 'w'.code.toByte())
    private val BARCODE_HRI_CMD: ByteArray = byteArrayOf(GS, 'H'.code.toByte())

    internal fun createPrintCode128Code(data: String, d2: Code128CodeSet = Code128CodeSet.A): ByteArray {
        val m = 73
        val d1 = 123
        val parameter = byteArrayOf(d1.toByte(), d2.code.toByte()) + data.toEucKrByteArray()
        val n = parameter.size

        return PRINT_BARCODE_CMD + byteArrayOf(m.toByte(), n.toByte()) + parameter
    }
}

바코드 출력은:

  1. 바코드 높이/너비 설정
  2. HRI(Human Readable Interpretation) 위치 설정
  3. 다양한 바코드 타입(CODE128, ITF, EAN8 등) 지원

BarcodeSize 밸류 오브젝트를 통한 크기 제어

internal class BarcodeWidth(private val size: Int) {
    init {
        require(
            size / BARCODE_WIDTH_CORRECTION_VALUE
                    in BARCODE_WIDTH_MIN..BARCODE_WIDTH_MAX
        )
        {
            "TM-T83III는 바코드 가로 길이를 ${BARCODE_WIDTH_MIN * BARCODE_WIDTH_CORRECTION_VALUE}과 " +
                    "${BARCODE_WIDTH_MAX * BARCODE_WIDTH_CORRECTION_VALUE}만 지원합니다."
        }
    }

    internal val code: ByteArray
        get() = PrinterDialect.createBarcodeWidthCode(size / BARCODE_WIDTH_CORRECTION_VALUE)
}

BarcodeSize 클래스의 특징:

  1. 크기 유효성 검사
  2. 프린터 모델별 제약사항 반영
  3. 보정값을 통한 실제 출력 크기 조정

3. QR 코드 출력 구현

PrinterDialect의 QR 코드 관련 명령어

object PrinterDialect {
    private val QR_CODE_CMD: ByteArray = byteArrayOf(GS, '('.code.toByte(), 'k'.code.toByte())

    internal fun createSelectQrCodeModelCode(): ByteArray {
        val cn: Byte = 49
        val parameter = byteArrayOf(cn, SELECT_QR_MODEL_FN, QR_MODEL_NO, n2)
        val plPh = parameter.plPh
        return QR_CODE_CMD + plPh + parameter
    }

    internal fun createQrCodeSizeCode(size: Int = 5): ByteArray {
        val cn: Byte = 49
        val parameter = byteArrayOf(cn, QR_SIZE_FN, size.toByte())
        val plPh = parameter.plPh
        return QR_CODE_CMD + plPh + parameter
    }
}

QR 코드 출력의 특징:

  1. QR 코드 모델 선택
  2. 크기 설정
  3. 오류 정정 레벨 설정
  4. 데이터 저장 및 출력 명령 분리

QR 코드 크기 관리

internal fun Int.toQrModuleSize() =
    (this * PrinterDialect.QR_SIZE_RATIO).toInt()
        .coerceIn(
            PrinterDialect.QR_MODULE_SIZE_MIN,
            PrinterDialect.QR_MODULE_SIZE_MAX
        )

internal fun Pair<Int, Int>.pickNonDefault(): Int {
    return if (this.first == this.second) {
        this.first
    } else {
        if (this.first.isNotQrSizeDefault()) {
            if (this.second.isNotQrSizeDefault()) {
                max(this.first, this.second)
            } else {
                this.first
            }
        } else {
            this.second
        }
    }
}

QR 코드 크기 관리의 특징:

  1. 모듈 사이즈 자동 계산
  2. 가로/세로 크기 자동 조정
  3. 기본값 처리 로직

4. 이미지 출력 구현

이미지 처리 과정

internal fun BufferedImage.toDitheredBitmapData(): ByteArray {
    return this.toGrayScale().toDitheredData().toBitmapData()
}

private fun BufferedImage.toGrayScale(): Array<IntArray> {
    //..grayScale 연산 코드.
    return grayScale
}

이미지 출력 구현의 특징:

  1. 그레이스케일 변환
  2. 디더링(Dithering) 처리
  3. 비트맵 데이터 변환

출력 기능별 특징 비교

1. 텍스트 출력

  • EUC-KR 인코딩으로 한글 지원
  • 다양한 스타일 옵션
  • 크기 조절의 유연성

2. 바코드 출력

  • 다양한 바코드 타입 지원
  • 크기 제약 조건 관리
  • HRI 위치 설정 기능

3. QR 코드 출력

  • 자동 크기 조정
  • 오류 정정 레벨 설정
  • 효율적인 데이터 처리

4. 이미지 출력

  • 그레이스케일 처리
  • 디더링을 통한 품질 개선

마치며

프린터의 각 출력 기능은 명령어 생성(PrinterDialect)과 실제 출력(T83PrinterAdaptor) 계층이 분리되어 있어, 아래와 같은 이점을 얻을 수 있었습니다.

  1. 새로운 연결 방식 별 독립적인 개발시 명령어 셋 재사용 가능
  2. 새로운 출력 기능 추가가 용이
  3. 프린터 모델별 제약사항 관리가 편리

다음 글에서는 프린터 상태 관리와 예외 처리에 대해 자세히 살펴보도록 하겠습니다.

728x90