Other Projects
H Hershey's Font  Lab Report
One of the "vector characters" in the Hershey Font 

Windows 2000 / Delphi 7 Version

Red Hat 7.2 Linux / Kylix 3 Version

The purpose of this Delphi/Kylix project is to demonstrate how to manipulate the data and display the characters in the original Hershey font files.

The "Hershey Fonts" were developed in the 1960s by Dr. A. V. Hershey at the U.S. Naval Weapons Laboratory.

The U.S. National Bureau of Standards published the 173-page Special Publication 424 in 1976:  A Contribution To Computer Typesetting Techniques:  Tables of Coordinates for Hershey's Repertory of Occidental Type Fonts and Graphic Symbols.  The publication listed all the data points and showed the graphics for each character.  The data were available on magnetic tape as recently as the mid-1980s for $500  from the National Technical Information Service!  A few years later the nearly 500 KB data file could be found on the Internet -- but it's somewhat difficult to find today.

There were 1377 characters in the original Hershey set, each of which is assigned a number between 1 and 3926.  The characters are described as uniplex, duplex, or triplex according to the number of parallel strokes used in the construction of the character.  Three sizes of characters are available:  the principal or normal size (21 raster units high, em = 32), the indexical size (13 raster units high, em = 21), and the cartographic size (9 raster units high).

In the Hershey system, characters are drawn by connecting lines between successive (x, y) coordinates pairs.  The coordinates of each character are given in raster coordinates, which are integers ranging from 49 to -49.  A useful quantity is the printer's em, or the distance between the bottoms of two successive lines of close packed text.  

The data are organized in the following way:  The first column is the character number, the first pair of numbers separated by colons (:) are the left and right boundaries of the character in raster coordinates, and succeeding pairs of numbers set off by colons denote the (x,y) set for that character.  An (x,y) coordinate pair of (-64, 0) indicates that the pen is lifted at that point in the character; a coordinate pair of (-64, -64) indicates that the end of the character has been reached.

Here is what the data for the first three characters look like:

    1  : -5   5:  0  -5: -4   4:-64   0:  0  -5:  4   4:-64   0: -2   1:
       :  2   1:-64 -64:
    2  : -5   5: -3  -5: -3   4:-64   0: -3  -5:  1  -5:  3  -4:  3  -2:
       :  1  -1:-64   0: -3  -1:  1  -1:  3   0:  3   3:  1   4: -3   4:
       :-64 -64:
    3  : -5   6:  4  -4:  2  -5:  0  -5: -2  -4: -3  -2: -3   1: -2   3:
       :  0   4:  2   4:  4   3:-64 -64:                                                       


Materials and Equipment

Software Requirements

Windows 95/98/2000 Linux
Delphi 3/4/5/6/7 (to recompile) Kylix 3 (to recompile)
Hershey.EXE Hershey excutable


Hardware Requirements
800-by-600 video display 


1.  Start the Windows or Linux executable, Hershey.

2.  In the list box at the left of the screen, scroll up or down and select the desired Hershey symbol -- or just step through one-by-one to view them all.  When a Hershey character is selected, the "Points" memo box shows the data for that symbol. 

3.  If desired, change the scale factor, or the rotation angle, for the selected character.

4.  Press Show Table button to view sections of the Hershey font.


 The original data files, which were combined into a single Hershey.DAT file, were read in two steps in the FormCreate method.  The first step involved reading the "Raw" data from the original file.  The second step combined lines for the same symbol into a single line in the ListBoxHershey list.

Loading Hershey.DAT into ListBoxHershey

procedure TFormHershey.FormCreate(Sender: TObject);
    Filename: TFilename;
    i       : INTEGER;
    Raw     : TStringList;
    s       : STRING;
  // Don't allow form to be resized
  WITH FormHershey.Constraints DO
    MaxHeight := Height;
    MaxWidth := Width;
    MinHeight := Height;
    MinWidth := Width

  Hershey := TStringList.Create;

  Filename := ExtractFilePath(ParamStr(0)) + 'Hershey.DAT';

  IF FileExists(Filename)

    Screen.Cursor := crHourGlass;

      Raw := TStringList.Create;

        // Reduce each character to one line
        s := Raw[0];
        FOR i := 1 TO Raw.Count-1 DO
          IF   COPY(Raw[i], 1, 8) = ' :'
          THEN s := s + COPY(Raw[i],9, LENGTH(Raw[i])-8)
          ELSE BEGIN
            s := COPY(s, 1, Pos(':',s)-1);

            s := Raw[i]
        Hershey.Add(s); // take care of last character
        s := COPY(s, 1, Pos(':',s)-1);

        ListBoxHershey.ItemIndex := 0;


      Screen.Cursor := crDefault

    // Avoid SpinEditScaleChanged being called during
    // initialization until set here.
    SpinEditScale.OnChanged := SpinEditScaleChanged;

  ELSE ShowMessage('Cannot find data file "Hershey.DAT"')

