1 /*
2 Large portions of this file were adapted from the SciTE text
3 editor source code.
4 
5 SciTE is copyright 1998-2010 by Neil Hodgson <neilh@scintilla.org>
6 and is released under the following license:
7 
8 All Rights Reserved
9 
10 Permission to use, copy, modify, and distribute this software and its
11 documentation for any purpose and without fee is hereby granted,
12 provided that the above copyright notice appear in all copies and that
13 both that copyright notice and this permission notice appear in
14 supporting documentation.
15 
16 NEIL HODGSON DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
17 SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18 AND FITNESS, IN NO EVENT SHALL NEIL HODGSON BE LIABLE FOR ANY
19 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
21 WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22 TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
23 OR PERFORMANCE OF THIS SOFTWARE.
24 */
25 
26 
27 #include <stdio.h>
28 #include <string.h>
29 
30 #include <fx.h>
31 #include <FX88591Codec.h>
32 
33 #include "appname.h"
34 #include "scidoc.h"
35 #include "prefs.h"
36 #include "lang.h"
37 #include "export.h"
38 
39 #define CharAt(where) sci->sendMessage(SCI_GETCHARAT, where, 0)
40 
41 //---------- Save to HTML ----------
42 
SaveToHTML(SciDoc * sci,FILE * fp)43 void SaveToHTML(SciDoc*sci, FILE *fp )
44 {
45   sci->sendMessage(SCI_COLOURISE, 0, -1);
46   int tabSize = 4;
47   int lengthDoc = sci->GetTextLength();
48 
49 
50   bool styleIsUsed[STYLE_MAX + 1];
51   bool onlyStylesUsed=true;
52   if (onlyStylesUsed) {
53     int i;
54     for (i = 0; i <= STYLE_MAX; i++) {
55       styleIsUsed[i] = false;
56     }
57     // check the used styles
58     for (i = 0; i < lengthDoc; i++) {
59       styleIsUsed[sci->sendMessage(SCI_GETSTYLEAT,i,0) & 0x7F] = true;
60     }
61   } else {
62     for (int i = 0; i <= STYLE_MAX; i++) {
63       styleIsUsed[i] = true;
64     }
65   }
66   styleIsUsed[STYLE_DEFAULT] = true;
67 
68   fputs( "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \
69     \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n", fp);
70 
71   fputs("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n", fp);
72   fputs("<head>\n", fp);
73   fprintf(fp, "<title>%s</title>\n", FXPath::name(sci->Filename()).text());
74   // Probably not used by robots, but making a little advertisement for those looking
75   // at the source code doesn't hurt...
76   fputs("<meta name=\"Generator\" content=\"" APP_NAME " HTML export\" />\n", fp);
77   if ( sci->sendMessage(SCI_GETCODEPAGE,0,0) == SC_CP_UTF8) {
78     fputs("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n", fp);
79   }
80   fputs("<style type=\"text/css\">\n", fp);
81 
82   char* bgColour=Settings::globalStyle()->bg;
83   LangStyle* ls = sci->getLanguage();
84 
85   for (int istyle = 0; istyle <= STYLE_MAX; istyle++) {
86     if ((istyle > STYLE_DEFAULT) && (istyle <= STYLE_LASTPREDEFINED)) { continue; }
87     if (styleIsUsed[istyle]) {
88       StyleDef*sd=GetStyleFromId(ls?ls->styles:Settings::globalStyle(), istyle);
89      if (!sd) { sd=Settings::globalStyle(); }
90       if (istyle == STYLE_DEFAULT) {
91         fprintf(fp, "span {\n");
92       } else {
93         fprintf(fp, ".S%0d {\n", istyle);
94       }
95       if (sd->style & Italic) { fprintf(fp, "  font-style: italic;\n"); }
96       if (sd->style & Bold) { fprintf(fp, "  font-weight: bold;\n"); }
97       if (sd->fg[0]) {
98         fprintf(fp, "  color: %s;\n", sd->fg);
99       } else if (istyle == STYLE_DEFAULT) {
100         fprintf(fp, "  color: #000000;\n");
101       }
102       if (sd->bg[0]) {
103         if (istyle != STYLE_DEFAULT && (strcmp(bgColour,sd->bg)!=0)) {
104           fprintf(fp, "  background: %s;\n", sd->bg);
105           fprintf(fp, "  text-decoration: inherit;\n");
106         }
107       }
108       fprintf(fp, "}\n");
109     }
110   }
111   fputs("</style>\n", fp);
112   fputs("</head>\n", fp);
113   if (bgColour[0]) {
114     fprintf(fp, "<body bgcolor=\"%s\">\n", bgColour);
115   } else {
116     fputs("<body>\n", fp);
117   }
118 
119   int styleCurrent = sci->sendMessage(SCI_GETSTYLEAT,0,0);
120   bool inStyleSpan = false;
121   bool inFoldSpan = false;
122   // Global span for default attributes
123   fputs("<pre>", fp);
124 
125   if (styleIsUsed[styleCurrent]) {
126     fprintf(fp, "<span class=\"S%0d\">", styleCurrent);
127     inStyleSpan = true;
128   }
129   // Else, this style has no definition (beside default one):
130   // no span for it, except the global one
131 
132   int column = 0;
133   for (int i = 0; i < lengthDoc; i++) {
134     char ch = sci->sendMessage(SCI_GETCHARAT,i,0);
135     int style = sci->sendMessage(SCI_GETSTYLEAT,i,0);
136 
137     if (style != styleCurrent) {
138       if (inStyleSpan) {
139         fputs("</span>", fp);
140         inStyleSpan = false;
141       }
142       if (ch != '\r' && ch != '\n') {  // No need of a span for the EOL
143         if (styleIsUsed[style]) {
144           fprintf(fp, "<span class=\"S%0d\">", style);
145           inStyleSpan = true;
146         }
147         styleCurrent = style;
148       }
149     }
150     if (ch == ' ') {
151     fputc(' ', fp);
152     column++;
153 
154     } else if (ch == '\t') {
155       int ts = tabSize - (column % tabSize);
156       for (int itab = 0; itab < ts; itab++) { fputc(' ', fp);  }
157       column += ts;
158     } else if (ch == '\r' || ch == '\n') {
159       if (inStyleSpan) {
160         fputs("</span>", fp);
161         inStyleSpan = false;
162       }
163       if (inFoldSpan) {
164         fputs("</span>", fp);
165         inFoldSpan = false;
166       }
167       if ((ch == '\r') && (sci->sendMessage(SCI_GETCHARAT, i + 1, 0) == '\n')) {
168         i++;  // CR+LF line ending, skip the "extra" EOL char
169       }
170       column = 0;
171       styleCurrent = sci->sendMessage(SCI_GETSTYLEAT, i + 1, 0);
172       fputc('\n', fp);
173       if (styleIsUsed[styleCurrent] && (CharAt(i+1) != '\r') && (CharAt(i+1) != '\n')) {
174         // We know it's the correct next style,
175         // but no (empty) span for an empty line
176         fprintf(fp, "<span class=\"S%0d\">", styleCurrent);
177         inStyleSpan = true;
178       }
179     } else {
180       switch (ch) {
181       case '<':
182         fputs("&lt;", fp);
183         break;
184       case '>':
185         fputs("&gt;", fp);
186         break;
187       case '&':
188         fputs("&amp;", fp);
189         break;
190       default:
191         fputc(ch, fp);
192       }
193       column++;
194     }
195   }
196   if (inStyleSpan) {
197     fputs("</span>", fp);
198   }
199   fputs("</pre>", fp);
200   fputs("\n</body>\n</html>\n", fp);
201 }
202 
203 class Point {
204 public:
205   int x;
206   int y;
Point(int x_=0,int y_=0)207   explicit Point(int x_=0, int y_=0) : x(x_), y(y_) {}
208   static Point FromLong(long lpoint);
209 };
210 
211 class PRectangle {
212 public:
213   int left;
214   int top;
215   int right;
216   int bottom;
PRectangle(int left_=0,int top_=0,int right_=0,int bottom_=0)217   PRectangle(int left_=0, int top_=0, int right_=0, int bottom_ = 0) :
218     left(left_), top(top_), right(right_), bottom(bottom_) {
219   }
operator ==(PRectangle & rc)220   bool operator==(PRectangle &rc) {
221     return (rc.left == left) && (rc.right == right) &&
222       (rc.top == top) && (rc.bottom == bottom);
223   }
Contains(Point pt)224   bool Contains(Point pt) {
225     return (pt.x >= left) && (pt.x <= right) &&
226       (pt.y >= top) && (pt.y <= bottom);
227   }
Contains(PRectangle rc)228   bool Contains(PRectangle rc) {
229     return (rc.left >= left) && (rc.right <= right) &&
230       (rc.top >= top) && (rc.bottom <= bottom);
231   }
Intersects(PRectangle other)232   bool Intersects(PRectangle other) {
233     return (right > other.left) && (left < other.right) &&
234       (bottom > other.top) && (top < other.bottom);
235   }
Move(int xDelta,int yDelta)236   void Move(int xDelta, int yDelta) {
237     left += xDelta;
238     top += yDelta;
239     right += xDelta;
240     bottom += yDelta;
241   }
Width()242   int Width() { return right - left; }
Height()243   int Height() { return bottom - top; }
Empty()244   bool Empty() {
245     return (Height() <= 0) || (Width() <= 0);
246   }
247 };
248 
249 
IntFromHexDigit(int ch)250 static int IntFromHexDigit(int ch) {
251   if ((ch >= '0') && (ch <= '9')) {
252     return ch - '0';
253   } else if (ch >= 'A' && ch <= 'F') {
254     return ch - 'A' + 10;
255   } else if (ch >= 'a' && ch <= 'f') {
256     return ch - 'a' + 10;
257   } else {
258     return 0;
259   }
260 }
261 
IntFromHexByte(const char * hexByte)262 int IntFromHexByte(const char *hexByte) {
263   return IntFromHexDigit(hexByte[0]) * 16 + IntFromHexDigit(hexByte[1]);
264 }
265 
266 //---------- Save to PDF ----------
267 
268 /*
269   PDF Exporter. Status: Beta
270   Contributed by Ahmad M. Zawawi <zeus_go64@hotmail.com>
271   Modifications by Darren Schroeder Feb 22, 2003; Philippe Lhoste 2003-10
272   Overhauled by Kein-Hong Man 2003-11
273 
274   This exporter is meant to be small and simple; users are expected to
275   use other methods for heavy-duty formatting. PDF elements marked with
276   "PDF1.4Ref" states where in the PDF 1.4 Reference Spec (the PDF file of
277   which is freely available from Adobe) the particular element can be found.
278 
279   Possible TODOs that will probably not be implemented: full styling,
280   optimization, font substitution, compression, character set encoding.
281 */
282 #define PDF_TAB_DEFAULT    8
283 #define PDF_FONT_DEFAULT  1  // Helvetica
284 #define PDF_FONTSIZE_DEFAULT  10
285 #define PDF_SPACING_DEFAULT  1.2
286 #define PDF_HEIGHT_DEFAULT  792  // Letter
287 #define PDF_WIDTH_DEFAULT  612
288 #define PDF_MARGIN_DEFAULT  72  // 1.0"
289 #define PDF_ENCODING    "WinAnsiEncoding"
290 
291 struct PDFStyle {
292   char fore[24];
293   int font;
294 };
295 
296 static const char *PDFfontNames[] = {
297             "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique",
298             "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique",
299             "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic"
300         };
301 
302 // ascender, descender aligns font origin point with page
303 static short PDFfontAscenders[] =  { 629, 718, 699 };
304 static short PDFfontDescenders[] = { 157, 207, 217 };
305 static short PDFfontWidths[] =     { 600,   0,   0 };
306 
getPDFRGB(char * pdfcolour,const char * stylecolour)307 static inline void getPDFRGB(char* pdfcolour, const char* stylecolour) {
308   // grab colour components (max string length produced = 18)
309   for (int i = 1; i < 6; i += 2) {
310     char val[20];
311     // 3 decimal places for enough dynamic range
312     int c = (IntFromHexByte(stylecolour + i) * 1000 + 127) / 255;
313     if (c == 0 || c == 1000) {  // optimise
314       sprintf(val, "%d ", c / 1000);
315     } else {
316       sprintf(val, "0.%03d ", c);
317     }
318     strcat(pdfcolour, val);
319   }
320 }
321 
SaveToPDF(SciDoc * sci,FILE * fp)322 void SaveToPDF(SciDoc*sci,  FILE *fp)
323 {
324   // This class conveniently handles the tracking of PDF objects
325   // so that the cross-reference table can be built (PDF1.4Ref(p39))
326   // All writes to fp passes through a PDFObjectTracker object.
327   class PDFObjectTracker {
328   private:
329     FILE *fp;
330     int *offsetList, tableSize;
331   public:
332     int index;
333     PDFObjectTracker(FILE *fp_) {
334       fp = fp_;
335       tableSize = 100;
336       offsetList = new int[tableSize];
337       index = 1;
338     }
339     ~PDFObjectTracker() {
340       delete []offsetList;
341     }
342     void write(const char *objectData) {
343       unsigned int length = strlen(objectData);
344       // note binary write used, open with "wb"
345       fwrite(objectData, sizeof(char), length, fp);
346     }
347     void write(int objectData) {
348       char val[20];
349       sprintf(val, "%d", objectData);
350       write(val);
351     }
352     // returns object number assigned to the supplied data
353     int add(const char *objectData) {
354       // resize xref offset table if too small
355       if (index > tableSize) {
356         int newSize = tableSize * 2;
357         int *newList = new int[newSize];
358         for (int i = 0; i < tableSize; i++) {
359           newList[i] = offsetList[i];
360         }
361         delete []offsetList;
362         offsetList = newList;
363         tableSize = newSize;
364       }
365       // save offset, then format and write object
366       offsetList[index - 1] = ftell(fp);
367       write(index);
368       write(" 0 obj\n");
369       write(objectData);
370       write("endobj\n");
371       return index++;
372     }
373     // builds xref table, returns file offset of xref table
374     int xref() {
375       char val[32];
376       // xref start index and number of entries
377       int xrefStart = ftell(fp);
378       write("xref\n0 ");
379       write(index);
380       // a xref entry *must* be 20 bytes long (PDF1.4Ref(p64))
381       // so extra space added; also the first entry is special
382       write("\n0000000000 65535 f \n");
383       for (int i = 0; i < index - 1; i++) {
384         sprintf(val, "%010d 00000 n \n", offsetList[i]);
385         write(val);
386       }
387       return xrefStart;
388     }
389   };
390 
391   // Object to manage line and page rendering. Apart from startPDF, endPDF
392   // everything goes in via add() and nextLine() so that line formatting
393   // and pagination can be done properly.
394   class PDFRender {
395   private:
396     bool pageStarted;
397     bool firstLine;
398     int pageCount;
399     int pageContentStart;
400     double xPos, yPos;  // position tracking for line wrapping
401     FXString pageData;  // holds PDF stream contents
402     FXString segment;  // character data
403     char *segStyle;    // style of segment
404     bool justWhiteSpace;
405     int styleCurrent, stylePrev;
406     double leading;
407     char *buffer;
408   public:
409     PDFObjectTracker *oT;
410     PDFStyle *style;
411     int fontSize;    // properties supplied by user
412     int fontSet;
413     int pageWidth, pageHeight;
414     PRectangle pageMargin;
415     //
416     PDFRender() {
417       pageStarted = false;
418       pageCount = 0;
419       style = NULL;
420       buffer = new char[250];
421       segStyle = new char[100];
422     }
423     ~PDFRender() {
424       if (style) { delete []style; }
425       delete []buffer;
426       delete []segStyle;
427     }
428     //
429     double fontToPoints(int thousandths) {
430       return (double)fontSize * thousandths / 1000.0;
431     }
432     void setStyle(char *buff, int style_) {
433       int styleNext = style_;
434       if (style_ == -1) { styleNext = styleCurrent; }
435       *buff = '\0';
436       if (styleNext != styleCurrent || style_ == -1) {
437         if (style[styleCurrent].font != style[styleNext].font
438                 || style_ == -1) {
439           sprintf(buff, "/F%d %d Tf ",
440                   style[styleNext].font + 1, fontSize);
441         }
442         if (strcmp(style[styleCurrent].fore, style[styleNext].fore) != 0
443                 || style_ == -1) {
444           strcat(buff, style[styleNext].fore);
445           strcat(buff, "rg ");
446         }
447       }
448     }
449     //
450     void startPDF() {
451       if (fontSize <= 0) {
452         fontSize = PDF_FONTSIZE_DEFAULT;
453       }
454       // leading is the term for distance between lines
455       leading = fontSize * PDF_SPACING_DEFAULT;
456       // sanity check for page size and margins
457       int pageWidthMin = (int)leading + pageMargin.left + pageMargin.right;
458       if (pageWidth < pageWidthMin) {
459         pageWidth = pageWidthMin;
460       }
461       int pageHeightMin = (int)leading + pageMargin.top + pageMargin.bottom;
462       if (pageHeight < pageHeightMin) {
463         pageHeight = pageHeightMin;
464       }
465       // start to write PDF file here (PDF1.4Ref(p63))
466       // ASCII>127 characters to indicate binary-possible stream
467       oT->write("%PDF-1.3\n%\267\276\255\252\n");
468       styleCurrent = STYLE_DEFAULT;
469 
470       // build objects for font resources; note that font objects are
471       // *expected* to start from index 1 since they are the first objects
472       // to be inserted (PDF1.4Ref(p317))
473       for (int i = 0; i < 4; i++) {
474         sprintf(buffer, "<</Type/Font/Subtype/Type1"
475                 "/Name/F%d/BaseFont/%s/Encoding/"
476                 PDF_ENCODING
477                 ">>\n", i + 1,
478                 PDFfontNames[fontSet * 4 + i]);
479         oT->add(buffer);
480       }
481       pageContentStart = oT->index;
482     }
483     void endPDF() {
484       if (pageStarted) {  // flush buffers
485         endPage();
486       }
487       // refer to all used or unused fonts for simplicity
488       int resourceRef = oT->add(
489                   "<</ProcSet[/PDF/Text]\n"
490                   "/Font<</F1 1 0 R/F2 2 0 R/F3 3 0 R"
491                   "/F4 4 0 R>> >>\n");
492       // create all the page objects (PDF1.4Ref(p88))
493       // forward reference pages object; calculate its object number
494       int pageObjectStart = oT->index;
495       int pagesRef = pageObjectStart + pageCount;
496       for (int i = 0; i < pageCount; i++) {
497         sprintf(buffer, "<</Type/Page/Parent %d 0 R\n"
498                 "/MediaBox[ 0 0 %d %d"
499                 "]\n/Contents %d 0 R\n"
500                 "/Resources %d 0 R\n>>\n",
501                 pagesRef, pageWidth, pageHeight,
502                 pageContentStart + i, resourceRef);
503         oT->add(buffer);
504       }
505       // create page tree object (PDF1.4Ref(p86))
506       pageData = "<</Type/Pages/Kids[\n";
507       for (int j = 0; j < pageCount; j++) {
508         sprintf(buffer, "%d 0 R\n", pageObjectStart + j);
509         pageData += buffer;
510       }
511       sprintf(buffer, "]/Count %d\n>>\n", pageCount);
512       pageData += buffer;
513       oT->add(pageData.text());
514       // create catalog object (PDF1.4Ref(p83))
515       sprintf(buffer, "<</Type/Catalog/Pages %d 0 R >>\n", pagesRef);
516       int catalogRef = oT->add(buffer);
517       // append the cross reference table (PDF1.4Ref(p64))
518       int xref = oT->xref();
519       // end the file with the trailer (PDF1.4Ref(p67))
520       sprintf(buffer, "trailer\n<< /Size %d /Root %d 0 R\n>>"
521               "\nstartxref\n%d\n%%%%EOF\n",
522               oT->index, catalogRef, xref);
523       oT->write(buffer);
524     }
525     void add(char ch, int style_) {
526       if (!pageStarted) {
527         startPage();
528       }
529       // get glyph width (TODO future non-monospace handling)
530       double glyphWidth = fontToPoints(PDFfontWidths[fontSet]);
531       xPos += glyphWidth;
532       // if cannot fit into a line, flush, wrap to next line
533       if (xPos > pageWidth - pageMargin.right) {
534         nextLine();
535         xPos += glyphWidth;
536       }
537       // if different style, then change to style
538       if (style_ != styleCurrent) {
539         flushSegment();
540         // output code (if needed) for new style
541         setStyle(segStyle, style_);
542         stylePrev = styleCurrent;
543         styleCurrent = style_;
544       }
545       // escape these characters
546       if (ch == ')' || ch == '(' || ch == '\\') {
547         segment += '\\';
548       }
549       if (ch != ' ') { justWhiteSpace = false; }
550       segment += ch;  // add to segment data
551     }
552     void flushSegment() {
553       if (segment.length() > 0) {
554         if (justWhiteSpace) {  // optimise
555           styleCurrent = stylePrev;
556         } else {
557           pageData += segStyle;
558         }
559         pageData += "(";
560         pageData += segment;
561         pageData += ")Tj\n";
562       }
563       segment.clear();
564       *segStyle = '\0';
565       justWhiteSpace = true;
566     }
567     void startPage() {
568       pageStarted = true;
569       firstLine = true;
570       pageCount++;
571       double fontAscender = fontToPoints(PDFfontAscenders[fontSet]);
572       yPos = pageHeight - pageMargin.top - fontAscender;
573       // start a new page
574       sprintf(buffer, "BT 1 0 0 1 %d %d Tm\n",
575               pageMargin.left, (int)yPos);
576       // force setting of initial font, colour
577       setStyle(segStyle, -1);
578       strcat(buffer, segStyle);
579       pageData = buffer;
580       xPos = pageMargin.left;
581       segment.clear();
582       flushSegment();
583     }
584     void endPage() {
585       pageStarted = false;
586       flushSegment();
587       // build actual text object; +3 is for "ET\n"
588       // PDF1.4Ref(p38) EOL marker preceding endstream not counted
589       char *textObj = new char[pageData.length() + 100];
590       // concatenate stream within the text object
591       sprintf(textObj, "<</Length %d>>\nstream\n%s"
592               "ET\nendstream\n",
593               static_cast<int>(pageData.length() - 1 + 3),
594               pageData.text());
595       oT->add(textObj);
596       delete []textObj;
597     }
598     void nextLine() {
599       if (!pageStarted) {
600         startPage();
601       }
602       xPos = pageMargin.left;
603       flushSegment();
604       // PDF follows cartesian coords, subtract -> down
605       yPos -= leading;
606       double fontDescender = fontToPoints(PDFfontDescenders[fontSet]);
607       if (yPos < pageMargin.bottom + fontDescender) {
608         endPage();
609         startPage();
610         return;
611       }
612       if (firstLine) {
613         // avoid breakage due to locale setting
614         int f = (int)(leading * 10 + 0.5);
615         sprintf(buffer, "0 -%d.%d TD\n", f / 10, f % 10);
616         firstLine = false;
617       } else {
618         sprintf(buffer, "T*\n");
619       }
620       pageData += buffer;
621     }
622   };
623   PDFRender pr;
624 
625   sci->sendMessage(SCI_COLOURISE, 0, -1);
626   int tabSize = PDF_TAB_DEFAULT;
627 
628   pr.fontSize = 0;
629 
630   FXString propItem = "Courier";
631   pr.fontSet = PDF_FONT_DEFAULT;
632   if (propItem.length()) {
633     if (propItem == "Courier")
634       pr.fontSet = 0;
635     else if (propItem == "Helvetica")
636       pr.fontSet = 1;
637     else if (propItem == "Times")
638       pr.fontSet = 2;
639   }
640 
641   pr.pageWidth = PDF_WIDTH_DEFAULT;
642   pr.pageHeight = PDF_HEIGHT_DEFAULT;
643   pr.pageMargin.left = PDF_MARGIN_DEFAULT;
644   pr.pageMargin.right = PDF_MARGIN_DEFAULT;
645   pr.pageMargin.top = PDF_MARGIN_DEFAULT;
646   pr.pageMargin.bottom = PDF_MARGIN_DEFAULT;
647 
648   // collect all styles available for that 'language'
649   // or the default style if no language is available...
650   pr.style = new PDFStyle[STYLE_MAX + 1];
651 
652   LangStyle* ls = sci->getLanguage();
653   if (ls) {
654     StyleDef* sd;
655     int i;
656     for (i=0; (i<=STYLE_MAX); i++) {  // get keys
657      sd=GetStyleFromId(ls->styles, i);
658      if (!sd) { sd=Settings::globalStyle(); }
659 
660       pr.style[i].font = 0;
661       if (sd->style & Italic) { pr.style[i].font |= 2; }
662       if (sd->style & Bold) { pr.style[i].font |= 1; }
663       pr.style[i].fore[0] = '\0';
664       if (sd->fg[0]) {
665         getPDFRGB(pr.style[i].fore, sd->fg);
666       } else if (i == STYLE_DEFAULT) {
667         strcpy(pr.style[i].fore, "0 0 0 ");
668       }
669       pr.fontSize = PDF_FONTSIZE_DEFAULT;
670     }
671     // patch in default foregrounds
672     for (int j = 0; j <= STYLE_MAX; j++) {
673       if (pr.style[j].fore[0] == '\0') {
674         strcpy(pr.style[j].fore, pr.style[STYLE_DEFAULT].fore);
675       }
676     }
677   } else {
678     for (int i = 0; i <= STYLE_MAX; i++) {
679       strcpy(pr.style[i].fore, "0 0 0 ");
680     }
681   }
682 
683 
684   // initialise PDF rendering
685   PDFObjectTracker ot(fp);
686   pr.oT = &ot;
687   pr.startPDF();
688 
689   // do here all the writing
690   int lengthDoc = sci->GetTextLength();
691   int lineIndex = 0;
692 
693   if (!lengthDoc) {  // enable zero length docs
694     pr.nextLine();
695   } else {
696     FX88591Codec *codec = sci->GetUTF8() ? new FX88591Codec() : NULL;
697     for (int i = 0; i < lengthDoc; i++) {
698       char ch = sci->sendMessage(SCI_GETCHARAT,i,0);
699       int style = sci->sendMessage(SCI_GETSTYLEAT,i,0);
700 
701       if (ch == '\t') {
702         // expand tabs
703         int ts = tabSize - (lineIndex % tabSize);
704         lineIndex += ts;
705         for (; ts; ts--) {  // add ts count of spaces
706           pr.add(' ', style);  // add spaces
707         }
708       } else if (ch == '\r' || ch == '\n') {
709         if (ch == '\r' && sci->sendMessage(SCI_GETCHARAT,i+1,0) == '\n') {
710           i++;
711         }
712         // close and begin a newline...
713         pr.nextLine();
714         lineIndex = 0;
715       } else {
716         // write the character normally...
717         if (codec) {
718           long charlen=sci->sendMessage(SCI_POSITIONAFTER,i,0)-i;
719           if ((charlen>1)&&(charlen<=8)) {
720             // PDF doesn't like UTF-8, try conversion to single-byte ISO-8859
721             char utf[9]="\0";
722             char asc[2]="\0";
723             Sci_TextRange tr;
724             tr.chrg.cpMin=i;
725             tr.chrg.cpMax=i+charlen;
726             tr.lpstrText=utf;
727             sci->sendMessage(SCI_GETTEXTRANGE, 0, reinterpret_cast<long>(&tr));
728             codec->utf2mb(asc, sizeof(asc), tr.lpstrText, charlen);
729             if (asc[0]&&!asc[1]) {
730               // Conversion succeeded: use our single byte and move past the multi-bytes
731               ch=asc[0];
732               i+=charlen-1;
733             }
734           }
735         }
736         pr.add(ch, style);
737         lineIndex++;
738       }
739     }
740     if (codec) { delete codec; }
741   }
742 
743   // write required stuff and close the PDF file
744   pr.endPDF();
745 
746 }
747 
748