Blob Blame History Raw
/* Copyright (C) 2002 Bjoern Beutel. */

/* Description. =============================================================*/

/* Common routines for all Malaga GTK windows. */

/* Includes. ================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <errno.h>
#include <limits.h>
#include <pango/pango.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <cairo.h>
#include "basic.h"
#include "scanner.h"
#include "input.h"
#include "files.h"
#include "canvas.h"

/* Constants. ===============================================================*/

enum {BRACKET_RATIO = 6}; /* Height:width ratio of angle brackets. */
enum {BUFFER_SIZE = 200}; /* Size of Postscript conversion buffer. */

#define CM (72.0 / 2.54) /* How many Postscript points make one centimeter. */
#define PAPER_WIDTH (20.9 * CM) /* Default width of paper. */
#define PAPER_HEIGHT (29.65 * CM) /* Default height of paper. */
#define PAPER_BORDER (1.5 * CM)
#define PAGE_WIDTH (PAPER_WIDTH - 2 * PAPER_BORDER)
#define PAGE_HEIGHT (PAPER_HEIGHT - 2 * PAPER_BORDER)

/* Hangul code points in Unicode. */
enum {FIRST_JAMO = 0x3131, 
      LAST_JAMO = 0x3163, 
      JAMO_COUNT = (LAST_JAMO - FIRST_JAMO + 1),
      FIRST_SYLLABLE = 0xac00,
      LAST_SYLLABLE = 0xd7a3,
      SYLLABLE_COUNT = (LAST_SYLLABLE - FIRST_SYLLABLE + 1)};

/* Code points in the N3F Hangul Postscript font. */
enum {FIRST_CHOSEONG = 162, CHOSEONG_COUNT = 19, NO_CHOSEONG = 161,
      FIRST_JUNGSEONG = 182, JUNGSEONG_COUNT = 21, NO_JUNGSEONG = 181, 
      FIRST_JONSEONG = 203, JONSEONG_COUNT = 28};

/*---------------------------------------------------------------------------*/

/* This table maps Unicode Hangul Jamos to the N3F encoding. It starts at 
 * code point FIRST_JAMO. */
static char_t jamos[JAMO_COUNT] =
{ 
  162, 163, 205, 164, 207, 208, 165, 166, 167, 211, 212, 213, 214, 215, 216,
  217, 168, 169, 170, 220, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180,
  182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196,
  197, 198, 199, 200, 201, 202
};

/*---------------------------------------------------------------------------*/

/* Widths of Adobe Helvetica characters in Latin1 encoding. */
static int widths_latin1[256] = 
{
  278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 
  278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 
  278, 278, 278, 278, 355, 556, 556, 889, 667, 222, 333, 333, 389, 584, 278, 
  333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 
  584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500,
  667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667,
  611, 278, 278, 278, 469, 556, 222, 556, 556, 500, 556, 556, 278, 556, 556,
  222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722,
  500, 500, 500, 334, 260, 334, 584, 278, 278, 278, 278, 278, 278, 278, 278,
  278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 333, 333, 333, 333, 333,
  333, 333, 333, 278, 333, 333, 278, 333, 333, 333, 278, 333, 556, 556, 556,
  556, 260, 556, 333, 737, 370, 556, 584, 333, 737, 333, 400, 584, 333, 333,
  333, 556, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667,
  667, 667, 667,1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722,
  778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556,
  556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278,
  556, 556, 556, 556, 556, 556, 556, 584, 611, 556, 556, 556, 556, 500, 556, 
  500
};

/* Widths of N3F-5 chars in N3F Encoding. Table starts at code point
 * FIRST_N3F. */
enum {FIRST_N3F = 161, 
      LAST_N3F = 229,
      N3F_COUNT = LAST_N3F - FIRST_N3F + 1};
static int widths_n3f[N3F_COUNT] =
{
  436, 436, 602, 436, 436, 602, 436, 436, 436, 625, 436, 663, 436, 436, 687,
  436, 436, 436, 436, 436, 80 , 370, 446, 370, 446, 286, 459, 286, 459, 80 , 
  370, 446, 268, 80 , 80 , 286, 459, 268, 80 , 80 , 268, 268, 0  , 0  , 0  ,
  0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  ,
  0  , 0  , 0  , 0  , 0  , 0  , 0  , 0  , 0
};

/*---------------------------------------------------------------------------*/

