Bullets and numbered lists

From Scribus Wiki
Revision as of 20:49, 17 January 2011 by Meira (talk | contribs) (→‎Code)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
This article is part of the Scripts series.

Improved bullets simple script using regular expressions Python module. Build bulleted lists; numbered lists starting with any number plus separator; upper case or lower case lettered list; and delete list marks. Create styles -- if it doesn't exists -- with proper 2 digits indentation, but is necessary set tabulation "by hand", as this can't be done by script.

Install

  1. Copy the code and paste in a text file named "Bullets.py" or whatever "*.py".
  2. Run it by "Scripts > Execute script..."
  3. In order to scripts appear in "Scripts > Scribus scripts..." menu, put the file in:* Windows folder "x:\Program files\Scribus 1.3.9\share\scripts\" or in * Linux directory (copy as root) "/usr/share/scribus-nt/scripts/" (I gess). But this folders is for official scripts and could be overwritting when Scribus reinstalls.

Usage

Select a text frame or some paragraphs and run the script by choosing "Scripts" menu, options "Execute script..." or "SCribus scripts". Asked, input the operation type:

  • "*" will create bulleted list.
  • A number (up 3 digit) will create a numbered list begining with such number. Ex.: "1)". Range: 0 to 999.
  • One or two letters, upper or lower case, will create a lettered list begining with such letters with same case. Ex.: "a)". Range: "a" to "zz".
  • "/" will clear all marks.
  • An optional character after numbers or letters will be used as separator. Default: dot.
  • You can change bullets size putting any value but zero in bulletedListCharSize variable.
  • You can change bullets font putting it Scribus name in bulletedListCharFont variable.
  • This works for numbers and letters too, with its propers variables.
  • Get character hex code from menu: "Insert > Special character > [magnifying glass button]".
  • Change bullet character putting it code in bulletString variable, in place "006F" and "F06F":
if os.name == 'posix':
    bulletString = u'\u006F\t'  #  lower right drop-shadowed white square U+274F
else:
    bulletString = u'\uF06F\t' 

Tested and working in Scribus 1.3.9 running in Windows XP, Wine and Ubuntu 10.10.

Caveats

  • The script will clear any character formating in selected paragraphs (fonts, bold, superscripts etc), as scribus.getText() Python function get just plain text, not formating.
  • Some fonts can have different encodings depending operational system. Bullets glyphs can't appear if a publication made in Linux is opened in Windows, and vice-versa. This is true for "Dingbats" font from URW foundry.

Code

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
"""
Insert marks, numbers or letters in front selected paragraphs.
Author: prof. MS. José Antonio Meira da Rocha
mailto:joseantoniomeira@gmail.com
http://meiradarocha.jor.br
License GPL 2.0
2011-01-17a
 
Usage:
======
Select paragraphs at any points. Dialog will ask mark type.
Input:  
* "*" will create bulleted list.
* A number (up 3 digit) will create a numbered list begining with such number. Ex.: "1)"
* One or two letters uppper or lower case will create a lettered list begining with such letters. Ex.: "a)"
* An optional character after numbers or letters will be used as separator. Default: dot.
* "/" will clear all marks.
 
Caveats:
* Cleans all text format of text selection (font, bold, subscript, etc).
* Creates paragraph style "Numbered List", "Lettered list" or "Bullet", if it doesn't exist, with proper indentation. 
  Tabulation need to be defined by user to identical indentation value.
* If there is no selection, script will apply marks to all text in current frame.
* Some fonts can have different encodings depending operational system. 
  Bullets glyphs can't appear if a publication made in Linux is opened in Windows, and vice-versa. 
  This is true for "Dingbats" font from URW foundry. 
"""
 
import sys
import re
import os
 
try:
    import scribus
except ImportError,err:
    print "This Python script is written for the Scribus scripting interface."
    print "It can only be run from within Scribus."
    sys.exit(1)
from scribus import BUTTON_OK, ICON_WARNING
 
