Version 2.0.0

A CGM Viewer Applet

by Berthold Daum
July 2007 updated December 2010

Sick of fuzzy images?

In 2001 business graphics, maps, charts, CAD drawings usually did't look very good on the web. Again and again we came across pages where we had to squint our eyes and ask ourself  "What's that". 
The reason was that these drawings were converted to GIF or JPeG files. Drawing packages are vector based and GIF or JPeG are not, so a lot of sharpness, contrast, and detail is lost during conversion. GIF and JPeG are great for photos and other half tone images, but for vector drawings they are not.

SVG was just in the making at that time and we needed a quick solution. That's why we came up with the CGM viewer applet (cgmVA). Most drawing packages could export CGM (Computer Graphic Metafile, an ISO standard). The applet was written in Java, is quite small (54k), and should run on virtually any platform.

By now, cgmVA has been downloaded many times. In todays environments, I would recommend using Flash, SVG, or HTML5 for vector based graphics. Nevertheless, this applet is used freqently when it comes to displaying engineering data and the like on the web.

The following images demonstrate the difference between bitmapped images and the vector graphics of cgmVA.

 GIF - 10k  JPeG medium quality - 9k  CGM zipped -11k
africa.gif africa.jpg Sorry, you need a browser that runs Java to see the drawing. Did you disable Java in Internet Explorer or Netscape Navigator?

Try to zoom in

Try to zoom in

Click on image to zoom in


While in this example the CGM file is the largest of the three, CGM files don't get larger if you decide to display the image in a larger size. You can display a drawing full screen, without download  time penalties.

Our cgmVA is interactive - users can zoom into an image and can pan within the applet window. For practical reasons we have limited the zoom factor to 16:1. The mouse controls are explained in the browsers status line (move the mouse over the picture). cgmVA can also be controlled via a toolbar using Javascript. For an example please see here.

But that is only where the fun starts: cgmVA supports animation. Instead of inventing an own animation script language, we decided to offer an animation interface instead and to use JavaScript to control the animation. This allows very sophisticated animations mixed with other multimedia elements. For an example, see below.


Platforms

cgmVA runs on any Java-enabled browser that supports at least JavaSE 1.3.
Animation requires a JavaScript/ECMAScript enabled browser. Popular browsers such as Microsoft Internet Explorer or Firefox should have no trouble executing this applet. However, you may want to make sure that both Java and JavaScript are enabled.

top


Context

In addition to cgmVA, we provide cgmPanel, a Java class library for CGM support in Java applications. This class library implements a CGM enabled subclass of JPanel under Java Swing and Java 1.3 or later. It supports all features of the cgmVA (plus some more) including animation. A demo version can be downloaded from here.

Also available is cgmSwt, an Eclipse plug-in for CGM support. This includes a SWT based library supporting all the features of the cgmVA (plus some more) including animation, and an CGM viewer plug-in for the Eclipse platform. cgmSwt requires Java 1.4, GDI+ on Windows (Windows XP) or the Cairo Vector Engine under Linux GTK. A demo version can be downloaded from here.

top


CGM functionality


cgmVA can read binary and ASCII CGM files, but not ISO character encoded CGM files.

cgmVA supports  most of the functionality of CGM Version 1 with a few exceptions:

  • the advanced text attribute Character Expansion is only supported for Hershey fonts.
  • only hollow, empty, solid, horizontal and vertical hatching is supported for Interior Style.
  • Generalized Drawing Primitives are not supported (GDPs are mostly used for proprietary extensions). 

In addition cgmVA supports Bezier polylines from the CGM 3 standard.


top


 Embedding into HTML



The following HTML code displays the image my.cgm in a 400x400 area (Please refer to the Java Applet documentation for an explanation of the parameters).
We start by specifying the applet itself. We assume that the applet classes are all stored in a subdirectory called cgmva/.

<applet code="CgmViewApplet.class" codebase="cgmva/" width=400 height=400>

Alternatively you can use the JAR file cgmVA.jar (54k) supplied in the download package.

<applet code="CgmViewApplet.class" archive="cgmva.jar" codebase="cgmva/" width=400 height=400>

Next we define which CGM files to display:

<param name="filename" value="cgmva/cgms/my.cgm">

You can specify several filenames separated by commas. All these images will be displayed in Layer 0 (see below) on top of each other. For instance:

<param name="filename" value="cgmva/cgms/back.cgm,cgmva/cgms/front.cgm">

In this case it might be better to specify the directory for the images separately:

<param name="imagebase" value="cgmva/cgms/">
<param name="filename" value="back.cgm,front.cgm
">

For security reasons, Java applets can only access files within the codebase directory. Images must be stored in the same directory as the applet classes or in a subdirectory.
Note: The file extension ".cgm" can be omitted - it is the default file extension for CGM images.

Finally, we close the applet definition:

</applet>

top

 Using zipped archives

It is possible to pack all image files into a zipped archive specifying an optional parameter cgmArchive. cgmVA will load the whole archive and will satisfy requests for individual image files from this archive. This can result in dramatic download speed improvements, especially with animations, where many images are used.

Note: These ZIP-archives should not contain path names.

Example:

<param name="imagebase" value="cgmva/cgms/">
<param name="filename" value="scene">
<param name="cgmArchive" value="hello.zip">

The file scene.cgm will be retrieved from archive hello.zip.
(If not found there it will be retrieved from its own URL.)
Note: The file extension ".zip" can be omitted - it is the default file extension for ZIP archives.

top

 Pixel based images

GIF-, JPeG-, and PNG-images can be displayed along with CGM images. Pixel based images are especially useful if you want to use a photograph as background of a drawing:

<param name="filename" value="cgmva/cgms/back.gif,cgmva/cgms/front.cgm">

Note:  Pixel based images files cannot be placed into a zipped archive, but are loaded individually.

top

 Fonts

Normally, cgmVA uses native fonts for text output. In addition, it is possible to use Hershey fonts. To use your own Hershey fonts, simply put them into the zipped cgmArchive. Alternatively, you may specify a font list to load font files individually:

<param name="fontbase" value="cgmva/fonts/">
<param name="hersheyFonts" value="Times,GillSans,GillSans_BoldItalic">

Note: The default extension for font file names is ".jhf".

cgmVA uses these Hershey fonts if their file names match with the specified font names in the drawing. A typical font name consists of the font family name, like "GillSans", and an optional style modifier: "_BoldItalic", "_Bold", "_Italic". To find out which fonts are used in a drawing, open the drawing with a text editor and look for font names.

Hershey fonts are available from http://www.nyx.net/~jbuzbee/font.html

Note: Some advanced text features like character expansion are not available with native fonts but only with Hershey fonts. cgmVA switches automatically to the the Hershey font Futura light when rotated text is to be displayed in a native font. You must place the file futural.jhf into the fontbase and declare it like shown above, when your drawing contains character expansion text.

top

 Rendering Quality

By default, cgmVA uses the antialiasing settings of the host platform. However, you may influence antialiasing behavior by setting the parameter:

<param name="antialiasing" value="on">

or:

<param name="antialiasing" value="off">

By default, cgmVA uses a compromise between quality and speed when rendering images. However, you may influence rendering behavior by setting the parameter:

<param name="rendering" value="speed">

or:

<param name="rendering" value="quality">

top

 Background colour

The background colour can be specified with the parameter

<param name="bgcolor" value="#rrggbb">

