[reportlab-users] Circular text widget/drawing?

Jerome Alet reportlab-users@reportlab.com
Tue, 28 Jan 2003 19:58:32 +0100


--YZ5djTAD1cGYuMQK
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

On Tue, Jan 28, 2003 at 05:49:07PM +0100, Dinu Gherman wrote:
> Hi, just being nosey... has anybody written a circular text widget
> or drawing she'd like to share?

this was a starting point of something I wanted to write.

unfortunately lack of time etc...

hoping you'll like it.

include it as a demo if you want : I hereby give you the right 
to remove the GPL notice at the top and use and redistribute it
as you see fit, for example just put a RL license into it instead.

bye,

Jerome Alet
-- 
(c) 1928-???? The letters U, S, and A and the U.S.A. flag are  
the copyrighted property of Disney Enterprises Inc.  
Any infringment will be punished with the death penalty.
Details : http://yro.slashdot.org/article.pl?sid=03/01/15/1528253&tid=123

--YZ5djTAD1cGYuMQK
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="cdlabels.py"

#! /usr/bin/env python
#
# cdlabels (c) 2002 Jerome Alet
#
# 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.
#
# author: Jerome Alet - <alet@librelogiciel.com>
#
# This is the cdlabels, a CD labels generator written in Python
#
# Feel free to send me any feedback about this software at: alet@librelogiciel.com
#
# $Id: cdlabels.py,v 1.2 2002/03/19 19:11:51 jerome Exp $
#
# $Log: cdlabels.py,v $
# Revision 1.2  2002/03/19 19:11:51  jerome
# Modified sample data
#
# Revision 1.1.1.1  2002/03/19 14:55:52  jerome
# Initial import into CVS
#
#
#

import sys
import string
import cStringIO
from math import pi,sqrt
import PIL.Image

try :
    import DateTime
except ImportError :
    from mx import DateTime

from reportlab.lib.units import cm
from reportlab.pdfgen import canvas


__author__ = "alet@librelogiciel.com (Jerome Alet)"

__version__ = "0.1"

__doc__ = """cdlabels v%s (c) 2002 Jerome Alet - <%s>""" % (__version__, __author__)

class CDLabelsError :
        """CDLabel's Exception class."""
        def __init__(self, value) :
                self.value = value

        def __str__(self) :
                return str(self.value)

DISKSIZES = { \
              # id : (minradius, maxradius) (innerskip, outerskip) in centimeters
              "CD" : ((0.75, 6), (0.5, 0.1)), \
            }