Once the table is loaded, the first symbol in the list is automatically selected.  Whenever a symbol is selected, or when its scaling factor or rotation angle is changed, the HersheyChange method is called:

Changing a  selected symbol, or change in scaling or rotation of a symbol

procedure TFormHershey.ListBoxHersheyClick(Sender: TObject);
procedure TFormHershey.UpdateHersheyLook(Sender: TObject);
  // This appraoch avoids unnecessry updates and flicker 
  // of MemoCharacter

The HersheyChange method updates the display of a single Hershey symbol:

Displaying single Hershey symbol
PROCEDURE TFormHershey.HersheyChange(CONST NewPointList:  TUpdateList);
    TPenMode = (pmUp, pmDown);
    alpha        :  Double;
    Bitmap       :  TBitmap;
    BoundaryLeft :  INTEGER;
    BoundaryRight:  INTEGER;
    CosAlpha     :  Double;
    index        :  STRING;
    penmode      :  TPenMode;
    s            :  STRING;
    Scale        :  INTEGER;
    SinAlpha     :  Double;
    t            :  STRING;
    x            :  INTEGER;
    xMidPoint    :  INTEGER;
    xOld         :  INTEGER;
    xPrime       :  INTEGER;
    xSave        :  INTEGER;
    y            :  INTEGER;
    yOld         :  INTEGER;
    yMidPoint    :  INTEGER;
    yPrime       :  INTEGER;
  xOld := 0;   // avoid compiler warning
  yOld := 0;
  xMidPoint := Image.Width  DIV 2;
  yMidPoint := Image.Height DIV 2;
  Scale := SpinEditScale.Value;
  IF   NewPointList = ulUpdateList
  THEN MemoCharacter.Lines.Clear;
  s := ListBoxHershey.Items[ListBoxHershey.ItemIndex];
  index := Trim( COPY(s,1,7) );
  BoundaryLeft  := StrToInt(COPY(s,1,3));
  BoundaryRight := StrToInt(COPY(s,5,3));
  Bitmap := TBitmap.Create;
    Bitmap.Width  := Image.Width;
    Bitmap.Height := Image.Height;
    Bitmap.Canvas.Font.Name := 'Arial';
    Bitmap.Canvas.Font.Height := MulDiv(Image.Height, 12, 100);
    Bitmap.Canvas.Font.Style := [fsBold];
    Bitmap.Canvas.TextOut(2,2, index);
    Bitmap.Canvas.Pen.Color := clSilver;
    Bitmap.Canvas.MoveTo(xMidPoint + BoundaryLeft*Scale,  yMidPoint - 5*Scale);
    Bitmap.Canvas.LineTo(xMidPoint + BoundaryLeft*Scale,  yMidPoint + 5*Scale);
    Bitmap.Canvas.MoveTo(xMidPoint + BoundaryRight*Scale, yMidPoint - 5*Scale);
    Bitmap.Canvas.LineTo(xMidPoint + BoundaryRight*Scale, yMidPoint + 5*Scale);
    penmode := pmUp;
    WHILE( LENGTH(s) > 0 ) DO
      x := StrToInt(COPY(s,1,3));
      y := StrToInt(COPY(s,5,3));
      IF   NewPointList = ulUpdateList
      THEN MemoCharacter.Lines.Add( '(' + IntToStr(x) + ', ' + IntToStr(y) + ')' );
      // Rotate figure
      // As suggested by Jacques Oberto, rotation of these figures is easy
      // since the center of the figure is the origin.
      t := 'Rotation = ' + IntToStr(TrackBarRotation.Position) +
           ' degree';
      IF   TrackBarRotation.Position <> 1
      THEN t := t + 's';
      LabelRotate.Caption := t;
      // use "-" here to force counterclockwise rotation
      alpha := -TrackBarRotation.Position * PI / 180;  {degrees to radians}
      // Could use SinCos function in math unit instead of separate calls
      SinAlpha := Sin(alpha);
      CosAlpha := Cos(alpha);
      // See Rotation Math background
      xSave  := x;
      xPrime := Round(xSave*CosAlpha - y*SinAlpha);
      yPrime := Round(xSave*SinAlpha + y*CosAlpha);
      // Rescale and translate figure
      xPrime := xMidPoint + Scale*xPrime;
      yPrime := yMidPoint + Scale*yPrime;
      Bitmap.Canvas.Pen.Color   := clBlue;
      Bitmap.Canvas.Brush.Color := clBlue;
      // "-64" is tag for pen up
      IF   (x = -64) OR (y = -64)
      THEN penmode := pmUp
        IF   penmode = pmUp
        THEN BEGIN
          Bitmap.Canvas.MoveTo(xPrime, yPrime);
          penmode := pmDown
        ELSE BEGIN
          IF   CheckBoxShowDots.Checked
          THEN BEGIN
            Bitmap.Canvas.Ellipse(xOld-2,   yOld-2,   xOld+2,   yOld+2);
            Bitmap.Canvas.Ellipse(xPrime-2, yPrime-2, xPrime+2, yPrime+2);
            Bitmap.Canvas.MoveTo(xOld, yOld)
          Bitmap.Canvas.LineTo(xPrime, yPrime);
      xOld := xPrime;
      yOld := yPrime;
    Image.Picture.Graphic := Bitmap;
