├── .gitignore ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST ├── README ├── README.md ├── brotherprint ├── __init__.py ├── brotherprint.py └── test │ ├── __init__.py │ └── brotherprint_test.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /dist 3 | MANIFEST.in 4 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v.1.2, 9/24/2015 -- Bug fix for abs_horz_pos and rel_horz_pos parameters handling. 2 | v.1, 11/27/2012 -- Initial release. 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Kyle Petrovich 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | README 3 | setup.py 4 | brotherprint/__init__.py 5 | brotherprint/brotherprint.py 6 | brotherprint/test/__init__.py 7 | brotherprint/test/brotherprint_test.py 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | WARNING: This project is not actively maintained, use at your own risk. [Seeking new maintainer](https://github.com/fozzle/python-brotherprint/issues/5) 2 | 3 | 4 | python-brotherprint 5 | =================== 6 | 7 | Brother networked label printers library for Python 8 | 9 | This was developed for the QL-580N but I've heard it also works for the following: 10 | 11 | * QL-720NW 12 | 13 | Installation 14 | ============ 15 | 16 | The easiest way to install this package is with pip. 17 | 18 | pip install brotherprint 19 | 20 | Usage 21 | ===== 22 | 23 | This library supports two printing modes. ESC/P commands, and templates. I highly recommend using templates, because it is easier to manage, and offers more features. I will, however, go over both. 24 | You should review the official Brother documentation [here (ESCP Docs)](http://www.mediafire.com/?3wbanr34bsr18dw) and [here (Template Command Docs)](http://www.mediafire.com/?v798mue7i58ed66), to know what is available. 25 | 26 | ## Setup 27 | Regardless of which mode you are using, you need to intialize and connect a socket object, and pass the resulting socket object to the BrotherLabel object instantiator. 28 | 29 | from brotherprint import BrotherPrint 30 | f_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 31 | f_socket.connect((,)) 32 | printjob = BrotherPrint(f_socket) 33 | 34 | ### ESC/P Printing 35 | Invoke escp commands through certain BrotherLabel object methods (see actual file for method descriptions) 36 | Make sure to end with the print page command, signifying the end of a label. 37 | 38 | printjob.command_mode() 39 | printjob.initialize() 40 | printjob.bold('on') 41 | printjob.send() 42 | printjob.print_page() 43 | 44 | ### Template Printing 45 | Create your template and upload it to the printer. After creating a BrotherLabel object, call template_mode() to set the printer to template mode, and then use the template commands to fill in your label. 46 | 47 | printjob.template_mode() 48 | printjob.template_init() 49 | printjob.choose_template() 50 | printjob.select_and_insert(, ) 51 | printjob.select_and_insert(, ) 52 | printjob.select_and_insert(, ) 53 | printjob.template_print() 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WARNING: This project is not actively maintained, use at your own risk. [Seeking new maintainer](https://github.com/fozzle/python-brotherprint/issues/5) 2 | 3 | 4 | python-brotherprint 5 | =================== 6 | 7 | Brother networked label printers library for Python 8 | 9 | This was developed for the QL-580N but I've heard it also works for the following: 10 | 11 | * QL-720NW 12 | 13 | Installation 14 | ============ 15 | 16 | The easiest way to install this package is with pip. 17 | 18 | pip install brotherprint 19 | 20 | Usage 21 | ===== 22 | 23 | This library supports two printing modes. ESC/P commands, and templates. I highly recommend using templates, because it is easier to manage, and offers more features. I will, however, go over both. 24 | You should review the official Brother documentation [here (ESCP Docs)](http://www.mediafire.com/?3wbanr34bsr18dw) and [here (Template Command Docs)](http://www.mediafire.com/?v798mue7i58ed66), to know what is available. 25 | 26 | ## Setup 27 | Regardless of which mode you are using, you need to intialize and connect a socket object, and pass the resulting socket object to the BrotherLabel object instantiator. 28 | 29 | from brotherprint import BrotherPrint 30 | from socket 31 | f_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 32 | f_socket.connect(('', )) 33 | printjob = BrotherPrint(f_socket) 34 | 35 | ### ESC/P Printing 36 | Invoke escp commands through certain BrotherLabel object methods (see actual file for method descriptions) 37 | Make sure to end with the print page command, signifying the end of a label. 38 | 39 | printjob.command_mode() 40 | printjob.initialize() 41 | printjob.bold('on') 42 | printjob.send() 43 | printjob.print_page() 44 | 45 | ### Template Printing 46 | Create your template and upload it to the printer. After creating a BrotherLabel object, call template_mode() to set the printer to template mode, and then use the template commands to fill in your label. 47 | 48 | printjob.template_mode() 49 | printjob.template_init() 50 | printjob.choose_template() 51 | printjob.select_and_insert(, ) 52 | printjob.select_and_insert(, ) 53 | printjob.select_and_insert(, ) 54 | printjob.template_print() 55 | 56 | -------------------------------------------------------------------------------- /brotherprint/__init__.py: -------------------------------------------------------------------------------- 1 | from brotherprint import BrotherPrint -------------------------------------------------------------------------------- /brotherprint/brotherprint.py: -------------------------------------------------------------------------------- 1 | import re 2 | '''Brother Python EscP Command Library 3 | 4 | Description: 5 | A collection of functions to more easily facilitate printing to the Brother QL label 6 | printers without having to memorize the ESC/P commands. Also handles sending to sockets 7 | for you. 8 | ''' 9 | 10 | 11 | class BrotherPrint: 12 | 13 | font_types = {'bitmap': 0, 14 | 'outline': 1} 15 | 16 | def __init__(self, fsocket): 17 | self.fsocket = fsocket 18 | self.fonttype = self.font_types['bitmap'] 19 | 20 | ########################################################################### 21 | # System Commands & Settings 22 | ########################################################################### 23 | 24 | def raster_mode(self): 25 | '''Sets printer to raster mode 26 | 27 | Args: 28 | None 29 | 30 | Returns: 31 | None 32 | 33 | Raises: 34 | None 35 | ''' 36 | self.send(chr(27)+'ia'+chr(1)) 37 | 38 | def template_mode(self): 39 | '''Sets printer to template mode 40 | 41 | Args: 42 | None 43 | Returns: 44 | None 45 | Raises: 46 | None 47 | ''' 48 | self.send(chr(27)+'i'+'a'+'3') 49 | 50 | def command_mode(self): 51 | '''Calling this function sets the printer to ESC/P command mode. 52 | 53 | Args: 54 | None 55 | Returns: 56 | None 57 | Raises: 58 | None 59 | ''' 60 | self.send(chr(27)+chr(105)+chr(97)+'0') 61 | 62 | def initialize(self): 63 | '''Calling this function initializes the printer. 64 | 65 | Args: 66 | None 67 | Returns: 68 | None 69 | Raises: 70 | None 71 | ''' 72 | self.fonttype = self.font_types['bitmap'] 73 | self.send(chr(27)+chr(64)) 74 | 75 | def select_charset(self, charset): 76 | '''Select international character set and changes codes in code table accordingly 77 | 78 | Args: 79 | charset: String. The character set we want. 80 | Returns: 81 | None 82 | Raises: 83 | RuntimeError: Invalid charset. 84 | ''' 85 | charsets = {'USA':0, 86 | 'France':1, 87 | 'Germany':2, 88 | 'UK':3, 89 | 'Denmark':4, 90 | 'Sweden':5, 91 | 'Italy':6, 92 | 'Spain':7, 93 | 'Japan':8, 94 | 'Norway':9, 95 | 'Denmark II':10, 96 | 'Spain II':11, 97 | 'Latin America':12, 98 | 'South Korea':13, 99 | 'Legal':64, 100 | } 101 | if charset in charsets: 102 | self.send(chr(27)+'R'+chr(charsets[charset])) 103 | else: 104 | raise RuntimeError('Invalid charset.') 105 | 106 | def select_char_code_table(self, table): 107 | '''Select character code table, from tree built in ones. 108 | 109 | Args: 110 | table: The desired character code table. Choose from 'standard', 'eastern european', 'western european', and 'spare' 111 | Returns: 112 | None 113 | Raises: 114 | RuntimeError: Invalid chartable. 115 | ''' 116 | tables = {'standard': 0, 117 | 'eastern european': 1, 118 | 'western european': 2, 119 | 'spare': 3 120 | } 121 | if table in tables: 122 | self.send(chr(27)+'t'+chr(tables[table])) 123 | else: 124 | raise RuntimeError('Invalid char table.') 125 | 126 | def cut_setting(self, cut): 127 | '''Set cut setting for printer. 128 | 129 | Args: 130 | cut: The type of cut setting we want. Choices are 'full', 'half', 'chain', and 'special'. 131 | Returns: 132 | None 133 | Raises: 134 | RuntimeError: Invalid cut type. 135 | ''' 136 | 137 | cut_settings = {'full' : 0b00000001, 138 | 'half' : 0b00000010, 139 | 'chain': 0b00000100, 140 | 'special': 0b00001000 141 | } 142 | if cut in cut_settings: 143 | self.send(chr(27)+'iC'+chr(cut_settings[cut])) 144 | else: 145 | raise RuntimeError('Invalid cut type.') 146 | 147 | 148 | ########################################################################### 149 | # Format Commands 150 | ########################################################################### 151 | def rotated_printing(self, action): 152 | '''Calling this function applies the desired action to the printing orientation 153 | of the printer. 154 | 155 | Args: 156 | action: The desired printing orientation. 'rotate' enables rotated printing. 'normal' disables rotated printing. 157 | Returns: 158 | None 159 | Raises: 160 | RuntimeError: Invalid action. 161 | ''' 162 | if action=='rotate': 163 | action='1' 164 | elif action=='cancel': 165 | action='0' 166 | else: 167 | raise RuntimeError('Invalid action.') 168 | self.send(chr(27)+chr(105)+chr(76)+action) 169 | 170 | def feed_amount(self, amount): 171 | '''Calling this function sets the form feed amount to the specified setting. 172 | 173 | Args: 174 | amount: the form feed setting you desire. Options are '1/8', '1/6', 'x/180', and 'x/60', 175 | with x being your own desired amount. X must be a minimum of 24 for 'x/180' and 8 for 'x/60' 176 | Returns: 177 | None 178 | Raises: 179 | None 180 | ''' 181 | n = None 182 | if amount=='1/8': 183 | amount = '0' 184 | elif amount=='1/6': 185 | amount = '2' 186 | elif re.search('/180', amount): 187 | n = re.search(r"(\d+)/180", amount) 188 | n = n.group(1) 189 | amount = '3' 190 | elif re.search('/60', amount): 191 | n = re.search(r"(\d+)/60", amount) 192 | n = n.group(1) 193 | amount = 'A' 194 | if n: 195 | self.send(chr(27)+amount+n) 196 | else: 197 | self.send(chr(27)+amount) 198 | 199 | def page_length(self, length): 200 | '''Specifies page length. This command is only valid with continuous length labels. 201 | 202 | Args: 203 | length: The length of the page, in dots. Can't exceed 12000. 204 | Returns: 205 | None 206 | Raises: 207 | RuntimeError: Length must be less than 12000. 208 | ''' 209 | mH = length/256 210 | mL = length%256 211 | if length < 12000: 212 | self.send(chr(27)+'('+'C'+chr(2)+chr(0)+chr(mL)+chr(mH)) 213 | else: 214 | raise RuntimeError('Length must be less than 12000.') 215 | 216 | def page_format(self, topmargin, bottommargin): 217 | '''Specify settings for top and bottom margins. Physically printable area depends on media. 218 | 219 | Args: 220 | topmargin: the top margin, in dots. The top margin must be less than the bottom margin. 221 | bottommargin: the bottom margin, in dots. The bottom margin must be less than the top margin. 222 | Returns: 223 | None 224 | Raises: 225 | RuntimeError: Top margin must be less than the bottom margin. 226 | ''' 227 | tL = topmargin%256 228 | tH = topmargin/256 229 | BL = bottommargin%256 230 | BH = topmargin/256 231 | if (tL+tH*256) < (BL + BH*256): 232 | self.send(chr(27)+'('+'c'+chr(4)+chr(0)+chr(tL)+chr(tH)+chr(BL)+chr(BH)) 233 | else: 234 | raise RuntimeError('The top margin must be less than the bottom margin') 235 | 236 | def left_margin(self, margin): 237 | '''Specify the left margin. 238 | 239 | Args: 240 | margin: The left margin, in character width. Must be less than the media's width. 241 | Returns: 242 | None 243 | Raises: 244 | RuntimeError: Invalid margin parameter. 245 | ''' 246 | if margin <= 255 and margin >= 0: 247 | self.send(chr(27)+'I'+chr(margin)) 248 | else: 249 | raise RuntimeError('Invalid margin parameter.') 250 | 251 | def right_margin(self, margin): 252 | '''Specify the right margin. 253 | 254 | Args: 255 | margin: The right margin, in character width, must be less than the media's width. 256 | Returns: 257 | None 258 | Raises: 259 | RuntimeError: Invalid margin parameter 260 | ''' 261 | if margin >=1 and margin <=255: 262 | self.send(chr(27)+'Q'+chr(margin)) 263 | else: 264 | raise RuntimeError('Invalid margin parameter in function rightMargin') 265 | 266 | def horz_tab_pos(self, positions): 267 | '''Sets tab positions, up to a maximum of 32 positions. Also can clear tab positions. 268 | 269 | Args: 270 | positions: either a list of tab positions (between 1 and 255), or 'clear'. 271 | Returns: 272 | None 273 | Raises: 274 | RuntimeError: Invalid position parameter. 275 | RuntimeError: Too many positions. 276 | ''' 277 | if positions == 'clear': 278 | self.send(chr(27)+'D'+chr(0)) 279 | return 280 | if positions.min < 1 or positions.max >255: 281 | raise RuntimeError('Invalid position parameter in function horzTabPos') 282 | sendstr = chr(27) + 'D' 283 | if len(positions)<32: 284 | for position in positions: 285 | sendstr += chr(position) 286 | self.send(sendstr+chr(0)) 287 | else: 288 | raise RuntimeError('Too many positions in function horzTabPos') 289 | 290 | def vert_tab_pos(self, positions): 291 | '''Sets tab positions, up to a maximum of 32 positions. Also can clear tab positions. 292 | 293 | Args: 294 | positions -- Either a list of tab positions (between 1 and 255), or 'clear'. 295 | Returns: 296 | None 297 | Raises: 298 | RuntimeError: Invalid position parameter. 299 | RuntimeError: Too many positions. 300 | ''' 301 | if positions == 'clear': 302 | self.send(chr(27)+'B'+chr(0)) 303 | return 304 | if positions.min < 1 or positions.max >255: 305 | raise RuntimeError('Invalid position parameter in function horzTabPos') 306 | sendstr = chr(27) + 'D' 307 | if len(positions)<=16: 308 | for position in positions: 309 | sendstr += chr(position) 310 | self.send(sendstr + chr(0)) 311 | else: 312 | raise RuntimeError('Too many positions in function vertTabPos') 313 | 314 | 315 | ############################################################################ 316 | # Print Operations 317 | ############################################################################ 318 | def send(self, text): 319 | '''Sends text to printer 320 | 321 | Args: 322 | text: string to be printed 323 | Returns: 324 | None 325 | Raises: 326 | None''' 327 | self.fsocket.send(text) 328 | 329 | def forward_feed(self, amount): 330 | '''Calling this function finishes input of the current line, then moves the vertical 331 | print position forward by x/300 inch. 332 | 333 | Args: 334 | amount: how far foward you want the position moved. Actual movement is calculated as 335 | amount/300 inches. 336 | Returns: 337 | None 338 | Raises: 339 | RuntimeError: Invalid foward feed. 340 | ''' 341 | if amount <= 255 and amount >=0: 342 | self.send(chr(27)+'J'+chr(amount)) 343 | else: 344 | raise RuntimeError('Invalid foward feed, must be less than 255 and >= 0') 345 | 346 | def abs_vert_pos(self, amount): 347 | '''Specify vertical print position from the top margin position. 348 | 349 | Args: 350 | amount: The distance from the top margin you'd like, from 0 to 32767 351 | Returns: 352 | None 353 | Raises: 354 | RuntimeError: Invalid vertical position. 355 | ''' 356 | mL = amount%256 357 | mH = amount/256 358 | if amount < 32767 and amount > 0: 359 | self.send(chr(27)+'('+'V'+chr(2)+chr(0)+chr(mL)+chr(mH)) 360 | else: 361 | raise RuntimeError('Invalid vertical position in function absVertPos') 362 | 363 | def abs_horz_pos(self, amount): 364 | '''Calling this function sets the absoulte print position for the next data, this is 365 | the position from the left margin. 366 | 367 | Args: 368 | amount: desired positioning. Can be a number from 0 to 2362. The actual positioning 369 | is calculated as (amount/60)inches from the left margin. 370 | Returns: 371 | None 372 | Raises: 373 | None 374 | ''' 375 | n1 = amount%256 376 | n2 = amount/256 377 | self.send(chr(27)+'${n1}{n2}'.format(n1=chr(n1), n2=chr(n2))) 378 | 379 | def rel_horz_pos(self, amount): 380 | '''Calling this function sets the relative horizontal position for the next data, this is 381 | the position from the current position. The next character will be printed (x/180)inches 382 | away from the current position. The relative position CANNOT be specified to the left. 383 | This command is only valid with left alignment. 384 | 385 | Args: 386 | amount: desired positioning. Can be a number from 0 to 7086. The actual positioning 387 | is calculated as (amount/180)inches from the current position. 388 | Returns: 389 | None 390 | Raises: 391 | None 392 | ''' 393 | n1 = amount%256 394 | n2 = amount/256 395 | self.send(chr(27)+'\{n1}{n2}'.format(n1=chr(n1),n2=chr(n2))) 396 | 397 | def alignment(self, align): 398 | '''Sets the alignment of the printer. 399 | 400 | Args: 401 | align: desired alignment. Options are 'left', 'center', 'right', and 'justified'. Anything else 402 | will throw an error. 403 | Returns: 404 | None 405 | Raises: 406 | RuntimeError: Invalid alignment. 407 | ''' 408 | if align=='left': 409 | align = '0' 410 | elif align=='center': 411 | align = '1' 412 | elif align=='right': 413 | align = '2' 414 | elif align=='justified': 415 | align = '3' 416 | else: 417 | raise RuntimeError('Invalid alignment in function alignment') 418 | self.send(chr(27)+'a'+align) 419 | 420 | def carriage_return(self): 421 | '''Performs a line feed amount, sets next print position to the beginning of the next line, 422 | will ignore a subsequent line feed command. 423 | 424 | Args: 425 | None 426 | Returns: 427 | None 428 | Raises: 429 | None 430 | ''' 431 | self.send(chr(13)) 432 | 433 | def line_feed(self): 434 | '''Performs line feed operation, any carriage return command subsequent to a lineFeed will 435 | be ignored. 436 | 437 | Args: 438 | None 439 | Returns: 440 | None 441 | Raises: 442 | None 443 | ''' 444 | self.send(chr(10)) 445 | 446 | def page_feed(self): 447 | '''Page feed. 448 | 449 | Keyword arguments: 450 | None 451 | Returns: 452 | None 453 | Raises: 454 | None 455 | ''' 456 | self.send(chr(12)) 457 | 458 | def print_page(self, cut): 459 | '''End input, set cut setting, and pagefeed. 460 | 461 | Args: 462 | cut: cut setting, choose from 'full', 'half', 'special' and 'chain' 463 | Returns: 464 | None 465 | Raises: 466 | None 467 | ''' 468 | self.cut_setting(cut) 469 | self.page_feed() 470 | 471 | def frame(self, action): 472 | '''Places/removes frame around text 473 | 474 | Args: 475 | action -- Enable or disable frame. Options are 'on' and 'off' 476 | Returns: 477 | None 478 | Raises: 479 | RuntimeError: Invalid action. 480 | ''' 481 | choices = {'on': '1', 482 | 'off': '0'} 483 | if action in choices: 484 | self.send(chr(27)+'if'+choices[action]) 485 | else: 486 | raise RuntimeError('Invalid action for function frame, choices are on and off') 487 | 488 | def horz_tab(self): 489 | '''Applies horizontal tab to nearest tab position 490 | 491 | Args: 492 | None 493 | Returns: 494 | None 495 | Raises: 496 | None 497 | ''' 498 | self.send(chr(9)) 499 | 500 | def vert_tab(self): 501 | '''Applies vertical tab to nearest vertical tab position 502 | 503 | Args: 504 | None 505 | Returns: 506 | None 507 | Raises: 508 | None 509 | ''' 510 | self.send(chr(11)) 511 | ########################################################################### 512 | # Text Operations 513 | ########################################################################### 514 | def bold(self, action): 515 | '''Enable/cancel bold printing 516 | 517 | Args: 518 | action: Enable or disable bold printing. Options are 'on' and 'off' 519 | Returns: 520 | None 521 | Raises: 522 | RuntimeError: Invalid action. 523 | ''' 524 | if action =='on': 525 | action = 'E' 526 | elif action == 'off': 527 | action = 'F' 528 | else: 529 | raise RuntimeError('Invalid action for function bold. Options are on and off') 530 | self.send(chr(27)+action) 531 | 532 | def italic(self, action): 533 | '''Enable/cancel italic printing 534 | 535 | Args: 536 | action: Enable or disable italic printing. Options are 'on' and 'off' 537 | Returns: 538 | None 539 | Raises: 540 | RuntimeError: Invalid action. 541 | ''' 542 | if action =='on': 543 | action = '4' 544 | elif action=='off': 545 | action = '5' 546 | else: 547 | raise RuntimeError('Invalid action for function italic. Options are on and off') 548 | self.send(chr(27)+action) 549 | 550 | def double_strike(self, action): 551 | '''Enable/cancel doublestrike printing 552 | 553 | Args: 554 | action: Enable or disable doublestrike printing. Options are 'on' and 'off' 555 | Returns: 556 | None 557 | Raises: 558 | RuntimeError: Invalid action. 559 | ''' 560 | if action == 'on': 561 | action = 'G' 562 | elif action == 'off': 563 | action = 'H' 564 | else: 565 | raise RuntimeError('Invalid action for function doubleStrike. Options are on and off') 566 | self.send(chr(27)+action) 567 | 568 | def double_width(self, action): 569 | '''Enable/cancel doublewidth printing 570 | 571 | Args: 572 | action: Enable or disable doublewidth printing. Options are 'on' and 'off' 573 | Returns: 574 | None 575 | Raises: 576 | RuntimeError: Invalid action. 577 | ''' 578 | if action == 'on': 579 | action = '1' 580 | elif action == 'off': 581 | action = '0' 582 | else: 583 | raise RuntimeError('Invalid action for function doubleWidth. Options are on and off') 584 | self.send(chr(27)+'W'+action) 585 | 586 | def compressed_char(self, action): 587 | '''Enable/cancel compressed character printing 588 | 589 | Args: 590 | action: Enable or disable compressed character printing. Options are 'on' and 'off' 591 | Returns: 592 | None 593 | Raises: 594 | RuntimeError: Invalid action. 595 | ''' 596 | if action == 'on': 597 | action = 15 598 | elif action == 'off': 599 | action = 18 600 | else: 601 | raise RuntimeError('Invalid action for function compressedChar. Options are on and off') 602 | self.send(chr(action)) 603 | 604 | def underline(self, action): 605 | '''Enable/cancel underline printing 606 | 607 | Args: 608 | action -- Enable or disable underline printing. Options are '1' - '4' and 'cancel' 609 | Returns: 610 | None 611 | Raises: 612 | None 613 | ''' 614 | if action == 'off': 615 | action = '0' 616 | self.send(chr(27)+chr(45)+action) 617 | else: 618 | self.send(chr(27)+chr(45)+action) 619 | 620 | def char_size(self, size): 621 | '''Changes font size 622 | 623 | Args: 624 | size: change font size. Options are 24' '32' '48' for bitmap fonts 625 | 33, 38, 42, 46, 50, 58, 67, 75, 83, 92, 100, 117, 133, 150, 167, 200 233, 626 | 11, 44, 77, 111, 144 for outline fonts. 627 | Returns: 628 | None 629 | Raises: 630 | RuntimeError: Invalid font size. 631 | Warning: Your font is currently set to outline and you have selected a bitmap only font size 632 | Warning: Your font is currently set to bitmap and you have selected an outline only font size 633 | ''' 634 | sizes = {'24':0, 635 | '32':0, 636 | '48':0, 637 | '33':0, 638 | '38':0, 639 | '42':0, 640 | '46':0, 641 | '50':0, 642 | '58':0, 643 | '67':0, 644 | '75':0, 645 | '83':0, 646 | '92':0, 647 | '100':0, 648 | '117':0, 649 | '133':0, 650 | '150':0, 651 | '167':0, 652 | '200':0, 653 | '233':0, 654 | '11':1, 655 | '44':1, 656 | '77':1, 657 | '111':1, 658 | '144':1 659 | } 660 | if size in sizes: 661 | if size in ['24','32','48'] and self.fonttype != self.font_types['bitmap']: 662 | raise Warning('Your font is currently set to outline and you have selected a bitmap only font size') 663 | if size not in ['24', '32', '48'] and self.fonttype != self.font_types['outline']: 664 | raise Warning('Your font is currently set to bitmap and you have selected an outline only font size') 665 | self.send(chr(27)+'X'+chr(0)+chr(int(size))+chr(sizes[size])) 666 | else: 667 | raise RuntimeError('Invalid size for function charSize, choices are auto 4pt 6pt 9pt 12pt 18pt and 24pt') 668 | 669 | def select_font(self, font): 670 | '''Select font type 671 | 672 | Choices are: 673 | 674 | 'brougham' 675 | 'lettergothicbold' 676 | 'brusselsbit' 677 | 'helsinkibit' 678 | 'sandiego' 679 | 680 | 'lettergothic' 681 | 'brusselsoutline' 682 | 'helsinkioutline' 683 | 684 | Args: 685 | font: font type 686 | Returns: 687 | None 688 | Raises: 689 | RuntimeError: Invalid font. 690 | ''' 691 | fonts = {'brougham': 0, 692 | 'lettergothicbold': 1, 693 | 'brusselsbit' : 2, 694 | 'helsinkibit': 3, 695 | 'sandiego': 4, 696 | 'lettergothic': 9, 697 | 'brusselsoutline': 10, 698 | 'helsinkioutline': 11} 699 | 700 | if font in fonts: 701 | if font in ['broughham', 'lettergothicbold', 'brusselsbit', 'helsinkibit', 'sandiego']: 702 | self.fonttype = self.font_types['bitmap'] 703 | else: 704 | self.fonttype = self.font_types['outline'] 705 | 706 | self.send(chr(27)+'k'+chr(fonts[font])) 707 | else: 708 | raise RuntimeError('Invalid font in function selectFont') 709 | 710 | def char_style(self, style): 711 | '''Sets the character style. 712 | 713 | Args: 714 | style: The desired character style. Choose from 'normal', 'outline', 'shadow', and 'outlineshadow' 715 | Returns: 716 | None 717 | Raises: 718 | RuntimeError: Invalid character style 719 | ''' 720 | styleset = {'normal': 0, 721 | 'outline': 1, 722 | 'shadow': 2, 723 | 'outlineshadow': 3 724 | } 725 | if style in styleset: 726 | self.send(chr(27) + 'q' + chr(styleset[style])) 727 | else: 728 | raise RuntimeError('Invalid character style in function charStyle') 729 | 730 | def pica_pitch(self): 731 | '''Print subsequent data with pica pitch (10 char/inch) 732 | 733 | Args: 734 | None 735 | Returns: 736 | None 737 | Raises: 738 | None 739 | ''' 740 | self.send(chr(27)+'P') 741 | 742 | def elite_pitch(self): 743 | '''Print subsequent data with elite pitch (12 char/inch) 744 | 745 | Args: 746 | None 747 | Returns: 748 | None 749 | Raises: 750 | None 751 | ''' 752 | self.send(chr(27)+'M') 753 | 754 | def micron_pitch(self): 755 | '''Print subsequent data with micron pitch (15 char/inch) 756 | 757 | Args: 758 | None 759 | Returns: 760 | None 761 | Raises: 762 | None 763 | ''' 764 | self.send(chr(27)+'g') 765 | 766 | def proportional_char(self, action): 767 | '''Specifies proportional characters. When turned on, the character spacing set 768 | with charSpacing. 769 | 770 | Args: 771 | action: Turn proportional characters on or off. 772 | Returns: 773 | None 774 | Raises: 775 | RuntimeError: Invalid action. 776 | ''' 777 | actions = {'off': 0, 778 | 'on': 1 779 | } 780 | if action in actions: 781 | self.send(chr(27)+'p'+action) 782 | else: 783 | raise RuntimeError('Invalid action in function proportionalChar') 784 | 785 | def char_spacing(self, dots): 786 | '''Specifes character spacing in dots. 787 | 788 | Args: 789 | dots: the character spacing you desire, in dots 790 | Returns: 791 | None 792 | Raises: 793 | RuntimeError: Invalid dot amount. 794 | ''' 795 | if dots in range(0,127): 796 | self.send(chr(27)+chr(32)+chr(dots)) 797 | else: 798 | raise RuntimeError('Invalid dot amount in function charSpacing') 799 | 800 | ############################################################################ 801 | # Bit Image 802 | ############################################################################ 803 | 804 | # TODO: Complete bit image methods 805 | 806 | 807 | 808 | 809 | ############################################################################ 810 | # Barcode 811 | ############################################################################ 812 | 813 | def barcode(self, data, format, characters='off', height=48, width='small', parentheses='on', ratio='3:1', equalize='off', rss_symbol='rss14std', horiz_char_rss=2): 814 | '''Print a standard barcode in the specified format 815 | 816 | Args: 817 | data: the barcode data 818 | format: the barcode type you want. Choose between code39, itf, ean8/upca, upce, codabar, code128, gs1-128, rss 819 | characters: Whether you want characters below the bar code. 'off' or 'on' 820 | height: Height, in dots. 821 | width: width of barcode. Choose 'xsmall' 'small' 'medium' 'large' 822 | parentheses: Parentheses deletion on or off. 'on' or 'off' Only matters with GS1-128 823 | ratio: ratio between thick and thin bars. Choose '3:1', '2.5:1', and '2:1' 824 | equalize: equalize bar lengths, choose 'off' or 'on' 825 | rss_symbol: rss symbols model, choose from 'rss14std', 'rss14trun', 'rss14stacked', 'rss14stackedomni', 'rsslimited', 'rssexpandedstd', 'rssexpandedstacked' 826 | horiz_char_rss: for rss expanded stacked, specify the number of horizontal characters, must be an even number b/w 2 and 20. 827 | ''' 828 | 829 | barcodes = {'code39': '0', 830 | 'itf': '1', 831 | 'ean8/upca': '5', 832 | 'upce': '6', 833 | 'codabar': '9', 834 | 'code128': 'a', 835 | 'gs1-128': 'b', 836 | 'rss': 'c'} 837 | 838 | widths = {'xsmall': '0', 839 | 'small': '1', 840 | 'medium': '2', 841 | 'large': '3'} 842 | 843 | ratios = {'3:1': '0', 844 | '2.5:1': '1', 845 | '2:1': '2'} 846 | 847 | rss_symbols = {'rss14std': '0', 848 | 'rss14trun': '1', 849 | 'rss14stacked': '2', 850 | 'rss14stackedomni' : '3', 851 | 'rsslimited': '4', 852 | 'rssexpandedstd': '5', 853 | 'rssexpandedstacked': '6' 854 | } 855 | 856 | character_choices = {'off': '0', 857 | 'on' : '1'} 858 | parentheses_choices = {'off':'1', 859 | 'on': '0'} 860 | equalize_choices = {'off': '0', 861 | 'on': '1'} 862 | 863 | sendstr = '' 864 | n2 = height/256 865 | n1 = height%256 866 | if format in barcodes and width in widths and ratio in ratios and characters in character_choices and rss_symbol in rss_symbols: 867 | sendstr += (chr(27)+'i'+'t'+barcodes[format]+'s'+'p'+'r'+character_choices[characters]+'u'+'x'+'y'+'h' + chr(n1) + chr(n2) + 868 | 'w'+widths[width]+'e'+parentheses_choices[parentheses]+'o'+rss_symbols[rss_symbol]+'c'+chr(horiz_char_rss)+'z'+ratios[ratio]+'f'+equalize_choices[equalize] 869 | + 'b' + data + chr(92)) 870 | if format in ['code128', 'gs1-128']: 871 | sendstr += chr(92)+ chr(92) 872 | self.send(sendstr) 873 | else: 874 | raise RuntimeError('Invalid parameters') 875 | 876 | ############################################################################ 877 | # Template Commands 878 | ############################################################################ 879 | 880 | def template_print(self): 881 | '''Print the page 882 | 883 | Args: 884 | None 885 | Returns: 886 | None 887 | Raises: 888 | None 889 | ''' 890 | self.send('^FF') 891 | 892 | def choose_template(self, template): 893 | '''Choose a template 894 | 895 | Args: 896 | template: String, choose which template you would like. 897 | Returns: 898 | None 899 | Raises: 900 | None 901 | ''' 902 | n1 = int(template)/10 903 | n2 = int(template)%10 904 | self.send('^TS'+'0'+str(n1)+str(n2)) 905 | 906 | def machine_op(self, operation): 907 | '''Perform machine operations 908 | 909 | Args: 910 | operations: which operation you would like 911 | Returns: 912 | None 913 | Raises: 914 | RuntimeError: Invalid operation 915 | ''' 916 | operations = {'feed2start': 1, 917 | 'feedone': 2, 918 | 'cut': 3 919 | } 920 | 921 | if operation in operations: 922 | self.send('^'+'O'+'P'+chr(operations[operation])) 923 | else: 924 | raise RuntimeError('Invalid operation.') 925 | 926 | def template_init(self): 927 | '''Initialize command for template mode 928 | 929 | Args: 930 | None 931 | Returns: 932 | None 933 | Raises: 934 | None 935 | ''' 936 | self.send(chr(94)+chr(73)+chr(73)) 937 | 938 | def print_start_trigger(self, type): 939 | '''Set print start trigger. 940 | 941 | Args: 942 | type: The type of trigger you desire. 943 | Returns: 944 | None 945 | Raises: 946 | RuntimeError: Invalid type. 947 | ''' 948 | types = {'recieved': 1, 949 | 'filled': 2, 950 | 'num_recieved': 3} 951 | 952 | if type in types: 953 | self.send('^PT'+chr(types[type])) 954 | else: 955 | raise RuntimeError('Invalid type.') 956 | 957 | def print_start_command(self, command): 958 | '''Set print command 959 | 960 | Args: 961 | command: the type of command you desire. 962 | Returns: 963 | None 964 | Raises: 965 | RuntimeError: Command too long. 966 | ''' 967 | size = len(command) 968 | if size > 20: 969 | raise RuntimeError('Command too long') 970 | n1 = size/10 971 | n2 = size%10 972 | self.send('^PS'+chr(n1)+chr(n2)+command) 973 | 974 | def received_char_count(self, count): 975 | '''Set recieved char count limit 976 | 977 | Args: 978 | count: the amount of received characters you want to stop at. 979 | Returns: 980 | None 981 | Raises: 982 | None 983 | ''' 984 | n1 = count/100 985 | n2 = (count-(n1*100))/10 986 | n3 = (count-((n1*100)+(n2*10))) 987 | self.send('^PC'+chr(n1)+chr(n2)+chr(n3)) 988 | 989 | def select_delim(self, delim): 990 | '''Select desired delimeter 991 | 992 | Args: 993 | delim: The delimeter character you want. 994 | Returns: 995 | None 996 | Raises: 997 | RuntimeError: Delimeter too long. 998 | ''' 999 | size = len(delim) 1000 | if size > 20: 1001 | raise RuntimeError('Delimeter too long') 1002 | n1 = size/10 1003 | n2 = size%10 1004 | self.send('^SS'+chr(n1)+chr(n2)) 1005 | 1006 | def select_obj(self, name): 1007 | '''Select an object 1008 | 1009 | Args: 1010 | name: the name of the object you want to select 1011 | Returns: 1012 | None 1013 | Raises: 1014 | None 1015 | ''' 1016 | self.send('^ON'+name+chr(0)) 1017 | 1018 | def insert_into_obj(self, data): 1019 | '''Insert text into selected object. 1020 | 1021 | Args: 1022 | data: The data you want to insert. 1023 | Returns: 1024 | None 1025 | Raises: 1026 | None 1027 | ''' 1028 | if not data: 1029 | data = '' 1030 | size = len(data) 1031 | n1 = size%256 1032 | n2 = size/256 1033 | 1034 | self.send('^DI'+chr(n1)+chr(n2)+data) 1035 | 1036 | def select_and_insert(self, name, data): 1037 | '''Combines selection and data insertion into one function 1038 | 1039 | Args: 1040 | name: the name of the object you want to insert into 1041 | data: the data you want to insert 1042 | Returns: 1043 | None 1044 | Raises: 1045 | None 1046 | ''' 1047 | self.select_obj(name) 1048 | self.insert_into_obj(data) 1049 | 1050 | 1051 | -------------------------------------------------------------------------------- /brotherprint/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fozzle/python-brotherprint/5fb92df11b599c30a7da3d6ac7ed60acff230044/brotherprint/test/__init__.py -------------------------------------------------------------------------------- /brotherprint/test/brotherprint_test.py: -------------------------------------------------------------------------------- 1 | # TODO: Create tests -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='brotherprint', 5 | version='0.1.3', 6 | author='Kyle Petrovich', 7 | author_email='kylepetrovich@gmail.com', 8 | packages=['brotherprint', 'brotherprint.test'], 9 | url='http://github.com/fozzle/python-brotherprint', 10 | license='LICENSE.txt', 11 | description='Wrapper for Brother networked label printing commands.', 12 | long_description=open('README').read(), 13 | ) 14 | --------------------------------------------------------------------------------