Newer
Older
GB_Printer / Dump / extensions / eggbot_acrostic.py
# eggbot_acrostic.py
#
# Render an acrostic poem using the Hershey fonts
#
# This extension requires the hersheydata.py file which is part of the
# Hershey Text rendering extension written by Windell H. Oskay of
# www.evilmadscientist.com.  Information on that extension may be found at
#
#   http://www.evilmadscientist.com/go/hershey
#
# Copyright 2011, Daniel C. Newman,
#
# Significant portions of this code were written by Windell H. Oskay and are
# Copyright 2011, Windell H. Oskay, www.evilmadscientist.com
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import hersheydata			# data file w/ Hershey font data
import inkex
import simplestyle

WIDTH     = 3200
HEIGHT    = 800
MAX_H     = 32		# Maximum height of a Hershey font character (parens)
LINE_SKIP = 6		# Baseline skip between lines of text

# Mapping table to map the names used here to the corresponding
# names used in hersheydata.py.  This helps prevent end users from
# being impacted by a name change in hersheydata.py.  This can also
# be used to deal with a face being removed from hersheydata.py

map_our_names_to_hersheydata = {
	'astrology' : 'astrology',
	'cursive' : 'cursive',
	'cyrillic' : 'cyrillic',
	'futural' : 'futural',
	'futuram' : 'futuram',
	'gothiceng' : 'gothiceng',
	'gothicger' : 'gothicger',
	'gothicita' : 'gothicita',
	'greek' : 'greek',
	'japanese' : 'japanese',
	'markers' : 'markers',
	'mathlow' : 'mathlow',
	'mathupp' : 'mathupp',
	'meteorology' : 'meteorology',
	'music' : 'music',
	'scriptc' : 'scriptc',
	'scripts' : 'scripts',
	'symbolic' : 'symbolic',
	'timesg' : 'timesg',
	'timesi' : 'timesi',
	'timesib' : 'timesib',
	'timesr' : 'timesr',
	'timesrb' : 'timesrb' }

# The following two routines are lifted with impunity from Windell H. Oskay's
# hershey.py Hershey Text extension for Inkscape.  They are,
# Copyright 2011, Windell H. Oskay, www.evilmadscientist.com

def draw_svg_text(char, face, offset, vertoffset, parent):

	style = { 'stroke': '#000000', 'fill': 'none' }
	pathString = face[char]
	splitString = pathString.split()
	midpoint = offset - int(splitString[0])
	i = pathString.find("M")
	if i >= 0:
		pathString = pathString[i:] #portion after first move
		trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
		text_attribs = {'style':simplestyle.formatStyle(style), 'd':pathString, 'transform':trans}
		inkex.etree.SubElement(parent, inkex.addNS('path','svg'), text_attribs)
	return midpoint + int(splitString[1]) 	#new offset value

def renderText( parent, w, y, text, typeface ):

	'''
	Render a string of text starting from the point (w, y) and using
	the supplied typeface data.
	'''

	if ( text is None ) or ( text == '' ):
		return

	spacing = 3  # spacing between letters
	letterVals = [ ord( q ) - 32 for q in text ]

	for q in letterVals:
		if ( q < 0 ) or ( q > 95 ):
			w += 2 * spacing
		else:
			w = draw_svg_text( q, typeface, w, y, parent )

	return w

def renderLine( parent, x, y, line, typeface1, typeface2 ):

	'''
	Render a single line of text:
	+ The text runs horizontally from left to right starting at the point (x,y)
	+ The first character of the line is written using "typeface1"
	+ The remainder of the line of text is written using "typeface2"

	The entire line is stored as individual paths which are child elements
	of the SVG element "parent".  The text in typeface2 (line[1:]) is also
	placed in it's own subgroup.  The leading character is not placed in
	that subgroup.  The reasoning is that the user may want to pick out
	the first character of each line of text and put them in another layer
	for plotting in a different color.
	'''

	# Return now if there's nothing to do
	if ( line is None ) or ( line == '' ):
		return

	# Render the first character
	x = renderText( parent, x, y, [ line[0] ], typeface1 )

	# Render the rest of the line
	line = line[1:]
	if line == '':
		return
	g = inkex.etree.SubElement( parent, 'g' )
	renderText( g, x, y, line, typeface2 )