END {HersheyChange}; 

The Show Table button displays a separate screen with all the Hershey symbols shown in eight tables.  See the source code for how this is done.

Conversion from Delphi 3 to Kylix was not completely straightforward (but this was only my second Delphi-to-Kylix conversion).  

The units needed by the VCL version include:

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Spin, ComCtrls;

The units needed by the CLX version include:

SysUtils, Types, Classes, Variants, QGraphics, QControls, QForms, QDialogs,
QStdCtrls, QExtCtrls, QComCtrls; 

Note that many of the VCL units have "Q" corresponding names in CLX.

I encountered and fixed these problems in the conversion:

1.  I observed that Kylix forms needed to have "Scaled = FALSE" to get the same screen look as "Scaled = TRUE" in Delphi.  This is confusing.  Also be sure to set AutoScroll=FALSE.

2.  The Application icon must be set as a Form property in Kylix.  In Delphi this also works but I had learned to set it in Delphi via Project | Options | Application | Icon.

3.  I had problems getting Kylix to honor the Border Icons (maximize = FALSE) and Border Style ("single"), but they worked after resetting them a several times.  

4.  The TListBox showing the list of Hershey symbol codes had a horizontal scroll bar in Kylix, but not in Delphi.

5.  The OnChange of the TSpinEdit did not survive the transfer from Delphi to Kylix.  I had to re-establish this method.

6.  Before converting to Kylix 1 from Delphi 3, I had to compile the program in Delphi 5 and request "Text DFM".  (I'm not sure why this was not true in converting the SimpleBarChart project.)

Additional problems encountered in K3/D7 upgrade:

1. The SpinEditScale.OnChanged method is firing too soon during initialization -- so don't even define method until near end of form create.

2. A problem was observed in getting rid of a horizontal scollbar with ListBoxHershey due to variability in scaling of components.  To avoid this problem, I created a e form global Hershey TStringList and kept complete info there instead of the ListBoxHershey. This allowed getting rid of owner drawing of ListBoxHershey.

The Hershey font was an interesting historical vector font, which provided an excuse to study the conversion of a Delphi project to Kylix under Linux.

 Also see:  

Hershey Font, vector fonts, rotation, scaling,  VCL, CLX, TStringList, TListBox, TSpinEdit, Application Icon, Delphi 3/4/5/6/7, Kylix 1/2/3, Linux

Delphi 3/4/5/6/7 VCL Source and EXE:   HersheyVCL.zip (2227 KB) zip file includes Hershey.DAT file) [older version]

Delphi 7 CLX Source and executable:  HersheyCLX.zip (301 KB) includes Hershey.DAT file [newer version]

Kylix 3 CLX Source and executable:  Hershey.tar.gz (344 KB) includes Hershey.DAT file 
In Linux to extract files:   gunzip < Hershey.tar.gz | tar xvf -

Updated 14 Jun 2009
Since 10 May 2001