static const char n3f_font_definition[] = 
"%%BeginResource: font n3f-5\n"
"%\n"
"% Copyright 1996, 1998 Lee Yongjae <yjlee@cglab.snu.ac.kr>\n"
"%\n"
"% Permission to use, copy, modify, and distribute this software and its\n"
"% documentation for any purpose and without fee is hereby granted,\n"
"% provided that the above copyright notice appears in all copies and\n"
"% that both, the copyright notice and this permission notice, appear in\n"
"% supporting documentation, and that the name of the copyright holder\n"
"% is not used in advertising or publicity pertaining to distribution\n"
"% of the software without specific, written prior permission.\n"
"%\n"
"12 dict begin /FontInfo 9 dict dup begin /FullName (n3f-5) readonly def\n"
"/isFixedPitch false def /Notice (Copyright 1996 Lee Yongjae) def\n"
"/ItalicAngle 0 def /UnderlinePosition -100 def /UnderlineThickness 50 def\n"
"end readonly def /FontName /n3f-5 def /Encoding 256 array 0 1 255 {1 index\n"
"exch /.notdef put} for dup 161 /k_f1 put dup 162 /k_K put dup 163 /k_Kk put\n"
"dup 164 /k_N put dup 165 /k_T put dup 166 /k_Tt put dup 167 /k_R put dup\n"
"168 /k_M put dup 169 /k_P put dup 170 /k_Pp put dup 171 /k_S put dup 172\n"
"/k_Ss put dup 173 /k_O put dup 174 /k_C put dup 175 /k_Cc put dup 176 /k_Ch\n"
"put dup 177 /k_Kh put dup 178 /k_Th put dup 179 /k_Ph put dup 180 /k_H put\n"
"dup 181 /k_f2 put dup 182 /k_a put dup 183 /k_ae put dup 184 /k_ya put dup\n"
"185 /k_yae put dup 186 /k_eo put dup 187 /k_e put dup 188 /k_yeo put dup\n"
"189 /k_ye put dup 190 /k_o put dup 191 /k_wa put dup 192 /k_wae put dup 193\n"
"/k_oe put dup 194 /k_yo put dup 195 /k_u put dup 196 /k_weo put dup 197\n"
"/k_we put dup 198 /k_wi put dup 199 /k_yu put dup 200 /k_eu put dup 201\n"
"/k_yi put dup 202 /k_i put dup 203 /k_k put dup 204 /k_kk put dup 205 /k_ks\n"
"put dup 206 /k_n put dup 207 /k_nc put dup 208 /k_nh put dup 209 /k_t put\n"
"dup 210 /k_l put dup 211 /k_lk put dup 212 /k_lm put dup 213 /k_lp put dup\n"
"214 /k_ls put dup 215 /k_lth put dup 216 /k_lph put dup 217 /k_lh put dup\n"
"218 /k_m put dup 219 /k_p put dup 220 /k_ps put dup 221 /k_s put dup 222\n"
"/k_ss put dup 223 /k_ng put dup 224 /k_c put dup 225 /k_ch put dup 226\n"
"/k_kh put dup 227 /k_th put dup 228 /k_ph put dup 229 /k_h put readonly def\n"
"/PaintType 0 def /FontType 1 def /FontMatrix [0.001 0 0 0.001 0 0] readonly\n"
"def /FontBBox { -529 -252 703 701 } readonly def /UniqueID 4030051 def\n"
"currentdict end currentfile eexec\n"
"D9D66F633B846A989B9974B0179FC6CC445BCF7C3C3333173232E3FDBFF43949\n"
"1DB866C3EF1B30F529F56D40DA3462EACD8F8BDB057A36A23611B08B42B62E04\n"
"F78389045DBF331069C7CB96640FF89489B5599FD6BE8EF25688798EC4F2C13F\n"
"431245DFF93AA4EF41E6D82885E1C6AB7F67F4BF4B809E88F7CBB013AA0CCEF4\n"
"187B2C4653946A3B5C610D2FF1E0E7D3AB43709002F44517BE09E1C3F35D2F59\n"
"70F3A4615689AA659C58AB0C3741705267C93A5D6CE9C8D3BC8F086A7492CF58\n"
"003DE8CA89A1609FA0E9E6FFF90E99BA58F8C5D720E87110453D4959E5333F1F\n"
"2B383165401D07AC9CB68B69A6C2E343001EA94177A6EFE9F6CB1DB741B84D6B\n"
"BD1E84F5175262D86AB8AB478840A805BA8BD467219CD28A5C4B3820CD173783\n"
"258C58123D0077CD375CFF73E250093D72C85E8EDE7FEE4B09F1EED41C10705D\n"
"FEA2ACC563D8471B4B266DE8E655F92D3620EEF32EB23D83FB199B98894CD857\n"
"7E035EA9C44A817375FCB4A9BCE3370EF65EE1F74A86B4EFAC30F091D4D34A3C\n"
"EEE0BF8897E6C79DF68ACE676F3F2145304CF7E6339D1EE856ADE3DFB7F9D696\n"
"2E7F000C17B4E1404C86D34ADA528F30CB522C18A00B709D05B77EF2BDD58B88\n"
"2B96C6623112C67849619CA63CB8AC370617E70FFC7FBA86E5DFFBC4B2EE6062\n"
"E701743060851BD8DB9DB790793CBF2DCEE3E5A755CFF7FCB5EB90E05899DEF7\n"
"EA3BFD6B1B6163488E883065B81AE93C4214F0F0CD360B84B337483286EC37DD\n"
"52E8DBCD28B403DDBA54E355721B6EBA71EF5B565D43BC79FD6A051673CFDBED\n"
"BEF9342B3C9157EEE4954566F87AF445C8A6CC6E5537429B2527C5F3FC38108E\n"
"BDE872FEE3F26A17EFDBB9D73B43B65CB13E24100DFA006BEB1832B9C45C642F\n"
"239155ED4ED43F0782AF636F17EB20BE8C8967E283DC6A00B15EAF8E8D239C29\n"
"944F60CB5DE69B4DF9F56EE75D274C897DB3772D7CD0E1275E30C73F0C023560\n"
"917768FE34D77039619F153CD8AC0826DF1C685DCFA7A6688008E0411FCB6EC1\n"
"5CFA71301D5162A04BC7B715282F4CC8EA0A5E7D36665DF8136D37227F139DC7\n"
"0CBFBCA56669AE03A0F69B23E7D313BB70F754419AFD2E9E8B6E2ED827D71773\n"
"24612554F335CBE16973BC44C1AADE6C2716B284777A2486BFEA7ABEE1D2E4BB\n"
"8E4FF2F56DCF2767A73599268B055E856549A52A6A775C61C4A02C6AAEF3C444\n"
"C709146DE1678608F5D46A50C126DC5E4161A67916D828E9BD8D3BEA2A834527\n"
"CEF127D99ADADE3895DFC60447332E0E34D9F34C544FCABCE014DB9E0469E1B6\n"
"9E32205941840B9BF973B5A70CFD1D2A14DAA1159517D180565ABDAB2CFB296B\n"
"AFB72AB045423ED9094F0E300B7C06E1D5C32E7315A354033E02EAAFB0BBB4E6\n"
"E4990C5C0C9D40406608B43835C76463EC08FBBF5A3742EB2AB231DC2B3DC3C0\n"
"BD7C9793C9A12FA4200E6F59DD7ABAD4C05684D1136FD2EB7ECA405596E23784\n"
"C73A815F3EFADDDE43C92BA6B1776BA381423C731F10D7F85D5853A8C9124183\n"
"EFC63F8BEC944AE48AD4917F51ED03F87755CAB6FFF549BE13F92BE50B4806B3\n"
"16A5DAD7A6F4D845E853C1CC115AB7E455293A1C911AACD1457282AF25336FDB\n"
"B94A55F79F11B7C95CA81494697BF72C8866CAD79633F0154B6D3DBADB7EE005\n"
"2A0EF550284490DD1B034946E245FC2CC1EFD485D1F376AEBDCFF96A838C3ECE\n"
"4318658747C18B728784C78289C86891300D53F4EAD5A0CF90DF91632AB92FE4\n"
"1BCD86336AF3DD0936B0CE6E4ADF70BFB767DC29F05E7F874A6366D8E63DA571\n"
"089C6C9C49625CE9FFFBB596CE078D175A6213800E59218D7B8DBFC0640CC191\n"
"DC5F095B7BD881889D00BA5B8010785626C470C7AAD7D60C054C4FF2EFE9DAEF\n"
"997FBAAD4A5C6567838A49B6634BE4AAB05A0806CAC18777E87E0DC6B078F866\n"
"E40537462E96004AC6097ED2E47A9725EF80C77DE8AE7898282200D23FCC3337\n"
"A4CDB738445AF479009B14ADEB95B2558DEF31712B8915CBD959AFC8F8390A5A\n"
"B178F926A57F6C63E84658AB33FCD3D41F3209877ADF46AD8C907C14882397C8\n"
"2B2D5A7936D0B92C149ED8A751916471BA052D1302EC62EA274F04AA667821A4\n"
"3533DCE8DD20901983D574283A68683D07E37FE59965710F8CAADCB7681DE258\n"
"E59CAB0B1793F67F686A311296B2BE7A7D10C868F8BD39A1CEF857B8A4E027FC\n"
"97BC55F1C9B2444C04C175350218C60C1944500391DDC69E7FE4455A753A66DC\n"
"E85044D657B2D71AFFBA2DB53C70BD06357B687672C78B629AECF6E7C406ED52\n"
"5539D9E2C1E7E7EB5B27A206164F06D850A1BC5E92C2CE65C0D3037EC911E440\n"
"1C738E8824D2B910C020E1650D5853A7B5D492EC1AE1E1D5C5DB26D5777F11E4\n"
"344AB9F87997D46C86C6145590C912FA35AFA2F3F96E3AE2D6A78CE53E4F1F9A\n"
"F39EDAC046ACCC5A0AB801FC11C98BDC61D689263040E9633EC3779A4D537448\n"
"F805224DC4A9CD3D1353E0D662865A0148B1C3DAC0987B5735DABF8BFF7A7084\n"
"F47978869D6CBE9505E58ED8D56784FDA1722B76E87D4DEF3C3BA4483F8AF555\n"
"ECD0017C1CBDA13C74FA98B6F1F85185404D5A37B3EA1380BB1F3DFCB977B294\n"
"3CC6EBF8C4018D782D96671D2AA125E3DF8D9AE4BAFC09B85DBDA44FA7CD6493\n"
"9589D3A258954FE2473F133AB1BDCBDFD79546E823395CDD248410C5D5CADF40\n"
"47A5758106F0A92B0AA87F5C548D791A2594A1AAC4AFE84A91DA057724857615\n"
"66633131C9A41B7C8315F04B97FBFC80B2D621F88DF6566613FCC27A08222546\n"
"6EB0859B060B93EBECE5F69976BAD8BBDB1DCF36DE6A653942A3FE1B58FE88C1\n"
"865EA81AEB3945B35AF29B6CAB2943F40936B43110376409CCE7A2BFA1A618CA\n"
"E03BA0390E6DA539C6FB4CFE82CD5ED290505C33BDA643254305E3C27288F2AE\n"
"0D22BC2C35452E5827BC119154D0953543663C4AFE36C00EF6402370E3D8C0D1\n"
"077BBA2BAB79EE3C7C8F70853EDE9E85DF996674F7A5883F3C763BAE63BB22BD\n"
"180AE2E1A7E787CE80784EBFD0240E595F9BC3D9750448E870413E0ECAB69004\n"
"03DF2D305F6EB4361942E75B0804F1609B5DE3F2C2D4A6C9543EC20F823A87A5\n"
"F695983F3FBC101C5000277D81F1EC4ADC2F105E06BC5EB528C399D138572C57\n"
"E8DC7A6F41DC6F8D44534388C30BFA963C52BC2F2F786C69BBBD405C7904ADE6\n"
"251C08F462A33A1D7D65D954299921053A87C2D8AAC2BDF9D713AD90608C6420\n"
"48AD52351B15C2211F17867244284964870D00A19B0B2378F28E232938D0DBC4\n"
"48B5FAC2207C17B6BAF3CE9EB82A315354A7EC183226EAC400067E06246BA400\n"
"B64212899C7049F4C13A8C079C2A6EA22DDFE73F4EA683B053ED02837BFA669E\n"
"BE87C0FBE2FD749D2BBB01CF22A8CBB3F4D543649B45E397F1F3ADAA57F98A3A\n"
"0F5ACF4F92541FF903DB28F9A29EB0AAE46430083913B3CD6FAA042D2EEC48A7\n"
"79CF038443F660845621E68CA56BAD5DF203A559CF770E515B726149F1821E38\n"
"017F04CF73C9B146C1BAF29BC0EA6FCB06FF083201E09C67F16441C37609FF27\n"
"5933986E314AC7FEB4CB45523CEC5F46F100D39BA02949F3F05AB76BC5E2F4FD\n"
"B91B6EFCD17E2123B260B3B954BE200FE9A6A1AEF77D8BA16CC44E3475D15289\n"
"8A6FA3D19D4CE4F3DC0325E40237647A9E2C053CBE4BEFF24F6B2E00FDA93593\n"
"176F94639A40546722168D68B5608F8CE798FC5F84D7684ABE805BAD9D8B9AF5\n"
"989C2233956A4DA479921ECBD922F882D0A94F9CF67457FA73673B65912D8410\n"
"7AC025DF0D2DDC21453C5D251505F5CD0E268C7421AD668A33C66A524B37DCAA\n"
"2F7C5557099D723FED2C7964E11D857760CEAE192FC30D685D599196324B29D9\n"
"E3A44C3448091957AD6429165A7F406E39C138680C0805D54A3CF15028EA725D\n"
"E88FA056E89108C0C1D23CA3C6A71B21B0EBA517D6FE77079B7088C0FC900101\n"
"CB2191D9ECA7A8B82B6F6DAE2FDB01EE67809232BC5C0BB2C26E828B4A6A3A30\n"
"2510326780A7A6FD6486ADD2BF76C24821791D5E1F36BAD6749A8DB20F925CFE\n"
"8D7BCF9F9EFBB8E09A48DDFF0D622E76F4F1C5A400918673FB13C241941CB237\n"
"FB2231A5C87D5B243DDB58AF892CA939AF35994BC6C9CF52972832FEC6D3B1D6\n"
"BEBB1D1D06D98F44F435108FE3E43596E5A84141609EA0C4DF97A1BF4B117EFF\n"
"B7952B3A022893E8356F8E2583B258E6738DEC7AB8EB78C35CE04495D1196156\n"
"A9829343464576E626889251A472BA52A3C8ADA9646B99EE75DC5207C54938D2\n"
"39E04F381C44D067F935426F4690FE1B6674FC367EBD5E93D151AD7880324F9B\n"
"AAD544F08F3E5911A1A47B0C2BE61C0A988D8E3E7DC05FC9C3311AE4F7E57FA7\n"
"316AFC4A7383A4C55555ABD374995B938AB0B69089420117605137B612737288\n"
"81007C3444CA68D4EAE0E0A1A240B708697456B440BCD7B598BFA428D5CD28E8\n"
"F96BA3A9D4A0C2728E89AFE02D14D1874639E163C09C71398C891CBEF3B8BA6D\n"
"CDE95EB756B8ACB762875E559267EE8EC451A3CF7E95A1D4A9EE33C87AF036FC\n"
"BFC7D0B825D86C9C277BB42D415C6D44C4F45A14320C779C1A4B0E13347B0001\n"
"1DA60DEE09061F725479C403EA4973C6D450EB15EA6D033985AAC2592B012AF6\n"
"8D2B854FFDF9BB03EC14805E251300760AD41804A8FA2821CA77794D150484EE\n"
"EA58CBB43951DC00B9493B6DC15E200BF394E19A446DBABFF71BD75CAE3D6476\n"
"E0CD659214F462A66BD348C81F97A31C3E4B8078223F00D36EC25E48D65CE127\n"
"DF09839B9FFCDE6055FC4CDB136126CFE4891C0BF7DAF9D582378845361EDDF4\n"
"93BDD1954DE5DBA70B98C462744FDE3DDDD6719BB00FF4A01136C36D20D00951\n"
"A7EB060BAEB0965A18CAD664E6DCFAA98F496704DAA6FE0787D934FFACB0E861\n"
"9753ED6BF44106206855C9E313BDC268208769E8FDD864A2A75DFFA213C0A49E\n"
"C4386F1E55F5FBD43E0AD5B3EF46531D7124D22F5080220D7DC6FF9D2E03DAF1\n"
"797544ACB18E060A04A5ABFAC6E20B8B374D606D555B6560EBCD7044B78D38DC\n"
"AAE19EEBA625224F9E016D49B6A5710CC3A0871BD3CC00265CD8E67109F7EB61\n"
"550A41FE60D2474F961264D218E302CC35205A1D655F878C8FB831DCAA33E134\n"
"70E2D2C49EAB90176513B2D1A3DB731D7207F070AAAF90B7990BA77FB559221A\n"
"855D62E824F18DF1A63DA56813CC79C4B990E87E41C4E8A9A6244E108D94D48D\n"
"A8079D448A916F68A39B5ACB375AF61C52725B1361A0FBC5818A0052F4F0B1CA\n"
"FB11446E47D6177318E3F83511BFDCE237FE94BF1223C29EF05BC57E1668A7EE\n"
"C4D9B5CAEBF4F14ABCF26719D0863FDB73DB4897E02C995DEEE70CEA14C35542\n"
"6D508B3CDD8F8A10A78F6219991D2133D77F6D6D4D346E2207F29478D8F071ED\n"
"73B331A0D41DE6B15355F85ED54AA37C502D63B9090D53F9B025877225E6FB88\n"
"6A1809D536F1F096DE2FADC386926BC07421BD0C49A01C4D77D4627BCD349528\n"
"97FC9A0D7FF60C5B705E2B1A669C938733E30BC55F03476A6B8B5CD03C3150B8\n"
"1236D235FE1E7EAFFF2FB5DFF9980328ECE0B627B4C178B6A95A4CF197611748\n"
"959878A68FBDD8857205B75524512B896357FDA8D464244646018F6078B687E3\n"
"7372A3E9792BDD3435302CB509E728BCD7B7377F2E0D5C02040033D9113AF22B\n"
"BE956919AD2CA332C1FE4B5E4D6741523A884E222FA8EFDC1C24FBBDEEF6465D\n"
"65D8A0E2C334DD806062ECB16A5BF6D24221141A2ACA31EF8AA7BA7A10ADED5C\n"
"9CC618E69EA66BE2A7A625970751BD86412514DA8441A1DBF6F0D49112A5FF88\n"
"95872BC5B995CEF7D3A27936B17E134AA66CDC3583DBA0678DFE12DFAC7514B9\n"
"52D46F914F73AB0208DB3CDDE326FFF7293CA9E3C305F8275B78E7870A50BB50\n"
"874E93D2D26A15F0FFE0D74345E536AD39ECDA53FFDA5A625001C26C97FC6FAF\n"
"F0864403FCB78C2A7EC1727DAD5BD64762086DCF5A17D4E9035123FA43C1630C\n"
"69BB2E10FAF95973AE38A13BF57A5BE8E579A43E618D4DBCAC9075D89E6CF5FF\n"
"F0DEBB17E452D336CC87DB03A3C872BC63A11F5061F7E112723E1C89ACAC5679\n"
"E7D98AA82A939FCD42CFE1BF81E87FA66FB3DEE9006B13E339FD9E034C485FA5\n"
"04812788EA8CB1A932C94645D26528CE15408B526E8EF98B1CE8363C5FC47054\n"
"8065293A984D7BB9A21F99D4BBD1B78A09FDFB18A6E8B2944D6C8D52A20E3E5F\n"
"71FDFBEB31B570D0F9D7C265338A6C5E9E0CBA742A01208D560B8FCAF15BE825\n"
"ED294D9613FCC511B62612EE6A4F35EC370A57DCC608963A73197C159FD9EB5B\n"
"F0F0AB9A86ACE51BB0C1F89029393712E1F5EFF2335512803C27524ABA13AB32\n"
"F1593B11A4996B133DD81051479A49A2459D223858A48ABBAB17E4E0B0EEE26B\n"
"0EE3EF75772268FE26F6441CE2917D18EC251DCAF7D2554B2F955FFE606957AE\n"
"2F86B7AE7CE8279638A0A3EDC3FABA38F24C4904B75262210FAC02D7A71DA8C8\n"
"C07946A02F8C24DD5A54F751A65E0ECA012F4C80C13C0AE59120AF0790B004F1\n"
"4D479EFBD046EE049B12EF9FEC70DD8196DE145A7313058A292CC7E360288C28\n"
"A596430C9B9F6A06E1C37DCAB3C9154F1F228778D77AA82126AC31F860667182\n"
"A74FF150224860B76429E3BE635A4A93CA6DDEA12E7E34501992F31DA9C55D43\n"
"214DE92977FB118F2523123FCF45C6451FAA16447108902A96C05914FC82A62A\n"
"349B3477F67C1CBCA1B3D135A649CCBC9D7BD3931EC402CAFF93974836A8E5A5\n"
"00F251261273FDE7649955FE45EA108D4B3C3895EB1E1EBC4B39DFBCAD92A245\n"
"75B09F873B851907139E6D430F4F928111414559B2FDB535DB222F8EED030F48\n"
"27EEFE50F57597190CC790B3B1B55F21433D8DC831F30B2B0F70FBDA4160F94B\n"
"36ED20D77E577E9250875DC6FC88BF280639842556AF84FF7538DCFF36D5536B\n"
"9FC9B0EFC405FF23679BC1E39FBFBCB428D5F209562BE553750DC8CD6B9F6FB6\n"
"BA9655CAEDCDE73757DEEE940C4F4FF04CA455AE8C3D94432FF9874872B77C20\n"
"71F46663493F8ACFED7B8F1C33C5C19B139A0460CBB676F523458270BA4393B9\n"
"EFFD55BD5074FB63F90827F5C9C46C9DDFA4B84AD0F48807094FC847F51C64D5\n"
"E1224E16AD882A0ED6C647E0FA2A0BBBD19F461FDA5A255BAF014599DAA755E1\n"
"18C52E7851D6651DDDD638EF488113772D52C41E135D4A4B587359C88F1BFBCB\n"
"C7925A384EFCCB35A8EA112AC298D30263A3E9A857949EE4E04BD278BEE68307\n"
"D1B351F54A233AE3FCACF2947FF6E949E6C391C24DC2CEEE5267221C30AD63C7\n"
"2E09DAAB39B2A33BD4FF51441CDAB009D0F30EED102B36C77AD3EF4C30233C61\n"
"D1ABDB13B7C9EF211DC7003B3A848DBAA7573778CC2518D8DD872106182914D0\n"
"A8E4156D99481972BDA413F9FF42BDF74BE29A49EEAD55804BD5BBB594A0D250\n"
"4B5898EE8FA5A029D850B0C6582E9EEB89B17902A94FBCBD637503C0BF334681\n"
"2BDA2CED91F072946FDE2AA824B824E628D4A229C63C8ADB8F235B77AFE501FB\n"
"91DD3B8D226DC0E69F9F68F12492F7659CB8BCC352DA9DBBC417914F6C8A291A\n"
"3EC92956A937FA07D337240B7946C311731F681281B772E330CCF5DC918D0F51\n"
"EFF203DB777EBC6267D218469675DDAD79774D0E7BCBB3C2196D8D264BC6E5CD\n"
"6E3FE1DFE75A6CDF53FB3D889A723DE8017EAD77C1E67853F9A45B091B2C2A5A\n"
"3FFB3C318024E34632220F1B2A470A2A00A1C28D5B61B8ABD95B060021D149A7\n"
"A765D00C76D38BE872BDC55EE8C43A2F98FB291ACCBA870FCAB713ED4F6D76A4\n"
"B9CB086E3B8CF4E0F1F51D43A16F56EE69373CA6D6D6AAD8B0D4DB1CF83505F5\n"
"E54133E9939074D9DE2CE82AC1F8469AFF9E6C1E43C32DC2A12367CE38141616\n"
"4726CE8AD13EF9F07DF45108F6DDD16677DAA5250C9351DCAD937E060D0E12A3\n"
"FD0858C4C2961C1E9E0DBD611B661400E40011BCF0E9EA20EA056685502399C8\n"
"4E0113757D35D7F01B753FFAF38560E6BCDAF5AF9483266645A00BC17711AC1E\n"
"D4B504AAB85349A31BD73EA6516F069A9CBF808D16EEAE065B6BED42DC799904\n"
"A9DC3B361557973754ED0FA022D82FFDBC454865A12A68216338F1BACBF8A9E7\n"
"492DB4518DEF56FDC0AE0BA3A2A20EEF538DA7804F1F223683638E1920F28756\n"
"D4926C7B9133D332502C7EE7F71EB00715704E0162ED8E1A0422FCD864F71600\n"
"CA2D1D740D45FF4E0D1235B143D1F690A019E8A57C46357207C4F7DD81747392\n"
"ADC44AEE8CB7586D7C4B4CB63753C5EB9271E27D34E6AED844212E8C203F2282\n"
"E79E8F25C16037BF85AD0DBA4E594DBC82C2FDB4B6973C4D470EE402A19B527E\n"
"BC61FC9D96C68B504CEC3406A159E2C8B6F2E79BCA75F51BBF4B61C8F5A21C56\n"
"DEADECDFAD4347200532497D3FE6FBA8E9BEC3D503A97CA50BA52CA242F6CDB1\n"
"90B6A5257BA4D9D18ABABFDABB4A65E1D5D3F6A6764D44D7CB6BF4AC888EB728\n"
"6E396FE80939AC26C8AD2A3DF3F3EEFA38544FA28F36E577B01E944E499014C5\n"
"A0F8B593151832FE49DF1EB8412012585DB9D057FBD60967893EE00D413C9FFD\n"
"3076E00DE4ECF271D390655C6C661141D894DA2362D9117D21C492F43728CA7E\n"
"8FAD31601D16CB92A7C53A4D388753E391106BD52C8FC44F364E75FB0B38895A\n"
"C67698F992012379A68AD2CD12679D5A88973B0EA80C8CD163918D8B5E9C18A0\n"
"ACB813099EC4CF63FBA34E5A1F9AF41C597FFC7843DB506CED639A1B56E77D26\n"
"3F076D4391E96A1E38D7E7F9279E7593B2E9E15FE3DB0D0F3629304D914FB012\n"
"EFD7628E50A1E9A62FDE38E67482CB5338880ACA34765DA71E7B3176F235365C\n"
"EC6F60B7201065B459AFD536A9D1605B835793B34646484E7E500A63DF569987\n"
"FB00E458300019B681E1B38DEB754C2A611E872ADEE0C300AC04341CD0410F6F\n"
"165E1004EEBA0A52DCDADB4C86ABB50529A38A509BE89A4E2A35AA7899F964F3\n"
"61297AA16F717BC2BDD79C23851618DD8FE9C15FA740A0F9D42FB8E620AF461A\n"
"8F9744F0D92888ED96B86AE357930D0D9D2DC8917D74B4F2EED482AABEF9EC6D\n"
"C1CCE9796830ED60FD27435F1280894A94C0EDD2551C1CED18B9BC713CB17537\n"
"DD797DCD049D65F9D2826AE6E19BFD3105E95E4E1B747FAE3877EC12F998A267\n"
"69E7E783FA612E70F43F551F66AB8B1F6B93B8F2A62A7EC67FA00F56EF464020\n"
"43E6286D0A40F4401DF7A6958679205B9DB58DE63FCFBD23C171F010419DB21B\n"
"42C2CE7D3F34ABD4A196A75C46363DB71CDA9CC04BF4046DE27F5CD9CF8E74B9\n"
"F440DE40658E62000A9A20F5ADD6213DA6B0915F5306BC4C9824B8E0BAC64ADE\n"
"88C1515DCCCD81CCB83E8E2E59655177EBE4DAC666C71BC21A8599BB2F533FE8\n"
"44DB822B632AA8B85D7B938C89717B13F9EC0C4975B2404389CF9B4D40DB1A73\n"
"701F3CF8FDB977AD919493746DF8952F9590E6FA4DAA82C37254BF300B65A9BC\n"
"7FB93762E42C0856C4231BB1F04A03F29FB8E70B4E909372F8F1496BA47CA82F\n"
"FBF3808CF096B1D5D7ADE9AED8872E6E8B3791311AFBB5DE7D0D8C3B0D8E9243\n"
"3BE02526125EF34F0A575E0E789B2038DF2F4ED5182EBD5CF5A9AE771F8972F7\n"
"10B6AF7BA5F0810D4E0F01465FB4A4C25F916B60CA5BA7DDE92CB24D0DE0F713\n"
"A963530BB2EA13F54C1C0FBF8D9BCB94C781FFEAEE6AE48BC951317BAB88B140\n"
"91343ACE736604B473CFC14A1845D648737C47B42EE4286CD7156C987704CEEF\n"
"FD2BFBDC4B63CE2B633C09ACC51FBEC2F8B64E815730465F5F61C1E15A1A8632\n"
"D6E55EE400D6A875D4CAF9839E53E3C28C99EBAB7E0B523D44B1270FD41FB736\n"
"1DDB0EF87AEBF2F2CE2B6DBCFDAB74A65D80E82D08F2C5F0F98F84796666FAA1\n"
"3C9399831AFA98B6D2FC37FE47C2C24E30020D85C7CB03F4EB85A6904AABB074\n"
"6E916B55F27832DD190335B88E31BE657E560A847E1E6C21CD85B2EDAF8726F9\n"
"5A1856B8D26969145B9F60E61254C57EDA7827896FFBFFC4C35F338F22175278\n"
"09C5D48862BBD55F1E4ECA3A2C6092D55D4E4AAB3542CAD1B77B2338314C7102\n"
"89BCE74F362C477ADECA09368945A337636DB7A5AED30DD64E3F3D4D1668492B\n"
"C40F67940FC90E5E64DFFBC97B23E130D387FF6FA785515AB8116FBAE382196F\n"
"B546F8B447217FD21133C1D0DAC1F45893F2A5ADFE29E73A444EC718603D0077\n"
"E69FC22A22849EBA5727C6A05FECDE20A1DE131A6D4DA9A5408147B23C320366\n"
"8D4C64678E2B71F757FF0E177219D3750F6A74A025ED156BFE446BBA3194ECD4\n"
"C71444B4E63FA0DAA81908C395C77C6484CC26E3E52D3E1D8E341C8366338254\n"
"FAEA45B6E32312C878F870B059B897F7D55F89FBE6427F40DF26310FED372B87\n"
"52E15FFA84287EB0740874FD97D090FA566FDED66372FA95D1C75DA5F2A02D42\n"
"2CED62419A1CA43FC1AA0B806866E07366BDAF329651C088EAB26D1F19A95D19\n"
"2BB46558622D6777BBC2DF431E9457A0F617B77A87F7414759FFFD66E00A16DB\n"
"9B24E997416A1BEF302768E81E252903B975275A468EC0A247BA670D8B209534\n"
"2B734D767266E2241384B5893288A0D50E12C851A47990A2BAF2445BFB7C8852\n"
"F2BA046AC935C119C3C6542AD6B5C339E11FCB55EB12BF753BB43F99E4B48317\n"
"4C3F2A0530B5AF0D89910CFBCA2290920EA1ACB34951D175D0AEF4FEAA62CE93\n"
"51F5C0ED55F0292D7F28E033D0ED773C5C5AD3E47D7DFE86C76B0C7A895F9DB6\n"
"880FE08C2008DD3C2C88FA04ABAD5D3D445ABDB5860FB48C0B973C251B138EC2\n"
"7FA2CE7F55BF4196D06AEE270858E9521D9E085C58F7561388650BEACCBC2687\n"
"C69BBDA32CFDB923EF5052D333E9BBC3549AD32CDD53F04C7D8CA21751B47ABD\n"
"C1C60A0DC155CD068EECEE0E7CA67CD539D85F2D8489C6C8F1CD0CEE611709D9\n"
"A7FACD511BF663667398082BF096D13665A1502EEDB5F0EAF00264F8A803B2B0\n"
"E47D4B4FEFF6761C382D73D2B430CA80207D8321A0A2EAD8B67EE5804022BE6C\n"
"5235002A07AFE2D1A77D467B45FD7BBFA5BD3CB3309FC140E2D55178A091A33F\n"
"5A9767BE25067AA7E12BC25AA6949F9DA0F81FB5311DF81363E117B3A383342F\n"
"3E0F4462676BE061AE4716D923B2C7BAA284A247842F6BF6E7D3901D3D3BF7A7\n"
"B4654D145D95B900EDB009C33540D3F5A4A1AE94E6BCAA3C85AC14CC197B467A\n"
"9E43E0773EBC4FD5FF73433A26C02CEEA99F515F5C15C019FA816DFB021FDCB1\n"
"047A79234BAF4952A0CC48EAED0A62EFCB4EFCCB72D4C9DF8A8803650F8543FD\n"
"714D869C0F1AC1CF5FAC20503A072372D922B9C6F14FA5D379306059C9FAF228\n"
"6AE1AAB6C87AD689AC039C11A6E74C8B7B726F671FF0BA4850FC9E06340D39A3\n"
"66A9D572B0A6A029F95B82787D2F9F40114C4B90B763DD6169678C53AFE0FB0E\n"
"105C77449EAF6F95C5C7AB06F03F3790B93617786D8AA01D92DC2F97D42CC6F1\n"
"BC68E73510AE2F22A5953493207D26117B4933BAFB8311F4298A467DEBABBC29\n"
"871532FA9C166056FB144016D341B0A864FB882C4114E7157E24FE046D5B65E7\n"
"5879DF87597BEB6224EFD2910C206589CC828C1690F40A75A99CEDB51B46B67A\n"
"C337BE8AA78C66E9652EE5C6281A0AD6432017570EBD8E9524A388360399B051\n"
"7E37544EFA2DB5A87CC52245494C8FA19844506585D3F3868934D5775F4F97C2\n"
"492FCFBE08E468BDF0E7765197104FBC00D963D0E9E0D778BAEF2C15CDCC4D6E\n"
"12E197FED3C2022A091D65D3F9F5302E7E9A280CC8A09388DD366D5083F659AF\n"
"73FB2834544914212D78ED6384EA96841ED737061D7AB0FA5B4381A38CD3EC13\n"
"AAA4E19C408992F2FC189B03839B38DC15A08305609C49C0A8C2E958DF933470\n"
"D535E5266F746FF5B5B4CD8FD3467B5AA1A1F4601E1532CA93FF14072DF2E7ED\n"
"A154A191E27D68B61D026FF5DF0AA39C398744DBF04FA587B226337D77EF11F2\n"
"2457472DC1C99E84D2E42C71A2E313EFF7E8AC506CC2BD5B585D0D58E9431DD4\n"
"F9758152794FB6D27F494AEBD3EF4250F24FF01924C3F18874F5AA64A532C8FF\n"
"77AF4A8641DA35622B07D4F964862F0B043C54555723887C5E67E192C28B2D9A\n"
"ACDE51F21B4D09D74AEA2A2B40769C4BA5F23B46E1415229CDD439007F6DED01\n"
"B4334AA5A23FF4A40D5308EA52EE0DEA1F5CEA92F9AE8A4437447FEE9464AAE6\n"
"3FDD49629AF6ABBDE4F38FC2FB9C9D152C0F64F64678D5100DC9E5C3A49FDB6B\n"
"5A56DB67D5126812B1D9269E6E9AC2A723AE9B296645459EF000FA9252EC9228\n"
"9DB18FF27595D6E952D75AC5A5DC3A48D7F40265280E263DF90E35B8F6E081B6\n"
"00633552A5BFCD6A526A46A89F8A1D7FF4F7FFABBBE1F6F4B80651B21F78B391\n"
"84B46A49F7404E8EEDC3F0B710EB231CC2CE0A8C81E27C01B1210F0E05FEB4E8\n"
"69D480F80DE0F4A1F4FD719902E82F2EDD0CEBDC764800F54D0594363E2BB797\n"
"82C1DC01960B76A9CF4B84966DD2D1E75EE1393B0ABD4E48462A195BF28B7C48\n"
"A2DFCD4163B981E785D06104F46B20F949B033C51AD5FAEE7CB3894C29E30797\n"
"FE5E6FF8C455A869DDE04686C30F8CAFF428AB5BFBAF276D90DE79A4B86455D8\n"
"8D94BF8D436BA46330A18BF55B393C926F1947AB996FC46D22CBD931427A4E81\n"
"1009C4EE608BC868A843DD888690530E1F8FADA75FC7CC0962C1DBFDEE630DCE\n"
"0BD7DC763F802548E813AE4394FC6B5CE560BF122A7D5593092C99010684070E\n"
"354201416C5E6B9E4A7F804FC89F3E50A70B6A2644329C7BCE70D2E6ABEAC786\n"
"4F2AA7361B71D289AE0EA5E11D6B8964B6AB1B03A455E775BC051BB7B6846CC1\n"
"6C292406018A00249C21CD1357EDDBFF\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"0000000000000000000000000000000000000000000000000000000000000000\n"
"cleartomark\n"
"%%EndResource\n";

