Graphics

This document gives an review of what is possible with PROforma when you do not want to use text. Some of these things also have their consequences when displaying text, but we have chosen to make the font management a separate document as there is so much to say about it.

Note that the PROforma functions (they all return an error code) use a consistent naming scheme (as introduced in syslib). The name always starts with the general module name in capitals ("PF" for PROforma), followed by some words defining the action. Each word starts with a capital, and the most important words (indicating a group of commands) are given first. For example "PFLineWidth" is a PROforma command (PF), concerning lines (Line), and specifically the width (Width).


Gstate

All commands in PROforma actually require a gstate as an access point to PROforma. Therefore we have introduced some commands to create and delete gstates.
PFGstateInit
Create a new gstate. The client has to specify which driver to use and the device which should be used for that driver. The client also determines the size of the page and gets to know how many passes are necessary to render a complete page.

Drivers can be specified either by name, or using a driverid. The driverid's are not fixed and depend on the configuration. Positive driverid's are interpretted as a pointer to a string containing the driver name (case dependent). Some driverid's and names are reserved : "default driver", "screen driver" and "dummy driver" are the reserved name, and the values are PF_DRIVER_DEFAULT, PF_DRIVER_SCREEN and PF_DRIVER_DUMMY.

You can also specify the device to which the driver should send it's output. However is NULL or "" is passed, then the output will be sent to the default device (as can be configured). Note that some drivers are only capable of sending their image to one device.

The size of the maximum area depends on the type of the driver. A printer driver will typically have a 595x842pt area, matching an A4 page (excluding marging forced by the printer). The screen driver has a maximum size of 720x540pt. This is approximately a 12inch screen (10x7.5 inch).

The client can specify the requested page size and position on the device. However, this area is clipped to the actually usable area. If the resulting area is non-existing (so no part of the requested area is usable on that printer), an error is reported.

When a gstate is opened, PROforma assumes you want to use the entire page. So the PageOrigin is set to the position of the top left pixel of the PageBbox on the device.

Most internal structures are initialised properly by PROforma like the flatness, drawing colour (black), paper colour (white),... However some things are not initialised like the linewidth, and the path type (so this should be set or a path will not become visible).

PFGstateRemove
Remove the given gstate from memory. This releases all the fonts which are not used by other gstates (and not resident), and also releases the current path,...

Drivers

PROforma also includes routines to enquire about the available printer drivers. This can be necessary because driverid's are not fixed. They can vary between versions and/or configurations of PROforma. You need a gstate to able to query the available drivers. For this purpose you could initialise a gstate with PF_DRIVER_DUMMY as driver (the dummy driver does not allow you to draw anything and is specificaly intended to query PROforma).

PFDriverCount
Get the number of available drivers. This count does not include the screen driver as this has a fixed driverid (PF_DRIVER_SCREEN). This command also assures that the next call to PFDriverNext for this gstate will return the first driver in the list.
PFDriverNext
Get the driverid and name of the next driver in the list. This list is not sorted !

Transformation matrix

To make it easy for a client to produce moved, slanted, scaled, rotated,... images, PROforma uses a transformation matrix. This matrix converts given coordinates to coordinates in default user space (which are then, internally, converted to device space). There is always a current transformation matrix. The default does nothing (unity matrix).

To allow the client to set the matrix to a certain state, which can be altered and later recovered, it is possible to save and restore the CTM.

PFCTMMove
Move the origin of the current transformation matrix. This means that all objects are actually moved over the given distance. This command could be simulated by adding the given coordinates to all following absolute coordinates. This routine is a macro for PFCTMSet.
PFCTMScale
Scale the current transformation matrix. The origin remains in the same position. All following objects are enlarged by the given factor. This command maintains the ratio, so everything is scaled by an equal amount in all directions. This routine is a macro for PFCTMSet.
PFCTMXScale
Scale the current transformation matrix along the x axis. The origin remains in the same position. All following objects are enlarged along the x axis by the given factor. This command does not maintain the ratio, scaling is only along the x axis (which can be rotated) ! This routine is a macro for PFCTMSet.
PFCTMYScale
Scale the current transformation matrix along the y axis. The origin remains in the same position. All following object are enlarged along the y axis by the given factor. This command does not maintain the ratio, scaling is only along the y axis (which can be rotated) ! This routine is a macro for PFCTMSet.
PFCTMSet
Set the current transformation matrix to the given value, which is relative to the previous value of the CTM. This command should not be performed during the building of a path as that would give problems when closing the current subpath (just as the other commands which change the CTM).
PFCTMReset
Reset the current transformation matrix to the standard values, being all measurements in default user space, the origin at the top left, and axisses extending right and down. This command clears the list of CTM's which are saved. Default user space is normally 1/72 inch (approx .35mm), but this can be changed with PFPageScale.
PFCTMSave
This command allows the client to save the values of the CTM, so that it can later be recovered. Can be called as often as needed (memory permitting, not that it uses much).
PFCTMRestore
Restore the CTM to a previously saved CTM. You should consider the list of CTM's as as last in, first out (LIFO) stack. PFCTMRestore removes the last topmost CTM from the stack and makes it the current.

