Skip to page content or skip to Accesskey List.
Search evolt.org
evolt.org login: or register

Work

Main Page Content

Give the user control over your fonts with Perl

Rated 4.16 (Ratings: 4) (Add your rating)

Log in to add a comment
(2 comments so far)

Want more?

 
Picture of MartinB

Martin Burns

Member info | Full bio

User since: April 26, 1999

Last login: March 30, 2010

Articles written: 128

My site is a funny old thing - it attempts to communicate less by what it says than by how it says it.

A large part of my work is in convincing clients that their site should be both usable, and accessible to people with a range of disabilities. It was therefore essential that I enable users with less than perfect vision to use my site. Unfortunately, the most reliable way to display fonts, which while separating content and presentation as per W3C Accessibility guidelines doesn't always enable users to resize their fonts using IE and Netscape 'Text Size' tools.

Having read Aardvark's excellent article on user font resizing, I felt sure that this was a sensible way to go for cookie and CSS enabled browsers. It's not everyone, to be sure, but it's a significant improvement.

One problem, though. His article is coded for a Windows NT/IIS/ASP solution. My site is Unix/Apache/Perl hosted.

However, as I do know a bit about this environment, translating the ASP solution to my system wasn't too hard. If you're wanting to give users control over their font sizes with Perl/Apache/Unix, here's the code you need.

The Basic Idea