/* Types. ===================================================================*/

typedef enum {GSTRING_TYPE, GRECORD_TYPE, GLIST_TYPE} gvalue_type_t;
/* Type of a node in "gvalue_t". */

typedef struct value /* A Malaga value in "malshow". */
{ 
  struct value *next; /* Next element if value is record or list element. */
  struct value *first; /* First element if value is record or list. */
  gvalue_type_t value_type; /* Type of this value. */
  PangoLayout *string; /* String (for GSTRING_TYPE). */
  PangoLayout *attribute; /* Attribute name (if record element). */
  int_t attrib_width; /* Width of attribute. */
  int_t ascent; /* Distance between top and baseline. */
  int_t width, height; /* Size of value. */
} gvalue_t;

typedef struct /* Implementation type of "pos_value_t". */
{ 
  int_t x, y, width, height, ascent;
  /* Private items follow. */
  gvalue_t *value;
} pos_value_i_t;

typedef struct /* Implementation type of "pos_string_t". */
{ 
  int_t x, y, width, height, ascent;
  /* Private items follow. */
  PangoLayout *layout;
} pos_string_i_t;

struct canvas 
{ 
  GtkWidget *window; /* Toplevel window for this canvas. */
  GtkWidget *draw_area; /* Drawing area widget. */
  GtkWidget *file_selection;
  GtkWidget *hscrollbar, *vscrollbar; /* Needed for adjust_canvas(). */
  GtkAdjustment *hadjust, *vadjust;
  GtkItemFactory *factory; /* Item factory for the pulldown menu. */
  GtkItemFactory *popup_menu_factory;
  GdkEventButton *event; /* Button press event currently handled. */
  bool_t show_hscrollbar, show_vscrollbar;
  int_t vscrollbar_width, hscrollbar_height;
  string_t ps_file_name; /* Default file name for FILE_SELECTION. */
  int_t width, height; /* Current size of canvas. */
  int_t area_width, area_height; /* Size of DRAW_AREA. */
  int_t x, y; /* Upper left corner of canvas in drawing area. */
  expose_func_t expose;
  configure_func_t configure;
  close_func_t close;
  mouse_func_t mouse_event;
  PangoFontDescription *font;
  int_t font_size; /* Selected font size. */
  int_t font_ascent; /* Ascent of FONT. */
  int_t font_height; /* Height of a line in FONT. */
  int_t space_width; /* Width of a space in FONT. */
  int_t comma_width; /* Width of a comma in FONT. */
  int_t border_width; /* Width of record border. */
  int_t line_width; /* Width of canvas lines. */
  int_t border_height; /* Height of list and record borders. */
  bool_t hanging_style; /* TRUE if values are "hanging down" from baseline. */
  bool_t alternate_cursor; /* TRUE if alternate cursor is displayed. */
  PangoLayout *comma_layout, *space_layout;
};