Drawing parameters

Because PROforma actually attempts to produce pages as fast and efficient as possible, the client has to know how the path has to be visualised before it is actually built.

PFPathMethod
Set the drawing method for the following paths. This is a general routine to set this drawing method, with a parameter. Especially useful when the path is defined in a data structure. Macros are provided to the individual cases (given below). If an invalid drawing method is passed, the drawing method is undefined (nothing is drawn).
PFPathStroked
Notify PROforma that all future path construction commands will apply to stroked paths. Stroked paths have a thickness (as specified by PFLineWidth), and always have round caps and round joins. Stroked lines are always visible. This means that even zero width lines are drawn one pixel wide (also called hairline). This is done to make sure that they don't disappear from the final result. Note however that hairlines can be so thin on certain high resolution devices that they may be invisible. Also some devices (like some laser printers) are very bad at colouring small areas, which can have the same result.
PFPathFilled
Notify PROforma that all future path construction commands will apply to paths which are filled using the winding rule. Please note that areas which are less than a pixel wide or tall can disappear from the final result. To determine whether an area is filled by the winding rule. Initialise the winding counter to zero and draw a line to infinity. For every edge which is crossed you should add one to the winding counter if the edge goes up (left if the edge is horizontal). If the edge goes down (right for horizontal), then you should subtract one from the counter. The area will be filled when the winding counter is not zero. All areas have to be closed for this rule to work. The direction of the path is very important as can be seen in the picture.
PFPathEOFilled
Notify PROforma that all future path construction commands will apply to paths which are filled using the even odd rule. Please note that areas which are less than a pixel wide or tall can disappear from the final result, (see "PROforma Imaging Model"). To determine whether an area is filled by the even odd rule. Initialise the winding counter to zero and draw a line to infinity. For every edge which is crossed you should add one to the winding counter. If the resulting winding counter is odd, then the area will be filled. All areas should be closed for this rule to work.
PFColourGray
Select the current grayshade for drawing. Grayshades are given in percentages. All devices have a few distinct grayshades. Higher resolution devices have more grayshades than low resolution devices.
PFColourRGB
Select the current colour for drawing. RGB colours use an additive colour model, where the red, green and blue components are given. Each component is a percentage, ranging from black (0) to the pure colour (100). Devices always have a native colourspace. If that is not RGB, then the colour which is given is transformed to the devices native colourspace.
PFColourCMYK
Select the current colour for drawing. CMYK colours use a subtractive colour model, where the cyan (kind of blue), magenta (kind of red), yellow and black components are given. Each component is a percentage, ranging from white (0) to the pure colour (100). The black component exists because mixing cyan, magenta and yellow inks, usually turns out more dark brown than black. Therefore the black component should be removed and given separately. Devices always have a native colourspace. If that is not CMYK, then the colour which is given is transformed to the devices native colourspace.
PFLineWidth
Set the linewidth in user coordinates for stroking. All lines and curves which are drawn stroked after this command is given will have the given linewidth until the next PFLineWidth command. The linewidth should not be changed while the path is built as this could cause unexpected results.
PFFlatness
Set the flatness. This is the amount of tolerance the approximation of the bezier curves allows. A high value increases drawing speed, but decreases the accuracy. If this value is too high, curves will degenerate to polygons.

Clipping path

To aid in special constructions, you can define a clipping path. This means that of any following drawings, only the parts which fall inside the clipping path are visible.

PFClipClear
Clear the clipping path and the current path (initial state). The parts of the path which are not inside the PageBbox will never be visualised, irrespective of the clipping path.
PFPathClip
Convert the path which was built into the current clipping path. The path will actually be clipped according to the previous clipping path. The path will also be cleared by this command, and the current point will be reset.

The path will be clipped as drawn with PFPathFilled unless the current drawing mode is PFPathEOFilled. If the linewidth is hairline (less than a pixel thick), then the clipping path will make everything invisible !

Clipping paths are a powerful tool. They allow you to look through an area. It can be viewed as if the drawing which you draw afterwards is the building of a large pattern which is used to fill the area indicated by the path which is clipped.

Thus a clipping path can for example be used if you want to produce graphics with a gradient fill (the colour tone changes). This can be achieved by using the shape of the drawing to define a clipping path, and then draw a series of blocks with slightly differing colours, which are clipped. This will produce the proper result.