The colour encoding is the same as in HTML (#ffffff for white, #000000 for black). 

If this parameter is not specified, the background colour is determined by the first image.  If this image does not set the background colour, cgmVA defaults to the applet background colour, which is grey. 

Note, that many CGM images establish an own canvas in form of a filled rectangle. This will, of course, override the background setting.

top

 Inverse colours

The parameter 

<param name="inverse" value="true">

makes all CGM-colours to appear inverse.

top

 Event handler

cgmVA can notify a JavaScript function about Animation and Navigation events. You can specify the name of a JavaScript function as an element handler with

<param name="eventHandler" value="myHandler">

The default value is "appletEvent". To enable event notification you must also specify a mayscript attribute in the applet clause:

<applet code="CgmViewApplet.class" ... mayscript ...>

top

 Fail safe

What do we do with browsers that don't support Java or where the user has disabled Java? In this case we use a pixel based version of the drawing to fall back on. Here is the way how it is done. Just include the GIF image between the <applet> and </applet> tags:

<applet code="CgmViewApplet.class" codebase="cgmva/" width=400 height=400>
<IMG SRC="Images/my.GIF" width=400 height=400>
</applet>

top

 Running under the Java2 plug-in

cgmVA can run under the Java2 plug-in. We do not recommend this because:

  • loading is much slower
  • The HTML code to embed an applet is platform specific. The old APPLET tag is replaced by OBJECT for IE4/5 and by EMBED for Navigator. Pages that must run on all platforms get rather complex. If you still want to do it, use JavaSoft's java-plug-in-html-converter to convert APPLET tags into EMBEDs or OBJECTs.
  • NO scripting! Back to basics!

top


Mouse controls


Zooming and scrolling is achieved by using the mouse. Mouse controls can be used , too, when an animation is running.

By default, mouse controls are enabled and a help text is displayed in the browsers status bar as the user moves the mouse across the applet area. This help text moves with the mouse movement to be fully readable in short status lines. You can avoid this movement by specifying a text that starts with a period.


The default text is:
  Click/drag=Zoom in, AltClick=Zoom out, AltDrag=Pan, AltDoubleClick=Reset, ShiftClick=Centre

You can supply your own message with the parameter controls:

<param name="controls" value="What else would you want?">

If you supply an empty or blank string, the mouse controls are switched off:

<param name="controls" value="">


The zoom factor can be set with:

<param name="zoomFactor" value="1.2">

The default value is 1.414...

top


Keyboard controls

The parameter 

<param name="keyActions" value="true">

enables the keyboard for scrolling and panning. This is to allow mouseless operation. The following keys are available:

+ zoom in
- zoom out
Enter fire hyperlink
Spacebar reset
! find next hotspot
Cursor keys panning

top


Imagemaps

Using image maps  with cgmVA is easy. Just specify another parameter

<param name="imagemap" value="mapname,url1,url2,...">

mapname specifies another CGM  file relative to imagebase. This CGM file contains a drawing that determines the hot areas in the drawing. Please note, that cgmVA can only recognize filled elements as hot areas. If you move the mouse over such an area, cgmVA displays the attached URL in the status line. When you click, the URL well be fetched.

If no URL is attached to an image map element, cgmVA displays the number of the element in the status line. This helps to find out element numbers during development and to attach the correct URLs.

Optional, an hyperlink can be highlighted when the mouse enters the hotspot region. Specify

     <param name="showHotspots" value="true">

When 

     <param name="keyActions" value="true">

is specified, the "!" key can be used to jump from hotspot to hotspot.

Image maps override other mouse controls.

An example is seen in the toolbar demonstration.

top


Events

You can define a define a separate JavaScript event handler for each instance of cgmVA (see above). You must then supply a JavaScript function that will be invoked for each key and mouse action:

function appletEvent(task,device,event) {
....
}

This function is supplied with the following parameter values:

task

"Navigation" A navigation event (scroll, zoom)
"Animation" An animation event (mouse animation event or key event)

device

"Mouse" A mouse event
"Key" A key event

event

"Down" mouse button or key was pressed
"Up" key was released
"Click" mouse click (button released)
"DoubleClick" mouse double click
"Drag" during dragging (button still pressed)
"Drop" Dragging finished (button released)
"Move" mouse was moved

An example is seen in the toolbar demonstration.

top

 


Animation

Version

The applet version string can be obtained with

string=getVersion()



 Layers, Images, Panes, Components

cgmVA organises each scene in up to 16 layers (0-15). Each layer can contain an unlimited number of images. Each image is represented by a separate file and can contain many components organised in several panes.
Layers with higher numbers are in front of layers with lower numbers. The same is true for panes and components.
It is possible to control each image separately or to apply controls to whole layers. A limited set of controls is available for panes and components within images. By default new layers are visible and are located at (0,0) with a magnification of (1,1). When the applet is invoked, the main image (specified in the "filename" parameter) is located in layer 0 at position 0,0 with normal size.

top

Coordinates


The base image also defines the coordinates for all subsequent operations. (0,0) is the left upper corner of the image, and the longest edge of the image has the length of 1000.
Each image is automatically scaled to fit into a 1000x1000 square when loaded. Translating operations are used to position and size the image as required. The aspect of images is maintained but can be changed by translating operations.
The working area for all animation operations is the square (0,0) (1000,1000). cgmVA finally maps this area to the actual applet area.

top

Visibility

There are four visibility levels that can be applied to single layers or single pictures:

  • show
    The image is displayed with all lines and fills, find operations succeed on fills.
    show(l,name)  Loads the image "name" into layer "l" if not already loaded. The image is set to "visible".
    You can also specify a comma separated name list.
    show(l)  Sets layer "l" to "visible", all visible or wire images in this layer are displayed.
    show(l,name,c) sets component "c" in image "name" in layer "l" to visible.
  • wire
    The image is displayed without fills, but find operations succeed on  the invisible fills.
    wire(l,name)  Loads the image "name" into layer "l" if not already loaded. The image is set to "wire".
    You can also specify a comma separated name list.
    wire(l)  Sets layer "l" to "wire", all visible or wire images in this layer are displayed in transparent fashion.
    wire(l,name,c) sets component "c" in image "name" in layer "l" to "wire".
  • map
    The image is not displayed, but find operations succeed on  the invisible fills. Used for image maps.
    map(l,name)  Loads the image "name" into layer "l" if not already loaded. The image is set to "map".
    You can also specify a comma separated name list.
    map(l)  Sets layer "l" to "map", all images in this layer are not displayed.
    map(l,name,c) sets component "c" in image "name" in layer "l" to "map".
  • hidden
    The image is not displayed. Find operations fail.
    hide(l,name)  Loads the image "name" into layer "l" if not already loaded. The image is set to "invisible".
    You can also specify a comma separated name list.
    hide(l)  Sets layer "l" to "invisible", all images in this layer are hidden.
    hide(l,name,c) sets component "c" in image "name" in layer "l" to "invisible". 
    For example, hide(l,name,0) can be used to hide the first component of a picture which usually contains the background rectangle.
clip(x,y,w,h)
Sets a clipping rectangle at position (x,y) with with w and height h for the whole scene.
Can be used for blending effects.
clip(l,x,y,w,h)
Sets a clipping rectangle at position (x,y) with with w and height h for layer "l". Can be used for blending effects.

replaceText(l,name,n,text)
Replaces the text in the n-th text element of image "name" in layer "l" with the specified text string. This operation changes all clones, too. Counting begins at 1.
Good for reusing images where only the text changes.

top

Streaming


preload(names)  
Specify a comma separated name list. The list can contain zipped archives (.zip), sounds (.au), Hershey fonts (.jhf), and cgm files (.cgm). The default file extension is .cgm. The files are preloaded for later use. The use of this command is optional, but can result in a smoother animation.
unload(names)  
Specify a comma separated name list. The list can contain sounds (.au), Hershey fonts (.jhf), and cgm files (.cgm). The default file extension is .cgm. The files are removed from memory. This command is only necessary for very long animations with changing scenes when memory space becomes an issue.
(Sound files can be removed while playing - the sound file will be removed after it has stopped playing).
top

 Translating Operations

Layer translations are relative to the base image area. Image translations are relative to the containing layer.

translate(x,y)
translate the whole scene by a (x,y) distance. This has only effect on scenes magnified by a scale operation (see below). With translate x- and y- values are move-to values, not move-by values.

translate(l,x,y)
translate layer "l" by a (x,y) distance. Layer numbering starts at 0.
translate(l,name,x,y)
translate picture "name" in layer "l" by a (x,y) distance relative to layer origin.

scale(mx,my)
scale the whole scene horizontally by factor mx and vertically by factor my. Factors between 1 and 16 are allowed.
scale(l,mx,my)
scale layer "l" horizontally by factor mx and vertically by factor my. Factors between 0 and 16 are allowed.
scale(l,name,mx,my)
scale picture "name" in layer "l" horizontally by factor mx and vertically by factor my. Factors between 0 and 16 are allowed.
scroll(x,y)
scrolls the visible window by a (x,y) distance. This is equivalent to a user scroll operation.
 
setViewX(x)
sets the visible window to scene x-position.
setView(y)
sets the visible window to scene position y on the Y-axis.
getViewX()
returns the x-position of the visible window within the scene
getViewY()
returns the y-position of the visible window within the scene
getViewWith()
returns the width of the visible window in scene units
getViewHeight()
returns the height of the visible window in scene units
 
zoom(m,x,y)
set zoom magnification to"m" towards point (x,y).
This has the same effect as  a user zoom action. It overrides user zoom actions and can be overridden by user zoom actions.
zoom(m)
set zoom magnification to"m" towards centre of window.
getZoom()
returns the current zoom magnification.
setZoomFactor(f)
sets the factor by which the zoom magnification is increased or decreased. The default value is sqrt(2).
getZoomFactor()
returns the zoom factor.

getLayerX(l)  
getX(l)                                           (deprecated)
get horizontal position of layer "l".
getCgmX(l,name)
getX(l,name)                                 (deprecated)
get horizontal position of picture "name" in layer "l" relative to layer origin.

getComponentX(l,name,comp)
getX(l,name,comp)                       (deprecated)
get horizontal position of component "comp" in picture "name" in layer "l" relative to layer origin.

getLayerY(l) 
getY(l)                                           (deprecated)
get vertical position of layer "l".
getCgmY(l,name)
getY(l,name)                                 (deprecated)
get vertical position of picture "name" in layer "l" relative to layer origin.
getComponentY(l,name,comp)
getY(l,name,comp)                       (deprecated)
get vertical position of component "comp" in picture "name" in layer "l" relative to layer origin.
movePane(l,name,p,x,y)
Moves pane "p" within image "name" in layer "l" by the distance specified in "x" and "y". Pane numbering starts at 0.
moveComponent(l,name,c,x,y)
Moves component "c" within image "name" in layer "l" by the distance specified in "x" and "y". Component numbering starts at 0.

top

Cloning


To use several instances of the same image append an identification to the name. Identification must start with a "#" character. For instance, 

show(3,dolly.cgm#2)  

shows a clone of dolly.cgm in layer 3.

Clones can be translated, scaled, shown and hidden individually. However, text replacements affect all clones simultaneously.

top

Rendering

render()
This will redraw the whole scene.
Show, hide, and display do not have any effect on the display until render() is called.
top

User interaction

setEventMask(modifiers)

By default, all mouse events with the CTRL-key pressed are regarded as animation mouse events.
You can change this with setEventMask. (Values 1-4 can be combined).
0 pass all mouse events to the animation interface.
(disables user zoom).
1 Shift-modifier
2 CTRL-modifier (default)
4 Meta-modifier or right mouse button

modifier=getModifier()
returns the modifier value from the last mouse event.
1 Shift
2 CTRL
4 Meta or right mouse button
8 Alt or middle mouse button
 
setModifier(modifier,mode)
sets the modifier keys for the next key or mouse click or drag event.

modifier
0 No modifier
1 Shift
2 CTRL
4 Meta or right mouse button
5 Meta+Shift. This enables drag but inhibits zoom. The cursor changes to a hand.
8 Alt or middle mouse button
16 Double Click


mode

0 Switch off
1 Only for next mouseclick/keypress
2 until switched off
 
event = mouseControl(statusline)
Shows the specified text in the status line and returns a mouse event.
If no new mouse event happened during the last invocation of mouseControl an empty string is returned.
Otherwise:
"Down" mouse button was pressed
"Click" mouse click (button released)
"DoubleClick" mouse double click
"Drag" during dragging (button still pressed)
"Drop" Dragging finished (button released)
"Move" mouse was moved

event = mouseControl()
Same as above, but status line remains untouched.
event = mouseControl("")
Same as above, but status line is reset to the state before animation was started.

getSceneX()
getX()              (dropped)
returns horizontal mouse position for recent mouse control.
getSceneY()
getY()             (dropped)
returns vertical mouse position for recent mouse control.
getKey()
returns the key code of the most recent key pressed. If the key is still down, the value is negative, if the key is up again, the value is positive.
Note: Key events are not buffered.
getKeyModifier()
returns the modifier for the last key (key down) event. Modifier values same as above.
top

Finding objects

cgmVA can find the foremost object in a scene for a specified (x,y) position and returns filename and clone id of the found picture. The algorithm used is:

  • fail on hidden objects or layers (returns empty string)
  • CGM-pictures: succeed on foremost object that has a fill at the search position.
    The visibility status of the objects must be "show", "wire" or "map".
  • GIF-images: succeed on all non transparent pixels of the image.

name=findPicture(x,y)
find the picture at position x,y.
Removed: Use findPicture(-1,x,y).
name=findPicture(l,x,y)
find the picture at position x,y in layer "l". Specify -1 for the layer number to search in all layers

name=findPicture(event)
same as above, but the position is derived from the event:
"Down", "Click", "DoubleClick" Position when mouse button was pressed
"Drag" or "Drop" Current mouse position during drag operation
"Move" Mouse position during mouse move
 
name=findPicture(l,event)
as above, but restricts the search to layer "l".
name=findText(t)
find the specified string t in a text element. A matching text element is marked. Supply the empty string to find the next occurence.
name=findText(l,t)
as above, but restricts the search to layer "l".
number=getComponent()
returns the index of the image component that caused a previous "find" operation to succeed.
 
number=getLayer()
returns the layer index that caused a previous "find" operation to succeed.
top


Sound

Although you can use the browsers sound facilities from JavaScript, we provide methods to play sound (.au) files.
The advantage is that this is truly platform independent and does not require plug-ins.

playSound(filename)
plays the specified sound file. The default file extension is .au.
stopSound(filename)
stops the playing of the specified sound file.
loopSound(filename)
play the specified sound file continuously.

Optionally you can use the sound-parameter in the applet definition to preload sound files:

<param name="soundbase" value="cgmva/sounds/">
<param name="sound" value="sound1,sound2">

top

Invocation from JavaScript


Note:
Invoking Java-Methods from JavaScript requires Netscape Navigator with LiveConnect/LiveWire or MicroSoft InterExplorer 4.0.


First embed the applet into HTML. For instance:

<applet code="CgmViewApplet.class" codebase="cgmva/" name="cgm1" width=400 height=400>
<param name="imagebase" value="cgmva/cgms/">
<param name="filename" value="my">
</applet>

Note the name definition cgm1. This name is used in the following JavaScript function to address the specific applet instance.

Now we define a JavaScript function in the HTML header section:

<html><head><title>Test</title>

<script language="JavaScript">
function test(i) {
// we run 100 times
  if (i < 100) {
// each time make layer 0 a bit smaller
     document.cgm1.scale(0,(100-i)/100,(100-i)/100);
// done, now display the result
     document.cgm1.render();
// increment counter
     i++;
// now post a timer event with "test(i+1)" to be executed
    timerOne=window.setTimeout("test(" + i + ")",100);
  }
}
</script>
</head>

top


Finally, you have to invoke the JavaScript function somewhere. Here we define a button and an event handler:

<form name="Input">
<input type=button value="Test" onclick="test(0)">
</form>


Note:
JavaScript functions can be contained in separate files, so they can be used from different HTML pages.


top


Hello, world

"Hello, world" illustrates several animation techniques:

  • use mouse controls to zoom into the scene
    (left click to zoom in, drag to scroll, right click to zoom out)
  • start the animation by pressing the Start-button below


When the animation is running you can 

  • move the mouse over the dinosaur. This will make the dinosaur issue a statement.

press the Ctrl-key and

  • drag the dinosaur across the scene.
  • click on the planet. Surprise!


Sorry, you need a browser that runs Java to see the drawing. Have you disabled Java in Internet Explorer or Netscape Navigator?



Applet Definition:

<APPLET code="CgmViewApplet.class" archive="cgmVA.jar" codebase="cgmva/" name="hi" 
width=450 height=450
MAYSCRIPT>
<PARAM name="imagebase" value="cgmva/cgms/">
<PARAM name="cgmArchive" value="hello.zip">
<PARAM name="filename" value="Scene">
<PARAM name="soundbase" value="cgmva/sounds/">
<PARAM name="sound" value="hi">
</APPLET>
<FORM name="Input">
<INPUT type=button value="Start" onclick="hello(0)">
<INPUT type=button value="Stop" onclick="cancel_hello()"> <BR>
<BR>
</FORM>

The JavaScript definitions for hello(0) and cancel_hello() are in the HTML header. 
Please see the HTML code of this page.
          

top


History and Status
Acknowledgements


This project was initiated by Alexander Larsson (alla@lysator.liu.se), who wrote V0.1.
Text objects, mouse control, animation, ZIP- and GIF-support were added by Berthold Daum (berthold.daum@online.de).
The Hershey-class was contributed  by James P. Buzbee (jbuzbee@nyx.net).
Dirk Bosmans (dirk.bosmans@tijd.com) simplified the fail safe clause.


Some algorithms were imported from
 Core Web Programming from Prentice Hall Publishers, 1997 Marty Hall, hall@apl.jhu.edu.


top


Download

If you wish to be notified about new versions of cgmVA please leave your name and email address here:

Download latest version of cgmVA

top


Other CGM products

To include CGM functionality into your own Java applications we provide the packages CgmCanvas and CgmSWT. See here.

top