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, title="Fear of StyleSheets at AListApart. Opens in a new window" target="_foo">the

most reliable way to display fonts
, which while separating content and presentation as per

target="_foo" title="Opens in a new window.">W3C Accessibility guidelines

doesn't always enable users to resize their fonts using IE and Netscape 'Text

Size' tools.

Having read title="Give the User Control Over Your Fonts">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:

#!/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="";

#%cookie="";

$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))

&& (($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) && ($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 (!&ReadParse) {

# print &PrintHeader, "<!-- script parse error ($!)-->";

#}

&ReadParse;

if (($in{'textsize'}) && ($CSS)) {

$cookie{'textsize'} = $in{'textsize'};

#testing

#print $cookie{'textsize'}

}

################

## Start the headers

print "Content-Type: text/html

";

################

## Output the cookies

if ((%cookie) && ($CSS)) {

foreach $variable (keys %cookie) {

$cookie_var = $cookie{"$variable"};

print "Set-Cookie: $variable=$cookie_var\; domain=$ENV{'SERVER_NAME'}\; path=\/\;

";

}

}

###############

## 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

'; #ie in the past

$tmpfile="/full/path/to/your/public_html/resources/transparent.gif";

open( FROM, "< $tmpfile" );

binmode FROM;

binmode STDOUT;

# Print out the contents of the image file.

print "Content-type: image/gif

";

print $expires,"

";

print $pragma,"

";

print "

";

while (<FROM>) {

print $_;

}

close FROM;

###############

## End the headers

print "

";

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

<img src="/cgi-bin/font_cookie.cgi?<!--#ECHO VAR="QUERY_STRING"-->" height="1" width="1" alt="" />

(The <!--#ECHO VAR="QUERY_STRING"--> 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:

<a href="<!--#ECHO VAR="DOCUMENT_URI"-->?textsize=large"<Large fonts</a>

<a href="<!--#ECHO VAR="DOCUMENT_URI"-->?textsize=normal"<Medium fonts</a>

<a href="<!--#ECHO VAR="DOCUMENT_URI"-->?textsize=small"<Small fonts</a>

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.

##############################################################################

# 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';

&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 "$textsize

";

###########

## Output the start of the block - YMMV

print qq <h4 class="css_swap">Text not right?</h4>

;

print qq <p class="css_swap">

;

if ($size{"large"}) {

## If the text is set to large, offer the option to reduce to normal

print qq <a href="$PAGE?textsize=normal">Reduce Text</a> ;

} elsif ($size{"small"}) {

## If the text is set to small, offer the option to enlarge to normal

print qq <a href="$PAGE?textsize=normal">Enlarge Text</a> ;

} else {

## If the text is normal, offer the option to enlarge or reduce

print qq <a href="$PAGE?textsize=large">Enlarge Text</a><br />

;

print qq <a href="$PAGE?textsize=small">Reduce Text</a><br /> ;

###########

## Output the end of the block - YMMV

print q </p> ;

}

} else {

print "<!--No variation for you, sorry-->";

}

print "

";

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:

<!--#exec cmd="/full/path/to/your/cgi-bin/css_resize.cgi"-->

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:

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

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

<link rel="stylesheet" href="/resources/large.css">

<link rel="stylesheet" href="/resources/normal.css">

<link rel="stylesheet" href="/resources/small.css">

And here's the code to deliver it:

#!/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='<link rel="stylesheet" href="/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 (!&ReadParse) {

# print &PrintHeader, "<!-- script parse error ($!)-->";

#}

&ReadParse;

if ($in{'textsize'}) {

$size = $in{'textsize'};

#testing

#print $cookie{'textsize'}

}

#&PrintHeader;

#print $ENV{'QUERY_STRING'};

print $stylesheet_ref.$size.".css\" \/>

";

#print $ENV{'HTTP_COOKIE'};

And once again, you bring this into your page (in the <head> section of

course) with an SSI exec call:

<!--#exec cmd="/full/path/to/your/cgi-bin/css_swap.cgi"-->