#######################
# Change this variables
# to match your needs
#######################
numberedListParaStyle = 'Numbered list'
# Font name in Scribus style. Empty string don't apply font.
# Get font list with 'getFontNames()' <F9> in Scripter console
numberedListCharFont = ''  
numberedListCharSize = 0  # In points. Zero don't apply font size
 
letteredListParaStyle = 'Lettered list'
letteredListCharFont = '' #  
letteredListCharSize = 0 #  
 
bulletedListParaStyle = 'Bullet'
bulletedListCharFont = 'Dingbats Regular' # 'Wingdings Regular' #  
bulletedListCharSize = 0
# Put your favorite unicode char and tab: '\u2022\t' # u'•\t' # bullet U+2022
# Get hex code from menu: "Insert > Special character > [magnifying button]".
# Modern dingbat fonts, as Zapf Dingbats or Wingdings, need special hex codes.
# URW "Dingbats Regular" Type 1 font have different code in Linux and in Windows,
# none of them Unicode.
# "ITC Zapf Dingbats Regular" have same code in both OS, but not Unicode encoding.
if os.name == 'posix':
    bulletString = u'\u006F\t'  #  lower right drop-shadowed white square U+274F
else:
    bulletString = u'\uF06F\t' 
 
defaultParaStyle = 'Default Paragraph Style'
defaultCharStyle = 'Default Character Style'
separator = '.'
indent = 22 # points
 
 
###########
# Locales
###########
 

# Message to ask frame selection
scriptWindowTitle = "Format list"
askSelectText = "<h2>Select a text</h2>\n" \
+"Select a <b>story text</b> where you want insert numbers."

# Message to open a document
askOpenDoc = "<h2>Open a document</h2>" \
+"Open a document before run this script."

# Message asking select more text
askSelectMoreText = "<h2>Select more text</h2>" \
+"Not unique text selected. Select more text before run this script."

wrongFrameTypeErrorMsg = "<h2>Wrong object selected</h2>" \
+"You must select a <b>text frame</b>."
 
markOperation = ''
bulletOperation = '*'
numberOperation = '1'
cleanOperation = '/'
alphaOperation = 'a'
defaultOperation = bulletOperation
alphabet = 'abcdefghijklmnopqrstuvwxyz'
alphabetLen = len(alphabet)

askOperationMsg = '<h2>What mark operation?</h2>' \
+'<ul><li>"<b>'+bulletOperation+'</b>" input will create <b>bullet</b> marks.' \
+'and apply <i>'+bulletedListParaStyle+'</i>  style.' \
+'<li>A <b>number</b> input will create <b>numbered list</b> starting <br>' \
+'with such number and apply <i>'+numberedListParaStyle+'</i>  style.' \
+'<li>One or two <b>letters</b> input, upper or lower case, <br>' \
+'will create <b>lettered list</b> starting with such letters,<br>' \
+'and apply <i>'+letteredListParaStyle+'</i>  style.' \
+'<li><font color="#0000ff">Optional char after number or letter<br>' \
+'will be used as separator. Default: dot. Ex.: "<b>a)</b>"</font>' \
+'<li>"<b>'+cleanOperation.lower()+'</b>" input will <b>delete</b> marks ' \
+'and apply <br><i>'+defaultParaStyle+'</i>  style.</li>' \
+'<li>Any <b>other</b> input will <b>cancel</b> all operations.</li></ul>' \
 
###### No need to change after this line #############
 
# Regular expression to match any mark:
#  '\r' = carriage return in Python
#  '\t' = tabulation in Python
#  [^\t|^\r] = non-tab or non-CR chars
#  {,4} = 0 to 4 currencies from paragraph begin to tab
reAnyMark  = r'(\r[^\t|^\r]{,4}\t)'  
 
##############
# Apply style
def applyStyle(style,frame):
    '''Apply a style in selected text.  
    If style doesn't exist, create it.'''
    try:
        scribus.setStyle(style,frame)
    except:
        scribus.createParagraphStyle(name=style,leftmargin=indent,firstindent=(indent*-1))
        scribus.setStyle(style,frame)
    return frame
 
