Skip to content

Instantly share code, notes, and snippets.

@monomon
Last active August 29, 2015 14:05
Show Gist options
  • Select an option

  • Save monomon/6dfc020f8efc87e7c29c to your computer and use it in GitHub Desktop.

Select an option

Save monomon/6dfc020f8efc87e7c29c to your computer and use it in GitHub Desktop.
Scribus scripts
# -*- 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
# -*- 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