source: trunk/libdjvu/XMLParser.cpp @ 209

Last change on this file since 209 was 206, checked in by Eugene Romanenko, 14 years ago

DJVU plugin: djvulibre updated to version 3.5.19

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