/* Global variables. ========================================================*/

string_t font_family;
int_t font_size;
bool_t hanging_style;

/* Variables. ===============================================================*/

/* The following variables can be shared among all canvases since there is 
 * always only one canvas being drawn. */
static char_t ps_string[ BUFFER_SIZE ]; /* Converted PostScript string. */
static GdkDrawable *drawable; /* Drawable we currently draw into. */
static cairo_t *cairo; /* Cairo context for DRAWABLE. */
static rectangle_t area; /* The clipping rectangle of DRAWABLE, using the
			  * coordinates of the whole canvas. */
static double canvas_shift; /* Shift if stroke width is odd. */
static GdkRectangle clip; /* The clipping rectangle of DRAWABLE, using the
			   * coordinates of DRAWABLE. */
static GdkGC *gc; /* Graphics context used to draw. */
static GdkColor colors[4]; /* BLACK, WHITE, RED and BLUE. */
static FILE *ps_stream; /* Stream used for PostScript output. */
static bool_t ps_mode; /* TRUE => draw into PS_STREAM; 
			* FALSE => draw into DRAWABLE. */
static bool_t use_n3f; /* TRUE iff we'll output N3F Hangul characters to
			* PS_STREAM. */

/* The following variables can be shared among all canvases since they are 
 * only set up at initialisation. */
static GdkCursor *alternate_cursor;
static PangoContext *pango_context;

/* Forward declarations. ====================================================*/

static gvalue_t *parse_value( void );

/* Parsing Malaga values. ===================================================*/

static void 
free_value( gvalue_t **value_p )
/* Free *VALUE_P. */
{
  gvalue_t *value, *next_value;

  for (value = *value_p; value != NULL; value = next_value) 
  { 
    next_value = value->next;
    free_value( &value->first );
    if (value->attribute != NULL)
      g_object_unref( value->attribute );
    if (value->string != NULL)
      g_object_unref( value->string );
    free_mem( &value );
  }
  *value_p = NULL;
}

/*---------------------------------------------------------------------------*/

static string_t
parse_symbol( void )
/* Parse a symbol and return it. */
{
  string_t symbol;
   
  test_token( TOK_IDENT );
  symbol = new_string( token_name, NULL );
  read_next_token();
  return symbol;
}

/*---------------------------------------------------------------------------*/

static void value_set_string( gvalue_t *value, char_t *string ) 
{
  if (pango_context == NULL) 
    pango_context = gdk_pango_context_get(); 

  value->value_type = GSTRING_TYPE;
  value->string = pango_layout_new( pango_context );
  pango_layout_set_text( value->string, string, -1 );
  free( string );
}

/*---------------------------------------------------------------------------*/

static void value_set_attribute( gvalue_t *value, char_t *attribute ) 
{
  if (pango_context == NULL) 
    pango_context = gdk_pango_context_get(); 

  value->attribute = pango_layout_new( pango_context );
  pango_layout_set_text( value->attribute, attribute, -1 );
  free( attribute );
}

/*---------------------------------------------------------------------------*/

static gvalue_t *
parse_attribute_value_pair( void )
/* Parse an attribute-value pair and return a pointer to it. */
{
  string_t attribute;
  gvalue_t *new_value;
  
  if (next_token == '(') /* Read a hidden attribute. */
  { 
    read_next_token();
    attribute = parse_symbol();
    parse_token( ')' );
    new_value = new_mem( sizeof( gvalue_t ) );
    value_set_attribute( new_value, concat_strings( attribute, ":", NULL ) );
    value_set_string( new_value, new_string( "...", NULL ) );
    free_mem( &attribute );
  } 
  else 
  { 
    attribute = parse_symbol();
    parse_token( ':' );
    new_value = parse_value();
    value_set_attribute( new_value, concat_strings( attribute, ":", NULL ) );
    free_mem( &attribute );
  }
  return new_value;
}

/*---------------------------------------------------------------------------*/

static gvalue_t *
parse_value( void )
/* Parse a value and return it as a "gvalue_t". */
{
  gvalue_t *new_value;
  gvalue_t *element; /* Last list or record element. */

  new_value = new_mem( sizeof( gvalue_t ) );
  switch (next_token) 
  {
  case '<':
    new_value->value_type = GLIST_TYPE;
    read_next_token();
    if (next_token != '>') 
    { 
      /* Insert NEW_VALUE as first list element. */
      new_value->first = parse_value();
      element = new_value->first;
      while (next_token == ',') 
      { 
        /* Insert NEW_VALUE as successor element. */
	read_next_token();
        element->next = parse_value();
        element = element->next;
      }
    }
    parse_token( '>' );
    break;
  case '[':
    new_value->value_type = GRECORD_TYPE;
    read_next_token();
    if (next_token != ']') 
    { 
      new_value->first = parse_attribute_value_pair();
      element = new_value->first;
      while (next_token == ',') 
      { 
	read_next_token();
        element->next = parse_attribute_value_pair();
        element = element->next;
      }
    }
    parse_token( ']' );
    break;
  case TOK_IDENT:
    value_set_string( new_value, new_string( token_name, NULL ) );
    read_next_token();
    break;
  case TOK_STRING:
    value_set_string( new_value, new_string_readable( token_string, NULL ) );
    read_next_token();
    break;
  case TOK_NUMBER:
    value_set_string( new_value, double_to_string( token_number ) );
    read_next_token();
    break;
  case '-':
    read_next_token();
    test_token( TOK_NUMBER );
    value_set_string( new_value, double_to_string( -token_number ) );
    read_next_token();
    break;
  default:
    complain( "Value expected, not \"%s\".", token_as_text( next_token ) );
  }

  return new_value;
}

/* Displaying Malaga values. ================================================*/

int_t
get_line_width( canvas_t *canvas )
/* Get line width in pixels for CANVAS. */
{
  return canvas->line_width;
}

/*---------------------------------------------------------------------------*/
int_t
get_space_width( canvas_t *canvas )
/* Get space width in pixels for CANVAS. */
{
  return canvas->space_width;
}

/*---------------------------------------------------------------------------*/

int_t
get_font_height( canvas_t *canvas )
/* Get font height in pixels for CANVAS. */
{
  return canvas->font_height;
}

/*---------------------------------------------------------------------------*/

int_t
get_font_ascent( canvas_t *canvas )
/* Get number of pixels above baseline of current font for CANVAS. */
{
  return canvas->font_ascent;
}

/*---------------------------------------------------------------------------*/

int_t 
get_border_width( canvas_t *canvas )
/* Get border width of CANVAS in pixels. */
{
  return (ps_mode ? 500 : 5);
}

/*---------------------------------------------------------------------------*/

void 
set_color( color_t color )
/* Set the current drawing color to COLOR. */
{
  if (ps_mode)
  {
    if (color == WHITE) 
      fprintf( ps_stream, "1 G\n" );
    else 
      fprintf( ps_stream, "0 G\n" );
  }
  else 
  {
    gdk_gc_set_foreground( gc, &colors[ color ] );
    gdk_cairo_set_source_color( cairo, &colors[ color ] );
  }
}

/*---------------------------------------------------------------------------*/

static bool_t
clip_line( double *x1, double *y1, double *x2, double *y2, 
	   double x_min, double x_max )
/* Clip the x-coordinates of the line from (X1,Y1) to (X2,Y2) to the range
 * [X_MIN..X_MAX].  The start point and the end point may be swapped. */ 
{
  double t;

  /* Make sure that *X1 <= *X2. */
  if (*x2 < *x1)
  {
    t = *x1; *x1 = *x2; *x2 = t;
    t = *y1; *y1 = *y2; *y2 = t;
  }

  if (*x2 < x_min || *x1 > x_max)
    return FALSE;

  if (*x1 < x_min)
  {
    /* We can be sure that *X1 != *X2, since *X1 < X_MIN, but *X2 >= X_MIN. */
    *y1 += ((x_min - *x1) * (*y2 - *y1)) / (*x2 - *x1);
    *x1 = x_min;
  }

  if (*x2 > x_max)
  {
    /* We can be sure that *X1 != *X2, since *X2 > X_MAX, but *X1 <= X_MAX. */
    *y2 += ((x_max - *x2) * (*y2 - *y1)) / (*x2 - *x1);
    *x2 = x_max;
  }

  return TRUE;
}

/*---------------------------------------------------------------------------*/

static void
draw_lines_clipped( GdkPoint p[], int count )
/* Draw lines from p[0] TO p[1], p[1] TO p[2], ..., p[count-2] TO p[count-1].
 * Clip coordinates that are out of the current clip range.  
 *
 * This algorithm clips to the exact start and end points, but it cannot
 * guarantee that the line is translational invariant: If a clipped line is
 * drawn from (x1, y1) to (x2, y2) and another line from (x1 + dx, y1 + dy) to
 * (x2 + dx, y2 + dy), we cannot guarantee that, for each point (x, y) which is
 * part of the first line, the point (x + dx, y + dy) is part of the second
 * line, and vice versa. */
{
  int_t i;
  double x1, y1, x2, y2;
  const double left = clip.x - 10;
  const double right = clip.x + clip.width + 10;
  const double top = clip.y - 10;
  const double bottom = clip.y + clip.height + 10;

  for (i = 1; i < count; i++)
  {
    x1 = p[i-1].x;
    y1 = p[i-1].y;
    x2 = p[i].x;
    y2 = p[i].y;
    if (clip_line( &x1, &y1, &x2, &y2, left, right)
	&& clip_line( &y1, &x1, &y2, &x2, top, bottom))
    {
      cairo_move_to( cairo, x1 + canvas_shift, y1 + canvas_shift);
      cairo_line_to( cairo, x2 + canvas_shift , y2 + canvas_shift);
    }
  }
}

