1 /** @file
2  * libLASi provides a C++ output stream interface for writing
3  * Postscript documents containing text strings in any of the world's
4  * scripts supported by Unicode 4.0 and Pango.
5  * Copyright (C) 2003, 2004, 2006 by Larry Siden.
6  * See README file in project root directory for copyright and contact info.
7  * See COPYING file in project root for terms of re-distribution.
8  */
9 
10 #include <ostream>
11 #include <stdexcept>
12 #include <pango/pango.h>
13 #include <ctype.h>
14 #include <algorithm>
15 #include <cmath>
16 #include <LASi.h>
17 #include "contextMgr.h"
18 #include "glyphMgr.h"
19 #include "util.h"
20 #include "memory.h"
21 #include "stringDimensions.h"
22 #include <iomanip>
23 
24 #include <stdlib.h>
25 
26 using namespace std;
27 using namespace LASi;
28 
29 // Convert a UTF-8 sequence of characters pointed to by ptr to a UCS4
30 // value pointed to by unichar.  (This function has been adapted from
31 // LGPL'd PLplot software).
32 
33 #define LASI_INVALID_UCS4 0xffffffff
34 
35 // This function returns NULL, if an invalid UTF-8 sequence of chars
36 // is detected.  In addition for this case the UCS4 *unichar value
37 // gets updated to the invalid UCS4 value of LASI_INVALID_UCS4.  If a valid
38 // UTF-8 sequence is detected this function returns a pointer to the
39 // memory location just after that sequence.  In addition for this
40 // case the UCS4 *unichar value gets updated to the value
41 // corresponding to the detected valid sequence.
42 
43 
44 static const char *
utf8_to_ucs4(const char * ptr,uint32_t * unichar)45 utf8_to_ucs4( const char *ptr, uint32_t *unichar )
46 {
47     char tmp;
48     int  isFirst = 1;
49     int  cnt     = 0;
50 
51     do
52     {
53         // Get next character in string
54         tmp = *ptr++;
55         if ( isFirst ) // First char in UTF8 sequence
56         {
57             isFirst = 0;
58             // Determine length of sequence
59             if ( (unsigned char) ( tmp & 0x80 ) == 0x00 ) // single char
60             {
61                 *unichar = (unsigned int) tmp & 0x7F;
62                 cnt      = 0;
63             }
64             else if ( (unsigned char) ( tmp & 0xE0 ) == 0xC0 ) // 2 chars
65             {
66                 *unichar = (unsigned int) tmp & 0x1F;
67                 cnt      = 1;
68             }
69             else if ( (unsigned char) ( tmp & 0xF0 ) == 0xE0 ) // 3 chars
70             {
71                 *unichar = (unsigned char) tmp & 0x0F;
72                 cnt      = 2;
73             }
74             else if ( (unsigned char) ( tmp & 0xF8 ) == 0xF0 ) // 4 chars
75             {
76                 *unichar = (unsigned char) tmp & 0x07;
77                 cnt      = 3;
78             }
79             else if ( (unsigned char) ( tmp & 0xFC ) == 0xF8 ) // 5 chars
80             {
81                 *unichar = (unsigned char) tmp & 0x03;
82                 cnt      = 4;
83             }
84             else if ( (unsigned char) ( tmp & 0xFE ) == 0xFC ) // 6 chars
85             {
86                 *unichar = (unsigned char) tmp & 0x01;
87                 cnt      = 5;
88             }
89             else  // Malformed
90             {
91                 *unichar = LASI_INVALID_UCS4;
92                 return NULL;
93             }
94         }
95         else   // Subsequent char in UTF8 sequence
96         {
97             if ( (unsigned char) ( tmp & 0xC0 ) == 0x80 )
98             {
99                 *unichar = ( *unichar << 6 ) | ( (unsigned int) tmp & 0x3F );
100                 cnt--;
101             }
102             else  // Malformed
103             {
104                 *unichar = LASI_INVALID_UCS4;
105                 return NULL;
106             }
107         }
108     } while ( cnt > 0 );
109     return ptr;
110 }
111 
nameof(const FT_Face & face,const FT_UInt glyph_index,uint32_t unichar)112 static string nameof(const FT_Face& face, const FT_UInt glyph_index, uint32_t unichar)
113 {
114   const int N = 256; // Length of buffer to hold glyph name
115   char glyph_name[N];
116   static unsigned int unique_int = 1;
117 
118   if (!FT_HAS_GLYPH_NAMES(face)){
119     // Since the glyph has no name entry for the font that is broken this way,
120     // must generate our own unique glyph name.
121     if(unichar != LASI_INVALID_UCS4)
122       // This version should always produce exactly the same unique glyph name
123       // for the same glyph no matter how many times nameof is called
124       // and regardless of the order of the glyphs that are identified.
125       snprintf(glyph_name, N, "LASi_glyph_U+%04X", unichar);
126     else
127       // This version produces a unique glyph_name with the
128       // drawbacks that (i) multiple names can become
129       // associated with the same glyph (if nameof is called
130       // twice for the same glyph which does happen, e.g., when
131       // bounding-box calculations are made)), and (ii) the derived
132       // name depends very much on the order of glyphs that are encountered
133       // in strings.  N.B. for this case we zero pad to 10 digits
134       // to assure the lexical order of glyph names is the same
135       // as the unique_int order.
136       snprintf(glyph_name, N, "LASi_glyph_%010u", unique_int++);
137   }else{
138     // Get the glyph name from the font when present:
139     FT_Get_Glyph_Name(face, glyph_index, glyph_name, N);
140   }
141   return string(glyph_name);
142 }
143 
GlyphId(FT_Face face,const FT_UInt index,uint32_t unichar)144 PostscriptDocument::GlyphId::GlyphId(FT_Face face, const FT_UInt index, uint32_t unichar)
145 {
146   const std::string glyphName(nameof(face, index, unichar));
147   const std::string faceName(face->family_name);
148   const std::string& variant(face->style_name);
149   ostringstream os;
150   os << glyphName << '-' << faceName << '-' << variant << '-' << index;
151   _str = os.str();
152   const int len = _str.size();
153 
154   //DEBUG:
155   //cerr << "PostscriptDocument::GlyphId::GlyphId(...) before _str=" << _str << endl;
156   // replace spaces with '-'
157   for (int i=0 ; i < len ; ++i) {
158     if (isspace(_str[i]))
159       _str.replace(i, 1, 1, '-');
160   }
161   //DEBUG:
162   //cerr << "PostscriptDocument::GlyphId::GlyphId(...) _str=" << _str << endl;
163 }
164 
PostscriptDocument()165 PostscriptDocument::PostscriptDocument()
166   : _pContextMgr(new ContextMgr()), _fontSize(10), _osBody(*this),
167 _osFooter(*this)
168 {}
169 
~PostscriptDocument()170 PostscriptDocument::~PostscriptDocument()
171 {
172   delete _pContextMgr;
173 }
174 
pangoContext() const175 inline PangoContext* PostscriptDocument::pangoContext() const
176 {
177   return static_cast<PangoContext*>(*_pContextMgr);
178 }
179 
setFont(const char * const family,LASi::FontStyle style,LASi::FontWeight weight,LASi::FontVariant variant,LASi::FontStretch stretch)180 void PostscriptDocument::setFont(
181     const char* const       family,
182     LASi::FontStyle   style,
183     LASi::FontWeight  weight,
184     LASi::FontVariant variant,
185     LASi::FontStretch stretch)
186 {
187 
188     //
189     // Style:
190     //
191     PangoStyle   _style;
192     switch(style){
193     case NORMAL_STYLE:
194        _style=PANGO_STYLE_NORMAL;
195        break;
196     case ITALIC:
197        _style=PANGO_STYLE_ITALIC;
198        break;
199     case OBLIQUE:
200        _style=PANGO_STYLE_OBLIQUE;
201        break;
202     default:
203        _style=PANGO_STYLE_NORMAL;
204        break;
205     }
206 
207     //
208     // Weight:
209     //
210     PangoWeight  _weight;
211     switch(weight){
212     case NORMAL_WEIGHT:
213        _weight=PANGO_WEIGHT_NORMAL;
214        break;
215     case BOLD:
216        _weight=PANGO_WEIGHT_BOLD;
217        break;
218     case ULTRALIGHT:
219        _weight=PANGO_WEIGHT_ULTRALIGHT;
220        break;
221     case LIGHT:
222        _weight=PANGO_WEIGHT_LIGHT;
223        break;
224     case ULTRABOLD:
225        _weight=PANGO_WEIGHT_ULTRABOLD;
226        break;
227     case HEAVY:
228        _weight=PANGO_WEIGHT_HEAVY;
229        break;
230     default:
231        _weight=PANGO_WEIGHT_NORMAL;
232        break;
233     }
234 
235     //
236     // Variant:
237     //
238     PangoVariant _variant;
239     switch(variant){
240     case NORMAL_VARIANT:
241       _variant=PANGO_VARIANT_NORMAL;
242       break;
243     case SMALLCAPS:
244       _variant=PANGO_VARIANT_SMALL_CAPS;
245       break;
246     default:
247        _variant=PANGO_VARIANT_NORMAL;
248        break;
249     }
250 
251     //
252     // Stretch:
253     //
254     PangoStretch _stretch;
255     switch(stretch){
256     case NORMAL_STRETCH:
257       _stretch=PANGO_STRETCH_NORMAL;
258       break;
259     case ULTRACONDENSED:
260       _stretch=PANGO_STRETCH_ULTRA_CONDENSED;
261       break;
262     case EXTRACONDENSED:
263       _stretch=PANGO_STRETCH_EXTRA_CONDENSED;
264       break;
265     case CONDENSED:
266       _stretch=PANGO_STRETCH_CONDENSED;
267       break;
268     case SEMICONDENSED:
269       _stretch=PANGO_STRETCH_SEMI_CONDENSED;
270       break;
271     case SEMIEXPANDED:
272       _stretch=PANGO_STRETCH_SEMI_EXPANDED;
273       break;
274     case EXPANDED:
275       _stretch=PANGO_STRETCH_EXPANDED;
276       break;
277     case EXTRAEXPANDED:
278       _stretch=PANGO_STRETCH_EXTRA_EXPANDED;
279       break;
280     case ULTRAEXPANDED:
281       _stretch=PANGO_STRETCH_ULTRA_EXPANDED;
282       break;
283     default:
284        _stretch=PANGO_STRETCH_NORMAL;
285        break;
286     }
287 
288     PangoFontDescription* font_description = pango_font_description_new();
289     pango_font_description_set_family (font_description, family);
290     pango_font_description_set_style (font_description, _style);
291     pango_font_description_set_weight (font_description, _weight);
292     pango_font_description_set_variant (font_description, _variant);
293     pango_font_description_set_stretch (font_description, _stretch);
294     pango_font_description_set_size(font_description, DRAWING_SCALE*PANGO_SCALE);
295     pango_context_set_font_description (static_cast<PangoContext*>(*_pContextMgr), font_description);
296 
297 }
298 
299 // Assume a given string s has been divided by pango_itemize into
300 // PangoItems where the substring glyphs associated with that item can be
301 // rendered with a constant face (defined as a freetype2 font of fixed
302 // family, slant, weight, and width), and pItem is a pointer to one
303 // of the PangoItems generated by pango_itemize.  The purpose of
304 // PangoItem_do is to process that PangoItem.
305 //
306 // N.B. Cannot be declared as a simple static function because GLYPH_FUNC is
307 // a private typedef for the PostscriptDocument class.
308 
PangoItem_do(const char * s,PangoItem * const pItem,const GLYPH_FUNC func,void * contextData,bool applyOffset)309 FT_Error PostscriptDocument::PangoItem_do(const char *s, PangoItem* const pItem, const GLYPH_FUNC func, void* contextData, bool applyOffset)
310 {
311     // next_glyph and unichar both used to help create a unique glyph id.
312     const char *next_glyph = s;
313     uint32_t unichar;
314 
315     PangoGlyphString* const pGlyphString = pango_glyph_string_new();
316     pango_shape(s, pItem->length, &pItem->analysis, pGlyphString);
317 
318     const FT_Face face = pango_ft2_font_get_face(pItem->analysis.font);
319     //DEBUG
320     //std::cerr << "face->family_name = " <<  face->family_name << std::endl;
321     if (!(face->face_flags & FT_FACE_FLAG_SCALABLE)){
322       //DEBUG
323       //std::cerr << "face->family_name = " <<  face->family_name << " does not contain outline glyphs" << std::endl;
324       return FT_Err_Invalid_Outline;
325     }
326     PangoGlyphInfo* const pGlyphInfo = pGlyphString->glyphs;
327     // For each glyph within a pango item.
328     for (int i=0 ; i < pGlyphString->num_glyphs ; ++i) {
329       const FT_UInt glyph_index = pGlyphInfo[i].glyph;    // get glyph index
330 
331       FT_Error error;
332       // This loop is initialized with next_glyph = s.  But if this is a subsequent iteration
333       // of this loop and utf8_to_ucs4 has failed previously in this loop, then the following
334       // values have already been set: next_glyph = NULL and unichar = LASI_INVALID_UCS4
335       // so there is no need to call utf8_to_ucs4 again.
336       if(next_glyph !=NULL)
337 	next_glyph = utf8_to_ucs4(next_glyph, &unichar);
338       PostscriptDocument::GlyphId glyphId(face, glyph_index, unichar); // construct GlyphId
339       FreetypeGlyphMgr& glyphMgr = _glyphMap[glyphId];  // access glyph from map
340 
341       if (0 == static_cast<FT_Glyph>(glyphMgr)) { // start of logic block if glyph is not in map
342         //
343         // access glyph from font face and put it in map
344         //
345         FT_Glyph glyph;
346         //DEBUG:
347         //std::cerr << "Glyph Index: " << std::hex << glyph_index << std::endl;
348 
349         error = FT_Load_Glyph(face,glyph_index,FT_LOAD_NO_BITMAP);
350         if(error){
351           //
352 	  //DEBUG:
353 	  //
354 	  //std::cerr << "PANGO is returning a glyph index of " << std::hex << glyph_index << std::endl;
355 	  //std::cerr << "but PANGO_GLYPH_UNKNOWN_FLAG is supposed to be: " << 0x10000000 << std::endl;
356 	  //std::cerr << "and PANGO_GLYPH_EMPTY        is supposed to be: " << 0x0FFFFFFF << std::endl;
357 
358 	  //std::cerr << "FT_Load_Glyph is returning error = " << std::hex << error << " for a glyph index of " << std::hex << glyph_index << " associated with the substring " << s << std::endl;
359 	  //std::cerr << "Attempting the glyph_index = 0 workaround" << std::endl;
360 	  //
361 	  // Substitute replacement glyph that often works:
362 	  // glyph_index of 0 is normally a signal that the glyph
363 	  // doesn't exist so most fonts are supposed to respond to
364 	  // glyph_index 0 with their default replacement glyph:
365 	  error = FT_Load_Glyph(face,0,FT_LOAD_NO_BITMAP);
366 	  // N.B. error would continue to be non zero for non-outline
367 	  // fonts, but because those are intercepted with the
368 	  // face->face_flags check above, error *should* always be
369 	  // zero for this case.  But check it anyhow "just in case"
370 	  // there are other reasons why glyph_index 0 does not work.
371 	  if(error){
372 	    //DEBUG:
373 	    //std::cerr << "The attempted glyph_index = 0 workaround did not work" << std::endl;
374 	    //std::cerr << "FT_Load_Glyph is returning error = " << std::hex << error << " for a glyph index of " << std::hex << glyph_index << std::endl;
375 	    // erase bad item from map
376 	    _glyphMap.erase(glyphId);
377 	    return error;
378 	  }
379 
380         }
381 	error = FT_Get_Glyph(face->glyph, &glyph);
382 	if(error)
383 	  {
384 	    //DEBUG:
385 	    //std::cerr << " FT_Get_Glyph is returning error = " << std::hex << error << " for a glyph index of " << std::hex << glyph_index << std::endl;
386 	    // erase bad item from map
387 	    _glyphMap.erase(glyphId);
388 	    return error;
389 	  }
390 	glyphMgr.assign(glyph);
391       } // end of logic block if glyph is not in map
392 
393       ostream* pos = 0;
394       double   x_rmove = 0, y_rmove = 0;
395       if (applyOffset) {
396         const PangoGlyphGeometry& geo = pGlyphInfo[i].geometry;
397         if (geo.x_offset != 0 || geo.y_offset != 0) {
398           const double scale = _fontSize / (DRAWING_SCALE * PANGO_SCALE);
399           pos = reinterpret_cast<ostream*>(contextData);
400           x_rmove = scale * geo.x_offset;
401           y_rmove = scale * geo.y_offset;
402           (*pos) << x_rmove << ' ' << y_rmove << " rmoveto" << endl;
403         }
404       }
405       //
406       // glyph is guaranteed to be in map:
407       // Call the function that operates on the glyph:
408       //
409       (this->*func)(*_glyphMap.find(glyphId), contextData);
410       if (applyOffset && pos) { // undo previous rmoveto
411          (*pos) << -x_rmove << ' ' << -y_rmove << " rmoveto" << endl;
412       }
413     }  // End of glyph loop
414     pango_glyph_string_free(pGlyphString);
415     return FT_Err_Ok;
416 }
417 
for_each_glyph_do(const string & s,const GLYPH_FUNC func,void * contextData,bool applyOffset)418 void PostscriptDocument::for_each_glyph_do(const string& s, const GLYPH_FUNC func, void* contextData,
419      bool applyOffset)
420 {
421 
422   // String containing a glyph to to be used for emergency
423   // replacements of glyphs that cause unfixable freetype errors.  In
424   // this case we choose a newline because we have evidence that also
425   // generates a freetype error, but one that is easy to fix up
426   // (internally in PangoItem_do with the glyph_index = 0 fixup) by
427   // replacing it with the default replacement glyph which is normally
428   // an empty box for most fonts.  This is convoluted logic depending
429   // on side interactions with PangoItem_do internal logic, but
430   // necessary because I otherwise don't know how to generate the
431   // desired default replacement glyph.
432   const std::string er_s("\n");
433   //DEBUG
434   //const std::string er_s("er string");
435 
436   // Number of Unicode characters (not bytes) in the PangoItem.
437   int nr_num_chars;
438 
439   std::string saved_truncated_s;
440   std::string truncated_s;
441   std::string working_s;
442   bool er_cycle = false;
443   bool some_error_occurred = false;
444   // Note use of this pattern is recommended to assure deep copy because
445   // some libraries still use copy-on-write despite this being forbidden
446   // for std::string copies by modern C++ standard.
447   saved_truncated_s = s.c_str();
448 
449   while(er_cycle || saved_truncated_s.length() > 0)
450   {
451     if(er_cycle){
452       // Determine working_s that duplicates er_s nr_num_chars times,
453       // where nr_num_chars is the number of unicode glyphs to be replaced.
454       working_s = "";
455       for (int i = 0; i < nr_num_chars; i++)
456 	working_s.append(er_s);
457     }
458     else{
459       working_s = saved_truncated_s.c_str();
460     }
461 
462   PangoAttrList* const attrList = pango_attr_list_new(); // needed only for call to pango_itemize()
463 
464   GList* glItems = pango_itemize(
465       pangoContext(),
466       working_s.c_str(),
467       0, working_s.length(),
468       attrList,
469       (PangoAttrIterator *) 0);
470   pango_attr_list_unref(attrList);
471 
472   //DEBUG
473   //std::cerr << "working_s.c_str() = " << working_s.c_str() << std::endl;
474   for (; glItems ; glItems = g_list_next(glItems)) {
475     // For each pango item (string of UTF-8 characters in working_s with constant font attributes):
476     PangoItem* const pItem = reinterpret_cast<PangoItem*>(glItems->data);
477     // Truncated_s starts at one glyph beyond the first in the PangoItem that produced an error.
478     // Save it with deep copy before working_s gets trashed.
479     truncated_s = (working_s.c_str() + pItem->offset + pItem->length);
480     //DEBUG:
481     //std::cerr << "truncated_s.c_str() = " << truncated_s.c_str() << std::endl;
482     FT_Error error = PangoItem_do(working_s.c_str() + pItem->offset, pItem, func, contextData, applyOffset);
483     if(error)
484       {
485 	//std::cerr << "FT_Error = " << std::hex << error << " generated by one of the PangoItems with font family name \"" << pango_ft2_font_get_face(pItem->analysis.font)->family_name << "\" corresponding to the overall string \"" << s.c_str() << "\"" << std::endl;
486 	//std::cerr << "Substituting blanks for the glyphs in this string that cannot be rendered by libLASi" << std::endl;
487 	some_error_occurred = true;
488 	// If error on er_cycle, that means that not even er_s works which is bad news indeed!
489 	if(er_cycle)
490 	  evalReturnCode(error, "PangoItem_do");
491 
492 	// Save truncated_s with deep copy for after the er_cycle which will clobber truncated_s.
493 	saved_truncated_s = truncated_s.c_str();
494 	// Run an er_cycle to replace one glyph before continuing with saved_truncated_s.
495 	er_cycle = true;
496 	nr_num_chars = pItem->num_chars;
497 	pango_item_free(pItem);
498 	break;
499       }
500     pango_item_free(pItem);
501   }
502   g_list_free(glItems);
503   if(some_error_occurred)
504     {
505       some_error_occurred = false;
506       // If some_error_occurred, then er_cycle is true and you should process that case.
507       continue;
508     }
509   if(er_cycle)
510     // Got through er_s without some_error_occurred.  Now proceed with processing saved_truncated_s.
511     er_cycle = false;
512   else
513     // Got through saved_truncated_s without issues.  DONE!
514     break;
515   }
516 }
517 
518 /** Add the next glyphs dimensions to the bounding box (contextData).
519   * If the advance is in the x direction (the usual case),
520   * the box grows in the x direction, yMax becomes the height
521   * of the tallest character, and yMin the descent of the most
522   * descending character.
523   *
524   * @param mapval std::pair<GlyphId, FreetypeGlyphMgr>
525   * @param contextData std::pair<double, double>, the x and y dimensions
526   */
accrue_dimensions(const PostscriptDocument::GlyphMap::value_type & mapval,void * contextData)527 void PostscriptDocument::accrue_dimensions(
528     const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
529 {
530   // const GlyphId& gid = static_cast<GlyphId>(mapval.first);
531   const FreetypeGlyphMgr& glyphMgr = static_cast<FreetypeGlyphMgr>(mapval.second);
532   const FT_Glyph& glyph = static_cast<FT_Glyph>(glyphMgr);
533   //
534   //
535   //
536   //const double y_adv = std::abs(glyph->advance.y / (double) 0x10000);
537   //
538   const double x_adv = std::abs(glyph->advance.x / (double) 0x10000); // convert from 16.16 format
539 
540   //
541   //
542   //
543   FT_BBox bbox;
544   FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_UNSCALED, &bbox);
545 
546   //
547   // Get the mins and maxes, converting from 26.6 format:
548   //
549   // const double x_min = bbox.xMin/64.0;
550   // const double x_max = bbox.xMax/64.0;
551   //
552   const double y_min = bbox.yMin/64.0;
553   const double y_max = bbox.yMax/64.0;
554 
555   StringDimensions *SD = reinterpret_cast<StringDimensions *>(contextData);
556 
557   SD->accrueXAdvance(x_adv);
558   SD->setYMin(y_min);
559   SD->setYMax(y_max);
560 
561 }
562 
563 /** Insert a Postscript glyph_routine call into output stream (contextData).
564   */
invoke_glyph_routine(const PostscriptDocument::GlyphMap::value_type & mapval,void * contextData)565 void PostscriptDocument::invoke_glyph_routine(
566     const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
567 {
568   const GlyphId& gid = static_cast<GlyphId>(mapval.first);
569   ostream* pos = reinterpret_cast<ostream*>(contextData);
570   static_cast<ostream&>(*pos) << this->getFontSize() << " " << gid.str() << endl;
571 }
572 
573 /** Returns the line spacing, x-advance, y-minimum and y-maximum
574   * based on the current font face and font size.  A bounding box
575   * around the text string, s, can be constructed from the xAdvance,
576   * yMinimum, and yMaximum.  yMinimum tells you the descent from the
577   * baseline. yMaximum tells you the ascent from the baseline.
578   * The line spacing provides an inter-line spacing for multi-line
579   * text layout.
580   *
581   * This version accepts a const C-style character string.
582   *
583   */
get_dimensions(const char * s,double * lineSpacing,double * xAdvance,double * yMin,double * yMax)584 void PostscriptDocument::get_dimensions(const char* s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax)
585 {
586 
587   StringDimensions SD;
588   for_each_glyph_do(s, &PostscriptDocument::accrue_dimensions,&SD);
589 
590   const double scale = _fontSize / DRAWING_SCALE;
591   //
592   // We always want to retrieve at least the lineSpacing:
593   //
594   *lineSpacing = SD.getLineSpacing() * scale;
595   //
596   // But xAdvance, yMin, and yMax are only necessary to retrieve
597   // if we want to have the bounding box, so we allow default
598   // parameters set to NULL:
599   //
600   if(xAdvance!=NULL)  *xAdvance = SD.getXAdvance() * scale;
601   if(yMin    !=NULL)  *yMin     = SD.getYMin()     * scale;
602   if(yMax    !=NULL)  *yMax     = SD.getYMax()     * scale;
603 
604 }
605 
606 /** Returns the line spacing, x-advance, y-minimum and y-maximum
607   * based on the current font face and font size.  A bounding box
608   * around the text string, s, can be constructed from the xAdvance,
609   * yMinimum, and yMaximum.  yMinimum tells you the descent from the
610   * baseline. yMaximum tells you the ascent from the baseline.
611   * The line spacing provides an inter-line spacing for multi-line
612   * text layout.
613   *
614   * This version accepts an STL standard string class string.
615   *
616   */
get_dimensions(std::string s,double * lineSpacing,double * xAdvance,double * yMin,double * yMax)617 void PostscriptDocument::get_dimensions(std::string s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax){
618 
619 	get_dimensions(s.c_str(),lineSpacing,xAdvance,yMin,yMax);
620 
621 }
622 
623 
apply(oPostscriptStream & os) const624 void show::apply(oPostscriptStream& os) const
625 {
626   //DEBUG
627   //cerr << "show::apply(os): _str = " << _str << endl;
628   PostscriptDocument& doc = os.doc();
629   doc.for_each_glyph_do(_str, &PostscriptDocument::invoke_glyph_routine, &os, true);
630 }
631 
632 const unsigned int PostscriptDocument::DRAWING_SCALE = PANGO_SCALE;
633 
634 /**
635   * Writes out the document.
636   *
637   */
write(std::ostream & os,double llx,double lly,double urx,double ury)638 void PostscriptDocument::write(std::ostream& os, double llx, double lly, double urx, double ury)
639 {
640   //
641   // Output document header:
642   //
643 
644   //
645   // If any of the bounding box parameters are non-zero,
646   // then write out an EPS document with a bounding box:
647   //
648   if(llx || lly || urx || ury){
649     //
650     // Encapsulated PostScript Header:
651     //
652     os << "%!PS-Adobe-3.0 EPSF-3.0" << endl;
653     os << "%%BoundingBox: " << int(llx) << " " << int(lly) << " " << int(ceil(urx)) << " " << int(ceil(ury)) << endl;
654     os << "%%HiResBoundingBox: " << std::setprecision(9) << llx << " " << lly << " " << urx << " " << ury << endl;
655   }else{
656     //
657     // Normal Postscript header for print media:
658     //
659     os << "%!PS-Adobe-3.0" << endl;
660   }
661   //
662   // Rest of header:
663   //
664   os << "%%Creator: libLASi C++ Stream Interface for Postscript. LASi is hosted on http://www.unifont.org." << endl;
665   os << "%%Copyright: (c) 2003, 2004, 2006 by Larry Siden.  All Rights Reserved. Released under the LGPL." << endl;
666   os << endl;
667   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
668   os << "%" << endl;
669   os << "% START Document Header:" << endl;
670   os << "%" << endl;
671   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
672 
673   //
674   // If a "%!PS" is found at the beginning of the user's header,
675   // warn them that LASi already provides that:
676   //
677   if( _osHeader.str().find("%!PS")!=string::npos) cerr << "WARNING: LASi automatically provides \"%!PS-Adobe-3.0\" at the start of the document!" << endl;
678 
679   //
680   // Make sure there is a "%%BeginProlog" to complement the "%%EndProlog" that
681   // LASi puts after the end of the glyph routines:
682   //
683   if( _osHeader.str().find("%%BeginProlog")==string::npos) os << "%%BeginProlog" << endl;
684 
685   os << _osHeader.str() << endl;
686 
687   //
688   // Write out the glyph routines:
689   //
690 
691   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
692   os << "%" << endl;
693   os << "% START LASi Glyph Routines:" << endl;
694   os << "%" << endl;
695   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
696   os << "%%BeginResource: GlyphRoutines" << endl;
697 
698   for_each(_glyphMap.begin(), _glyphMap.end(),
699       write_glyph_routine_to_stream(os, static_cast<PangoContext*>(*_pContextMgr)));
700 
701   os << "%%EndResource" << endl;
702   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
703   os << "%" << endl;
704   os << "% END LASi Glyph Routines:"   << endl;
705   os << "%" << endl;
706   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
707   os << "%%EndProlog" << endl;
708 
709   os << endl;
710 
711   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
712   os << "%" << endl;
713   os << "% START Document Body:"       << endl;
714   os << "%" << endl;
715   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
716 
717   os << _osBody.str() << endl;
718 
719   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
720   os << "%" << endl;
721   os << "% END Document Body:"         << endl;
722   os << "%" << endl;
723   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
724   os << "%" << endl;
725   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
726   os << "%" << endl;
727   os << "% START Document Footer:"     << endl;
728   os << "%" << endl;
729   os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
730   os << "%%Trailer" << endl;
731 
732   os << _osFooter.str() << endl;
733 
734   os << "%%EOF" << endl;
735 
736 }
737