An Org Mode to ESCPOS converter
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

174 lines
6.5 KiB

  1. import sys
  2. import textwrap
  3. # Output a binary file which a ESCPOS thermal printer will understand
  4. # References:
  5. # - Command manual PDF, in this repository
  6. # - https://www.neodynamic.com/articles/How-to-print-raw-ESC-POS-commands-from-Javascript/
  7. outputFilename = "output.bin"
  8. esc = '\x1B'; # ESC byte in hex notation
  9. newLine = '\x0A'; # LF byte in hex notation
  10. # Emphasized + Double-height + Double-width mode selected (ESC ! (8 + 16 + 32)) 56 dec => 38 hex
  11. TextStyle_Large = '\x38'
  12. TextStyle_DoubleWidth = '\x20'
  13. TextStyle_RegularEmphasis = '\x08' # Regular size + emphasis (0 + 8)
  14. TextStyle_Regular = '\x00'
  15. # Format specifier, column width
  16. lineWrapColumns = {TextStyle_Large:16, TextStyle_DoubleWidth:16,
  17. TextStyle_RegularEmphasis:32, TextStyle_Regular:32}
  18. gOutputBuffer = ''
  19. gCurrentTextStyle = TextStyle_Regular
  20. def writeOutputBuffer():
  21. global gOutputBuffer
  22. encodedString = gOutputBuffer.encode("ascii")
  23. # I think the printer needs to be told this is the desired format
  24. # encodedString = gOutputBuffer.encode("gb18030")
  25. print("Outputting to {}".format(outputFilename))
  26. print("Use the following command (replacing [your printer]) to print:\n"
  27. "lpr -P [your printer] output.bin")
  28. outFile = open(outputFilename, "wb")
  29. outFile.write(encodedString)
  30. outFile.close()
  31. def setTextStyle(styleSpecifier):
  32. global gCurrentTextStyle
  33. if styleSpecifier not in lineWrapColumns:
  34. print("Warning: style specifier '{}' not fully supported".format(styleSpecifier))
  35. outputRaw(esc + '!' + styleSpecifier)
  36. gCurrentTextStyle = styleSpecifier
  37. def outputInitializationCode():
  38. # Initializes the printer (ESC @)
  39. outputRaw(esc + "@")
  40. setTextStyle(TextStyle_Regular)
  41. # Don't fix up newlines or anything
  42. def outputRaw(outString):
  43. global gOutputBuffer
  44. gOutputBuffer += outString
  45. # Wrap lines and convert newlines to the appropriate format character
  46. # TODO: Make empty lines newlines still show up instead of being removed by this
  47. def outputTextBlock(outString):
  48. global gOutputBuffer
  49. wrapWidth = lineWrapColumns[gCurrentTextStyle]
  50. # Fix newlines and wrap
  51. # lines = outString.replace('\n', newLine).split(newLine)
  52. # We will be adding this newline later, and it will just confuse the splitter.
  53. if outString[-1] == '\n':
  54. outString = outString[:-1]
  55. lines = outString.split("\n")
  56. wrappedLines = []
  57. for line in lines:
  58. if not line:
  59. wrappedLines.append('')
  60. wrappedLines += textwrap.wrap(line, width=wrapWidth)
  61. # exit()
  62. for line in wrappedLines:
  63. if line:
  64. gOutputBuffer += line + newLine
  65. else:
  66. gOutputBuffer += newLine
  67. def lineHasTagExactly(line, tag):
  68. return len(line) >= len(tag) and tag in line[:len(tag)]
  69. def lineGetTaggedValue(line, tag):
  70. return line[len(tag):]
  71. def orgModeToEscPos(orgLines):
  72. for line in orgLines:
  73. if lineHasTagExactly(line, '#+TITLE:'):
  74. setTextStyle(TextStyle_Large)
  75. outputTextBlock("\n" + lineGetTaggedValue(line, '#+TITLE:'))
  76. # Headings
  77. elif lineHasTagExactly(line, '* '):
  78. setTextStyle(TextStyle_DoubleWidth)
  79. outputTextBlock("\n" + lineGetTaggedValue(line, '* '))
  80. elif lineHasTagExactly(line, '** '):
  81. setTextStyle(TextStyle_RegularEmphasis)
  82. outputTextBlock("\n" + lineGetTaggedValue(line, '** '))
  83. # All deeper headings are just bolded and have a space at the start
  84. elif len(line) > 2 and line[0] == "*" and line[1] == "*":
  85. setTextStyle(TextStyle_RegularEmphasis)
  86. outputTextBlock("\n" + line[line.find(" "):])
  87. # Body text
  88. else:
  89. setTextStyle(TextStyle_Regular)
  90. outputTextBlock(line)
  91. def main(filenameToConvert):
  92. outputInitializationCode()
  93. orgFile = open(filenameToConvert, "r")
  94. orgLines = orgFile.readlines()
  95. orgFile.close()
  96. orgModeToEscPos(orgLines)
  97. writeOutputBuffer()
  98. return
  99. # Bit image mode (doesn't work)
  100. # outputRaw(esc + '*' + TextStyle_Regular + TextStyle_RegularEmphasis + TextStyle_RegularEmphasis +
  101. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  102. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  103. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  104. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  105. # '\x08\x00\x08\x08\x00\x08\x00\x08')
  106. # outputRaw('\x1D' + '\x2A' + TextStyle_DoubleWidth + TextStyle_DoubleWidth + TextStyle_RegularEmphasis +
  107. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  108. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  109. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  110. # '\x08\x00\x08\x08\x00\x08\x00\x08' +
  111. # '\x08\x00\x08\x08\x00\x08\x00\x08')
  112. # Style showcase (won't work properly anymore)
  113. # outputRaw( esc + '!' + TextStyle_Regular) #Regular size
  114. # outputTextBlock(journalString)
  115. # Emphasized + Double-height + Double-width mode selected (ESC ! (8 + 16 + 32)) 56 dec => 38 hex
  116. # outputRaw( esc + '!' + TextStyle_Large)
  117. # outputTextBlock("cookies and milk")
  118. # outputRaw( esc + '!' + '\x01') # Small size
  119. # outputTextBlock("Small cookies and milk")
  120. # outputRaw( esc + '!' + '\x09') # Small size + emphasis (1 + 8)
  121. # outputTextBlock("Small cookies and milk?")
  122. # outputRaw( esc + '!' + '\x10') # Double height regular
  123. # outputTextBlock("Tall text")
  124. # outputRaw( esc + '!' + '\x11') # Double height small
  125. # outputTextBlock("Small tall text")
  126. # outputRaw( esc + '!' + TextStyle_DoubleWidth) # Double width regular
  127. # outputTextBlock("Double width regular")
  128. # outputRaw( esc + '-') # Double width small underline (20h + 80h + 1h)
  129. # outputTextBlock("Double width regular underline?")
  130. # Something about this confuses my printer such that it starts printing corrupt data
  131. # From package escpos
  132. # escposStrOutputter = printer.Dummy()
  133. # It does not actually support Japanese like this
  134. # escposStrOutputter.text("このは、テストです。素晴しいですね。\n")
  135. # escposStrOutputter.text(journalString)
  136. # escposStrOutputter.image("test384.png")
  137. # print(escposStrOutputter.output)
  138. # outputRaw(escposStrOutputter.output)
  139. # outFile = open("output.bin", "wb")
  140. # outFile.write(escposStrOutputter.output)
  141. # outFile.close()
  142. if __name__ == "__main__":
  143. if len(sys.argv) != 2:
  144. print("Org Mode to ESCPOS\nUsage:\n\tpython3 ThermalPrinterConverter.py MyDoc.org")
  145. sys.exit(1)
  146. else:
  147. main(sys.argv[1])