/*---------------------------------------------------------------------------*/

void 
draw_lines( int_t count, int_t x, int_t y, ... )
/* Called as "draw_lines( COUNT, x_1, y_1, x_2, y_2, ..., x_COUNT, y_COUNT )".
 * Draw lines:
 * from (x_1, y_1) to (x_2, y_2),
 * from (x_2, y_2) to (x_3, y_3),
 * ...
 * from (x_COUNT-1, y_COUNT-1) to (x_COUNT, y_COUNT). */
{
  va_list args;
  int_t i;
  static GdkPoint *p;
  static int_t max_points;
  bool_t left, right, over, under, need_clipping;

  if (ps_mode)
  {
    fprintf( ps_stream, "%d %d M ", x, area.height - y );
    va_start( args, y );
    for (i = 1; i < count; i++)
    {
      x = va_arg( args, int_t );
      y = va_arg( args, int_t );
      fprintf( ps_stream, "%d %d L ", x, area.height - y );
    }
    va_end( args );
    fprintf( ps_stream, "S\n" );
  }
  else
  {
    if (count > max_points) 
      max_points = renew_vector( &p, sizeof( GdkPoint ), count );
    left = right = over = under = FALSE;
    va_start( args, y );
    for (i = 0; ; i++)
    {
      x -= area.x;
      y -= area.y;
      if (x >= 0) 
	right = TRUE;
      if (x < area.width) 
	left = TRUE;
      if (y >= 0) 
	under = TRUE;
      if (y < area.height) 
	over = TRUE;
      x += clip.x;
      y += clip.y;
      if (x < SHRT_MIN || x > SHRT_MAX || y < SHRT_MIN || y > SHRT_MAX)
	need_clipping = TRUE;
      p[i].x = x;
      p[i].y = y;
      if (i >= count - 1) 
	break;
      x = va_arg( args, int_t );
      y = va_arg( args, int_t );
    }
    va_end( args );
    if (left && right && over && under) 
    {
      cairo_new_path( cairo );
      if (need_clipping)
	draw_lines_clipped( p, count );
      else
      {
	for (i = 0; i < count; i++)
	  cairo_line_to(cairo, p[i].x + canvas_shift, p[i].y + canvas_shift);
      }
      cairo_stroke( cairo );
    }
  }
}

/*---------------------------------------------------------------------------*/

void 
draw_rectangle( int_t x, int_t y, int_t width, int_t height )
/* Draw a rectangle with NW coordinate (X,Y), WIDTH pixels wide and HEIGHT
 * pixels high. */
{
  if (ps_mode)
  { 
    fprintf( ps_stream, "%d %d M ", x, area.height - y );
    fprintf( ps_stream, "%d %d R ", width, 0 );
    fprintf( ps_stream, "%d %d R ", 0, -height );
    fprintf( ps_stream, "%d %d R ", -width, 0 );
    fprintf( ps_stream, "F\n" );
  }
  else
  {
    x -= area.x;
    y -= area.y;
    if (x + width > 0 && x < area.width && y + height > 0 && y < area.height)
    {
      cairo_new_path( cairo );
      cairo_rectangle( cairo, 
		       clip.x + x + canvas_shift, 
		       clip.y + y + canvas_shift, 
		       width, height );
      cairo_fill( cairo );
    }
  }
}

/*---------------------------------------------------------------------------*/

void 
draw_circle( bool_t filled, int_t x, int_t y, int_t r )
{
  if (ps_mode)
  {
    if (filled) 
      fprintf( ps_stream, "%d %d %d D\n", x, area.height - y, r );
    else 
      fprintf( ps_stream, "%d %d %d C\n", x, area.height - y, r );
  }
  else
  {
    x -= area.x;
    y -= area.y;
    if (x + r >= 0 && x - r <= area.width 
	&& y + r >= 0 && y - r <= area.height)
    {
      cairo_new_path( cairo );
      cairo_arc( cairo, clip.x + x + canvas_shift, clip.y + y + canvas_shift,
		 r, 0, 2 * M_PI );
      cairo_close_path( cairo );
      if (filled) 
	cairo_fill( cairo ); 
      else 
	cairo_stroke( cairo );
    }
  }
}

/*---------------------------------------------------------------------------*/

static void
utf8_string_to_n3f( const char **string )
/* Convert *STRING (in UTF-8) to N3F PostScript string.
 * Return its length. The converted string is in PS_STRING. */
{
  int_t c, d, choseong, jungseong, jonseong;
  char *ps;

  ps = ps_string;
  while (**string != EOS && ps - ps_string < BUFFER_SIZE - 4)
  {
    c = g_utf8_get_char( *string );
    if (c >= FIRST_SYLLABLE && c <= LAST_SYLLABLE)
    {
      c -= FIRST_SYLLABLE;
      d = c / JONSEONG_COUNT;
      jonseong = c % JONSEONG_COUNT;
      jungseong = d % JUNGSEONG_COUNT;
      choseong = d / JUNGSEONG_COUNT;
      *ps++ = choseong + FIRST_CHOSEONG;
      *ps++ = jungseong + FIRST_JUNGSEONG;
      if (jonseong > 0)
	*ps++ = jonseong - 1 + FIRST_JONSEONG;
    }
    else if (c >= FIRST_JAMO && c <= LAST_JAMO)
    {
      c = jamos[ c - FIRST_JAMO ];
      *ps++ = (c < FIRST_JUNGSEONG ? c : NO_CHOSEONG);
      *ps++ = (c >= FIRST_JUNGSEONG && c < FIRST_JONSEONG ? c : NO_JUNGSEONG);
      if (c >= FIRST_JONSEONG)
	*ps++ = c;
    }
    else
      break;
    *string = g_utf8_next_char( *string );
  }
  *ps = EOS;
}

/*---------------------------------------------------------------------------*/

static void
utf8_string_to_latin1( const char **string )
{
  int_t c;
  char *ps;

  ps = ps_string;
  while (**string != EOS && ps - ps_string < BUFFER_SIZE - 2)
  {
    c = g_utf8_get_char( *string );
    if ((c >= 32 && c <= 126) || (c >= 160 && c <= 255))
      *ps++ = c;
    else
      break;
    *string = g_utf8_next_char( *string );
  }
  *ps = EOS;
}

/*---------------------------------------------------------------------------*/

static int_t
ps_string_width( const char *string )
{
  int_t width;
  const char *s, *old_pos;

  width = 0;
  while (*string != EOS)
  {
    old_pos = string;

    /* Convert a Latin1 segment. */
    utf8_string_to_latin1( &string );
    for (s = ps_string; *s != EOS; s++)
      width += widths_latin1[ ORD(*s) ];

    /* Convert a Hangul segment. */
    utf8_string_to_n3f( &string );
    if (*s != EOS)
      use_n3f = TRUE;
    for (s = ps_string; *s != EOS; s++)
      width += widths_n3f[ ORD(*s) - FIRST_N3F ];

    /* Ignore a non-convertible character. */
    if (string == old_pos && string != EOS)
      string = g_utf8_next_char( string );
  }
  return width;
}

/*---------------------------------------------------------------------------*/

static void
print_ps_text( const char *string )
/* Convert STRING to Postscript format: prefix "(", ")", and "\" with "\",
 * insert line breaks if line gets too long. */
{
  int_t col;
  const char *s, *old_pos;

  while (*string != EOS)
  {
    old_pos = string;
    /* Print a Latin1 segment. */
    utf8_string_to_latin1( &string );
    if (*ps_string != EOS)
    {
      fprintf( ps_stream, "(" );
      col = 1;
      for (s = ps_string; *s != EOS; s++)
      {
	if (col > 76) /* Insert line break if line gets too long. */
	{
	  fprintf( ps_stream, "\\\n" );
	  col = 0;
	}
	if (*s == '(' || *s == ')' || *s == '\\')
	{
	  fputc( '\\', ps_stream );
	  col++;
	}
	fputc( *s, ps_stream );
	col++;
      }
      fprintf( ps_stream, ")\nT\n" );
    }

    /* Print a Hangul segment. */
    utf8_string_to_n3f( &string );
    if (*ps_string != EOS)
    {
      fprintf( ps_stream, "<" );
      col = 1;
      for (s = ps_string; *s != EOS; s++)
      {
	if (col > 76) /* Insert line break if line gets too long. */
	{
	  fprintf( ps_stream, "\n" );
	  col = 0;
	}
	fprintf( ps_stream, "%02x", ORD( *s ) );
	col += 2;
      }
      fprintf( ps_stream, ">\nH\n" );
    }

    /* Ignore a non-convertible character. */
    if (string == old_pos && string != EOS)
      string = g_utf8_next_char( string );
  }
}

/*---------------------------------------------------------------------------*/

static void config_layout( canvas_t *canvas, PangoLayout *layout, 
			   int_t *width, int_t *height, int_t *ascent )
{
  PangoLayoutIter *iter;

  if (ps_mode)
  {
    if (width != NULL)
      *width = ps_string_width( pango_layout_get_text( layout ) );
    if (height != NULL)
      *height = canvas->font_height;
    if (ascent != NULL)
      *ascent = canvas->font_ascent;
  }
  else
  {
    pango_layout_set_font_description( layout, canvas->font );
    pango_layout_get_pixel_size( layout, width, height );
    if (ascent != NULL)
    {
      iter = pango_layout_get_iter( layout );
      *ascent = pango_layout_iter_get_baseline( iter ) / PANGO_SCALE;
      pango_layout_iter_free( iter );
    }
  }
}

/*---------------------------------------------------------------------------*/

static void
draw_layout( canvas_t *canvas, PangoLayout *layout, int_t x, int_t y )
/* Draw LAYOUT at position (X,Y) in CANVAS. */
{
  int_t width, height;

  if (ps_mode)
  {
    fprintf( ps_stream, "%d %d M\n", 
	     x, area.height - y - canvas->font_ascent );
    print_ps_text( pango_layout_get_text( layout ) );
    return;
  }
  else
  {
    pango_layout_get_pixel_size( layout, &width, &height );
    x -= area.x;
    y -= area.y;
    if (x + width <= 0 || x >= area.width) 
      return;
    if (y + height <= 0 || y >= area.height) 
      return;
    
    gdk_draw_layout( drawable, gc, clip.x + x, clip.y + y, layout );
  }
}

/*---------------------------------------------------------------------------*/

static void
config_value( canvas_t *canvas, gvalue_t *value )
/* Compute the height, width and ascent of VALUE in CANVAS. */
{
  gvalue_t *element;
  int_t max_attrib_width, max_width, descent;

  if (value->attribute != NULL)
    config_layout( canvas, value->attribute, &value->attrib_width, NULL, NULL);

  switch (value->value_type) 
  {
  case GRECORD_TYPE:
    value->height = 0;
    max_attrib_width = 0;
    max_width = 0;
    value->ascent = canvas->border_height + canvas->font_ascent; /* Default. */
    for (element = value->first; element != NULL; element = element->next) 
    { 
      config_value( canvas, element );
      max_attrib_width = MAX( max_attrib_width, element->attrib_width );
      max_width = MAX( max_width, element->width );

      /* Update ascent and height */
      if (! canvas->hanging_style) 
      {
	value->ascent = (canvas->border_height + value->height 
			 + element->ascent);
      }
      value->height += element->height;
    }
    if (canvas->hanging_style && value->first != NULL)
      value->ascent = canvas->border_height + value->first->ascent;
    value->height = (canvas->border_height
		     + MAX( value->height, canvas->font_height )
		     + canvas->border_height);
    value->width = (canvas->border_width + max_attrib_width 
		    + canvas->space_width + max_width
		    + canvas->border_width);
    break;
  case GLIST_TYPE:
    value->width = 0;
    descent = canvas->font_height - canvas->font_ascent;
    value->ascent = canvas->font_ascent;
    for (element = value->first; element != NULL; element = element->next) 
    { 
      config_value( canvas, element );
      descent = MAX( descent, element->height - element->ascent );
      value->ascent = MAX( value->ascent, element->ascent );
      value->width += element->width;
      if (element->next != NULL) 
	value->width += (canvas->comma_width + canvas->space_width);
    }
    if (value->width == 0) 
      value->width = 2;
    value->ascent += canvas->border_height;
    value->height = value->ascent + descent + canvas->border_height; 
    value->width += 2 * (canvas->border_width / 3 
			 + ((value->height - canvas->border_height) 
			    / BRACKET_RATIO));
    break;
  case GSTRING_TYPE:
    config_layout( canvas, value->string, 
		   &value->width, &value->height, &value->ascent );
    break;
  }
}

/*---------------------------------------------------------------------------*/

static int_t
max_attribute_width( gvalue_t *value ) 
/* Compute maximum attribute width of record VALUE. */
{
  gvalue_t *element;
  int_t max_width;

  max_width = 0;
  for (element = value->first; element != NULL; element = element->next) 
    max_width = MAX( max_width, element->attrib_width );
  return max_width;
}

/*---------------------------------------------------------------------------*/