##########################
# Get text cursor position
def getTextCursor(story):
    '''Find position of text cursor in text frame.
    Text need to be selected.'''
    selectedFrame = scribus.getSelectedObject()
    try:
        selectedText = scribus.getText()
    except:
        scribus.messageBox(scriptWindowTitle,wrongFrameTypeErrorMsg,ICON_WARNING,BUTTON_OK)
        scribus.selectObject(story)
        sys.exit()
    scribus.deselectAll()
    allText = scribus.getAllText(selectedFrame)
     
    if allText.count(unicode(selectedText)) == 1:
        cur = allText.find(unicode(selectedText))
    else:
        scribus.messageBox(scriptWindowTitle,askSelectMoreText,ICON_WARNING,BUTTON_OK)
        scribus.selectObject(story)
        sys.exit(1)
     
    return cur,allText,selectedText
 
############
# Numbering
num = 0
def numbering(matchObj):
    '''Function to re.sub use.'''
    global num
    # Define limit to list
    limit = 999
    if num > limit: num = limit
    s = u'\r'+str(num)+separator+'\t'
    num += 1
    return s
 
####################
# Lettering
def lettering(matchObj):
    '''Function to re.sub use.'''
    global num
    # Define limit to list (zz)
    limit = ((alphabetLen+1) * alphabetLen)
    if num > limit: num = limit
    d1 = ((num-1) % alphabetLen)
    s1 = alphabet[d1]
    d2 = ((num-1) / alphabetLen)-1
    if d2 > -1:
        s2 = alphabet[d2]
        s1 = s2+s1
    s = u'\r'+s1+separator+'\t'
    if markOperation.isupper():
        s = s.upper()
    num += 1
    return s
 
####################
def processMarks(t):
    '''Delete old marks and insert new ones.'''
    # Clear old numbers
    splitedText = []
    s = re.sub(reAnyMark,u'\r',t)
    if markOperation == numberOperation:
        # Insert numbers
        s = re.sub(u'\r',numbering,s)
        splitedText = re.split(reAnyMark,s)
        print 'Split:\n',splitedText
    elif markOperation.isalpha():
        # Insert letters
        s = re.sub(u'\r',lettering,s)
        splitedText = re.split(reAnyMark,s)
        print 'Split:\n',splitedText
    elif markOperation == bulletOperation:
        # Insert bullets
        b = u'\r'+bulletString
        s = re.sub(u'\r',b,s)
        splitedText = re.split(reAnyMark,s)
        print 'Split:\n',splitedText
     
    return s,splitedText
 
#################
# Format marks
def formatMarks(begin,splitedText,story):
    '''Format marks inserted by processMarks().
    Use splitedText array as map to point text in story frame.'''
    for m in range (1,len(splitedText),2): # element 0 is ''
        text = splitedText[m]
        length = len(splitedText[m])-2  # don't count '\r' and '\t'
        print repr(text[1:len(text)-1])
        if markOperation == bulletOperation:
            if bulletedListCharFont:
                scribus.selectText(begin,length,story)
                try: scribus.setFont(bulletedListCharFont,story)
                except: print "Can't find font:",bulletedListCharFont
            if bulletedListCharSize:
                scribus.selectText(begin,length,story)
                try: scribus.setFontSize(bulletedListCharSize,story)
                except: print "Couldn't apply list char size"
        if markOperation == numberOperation:
            if numberedListCharFont:
                scribus.selectText(begin,length,story)
                try: scribus.setFont(numberedListCharFont,story)
                except: print "Can't find font:",numberedListCharFont
            if numberedListCharSize:
                scribus.selectText(begin,length,story)
                try: scribus.setFontSize(numberedListCharSize,story)
                except: print "Couldn't apply list char size"
        if markOperation.isalpha():
            if letteredListCharFont:
                scribus.selectText(begin,length,story)
                try: scribus.setFont(letteredListCharFont,story)
                except: print "Can't find font:",letteredListCharFont
            if letteredListCharSize:
                scribus.selectText(begin,length,story)
                try: scribus.setFontSize(letteredListCharSize,story)
                except: print "Couldn't apply list char size"
        lineAfterBullet = splitedText[m+1]
        print begin,lineAfterBullet
        begin = begin + length + 2 + len(unicode(lineAfterBullet))
 
