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