static void 
draw_value( canvas_t *canvas, gvalue_t *value, int_t x, int_t y )
/* Draw VALUE in CANVAS at X/Y. */
{
  gvalue_t *element;
  int_t x2, bracket_width;

  /* If (sub)value is out of bounds, we don't need to draw it. */
  if (x >= area.x + area.width || y >= area.y + area.height
      || x + value->width <= area.x || y + value->height <= area.y) 
  { 
    return;
  }

  switch (value->value_type) 
  {
  case GRECORD_TYPE:
    /* Draw left bracket. */
    draw_lines( 4,
		x + canvas->border_width * 5 / 6,
		y + canvas->border_height / 2,
		x + canvas->border_width / 6,
		y + canvas->border_height / 2,
		x + canvas->border_width / 6,
		y + value->height - 1 - canvas->border_height / 2,
		x + canvas->border_width * 5 / 6,
		y + value->height - 1 - canvas->border_height / 2 );

    /* Draw right bracket. */
    draw_lines( 4,
		x + value->width - 1 - canvas->border_width * 5 / 6, 
		y + canvas->border_height / 2, 
		x + value->width - 1 - canvas->border_width / 6, 
		y + canvas->border_height / 2,
		x + value->width - 1 - canvas->border_width / 6, 
		y + value->height - 1 - canvas->border_height / 2,
		x + value->width - 1 - canvas->border_width * 5 / 6, 
		y + value->height - 1 - canvas->border_height / 2 );

    /* Draw elements. */
    x2 = (x + canvas->border_width + 
	  max_attribute_width( value ) + canvas->space_width);
    y += canvas->border_height;
    for (element = value->first; element != NULL; element = element->next) 
    { 
      /* Draw attribute name. */
      draw_layout( canvas, element->attribute, 
		   x + canvas->border_width, 
		   y + (element->height - canvas->font_height) / 2 );

      /* Draw attribute value. */
      draw_value( canvas, element, x2, y );
      y += element->height;
    }
    break;
  case GLIST_TYPE:
    bracket_width = (canvas->border_width / 3
		     + ((value->height - canvas->border_height) 
			/ BRACKET_RATIO));

    /* Draw left bracket. */
    draw_lines( 3,
		x + bracket_width - canvas->border_width / 6, 
		y + canvas->border_height / 2, 
		x + canvas->border_width / 6, 
		y + value->height / 2,
		x + bracket_width - canvas->border_width / 6, 
		y + value->height - 1 - canvas->border_height / 2 );

    /* Draw right bracket. */
    draw_lines( 3,
		x + value->width - 1
		- (bracket_width - canvas->border_width / 6),
		y + canvas->border_height / 2, 
		x + value->width - 1 - canvas->border_width / 6,
		y + value->height / 2,
		x + value->width - 1 
		- (bracket_width - canvas->border_width / 6),
		y + value->height - 1 - canvas->border_height / 2 );

    /* Draw elements. */
    x += bracket_width;
    y += value->ascent; /* Baseline. */
    for (element = value->first; element != NULL; element = element->next) 
    { 
      draw_value( canvas, element, x, y - element->ascent );
      x += element->width;
      if (element->next != NULL) /* Draw ",". */
      { 
	draw_layout( canvas, canvas->comma_layout, x, y - canvas->font_ascent );
        x += (canvas->comma_width + canvas->space_width);
      }
    }
    break;
  case GSTRING_TYPE:
    draw_layout( canvas, value->string, x, y );
    break;
  }
}

/*---------------------------------------------------------------------------*/

static gboolean 
expose_event( GtkWidget *widget, GdkEventExpose *event, canvas_t *canvas ) 
{
  GdkColormap *colormap;

  if (gc == NULL)
  { 
    /* Allocate the shared graphics context and the shared colors. */
    gc = gdk_gc_new( gtk_widget_get_parent_window( canvas->draw_area ) );
    colormap = gtk_widget_get_colormap( canvas->draw_area );

    colors[ BLACK ].red = colors[ BLACK ].green = colors[ BLACK ].blue = 0;
    if (! gdk_colormap_alloc_color( colormap, &colors[ BLACK ], FALSE, TRUE ))
      complain( "Could not get black." );

    colors[ WHITE ].red = colors[ WHITE ].green = colors[ WHITE ].blue = 65535;
    if (! gdk_colormap_alloc_color( colormap, &colors[ WHITE ], FALSE, TRUE ))
      complain( "Could not get white." );

    colors[ RED ].red = 65535; colors[ RED ].green = colors[ RED ].blue = 0;
    if (! gdk_colormap_alloc_color( colormap, &colors[ RED ], FALSE, TRUE ))
      complain( "Could not get red." );

    colors[ BLUE ].blue = 65535; colors[ BLUE ].red = colors[ BLUE ].green = 0;
    if (! gdk_colormap_alloc_color( colormap, &colors[ BLUE ], FALSE, TRUE ))
      complain( "Could not get blue." );
  }

  area.x = canvas->x + event->area.x;
  area.y = canvas->y + event->area.y;
  clip.x = event->area.x;
  clip.y = event->area.y;
  clip.width = area.width = event->area.width;
  clip.height = area.height = event->area.height;
  drawable = canvas->draw_area->window;
  cairo = gdk_cairo_create(drawable);
  cairo_set_line_width( cairo, canvas->line_width );
  cairo_set_line_cap( cairo, CAIRO_LINE_CAP_SQUARE );
  cairo_set_line_join( cairo, CAIRO_LINE_JOIN_BEVEL );
  canvas->expose( canvas, &area );
  cairo_destroy(cairo);
  cairo = NULL;

  return TRUE;
}

/*---------------------------------------------------------------------------*/

static void
configure_draw_area( canvas_t *canvas )
{
  if (pango_context == NULL) 
    pango_context = gdk_pango_context_get(); 

  if (canvas->comma_layout == NULL)
  {
    canvas->comma_layout = pango_layout_new( pango_context );
    pango_layout_set_text( canvas->comma_layout, ",", -1 );
  }

  if (canvas->space_layout == NULL)
  {
    canvas->space_layout = pango_layout_new( pango_context );
    pango_layout_set_text( canvas->space_layout, " ", -1 );
  }

  if (ps_mode)
  {
    canvas->border_width = 600;
    canvas->border_height = 300;
    canvas->space_width = ps_string_width( " " );
    canvas->comma_width = ps_string_width( "," );
    canvas->font_ascent = 917;
    canvas->font_height = 1150;
    canvas->line_width = 25;
  }
  else
  {
    config_layout( canvas, canvas->space_layout, &canvas->space_width, 
		   &canvas->font_height, &canvas->font_ascent );
    config_layout( canvas, canvas->comma_layout, &canvas->comma_width, 
		   NULL, NULL );
    canvas->line_width = (canvas->font_height + 8) / 12;
    canvas->border_width = 6 * canvas->line_width;
    canvas->border_height = 3 * canvas->line_width;
    canvas_shift = ((canvas->line_width & 1) != 0) ? 0.5 : 0.0;
  }
  canvas->configure( canvas, &canvas->width, &canvas->height );
}

/*---------------------------------------------------------------------------*/

static void
write_postscript( canvas_t *canvas )
{
  double scaling;
  time_t now;

  area.x = 0;
  area.y = 0;
  area.width = canvas->width;
  area.height = canvas->height;

  scaling = 0.01;
  if (area.width * scaling > PAGE_WIDTH) 
    scaling = PAGE_WIDTH / (double) area.width;
  if (area.height * scaling > PAGE_HEIGHT) 
    scaling = PAGE_HEIGHT / (double) area.height;
  
  fprintf( ps_stream, 
	   "%%!PS-Adobe-3.0 EPSF-3.0\n"
	   "%%%%BoundingBox: %d %d %d %d\n",
	   (int_t) PAPER_BORDER, (int_t) PAPER_BORDER,
	   (int_t) ceil( area.width * scaling + PAPER_BORDER ), 
	   (int_t) ceil( area.height * scaling + PAPER_BORDER ) );
	   
  fprintf( ps_stream, 
	   "%%%%HiResBoundingBox: %f %f %f %f\n", 
	   PAPER_BORDER, PAPER_BORDER,
	   area.width * scaling + PAPER_BORDER, 
	   area.height * scaling + PAPER_BORDER );

  fprintf( ps_stream, 
	   "%%%%Creator: malshow\n"
	   "%%%%Title: %s\n", GTK_WINDOW( canvas->window )->title );

  time( &now );
  fprintf( ps_stream, "%%%%CreationDate: %s", ctime( &now ) );

  fprintf( ps_stream, 
	   "%%%%DocumentData: Clean8Bit\n"
	   "%%%%Orientation: Portrait\n" );

  if ( use_n3f )
    fprintf( ps_stream, "%%%%DocumentSuppliedResources: font n3f-5\n" );

  fprintf( ps_stream, 
	   "%%%%DocumentNeededResources: font Helvetica\n"
	   "%%%%EndComments\n" );

  if ( use_n3f )
    fprintf( ps_stream, "%s", n3f_font_definition );

  fprintf( ps_stream,
	   "save 11 dict begin\n"
	   "/ISOLatin1Encoding where {pop} {/ISOLatin1Encoding [\n"
	   "/space /space /space /space /space /space /space /space /space\n"
	   "/space /space /space /space /space /space /space /space /space\n"
	   "/space /space /space /space /space /space /space /space /space\n"
	   "/space /space /space /space /space /space /exclam /quotedbl\n"
	   "/numbersign /dollar /percent /ampersand /quoteright /parenleft\n"
	   "/parenright /asterisk /plus /comma /minus /period /slash /zero\n"
	   "/one /two /three /four /five /six /seven /eight /nine /colon\n"
	   "/semicolon /less /equal /greater /question /at /A /B /C /D /E /F\n"
	   "/G /H /I /J /K /L /M /N /O /P /Q /R /S /T /U /V /W /X /Y /Z\n"
	   "/bracketleft /backslash /bracketright /asciicircum /underscore\n"
	   "/quoteleft /a /b /c /d /e /f /g /h /i /j /k /l /m /n /o /p /q /r\n"
	   "/s /t /u /v /w /x /y /z /braceleft /bar /braceright /asciitilde\n"
	   "/space /space /space /space /space /space /space /space /space\n"
	   "/space /space /space /space /space /space /space /space\n"
	   "/dotlessi /grave /acute /circumflex /tilde /macron /breve\n"
	   "/dotaccent /dieresis /space /ring /cedilla /space /hungarumlaut\n"
	   "/ogonek /caron /space /exclamdown /cent /sterling /currency /yen\n"
	   "/brokenbar /section /dieresis /copyright /ordfeminine\n"
	   "/guillemotleft /logicalnot /hyphen /registered /macron /degree\n"
	   "/plusminus /twosuperior /threesuperior /acute /mu /paragraph\n"
	   "/periodcentered /cedilla /onesuperior /ordmasculine\n"
	   "/guillemotright /onequarter /onehalf /threequarters\n"
	   "/questiondown /Agrave /Aacute /Acircumflex /Atilde /Adieresis\n"
	   "/Aring /AE /Ccedilla /Egrave /Eacute /Ecircumflex /Edieresis\n"
	   "/Igrave /Iacute /Icircumflex /Idieresis /Eth /Ntilde /Ograve\n"
	   "/Oacute /Ocircumflex /Otilde /Odieresis /multiply /Oslash\n"
	   "/Ugrave /Uacute /Ucircumflex /Udieresis /Yacute /Thorn\n"
	   "/germandbls /agrave /aacute /acircumflex /atilde /adieresis\n"
	   "/aring /ae /ccedilla /egrave /eacute /ecircumflex /edieresis\n"
	   "/igrave /iacute /icircumflex /idieresis /eth /ntilde /ograve\n"
	   "/oacute /ocircumflex /otilde /odieresis /divide /oslash /ugrave\n"
	   "/uacute /ucircumflex /udieresis /yacute /thorn /ydieresis\n"
	   "] def} ifelse\n"
	   "/Helvetica-ISOLatin1\n"
	   "/Helvetica findfont\n" 
	   "dup length dict copy\n"
	   "dup /FID undef\n"
	   "dup /Encoding ISOLatin1Encoding put\n"
	   "definefont pop\n"
  	   "/C {0 360 arc stroke} bind def\n" /* Paint a circle. */
	   "/D {0 360 arc fill} bind def\n" /* Paint a filled disc. */
	   "/F {closepath fill} bind def\n"
	   "/G /setgray load def\n" );

  if (use_n3f)
    fprintf( ps_stream, "/H { /n3f-5 1000 selectfont show } bind def\n" );

  fprintf( ps_stream, 
	   "/L /lineto load def\n"
	   "/M /moveto load def\n"
	   "/R /rlineto load def\n"
	   "/S /stroke load def\n"
	   "/T { /Helvetica-ISOLatin1 1000 selectfont show } bind def\n" );

  fprintf( ps_stream, "%d setlinewidth\n", canvas->line_width );
  fprintf( ps_stream, "%f %f translate\n", PAPER_BORDER, PAPER_BORDER );
  fprintf( ps_stream, "%f %f scale\n", scaling, scaling );

  canvas->expose( canvas, &area );
  fprintf( ps_stream, 
	   "end restore showpage\n"
	   "%%%%EOF\n" );
}

/*---------------------------------------------------------------------------*/

