Last active
August 29, 2015 14:05
-
-
Save monomon/6dfc020f8efc87e7c29c to your computer and use it in GitHub Desktop.
Scribus scripts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| ''' | |
| Create a catalog of articles | |
| As a rule I finish the layout with some hand touches, but the bulk of it is generated. | |
| For each article display name, description, photo. | |
| Layout articles in two columns on the page. | |
| Articles are grouped by category and each category begins on a new page. | |
| Find a finished example at http://monomon.me/stuff/mmcosmetics/mm_catalog.pdf | |
| ''' | |
| import csv | |
| import logging | |
| import os | |
| import scribus | |
| logger = logging.getLogger('mm') | |
| handler = logging.FileHandler('mm_catalog.log', 'w', 'utf-8') | |
| handler.setFormatter(logging.Formatter('%(levelname)s:#%(lineno)s: %(message)s')) | |
| logger.addHandler(handler) | |
| logger.setLevel(logging.DEBUG) | |
| config = { | |
| 'width' : 151, | |
| 'height' : 200, | |
| 'pageMargins' : (10,10,10,10), | |
| 'textMargins' : 4, | |
| 'textDistances' : 2, | |
| 'lineWidth' : 4, | |
| 'initialTextWidth' : 43, | |
| 'initialTextHeight' : 30, | |
| 'initialTitleHeight' : 10, | |
| 'dataFile' : 'mm_articles_catalog.csv', | |
| 'font' : 'Liberation Sans Narrow Regular', | |
| 'fontSize' : 7, | |
| 'minFont' : 3.5, | |
| 'maxFont' : 12, | |
| 'titleFont' : 'Liberation Sans Narrow Bold', | |
| 'titleFontSize' : 12, | |
| 'lineSpacing' : 9, | |
| 'imageWidth' : 20, | |
| 'imageHeight' : 40, | |
| 'headerHeight' : 21, | |
| 'headerPadding' : 5, | |
| 'headerFont' : 'Liberation Sans Narrow Regular', | |
| 'headerFontSize' : 23, | |
| 'headerLogoWidth' : 23, | |
| 'headerLogoHeight' : 14, | |
| 'bleed' : 3, | |
| 'footerText' : 'www.dsm-bg.com', | |
| } | |
| fieldnames = [ | |
| 'name', | |
| 'cat_number', | |
| 'description', | |
| 'usage', | |
| 'contents', | |
| 'photo', | |
| 'weight', | |
| 'category' | |
| ] | |
| categories = ['body', 'face', 'hair', 'antiage', 'heal'] | |
| categoryTitles = { | |
| 'body' : 'Грижа за тялото', | |
| 'face' : 'Грижа за лицето', | |
| 'hair' : 'Грижа за косата', | |
| 'antiage' : 'Продукти срещу стареене на кожата', | |
| 'heal' : 'Лечебни продукти', | |
| } | |
| def create_title_text(x, y, width, height, data): | |
| tb = scribus.createText(x, y, width, height) | |
| scribus.insertText(data['name'], 0, tb) | |
| scribus.setStyle('titlePar', tb) | |
| scribus.insertText(('\n'), -1, tb) | |
| return tb | |
| def create_text_box(x, y, width, height, data): | |
| if 'description' not in data or data['description'] is '': | |
| raise ValueError('Description is empty') | |
| tb = scribus.createText(x, y, width, height) | |
| insert_text_lines(data['description'], tb) | |
| scribus.setStyle('defaultPar', tb) | |
| if 'weight' in data: | |
| scribus.insertText(('\n'), -1, tb) | |
| scribus.insertText(data['weight'], -1, tb) | |
| scribus.setStyle('defaultPar', tb) | |
| scribus.setTextDistances( | |
| 0, | |
| config['textDistances'], | |
| 0, | |
| config['textDistances'], | |
| tb | |
| ) | |
| return tb | |
| def insert_text_lines(text, textBox): | |
| ''' | |
| simple convenience to insert lines and apply a style to each | |
| (applying a style to a multiline text appears to only affect last line) | |
| ''' | |
| txt = text.split('\n') | |
| for line in txt: | |
| scribus.insertText('\n', -1, textBox) | |
| scribus.insertText(line, -1, textBox) | |
| scribus.setStyle('defaultPar', textBox) | |
| def auto_size_box(box): | |
| ''' | |
| try resizing in steps until text fits | |
| ''' | |
| w, h = scribus.getSize(box) | |
| # first resize in steps of 10 | |
| while scribus.textOverflows(box) > 0: | |
| h += 10 | |
| scribus.sizeObject(w, h, box) | |
| # go back one step | |
| h -= 10 | |
| scribus.sizeObject(w, h, box) | |
| # now increase by 1 | |
| while scribus.textOverflows(box) > 0: | |
| h += 1 | |
| scribus.sizeObject(w, h, box) | |
| return h | |
| def is_left(pagenum): | |
| return pagenum%2 == 0 | |
| def create_category_header(title, left, pageWidth): | |
| ''' | |
| create category header with a rectangle containing the logo | |
| and the category name | |
| ''' | |
| # seems you can't retrieve the svg image name | |
| if left: | |
| rect = scribus.createRect( | |
| -config['bleed'], | |
| -config['bleed'], | |
| config['width'] + config['bleed'], | |
| config['headerHeight'] + config['headerPadding'] | |
| ) | |
| scribus.placeSVG('../logo/logo3a.svg', config['pageMargins'][0], config['pageMargins'][1] - 4) | |
| txt = scribus.createText( | |
| config['pageMargins'][0] + config['headerLogoWidth'] + config['textMargins'], | |
| config['pageMargins'][1], | |
| config['width'] - config['pageMargins'][0] - config['pageMargins'][3] - config['headerLogoWidth'] - config['textMargins'], | |
| config['headerHeight'] - 2*config['headerPadding'] | |
| ) | |
| scribus.setText(title, txt) | |
| scribus.setStyle('headerPar', txt) | |
| else: | |
| rect = scribus.createRect(0, -config['bleed'], config['width'] + config['bleed'], config['headerHeight'] + config['headerPadding']) | |
| txt = scribus.createText( | |
| config['pageMargins'][0], | |
| config['pageMargins'][1], | |
| config['width'] - config['pageMargins'][0] - config['pageMargins'][3] - config['headerLogoWidth'] - config['textMargins'], | |
| config['headerHeight'] - 2*config['headerPadding'] | |
| ) | |
| scribus.setText(title, txt) | |
| scribus.setStyle('headerPar', txt) | |
| scribus.setTextAlignment(scribus.ALIGN_RIGHT, txt) | |
| scribus.placeSVG( | |
| '../logo/logo3a.svg', | |
| config['width'] - config['pageMargins'][0] - config['headerLogoWidth'], | |
| config['pageMargins'][1] - 4 | |
| ) | |
| scribus.setFillColor('lightBlue', rect) | |
| scribus.setLineColor('None', rect) | |
| def create_footer(left, pageWidth): | |
| ''' | |
| create simple footer | |
| ''' | |
| if left: | |
| tb = scribus.createText( | |
| config['pageMargins'][1], | |
| config['height'] - config['pageMargins'][3], | |
| pageWidth, | |
| 6 | |
| ) | |
| scribus.setText(config['footerText'], tb) | |
| scribus.setStyle('titlePar', tb) | |
| else: | |
| tb = scribus.createText( | |
| config['pageMargins'][1], | |
| config['height'] - config['pageMargins'][3], | |
| pageWidth, | |
| 6 | |
| ) | |
| scribus.setText(config['footerText'], tb) | |
| scribus.setStyle('titlePar', tb) | |
| scribus.setTextAlignment(scribus.ALIGN_RIGHT, tb) | |
| scribus.setTextColor('darkBlue', tb) | |
| return tb | |
| def create_item(row, margins, x, y): | |
| ''' | |
| create all elements for a single item in the catalog | |
| ''' | |
| tb = create_title_text(x + margins[1], y + margins[0], config['initialTextWidth'] + config['imageWidth'], config['initialTitleHeight'], row) | |
| h = auto_size_box(tb) | |
| tb = create_text_box(x + margins[1], y + margins[0] + h,config['initialTextWidth'], config['initialTextHeight'], row) | |
| if 'photo' not in row or row['photo'] is '': | |
| raise ValueError('Missing photo') | |
| img = scribus.createImage(x + config['initialTextWidth'] + margins[1], y + margins[0] + h, config['imageWidth'], config['imageHeight']) | |
| h += auto_size_box(tb) | |
| scribus.loadImage(os.path.join('photos', row['photo']), img) | |
| scribus.setScaleImageToFrame(scaletoframe=1, proportional=1, name=img) | |
| return h | |
| if __name__ == '__main__': | |
| # create doc | |
| # selecting measure unit works | |
| scribus.newDoc( | |
| (config['width'], config['height']), | |
| config['pageMargins'], | |
| scribus.PORTRAIT, 1, scribus.UNIT_MILLIMETERS, scribus.FACINGPAGES, scribus.FIRSTPAGERIGHT) | |
| # create styles | |
| scribus.createCharStyle('default', config['font'], config['fontSize']) | |
| # scribus.createParagraphStyle('defaultPar', linespacing=config['lineSpacing'], charstyle='default') | |
| scribus.createParagraphStyle('defaultPar', linespacing=config['lineSpacing'], charstyle='default') | |
| scribus.createCharStyle('title', config['titleFont'], config['titleFontSize'], 'bold') | |
| # scribus.createParagraphStyle('titlePar', linespacing=config['lineSpacing'], charstyle='title') | |
| scribus.createParagraphStyle('titlePar', charstyle='title') | |
| scribus.createCharStyle('header', config['headerFont'], config['headerFontSize'], fillcolor='White') | |
| scribus.createParagraphStyle('headerPar', linespacing=config['headerFontSize'], charstyle='header') | |
| scribus.defineColor('lightBlue', 80,0,40,20) | |
| scribus.defineColor('darkBlue', 205,0,105,20) | |
| # calculate page dimensions | |
| page_height = config['height'] - config['pageMargins'][0] - config['pageMargins'][3] | |
| page_width = config['width'] - config['pageMargins'][1] - config['pageMargins'][2] | |
| # open data | |
| with open(config['dataFile'], 'rb') as ff: | |
| reader = csv.DictReader(ff, fieldnames) | |
| current_height = 0 | |
| margins = scribus.getPageMargins() | |
| reader.next() # skip first row | |
| items = list(reader) | |
| scribus.newPage(-1) | |
| for k in categories: | |
| h,h2 = 0,0 | |
| try: | |
| # create category header | |
| create_category_header(categoryTitles[k], is_left(scribus.currentPage()), page_width) | |
| create_footer(is_left(scribus.currentPage()), page_width) | |
| current_height += config['headerHeight'] | |
| cat_items = filter(lambda a: a['category'] == k, items) | |
| for i in range(0, len(cat_items), 2): | |
| try: | |
| if current_height + config['initialTextHeight'] + config['textMargins'] >= page_height: | |
| current_height = 0 | |
| scribus.newPage(-1) | |
| create_category_header(categoryTitles[k], is_left(scribus.currentPage()), page_width) | |
| create_footer(is_left(scribus.currentPage()), page_width) | |
| current_height += config['headerHeight'] | |
| h,h2 = 0,0 | |
| h = create_item(cat_items[i], margins, 0, current_height) | |
| if i<=len(cat_items)-2: | |
| # make on second column | |
| h2 = create_item(cat_items[i+1], margins, config['initialTextWidth'] + config['imageWidth'] + config['textMargins'],current_height) | |
| else: | |
| h2 = 0 | |
| current_height += max(h,h2) + config['textMargins'] | |
| except ValueError, e: | |
| logger.error('[{0}] {1}'.format(cat_items[i]['name'], e)) | |
| # go back a page if things went wrong - this will be incorrect if page creation failed | |
| continue | |
| scribus.newPage(-1) | |
| current_height = 0 | |
| except Exception, e: | |
| # catch any uncaught exceptions | |
| logger.exception(e) | |
| continue |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -*- coding: utf-8 -*- | |
| ''' | |
| Create labels from CSV | |
| Label width and height are defined in the CSV data per item. | |
| For each label insert name, description, volume (ml., mg.), etc. | |
| Each label is tiled on a page as many times as it fits. | |
| Find generated example at http://monomon.me/stuff/mmcosmetics/mm_etiketi.pdf | |
| ''' | |
| import sys | |
| import csv | |
| import re | |
| import logging | |
| import scribus | |
| logger = logging.getLogger('mm') | |
| handler = logging.FileHandler('mm.log', 'w', 'utf-8') | |
| handler.setFormatter(logging.Formatter('%(levelname)s:#%(lineno)s: %(message)s')) | |
| logger.addHandler(handler) | |
| logger.setLevel(logging.DEBUG) | |
| config = { | |
| 'width' : 210, | |
| 'height' : 297, | |
| 'pageMargins' : (5,5,5,5), | |
| 'textMargins' : 4, | |
| 'textDistances' : 2, | |
| 'lineWidth' : 4, | |
| 'dataFile' : 'mm_articles.csv', | |
| 'font' : 'Liberation Sans Narrow Regular', | |
| 'fontSize' : 9, | |
| 'minFont' : 3.5, | |
| 'maxFont' : 12, | |
| 'titleFont' : 'Liberation Sans Narrow Bold', | |
| 'titleFontSize' : 10, | |
| 'lineSpacing' : 9, | |
| } | |
| extraText=['Производител: A-Meshi LTD, Israel - Rishon le Zion, "Pinkas" 4','Вносител: ММ Козметикс ЕООД','Благоевград, ул. „Димитър Талев“ 3','GSM 0897 820827','www.dsm-bg.com','Най-добро до: Виж опаковката'] | |
| fieldnames = [ | |
| 'name', | |
| 'cat_number', | |
| 'description', | |
| 'usage', | |
| 'contents', | |
| 'label_width', | |
| 'label_height', | |
| 'photo', | |
| 'weight', | |
| ] | |
| # do not create box if any of these is missing | |
| required_fields = [ | |
| 'name', | |
| 'description', | |
| 'label_width', | |
| 'label_height' | |
| ] | |
| # these will only be logged, but boxes are generated | |
| optional_fields = ['weight'] | |
| # new line followed by alphanum, spaces and dashes, then a colon | |
| labelRe = re.compile('[\r\n]+([\w\s-]+:)', re.MULTILINE | re.UNICODE) | |
| def create_text_box(x, y, data): | |
| # validate data | |
| missing_required = [field for field in required_fields if data[field] is ''] | |
| if len(missing_required)>0: | |
| raise ValueError('Missing required fields: {0}'.format(', '.join(missing_required))) | |
| missing_optional = [field for field in optional_fields if data[field] is ''] | |
| if len(missing_optional)>0: | |
| logger.warning('Missing optional fields: {0}'.format(', '.join(missing_optional))) | |
| # create textbox | |
| tb = scribus.createText(x, y, int(data['label_width']), int(data['label_height'])) | |
| scribus.setTextAlignment(scribus.ALIGN_LEFT, tb) | |
| scribus.setTextDistances( | |
| config['textDistances'], | |
| config['textDistances'], | |
| config['textDistances'], | |
| config['textDistances'], | |
| tb | |
| ) | |
| scribus.setLineColor('Black', tb) | |
| scribus.setLineWidth(config['lineWidth'], tb) | |
| scribus.insertText((data['name']), 0, tb) | |
| scribus.setStyle('titlePar', tb) | |
| # add newlines separately, seems that setStyle doesn't affect | |
| # paragraph otherwise | |
| scribus.insertText(('\n'), -1, tb) | |
| scribus.insertText(data['description'], -1, tb) | |
| scribus.setStyle('defaultPar', tb) | |
| if data['weight'] is not '': | |
| scribus.insertText(('\n'), -1, tb) | |
| scribus.insertText(data['weight'], -1, tb) | |
| scribus.setStyle('defaultPar', tb) | |
| # now add optional pieces | |
| if data['usage'] is not '': | |
| # currentLen = scribus.getTextLength(tb) | |
| scribus.insertText(('\nНачин на употреба: '), -1, tb) | |
| scribus.insertText(data['usage'], -1, tb) | |
| scribus.setStyle('defaultPar', tb) | |
| # extra text (contact details, etc.) | |
| for et in extraText: | |
| scribus.insertText(('\n'), -1, tb) | |
| scribus.insertText(et, -1, tb) | |
| scribus.setStyle('defaultPar', tb) | |
| # format 'labels' | |
| txt = scribus.getAllText(tb).decode('utf-8') | |
| for m in labelRe.finditer(txt): | |
| scribus.selectText(m.start(1), m.end(1) - m.start(1), tb) | |
| scribus.setFont(config['titleFont'], tb) | |
| scribus.selectText(0, 0, tb) | |
| return tb | |
| def create_grid_lines(rows, columns): | |
| # vertical lines | |
| for c in range(1, columns): | |
| x = c*(d[0] + config['textMargins']) - (config['textMargins']/2) + margins[2] | |
| scribus.createLine(x, 0, x, config['height']) | |
| # horizontal lines | |
| for r in range(1, rows): | |
| y = r*(d[1] + config['textMargins']) - (config['textMargins']/2) + margins[0] | |
| scribus.createLine(0, y, config['width'], y) | |
| def find_font_size(box, step): | |
| ''' | |
| reduce font size until text fits the box | |
| ''' | |
| fs = scribus.getFontSize(box) | |
| ls = fs*1.20 #scribus.getLineSpacing(box) | |
| while True: | |
| fs -= step | |
| ls -= step | |
| scribus.setFontSize(fs, box) | |
| scribus.setLineSpacing(ls, box) | |
| if scribus.textOverflows(box) <= 0: | |
| break | |
| # check if next step goes beyond minimum here | |
| if fs-step < config['minFont'] or ls-step < config['minFont']: | |
| raise AttributeError('Minimum font size reached, not fitting') | |
| return fs, ls | |
| def auto_size_text(box): | |
| ''' | |
| auto resize text so that it fits the box | |
| ''' | |
| if scribus.textOverflows(box) <= 0: | |
| return | |
| try: | |
| fs, ls = find_font_size(box, 1) | |
| except AttributeError: | |
| fs = scribus.getFontSize(box) | |
| ls = scribus.getLineSpacing(box) | |
| logger.debug(fs, ls) | |
| scribus.setFontSize(fs+1, box) | |
| try: | |
| find_font_size(box, 0.1) | |
| except AttributeError as e: | |
| raise e | |
| def calculate_grid(tb, page_width, page_height, dimensions): | |
| return ( | |
| int((page_height + config['textMargins']) // (dimensions[1] + config['textMargins'])), | |
| int((page_width + config['textMargins']) // (dimensions[0] + config['textMargins'])), | |
| ) | |
| if __name__ == '__main__': | |
| # create doc | |
| # selecting measure unit works | |
| scribus.newDoc( | |
| (config['width'], config['height']), | |
| config['pageMargins'], | |
| scribus.PORTRAIT, 1, scribus.UNIT_MILLIMETERS, scribus.NOFACINGPAGES, scribus.FIRSTPAGERIGHT) | |
| # create styles | |
| scribus.createCharStyle('default', config['font'], config['fontSize']) | |
| # scribus.createParagraphStyle('defaultPar', linespacing=config['lineSpacing'], charstyle='default') | |
| scribus.createParagraphStyle('defaultPar', charstyle='default') | |
| scribus.createCharStyle('title', config['titleFont'], config['titleFontSize'], 'bold') | |
| # scribus.createParagraphStyle('titlePar', linespacing=config['lineSpacing'], charstyle='title') | |
| scribus.createParagraphStyle('titlePar', charstyle='title') | |
| # calculate page dimensions | |
| page_height = config['height'] - config['pageMargins'][0] - config['pageMargins'][3] | |
| page_width = config['width'] - config['pageMargins'][1] - config['pageMargins'][2] | |
| # open data | |
| with open(config['dataFile'], 'rb') as ff: | |
| reader = csv.DictReader(ff, fieldnames) | |
| margins = scribus.getPageMargins() | |
| # sorted converts to list | |
| reader = sorted(reader, key=lambda r: r['name']) | |
| for row in reader: | |
| try: | |
| scribus.newPage(-1) | |
| tb = create_text_box(margins[1], margins[0], row) | |
| # height will come from row | |
| auto_size_text(tb) | |
| # now clone as many boxes as fit | |
| d = scribus.getSize(tb) | |
| rows, columns = calculate_grid(tb, page_width, page_height, d) | |
| for r in range(rows): | |
| for c in range(columns): | |
| newBox = scribus.duplicateObject(tb) | |
| scribus.moveObjectAbs( | |
| c*(d[0] + config['textMargins']) + margins[1], | |
| r*(d[1] + config['textMargins']) + margins[0] | |
| ) | |
| create_grid_lines(rows, columns) | |
| # remove original box - there is a duplicate | |
| scribus.deleteObject(tb) | |
| except ValueError as e: | |
| logger.error('[{0}] {1}'.format(row['name'], e)) | |
| # go back a page if things went wrong - this will be incorrect if page creation failed | |
| scribus.deletePage(scribus.currentPage()) | |
| continue | |
| except AttributeError as e: | |
| logger.error('[{0}] {1}'.format(row['name'], e)) | |
| # go back a page if things went wrong | |
| scribus.deletePage(scribus.currentPage()) | |
| continue | |
| except Exception as e: | |
| logger.exception('unexpected error\n{0}'.format(sys.exc_info()[0])) | |
| continue | |
| # remove first page as it is empty | |
| scribus.deletePage(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment