source: trunk/libdjvu/DjVuText.cpp @ 206

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

DJVU plugin: djvulibre updated to version 3.5.19

File size: 24.1 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: DjVuText.cpp,v 1.11 2007/03/25 20:48:31 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#include "DjVuText.h"
67#include "IFFByteStream.h"
68#include "BSByteStream.h"
69#include "debug.h"
70#include <ctype.h>
71
72
73
74#ifdef HAVE_NAMESPACES
75namespace DJVU {
76# ifdef NOT_DEFINED // Just to fool emacs c++ mode
77}
78#endif
79#endif
80
81
82
83#ifdef min
84#undef min
85#endif
86template<class TYPE>
87static inline TYPE min(TYPE a,TYPE b) { return (a<b)?a:b; }
88
89//***************************************************************************
90//******************************** DjVuTXT **********************************
91//***************************************************************************
92
93const char DjVuTXT::end_of_column    = 013;      // VT: Vertical Tab
94const char DjVuTXT::end_of_region    = 035;      // GS: Group Separator
95const char DjVuTXT::end_of_paragraph = 037;      // US: Unit Separator
96const char DjVuTXT::end_of_line      = 012;      // LF: Line Feed
97
98const int DjVuTXT::Zone::version  = 1;
99
100DjVuTXT::Zone::Zone()
101  : ztype(DjVuTXT::PAGE), text_start(0), text_length(0), zone_parent(0)
102{
103}
104
105DjVuTXT::Zone *
106DjVuTXT::Zone::append_child()
107{
108  Zone empty;
109  empty.ztype = ztype;
110  empty.text_start = 0;
111  empty.text_length = 0;
112  empty.zone_parent=this;
113  children.append(empty);
114  return & children[children.lastpos()];
115}
116
117void
118DjVuTXT::Zone::cleartext()
119{
120  text_start = 0;
121  text_length = 0;
122  for (GPosition i=children; i; ++i)
123    children[i].cleartext();
124}
125
126void
127DjVuTXT::Zone::normtext(const char *instr, GUTF8String &outstr)
128{
129  if (text_length == 0)
130    {
131      // Descend collecting text below
132      text_start = outstr.length();
133      for (GPosition i=children; i; ++i)
134        children[i].normtext(instr, outstr);
135      text_length = outstr.length() - text_start;
136      // Ignore empty zones
137      if (text_length == 0)
138        return;
139    }
140  else
141    {
142      // Collect text at this level
143      int new_start = outstr.length();
144      outstr = outstr + GUTF8String(instr+text_start, text_length);
145      text_start = new_start;
146      // Clear textual information on lower level nodes
147      for (GPosition i=children; i; ++i)
148        children[i].cleartext();
149    }
150  // Determine standard separator
151  char sep;
152  switch (ztype)
153    {
154    case COLUMN:
155      sep = end_of_column; break;
156    case REGION:
157      sep = end_of_region; break;
158    case PARAGRAPH: 
159      sep = end_of_paragraph; break;
160    case LINE:
161      sep = end_of_line; break;
162    case WORD:
163      sep = ' '; break;
164    default:
165      return;
166    }
167  // Add separator if not present yet.
168  if (outstr[text_start+text_length-1] != sep)
169    {
170      outstr = outstr + GUTF8String(&sep, 1);
171      text_length += 1;
172    }
173}
174
175unsigned int 
176DjVuTXT::Zone::memuse() const
177{
178  int memuse = sizeof(*this);
179  for (GPosition i=children; i; ++i)
180    memuse += children[i].memuse();
181  return memuse;
182}
183
184
185#ifndef NEED_DECODER_ONLY
186void 
187DjVuTXT::Zone::encode(
188  const GP<ByteStream> &gbs, const Zone * parent, const Zone * prev) const
189{
190  ByteStream &bs=*gbs;
191  // Encode type
192  bs.write8(ztype);
193 
194  // Modify text_start and bounding rectangle based on the context
195  // (whether there is a previous non-zero same-level-child or parent)
196  int start=text_start;
197  int x=rect.xmin, y=rect.ymin;
198  int width=rect.width(), height=rect.height();
199  if (prev)
200  {
201    if (ztype==PAGE || ztype==PARAGRAPH || ztype==LINE)
202    {
203      // Encode offset from the lower left corner of the previous
204      // child in the coord system in that corner with x to the
205      // right and y down
206      x=x-prev->rect.xmin;
207      y=prev->rect.ymin-(y+height);
208    } else // Either COLUMN or WORD or CHARACTER
209    {
210      // Encode offset from the lower right corner of the previous
211      // child in the coord system in that corner with x to the
212      // right and y up
213      x=x-prev->rect.xmax;
214      y=y-prev->rect.ymin;
215    }
216    start-=prev->text_start+prev->text_length;
217  } else if (parent)
218  {
219    // Encode offset from the upper left corner of the parent
220    // in the coord system in that corner with x to the right and y down
221    x=x-parent->rect.xmin;
222    y=parent->rect.ymax-(y+height);
223    start-=parent->text_start;
224  }
225  // Encode rectangle
226  bs.write16(0x8000+x);
227  bs.write16(0x8000+y);
228  bs.write16(0x8000+width);
229  bs.write16(0x8000+height);
230  // Encode text info
231  bs.write16(0x8000+start);
232  bs.write24(text_length);
233  // Encode number of children
234  bs.write24(children.size());
235 
236  const Zone * prev_child=0;
237  // Encode all children
238  for (GPosition i=children; i; ++i)
239  {
240    children[i].encode(gbs, this, prev_child);
241    prev_child=&children[i];
242  }
243}
244#endif
245
246void 
247DjVuTXT::Zone::decode(const GP<ByteStream> &gbs, int maxtext,
248                      const Zone * parent, const Zone * prev)
249{
250  ByteStream &bs=*gbs;
251  // Decode type
252  ztype = (ZoneType) bs.read8();
253  if ( ztype<PAGE || ztype>CHARACTER )
254    G_THROW( ERR_MSG("DjVuText.corrupt_text") );
255
256  // Decode coordinates
257  int x=(int) bs.read16()-0x8000;
258  int y=(int) bs.read16()-0x8000;
259  int width=(int) bs.read16()-0x8000;
260  int height=(int) bs.read16()-0x8000;
261
262  // Decode text info
263  text_start = (int) bs.read16()-0x8000;
264//  int start=text_start;
265  text_length = bs.read24();
266  if (prev)
267  {
268    if (ztype==PAGE || ztype==PARAGRAPH || ztype==LINE)
269    {
270      x=x+prev->rect.xmin;
271      y=prev->rect.ymin-(y+height);
272    } else // Either COLUMN or WORD or CHARACTER
273    {
274      x=x+prev->rect.xmax;
275      y=y+prev->rect.ymin;
276    }
277    text_start+=prev->text_start+prev->text_length;
278  } else if (parent)
279  {
280    x=x+parent->rect.xmin;
281    y=parent->rect.ymax-(y+height);
282    text_start+=parent->text_start;
283  }
284  rect=GRect(x, y, width, height);
285  // Get children size
286  int size = bs.read24();
287
288  // Checks
289  if (rect.isempty() || text_start<0 || text_start+text_length>maxtext )
290    G_THROW( ERR_MSG("DjVuText.corrupt_text") );
291
292  // Process children
293  const Zone * prev_child=0;
294  children.empty();
295  while (size-- > 0) 
296  {
297    Zone *z = append_child();
298    z->decode(gbs, maxtext, this, prev_child);
299    prev_child=z;
300  }
301}
302
303void 
304DjVuTXT::normalize_text()
305{
306  GUTF8String newtextUTF8;
307  page_zone.normtext( (const char*)textUTF8, newtextUTF8 );
308  textUTF8 = newtextUTF8;
309}
310
311int 
312DjVuTXT::has_valid_zones() const
313{
314  if (!textUTF8)
315    return false;
316  if (page_zone.children.isempty() || page_zone.rect.isempty()) 
317    return false;
318  return true;
319}
320
321
322#ifndef NEED_DECODER_ONLY
323void 
324DjVuTXT::encode(const GP<ByteStream> &gbs) const
325{
326  ByteStream &bs=*gbs;
327  if (! textUTF8 )
328    G_THROW( ERR_MSG("DjVuText.no_text") );
329  // Encode text
330  int textsize = textUTF8.length();
331  bs.write24( textsize );
332  bs.writall( (void*)(const char*)textUTF8, textsize );
333  // Encode zones
334  if (has_valid_zones())
335  {
336    bs.write8(Zone::version);
337    page_zone.encode(gbs);
338  }
339}
340#endif
341
342void 
343DjVuTXT::decode(const GP<ByteStream> &gbs)
344{
345  ByteStream &bs=*gbs;
346  // Read text
347  textUTF8.empty();
348  int textsize = bs.read24();
349  char *buffer = textUTF8.getbuf(textsize);
350  int readsize = bs.read(buffer,textsize);
351  buffer[readsize] = 0;
352  if (readsize < textsize)
353    G_THROW( ERR_MSG("DjVuText.corrupt_chunk") );
354  // Try reading zones
355  unsigned char version;
356  if ( bs.read( (void*) &version, 1 ) == 1) 
357  {
358    if (version != Zone::version)
359      G_THROW( ERR_MSG("DjVuText.bad_version") "\t" + GUTF8String(version) );
360    page_zone.decode(gbs, textsize);
361  }
362}
363
364GP<DjVuTXT> 
365DjVuTXT::copy(void) const
366{
367  return new DjVuTXT(*this);
368}
369
370
371static inline bool
372intersects_zone(GRect box, const GRect &zone)
373{
374  return
375    ((box.xmin < zone.xmin)
376      ?(box.xmax >= zone.xmin)
377      :(box.xmin <= zone.xmax))
378    &&((box.ymin < zone.ymin)
379      ?(box.ymax >= zone.ymin)
380      :(box.ymin <= zone.ymax));
381}
382
383void
384DjVuTXT::Zone::get_text_with_rect(const GRect &box, 
385                                  int &string_start, int &string_end) const
386{
387  GPosition pos=children;
388  if(pos?box.contains(rect):intersects_zone(box,rect))
389  {
390    const int text_end=text_start+text_length;
391    if(string_start == string_end)
392    {
393      string_start=text_start;
394      string_end=text_end;
395    }else
396    {
397      if (string_end < text_end)
398        string_end=text_end;
399      if(text_start < string_start)
400        string_start=text_start;
401    }
402  }else if(pos&&intersects_zone(box,rect))
403  {
404    do
405    {
406      children[pos].get_text_with_rect(box,string_start,string_end);
407    } while(++pos);
408  }
409}
410
411void
412DjVuTXT::Zone::find_zones(GList<Zone *> &list, 
413                          const int string_start, const int string_end) const
414{
415  const int text_end=text_start+text_length;
416  if(text_start >= string_start)
417    {
418      if(text_end <= string_end)
419        {
420          list.append(const_cast<Zone *>(this));
421        }
422      else if(text_start < string_end)
423        {
424          if (children.size())
425            for (GPosition pos=children; pos; ++pos)
426              children[pos].find_zones(list,string_start,string_end);
427          else
428            list.append(const_cast<Zone *>(this));
429        }
430    }
431  else if( text_end > string_start)
432    {
433      if (children.size())
434        for (GPosition pos=children; pos; ++pos)
435          children[pos].find_zones(list,string_start,string_end);
436      else
437        list.append(const_cast<Zone *>(this));
438    }
439}
440
441void
442DjVuTXT::Zone::get_smallest(GList<GRect> &list) const
443{
444  GPosition pos=children;
445  if(pos)
446    {
447      do {
448        children[pos].get_smallest(list);
449      } while (++pos);
450    }
451  else
452    {
453      list.append(rect);
454    }
455}
456
457void
458DjVuTXT::Zone::get_smallest(GList<GRect> &list, const int padding) const
459{
460  GPosition pos=children;
461  if(pos)
462    {
463      do {
464        children[pos].get_smallest(list,padding);
465      } while (++pos);
466    }
467  else if(zone_parent && zone_parent->ztype >= PARAGRAPH)
468    {
469      const GRect &xrect=zone_parent->rect;
470      if(xrect.height() < xrect.width())
471        {
472          list.append(GRect(rect.xmin-padding,xrect.ymin-padding,rect.width()
473                            +2*padding,xrect.height()+2*padding));
474        }
475      else
476        {
477          list.append(GRect(xrect.xmin-padding,rect.ymin-padding,xrect.width()
478                            +2*padding,rect.height()+2*padding));
479        }
480    }
481  else
482    {
483      list.append(GRect(rect.xmin-padding,rect.ymin-padding,rect.width()
484                        +2*padding,rect.height()+2*padding));
485    }
486}
487
488void
489DjVuTXT::get_zones(int zone_type, const Zone *parent, 
490                   GList<Zone *> & zone_list) const 
491   // get all the zones of  type zone_type under zone node parent
492{
493   // search all branches under parent
494   const Zone *zone=parent;
495   for( int cur_ztype=zone->ztype; cur_ztype<zone_type; ++cur_ztype )
496   {
497      GPosition pos;
498      for(pos=zone->children; pos; ++pos)
499      {
500         Zone *zcur=(Zone *)&zone->children[pos];
501         if ( zcur->ztype == zone_type )
502         {
503            GPosition zpos=zone_list;
504            if ( !zone_list.search(zcur,zpos) )
505               zone_list.append(zcur);
506         }
507         else if ( zone->children[pos].ztype < zone_type )
508            get_zones(zone_type, &zone->children[pos], zone_list);
509      }
510   }
511}
512
513GList<GRect>
514DjVuTXT::find_text_with_rect(const GRect &box, GUTF8String &text, 
515                             const int padding) const
516{
517  GList<GRect> retval;
518  int text_start=0;
519  int text_end=0;
520  page_zone.get_text_with_rect(box,text_start,text_end);
521  if(text_start != text_end)
522  {
523    GList<Zone *> zones;
524    page_zone.find_zones(zones,text_start,text_end);
525    GPosition pos=zones;
526    if(pos)
527    {
528      do
529      {
530        if(padding >= 0)
531        {
532          zones[pos]->get_smallest(retval,padding);
533        }else
534        {
535          zones[pos]->get_smallest(retval);
536        }
537      } while(++pos);
538    }
539  }
540  text=textUTF8.substr(text_start,text_end-text_start);
541  return retval;
542}
543
544
545GList<DjVuTXT::Zone *>
546DjVuTXT::find_text_in_rect(GRect target_rect, GUTF8String &text) const
547   // returns a list of zones of type WORD in the nearest/selected paragraph
548{
549   GList<Zone *> zone_list;
550   GList<Zone *> lines;
551
552   get_zones((int)PARAGRAPH, &page_zone, zone_list);
553   // it's possible that no paragraph structure exists for reasons that 
554   // 1) ocr engine is not capable 2) file was modified by user. In such case,
555   // we can only make a rough guess, i.e., select all the lines intersected with
556   // target_rect
557   if (zone_list.isempty())
558   {
559      get_zones((int)LINE, &page_zone, zone_list);
560      GPosition pos;
561      for(pos=zone_list; pos; ++pos)
562      {
563         GRect rect=zone_list[pos]->rect;
564         int h0=rect.height()/2;
565         if(rect.intersect(rect,target_rect) && rect.height()>h0)
566            lines.append(zone_list[pos]);
567      }
568   } else 
569   {
570      GPosition pos, pos_sel=zone_list;
571      float ar=0;
572      for(pos=zone_list; pos; ++pos)
573      {
574         GRect rect=zone_list[pos]->rect;
575         int area=rect.area();
576         if (rect.intersect(rect, target_rect))
577         {
578            float ftmp=rect.area()/(float)area;
579            if ( !ar || ar<ftmp )
580            {
581               ar=ftmp;
582               pos_sel=pos;
583            }
584         }
585      }
586      Zone *parag = 0;
587      if ( ar>0 ) parag=zone_list[pos_sel];
588      zone_list.empty();
589      if ( ar>0 ) 
590      {
591         get_zones((int)LINE, parag, zone_list);
592         if ( !zone_list.isempty() )
593         {
594            for(GPosition pos=zone_list; pos; ++pos)
595            {
596               GRect rect=zone_list[pos]->rect;
597               int h0=rect.height()/2;
598               if(rect.intersect(rect,target_rect) && rect.height()>h0)
599                  lines.append(zone_list[pos]);
600            }
601         }
602      }
603   }
604
605   zone_list.empty();
606   if (!lines.isempty()) 
607   {
608      int i=1, lsize=lines.size();
609
610      GList<Zone *> words;
611      for (GPosition pos=lines; pos; ++pos, ++i)
612      {
613         words.empty();
614         get_zones((int)WORD, lines[pos], words);
615
616         if ( lsize==1 )
617         {
618            for(GPosition p=words;p;++p)
619            {
620               GRect rect=words[p]->rect;
621               if(rect.intersect(rect,target_rect))
622               //if (target_rect.contains(words[p]->rect))
623                  zone_list.append(words[p]);
624            }
625         } else
626         {
627            if (i==1)
628            {
629               bool start=true;
630               for(GPosition p=words; p; ++p)
631               {
632                  if ( start )
633                  {
634                     GRect rect=words[p]->rect;
635                     if(rect.intersect(rect,target_rect))
636                        //if (target_rect.contains(words[p]->rect))
637                     {
638                        start=false;
639                        zone_list.append(words[p]);
640                     }
641                  } else 
642                     zone_list.append(words[p]);
643               }
644            } else if (i==lsize)
645            {
646               bool end=true;
647               for(GPosition p=words.lastpos();p;--p)
648               {
649                  if ( end )
650                  {
651                     GRect rect=words[p]->rect;
652                     if(rect.intersect(rect,target_rect))
653                        //if(target_rect.contains(words[p]->rect) )
654                     {
655                        end=false;
656                        zone_list.append(words[p]);
657                     }
658                  } else 
659                     zone_list.append(words[p]);
660               }
661            }
662
663            if (i!=1 && i!=lsize )
664            {
665               for(GPosition p=words;p;++p)
666                  zone_list.append(words[p]);
667            }
668         }
669      }
670   } 
671
672   return zone_list;
673}
674
675unsigned int 
676DjVuTXT::get_memory_usage() const
677{
678  return sizeof(*this) + textUTF8.length() + page_zone.memuse() - sizeof(page_zone); 
679}
680
681
682
683//***************************************************************************
684//******************************** DjVuText *********************************
685//***************************************************************************
686
687void
688DjVuText::decode(const GP<ByteStream> &gbs)
689{
690  GUTF8String chkid;
691  GP<IFFByteStream> giff=IFFByteStream::create(gbs);
692  IFFByteStream &iff=*giff;
693  while( iff.get_chunk(chkid) )
694  {
695    if (chkid == "TXTa")
696    {
697      if (txt)
698        G_THROW( ERR_MSG("DjVuText.dupl_text") );
699      txt = DjVuTXT::create();
700      txt->decode(iff.get_bytestream());
701    }
702    else if (chkid == "TXTz")
703    {
704      if (txt)
705        G_THROW( ERR_MSG("DjVuText.dupl_text") );
706      txt = DjVuTXT::create();
707      const GP<ByteStream> gbsiff=BSByteStream::create(iff.get_bytestream());
708      txt->decode(gbsiff);
709    }
710    // Add decoding of other chunks here
711    iff.close_chunk();
712  }
713}
714
715void
716DjVuText::encode(const GP<ByteStream> &gbs)
717{
718  if (txt)
719  {
720    const GP<IFFByteStream> giff=IFFByteStream::create(gbs);
721    IFFByteStream &iff=*giff;
722    iff.put_chunk("TXTz");
723    {
724      GP<ByteStream> gbsiff=BSByteStream::create(iff.get_bytestream(),50);
725      txt->encode(gbsiff);
726    }
727    iff.close_chunk();
728  }
729  // Add encoding of other chunks here
730}
731
732
733GP<DjVuText>
734DjVuText::copy(void) const
735{
736   GP<DjVuText> text= new DjVuText;
737      // Copy any primitives (if any)
738   *text=*this;
739      // Copy each substructure
740   if (txt)
741     text->txt = txt->copy();
742   return text;
743}
744
745static GUTF8String
746indent ( int spaces)
747{
748  GUTF8String ret;
749  for( int i = 0 ; i < spaces ; i++ )
750    ret += ' ';
751  return ret;
752}
753
754static const char *tags[8]=
755{ 0,
756  "HIDDENTEXT",
757  "PAGECOLUMN",
758  "REGION",
759  "PARAGRAPH",
760  "LINE",
761  "WORD",
762  "CHARACTER" };
763static const int tags_size=sizeof(tags)/sizeof(const char *);
764
765static GUTF8String
766start_tag(const DjVuTXT::ZoneType zone)
767{
768  GUTF8String retval;
769  if((tags_size > (int)zone)&&((int)zone > 0))
770  {
771    switch (zone)
772    {
773      case DjVuTXT::CHARACTER:
774        retval="<"+GUTF8String(tags[zone])+">";
775        break;
776      case DjVuTXT::WORD:
777        retval=indent(2*(int)zone+2)+"<"+tags[zone]+">";
778        break;
779      default:
780        retval=indent(2*(int)zone+2)+"<"+tags[zone]+">\n";
781        break;
782    }
783  }
784  return retval;
785}
786
787static GUTF8String
788start_tag(const DjVuTXT::ZoneType zone, const GUTF8String &attributes)
789{
790  GUTF8String retval;
791  if((tags_size > (int)zone)&&((int)zone > 0))
792  {
793    switch (zone)
794    {
795      case DjVuTXT::CHARACTER:
796        retval="<"+GUTF8String(tags[zone])+" "+attributes+">";
797        break;
798      case DjVuTXT::WORD:
799        retval=indent(2*(int)zone+2)+"<"+tags[zone]+" "+attributes+">";
800        break;
801      default:
802        retval=indent(2*(int)zone+2)+"<"+tags[zone]+" "+attributes+">\n";
803        break;
804    }
805  }
806  return retval;
807}
808
809static inline GUTF8String
810start_tag(const int layer)
811{
812  return start_tag((const DjVuTXT::ZoneType)layer);
813}
814
815
816static GUTF8String
817end_tag(const DjVuTXT::ZoneType zone)
818{
819  GUTF8String retval;
820  if((tags_size > (int)zone)&&((int)zone >= 0))
821  {
822    switch (zone)
823    {
824      case DjVuTXT::CHARACTER:
825        retval="</"+GUTF8String(tags[zone])+">";
826        break;
827      case DjVuTXT::WORD:
828        retval="</"+GUTF8String(tags[zone])+">\n";
829        break;
830      default:
831        retval=indent(2*(int)zone+2)+"</"+tags[zone]+">\n";
832        break;
833    }
834  }
835  return retval;
836}
837
838static inline GUTF8String
839end_tag(const int layer)
840{
841  return end_tag((const DjVuTXT::ZoneType)layer);
842}
843
844static GUTF8String
845tolayer(int &layer, const DjVuTXT::ZoneType next_layer)
846{
847  GUTF8String retval;
848  for( ;layer < (int)next_layer;layer++ )
849  {
850    retval+=start_tag(layer);
851  }
852  while (layer > (int)next_layer )
853  {
854    retval+=end_tag(--layer);
855  }
856  return retval;
857}
858
859static void
860writeText( ByteStream & str_out,
861            const GUTF8String &textUTF8,
862            const DjVuTXT::Zone &zone,
863            const int WindowHeight );
864
865static void
866writeText( ByteStream & str_out,
867           const GUTF8String &textUTF8,
868           const DjVuTXT::ZoneType zlayer,
869           const GList<DjVuTXT::Zone> &children,
870           const int WindowHeight )
871{
872//  assert( txt->has_valid_zones() );
873//  DEBUG_MSG( "--zonetype=" << txt->page_zone.ztype << "\n" );
874
875  //  Beginning tags for missing layers
876  int layer=(int)zlayer;
877  //  Output the next layer
878  for(GPosition pos=children ; pos ; ++pos )
879  {
880    str_out.writestring(tolayer(layer,children[pos].ztype));
881    writeText( str_out,
882                textUTF8,
883                children[pos],
884                WindowHeight );
885  }
886  str_out.writestring(tolayer(layer,zlayer));
887}
888
889static void
890writeText( ByteStream & str_out,
891            const GUTF8String &textUTF8,
892            const DjVuTXT::Zone &zone,
893            const int WindowHeight )
894{
895//  DEBUG_MSG( "--zonetype=" << zone.ztype << "\n" );
896
897  const GUTF8String xindent(indent( 2 * zone.ztype + 2 ));
898  GPosition pos=zone.children;
899  // Build attribute string
900  if( ! pos )
901  {
902    GUTF8String coords;
903    coords.format("coords=\"%d,%d,%d,%d\"",
904      zone.rect.xmin, WindowHeight - 1 - zone.rect.ymin,
905      zone.rect.xmax, WindowHeight - 1 - zone.rect.ymax);
906    const int start=zone.text_start;
907    const int end=textUTF8.firstEndSpace(start,zone.text_length);
908    str_out.writestring(start_tag(zone.ztype,coords));
909    str_out.writestring(textUTF8.substr(start,end-start).toEscaped());
910    str_out.writestring(end_tag(zone.ztype));
911  } else
912  {
913    writeText(str_out,textUTF8,zone.ztype,zone.children,WindowHeight);
914  }
915}
916
917void
918DjVuTXT::writeText(ByteStream &str_out,const int height) const
919{
920  if(has_valid_zones())
921  {
922    ::writeText(str_out,textUTF8,DjVuTXT::PAGE,page_zone.children,height);
923  }else
924  {
925    str_out.writestring(start_tag(DjVuTXT::PAGE));
926    str_out.writestring(end_tag(DjVuTXT::PAGE));
927  }
928}
929
930void
931DjVuText::writeText(ByteStream &str_out,const int height) const
932{
933  if(txt)
934  {
935    txt->writeText(str_out,height);
936  }else
937  {
938    str_out.writestring("<"+GUTF8String(tags[DjVuTXT::PAGE])+"/>\n");
939  }
940   
941}
942GUTF8String
943DjVuTXT::get_xmlText(const int height) const
944{
945  GP<ByteStream> gbs(ByteStream::create());
946  ByteStream &bs=*gbs;
947  writeText(bs,height);
948  bs.seek(0L);
949  return bs.getAsUTF8();
950}
951
952GUTF8String
953DjVuText::get_xmlText(const int height) const
954{
955  GUTF8String retval;
956  if(txt)
957  {
958    retval=txt->get_xmlText(height);
959  }else
960  {
961    retval="<"+GUTF8String(tags[DjVuTXT::PAGE])+"/>\n";
962  }
963  return retval;
964}
965
966
967#ifdef HAVE_NAMESPACES
968}
969# ifndef NOT_USING_DJVU_NAMESPACE
970using namespace DJVU;
971# endif
972#endif
973
Note: See TracBrowser for help on using the repository browser.