source: trunk/libdjvu/XMLParser.cpp @ 199

Last change on this file since 199 was 17, checked in by Eugene Romanenko, 16 years ago

update makefiles, remove absolute paths, update djvulibre to version 3.5.17

File size: 31.4 KB
Line 
1//C-  -*- C++ -*-
2//C- -------------------------------------------------------------------
3//C- DjVuLibre-3.5
4//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
5//C- Copyright (c) 2001  AT&T
6//C-
7//C- This software is subject to, and may be distributed under, the
8//C- GNU General Public License, Version 2. The license should have
9//C- accompanied the software or you may obtain a copy of the license
10//C- from the Free Software Foundation at http://www.fsf.org .
11//C-
12//C- This program is distributed in the hope that it will be useful,
13//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
14//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15//C- GNU General Public License for more details.
16//C-
17//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library
18//C- distributed by Lizardtech Software.  On July 19th 2002, Lizardtech
19//C- Software authorized us to replace the original DjVu(r) Reference
20//C- Library notice by the following text (see doc/lizard2002.djvu):
21//C-
22//C-  ------------------------------------------------------------------
23//C- | DjVu (r) Reference Library (v. 3.5)
24//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
25//C- | The DjVu Reference Library is protected by U.S. Pat. No.
26//C- | 6,058,214 and patents pending.
27//C- |
28//C- | This software is subject to, and may be distributed under, the
29//C- | GNU General Public License, Version 2. The license should have
30//C- | accompanied the software or you may obtain a copy of the license
31//C- | from the Free Software Foundation at http://www.fsf.org .
32//C- |
33//C- | The computer code originally released by LizardTech under this
34//C- | license and unmodified by other parties is deemed "the LIZARDTECH
35//C- | ORIGINAL CODE."  Subject to any third party intellectual property
36//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
37//C- | non-exclusive license to make, use, sell, or otherwise dispose of
38//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
39//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
40//C- | General Public License.   This grant only confers the right to
41//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
42//C- | the extent such infringement is reasonably necessary to enable
43//C- | recipient to make, have made, practice, sell, or otherwise dispose
44//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
45//C- | any greater extent that may be necessary to utilize further
46//C- | modifications or combinations.
47//C- |
48//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
49//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
50//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
51//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
52//C- +------------------------------------------------------------------
53//
54// $Id: XMLParser.cpp,v 1.10 2003/11/07 22:08:22 leonb Exp $
55// $Name:  $
56
57#ifdef HAVE_CONFIG_H
58# include "config.h"
59#endif
60#if NEED_GNUG_PRAGMAS
61# pragma implementation
62#endif
63
64// From: Leon Bottou, 1/31/2002
65// This is purely Lizardtech stuff.
66
67#include "XMLParser.h"
68#include "XMLTags.h"
69#include "ByteStream.h"
70#include "GOS.h"
71#include "DjVuDocument.h"
72#include "DjVuText.h"
73#include "DjVuAnno.h"
74#include "DjVuFile.h"
75#include "DjVuImage.h"
76#include "debug.h"
77#include <stdio.h>
78#include <ctype.h>
79#include <stdlib.h>
80
81
82#ifdef HAVE_NAMESPACES
83namespace DJVU {
84# ifdef NOT_DEFINED // Just to fool emacs c++ mode
85}
86#endif
87#endif
88
89static const char mimetype[]="image/x.djvu";
90static const char bodytag[]="BODY";
91static const char areatag[]="AREA";
92static const char maptag[]="MAP";
93static const char objecttag[]="OBJECT";
94static const char paramtag[]="PARAM";
95static const char wordtag[]="WORD";
96static const char linetag[]="LINE";
97static const char paragraphtag[]="PARAGRAPH";
98static const char regiontag[]="REGION";
99static const char pagecolumntag[]="PAGECOLUMN";
100static const char hiddentexttag[]="HIDDENTEXT";
101static const char metadatatag[]="METADATA";
102
103class lt_XMLParser::Impl : public lt_XMLParser
104{
105public:
106  Impl(void);
107  virtual ~Impl();
108  /// Parse the specified bytestream.
109  virtual void parse(const GP<ByteStream> &bs);
110  /// Parse the specified tags - this one does all the work
111  virtual void parse(const lt_XMLTags &tags);
112  /// write to disk.
113  virtual void save(void);
114  /// erase.
115  virtual void empty(void);
116protected:
117  GP<DjVuFile> get_file(const GURL &url,GUTF8String page);
118
119  void parse_anno(const int width, const int height,
120    const lt_XMLTags &GObject,
121    GMap<GUTF8String,GP<lt_XMLTags> > &Maps, DjVuFile &dfile);
122
123  void parse_text(const int width, const int height,
124    const lt_XMLTags &GObject, DjVuFile &dfile);
125
126  void parse_meta(const lt_XMLTags &GObject, DjVuFile &dfile);
127
128  void ChangeAnno( const int width, const int height,
129    DjVuFile &dfile, const lt_XMLTags &map);
130
131  void ChangeInfo(DjVuFile &dfile,const int dpi,const double gamma);
132
133  void ChangeText( const int width, const int height,
134    DjVuFile &dfile, const lt_XMLTags &map);
135
136  void ChangeMeta( DjVuFile &dfile, const lt_XMLTags &map);
137
138  void ChangeTextOCR( const GUTF8String &value, 
139    const int width, const int height,
140    const GP<DjVuFile> &dfile);
141
142  // we may want to make these list of modified file static so
143  // they only needed to be loaded and saved once.
144
145  GMap<GUTF8String,GP<DjVuFile> > m_files;
146  GMap<GUTF8String,GP<DjVuDocument> > m_docs;
147
148  GURL m_codebase; 
149  GCriticalSection xmlparser_lock;
150};
151
152static GP<ByteStream>
153OCRcallback(
154  void * const xarg,
155  lt_XMLParser::mapOCRcallback * const xcallback,
156  const GUTF8String &value=GUTF8String(),
157  const GP<DjVuImage> &image=0 );
158
159static inline GP<ByteStream>
160OCRcallback(const GUTF8String &value, const GP<DjVuImage> &image)
161{
162  return OCRcallback(0,0,value,image);
163}
164
165lt_XMLParser::lt_XMLParser() {}
166lt_XMLParser::~lt_XMLParser() {}
167lt_XMLParser::Impl::Impl() {}
168lt_XMLParser::Impl::~Impl() {}
169
170GP<lt_XMLParser>
171lt_XMLParser::create(void)
172{
173  return new lt_XMLParser::Impl;
174}
175
176// helper function for args
177static void 
178intList(GUTF8String coords, GList<int> &retval)
179{
180  int pos=0;
181  while(coords.length())
182  {
183    int epos;
184    unsigned long i=coords.toLong(pos,epos,10);
185    if(epos>=0)
186    {
187      retval.append(i);
188      const int n=coords.nextNonSpace(epos);
189      if(coords[n] != ',')
190        break;
191      pos=n+1;
192    }
193  }
194}
195
196void 
197lt_XMLParser::Impl::empty(void)
198{
199  GCriticalSectionLock lock(&xmlparser_lock);
200  m_files.empty();
201  m_docs.empty();
202}
203
204void 
205lt_XMLParser::Impl::save(void)
206{
207  GCriticalSectionLock lock(&xmlparser_lock);
208  for(GPosition pos=m_docs;pos;++pos)
209  {
210    const GP<DjVuDocument> doc(m_docs[pos]);
211    const GURL url=doc->get_init_url();
212   
213    DEBUG_MSG("Saving "<<(const char *)url<<" with new text and annotations\n");
214    const bool bundle=doc->is_bundled()||(doc->get_doc_type()==DjVuDocument::SINGLE_PAGE);
215    doc->save_as(url,bundle);
216  }
217  empty();
218}
219
220void
221lt_XMLParser::Impl::parse(const GP<ByteStream> &bs)
222{
223  const GP<lt_XMLTags> tags(lt_XMLTags::create(bs));
224  parse(*tags);
225}
226 
227static const GMap<GUTF8String,GMapArea::BorderType> &
228BorderTypeMap(void)
229{
230  static GMap<GUTF8String,GMapArea::BorderType> typeMap;
231  if (! typeMap.size()) 
232    {
233      typeMap["none"]=GMapArea::NO_BORDER;
234      typeMap["xor"]=GMapArea::XOR_BORDER;
235      typeMap["solid"]=GMapArea::SOLID_BORDER;
236      typeMap["default"]=GMapArea::SOLID_BORDER;
237      typeMap["shadowout"]=GMapArea::SHADOW_OUT_BORDER;
238      typeMap["shadowin"]=GMapArea::SHADOW_IN_BORDER;
239      typeMap["etchedin"]=GMapArea::SHADOW_EIN_BORDER;
240      typeMap["etchedout"]=GMapArea::SHADOW_EOUT_BORDER;
241    }
242  return typeMap;
243}
244
245static unsigned long
246convertToColor(const GUTF8String &s)
247{
248  unsigned long retval=0;
249  if(s.length())
250  {
251    int endpos;
252    if(s[0] == '#')
253    {
254      retval=s.substr(1,-1).toULong(0,endpos,16);
255    }
256    if(endpos < 0)
257    {
258      G_THROW( (ERR_MSG("XMLAnno.bad_color") "\t")+s );
259    }
260  }
261  return retval;
262}
263
264void
265lt_XMLParser::Impl::ChangeInfo(DjVuFile &dfile,const int dpi,const double gamma)
266{
267  GP<DjVuInfo> info;
268  if(dpi >= 5 && dpi <= 4800)
269  {
270    dfile.resume_decode(true);
271    if(dfile.info && (dpi != dfile.info->dpi) )
272    {
273      info=new DjVuInfo(*dfile.info);
274      info->dpi=dpi;
275    }
276  }
277  if(gamma >= 0.1 && gamma <= 5.0)
278  {
279    dfile.resume_decode(true);
280    if(dfile.info && (gamma != dfile.info->gamma) )
281    {
282      if(!info)
283        info=new DjVuInfo(*dfile.info);
284      info->gamma=gamma;
285    }
286  }
287  if(info)
288  {
289    dfile.change_info(info);
290  }
291}
292
293void
294lt_XMLParser::Impl::ChangeAnno(
295  const int width, const int height,
296  DjVuFile &dfile, 
297  const lt_XMLTags &map )
298{
299  dfile.resume_decode(true);
300  const GP<DjVuInfo> info(dfile.info);
301  const GP<DjVuAnno> ganno(DjVuAnno::create());
302  DjVuAnno &anno=*ganno;
303  GPosition map_pos;
304  map_pos=map.contains(areatag);
305  if(dfile.contains_anno())
306  {
307    GP<ByteStream> annobs=dfile.get_merged_anno();
308    if(annobs)
309    {
310      anno.decode(annobs);
311      if(anno.ant && info)
312      {
313        anno.ant->map_areas.empty();
314      }
315    }
316//    dfile.remove_anno();
317  }
318  if(info && map_pos)
319  {
320    const int h=info->height;
321    const int w=info->width;
322    double ws=1.0;
323    double hs=1.0;
324    if(width && width != w)
325    {
326      ws=((double)w)/((double)width); 
327    }
328    if(height && height != h)
329    {
330      hs=((double)h)/((double)height); 
331    }
332    if(!anno.ant)
333    {
334      anno.ant=DjVuANT::create();
335    }
336    GPList<GMapArea> &map_areas=anno.ant->map_areas;
337    map_areas.empty();
338    GPList<lt_XMLTags> gareas=map[map_pos];
339    for(GPosition pos=gareas;pos;++pos)
340    {
341      if(gareas[pos])
342      {
343        lt_XMLTags &areas=*(gareas[pos]);
344        GMap<GUTF8String,GUTF8String> args(areas.get_args());
345        GList<int> coords;
346        // ******************************************************
347        // Parse the coords attribute:  first read the raw data into
348        // a list, then scale the x, y data into another list.  For
349        // circles, you also get a radius element with (looks like an x
350        // with no matching y).
351        // ******************************************************
352        {
353          GPosition coords_pos=args.contains("coords");
354          if(coords_pos)
355          {
356            GList<int> raw_coords;
357            intList(args[coords_pos],raw_coords);
358            for(GPosition raw_pos=raw_coords;raw_pos;++raw_pos)
359            {
360              const int r=raw_coords[raw_pos];
361              const int x=(int)(ws*(double)r+0.5);
362              coords.append(x);
363              int y=h-1;
364              if(! ++raw_pos)
365              {
366                y-=(int)(hs*(double)r+0.5);
367              }else
368              {
369                y-=(int)(hs*(double)raw_coords[raw_pos]+0.5);
370              }
371              coords.append(y);
372//            DjVuPrintMessage("Coords (%d,%d)\n",x,y);
373            }
374          }
375        }
376        GUTF8String shape;
377        {
378          GPosition shape_pos=args.contains("shape");
379          if(shape_pos)
380          {
381            shape=args[shape_pos];
382          }
383        }
384        GP<GMapArea> a;
385        if(shape == "default")
386        {
387          GRect rect(0,0,w,h);
388          a=GMapRect::create(rect);
389        }else if(!shape.length() || shape == "rect")
390        {
391          int xx[4];
392          int i=0;
393          for(GPosition rect_pos=coords;(rect_pos)&&(i<4);++rect_pos,++i)
394          {
395            xx[i]=coords[rect_pos];
396          }
397          if(i!=4)
398          {
399            G_THROW( ERR_MSG("XMLAnno.bad_rect") );
400          }
401          int xmin,xmax; 
402          if(xx[0]>xx[2])
403          {
404            xmax=xx[0];
405            xmin=xx[2];
406          }else
407          {
408            xmin=xx[0];
409            xmax=xx[2];
410          }
411          int ymin,ymax; 
412          if(xx[1]>xx[3])
413          {
414            ymax=xx[1];
415            ymin=xx[3];
416          }else
417          {
418            ymin=xx[1];
419            ymax=xx[3];
420          }
421          GRect rect(xmin,ymin,xmax-xmin,ymax-ymin);
422          a=GMapRect::create(rect);
423        }else if(shape == "circle")
424        {
425          int xx[4];
426          int i=0;
427          GPosition rect_pos=coords.lastpos();
428          if(rect_pos)
429          {
430            coords.append(coords[rect_pos]);
431            for(rect_pos=coords;(rect_pos)&&(i<4);++rect_pos)
432            {
433              xx[i++]=coords[rect_pos];
434            }
435          }
436          if(i!=4)
437          {
438            G_THROW( ERR_MSG("XMLAnno.bad_circle") );
439          }
440          int x=xx[0],y=xx[1],rx=xx[2],ry=(h-xx[3])-1;
441          GRect rect(x-rx,y-ry,2*rx,2*ry);
442          a=GMapOval::create(rect);
443        }else if(shape == "oval")
444        {
445          int xx[4];
446          int i=0;
447          for(GPosition rect_pos=coords;(rect_pos)&&(i<4);++rect_pos,++i)
448          {
449            xx[i]=coords[rect_pos];
450          }
451          if(i!=4)
452          {
453            G_THROW( ERR_MSG("XMLAnno.bad_oval") );
454          }
455          int xmin,xmax; 
456          if(xx[0]>xx[2])
457          {
458            xmax=xx[0];
459            xmin=xx[2];
460          }else
461          {
462            xmin=xx[0];
463            xmax=xx[2];
464          }
465          int ymin,ymax; 
466          if(xx[1]>xx[3])
467          {
468            ymax=xx[1];
469            ymin=xx[3];
470          }else
471          {
472            ymin=xx[1];
473            ymax=xx[3];
474          }
475          GRect rect(xmin,ymin,xmax-xmin,ymax-ymin);
476          a=GMapOval::create(rect);
477        }else if(shape == "poly")
478        {
479          GP<GMapPoly> p=GMapPoly::create();
480          for(GPosition poly_pos=coords;poly_pos;++poly_pos)
481          {
482            int x=coords[poly_pos];
483            if(! ++poly_pos)
484              break;
485            int y=coords[poly_pos];
486            p->add_vertex(x,y);
487          }
488          p->close_poly();
489          a=p;
490        }else
491        {
492          G_THROW( ( ERR_MSG("XMLAnno.unknown_shape") "\t")+shape );
493        }
494        if(a)
495        {
496          GPosition pos;
497          if((pos=args.contains("href")))
498          {
499            a->url=args[pos];
500          }
501          if((pos=args.contains("target")))
502          {
503            a->target=args[pos];
504          }
505          if((pos=args.contains("alt")))
506          {
507            a->comment=args[pos];
508          }
509          if((pos=args.contains("bordertype")))
510          {
511            GUTF8String b=args[pos];
512            static const GMap<GUTF8String,GMapArea::BorderType> typeMap=BorderTypeMap();
513            if((pos=typeMap.contains(b)))
514            {
515              a->border_type=typeMap[pos];
516            }else
517            {
518              G_THROW( (ERR_MSG("XMLAnno.unknown_border") "\t")+b );
519            }
520          }
521          a->border_always_visible=!!args.contains("visible");
522          if((pos=args.contains("bordercolor")))
523          {
524            a->border_color=convertToColor(args[pos]);
525          }
526          if((pos=args.contains("highlight")))
527          {
528            a->hilite_color=convertToColor(args[pos]);
529          }
530          if((pos=args.contains("border")))
531          {
532             a->border_width=args[pos].toInt(); //atoi(args[pos]);
533          }
534          map_areas.append(a);
535        }
536      }
537    }
538  }
539  dfile.set_modified(true);
540  dfile.anno=ByteStream::create();
541  anno.encode(dfile.anno);
542}
543
544GP<DjVuFile>
545lt_XMLParser::Impl::get_file(const GURL &url,GUTF8String id)
546{
547  GP<DjVuFile> dfile;
548  GP<DjVuDocument> doc;
549  GCriticalSectionLock lock(&xmlparser_lock);
550  {
551    GPosition pos=m_docs.contains(url.get_string());
552    if(pos)
553    {
554      doc=m_docs[pos];
555    }else
556    {
557      doc=DjVuDocument::create_wait(url);
558      if(! doc->wait_for_complete_init())
559      {
560        G_THROW(( ERR_MSG("XMLAnno.fail_init") "\t")+url.get_string() );
561      }
562      m_docs[url.get_string()]=doc;
563    }
564    if(id.is_int())
565    {
566      const int xpage=id.toInt(); //atoi((char const *)page);
567      if(xpage>0)
568        id=doc->page_to_id(xpage-1);
569    }else if(!id.length())
570    { 
571      id=doc->page_to_id(0);
572    }
573  }
574  const GURL fileurl(doc->id_to_url(id));
575  GPosition dpos(m_files.contains(fileurl.get_string()));
576  if(!dpos)
577  {
578    if(!doc->get_id_list().contains(id))
579    {
580      G_THROW( ERR_MSG("XMLAnno.bad_page") );
581    }
582    dfile=doc->get_djvu_file(id,false);
583    if(!dfile)
584    {
585      G_THROW( ERR_MSG("XMLAnno.bad_page") );
586    }
587    m_files[fileurl.get_string()]=dfile;
588  }else
589  {
590    dfile=m_files[dpos];
591  }
592  return dfile;
593}
594 
595void
596lt_XMLParser::Impl::parse(const lt_XMLTags &tags)
597{
598  const GPList<lt_XMLTags> Body(tags.get_Tags(bodytag));
599  GPosition pos=Body;
600 
601  if(!pos || (pos != Body.lastpos()))
602  {
603    G_THROW( ERR_MSG("XMLAnno.extra_body") );
604  }
605  const GP<lt_XMLTags> GBody(Body[pos]);
606  if(!GBody)
607  {
608    G_THROW( ERR_MSG("XMLAnno.no_body") );
609  }
610
611  GMap<GUTF8String,GP<lt_XMLTags> > Maps;
612  lt_XMLTags::get_Maps(maptag,"name",Body,Maps);
613
614  const GPList<lt_XMLTags> Objects(GBody->get_Tags(objecttag));
615  lt_XMLTags::get_Maps(maptag,"name",Objects,Maps);
616
617  for(GPosition Objpos=Objects;Objpos;++Objpos)
618  {
619    lt_XMLTags &GObject=*Objects[Objpos];
620    // Map of attributes to value (e.g. "width" --> "500")
621    const GMap<GUTF8String,GUTF8String> &args=GObject.get_args();
622    GURL codebase;
623    {
624      DEBUG_MSG("Setting up codebase... m_codebase = " << m_codebase << "\n");
625      GPosition codebasePos=args.contains("codebase");
626      // If user specified a codebase attribute, assume it is correct (absolute URL):
627      //  the GURL constructor will throw an exception if it isn't
628      if(codebasePos)
629      {
630        codebase=GURL::UTF8(args[codebasePos]);
631      }else if (m_codebase.is_dir())
632      {
633        codebase=m_codebase;
634      }else
635      {
636        codebase=GURL::Filename::UTF8(GOS::cwd());
637      }
638      DEBUG_MSG("codebase = " << codebase << "\n");
639    }
640    // the data attribute specifies the input file.  This can be
641    //  either an absolute URL (starts with file:/) or a relative
642    //  URL (for now, just a path and file name).  If it's absolute,
643    //  our GURL will adequately wrap it.  If it's relative, we need
644    //  to use the codebase attribute to form an absolute URL first.
645    GPosition datapos=args.contains("data");
646    if(datapos)
647    {
648      bool isDjVuType=false;
649      GPosition typePos(args.contains("type"));
650      if(typePos)
651      {
652        if(args[typePos] != mimetype)
653        {
654//          DjVuPrintErrorUTF8("Ignoring %s Object tag\n",mimetype);
655          continue;
656        }
657        isDjVuType=true;
658      }
659      const GURL url=GURL::UTF8(args[datapos],(args[datapos][0] == '/')?codebase.base():codebase);
660      int width;
661      {
662        GPosition widthPos=args.contains("width");
663        width=(widthPos)?args[widthPos].toInt():0;
664      }
665      int height;
666      {
667        GPosition heightPos=args.contains("height");
668        height=(heightPos)?args[heightPos].toInt():0;
669      }
670      GUTF8String gamma;
671      GUTF8String dpi;
672      GUTF8String page;
673      GUTF8String do_ocr;
674      {
675        GPosition paramPos(GObject.contains(paramtag));
676        if(paramPos)
677        {
678          const GPList<lt_XMLTags> Params(GObject[paramPos]);
679          for(GPosition loc=Params;loc;++loc)
680          {
681            const GMap<GUTF8String,GUTF8String> &pargs=Params[loc]->get_args();
682            GPosition namepos=pargs.contains("name");
683            if(namepos)
684            {
685              GPosition valuepos=pargs.contains("value");
686              if(valuepos)
687              {
688                const GUTF8String name=pargs[namepos].downcase();
689                const GUTF8String &value=pargs[valuepos];
690                if(name == "flags")
691                {
692                  GMap<GUTF8String,GUTF8String> args;
693                  lt_XMLTags::ParseValues(value,args,true);
694                  if(args.contains("page"))
695                  {
696                    page=args["page"];
697                  }
698                  if(args.contains("dpi"))
699                  {
700                    dpi=args["dpi"];
701                  }
702                  if(args.contains("gamma"))
703                  {
704                    gamma=args["gamma"];
705                  }
706                  if(args.contains("ocr"))
707                  {
708                    do_ocr=args["ocr"];
709                  }
710                }else if(name == "page")
711                {
712                  page=value;
713                }else if(name == "dpi")
714                {
715                  dpi=value;
716                }else if(name == "gamma")
717                {
718                  gamma=value;
719                }else if(name == "ocr")
720                {
721                  do_ocr=value;
722                }
723              }
724            }
725          }
726        }
727      }
728      const GP<DjVuFile> dfile(get_file(url,page));
729      if(dpi.is_int() || gamma.is_float())
730      {
731        int pos=0;
732        ChangeInfo(*dfile,dpi.toInt(),gamma.toDouble(pos,pos));
733      }
734      parse_anno(width,height,GObject,Maps,*dfile);
735      parse_meta(GObject,*dfile);
736      parse_text(width,height,GObject,*dfile);
737      ChangeTextOCR(do_ocr,width,height,dfile);
738    }
739  }
740}
741
742void
743lt_XMLParser::Impl::parse_anno(
744  const int width,
745  const int height,
746  const lt_XMLTags &GObject,
747  GMap<GUTF8String,GP<lt_XMLTags> > &Maps,
748  DjVuFile &dfile )
749{
750  GP<lt_XMLTags> map;
751  {
752    GPosition usemappos=GObject.get_args().contains("usemap");
753    if(usemappos)
754    {
755      const GUTF8String mapname(GObject.get_args()[usemappos]);
756      GPosition mappos=Maps.contains(mapname);
757      if(!mappos)
758      {
759        G_THROW((ERR_MSG("XMLAnno.map_find") "\t")+mapname );
760      }else
761      {
762        map=Maps[mappos];
763      }
764    }
765  }
766  if(map)
767  {
768    ChangeAnno(width,height,dfile,*map);
769  }
770}
771
772#ifdef max
773#undef max
774#endif
775template<class TYPE>
776static inline TYPE max(TYPE a,TYPE b) { return (a>b)?a:b; }
777#ifdef min
778#undef min
779#endif
780template<class TYPE>
781static inline TYPE min(TYPE a,TYPE b) { return (a<b)?a:b; }
782
783// used to build the zone tree
784// true is returned if the GRect is known for this object,
785// and false, if the rectangle's size is just the parent size.
786static bool
787make_child_layer(
788  DjVuTXT::Zone &parent,
789  const lt_XMLTags &tag, ByteStream &bs,
790  const int height, const double ws, const double hs)
791{
792  bool retval=true;
793  // the plugin thinks there are only Pages, Lines and Words
794  // so we don't make Paragraphs, Regions and Columns zones
795  // if we did the plugin is not able to search the text but
796  // DjVuToText writes out all the text anyway
797  DjVuTXT::Zone *self_ptr;
798  char sepchar;
799  const GUTF8String name(tag.get_name());
800  if(name == wordtag)
801  {
802    self_ptr=parent.append_child();
803    self_ptr->ztype = DjVuTXT::WORD;
804    sepchar=' ';
805  }else if(name == linetag)
806  {
807    self_ptr=parent.append_child();
808    self_ptr->ztype = DjVuTXT::LINE;
809    sepchar=DjVuTXT::end_of_line;
810  }else if(name == paragraphtag)
811  {
812    self_ptr=parent.append_child();
813    self_ptr->ztype = DjVuTXT::PARAGRAPH;
814    sepchar=DjVuTXT::end_of_paragraph;
815  }else if(name == regiontag)
816  {
817    self_ptr=parent.append_child();
818    self_ptr->ztype = DjVuTXT::REGION;
819    sepchar=DjVuTXT::end_of_region;
820  }else if(name == pagecolumntag)
821  {
822    self_ptr=parent.append_child();
823    self_ptr->ztype = DjVuTXT::COLUMN;
824    sepchar=DjVuTXT::end_of_column;
825  }else
826  {
827    self_ptr = &parent;
828    self_ptr->ztype = DjVuTXT::PAGE;
829    sepchar=0;
830  }
831  DjVuTXT::Zone &self = *self_ptr;
832  self.text_start = bs.tell();
833  int &xmin=self.rect.xmin, &ymin=self.rect.ymin, 
834    &xmax=self.rect.xmax, &ymax=self.rect.ymax;
835  GRect default_rect;
836  default_rect.xmin=max(parent.rect.xmax,parent.rect.xmin);
837  default_rect.xmax=min(parent.rect.xmax,parent.rect.xmin);
838  default_rect.ymin=max(parent.rect.ymax,parent.rect.ymin);
839  default_rect.ymax=min(parent.rect.ymax,parent.rect.ymin);
840  // Now if there are coordinates, use those.
841  GPosition pos(tag.get_args().contains("coords"));
842  if(pos)
843  {
844    GList<int> rectArgs;
845    intList(tag.get_args()[pos], rectArgs);
846    if((pos=rectArgs))
847    {
848      xmin=(int)(ws*(double)rectArgs[pos]);
849      if(++pos)
850      {
851        ymin=(height-1)-(int)(hs*(double)rectArgs[pos]);
852        if(++pos)
853        {
854          xmax=(int)(ws*(double)rectArgs[pos]);
855          if(++pos)
856          {
857            ymax=(height-1)-(int)(hs*(double)rectArgs[pos]);
858            if(xmin>xmax) // Make sure xmin is really minimum
859            {
860              const int t=xmin;
861              xmin=xmax;
862              xmax=t;
863            }
864            if(ymin>ymax) // Make sure ymin is really minimum
865            {
866              const int t=ymin;
867              ymin=ymax;
868              ymax=t;
869            }
870          }
871        }
872      }
873    }
874  }
875  if(self.ztype == DjVuTXT::WORD)
876  {
877    if(! pos)
878    {
879      self.rect=default_rect;
880      retval=false;
881    }
882    const GUTF8String raw(tag.get_raw().fromEscaped());
883    const int i=raw.nextNonSpace(0);
884    bs.writestring(raw.substr(i,raw.firstEndSpace(i)-i));
885    if(sepchar)
886      bs.write8(sepchar);
887    self.text_length = bs.tell() - self.text_start;
888  }else if(pos)
889  {
890    pos=tag.get_content();
891    if(pos)
892    {
893      for(pos=tag.get_content(); pos; ++pos)
894      {
895        const GP<lt_XMLTags> t(tag.get_content()[pos].tag);
896        make_child_layer(self, *t, bs, height,ws,hs);
897      }
898      if(sepchar)
899        bs.write8(sepchar);
900      self.text_length = bs.tell() - self.text_start;
901    }else
902    {
903      const GUTF8String raw(tag.get_raw().fromEscaped());
904      const int i=raw.nextNonSpace(0);
905      bs.writestring(raw.substr(i,raw.firstEndSpace(i)-i));
906      if(sepchar)
907        bs.write8(sepchar);
908      self.text_length = bs.tell() - self.text_start;
909    }
910  }else
911  {
912    self.rect=default_rect;
913    if((pos=tag.get_content()))
914    {
915      do
916      {
917        const GP<lt_XMLTags> t(tag.get_content()[pos].tag);
918        const GRect save_rect(self.rect);
919        self.rect=default_rect;
920        if(retval=make_child_layer(self, *t, bs, height,ws,hs))
921        {
922          xmin=min(save_rect.xmin,xmin);
923          xmax=max(save_rect.xmax,xmax);
924          ymin=min(save_rect.ymin,ymin);
925          ymax=max(save_rect.ymax,ymax);
926        }else
927        {
928          // If the child doesn't have coordinates, we need to use a box
929          // at least as big as the parent's coordinates.
930          xmin=min(save_rect.xmin,default_rect.xmax);
931          xmax=max(save_rect.xmax,default_rect.xmin);
932          ymin=min(save_rect.ymin,default_rect.ymax);
933          ymax=max(save_rect.ymax,default_rect.ymin);
934          for(; pos; ++pos)
935          {
936            const GP<lt_XMLTags> t(tag.get_content()[pos].tag);
937            make_child_layer(self, *t, bs, height,ws,hs);
938          }
939          break;
940        }
941      } while(++pos);
942      if(sepchar)
943        bs.write8(sepchar);
944      self.text_length = bs.tell() - self.text_start;
945    }else
946    {
947      const GUTF8String raw(tag.get_raw().fromEscaped());
948      const int i=raw.nextNonSpace(0);
949      bs.writestring(raw.substr(i,raw.firstEndSpace(i)-i));
950      if(sepchar)
951        bs.write8(sepchar);
952      self.text_length = bs.tell() - self.text_start;
953    }
954  }
955  parent.rect.xmin=min(xmin,parent.rect.xmin);
956  parent.rect.ymin=min(ymin,parent.rect.ymin);
957  parent.rect.xmax=max(xmax,parent.rect.xmax);
958  parent.rect.ymax=max(ymax,parent.rect.ymax);
959  if(xmin>xmax)
960  {
961    const int t=xmin;
962    xmin=xmax;
963    xmax=t;
964  }
965  if(ymin>ymax)
966  {
967    const int t=ymin;
968    ymin=ymax;
969    ymax=t;
970  }
971//  DjVuPrintMessage("(%d,%d)(%d,%d)<<<\\%o>>>\n",
972//    xmin,ymin,xmax,ymax, sepchar);
973  return retval;
974}
975
976void 
977lt_XMLParser::Impl::ChangeTextOCR(
978  const GUTF8String &value,
979  const int width,
980  const int height,
981  const GP<DjVuFile> &dfile)
982{
983  if(value.length() && value.downcase() != "false")
984  {
985    const GP<ByteStream> bs=OCRcallback(value,DjVuImage::create(dfile));
986    if( bs && bs->size() )
987    {
988      const GP<lt_XMLTags> tags(lt_XMLTags::create(bs));
989      ChangeText(width,height,*dfile,*tags);
990    }
991  }
992}
993
994void 
995lt_XMLParser::Impl::ChangeMeta(
996  DjVuFile &dfile, const lt_XMLTags &tags )
997{
998  dfile.resume_decode(true);
999  GP<ByteStream> gbs(ByteStream::create());
1000  tags.write(*gbs,false);
1001  gbs->seek(0L);
1002  GUTF8String raw(gbs->getAsUTF8());
1003  if(raw.length())
1004  {
1005     //GUTF8String gs="<"+(metadatatag+(">"+raw))+"</"+metadatatag+">\n");
1006    dfile.change_meta(raw+"\n");
1007  }else
1008  {
1009    dfile.change_meta(GUTF8String());
1010  }
1011}
1012
1013void 
1014lt_XMLParser::Impl::ChangeText(
1015  const int width, const int height,
1016  DjVuFile &dfile, const lt_XMLTags &tags )
1017{
1018  dfile.resume_decode(true);
1019 
1020  GP<DjVuText> text = DjVuText::create();
1021  GP<DjVuTXT> txt = text->txt = DjVuTXT::create();
1022 
1023  // to store the new text
1024  GP<ByteStream> textbs = ByteStream::create(); 
1025 
1026  GP<DjVuInfo> info=(dfile.info);
1027  if(info)
1028  {
1029    const int h=info->height;
1030    const int w=info->width;
1031    txt->page_zone.text_start = 0;
1032    DjVuTXT::Zone &parent=txt->page_zone;
1033    parent.rect.xmin=0;
1034    parent.rect.ymin=0;
1035    parent.rect.ymax=h;
1036    parent.rect.xmax=w;
1037    double ws=1.0;
1038    if(width && width != w)
1039    {
1040      ws=((double)w)/((double)width);
1041    }
1042    double hs=1.0;
1043    if(height && height != h)
1044    {
1045      hs=((double)h)/((double)height);
1046    }
1047    make_child_layer(parent, tags, *textbs, h, ws,hs);
1048    textbs->write8(0);
1049    long len = textbs->tell();
1050    txt->page_zone.text_length = len;
1051    textbs->seek(0,SEEK_SET);
1052    textbs->read(txt->textUTF8.getbuf(len), len);
1053 
1054    dfile.change_text(txt,false);
1055  }
1056}
1057
1058void
1059lt_XMLParser::Impl::parse_text(
1060  const int width,
1061  const int height,
1062  const lt_XMLTags &GObject,
1063  DjVuFile &dfile )
1064{
1065  GPosition textPos = GObject.contains(hiddentexttag);
1066  if(textPos)
1067  {
1068    // loop through the hidden text - there should only be one
1069    // if there are more ??only the last one will be saved??
1070    GPList<lt_XMLTags> textTags = GObject[textPos];
1071    GPosition pos = textTags;
1072    ChangeText(width,height,dfile,*textTags[pos]);
1073  }
1074}
1075
1076void
1077lt_XMLParser::Impl::parse_meta(
1078  const lt_XMLTags &GObject,
1079  DjVuFile &dfile )
1080{
1081  GPosition metaPos = GObject.contains(metadatatag);
1082  if(metaPos)
1083  {
1084    // loop through the hidden text - there should only be one
1085    // if there are more ??only the last one will be saved??
1086    GPList<lt_XMLTags> metaTags = GObject[metaPos];
1087    GPosition pos = metaTags;
1088    ChangeMeta(dfile,*metaTags[pos]);
1089  }
1090}
1091
1092static GP<ByteStream>
1093OCRcallback(
1094  void * const xarg,
1095  lt_XMLParser::mapOCRcallback * const xcallback,
1096  const GUTF8String &value,
1097  const GP<DjVuImage> &image )
1098{
1099  GP<ByteStream> retval;
1100  static void *arg=0;
1101  static lt_XMLParser::mapOCRcallback *callback=0;
1102  if(image)
1103  {
1104    if(callback)
1105      retval=callback(arg,value,image);
1106  }else
1107  {
1108    arg=xarg;
1109    callback=xcallback;
1110  }
1111  return retval;
1112}
1113
1114void
1115lt_XMLParser::setOCRcallback(
1116  void * const arg,
1117  mapOCRcallback * const callback)
1118{
1119  ::OCRcallback(arg,callback);
1120}
1121
1122
1123#ifdef HAVE_NAMESPACES
1124}
1125# ifndef NOT_USING_DJVU_NAMESPACE
1126using namespace DJVU;
1127# endif
1128#endif
Note: See TracBrowser for help on using the repository browser.