Building a path

Of course you also need commands to build a path. This path can then be drawn, or used as a clipping path. It is strongly advised that no other operators are called while building a path, or between building the path and the actual drawing or clipping. If you do call other operators, the behaviour may be quite unexpected (see PROforma sessions, especially the text show operators should not be used).

PFMoveTo
Set the current point to the given absolute position. If you were drawing a filled path which was not closed, this will be done automatically. This commands actually starts a new subpath.
PFMoveR
Move the current point by the given distance. If you were drawing a filled path which was not closed, this will be done automatically. This commands actually starts a new subpath.
PFLineTo
Construct a line from the current point to the given point in absolute coordinates. After this command, the endpoint will be the new current point.
PFLineR
Construct a line from the current point with the given displacements. After this command, the endpoint will be the new current point.
PFCurveTo
Construct a bezier curve from the current point to the given endpoint, using the given control points for direction (all absolute coordinates). After this command, the endpoint will be the new current point.
PFCurveR
Construct a bezier curve from the current point to the given endpoint, using the given control points for direction (all relative coordinates). After this command, the endpoint will be the new current point.
PFClosePath
Make sure the current subpath is closed. A line segment will be added from the end of the subpath (the current point) to the beginning.
PFPathDraw
Make sure the path which was built is actually rendered in the buffer. The path will be empty after this command, and the current point reset.
PFPathClear
Clear the current path (make it empty). This also resets the current point.

Controlling the visible area

PFPageBboxReset
Reset the PageBbox and PageOrigin to the original values when the gstate was initialised (so reset it to the entire visible page).
PFPageBboxRestore
Reset the PageBbox to the entire visible page, and set the PageOrigin to the point which is already at the top left of this area. This allows the client to restore the PageBbox after a PFPageScroll or PFPageBboxSet command.
PFPageBboxSet
Set the new size of the PageBbox and moves the origin (relative to previous value) of visible area. All parameters to this function are in the default user space.
PFPageOriginSet
Move the PageOrigin relative to the previous value. The PageOrigin is the coordinate of the top left point in the PageBbox. All parameters to this function are in the default user space.
PFPageScale
This routine allows the client to set the scaling of a page. It is useful in cases where the page is zoomed in (as in LINEdesign). The default user space is actually changed from the previous value (initially 72 points/inch) to something else (e.g. to 144 pt/inch if factor was 2). Please note that this changes both the size of the PageBbox and the PageOrigin. Both are multiplied by the inverse of the scale.

PFPageScale allows the client to transparently change the imaginary size of the page. Contrary to PFCTMScale which has no effect on any parameters in default user space (such as for PFPageBboxSet).

PFPageBboxGet
Get the current size and origin of the PageBbox, and the current value of the PageOrigin. All returned values are given in default user space.

Controlling the page

PFPageShow
Copy the device buffer to the device. Will display the (part of) the page which has already been rendered. If the page will be built in several passes, this routine automatically adjusts the internal structures to render the next pass. The client is responsible for rebuilding the page. If this was the last pass of a page, the internal structures will be adjusted for the first pass again. This command also allows multiple copies of the page to be produced. However this option will only work if the device supports this (like a laser printer). The actual image in the buffer is not cleared by this command.
PFPageScroll
Allows the client to efficiently scroll in interactive applications. It is only allowed to scroll in one direction at a time (horizontal or vertical). The PageBbox and PageOrigin are automatically adjusted to fit the newly visible space (which probably has to be cleared first).
PFPageClear
Clear the page using the current paper colour (which by default is white). This will only clear the area which falls inside the current PageBbox, as this allows efficient redrawing of the screen for interactive applications.
PFPaperColourGray
Select the paper colour. Grayshades are given in percentages. All devices have a few distinct grayshades. Higher resolution devices have more grayshades than low resolution devices.
PFPaperColourRGB
Select the current paper colour. RGB colours use an additive colour model, where the red, green and blue components are given. Each component is a percentage, ranging from black (0) to the pure colour (100). Devices always have a native colourspace. If that is not RGB, then the colour which is given is transformed to the devices native colourspace.
PFPaperColourCMYK
Select the current paper colour. CMYK colours use a subtractive colour model, where the cyan (kind of blue), magenta (kind of red), yellow and black components are given. Each component is a percentage, ranging from white (0) to the pure colour (100). The black component exists because mixing cyan, magenta and yellow inks, usually turns out more dark brown than black. Therefore the black component should be removed and given separately. Devices always have a native colourspace. If that is not CMYK, then the colour which is given is transformed to the devices native colourspace.
PFDisplayMode
This command can be used to set the display mode which should be used for the page. On screen drivers, this allows you to make sure the users does not have to wait for the drawing to finish before they can see something.

Some devices (usually laser printers) can easily produce following copies after the first one at great speed. This is supported by PROforma.

PFCopies
Set the number of copies which should be produced. Only works if the device supports it (and the driver of course). If not supported this command returns an error.

Displaying pictures

A special library call is provided to display bitmaps. This is particularly important for DTP applications. Bitmaps can be visualised in any orientation. In fact a picture can encapsulate any kind of graphical object. A picture could also consist of vector graphics and/or (possibly preformatted) text. Picture drivers could be written for all these purposes.

Pictures always have to be in memory (an image of the file on disk) before they can be used by a picture driver (IOFileLoad). PROforma can then be used to try to recognize which picture driver can display the picture. However, recognizing a picture is not always possible. Sometimes, you have to know the type of the picture in advance (or let the user choose). The picture can be loaded with code like :

{
    Size size;
    Channel file;
    char *base;

    IOOpenPath(file,OPEN_OLD,path,&file);
    IOLength(file,&size);
    MEMAllocate(size+sizeof(FileInfo),&base);
    IOFileInfoGet(file,(FileInfo *)base);
    IOFileLoad(file,size,base+sizeof(FileInfo));
    IOClose(file);

    PFMoveTo(gstate,xpos,ypos);
    PFPictureDisplay(gstate, driver, base,
                     xsize, xsize);
}

Picture drivers (like printer drivers), can be identified either by name or by driverid. Some driverid's are also reserved for some specific drivers.

PFPictureCount
Get the number of available picture drivers. This also assures that the next call to PFPictureNext will return the first picture driver which is available.
PFPictureNext
Get the name and id of the next printer driver in the list.
PFPictureRecognize
Let the given picture driver test whether it can recognize the given picture. If it can, the picture driver can visualise the picture. However, the picture driver is only allowed to recognize picture of which it is very sure that it can be displayed. Therefore, if the file format does not include a descriptor (e.g. the type is only indicated by a file extension), then the driver probably has to reject pictures which it can display.
So the answers are either All the other commands assume that the picture which is passed can be displayed by the chosen picture driver !

The following code can be used to recognize the driver which can display the picture.

/* figure out the picture driver id */
{
    int recognized=FALSE;
    int id;
    PFPictureCount(gstate,NULL);
    while (!recognized && !PFPictureNext(gstate,&id,NULL))
    {
        if (PFPictureRecognize(gstate,id,picture base)==ERR_OK)
            recognized=TRUE;
    }
    if (recognized)
        the picture id is now stored in id
}
PFPictureRatio
Get the aspect ration of the picture, if known.
PFPictureColourCount
Get the number of colours which are used in the picture. This command allows you to figure out how many colours are used in the picture, so that the user can display the picture with different colours. If changing the colours is not possible, this command will indicate that.
PFPictureColourGrayGet
PFPictureColourRGBGet
PFPictureColourCMYKGet
Get the colours which are by default used in the picture. The colours are given in the requested colour space. The colours are filled in an array of the size as returned by PFPictureColourCount.
PFPictureDisplay
PFPictureDisplayGray
PFPictureDisplayRGB
PFPictureDisplayCMYK
Display the picture at the given size at the position indicated by the current point. When using PFPictureDisplay, the default colours for the picture will be used. The other comands need you to pass the colours which should be used (in the colourspace which is indicated by the command).

Windowing aids

PFWindowMove
This command adjust the internal structures to a gstate to match the position of the screen window (it does nothing for non-screen gstates). This should be called if the owning job uses the Pointer Environment and the window has been moved.
PFWindowSubSet
The concept of a WindowSub can be used recursively. A WindowSub is somewhat similar to the PageBbox, however, the pagebbox is used to redraw part of something, while the WindowSub is used to draw the contents of a part of the gstate, as a WindowSub on the screen. Any following PageBbox has to fall inside the current WindowSub. Any successive WindowSubs are also confined to fall inside the current WindowSub. Any call of PFPageScale is local inside the current and subsequent WindowSubs. The new WindowSub is always relative with the currect PageBbox, and the coordinates are in user space, which should not be rotated. When the WindowSub is set, the PageOrigin is at (0,0).
PFWindowSubRestore
The concept of a WindowSub can be used recursively, so restoring the WindowSub will only restore the the changes since the matching call of PFWindowSubSet (this acts as a LIFO stack, like PFCTMSet and PFCTMRestore). The CTM and PageScale, PageBbox and PageOrigin are restored to their original values.

PROGS, Professional & Graphical Software
last edited June 21, 1996