commit 2ea89a0559539f44f4431a64060f217598ac9e5a Author: Lucio Andrés Illanes Albornoz Date: Tue Jan 2 16:19:07 2018 +0100 Initial commit. diff --git a/mirc2png.py b/mirc2png.py new file mode 100755 index 0000000..ab88412 --- /dev/null +++ b/mirc2png.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +# +# mirc2png -- convert ASCII w/ mIRC control codes to monospaced PNG (for EFnet #MiRCART) +# Copyright (c) 2018 Lucio Andrés Illanes Albornoz +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# + +from enum import Enum +from PIL import Image, ImageDraw, ImageFont +import string, sys + +class MiRCART: + """Abstraction over ASCIIs containing mIRC control codes""" + + # {{{ mIRC colour number to RGBA map given ^B (bold) + ColourMapBold = [ + (255, 255, 255, 255), # White + (85, 85, 85, 255), # Grey + (85, 85, 255, 255), # Light Blue + (85, 255, 85, 255), # Light Green + (255, 85, 85, 255), # Light Red + (255, 85, 85, 255), # Light Red + (255, 85, 255, 255), # Pink + (255, 255, 85, 255), # Light Yellow + (255, 255, 85, 255), # Light Yellow + (85, 255, 85, 255), # Light Green + (85, 255, 255, 255), # Light Cyan + (85, 255, 255, 255), # Light Cyan + (85, 85, 255, 255), # Light Blue + (255, 85, 255, 255), # Pink + (85, 85, 85, 255), # Grey + (255, 255, 255, 255), # White + ] + # }}} + # {{{ mIRC colour number to RGBA map given none of ^[BFV_] (bold, italic, reverse, underline) + ColourMapNormal = [ + (255, 255, 255, 255), # White + (0, 0, 0, 255), # Black + (0, 0, 187, 255), # Blue + (0, 187, 0, 255), # Green + (255, 85, 85, 255), # Light Red + (187, 0, 0, 255), # Red + (187, 0, 187, 255), # Purple + (187, 187, 0, 255), # Yellow + (255, 255, 85, 255), # Light Yellow + (85, 255, 85, 255), # Light Green + (0, 187, 187, 255), # Cyan + (85, 255, 255, 255), # Light Cyan + (85, 85, 255, 255), # Light Blue + (255, 85, 255, 255), # Pink + (85, 85, 85, 255), # Grey + (187, 187, 187, 255), # Light Grey + ] + # }}} + # {{{ Parsing loop state + class State(Enum): + STATE_CHAR = 1 + STATE_COLOUR_SPEC = 2 + # }}} + + inFilePath = inFile = None; + inLines = inColsMax = inRows = None; + + outFontFilePath = outFontSize = None; + outImg = outImgDraw = outImgFont = None; + outCurColourBg = outCurColourFg = None; + outCurX = outCurY = None; + + inCurBold = inCurItalic = inCurReverse = inCurUnderline = None; + inCurColourSpec = None; + state = None; + inCurCol = None; + + # {{{ Calculate widest row in lines, ignoring non-printable & mIRC control code sequences + def getMaxCols(self, lines): + maxCols = 0; + for curRow in range(0, len(lines)): + curRowCols = 0; curState = self.State.STATE_CHAR; + curCol = 0; curColLen = len(lines[curRow]); + while curCol < curColLen: + curChar = lines[curRow][curCol] + if curState == self.State.STATE_CHAR: + if curChar == "": + curState = self.State.STATE_COLOUR_SPEC; curCol += 1; + elif curChar in string.printable: + curRowCols += 1; curCol += 1; + else: + curCol += 1; + elif curState == self.State.STATE_COLOUR_SPEC: + if curChar in set(",0123456789"): + curCol += 1; + else: + curState = self.State.STATE_CHAR; + maxCols = max(maxCols, curRowCols) + return maxCols + # }}} + # {{{ Parse single character as regular character and mutate state + def parseAsChar(self, char): + if char == "": + self.inCurCol += 1; self.inCurBold = 0 if self.inCurBold else 1; + elif char == "": + self.state = self.State.STATE_COLOUR_SPEC; self.inCurCol += 1; + elif char == "": + self.inCurCol += 1; self.inCurItalic = 0 if self.inCurItalic else 1; + elif char == "": + self.inCurCol += 1; + self.inCurBold = 0; self.inCurItalic = 0; self.inCurReverse = 0; self.inCurUnderline = 0; + self.inCurColourSpec = ""; + elif char == "": + self.inCurCol += 1; self.inCurReverse = 0 if self.inCurReverse else 1; + elif char == "": + self.inCurCol += 1; self.inCurUnderline = 0 if self.inCurUnderline else 1; + elif char == " ": + if self.inCurBold: + self.outImgDraw.rectangle(((self.outCurX, self.outCurY), (self.outCurX + 7, self.outCurY + 14)), fill=self.ColourMapBold[self.outCurColourBg]) + else: + self.outImgDraw.rectangle(((self.outCurX, self.outCurY), (self.outCurX + 7, self.outCurY + 14)), fill=self.ColourMapNormal[self.outCurColourBg]) + self.outCurX += 7; self.inCurCol += 1; + else: + if self.inCurBold: + self.outImgDraw.rectangle(((self.outCurX, self.outCurY), (self.outCurX + 7, self.outCurY + 14)), fill=self.ColourMapBold[self.outCurColourBg]) + # XXX implement italic, reverse, underline + self.outImgDraw.text((self.outCurX, self.outCurY), char, self.ColourMapBold[self.outCurColourFg], self.outImgFont) + else: + self.outImgDraw.rectangle(((self.outCurX, self.outCurY), (self.outCurX + 7, self.outCurY + 14)), fill=self.ColourMapNormal[self.outCurColourBg]) + # XXX implement italic, reverse, underline + self.outImgDraw.text((self.outCurX, self.outCurY), char, self.ColourMapNormal[self.outCurColourFg], self.outImgFont) + self.outCurX += 7; self.inCurCol += 1; + # }}} + # {{{ Parse single character as mIRC colour control code sequence and mutate state + def parseAsColourSpec(self, char): + if char in set(",0123456789"): + self.inCurColourSpec += char; self.inCurCol += 1; + else: + self.inCurColourSpec = self.inCurColourSpec.split(",") + if len(self.inCurColourSpec) == 2: + self.outCurColourFg = int(self.inCurColourSpec[0]) + self.outCurColourBg = int(self.inCurColourSpec[1] or self.outCurColourBg) + elif len(self.inCurColourSpec) == 1: + self.outCurColourFg = int(self.inCurColourSpec[0]) + else: + self.outCurColourBg = 1; self.outCurColourFg = 15; + self.inCurColourSpec = ""; self.state = self.State.STATE_CHAR; + # }}} + + # + # Initialisation method + def __init__(self, inFilePath, imgFilePath, fontFilePath, fontSize): + self.inFilePath = inFilePath; self.inFile = open(inFilePath, "r"); + self.inLines = self.inFile.readlines() + self.inColsMax = self.getMaxCols(self.inLines) + self.inRows = len(self.inLines) + self.outFontFilePath = fontFilePath; self.outFontSize = int(fontSize); + self.outImg = Image.new("RGBA", (self.inColsMax * 7, self.inRows * 14), self.ColourMapNormal[1]) + self.outImgDraw = ImageDraw.Draw(self.outImg) + self.outImgFont = ImageFont.truetype(self.outFontFilePath, self.outFontSize) + self.outCurColourBg = 1; self.outCurColourFg = 15; + self.outCurX = 0; self.outCurY = 0; + for inCurRow in range(0, len(self.inLines)): + self.inCurBold = 0; self.inCurItalic = 0; self.inCurReverse = 0; self.inCurUnderline = 0; + self.inCurColourSpec = ""; self.state = self.State.STATE_CHAR; + self.inCurCol = 0; + while self.inCurCol < len(self.inLines[inCurRow]): + if self.state == self.State.STATE_CHAR: + self.parseAsChar(self.inLines[inCurRow][self.inCurCol]) + elif self.state == self.State.STATE_COLOUR_SPEC: + self.parseAsColourSpec(self.inLines[inCurRow][self.inCurCol]) + self.outCurX = 0; self.outCurY += 14; + self.inFile.close(); + self.outImg.save(imgFilePath); + +# +# Entry point +def main(argv0, inFilePath, imgFilePath, fontFilePath, fontSize=11): + _MiRCART = MiRCART(inFilePath, imgFilePath, fontFilePath, fontSize) +if __name__ == "__main__": + if ((len(sys.argv) - 1) < 3)\ + or ((len(sys.argv) - 1) > 4): + print("usage: {} " \ + " " \ + " " \ + " " \ + "[]".format(sys.argv[0]), file=sys.stderr) + else: + main(*sys.argv) + +# vim:expandtab foldmethod=marker sw=8 ts=8 tw=120