static void
save_postscript( GtkWidget *widget, canvas_t *canvas )
{
  GtkWidget *dialog, *label, *okay_button;  
  string_t file_name;

  file_name = (string_t) gtk_file_selection_get_filename(
    GTK_FILE_SELECTION( canvas->file_selection ) );
  TRY
  {
    ps_stream = open_stream( file_name, "w" );
    ps_mode = TRUE;
    use_n3f = FALSE;
    configure_draw_area( canvas );
    write_postscript( canvas );
    if (ferror( ps_stream )) 
      complain( "Can't write to \"%s\": %s.", strerror( errno ) );
    close_stream( &ps_stream, file_name );
    ps_mode = FALSE;
    configure_draw_area( canvas );   
  }
  IF_ERROR 
  { 
    /* Reconfigure canvas before doing any GTK stuff, so it can be safely
     * exposed. */
    close_stream( &ps_stream, NULL );
    ps_mode = FALSE;
    configure_draw_area( canvas );   

    /* Show the error message. */
    dialog = gtk_dialog_new();
    gtk_window_set_title( GTK_WINDOW( dialog ), "Export Postscript" );
    gtk_window_set_transient_for( GTK_WINDOW( dialog ),
				  GTK_WINDOW( canvas->window ) );
    gtk_window_set_position( GTK_WINDOW( dialog ), GTK_WIN_POS_MOUSE );
    label = gtk_label_new( error_text->buffer );
    gtk_misc_set_padding( GTK_MISC( label ), 10, 10 );
    okay_button = gtk_button_new_with_label( "OK" );
    gtk_signal_connect_object( GTK_OBJECT( okay_button ), "clicked",
			       GTK_SIGNAL_FUNC( gtk_widget_destroy ), 
			       GTK_OBJECT( dialog ) );
    gtk_container_add( GTK_CONTAINER( GTK_DIALOG( dialog )->action_area ),
                       okay_button );
    gtk_container_add( GTK_CONTAINER( GTK_DIALOG( dialog )->vbox ), label);
    gtk_widget_show_all( dialog );
    RESUME;
  }
  END_TRY;
  gtk_widget_hide( canvas->file_selection );
  canvas->file_selection = NULL;
}

/*---------------------------------------------------------------------------*/

static void
export_postscript( canvas_t *canvas )
/* Open "Export Postscript" selector */
{
  string_t title;

  if (canvas->file_selection == NULL)
  {
    title = concat_strings( "Export ", GTK_WINDOW( canvas->window )->title,
			    " as Postscript", NULL );
    canvas->file_selection = gtk_file_selection_new( title );
    free_mem( &title );
    gtk_window_set_transient_for( GTK_WINDOW( canvas->file_selection ),
				  GTK_WINDOW( canvas->window ) );
    gtk_window_set_position( GTK_WINDOW( canvas->file_selection ), 
			     GTK_WIN_POS_MOUSE );
    gtk_file_selection_hide_fileop_buttons(
      GTK_FILE_SELECTION( canvas->file_selection ) );
    gtk_file_selection_set_filename(
      GTK_FILE_SELECTION( canvas->file_selection ), canvas->ps_file_name );
    gtk_signal_connect( 
      GTK_OBJECT( GTK_FILE_SELECTION( canvas->file_selection )->ok_button ),
      "clicked", GTK_SIGNAL_FUNC( save_postscript ), canvas ); 
    gtk_signal_connect_object(
      GTK_OBJECT( GTK_FILE_SELECTION( 
		    canvas->file_selection )->cancel_button ),
      "clicked", GTK_SIGNAL_FUNC( gtk_widget_hide ), 
      GTK_OBJECT( canvas->file_selection ) );
    gtk_signal_connect( GTK_OBJECT( canvas->file_selection ), 
			"delete_event", GTK_SIGNAL_FUNC( gtk_widget_hide), 
			NULL );
  }
  gtk_widget_show( canvas->file_selection );
}

/*---------------------------------------------------------------------------*/

static void
adjust_canvas( canvas_t *canvas )
{
  if (canvas->x + canvas->area_width > canvas->width)
    canvas->x = canvas->width - canvas->area_width;
  if (canvas->x < 0) 
    canvas->x = 0;

  if (canvas->y + canvas->area_height > canvas->height)
    canvas->y = canvas->height - canvas->area_height;
  if (canvas->y < 0) 
    canvas->y = 0;

  canvas->hadjust->value = canvas->x; 
  canvas->hadjust->upper = canvas->width;
  canvas->hadjust->page_increment = MAX( 20, canvas->area_width - 20 );
  canvas->hadjust->page_size = canvas->area_width;

  canvas->vadjust->value = canvas->y;
  canvas->vadjust->upper = canvas->height;
  canvas->vadjust->page_increment = MAX( 20, canvas->area_height - 20 );
  canvas->vadjust->page_size = canvas->area_height;

  gtk_adjustment_changed( canvas->hadjust );
  gtk_adjustment_changed( canvas->vadjust );

  /* If the pointer is currently in the drawing area, we might update the
   * cursor. */
  if (canvas->mouse_event != NULL)
  {
    int x, y;
    GdkModifierType mask;

    gdk_window_get_pointer( canvas->draw_area->window, &x, &y, &mask );
    canvas->mouse_event( canvas, canvas->x + x, canvas->y + y, 0 );
  }

  gtk_widget_queue_draw( canvas->draw_area );
}

/*---------------------------------------------------------------------------*/

static gboolean
delete_event( GtkWidget *widget, GdkEvent *event, canvas_t *canvas )
{
  hide_canvas( canvas );
  return TRUE;
}

/*---------------------------------------------------------------------------*/

static gboolean
key_press_event( GtkWidget *widget, GdkEventKey *event, canvas_t *canvas )
{
  switch (event->keyval)
  {
  case GDK_Left:
    if ((event->state & GDK_CONTROL_MASK) != 0) 
      canvas->x = 0;
    else if ((event->state & GDK_SHIFT_MASK) != 0)
      canvas->x -= canvas->hadjust->page_increment;
    else 
      canvas->x -= 10; 
    break;
  case GDK_Right:
    if ((event->state & GDK_CONTROL_MASK) != 0) 
      canvas->x = canvas->width;
    else if ((event->state & GDK_SHIFT_MASK) != 0)
      canvas->x += canvas->hadjust->page_increment;
    else 
      canvas->x += 10; 
    break;
  case GDK_Up: 
    if ((event->state & GDK_CONTROL_MASK) != 0) 
      canvas->y = 0;
    else if ((event->state & GDK_SHIFT_MASK) != 0)
      canvas->y -= canvas->vadjust->page_increment; 
    else 
      canvas->y -= 10; 
    break;
  case GDK_Down: 
    if ((event->state & GDK_CONTROL_MASK) != 0) 
      canvas->y = canvas->height;
    else if ((event->state & GDK_SHIFT_MASK) != 0)
      canvas->y += canvas->vadjust->page_increment; 
    else 
      canvas->y += 10; 
    break;
  case GDK_Home: 
    if ((event->state & GDK_CONTROL_MASK) != 0)
      canvas->x -= canvas->hadjust->page_increment;
    else 
      canvas->x = 0; 
    break;
  case GDK_End:
    if ((event->state & GDK_CONTROL_MASK) != 0)
      canvas->x += canvas->hadjust->page_increment;
    else 
      canvas->x = canvas->width; 
    break;
  case GDK_Page_Up:
    if ((event->state & GDK_CONTROL_MASK) != 0) 
      canvas->y = 0;
    else 
      canvas->y -= canvas->vadjust->page_increment; 
    break;
  case GDK_Page_Down: 
    if ((event->state & GDK_CONTROL_MASK) != 0) 
      canvas->y = canvas->height;
    else 
      canvas->y += canvas->vadjust->page_increment; 
    break;
  default:
    return FALSE;
  }
  adjust_canvas( canvas );
  return TRUE;
}

/*---------------------------------------------------------------------------*/