Exactly as with Adrian's original, it comes in three parts:

  1. Check to see if the browser will do CSS. If so, set a cookie saying so (so we don't have to check again).
  2. Check to see if the user has requested a non-default size. If so, set a cookie registering this preference.
  3. Using the cookies, deliver an appropriate stylesheet, chosen from small, medium and large, and query-string encoded links to the alternative sized-versions of the same page.

I have added one bit of cleverness - as Macs will display fonts at a different size than PCs, the default size for PCs is small, and that for Macs is medium.

The Code

Set the cookies

One problem which Perl/Apache SSIs have is that cookies have to be sent with the HTTP headers. However, SSIs aren't interpreted until after these have been sent for the page you're building with SSI. Therefore, throwing the cookies out by putting them in normal SSIs as ASP does:

Response.Cookies("TextSize") = "Normal"

won't work. Which is a shame. However, you can set cookies with any files you send as page elements, like images. And Perl/CGI can send images just as well as it can text.

Solution: Send a dummy 1px image (say your normal transparent pixel) from a CGI with the cookie(s) in its HTTP headers.. Which is exactly what the following script does. It:

  1. Checks to see if the browser is CSS-compatible. This is basically IE or NS 4+, and Opera as well - note that if IE6 doesn't report itself as IE5, then you will need to edit this script. NS6 pretends to be NS5, so we're OK on that score. I've also specifically excluded browsers such as iCab and Lynx, which allow users to choose to use UA strings which claim version4/5 compatibility. They also allow users to make up their own, but that's another issue.
  2. Checks to see if there is a querystring of the form: textsize=something
  3. Sets cookies appropriately for each of the above by delivering an image content-type with cookie headers.

Here's the script:

<pre>
#!/usr/bin/perl
##############################################################################
# font_cookie.cgi Version 1.0 #
# Copyright 2000 Martin Burns scripts@easyweb.co.uk #
# based on ASP code by Adrian Roselli #
# Created 21/9/00 Last Modified 21/9/00 #
##############################################################################
# COPYRIGHT NOTICE #
# Copyright [and -left] 1999-2000 Martin Burns and Adrian Roselli. #
# All Rights Reserved except as provided below. #
# #
# font_cookie.cgi may be used and modified free of charge by anyone so long #
# as this copyright notice and the comments above remain intact. By using #
# this code you agree to indemnify Martin Burns, EasyWeb Design and Adrian #
# Roselli from any liability that might arise from its use. #
# #
# This script is released under the GPL. #
# Selling the code for this program or any derivative work is expressly #
# forbidden. A full copy of the GPL can be found in the Code section of #
# http://evolt.org. In all cases copyright and this header must remain #
# intact. #
##############################################################################
## ASP Code by Adrian Roselli from http://roselli.org
## Provided free for evolt.org and its members.
## No catches, just give me credit.
## This browser detection only detects IE4.x and
## NN4.x, please modify it as you see fit to get
## the 5.x and alternate manufacturer CSS-capable
## browsers.
## Last change July 10, 1999

############################################################################
# Set some variables #
############################################################################
#Testing:
#use diagnostics;
#$ENV{'QUERY_STRING'} = 'textsize=larger';
#$ENV{'HTTP_USER_AGENT'} = 'Mozilla/4.0'; #(compatible; MSIE 5.5; Windows98)
#$ENV{'SERVER_NAME'} = 'www.easyweb.co.uk';
#$ENV{'HTTP_COOKIE'} = 'browser=css';


## Get the browser name and version number
$USERAGENT = $ENV{'HTTP_USER_AGENT'};

##initialise other vars
#%in=&quot;&quot;;
#%cookie=&quot;&quot;;
$REQUIRE_DIR = '/full/path/to/dir/with/subroutines/like/your/cgi-bin/';
$YES=1;
$NO=0;
$CSS=$NO;


############################################################################
# Get Required subroutines which need to be included. #
############################################################################

# Push the $REQUIRE_DIR onto the @INC array for include file directories.

if ($REQUIRE_DIR ne '') {
    push(@INC, $REQUIRE_DIR);
}

# Require Necessary Routines for this script to run.
require 'cgi-lib.pl'; #ye olde faithful

########################
## Browser Determination
## Check the UserAgent string to see
## if it contains Mozilla or MSIE, and then
## to see if it has 4. or 5. in the version number
## or alternatively is Opera
if (
      ((($USERAGENT =~ /mozilla/i) || ($USERAGENT =~ /msie/i))
      &amp;&amp; (($USERAGENT =~ /4./) || ($USERAGENT =~ /5./)))
     || ($USERAGENT =~ /opera/i)) {
     
     $CSS=$YES;
}

##Cut out sneaky ones like iCab which allow users to set the UA string
@sneakybrowsers = ( 'icab', 'lynx');
foreach $browser (@sneakybrowsers) {
if ($USERAGENT =~ /$browser/i) {$CSS = $NO}
}

## If the cookie Browser has no value,
## then perform the following
if (($ENV{'HTTP_COOKIE'} !~ /browser=/i) &amp;&amp; ($CSS)) {
    ## Sets a variable for the cookie so that the
    ## browser is marked as CSS-capable
    $cookie{'browser'} = 'css';
}



################
## Set Text Size

## If the URL does not have a ?size=value
## appended to it (meaning the user clicked
## one of the links), then perform the following
#if (!&amp;ReadParse) {
# print &amp;PrintHeader, &quot;&lt;!-- script parse error ($!)--&gt;&quot;;
#}
&amp;ReadParse;
if (($in{'textsize'}) &amp;&amp; ($CSS)) {
  $cookie{'textsize'} = $in{'textsize'};
  #testing
  #print $cookie{'textsize'}
}
################
## Start the headers
print &quot;Content-Type: text/html\n&quot;;

################
## Output the cookies
if ((%cookie) &amp;&amp; ($CSS)) {

    foreach $variable (keys %cookie) {
        $cookie_var = $cookie{&quot;$variable&quot;};
        print &quot;Set-Cookie: $variable=$cookie_var\; domain=$ENV{'SERVER_NAME'}\; path=\/\;\n&quot;;
    }
}

###############
## Output the image
## if you want to force the image not to be cached
$pragma='Pragma: no-cache';
$expires='Expires: Wed, 26 Feb 1997 08:21:57 GMT\n'; #ie in the past

$tmpfile=&quot;/full/path/to/your/public_html/resources/transparent.gif&quot;;
open( FROM, &quot;&lt; $tmpfile&quot; );
binmode FROM;
binmode STDOUT;

# Print out the contents of the image file.
print &quot;Content-type: image/gif\n&quot;;
print $expires,&quot;\n&quot;;
print $pragma,&quot;\n&quot;;
print &quot;\n&quot;;

while (&lt;FROM&gt;) {
      print $_;
   }

close FROM;


###############
## End the headers
print &quot;\n&quot;;
</pre>

The way you get this onto your page is by pretending it's a standard image:

&lt;img src=&quot;/cgi-bin/font_cookie.cgi?&lt;!--#ECHO VAR="QUERY_STRING&quot;--&gt;&quot; height=&quot;1&quot; width=&quot;1&quot; alt=&quot;&quot; /&gt;

(The &lt;!--#ECHO VAR=&quot;QUERY_STRING&quot;--&gt; bit just makes sure that any query strings applied to the page get passed to the CGI. This will principally be textsize=something.)

The only downside to getting the cookies out like this is that a cookie won't be available to the rest of your script until after the first page the user hits on your site - users will only be able to choose their font size from the second page they visit.

Choose a size

Obviously, the simple way to give the users choice over which size of fonts they are to receive is to put a bunch of SSI links on the page like this:

<pre>
&lt;a href=&quot;&lt;!--#ECHO VAR=&quot;DOCUMENT_URI&quot;--&gt;?textsize=large&quot;&lt;Large fonts&lt;/a&gt;
&lt;a href=&quot;&lt;!--#ECHO VAR=&quot;DOCUMENT_URI&quot;--&gt;?textsize=normal&quot;&lt;Medium fonts&lt;/a&gt;
&lt;a href=&quot;&lt;!--#ECHO VAR=&quot;DOCUMENT_URI&quot;--&gt;?textsize=small&quot;&lt;Small fonts&lt;/a&gt;
</pre>

But then the user has to know what size they have at present, and develop a mental model for your font calibration. It's also a bit silly to give the choice of the current size, as it won't do anything! A much more elegant solution is to only give relative choices of larger or smaller than the present size. This means that when you're at the smallest size, you only give the choice of 'Larger', and when you're at the largest size, you only give the choice of 'Smaller'.

What this script does is the logic behind that, working out the current size and only delivering appropriate choices which users can instinctively grasp.

<pre>
##############################################################################
# cssResize - css_resize.cgi Version 1.0 #
# Copyright 2000 Martin Burns scripts@easyweb.co.uk #
# based on ASP code by Adrian Roselli #
# Created 21/9/00 Last Modified 21/9/00 #
##############################################################################
# COPYRIGHT NOTICE #
# Copyright [and -left] 1999-2000 Martin Burns and Adrian Roselli. #
# All Rights Reserved except as provided below. #
# #
# cssResize may be used and modified free of charge by anyone so long #
# as this copyright notice and the comments above remain intact. By using #
# this code you agree to indemnify Martin Burns, EasyWeb Design and Adrian #
# Roselli from any liability that might arise from its use. #
# #
# This script is released under the GPL. #
# Selling the code for this program or any derivative work is expressly #
# forbidden. A full copy of the GPL can be found in the Code section of #
# http://evolt.org. In all cases copyright and this header must remain #
# intact. #
##############################################################################

## Code by Adrian Roselli from http://roselli.org
## Provided free for evolt.org and its members.
## No catches, just give me credit.
## Make sure you supply your own images, rollovers,
## whatever. These are just suggestions, especially
## since text links are not always attractive.
## Last change July 10, 1999

## Request the cookie Browser and see if it is a
## CSS-capable browser, based on your previous work.
## This ensures that you do not display the buttons
## if the browser cannot use them.



#Testing
#use diagnostics;



##Set a variable or two
#testing
#$ENV{'HTTP_COOKIE'} = 'textsize=small';
#$ENV{'QUERY_STRING'} = 'textsize=large';
$USER_AGENT = $ENV{'HTTP_USER_AGENT'};
require 'cgi-lib.pl';
&amp;ReadParse;
%size = ('large', '',
         'normal', '',
         'small', '');

#Testing
#$ENV{'HTTP_COOKIE'} = 'textsize=normal; browser=css';
#$ENV{'HTTP_COOKIE'} = 'browser=css';
#$ENV{'DOCUMENT_URI'} = '/cv/index2.html';
if ($ENV{'HTTP_COOKIE'} =~ /browser=css/i) {
    $PAGE = $ENV{'DOCUMENT_URI'};
    if ($ENV{'HTTP_COOKIE'} =~ /textsize=(large|small|normal)/i) {
$textsize=$1;

   } elsif ($USER_AGENT =~ /windows/i) {
       $textsize='small';

    } else {$textsize='normal'}


      ##Check for query strings
   if ($in{'textsize'}) {
$textsize = $in{'textsize'};
   }



   $size{$textsize}='yes';
# print &quot;$textsize\n&quot;;
 

    ###########
    ## Output the start of the block - YMMV
    print qq|&lt;h4 class=&quot;css_swap&quot;&gt;Text not right?&lt;/h4&gt;\n|;
    print qq|&lt;p class=&quot;css_swap&quot;&gt;\n|;

if ($size{&quot;large&quot;}) {
    ## If the text is set to large, offer the option to reduce to normal
print qq|&lt;a href=&quot;$PAGE?textsize=normal&quot;&gt;Reduce Text&lt;/a&gt;|;
} elsif ($size{&quot;small&quot;}) {
    ## If the text is set to small, offer the option to enlarge to normal
print qq|&lt;a href=&quot;$PAGE?textsize=normal&quot;&gt;Enlarge Text&lt;/a&gt;|;
} else {
    ## If the text is normal, offer the option to enlarge or reduce
print qq|&lt;a href=&quot;$PAGE?textsize=large&quot;&gt;Enlarge Text&lt;/a&gt;&lt;br /&gt;\n|;
    print qq|&lt;a href=&quot;$PAGE?textsize=small&quot;&gt;Reduce Text&lt;/a&gt;&lt;br /&gt;|;

    ###########
    ## Output the end of the block - YMMV
    print q|&lt;/p&gt;|;

    }
} else {
    print &quot;&lt;!--No variation for you, sorry--&gt;&quot;;
}
print &quot;\n\n&quot;;
</pre>
Interesting sidenote:
I've made the code more readable by using the Perl q and qq quoting functions. This allows you to use any character in place of single and double quotes in a print statement, thereby allowing you to use real single and double quotes in what you're printing without escaping them. This is particularly handy if you're outputting JavaScript which already plays silly beggers with single and double quotes.

You bring this into your page at the point where you want the options to appear with a standard SSI exec call:

&lt;!--#exec cmd=&quot;/full/path/to/your/cgi-bin/css_resize.cgi&quot;--&gt;

Deliver the stylesheet

The last thing we need to do is to deliver the appropriate stylesheet, either the one chosen by the user, or a sensible default where the user either thinks that the basic size is OK, or (on that vital first page before the cookie is set) hasn't yet seen the options.

I've set it up such that Windows machines get the small size as default, and Macs, Unix boxen etc get the normal (middle) size. Naturally, this will depend on how you set up the fonts in your stylesheets. In mine, the normal body text is set as follows:

  • Large: 12pt
  • Normal: 11pt
  • Small: 10pt

with all <hx> and other text styles also being appropriately scaled.

To deliver the appropriate stylesheet, the page will need one of the following:

    &lt;link rel=&quot;stylesheet&quot; href=&quot;/resources/large.css&quot;&gt;<br>
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/resources/normal.css&quot;&gt;<br>
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/resources/small.css&quot;&gt;
    

And here's the code to deliver it:

<pre>
#!/usr/bin/perl
##############################################################################
# cssSwap - css_swap.cgi Version 1.0 #
# Copyright 2000 Martin Burns scripts@easyweb.co.uk #
# based on ASP code by Adrian Roselli #
# Created 21/9/00 Last Modified 21/9/00 #
##############################################################################
# COPYRIGHT NOTICE #
# Copyright [and -left] 1999-2000 Martin Burns and Adrian Roselli. #
# All Rights Reserved except as provided below. #
# #
# cssSwap may be used and modified free of charge by anyone so long #
# as this copyright notice and the comments above remain intact. By using #
# this code you agree to indemnify Martin Burns, EasyWeb Design and Adrian #
# Roselli from any liability that might arise from its use. #
# #
# This script is released under the GPL. #
# Selling the code for this program or any derivative work is expressly #
# forbidden. A full copy of the GPL can be found in the Code section of #
# http://evolt.org. In all cases copyright and this header must remain #
# intact. #
##############################################################################
## Code by Adrian Roselli from http://roselli.org
## Provided free for evolt.org and its members.
## No catches, just give me credit.
## Make sure you point to the right style sheets.
## Last change July 10, 1999

##Set a variable or two
#testing
#$ENV{'HTTP_COOKIE'} = 'textsize=small';
require 'cgi-lib.pl';
$size='';
$stylesheet_ref='&lt;link rel=&quot;stylesheet&quot; href=&quot;/resources/';
$USER_AGENT=$ENV{'HTTP_USER_AGENT'};



## Iterate through the size options, with
## a default if there are no matches.

if ($ENV{'HTTP_COOKIE'} =~ /textsize=(large|small|normal)/i) {
    $size=$1;
} elsif ($USER_AGENT =~ /windows/i) {
    $size='small';
} else {$size='normal'}

################
## Check for query string vars

## If the URL does not have a ?size=value
## appended to it (meaning the user clicked
## one of the links), then perform the following
#if (!&amp;ReadParse) {
# print &amp;PrintHeader, &quot;&lt;!-- script parse error ($!)--&gt;&quot;;
#}
&amp;ReadParse;
if ($in{'textsize'}) {
  $size = $in{'textsize'};
  #testing
  #print $cookie{'textsize'}
}

#&amp;PrintHeader;
#print $ENV{'QUERY_STRING'};
print $stylesheet_ref.$size.&quot;.css\&quot; \/&gt;\n\n&quot;;
#print $ENV{'HTTP_COOKIE'};
</pre>

And once again, you bring this into your page (in the &lt;head&gt; section of course) with an SSI exec call:

&lt!--#exec cmd=&quot;/full/path/to/your/cgi-bin/css_swap.cgi&quot;--&gt;

Martin Burns has been doing this stuff since Netscape 1.0 days. Starting with the communication ends that online media support, he moved back through design, HTML and server-side code. Then he got into running the whole show. These days he's working for these people as a Project Manager, and still thinks (nearly 6 years on) it's a hell of a lot better than working for a dot-com. In his Copious Free Time™, he helps out running a Cloth Nappies online store.

Amongst his favourite things is ZopeDrupal, which he uses to run his personal site. He's starting to (re)gain a sneaking regard for ECMAscript since the arrival of unobtrusive scripting.

He's been a member of evolt.org since the very early days, a board member, a president, a writer and even contributed a modest amount of template code for the current site. Above all, he likes evolt.org to do things because it knowingly chooses to do so, rather than randomly stumbling into them. He's also one of the boys and girls who beervolts in the UK, although the arrival of small children in his life have knocked the frequency for 6.

Most likely to ask: Why would a client pay you to do that?

Least likely to ask: Why isn't that navigation frame in Flash?

XHTML css link

Submitted by MartinB on December 27, 2000 - 06:55.

Reading through, I've noticed that the second last line of the last script reads like this:
print $stylesheet_ref.$size.&quot;.css\&quot; \/&gt;\n\n&quot;;

which will output code like this:
&lt;link rel=&quot;stylesheet&quot; href=&quot;/resources/small.css&quot; /&gt;
(Note the closing slash)

This is fine, if, like me, you've coded your site using XHTML. If you're coding to HTML4.x, then the last line should be:
print $stylesheet_ref.$size.&quot;.css\&quot;&gt;\n\n&quot;;

which will lose the slash

login or register to post comments

Reducing server overhead with XSSI

Submitted by MartinB on October 21, 2001 - 11:50.

The above method works well, but for busy sites, the server overhead of running three CGIs on a single page is quite heavy.

A less server-intensive method for simple flow-control such as the css_swap.cgi script would be to use Apache's Extended Server Side Includes (XSSI), which allow you to run simple conditionalisations and set & substitute variables.

To do this, you'd only need one CSS file (which makes the maintenance easier too), but you'd need to ensure that the server parses .css files for XSSI. You'd do this by adding .css to your .htaccess file.

Then, you'd add the following to the start of your single CSS file:

<!--#if expr="${QUERY_STRING} = /textsize/" -->
   <!--#set var="size_string" value="$QUERY_STRING" -->
<!--#elif expr="${HTTP_COOKIE} = /textsize/" -->
    <!--#set var="size_string" value="$HTTP_COOKIE" -->
<!--#elif expr="${HTTP_USER_AGENT} = /Windows/" -->
    <!--#set var="size_string" value="textsize=small" -->
<!--#else -->
    <!--#set var="size_string" value="textsize=normal" -->
<!--#endif -->


<!--#if expr="${size_string} = /textsize\=small/" -->
    <!--#set var="very_small" value="8px" -->
    <!--#set var="small" value="9px" -->
    <!--#set var="normal" value="10px" -->
    <!--#set var="large" value="11px" -->
    <!--#set var="very_large" value="12px" -->
    <!--#set var="huge" value="14px" -->
    <!--#set var="enourmous" value="22px" -->

<!--#elif expr="${size_string} = /textsize\=large/" -->
    <!--#set var="very_small" value="10px" -->
    <!--#set var="small" value="11px" -->
    <!--#set var="normal" value="12px" -->
    <!--#set var="large" value="14px" -->
    <!--#set var="very_large" value="18px" -->
    <!--#set var="huge" value="22px" -->
    <!--#set var="enourmous" value="30px" -->

<!--#else -->
    <!--#set var="very_small" value="9px" -->
    <!--#set var="small" value="10px" -->
    <!--#set var="normal" value="11px" -->
    <!--#set var="large" value="12px" -->
    <!--#set var="very_large" value="14px" -->
    <!--#set var="huge" value="18px" -->
    <!--#set var="enourmous" value="24px" -->

<!--#endif -->

(yes, those are regular expressions in the XSSI) and make sure that every font-size: definition in the file is of the form font-size: &lt;!--#echo var=&quot;normal&quot; --&gt; ;, like so:

body    {
	    font-family :  "Trebuchet MS",Verdana, Geneva, Arial, sans-serif;
	    margin : 0;
	    font-size: <!--#echo var="normal" --> ;
	    color: #000033;
	    background-color: #3399CC
		}

h1      {
	    margin-left: 0px;
	    padding-left: 0px; 
	    padding-right : 5px;
	    padding-bottom: 5px;
	    margin-bottom : 0px;
	    padding-top : 5px;
	    color: Black;
	    font-size : <!--#echo var="enourmous" --> ;
		}

code    {
	    font-family : Courier,"Courier New",fixed-width ;
	    font-size : <!--#echo var="large" --> ;
	    color : #333333 ; 
		}	
			
input, select, textarea { 
	    font-family :  "Trebuchet MS", Verdana, Geneva, Arial, sans-serif;
	    font-size: <!--#echo var="small" --> ;	
	    margin-top : 0px ;	
	    margin-bottom : 3px ;
	    color : #000000 ; 
		}

login or register to post comments

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.orgEvolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.