class CDLabel :
    """A class for CD labels."""
    def __init__(self, title, subtitle="", tracks=[], date=DateTime.now(), size="CD") :
        """Initialize a CD label."""
        if not DISKSIZES.has_key(size) :
                raise CDLabelsError, "wrong label size %s" % size
        self.title = title
        self.subtitle = subtitle
        self.tracks = tracks
        self.date = "Date : %s" % str(date)
        ((self.minradius, self.maxradius), (self.innerskip, self.outerskip)) = DISKSIZES[size]
        self.minradius = self.minradius * cm
        self.maxradius = self.maxradius * cm
        self.innerskip = self.innerskip * cm
        self.outerskip = self.outerskip * cm

    def adjustCircularText(self, canv, text, radius, fontname, fontsize) :
        """Adjusts radius and font size to ensure a circular text will be drawn correctly.

           returns a tuple : (new radius, new fontsize).
           raises a CDLabelError exception when adjustment is impossible.

           TODO : make this method smarter.
        """
        oldfontsize = fontsize
        oldradius = radius
        ok = 0
        while (not ok) and fontsize and ((radius + fontsize) < (self.maxradius - self.outerskip)) :
            width = canv.stringWidth(text, fontname, fontsize)
            outercircle = 2.0 * pi * (radius + fontsize)
            if width < outercircle :
                ok = 1
            else :
                # TODO : do not do both at the same time
                fontsize = fontsize - 1
                radius = radius + 1.0
        if (not fontsize) or ((radius + fontsize) >= (self.maxradius - self.outerskip)) :
                raise CDLabelsError, "Text %s too long to be drawn with a radius of %f in font size %i" % (text, oldradius, oldfontsize)
        if oldfontsize != fontsize :
                sys.stderr.write("Text %s too long, font size changed from %i to %i\n" % (text, oldfontsize, fontsize))
        if oldradius != radius :
                sys.stderr.write("Text %s too long, radius changed from %f to %f\n" % (text, oldradius, radius))
        return (radius, fontsize)

    def drawCircularText(self, canv, text, radius, fontname, fontsize, adjust=1) :
        """Draws a circular text at a given distance from the centre.

           Algorithm from the PostScript Blue Book.
           If text is too long then font size is reduced and/or radius is increased if adjust=1.
        """
        if adjust :
            (radius, fontsize) = self.adjustCircularText(canv, text, radius, fontname, fontsize)
        canv.saveState()
        canv.setFont(fontname, fontsize)
        width = canv.stringWidth(text, fontname, fontsize)
        xradius = radius + (fontsize / 4.0)
        ratio = 360.0 / (4.0 * pi * xradius)
        canv.rotate(90.0 + (width * ratio))
        for letter in text :
            width = canv.stringWidth(letter, fontname, fontsize)
            halfangle = width * ratio
            canv.saveState()
            canv.rotate(-halfangle)
            canv.translate(radius, 0)
            canv.rotate(-90.0)
            canv.drawCentredString(0, 0, letter)
            canv.restoreState()
            canv.rotate(-2.0 * halfangle)
        canv.restoreState()

    def drawDate(self, canv) :
        self.drawCircularText(canv, self.date, self.minradius + self.innerskip + 2, 'Times-Roman', 8)

    def drawTitles(self, canv) :
        fontsize = 30
        self.drawCircularText(canv, self.title, self.maxradius - self.outerskip - (fontsize + 10), 'Helvetica-Bold', fontsize)
        self.drawCircularText(canv, self.subtitle, self.maxradius - self.outerskip - ((1.5 * fontsize) + 20), 'Helvetica', fontsize / 2)

    def drawTracks(self, canv) :
        if self.tracks :
            fontname = 'Helvetica'
            fontsize = 6
            mxlen = 0
            for track in self.tracks :
                mxlen = max(mxlen, canv.stringWidth(track, fontname, fontsize))

            yorigin = self.minradius + self.innerskip + (3*fontsize)

            # why the f..k are my maths this old ???
            #availablewidth = 2 * sqrt((self.maxradius - self.outerskip)**2 - yorigin**2)
            #print availablewidth

            canv.saveState()
            canv.setFont(fontname, fontsize)
            tracks = string.join(self.tracks, "\n")
            t = canv.beginText(-mxlen / 2.0, -yorigin)
            t.textLines(tracks)
            canv.drawText(t)
            canv.restoreState()

    def clip(self, canv) :
        """Sets a clipping region for future drawings."""
        # begin the clipping path
        path = canv.beginPath()

        # draw the internal and external limits
        path.circle(0, 0, self.minradius + self.innerskip)
        path.circle(0, 0, self.maxradius - self.outerskip)

        # validates the drawing path, with or without filling
        # TODO : stroke=0
        canv.clipPath(path, stroke = 1, fill = 0)

    def draw(self, canv) :
        """Draws the CD label on the PDF canvas."""
        # save current state of canvas
        canv.saveState()

        # set drawing thickness
        canv.setLineWidth(0.01)

        # set clipping region
        self.clip(canv)

        # then really draw the label's content
        self.drawDate(canv)
        self.drawTitles(canv)
        self.drawTracks(canv)

        # restore original state
        canv.restoreState()

def main() :
        tracks = ["1 - What the fuck is this ?", "2 - Hello point C", "3 - Foo Bar"]
        label = CDLabel("Jerome Alet", "and his orchestra", tracks)
        c = canvas.Canvas("label.pdf")
        c.translate(10.5*cm, 14.85*cm)
        label.draw(c)
        c.showPage()
        c.save()
        print __doc__

if __name__ == "__main__" :
        main()

--YZ5djTAD1cGYuMQK--