static void 
set_font_size( canvas_t *canvas, guint font_size )
{
  canvas->font_size = font_size;
  configure_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

static void 
set_hanging_style( canvas_t *canvas, guint action, GtkWidget *item )
{
  canvas->hanging_style = GTK_CHECK_MENU_ITEM( item )->active;
  configure_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

static void
configure_scrollbars( canvas_t *canvas )
{
  int_t width, height;
  bool_t show_hscrollbar, show_vscrollbar;

  width = canvas->area_width;
  if (canvas->show_vscrollbar)
    width += canvas->vscrollbar_width;
  show_hscrollbar = (canvas->width > width);

  height = canvas->area_height;
  if (canvas->show_hscrollbar)
    height += canvas->hscrollbar_height;
  show_vscrollbar = (canvas->height > height);

  if (show_vscrollbar && canvas->width + canvas->vscrollbar_width > width)
    show_hscrollbar = TRUE;
  if (show_hscrollbar && canvas->height + canvas->hscrollbar_height > height)
    show_vscrollbar = TRUE;

  if (show_hscrollbar != canvas->show_hscrollbar)
  {
    if (show_hscrollbar)
      gtk_widget_show( canvas->hscrollbar );
    else
      gtk_widget_hide( canvas->hscrollbar );
    canvas->show_hscrollbar = show_hscrollbar;
  }
  if (show_vscrollbar != canvas->show_vscrollbar)
  {
    if (show_vscrollbar)
      gtk_widget_show( canvas->vscrollbar );
    else
      gtk_widget_hide( canvas->vscrollbar );
    canvas->show_vscrollbar = show_vscrollbar;
  }
}

/*---------------------------------------------------------------------------*/

static void 
configure_event( GtkWidget *widget, 
		 GdkEventConfigure *event, 
		 canvas_t *canvas )
/* Called if CANVAS window size has changed. */
{
  canvas->area_width = event->width;
  canvas->area_height = event->height;
  configure_scrollbars( canvas );
  adjust_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

static void 
adjust_value_changed( GtkAdjustment *adjust, canvas_t *canvas )
/* Called if ADJUST has changed its value. Draw CANVAS at the new position. */
{
  canvas->x = canvas->hadjust->value;
  canvas->y = canvas->vadjust->value;
  gtk_widget_queue_draw( canvas->draw_area );
}

/*---------------------------------------------------------------------------*/

void
make_visible( canvas_t *canvas, int_t x, int_t y )
/* Make sure the point (X,Y) is in the displayed part of CANVAS. */
{
  if (x < canvas->x || x > canvas->x + canvas->area_width)
    canvas->x =  x - canvas->area_width / 2;
  if (y < canvas->y || y > canvas->y + canvas->area_height)
    canvas->y = y - canvas->area_height / 2;
  adjust_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

void
go_canvas_bottom( canvas_t *canvas )
/* Go to the bottom left of CANVAS. */
{
  canvas->x = 0;
  if (canvas->height < canvas->area_height) 
    canvas->y = 0;
  else 
    canvas->y = canvas->height - canvas->area_height;
  adjust_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

void
configure_canvas( canvas_t *canvas )
/* Call this if CANVAS must be reconfigured because its content has changed
 * its size (font size change, new content, etc.). */
{
  text_t *font_descriptor;

  if (canvas->font != NULL)
    pango_font_description_free( canvas->font );
  font_descriptor = new_text();
  print_text( font_descriptor, "%s %d", font_family, canvas->font_size );
  canvas->font = pango_font_description_from_string( font_descriptor->buffer );
  free_text (&font_descriptor );

  configure_draw_area( canvas );
  configure_scrollbars( canvas );
  adjust_canvas( canvas );
}

/*---------------------------------------------------------------------------*/

void 
show_canvas( canvas_t *canvas )
{
  gdk_window_raise( canvas->window->window );
  gtk_widget_show( canvas->window );
}

/*---------------------------------------------------------------------------*/

void 
hide_canvas( canvas_t *canvas )
{
  gtk_widget_hide( canvas->window );
  canvas->file_selection = NULL;
  if (canvas->close != NULL) 
    canvas->close( canvas );
}

/*---------------------------------------------------------------------------*/

void 
redraw_canvas( canvas_t *canvas )
{
  gtk_widget_queue_draw( canvas->draw_area );
}

/*---------------------------------------------------------------------------*/

void
set_cursor( canvas_t *canvas, bool_t alternate )
/* Set cursor for CANVAS to alternate shape if ALTERNATE == TRUE. */
{
  if (canvas->alternate_cursor == alternate 
      || canvas->draw_area->window == NULL) 
  {
    return;
  }
  if (alternate_cursor == NULL) 
    alternate_cursor = gdk_cursor_new( GDK_HAND2 );
  gdk_window_set_cursor( canvas->draw_area->window, 
			 alternate ? alternate_cursor : NULL );
  canvas->alternate_cursor = alternate;
}

/*---------------------------------------------------------------------------*/

void
set_popup_menu( canvas_t *canvas, 
		GtkItemFactoryEntry items[], int_t item_count )
/* Set a pop-up menu for CANVAS, consisting of ITEM_COUNT ITEMS. */
{
  canvas->popup_menu_factory 
    = gtk_item_factory_new( GTK_TYPE_MENU, "<main>", NULL );
  gtk_item_factory_create_items( canvas->popup_menu_factory, item_count, items,
				 canvas );
}

/*---------------------------------------------------------------------------*/

void
popup_menu( canvas_t *canvas )
/* Pop up the pop-up menu for CANVAS as a reaction of a button press event that
 * is currently handled. */
{
  if (canvas->event != NULL)
  {
    gtk_item_factory_popup( canvas->popup_menu_factory, 
			    canvas->event->x_root, 
			    canvas->event->y_root,
			    canvas->event->button, 
			    canvas->event->time );
  }
}

/*---------------------------------------------------------------------------*/

static gboolean
scroll_event( GtkWidget *widget, GdkEventScroll *event, canvas_t *canvas )
{
  switch (event->direction)
  {
  case GDK_SCROLL_UP:
    canvas->y -= canvas->area_height / 9;
    adjust_canvas( canvas );
    return TRUE;
  case GDK_SCROLL_DOWN:
    canvas->y += canvas->area_height / 9;
    adjust_canvas( canvas );
    return TRUE;
  default:
    return FALSE;
  }
}

/*---------------------------------------------------------------------------*/

static gboolean
button_press_event( GtkWidget *widget, GdkEventButton *event, 
		    canvas_t *canvas )
{
  canvas->event = event;
  return canvas->mouse_event( canvas,
			      ((int_t) event->x) + canvas->x, 
			      ((int_t) event->y) + canvas->y, 
			      event->button );
  canvas->event = NULL;
}

/*---------------------------------------------------------------------------*/

static gboolean
motion_notify_event( GtkWidget *widget, GdkEventMotion *event, 
		     canvas_t *canvas )
{
  return canvas->mouse_event( canvas, 
			      ((int_t) event->x) + canvas->x, 
			      ((int_t) event->y) + canvas->y, 0 );
}

/*---------------------------------------------------------------------------*/

static gboolean
enter_notify_event( GtkWidget *widget, GdkEventCrossing *event,
		    canvas_t *canvas )
{
  return canvas->mouse_event( canvas, 
			      ((int_t) event->x) + canvas->x, 
			      ((int_t) event->y) + canvas->y, 0 );
}

/*---------------------------------------------------------------------------*/

/* General menu items. */
static GtkItemFactoryEntry canvas_items[] = 
{
  { "/Window", NULL, NULL, 0, "<Branch>" },
  { "/Window/Export Postscript...", NULL, export_postscript, 0, NULL },
  { "/Window/Close", "<Control>C", hide_canvas, 0, NULL },
  { "/Style", NULL, NULL, 0, "<Branch>" },
  { "/Style/Font Size 8", NULL, set_font_size, 8, "<RadioItem>" },
  { "/Style/Font Size 10", NULL, set_font_size, 10, "/Style/Font Size 8" },
  { "/Style/Font Size 12", NULL, set_font_size, 12, "/Style/Font Size 8" },
  { "/Style/Font Size 14", NULL, set_font_size, 14, "/Style/Font Size 8" },
  { "/Style/Font Size 18", NULL, set_font_size, 18, "/Style/Font Size 8" },
  { "/Style/Font Size 24", NULL, set_font_size, 24, "/Style/Font Size 8" }
};

static GtkItemFactoryEntry hanging_style_items[] =
{
  { "/Style/sep1", NULL, NULL, 0, "<Separator>" },
  { "/Style/Hanging", NULL, set_hanging_style, 0, "<ToggleItem>" }
};

/*---------------------------------------------------------------------------*/

canvas_t *
create_canvas( string_t title,
	       string_t ps_file_name,
	       rectangle_t *geometry,
	       configure_func_t my_configure,
	       expose_func_t my_expose,
	       close_func_t my_close,
	       mouse_func_t my_mouse_event,
	       bool_t show_hanging_option,
	       GtkItemFactoryEntry items[], 
	       int_t item_count )
{
  canvas_t *canvas;
  GtkWidget *menu_bar, *vbox, *table;
  GtkAccelGroup *accel; /* Accelerator group. */
  GtkRequisition requisition;

  canvas = new_mem( sizeof( canvas_t ) );
  canvas->ps_file_name = ps_file_name;
  canvas->configure = my_configure;
  canvas->expose = my_expose;
  canvas->close = my_close;
  canvas->mouse_event = my_mouse_event;
  canvas->font_size = font_size;

  if (canvas->font_size == 0) 
    canvas->font_size = 10;

  /* Create a toplevel window. */
  canvas->window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  gtk_signal_connect( GTK_OBJECT( canvas->window ), "delete_event",
		      GTK_SIGNAL_FUNC( delete_event ), canvas );
  gtk_window_set_title( GTK_WINDOW( canvas->window ), title );
  if (geometry->x >= 0 && geometry->y >= 0)
    gtk_window_move( GTK_WINDOW( canvas->window ), geometry->x, geometry->y );
  if (geometry->width > 0 && geometry->height > 0)
  {
    gtk_window_set_default_size( GTK_WINDOW( canvas->window), 
				 geometry->width, geometry->height );
  }
  gtk_window_set_policy( GTK_WINDOW( canvas->window ), TRUE, TRUE, FALSE );
  gtk_signal_connect( GTK_OBJECT( canvas->window ), "key_press_event",
		      GTK_SIGNAL_FUNC( key_press_event ), canvas );

  /* Fill the toplevel window with a vbox. */
  vbox = gtk_vbox_new( FALSE, 0 );
  gtk_container_add( GTK_CONTAINER( canvas->window ), vbox );

  /* Add a menu bar to the vbox. */
  accel = gtk_accel_group_new();
  canvas->factory = gtk_item_factory_new( GTK_TYPE_MENU_BAR, "<main>", accel );
  gtk_item_factory_create_items( canvas->factory, ARRAY_LENGTH( canvas_items ), 
				 canvas_items, canvas );
  if (show_hanging_option)
  {
    gtk_item_factory_create_items( canvas->factory, 
				   ARRAY_LENGTH( hanging_style_items ),  
				   hanging_style_items, canvas );
  }
  if (items != NULL)
    gtk_item_factory_create_items( canvas->factory, item_count, items, canvas );
  gtk_window_add_accel_group( GTK_WINDOW( canvas->window ), accel );
  menu_bar = gtk_item_factory_get_widget( canvas->factory, "<main>" );
  if (menu_bar == NULL) 
    complain( "Could not create menu bar." );
  gtk_box_pack_start( GTK_BOX( vbox ), menu_bar, FALSE, FALSE, 0 );
  
  /* Add a table with 2 rows and 2 columns to the vbox. */
  table = gtk_table_new( 2, 2, FALSE );
  gtk_box_pack_start_defaults( GTK_BOX( vbox ), table );
  
  /* Add a drawing area to the table in row 1, column 1. */
  canvas->draw_area = gtk_drawing_area_new();
  gtk_signal_connect( GTK_OBJECT( canvas->draw_area ), "expose_event",
		      GTK_SIGNAL_FUNC( expose_event ), canvas );
  gtk_signal_connect( GTK_OBJECT( canvas->draw_area ), "configure_event",
		      GTK_SIGNAL_FUNC( configure_event ), canvas );
  gtk_signal_connect( GTK_OBJECT( canvas->draw_area ), "scroll_event",
		      GTK_SIGNAL_FUNC( scroll_event ), canvas );
  gtk_widget_add_events( canvas->draw_area, GDK_SCROLL_MASK );
  if (my_mouse_event != NULL)
  {
    gtk_signal_connect( GTK_OBJECT( canvas->draw_area ), "button_press_event",
			GTK_SIGNAL_FUNC( button_press_event ), canvas );
    gtk_signal_connect( GTK_OBJECT( canvas->draw_area ), "motion_notify_event",
			GTK_SIGNAL_FUNC( motion_notify_event ), canvas );
    gtk_signal_connect( GTK_OBJECT( canvas->draw_area ), "enter_notify_event",
			GTK_SIGNAL_FUNC( enter_notify_event ), canvas );
    gtk_widget_add_events( canvas->draw_area, 
			   GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK
			   | GDK_ENTER_NOTIFY_MASK );
  }
  gtk_table_attach( GTK_TABLE( table ), canvas->draw_area, 0, 1, 0, 1,
		    GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0 );
  
  /* Add a vscrollbar to the table in row 1, column 2. */
  canvas->vadjust = GTK_ADJUSTMENT( gtk_adjustment_new( 0, 0, 0, 10, 0, 0 ) );
  gtk_signal_connect( GTK_OBJECT( canvas->vadjust ), "value_changed",
		      GTK_SIGNAL_FUNC( adjust_value_changed ), canvas );
  canvas->vscrollbar = gtk_vscrollbar_new( canvas->vadjust );
  gtk_widget_size_request( canvas->vscrollbar, &requisition );
  canvas->vscrollbar_width = requisition.width;
  gtk_table_attach( GTK_TABLE( table ), canvas->vscrollbar, 1, 2, 0, 1,
		    0, GTK_FILL, 0, 0 );

  /* Add a hscrollbar to the table in row 2, column 1. */
  canvas->hadjust = GTK_ADJUSTMENT( gtk_adjustment_new( 0, 0, 0, 10, 0, 0 ) );
  gtk_signal_connect( GTK_OBJECT( canvas->hadjust ), "value_changed",
		      GTK_SIGNAL_FUNC( adjust_value_changed ), canvas );
  canvas->hscrollbar = gtk_hscrollbar_new( canvas->hadjust );
  gtk_widget_size_request( canvas->hscrollbar, &requisition );
  canvas->hscrollbar_height = requisition.height;
  gtk_table_attach( GTK_TABLE( table ), canvas->hscrollbar, 0, 1, 1, 2,
		    GTK_FILL, 0, 0, 0 );
  
  /* Select some menu items. */
  gtk_menu_item_activate( 
    GTK_MENU_ITEM( 
      gtk_item_factory_get_item_by_action( canvas->factory, 
					   canvas->font_size ) ) );
  canvas->hanging_style = hanging_style;
  if (show_hanging_option && canvas->hanging_style)
  {
    canvas->hanging_style = TRUE;
    gtk_menu_item_activate(
      GTK_MENU_ITEM( 
	gtk_item_factory_get_item( canvas->factory, "/Style/Hanging" ) ) );
  }

  configure_canvas( canvas );
  gtk_widget_show_all( canvas->window );
  canvas->show_hscrollbar = canvas->show_vscrollbar = TRUE;

  return canvas;
}

/*---------------------------------------------------------------------------*/

void
activate_menu_item( canvas_t *canvas, string_t path )
/* Activate the menu item PATH in CANVAS to VALUE. */
{
  gtk_menu_item_activate( 
    GTK_MENU_ITEM( gtk_item_factory_get_item( canvas->factory, path ) ) );
}

/*---------------------------------------------------------------------------*/

pos_string_t *
new_pos_string( string_t string )
/* Create new POS_STRING_T with value STRING. */
{
  pos_string_i_t *pos_string_i;

  if (pango_context == NULL) 
    pango_context = gdk_pango_context_get();
  
  pos_string_i = new_mem( sizeof( pos_string_i_t ) );
  if (string != NULL)
  {
    pos_string_i->layout = pango_layout_new( pango_context );
    pango_layout_set_text( pos_string_i->layout, string, -1 );
  }
  return (pos_string_t *) pos_string_i;
}

/*---------------------------------------------------------------------------*/

void 
config_pos_string( pos_string_t *pos_string, canvas_t *canvas )
/* Compute size of POS_STRING in CANVAS. */
{
  pos_string_i_t *pos_string_i = (pos_string_i_t *) pos_string;

  if (pos_string_i != NULL && pos_string_i->layout != NULL)
  {
    config_layout( canvas, pos_string_i->layout, &pos_string_i->width,
		   &pos_string_i->height, &pos_string_i->ascent );
  }
}

/*---------------------------------------------------------------------------*/

void 
draw_pos_string( pos_string_t *pos_string, canvas_t *canvas )
/* Draw POS_STRING in CANVAS. */
{
  pos_string_i_t *pos_string_i = (pos_string_i_t *) pos_string;

  if (pos_string_i != NULL && pos_string_i->layout != NULL)
  {
    draw_layout( canvas, pos_string_i->layout, 
		 pos_string_i->x, pos_string_i->y );
  }
}

/*---------------------------------------------------------------------------*/

void 
free_pos_string( pos_string_t **pos_string_p )
/* Free memory allocated by "set_pos_string". */
{
  pos_string_i_t *pos_string_i = (pos_string_i_t *) *pos_string_p;

  if (pos_string_i != NULL && pos_string_i->layout != NULL)
    g_object_unref( pos_string_i->layout );
  free_mem( pos_string_p );
}

/*---------------------------------------------------------------------------*/

pos_value_t *
parse_pos_value( void )
{
  pos_value_i_t *pos_value_i;

  pos_value_i = new_mem( sizeof( pos_value_i_t ) );
  pos_value_i->value = parse_value();
  return (pos_value_t *) pos_value_i;
}

/*---------------------------------------------------------------------------*/

void 
config_pos_value( pos_value_t *pos_value, canvas_t *canvas )
/* Compute size of POS_VALUE in CANVAS. */
{
  pos_value_i_t *pos_value_i = (pos_value_i_t *) pos_value;

  config_value( canvas, pos_value_i->value );
  pos_value_i->width = pos_value_i->value->width;
  pos_value_i->height = pos_value_i->value->height;
  pos_value_i->ascent = pos_value_i->value->ascent;
}

/*---------------------------------------------------------------------------*/

void 
draw_pos_value( pos_value_t *pos_value, canvas_t *canvas )
/* Draw POS_VALUE in CANVAS. */
{
  pos_value_i_t *pos_value_i = (pos_value_i_t *) pos_value;

  draw_value( canvas, pos_value_i->value, pos_value_i->x, pos_value_i->y );
}

/*---------------------------------------------------------------------------*/

void
free_pos_value( pos_value_t **pos_value_p )
{
  pos_value_i_t *pos_value_i = (pos_value_i_t *) *pos_value_p;

  if (pos_value_i != NULL)
  {
    free_value( &pos_value_i->value );
    free_mem( pos_value_p );
  }
}

/* End of file. =============================================================*/