#################
# Do all the job
def doAllOperation(story):
    '''Insert marks in front selected paragraphs or clean marks.'''
    # Get selection begin
    cur,allText,selectedText = getTextCursor(story)
    # Get first selected paragraph begin
    # (last line break position after selection)
    begin = allText.rfind(u'\r',0,cur)+1 # Scribus not use '\n'?
    # Get selection end
    end = cur+len(unicode(selectedText))
    # Get last selected paragraph end
    end = allText.find(u'\r',end)
    totalLen = end - begin
    scribus.selectText(begin,totalLen,story)
    selectedText = scribus.getText(story)
    #print '\nSelected:\n',repr(selectedText.encode('utf-8')) # debug
    # Put temporary carriage return to allow numbering at string begin
    selectedText = u'\r'+selectedText
    # Clean old marks and insert new ones
    processedText,splitedText = processMarks(unicode(selectedText))
    # Strip temporary initial CR
    processedText = unicode(processedText)[1:]
    scribus.deleteText(story)
    scribus.insertText(processedText,begin,story)
    # Apply style
    scribus.deselectAll()
    # Get selection new end
    length = len(unicode(processedText))
    scribus.selectText(begin,length,story)
    if markOperation == bulletOperation:
        applyStyle(bulletedListParaStyle,story)
    elif markOperation == numberOperation:
        applyStyle(numberedListParaStyle,story)
    elif markOperation == cleanOperation:
        applyStyle(defaultParaStyle,story)
    elif markOperation.isalpha():
        applyStyle(letteredListParaStyle,story)
    # Apply special formating
    formatMarks(begin,splitedText,story)
    scribus.selectObject(story)
 
#####################
def decodeInput(res):
    '''Parse user input.'''
    global num,separator
    f = res[0]
    # Test numbered list
    if f.isdigit():
        n = re.search('\d{1,3}',res).group()
        num = int(n)
        # Get separator
        s = re.search('[^\d]{1,1}',res)
        if s:
            separator = s.group()
        return '1'
    # Test alphabetic list
    elif f.isalpha():
        if len(res) > 1: # if there is second letter or separator
            f2 = re.search('[a-zA-Z]{1,2}',res).group()
            if len(f2) > 1:
                digit1 = f2[1].lower()
                num = alphabet.find(digit1)+1
                digit2 = f2[0].lower()
                num2 = alphabet.find(digit2)+1
                num2 = num2 * len(alphabet)
                num = num + num2
            else:
                digit1 = f2[0].lower()
                num = alphabet.find(digit1)+1
        else:
            digit1 = f.lower()
            num = alphabet.find(digit1)+1
        # Get separator
        s = re.search('[^a-zA-Z]{1,1}',res)
        if s:
            separator = s.group()
    elif f == bulletOperation: pass
    elif f == cleanOperation: pass
    else: f = ''
    return f
     
#####################
def handleSelected():
    """Handle frame selection."""
    global markOperation,separator
    story = scribus.getSelectedObject(0)  
    if story:
        res = scribus.valueDialog(scriptWindowTitle,askOperationMsg,defaultOperation)
        res = res.strip()
        if res:
            markOperation = decodeInput(res)
            if markOperation:
                doAllOperation(story)
                scribus.docChanged(True)
    else:
        scribus.messageBox(scriptWindowTitle,askSelectText,ICON_WARNING,BUTTON_OK)
 
###############  
def main(argv):
    """Main entry point."""
    if scribus.haveDoc():
        scribus.setRedraw(False)
        handleSelected()
    else:   
        scribus.messageBox(scriptWindowTitle,askOpenDoc,ICON_WARNING,BUTTON_OK)
 
#######################
def main_wrapper(argv):
    try:
        scribus.statusMessage("Running script...")
        scribus.progressReset()
        main(argv)
    finally:
        if scribus.haveDoc():
            scribus.setRedraw(True)
        scribus.statusMessage("")
        scribus.progressReset()
 
if __name__ == '__main__':
    main_wrapper(sys.argv)