class AcrosticText( inkex.Effect ):

	def __init__( self ):

		inkex.Effect.__init__( self )
		self.OptionParser.add_option( "--tab",	#NOTE: value is not used.
			action="store", type="string", dest="tab", default="splash",
			help="The active tab when Apply was pressed" )
		self.OptionParser.add_option( "--line01", action="store",
			type="string", dest="line1", default="")
		self.OptionParser.add_option( "--line02", action="store",
			type="string", dest="line2", default="")
		self.OptionParser.add_option( "--line03", action="store",
			type="string", dest="line3", default="")
		self.OptionParser.add_option( "--line04", action="store",
			type="string", dest="line4", default="")
		self.OptionParser.add_option( "--line05", action="store",
			type="string", dest="line5", default="")
		self.OptionParser.add_option( "--line06", action="store",
			type="string", dest="line6", default="")
		self.OptionParser.add_option( "--line07", action="store",
			type="string", dest="line7", default="")
		self.OptionParser.add_option( "--line08", action="store",
			type="string", dest="line8", default="")
		self.OptionParser.add_option( "--line09", action="store",
			type="string", dest="line9", default="")
		self.OptionParser.add_option( "--line10", action="store",
			type="string", dest="line10", default="")
		self.OptionParser.add_option( "--line11", action="store",
			type="string", dest="line11", default="")
		self.OptionParser.add_option( "--line12", action="store",
			type="string", dest="line12", default="")
		self.OptionParser.add_option( "--face1",
			action="store", type="string", dest="face1", default="scriptc",
			help="Leading font typeface" )
		self.OptionParser.add_option( "--face2", action="store",
			type="string", dest="face2", default="scripts",
			help="Secondary typeface" )
		self.OptionParser.add_option( "--flip", action="store", type="inkbool",
			dest="flip", default=False,
			help="Flip the text for plotting with the egg's bottom at the egg motor" )
		self.OptionParser.add_option( "--stretch",
			action="store", type="inkbool", dest="stretch", default=True,
			help="Stretch the text horizontally to account for egg distortions" )

	def effect( self ):

		# Process the lines, ignoring leading or trailing blank lines
		# and collapsing multiple internal runs of blank lines into a
		# single blank line.
		lines = []
		prior_empty = False
		for i in range( 1, 13 ):
			line = eval( 'self.options.line' + str( i ) ).strip()
			if line == '':
				if len( lines ) != 0:
					prior_empty = True
			else:
				if prior_empty:
					lines.append( '' )
					prior_empty = False
				lines.append( line )

		# Return now if there are no lines to print
		line_count = len( lines )
		if line_count == 0:
			return

		# Determine how much vertical room we need for our text
		h = line_count * MAX_H + ( line_count - 1 ) * LINE_SKIP

		svg = self.document.getroot()
		doc_height = self.unittouu( svg.attrib['height'] )
		if doc_height <= 0:
			doc_height = HEIGHT
		doc_width = self.unittouu( svg.attrib['width'] )
		if doc_width <= 0:
			doc_width = WIDTH

		# Scale to doc_height pixels high
		scale_y = float( doc_height ) / float( h )
		if self.options.stretch:
			scale_x = scale_y * 1.5
		else:
			scale_x = scale_y

		# Determine where to position the text
		# We do not bother centering the text horizontally
		# to do that we would need to pre-render the text to determine
		# the length of the longest line.  That's too much bother so
		# we just skip that potential nice-to-have.
		x = float( doc_width ) / ( 2.0 * scale_x )
		y = float( MAX_H ) / scale_y

		# Get the two type faces
		name1 = self.options.face1
		if map_our_names_to_hersheydata.has_key( name1 ):
			name1 = map_our_names_to_hersheydata[name1]
		face1 = eval( 'hersheydata.' + name1 )
		name2 = self.options.face2
		if map_our_names_to_hersheydata.has_key( name2 ):
			name2 = map_our_names_to_hersheydata[name2]
		face2 = eval( 'hersheydata.' + name2 )

		# Create the group which will contain all of the text
		# We DO NOT make this a child of the current layer as that
		# would subject us to any transforms it might have.  That's
		# an issue even in the simple case of someone opening a default
		# document and then changing its dimensions to 3200 x 800:
		# Inkscape imposes a transform in that situation.  While that
		# transform is no big deal, it's another complication in trying
		# to just make the resulting text look right (right size, right
		# approximate position, etc.).

		if self.options.flip:
			attribs = { 'transform' : 'matrix(-%f,0,0,-%f,%d,%d)' % ( scale_x, scale_y, doc_width, doc_height ) }
		else:
			attribs = { 'transform' : 'scale(%f,%f)' % ( scale_x, scale_y ) }
		container = inkex.etree.SubElement( self.document.getroot(), 'g', attribs )

		# Finally, we render each line of text
		for i in range( 0, len( lines ) ):
			if lines[i] != '':
				g = inkex.etree.SubElement( container, 'g' )
				renderLine( g, x, y, lines[i], face1, face2 )
			y += MAX_H + LINE_SKIP

if __name__ == '__main__':
	e = AcrosticText()
	e.affect()