source: trunk/Lucide/SOURCE/plugins/lupoppler/lupoppler.cpp @ 99

Last change on this file since 99 was 99, checked in by Eugene Romanenko, 15 years ago

Internationalize 'embedded' values

File size: 42.4 KB
Line 
1
2/*
3 *  This file was generated by the SOM Compiler.
4 *  Generated using:
5 *     SOM incremental update: 2.24
6 */
7
8/*
9 * Copyright (c) 2006, Eugene Romanenko, netlabs.org
10 *
11 *----------------------------------------------------------------------
12 * This file is part of poppler plugin for Lucide (lupoppler).
13 *
14 *  lupoppler is free software; you can redistribute it and/or modify
15 *  it under the terms of the GNU General Public License as published by
16 *  the Free Software Foundation; either version 2 of the License, or
17 *  (at your option) any later version.
18 *
19 *  lupoppler is distributed in the hope that it will be useful,
20 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 *  GNU General Public License for more details.
23 *----------------------------------------------------------------------
24 */
25
26/*
27 *  This file was generated by the SOM Compiler and Emitter Framework.
28 *  Generated using template emitter:
29 *      SOM Emitter emitxtm: 2.23.1.9
30 */
31
32#ifndef SOM_Module_lupoppler_Source
33#define SOM_Module_lupoppler_Source
34#endif
35#define LuPopplerDocument_Class_Source
36
37#include "lupoppler.xih"
38
39#include <goo\GooString.h>
40#include <goo\GooList.h>
41#include <splash\SplashBitmap.h>
42#include <UGooString.h>
43#include <GlobalParams.h>
44#include <ErrorCodes.h>
45#include <PDFDoc.h>
46#include <SplashOutputDev.h>
47#include <TextOutputDev.h>
48#include <PSOutputDev.h>
49#include <FontInfo.h>
50#include <Outline.h>
51#include <UnicodeMap.h>
52#include <Gfx.h>
53#include <Link.h>
54
55#define INCL_DOS
56#include <os2.h>
57
58#include <vector>
59using namespace std;
60#include <time.h>
61#include "cpconv.h"
62
63typedef vector<LuRectangle> RectList;
64
65
66unsigned _System LibMain( unsigned hmod, unsigned termination )
67{
68    if ( termination ) {
69        /* DLL is detaching from process */
70    } else {
71        /* DLL is attaching to process */
72    }
73    return( 1 );
74}
75
76
77extern "C" LuDocument * _System createObject()
78{
79    return new LuPopplerDocument;
80}
81
82extern "C" char * _System getSupportedExtensions()
83{
84    return "PDF";
85}
86
87extern "C" char * _System getDescription()
88{
89    return "PDF plugin, based on poppler library.";
90}
91
92
93class PopplerPage
94{
95    public:
96
97        Page *page;
98        TextOutputDev *text_dev;
99        Gfx *gfx;
100
101        PopplerPage();
102        ~PopplerPage();
103};
104
105PopplerPage::PopplerPage()
106{
107    page = NULL;
108    text_dev = NULL;
109    gfx = NULL;
110}
111
112PopplerPage::~PopplerPage()
113{
114    delete text_dev;
115    delete gfx;
116}
117
118
119class PopplerDocument
120{
121    public:
122
123        PDFDoc *doc;
124        SplashOutputDev *output_dev;
125        PopplerPage *pages;
126        char *text;
127        HMTX mutex;
128
129        PopplerDocument();
130        ~PopplerDocument();
131};
132
133PopplerDocument::PopplerDocument()
134{
135    doc        = NULL;
136    output_dev = NULL;
137    pages      = NULL;
138    text       = NULL;
139    mutex      = NULLHANDLE;
140    DosCreateMutexSem( NULL, &mutex, 0, FALSE );
141}
142
143PopplerDocument::~PopplerDocument()
144{
145    delete [] pages;
146    delete doc;
147    delete output_dev;
148    delete text;
149    DosCloseMutexSem( mutex );
150}
151
152
153static char *newstrdup( const char *s )
154{
155    if ( s == NULL ) {
156        return NULL;
157    }
158    char *temp = new char[ strlen( s ) + 1 ];
159    strcpy( temp, s );
160    return temp;
161}
162
163static char *somstrdup( const char *s )
164{
165    if ( s == NULL ) {
166        return NULL;
167    }
168    char *temp = (char *)SOMMalloc( strlen( s ) + 1 );
169    strcpy( temp, s );
170    return temp;
171}
172
173
174SOM_Scope short  SOMLINK getBpp(LuPopplerDocument *somSelf,  Environment *ev)
175{
176    return 3;
177}
178
179SOM_Scope boolean  SOMLINK isScalable(LuPopplerDocument *somSelf,
180                                       Environment *ev)
181{
182    return TRUE;
183}
184
185
186SOM_Scope boolean  SOMLINK isRotable(LuPopplerDocument *somSelf,
187                                      Environment *ev)
188{
189    return TRUE;
190}
191
192
193SOM_Scope long  SOMLINK getPageCount(LuPopplerDocument *somSelf,
194                                      Environment *ev)
195{
196    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
197    return ((PopplerDocument *)somThis->data)->doc->getNumPages();
198}
199
200
201SOM_Scope void  SOMLINK getPageSize(LuPopplerDocument *somSelf,
202                                    Environment *ev, long pagenum,
203                                    double* width, double* height)
204{
205    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
206
207    Page *page = ((PopplerDocument *)somThis->data)->pages[ pagenum ].page;
208
209    double page_width, page_height;
210    int rotate = page->getRotate();
211    if ( rotate == 90 || rotate == 270 ) {
212        page_height = page->getCropWidth();
213        page_width = page->getCropHeight();
214    } else {
215        page_width = page->getCropWidth();
216        page_height = page->getCropHeight();
217    }
218
219    if ( width != NULL ) {
220        *width = page_width;
221    }
222    if ( height != NULL ) {
223        *height = page_height;
224    }
225}
226
227
228static void copy_page_to_pixbuf( Environment *ev, SplashBitmap *bitmap, LuPixbuf *pixbuf )
229{
230    int splash_width, splash_height, splash_rowstride;
231    int pixbuf_rowstride, pixbuf_height, pixbuf_width;
232    int height, width, rowstride;
233    char *pixbuf_data, *dst, *src;
234
235    SplashColorPtr color_ptr = bitmap->getDataPtr();
236
237    splash_width = bitmap->getWidth();
238    splash_height = bitmap->getHeight();
239    splash_rowstride = bitmap->getRowSize();
240
241    //somPrintf( "splash_width: %d   splash_height: %d   splash_rowstride: %d\n",
242    //            splash_width, splash_height, splash_rowstride );
243
244    pixbuf_data = (char *)pixbuf->getDataPtr( ev );
245    pixbuf_width = pixbuf->getWidth( ev );
246    pixbuf_height = pixbuf->getHeight( ev );
247    pixbuf_rowstride = pixbuf->getRowSize( ev );
248
249    width = __min( splash_width, pixbuf_width );
250    height = __min( splash_height, pixbuf_height );
251    rowstride = __min( splash_rowstride, pixbuf_rowstride );
252
253    int i, j;
254    for ( i = 0, j = ( height - 1 ); i < height; i++, j-- )
255    {
256        dst = pixbuf_data + i * pixbuf_rowstride;
257        src = ((char *)color_ptr) + j * splash_rowstride;
258        memcpy( dst, src, rowstride );
259    }
260
261    // test
262    //memcpy( pixbuf_data, color_ptr, pixbuf->getDataLen( ev ) );
263}
264
265
266SOM_Scope void  SOMLINK renderPageToPixbuf(LuPopplerDocument *somSelf,
267                                            Environment *ev,
268                                           long pagenum, long src_x,
269                                           long src_y, long src_width,
270                                           long src_height, double scale,
271                                           long rotation, LuPixbuf* pixbuf)
272{
273    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
274    PopplerDocument *document = (PopplerDocument *)somThis->data;
275    Page *page = document->pages[ pagenum ].page;
276
277    if ( ( scale < 0.0 ) || ( pixbuf == NULL ) ) {
278        return;
279    }
280
281    DosRequestMutexSem( document->mutex, SEM_INDEFINITE_WAIT );
282
283    page->displaySlice( document->output_dev,
284                72.0 * scale, 72.0 * scale,
285                rotation,
286                gFalse, /* useMediaBox */
287                gTrue, /* Crop */
288                src_x, src_y,
289                src_width, src_height,
290                NULL, /* links */
291                document->doc->getCatalog() );
292
293    DosReleaseMutexSem( document->mutex );
294
295    copy_page_to_pixbuf( ev, document->output_dev->getBitmap(), pixbuf );
296}
297
298
299SOM_Scope boolean  SOMLINK isAsynchRenderingSupported(LuPopplerDocument *somSelf,
300                                                       Environment *ev)
301{
302    return TRUE;
303}
304
305
306struct asynchCallbackData
307{
308    Environment      *ev;
309    LuPixbuf         *pixbuf;
310    SplashOutputDev  *out;
311    void             *fndata;
312    _asynchCallbackFn fnd;
313    _asynchCallbackFn fna;
314    long              tmr;
315    bool              forceDraw;
316    long              delay;
317};
318
319static GBool abortCheckCbk( void *data )
320{
321    long now;
322    asynchCallbackData *cd = (asynchCallbackData *)data;
323    DosQuerySysInfo( QSV_MS_COUNT, QSV_MS_COUNT, &now, sizeof( long ) );
324    long dist = ( now - cd->tmr );
325    if ( ( dist > cd->delay ) || cd->forceDraw ) 
326    {
327        // Note: we use out->getBitmap() on each iteration instead
328        //       of remembering pointer to bitmap before call
329        //       page->displaySlice() because OutputDev may change
330        //       bitmap during page->displaySlice() processing.
331        copy_page_to_pixbuf( cd->ev, cd->out->getBitmap(), cd->pixbuf );
332        cd->fnd( cd->fndata );
333        cd->tmr = now;
334        cd->delay += 100;
335    }
336    return (GBool)cd->fna( cd->fndata );
337}
338
339SOM_Scope void  SOMLINK renderPageToPixbufAsynch(LuPopplerDocument *somSelf,
340                                                  Environment *ev,
341                                                 long pagenum,
342                                                 long src_x,
343                                                 long src_y,
344                                                 long src_width,
345                                                 long src_height,
346                                                 double scale,
347                                                 long rotation,
348                                                 LuPixbuf* pixbuf,
349                                                 LuDocument_asynchCallbackFn fnd,
350                                                 LuDocument_asynchCallbackFn fna,
351                                                 somToken fndata)
352{
353    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
354    PopplerDocument *document = (PopplerDocument *)somThis->data;
355    Page *page = document->pages[ pagenum ].page;
356
357    if ( ( scale < 0.0 ) || ( pixbuf == NULL ) ) {
358        return;
359    }
360
361    asynchCallbackData acd;
362    acd.ev        = ev;
363    acd.pixbuf    = pixbuf;
364    acd.out       = document->output_dev;
365    acd.fndata    = fndata;
366    acd.fnd       = (_asynchCallbackFn)fnd;
367    acd.fna       = (_asynchCallbackFn)fna;
368    acd.forceDraw = false;
369    acd.delay     = 200;
370    DosQuerySysInfo( QSV_MS_COUNT, QSV_MS_COUNT, &acd.tmr, sizeof( long ) );
371
372    DosRequestMutexSem( document->mutex, SEM_INDEFINITE_WAIT );
373
374    //somPrintf( "src_x: %d, src_y: %d, src_width: %d, src_height: %d\n",
375    //           src_x, src_y, src_width, src_height );
376
377    page->displaySlice( document->output_dev,
378                72.0 * scale, 72.0 * scale,
379                rotation,
380                gFalse, /* useMediaBox */
381                gTrue, /* Crop */
382                src_x, src_y,
383                src_width, src_height,
384                NULL, /* links */
385                document->doc->getCatalog(),
386                abortCheckCbk, &acd );
387    DosReleaseMutexSem( document->mutex );
388
389    acd.forceDraw = true;
390    abortCheckCbk( &acd );
391}
392
393static TextOutputDev *get_text_output_dev( PopplerPage *page,
394                                           PopplerDocument *document )
395{
396    if ( page->text_dev == NULL )
397    {
398        DosRequestMutexSem( document->mutex, SEM_INDEFINITE_WAIT );
399        page->text_dev = new TextOutputDev( NULL, gTrue, gFalse, gFalse );
400
401        page->gfx = page->page->createGfx(page->text_dev,
402                          72.0, 72.0, 0,
403                          gFalse, /* useMediaBox */
404                          gTrue, /* Crop */
405                          -1, -1, -1, -1,
406                          NULL, /* links */
407                          document->doc->getCatalog(),
408                          NULL, NULL, NULL, NULL);
409
410        page->page->display( page->gfx );
411        page->text_dev->endPage();
412        DosReleaseMutexSem( document->mutex );
413    }
414
415    return page->text_dev;
416}
417
418
419SOM_Scope LuDocument_LuRectSequence*  SOMLINK getSelectionRectangles(LuPopplerDocument *somSelf,
420                                                                    Environment *ev,
421                                                                   long pagenum,
422                                                                   LuRectangle* selection)
423{
424    LuDocument_LuRectSequence *rectangles = NULL;
425
426    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
427    PopplerDocument *document = (PopplerDocument *)somThis->data;
428    PopplerPage *page = &( document->pages[ pagenum ] );
429
430    TextOutputDev *text_dev = get_text_output_dev( page, document );
431
432    PDFRectangle poppler_selection;
433    poppler_selection.x1 = selection->x1;
434    poppler_selection.y1 = selection->y1;
435    poppler_selection.x2 = selection->x2;
436    poppler_selection.y2 = selection->y2;
437
438    GooList *list = text_dev->getSelectionRegion( &poppler_selection, 1.0 );
439    int len = list->getLength();
440
441    if ( len > 0 )
442    {
443        rectangles = (LuDocument_LuRectSequence *)SOMMalloc( sizeof( LuDocument_LuRectSequence ) );
444        rectangles->_maximum = len;
445        rectangles->_length = len;
446        rectangles->_buffer = (LuRectangle *)SOMMalloc( sizeof( LuRectangle ) * len );
447
448        for ( int i = 0; i < len; i++ )
449        {
450            PDFRectangle *selection_rect = (PDFRectangle *)list->get( i );
451            rectangles->_buffer[ i ].x1 = selection_rect->x1;
452            rectangles->_buffer[ i ].y1 = selection_rect->y1;
453            rectangles->_buffer[ i ].x2 = selection_rect->x2;
454            rectangles->_buffer[ i ].y2 = selection_rect->y2;
455            delete selection_rect;
456        }
457    }
458    delete list;
459
460    return rectangles;
461}
462
463
464SOM_Scope boolean  SOMLINK isHaveText(LuPopplerDocument *somSelf,
465                                      Environment *ev)
466{
467    return TRUE;
468}
469
470
471SOM_Scope string  SOMLINK getText(LuPopplerDocument *somSelf,
472                                   Environment *ev, long pagenum,
473                                  LuRectangle* selection)
474{
475    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
476
477    if ( selection == NULL ) {
478        return NULL;
479    }
480
481    PopplerDocument *document = (PopplerDocument *)somThis->data;
482    PopplerPage *page = &( document->pages[ pagenum ] );
483
484    TextOutputDev *text_dev = get_text_output_dev( page, document );
485
486    GooString *sel_text = new GooString;
487    char *result;
488    PDFRectangle pdf_selection;
489
490    pdf_selection.x1 = selection->x1;
491    pdf_selection.y1 = selection->y1;
492    pdf_selection.x2 = selection->x2;
493    pdf_selection.y2 = selection->y2;
494
495    DosRequestMutexSem( document->mutex, SEM_INDEFINITE_WAIT );
496    sel_text = text_dev->getSelectionText( &pdf_selection );
497
498    delete document->text;
499    document->text = newstrdup( sel_text->getCString() );
500    delete sel_text;
501    DosReleaseMutexSem( document->mutex );
502
503    return document->text;
504}
505
506
507SOM_Scope boolean  SOMLINK isHaveLinks(LuPopplerDocument *somSelf,
508                                        Environment *ev)
509{
510    return TRUE;
511}
512
513
514static long find_dest_page( PDFDoc *doc, LinkDest *link_dest )
515{
516    long page_num = 0;
517
518    if ( link_dest == NULL ) {
519        return page_num;
520    }
521
522    if ( link_dest->isPageRef() )
523    {
524        Ref page_ref = link_dest->getPageRef();
525        page_num = doc->findPage( page_ref.num, page_ref.gen ) - 1;
526    }
527    else {
528        page_num = link_dest->getPageNum() - 1;
529    }
530
531    return page_num;
532}
533
534static void build_goto_dest( PDFDoc *doc, LuLink *evlink, LinkGoTo *link )
535{
536    LinkDest *link_dest;
537    UGooString *named_dest;
538
539    if ( !link->isOk() ) {
540        return;
541    }
542
543    link_dest = link->getDest();
544    named_dest = link->getNamedDest();
545
546    if ( link_dest != NULL ) {
547        evlink->page = find_dest_page( doc, link_dest );
548    } else if ( named_dest != NULL ) {
549        link_dest = doc->findDest( named_dest );
550        evlink->page = find_dest_page( doc, link_dest );
551        delete link_dest;
552    } else {
553        evlink->page = 0;
554    }
555}
556
557static void build_link( PDFDoc *doc, LuLink *evlink,
558                        const char *title, LinkAction *link_action )
559{
560    evlink->title = somstrdup( title );
561    evlink->uri = NULL;
562    evlink->type = LU_LINK_TYPE_TITLE;
563    evlink->page = 0;
564
565    if ( link_action == NULL ) {
566        return;
567    }
568
569    switch ( link_action->getKind() )
570    {
571        case actionGoTo:
572            {
573                evlink->type = LU_LINK_TYPE_PAGE;
574                LinkGoTo *lgt = dynamic_cast <LinkGoTo *> (link_action);
575                build_goto_dest( doc, evlink, lgt );
576            }
577            break;
578
579        case actionURI:
580            {
581                evlink->type = LU_LINK_TYPE_EXTERNAL_URI;
582                LinkURI *lu = dynamic_cast <LinkURI *> (link_action);
583                char *uri = lu->getURI()->getCString();
584                if ( uri != NULL ) {
585                    evlink->uri = somstrdup( uri );
586                }
587            }
588            break;
589    }
590}
591
592SOM_Scope LuDocument_LuLinkMapSequence*  SOMLINK getLinkMapping(LuPopplerDocument *somSelf,
593                                                                 Environment *ev,
594                                                                long pagenum)
595{
596    LuDocument_LuLinkMapSequence *mapping = NULL;
597
598    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
599    PopplerDocument *document = (PopplerDocument *)somThis->data;
600    PopplerPage *page = &( document->pages[ pagenum ] );
601
602    Object obj;
603    Links *links = new Links( page->page->getAnnots( &obj ),
604                            document->doc->getCatalog()->getBaseURI() );
605    obj.free();
606
607    if ( links == NULL ) {
608        return NULL;
609    }
610
611    double height = 0;
612    getPageSize( somSelf, ev, pagenum, NULL, &height );
613
614    int len = links->getNumLinks();
615
616    if ( len > 0 )
617    {
618        mapping = (LuDocument_LuLinkMapSequence *)SOMMalloc( sizeof( LuDocument_LuLinkMapSequence ) );
619        mapping->_maximum = len;
620        mapping->_length = len;
621        mapping->_buffer = (LuLinkMapping *)SOMMalloc( sizeof( LuLinkMapping ) * len );
622
623        for ( int i = 0; i < len; i++ )
624        {
625            Link *link = links->getLink( i );
626            LinkAction *link_action = link->getAction();
627            build_link( document->doc, &(mapping->_buffer[ i ].link), NULL, link_action );
628
629            link->getRect( &(mapping->_buffer[ i ].area.x1),
630                           &(mapping->_buffer[ i ].area.y1),
631                           &(mapping->_buffer[ i ].area.x2),
632                           &(mapping->_buffer[ i ].area.y2) );
633
634            mapping->_buffer[ i ].area.x1 -= page->page->getCropBox()->x1;
635            mapping->_buffer[ i ].area.x2 -= page->page->getCropBox()->x1;
636            mapping->_buffer[ i ].area.y1 -= page->page->getCropBox()->y1;
637            mapping->_buffer[ i ].area.y2 -= page->page->getCropBox()->y1;
638
639            double y1 = mapping->_buffer[ i ].area.y1;
640            double y2 = mapping->_buffer[ i ].area.y2;
641            mapping->_buffer[ i ].area.y1 = height - y2;
642            mapping->_buffer[ i ].area.y2 = height - y1;
643        }
644    }
645
646    return mapping;
647}
648
649
650SOM_Scope boolean  SOMLINK isSaveable(LuPopplerDocument *somSelf,
651                                       Environment *ev)
652{
653    return TRUE;
654}
655
656
657SOM_Scope boolean  SOMLINK saveAs(LuPopplerDocument *somSelf,
658                                   Environment *ev, string filename)
659{
660    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
661    PopplerDocument *document = (PopplerDocument *)somThis->data;
662
663    boolean retval = FALSE;
664
665    if ( filename != NULL ) {
666        retval = document->doc->saveAs( new GooString( filename ) );
667    }
668
669    return retval;
670}
671
672
673SOM_Scope boolean  SOMLINK isPostScriptExportable(LuPopplerDocument *somSelf,
674                                           Environment *ev)
675{
676    return TRUE;
677}
678
679
680SOM_Scope boolean SOMLINK exportToPostScript(LuPopplerDocument *somSelf,
681                                    Environment *ev, string filename,
682                                    long first_page, long last_page,
683                                    double width, double height,
684                                    boolean duplex, boolean* brkExport)
685{
686    if ( filename == NULL ) {
687        return FALSE;
688    }
689    if ( last_page < first_page ) {
690        return FALSE;
691    }
692
693    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
694    PDFDoc *doc = ((PopplerDocument *)somThis->data)->doc;
695
696    PSOutputDev *out = new PSOutputDev( filename, doc->getXRef(),
697                                        doc->getCatalog(),
698                                        first_page + 1, last_page + 1,
699                                        psModePS, (int)width, (int)height,
700                                        duplex, 0, 0, 0, 0, gFalse );
701
702        if ( !out->isOk() ) {
703                delete out;
704        return FALSE;
705        }
706
707        if ( *brkExport ) {
708                delete out;
709        return TRUE;
710        }
711       
712    for ( long i = first_page; (i <= last_page) && !(*brkExport); i++ ) {
713        doc->displayPage( out, i + 1, 72.0, 72.0, 0, gFalse, gTrue, gFalse );
714    }
715
716    delete out;
717    return TRUE;
718}
719
720
721SOM_Scope boolean  SOMLINK isHaveFontInfo(LuPopplerDocument *somSelf,
722                                           Environment *ev)
723{
724    return TRUE;
725}
726
727
728SOM_Scope LuDocument_LuFontInfoSequence*  SOMLINK getFontInfo(LuPopplerDocument *somSelf,
729                                                               Environment *ev)
730{
731    LuDocument_LuFontInfoSequence *fonts = NULL;
732
733    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
734    PopplerDocument *document = (PopplerDocument *)somThis->data;
735    PDFDoc *doc = document->doc;
736
737    DosRequestMutexSem( document->mutex, SEM_INDEFINITE_WAIT );
738    FontInfoScanner *scanner = new FontInfoScanner( doc );
739    GooList *items = scanner->scan( doc->getNumPages() );
740    delete scanner;
741    DosReleaseMutexSem( document->mutex );
742
743    if ( items == NULL ) {
744        return NULL;
745    }
746
747    int len = items->getLength();
748
749    if ( len == 0 ) {
750        delete items;
751        return NULL;
752    }
753
754    fonts = (LuDocument_LuFontInfoSequence *)SOMMalloc( sizeof( LuDocument_LuFontInfoSequence ) );
755    fonts->_maximum = len;
756    fonts->_length = len;
757    fonts->_buffer = (LuFontInfo *)SOMMalloc( sizeof( LuFontInfo ) * len );
758
759    for ( int i = 0; i < len; i++ )
760    {
761        fonts->_buffer[i].name = NULL;
762        fonts->_buffer[i].type = NULL;
763        fonts->_buffer[i].embedded = NULL;
764
765        FontInfo *info = (FontInfo *)items->get( i );
766
767        // name
768        GooString *gnm = info->getName();
769        if ( gnm != NULL )
770        {
771            char *nm = gnm->getCString();
772            if ( info->getSubset() && ( nm != NULL ) )
773            {
774                while ( *nm && ( *nm != '+' ) ) {
775                    nm++;
776                }
777                if ( *nm ) {
778                    nm++;
779                }
780            }
781            if ( nm != NULL ) {
782                if ( *nm ) {
783                    fonts->_buffer[i].name = somstrdup( nm );
784                }
785            }
786        }
787
788        // type
789        char *t = "Unknown font type";
790        switch ( info->getType() )
791        {
792            case FontInfo::Type1:        t = "Type 1";          break;
793            case FontInfo::Type1C:       t = "Type 1C";         break;
794            case FontInfo::Type3:        t = "Type 3";          break;
795            case FontInfo::TrueType:     t = "TrueType";        break;
796            case FontInfo::CIDType0:     t = "Type 1 (CID)";    break;
797            case FontInfo::CIDType0C:    t = "Type 1C (CID)";   break;
798            case FontInfo::CIDTrueType:  t = "TrueType (CID)";  break;
799        }
800        fonts->_buffer[i].type = somstrdup( t );
801
802        // embedded
803        if ( info->getEmbedded() ) {
804            if ( info->getSubset() ) {
805                fonts->_buffer[i].embedded = LU_FONTEMBED_EMBEDDED_SUBSET;
806            } else {
807                fonts->_buffer[i].embedded = LU_FONTEMBED_EMBEDDED;
808            }
809        } else {
810            fonts->_buffer[i].embedded = LU_FONTEMBED_NOT_EMBEDDED;
811        }
812    }
813
814    return fonts;
815}
816
817
818SOM_Scope boolean  SOMLINK isHaveIndex(LuPopplerDocument *somSelf,
819                                        Environment *ev)
820{
821    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
822    PDFDoc *doc = ((PopplerDocument *)somThis->data)->doc;
823
824    Outline *outline = doc->getOutline();
825    if ( outline == NULL ) {
826        return FALSE;
827    }
828
829    GooList *items = outline->getItems();
830    if ( items == NULL ) {
831        return FALSE;
832    }
833
834    return TRUE;
835}
836
837static char *unicode_to_char( Unicode *unicode, int len )
838{
839    static UnicodeMap *uMap = NULL;
840    if ( uMap == NULL )
841    {
842        GooString *enc = new GooString( "UTF-8" );
843        uMap = globalParams->getUnicodeMap( enc );
844        uMap->incRefCnt();
845        delete enc;
846    }
847
848    GooString gstr;
849    char buf[8]; // 8 is enough for mapping an unicode char to a string
850    int i, n;
851
852    for ( i = 0; i < len; ++i ) {
853        n = uMap->mapUnicode( unicode[i], buf, sizeof( buf ) );
854        gstr.append( buf, n );
855    }
856
857    return newstrdup( gstr.getCString() );
858}
859
860static char *newstrFromUTF8( const char *s )
861{
862    unsigned blen = strlen( s ) + 1;
863    char *b = new char[ blen ];
864    memset( b, 0, blen );
865    char *bsav = b;
866    const char *from = s;
867    unsigned flen = strlen( s );
868    cnvUTF8ToSys( &from, &flen, &b, &blen );
869    return bsav;
870}
871
872static void add_item( Environment *ev, PDFDoc *doc, LuIndexNode *n, GooList *items )
873{
874    if ( items == NULL ) {
875        return;
876    }
877
878    int len = items->getLength();
879
880    for ( int i = 0; i < len; i++ )
881    {
882        OutlineItem *item = (OutlineItem *)items->get( i );
883        LinkAction *link_action = item->getAction();
884        LuLink evlink;
885        char *t1 = unicode_to_char( item->getTitle(), item->getTitleLength() );
886        char *t2 = newstrFromUTF8( t1 );
887        build_link( doc, &evlink, t2, link_action );
888        delete t2;
889        delete t1;
890        LuIndexNode *cn = new LuIndexNode( ev, &evlink );
891        n->addChild( ev, cn );
892
893        item->open();
894        if ( item->hasKids() )
895        {
896            GooList *citems = item->getKids();
897            add_item( ev, doc, cn, citems );
898        }
899    }
900}
901
902SOM_Scope LuIndexNode*  SOMLINK getIndex(LuPopplerDocument *somSelf,
903                                          Environment *ev)
904{
905    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
906    PDFDoc *doc = ((PopplerDocument *)somThis->data)->doc;
907
908    Outline *outline = doc->getOutline();
909    if ( outline == NULL ) {
910        return NULL;
911    }
912
913    GooList *items = outline->getItems();
914    if ( items == NULL ) {
915        return NULL;
916    }
917
918    LuIndexNode *root = new LuIndexNode( ev, NULL );
919    add_item( ev, doc, root, items );
920
921    return root;
922}
923
924
925static bool has_unicode_marker( GooString *string )
926{
927    return ( ( (string->getChar(0) & 0xff) == 0xfe ) &&
928             ( (string->getChar(1) & 0xff) == 0xff ) );
929}
930
931// return SOMMalloc'ed string
932static char *propcnv( GooString *s )
933{
934    if ( has_unicode_marker( s ) )
935    {
936        unsigned blen = s->getLength() * 2;
937        char *b = (char *)SOMMalloc( blen );
938        memset( b, 0, blen );
939        char *bsav = b;
940        const char *from = s->getCString() + 2;
941        unsigned flen = s->getLength() - 2;
942        cnvUniBEToSys( &from, &flen, &b, &blen );
943        return bsav;
944    }
945
946    return somstrdup( s->getCString() );
947}
948
949static time_t propToDate( const char *date_string )
950{
951    int year, mon, day, hour, min, sec;
952    int scanned_items;
953
954    // See PDF Reference 1.3, Section 3.8.2 for PDF Date representation
955    if ( ( date_string[0] == 'D' ) && ( date_string[1] == ':' ) ) {
956        date_string += 2;
957    }
958
959    // FIXME only year is mandatory; parse optional timezone offset
960    scanned_items = sscanf( date_string, "%4d%2d%2d%2d%2d%2d",
961                            &year, &mon, &day, &hour, &min, &sec);
962
963    if ( scanned_items != 6 ) {
964        return (time_t)(-1);
965    }
966
967    // Workaround for y2k bug in Distiller 3, hoping that it won't
968    // be used after y2.2k
969    if ( ( year < 1930 ) && ( strlen( date_string ) > 14 ) )
970    {
971        int century, years_since_1900;
972        scanned_items = sscanf( date_string, "%2d%3d%2d%2d%2d%2d%2d",
973                &century, &years_since_1900, &mon, &day, &hour, &min, &sec );
974
975        if ( scanned_items != 7 ) {
976            return (time_t)(-1);
977        }
978
979        year = century * 100 + years_since_1900;
980    }
981
982    struct tm time = { 0 };
983    time.tm_year  = year - 1900;
984    time.tm_mon   = mon - 1;
985    time.tm_mday  = day;
986    time.tm_hour  = hour;
987    time.tm_min   = min;
988    time.tm_sec   = sec;
989    time.tm_wday  = -1;
990    time.tm_yday  = -1;
991    time.tm_isdst = -1; // 0 = DST off, 1 = DST on, -1 = don't know
992
993    return mktime( &time );
994}
995
996static long convPageMode( Catalog::PageMode pageMode )
997{
998    switch ( pageMode )
999    {
1000        case Catalog::pageModeThumbs:
1001            return LU_DOCUMENT_MODE_USE_THUMBS;
1002        case Catalog::pageModeFullScreen:
1003            return LU_DOCUMENT_MODE_FULL_SCREEN;
1004        case Catalog::pageModeOC:
1005            return LU_DOCUMENT_MODE_USE_OC;
1006        case Catalog::pageModeAttach:
1007            return LU_DOCUMENT_MODE_USE_ATTACHMENTS;
1008        case Catalog::pageModeNone:
1009            return LU_DOCUMENT_MODE_NONE;
1010    }
1011
1012    return -1;
1013}
1014
1015static long convLayout( Catalog::PageLayout pageLayout )
1016{
1017    switch ( pageLayout )
1018    {
1019        case Catalog::pageLayoutSinglePage:
1020            return LU_DOCUMENT_LAYOUT_SINGLE_PAGE;
1021        case Catalog::pageLayoutOneColumn:
1022            return LU_DOCUMENT_LAYOUT_ONE_COLUMN;
1023        case Catalog::pageLayoutTwoColumnLeft:
1024            return LU_DOCUMENT_LAYOUT_TWO_COLUMN_LEFT;
1025        case Catalog::pageLayoutTwoColumnRight:
1026            return LU_DOCUMENT_LAYOUT_TWO_COLUMN_RIGHT;
1027        case Catalog::pageLayoutTwoPageLeft:
1028            return LU_DOCUMENT_LAYOUT_TWO_PAGE_LEFT;
1029        case Catalog::pageLayoutTwoPageRight:
1030            return LU_DOCUMENT_LAYOUT_TWO_PAGE_RIGHT;
1031    }
1032    return -1;
1033}
1034
1035SOM_Scope LuDocumentInfo*  SOMLINK getDocumentInfo(LuPopplerDocument *somSelf,
1036                                                    Environment *ev)
1037{
1038    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
1039    PDFDoc *doc = ((PopplerDocument *)somThis->data)->doc;
1040
1041    LuDocumentInfo *info = (LuDocumentInfo *)SOMMalloc( sizeof( LuDocumentInfo ) );
1042    memset( info, 0, sizeof( LuDocumentInfo ) );
1043
1044    Object objdict;
1045    doc->getDocInfo( &objdict );
1046    if ( objdict.isDict() )
1047    {
1048        Dict *d = objdict.getDict();
1049        Object obj;
1050
1051        if ( d->lookup( "Title", &obj )->isString() ) {
1052            info->title = propcnv( obj.getString() );
1053            info->fields_mask |= LU_DOCUMENT_INFO_TITLE;
1054        }
1055        obj.free();
1056        if ( d->lookup( "Author", &obj )->isString() ) {
1057            info->author = propcnv( obj.getString() );
1058            info->fields_mask |= LU_DOCUMENT_INFO_AUTHOR;
1059        }
1060        obj.free();
1061        if ( d->lookup( "Subject", &obj )->isString() ) {
1062            info->subject = propcnv( obj.getString() );
1063            info->fields_mask |= LU_DOCUMENT_INFO_SUBJECT;
1064        }
1065        obj.free();
1066        if ( d->lookup( "Keywords", &obj )->isString() ) {
1067            info->keywords = propcnv( obj.getString() );
1068            info->fields_mask |= LU_DOCUMENT_INFO_KEYWORDS;
1069        }
1070        obj.free();
1071        if ( d->lookup( "Creator", &obj )->isString() ) {
1072            info->creator = propcnv( obj.getString() );
1073            info->fields_mask |= LU_DOCUMENT_INFO_CREATOR;
1074        }
1075        obj.free();
1076        if ( d->lookup( "Producer", &obj )->isString() ) {
1077            info->producer = propcnv( obj.getString() );
1078            info->fields_mask |= LU_DOCUMENT_INFO_PRODUCER;
1079        }
1080        obj.free();
1081        if ( d->lookup( "CreationDate", &obj )->isString() ) {
1082            char *d = propcnv( obj.getString() );
1083            info->creation_date = propToDate( d );
1084            if ( (long)info->creation_date != -1 ) {
1085                info->fields_mask |= LU_DOCUMENT_INFO_CREATION_DATE;
1086            }
1087            SOMFree( d );
1088        }
1089        obj.free();
1090        if ( d->lookup( "ModDate", &obj )->isString() ) {
1091            char *d = propcnv( obj.getString() );
1092            info->modified_date = propToDate( d );
1093            if ( (long)info->modified_date != -1 ) {
1094                info->fields_mask |= LU_DOCUMENT_INFO_MOD_DATE;
1095            }
1096            SOMFree( d );
1097        }
1098        obj.free();
1099    }
1100
1101    char *format = (char *)SOMMalloc( 16 );
1102    snprintf( format, 16, "PDF-%.2f", doc->getPDFVersion() );
1103    info->format = format;
1104    info->fields_mask |= LU_DOCUMENT_INFO_FORMAT;
1105
1106    info->linearized = doc->isLinearized();
1107    info->fields_mask |= LU_DOCUMENT_INFO_LINEARIZED;
1108
1109    Catalog *catalog = doc->getCatalog();
1110
1111    if ( ( catalog != NULL ) && catalog->isOk() )
1112    {
1113        info->layout = convLayout( catalog->getPageLayout() );
1114        if ( info->layout != -1 ) {
1115            info->fields_mask |= LU_DOCUMENT_INFO_LAYOUT;
1116        }
1117
1118        info->mode = convPageMode( catalog->getPageMode() );
1119        if ( info->mode != -1 ) {
1120            info->fields_mask |= LU_DOCUMENT_INFO_START_MODE;
1121        }
1122    }
1123
1124    info->fields_mask |= LU_DOCUMENT_INFO_PERMISSIONS;
1125    if ( doc->okToPrint() ) {
1126        info->permissions |= LU_DOCUMENT_PERMISSIONS_OK_TO_PRINT;
1127    }
1128    if ( doc->okToChange() ) {
1129        info->permissions |= LU_DOCUMENT_PERMISSIONS_OK_TO_MODIFY;
1130    }
1131    if ( doc->okToCopy() ) {
1132        info->permissions |= LU_DOCUMENT_PERMISSIONS_OK_TO_COPY;
1133    }
1134    if ( doc->okToAddNotes() ) {
1135        info->permissions |= LU_DOCUMENT_PERMISSIONS_OK_TO_ADD_NOTES;
1136    }
1137
1138    info->n_pages = doc->getNumPages();
1139    info->fields_mask |= LU_DOCUMENT_INFO_N_PAGES;
1140
1141    return info;
1142}
1143
1144
1145SOM_Scope boolean  SOMLINK getThumbnailSize(LuPopplerDocument *somSelf,
1146                                             Environment *ev,
1147                                            long pagenum,
1148                                            short suggested_width,
1149                                            short* width, short* height)
1150{
1151    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
1152    Page *page = ((PopplerDocument *)somThis->data)->pages[ pagenum ].page;
1153
1154    Object thumb;
1155    Dict *dict;
1156    boolean retval = FALSE;
1157
1158    page->getThumb( &thumb );
1159    if ( thumb.isNull() )
1160    {
1161        thumb.free();
1162        return FALSE;
1163    }
1164
1165    dict = thumb.streamGetDict();
1166
1167    // Theoretically, this could succeed and you would still fail when
1168    // loading the thumb
1169    int w = 0, h = 0;
1170    if ( dict->lookupInt( "Width", "W", &w ) && dict->lookupInt( "Height", "H", &h ) )
1171    {
1172        if ( width != NULL ) {
1173            *width = w;
1174        }
1175        if ( height != NULL ) {
1176            *height = h;
1177        }
1178
1179        retval = TRUE;
1180    }
1181
1182    thumb.free();
1183
1184    return retval;
1185}
1186
1187SOM_Scope LuPixbuf*  SOMLINK getThumbnail(LuPopplerDocument *somSelf,
1188                                           Environment *ev, long pagenum,
1189                                          short suggested_width)
1190{
1191    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
1192    Page *page = ((PopplerDocument *)somThis->data)->pages[ pagenum ].page;
1193
1194    unsigned char *data;
1195    int width, height, rowstride;
1196
1197    if ( !page->loadThumb( &data, &width, &height, &rowstride ) ) {
1198        return NULL;
1199    }
1200
1201    short bpp = getBpp( somSelf, ev );
1202    LuPixbuf *pixbuf = new LuPixbuf( ev, width, height, bpp );
1203    char *pixbuf_data = (char *)pixbuf->getDataPtr( ev );
1204    int pixbuf_rowstride = pixbuf->getRowSize( ev );
1205    char *src, *dst;
1206    int i, j;
1207    for ( i = 0, j = ( height - 1 ); i < height; i++, j-- )
1208    {
1209        src = data + ( j * rowstride );
1210        dst = pixbuf_data + (i * pixbuf_rowstride);
1211        for ( int k = 0; k < pixbuf_rowstride; k += bpp )
1212        {
1213            dst[ k ]     = src[ k + 2 ];
1214            dst[ k + 1 ] = src[ k + 1 ];
1215            dst[ k + 2 ] = src[ k ];
1216        }
1217    }
1218    gfree( data );
1219
1220    return pixbuf;
1221}
1222
1223
1224SOM_Scope LuDocument_LuRectSequence*  SOMLINK searchText(LuPopplerDocument *somSelf,
1225                                                          Environment *ev,
1226                                                         long pagenum,
1227                                                         string text,
1228                                                         boolean caseSensitive)
1229{
1230    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData(somSelf);
1231    PopplerDocument *document = (PopplerDocument *)somThis->data;
1232    Page *page = document->pages[ pagenum ].page;
1233
1234    DosRequestMutexSem( document->mutex, SEM_INDEFINITE_WAIT );
1235    TextOutputDev *output_dev = new TextOutputDev( NULL, gTrue, gFalse, gFalse );
1236    page->display( output_dev, 72, 72, 0, gFalse,
1237                   gTrue, NULL, document->doc->getCatalog() );
1238    DosReleaseMutexSem( document->mutex );
1239
1240
1241    // Convert string from system encoding to UCS-4
1242    // first, convert to UCS-2
1243    unsigned text_len = strlen( text );
1244    unsigned text_len_sav = text_len;
1245    unsigned ucs2_len = ( text_len + 1 ) * 2;
1246    char *ucs2 = new char[ ucs2_len ];
1247    memset( ucs2, 0, ucs2_len );
1248    char *ucs2sav = ucs2;
1249    cnvSysToUCS2( (const char **)&text, &text_len, &ucs2, &ucs2_len );
1250    // second, convert UCS-2 to UCS-4
1251    short *uucs2 = (short *)ucs2sav;
1252    unsigned ucs4_len = ( text_len_sav + 1 ) * 2;
1253    unsigned *ucs4 = new unsigned[ ucs4_len ];
1254    memset( ucs4, 0, ucs4_len * sizeof( unsigned ) );
1255    int real_ucs4_len = 0;
1256    for ( real_ucs4_len = 0; *uucs2; real_ucs4_len++ ) {
1257        ucs4[ real_ucs4_len ] = *uucs2++;
1258    }
1259    delete ucs2sav;
1260    // conversion end
1261
1262    RectList *rl = new RectList;
1263    double xMin, yMin, xMax, yMax;
1264    xMin = 0;
1265    yMin = 0;
1266    while ( output_dev->findText( ucs4, real_ucs4_len,
1267                   gFalse, gTrue, // startAtTop, stopAtBottom
1268                   gTrue, gFalse, // startAtLast, stopAtLast
1269                   caseSensitive, gFalse, // caseSensitive, backwards
1270                   &xMin, &yMin, &xMax, &yMax ) )
1271    {
1272        LuRectangle r;
1273        r.x1 = xMin;
1274        r.y1 = yMin;
1275        r.x2 = xMax;
1276        r.y2 = yMax;
1277        rl->push_back( r );
1278    }
1279    delete ucs4;
1280    delete output_dev;
1281
1282    LuDocument_LuRectSequence *rectangles = NULL;
1283    int len = rl->size();
1284    if ( len > 0 )
1285    {
1286        rectangles = (LuDocument_LuRectSequence *)SOMMalloc( sizeof( LuDocument_LuRectSequence ) );
1287        rectangles->_maximum = len;
1288        rectangles->_length = len;
1289        rectangles->_buffer = (LuRectangle *)SOMMalloc( sizeof( LuRectangle ) * len );
1290
1291        for ( int i = 0; i < len; i++ )
1292        {
1293            rectangles->_buffer[ i ].x1 = (*rl)[i].x1;
1294            rectangles->_buffer[ i ].y1 = (*rl)[i].y1;
1295            rectangles->_buffer[ i ].x2 = (*rl)[i].x2;
1296            rectangles->_buffer[ i ].y2 = (*rl)[i].y2;
1297        }
1298    }
1299    delete rl;
1300
1301    return rectangles;
1302}
1303
1304
1305SOM_Scope boolean  SOMLINK isFixedImage(LuPopplerDocument *somSelf, 
1306                                         Environment *ev)
1307{
1308    return FALSE;
1309}
1310
1311
1312SOM_Scope void SOMLINK somDefaultInit(LuPopplerDocument *somSelf,
1313                                      som3InitCtrl* ctrl)
1314{
1315    // generated section - do not modify
1316    LuPopplerDocumentData *somThis;
1317    somInitCtrl globalCtrl;
1318    somBooleanVector myMask;
1319    LuPopplerDocument_BeginInitializer_somDefaultInit;
1320    LuPopplerDocument_Init_LuDocument_somDefaultInit(somSelf, ctrl);
1321    // end of generated section
1322
1323    // local LuPopplerDocument initialization code
1324    PopplerDocument *d = new PopplerDocument;
1325    somThis->data = d;
1326}
1327
1328
1329static void set_error( char **error, const char *fmt, ... )
1330{
1331    if ( error == NULL ) {
1332        return;
1333    }
1334
1335    va_list argptr;
1336    va_start( argptr, fmt );
1337    char *msg = new char[ 1000 ];
1338    vsnprintf( msg, 1000, fmt, argptr );
1339    *error = somstrdup( msg );
1340    delete msg;
1341}
1342
1343
1344SOM_Scope boolean  SOMLINK loadFile(LuPopplerDocument *somSelf,
1345                                    Environment *ev, string filename,
1346                                    string password, string* error)
1347{
1348    LuPopplerDocumentData *somThis = LuPopplerDocumentGetData( somSelf );
1349
1350    PDFDoc *newDoc;
1351    GooString *filename_g;
1352    GooString *password_g;
1353    int err;
1354
1355    if ( !globalParams ) {
1356        globalParams = new GlobalParams( NULL );
1357    }
1358
1359    filename_g = new GooString(filename);
1360
1361    password_g = NULL;
1362    if (password != NULL) {
1363        password_g = new GooString(password);
1364    }
1365
1366    newDoc = new PDFDoc(filename_g, password_g, password_g);
1367    if (password_g) {
1368        delete password_g;
1369    }
1370
1371    if (!newDoc->isOk()) {
1372        err = newDoc->getErrorCode();
1373        delete newDoc;
1374
1375        if (err == errEncrypted) {
1376            set_error(error, "Document is encrypted.");
1377        } else {
1378            set_error(error, "Failed to load document (error %d) '%s'\n", err, filename );
1379        }
1380
1381        return FALSE;
1382    }
1383
1384    PopplerDocument *document = (PopplerDocument *)somThis->data;
1385
1386    document->doc = newDoc;
1387    SplashColor white;
1388    white[0] = 255;
1389    white[1] = 255;
1390    white[2] = 255;
1391    document->output_dev = new SplashOutputDev( splashModeBGR8, 4, gFalse, white );
1392    document->output_dev->startDoc( document->doc->getXRef() );
1393
1394    long numpages = document->doc->getNumPages();
1395    document->pages = new PopplerPage[ numpages ];
1396    for ( long pagenum = 0; pagenum < numpages; pagenum++ ) {
1397        document->pages[ pagenum ].page =
1398            document->doc->getCatalog()->getPage( pagenum + 1 );
1399    }
1400
1401    return TRUE;
1402}
1403
1404
1405SOM_Scope void SOMLINK somDestruct(LuPopplerDocument *somSelf,
1406                                   octet doFree, som3DestructCtrl* ctrl)
1407{
1408    LuPopplerDocumentData *somThis;
1409    somDestructCtrl globalCtrl;
1410    somBooleanVector myMask;
1411    LuPopplerDocument_BeginDestructor;
1412
1413    // local LuPopplerDocument deinitialization code
1414    PopplerDocument *document = (PopplerDocument *)somThis->data;
1415    delete document;
1416    // end of local LuPopplerDocument deinitialization code
1417
1418    LuPopplerDocument_EndDestructor;
1419}
1420
1421
Note: See TracBrowser for help on using the repository browser.