TextFormatter.cpp
上传用户:hmc_gdtv
上传日期:2013-08-04
资源大小:798k
文件大小:20k
源码类别:

Windows Mobile

开发平台:

Visual C++

  1. /*
  2.  * Copyright (c) 2001,2002,2003 Mike Matsnev.  All Rights Reserved.
  3.  *
  4.  * Redistribution and use in source and binary forms, with or without
  5.  * modification, are permitted provided that the following conditions
  6.  * are met:
  7.  *
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice immediately at the beginning of the file, without modification,
  10.  *    this list of conditions, and the following disclaimer.
  11.  * 2. Redistributions in binary form must reproduce the above copyright
  12.  *    notice, this list of conditions and the following disclaimer in the
  13.  *    documentation and/or other materials provided with the distribution.
  14.  * 3. Absolutely no warranty of function or purpose is made by the author
  15.  *    Mike Matsnev.
  16.  *
  17.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  18.  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  19.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  20.  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  21.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  22.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  23.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  24.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  26.  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27.  * 
  28.  * $Id: TextFormatter.cpp,v 1.66.2.6 2004/01/08 11:06:06 mike Exp $
  29.  * 
  30.  */
  31. #include <afxwin.h>
  32. #include <afxtempl.h>
  33. #include "ptr.h"
  34. #include "FDC.h"
  35. #include "FilePos.h"
  36. #include "TextFile.h"
  37. #include "TextFormatter.h"
  38. #include "TextViewNG.h"
  39. #ifdef _DEBUG
  40. #undef THIS_FILE
  41. static char THIS_FILE[]=__FILE__;
  42. #define new DEBUG_NEW
  43. #endif
  44. #ifndef MAXINT
  45. #define MAXINT 0x7fffffff
  46. #endif
  47. #define FBIG_ADD   4
  48. #define FSMALL_ADD   -1
  49. TextFormatter::TextFormatter(TextFile *tf) :
  50.     m_tf(tf),
  51.     m_width(1),
  52.     m_height(1),
  53.     m_pages(1),
  54.     m_justified(false),
  55.     m_hyphenate(false),
  56.     m_hllen(0),
  57.     m_angle(0)
  58. {
  59.   m_junk.flags=Line::first|Line::last;
  60. }
  61. void  TextFormatter::GetTextExtent(CFDC& dc,Paragraph& line,int off,
  62.    int width,int& nch,int *dx,int& lh,int& lb)
  63. {
  64.   const wchar_t   *sp=line.str;
  65.   Attr   *att=line.cflags+off;
  66.   int   len=nch-off;
  67.   int   xoff=0;
  68.   sp+=off;
  69.   nch=0;
  70.   while (len>0 && width>0) {
  71.     int   i;
  72.     int   n=0;
  73.     SIZE  sz;
  74.     Attr  curatt=att[0];
  75.     if (curatt.img) { // inline image
  76.       int   idx=*sp;
  77.       Image img;
  78.       if (idx<line.links.size() &&
  79.   m_tf->GetImage(line.links[idx].target,dc.DC(),
  80.     width,m_height,m_angle,img))
  81.       {
  82. int   dh=img.height-lb;
  83. if (dh>0) {
  84.   lh+=dh;
  85.   lb+=dh;
  86. }
  87. sz.cx=img.width;
  88. *dx=xoff+img.width;
  89.       }
  90.       n=1;
  91.       ++nch;
  92.       if (len==1)
  93. break;
  94.     } else {
  95.       for (i=1;i<len && att[i].fontflags()==curatt.fontflags();++i);
  96.       dc.SelectFont(curatt.fsize,curatt.fontattr(),true);
  97.       int   fh,fa;
  98.       dc.GetFontSize(fh,fa);
  99.       if (lh<fh) {
  100. lh=fh;
  101. lb=fa;
  102.       }
  103.       dc.GetTextExtent(sp,i,width,n,dx,sz);
  104.       if (n==0)
  105. break;
  106.       for (int j=0;j<n;++j)
  107. dx[j]+=xoff;
  108.       nch+=n;
  109.       if (i==len)
  110. break;
  111.     }
  112.     xoff+=sz.cx;
  113.     width-=sz.cx;
  114.     dx+=n;
  115.     sp+=n;
  116.     att+=n;
  117.     len-=n;
  118.   }
  119. }
  120. void CopyAttr(Attr *dest,const Attr *src,DWORD len) {
  121.   while (len--)
  122.     (*dest++=*src++).hyphen=false;
  123. }
  124. static void AdjustIndent(int& width,int& indent,int li,int ri,int fi,int lpx) {
  125.   int pli=lpx*(li+fi)/25;
  126.   int pri=lpx*ri/25;
  127.   if (width-pli<50)
  128.     pli=width-50;
  129.   width-=pli;
  130.   indent+=pli;
  131.   width-=pri;
  132.   if (width<50)
  133.     width=50;
  134. }
  135. // split image into strips
  136. int     TextFormatter::WrapImage(CFDC& dc,Paragraph& pp,
  137.      FilePos& pos,LineArray& la,
  138.      int top,int maxh)
  139. {
  140.   Image   img;
  141. #if 0
  142.   int   curwidth=m_width;
  143.   int   ispace=0;
  144.   AdjustIndent(curwidth,ispace,pp.lindent,pp.rindent,0,dc.GetLPX());
  145. #else
  146.   int   curwidth=m_total_width;
  147.   int   ispace=-m_margin;
  148. #endif
  149.   if (pp.links.size()<=0 ||
  150.     !m_tf->GetImage(pp.links[0].target,dc.DC(),curwidth,m_height,m_angle,img))
  151.   { // image fetch failed, just skip the paragraph
  152.     pos.off+=pp.len;
  153.     return 0;
  154.   }
  155. #if 0
  156.   if (top && img.height>maxh)
  157.     return -1;
  158. #endif
  159.   // calc strips, min strip height is 16
  160.   int   striph=(img.height+pp.str.size()-1)/pp.str.size();
  161.   if (striph<16)
  162.     striph=16;
  163.   if (striph>img.height)
  164.     striph=img.height;
  165.   // number of visible strips
  166.   int   vstrips=(img.height+striph-1)/striph;
  167.   int   topstrip=vstrips;
  168.   if (topstrip>pp.len)
  169.     topstrip=pp.len;
  170.   // all lines are the same
  171.   Line   line(L" ",1,false);
  172.   line.attr[0].wa=0;
  173.   line.dx[0]=0;
  174.   line.ispace=ispace+(curwidth-img.width)/2;
  175.   line.href=pp.links[0].target;
  176.   line.pos=pos;
  177.   line.flags=Line::image;
  178.   line.height=striph;
  179.   line.base=curwidth;
  180.   line.imageheight=m_height;
  181.   // take care of the strip offset
  182.   int   stripnum=pos.off;
  183.   int   yoffset=stripnum*striph;
  184.   // add visible strips as lines
  185.   int   toth=0;
  186.   // add visible strips
  187.   while (stripnum<topstrip) {
  188.     line.yoffset=yoffset;
  189.     line.pos=pos;
  190.     if (stripnum==vstrips-1) { // last line
  191.       // assign all unsused spaces here
  192.       int spcount=pp.len-pos.off;
  193.       line.attr=Buffer<Attr>(spcount);
  194.       line.str=Buffer<wchar_t>(spcount);
  195.       line.dx=Buffer<int>(spcount);
  196.       for (int i=0;i<spcount;++i) {
  197. line.attr[i].wa=0;
  198. line.str[i]=L' ';
  199. line.dx[i]=0;
  200.       }
  201.       line.real_len=spcount;
  202.       // adjust line height
  203.       line.height=img.height-striph*stripnum;
  204.     }
  205.     if (toth+line.height>maxh)
  206.       break;
  207.     la.Add(line);
  208.     pos.off+=line.real_len;
  209.     ++stripnum;
  210.     yoffset+=striph;
  211.     toth+=line.height;
  212.   }
  213.   // ok, return processed height
  214.   // if we didn't process anything, return a failure
  215.   return toth ? toth : -1;
  216. }
  217. // the formatter's core, wraps the line and justifies it if needed
  218. int     TextFormatter::WrapLine(CFDC& dc,Paragraph& pp,
  219.     FilePos& pos,LineArray& la,
  220.     int top,int maxh)
  221. {
  222.   if (maxh<=0)
  223.     return 0;
  224.   // process images separately
  225.   if (pp.flags&Paragraph::image)
  226.     return WrapImage(dc,pp,pos,la,top,maxh);
  227.   if (pp.len==0 || (pp.len==1 && pp.str[0]==L' ')) {
  228.     dc.SelectFont(0,0);
  229.     int   fh,fa;
  230.     dc.GetFontSize(fh,fa);
  231.     if (fh>maxh)
  232.       return -1;
  233.     Line    l;
  234.     l.pos=pos;
  235.     l.flags=Line::first|Line::last|Line::defstyle;
  236.     l.height=fh;
  237.     l.base=fa;
  238.     la.Add(l);
  239.     pos.off=pp.len;
  240.     return l.height;
  241.   }
  242.   if (m_hyphenate)
  243.     pp.Hyphenate();
  244.   const wchar_t     *str=pp.str;
  245.   int     len=pp.len;
  246.   Buffer<int>     dx(len+1);
  247.   int     toth=0;
  248.   while (toth<maxh && pos.off<len) {
  249.     // 1. get text size
  250.     int       nch=len;
  251.     int       curwidth=m_width;
  252.     int       ispace=0;
  253.     if (pos.off==0 && (pp.flags&(Paragraph::center|Paragraph::right))==0)
  254.       AdjustIndent(curwidth,ispace,pp.lindent,pp.rindent,pp.findent,dc.GetLPX());
  255.     else
  256.       AdjustIndent(curwidth,ispace,pp.lindent,pp.rindent,0,dc.GetLPX());
  257.     dx[0]=0;
  258.     int lh=1,lbase=1;
  259.     GetTextExtent(dc,pp,pos.off,curwidth,nch,dx+1,lh,lbase);
  260.     if (toth+lh>maxh)
  261.       return -1;
  262.     if (nch==0)
  263.       nch=1;
  264.     // 2. do word wrap
  265.     bool  addhyp=false;
  266.     if (nch+pos.off<pp.str.size()) {
  267.       int   i;
  268.       for (i=nch;i>0 && str[pos.off+i]!=L' ';--i) {
  269. // wrap at existing dashes
  270. if (i<nch && (str[pos.off+i]==L'-' || str[pos.off+i]==0x2013 ||
  271.   str[pos.off+i]==0x2014) && i<len-1 && (str[pos.off+i+1]==L' ' ||
  272.   iswalpha(str[pos.off+i+1])))
  273. {
  274.   ++i;
  275.   break;
  276. }
  277. // or at possible hyphenation points
  278. if (m_hyphenate && pp.cflags[pos.off+i].hyphen &&
  279.       dx[i]+dc.GetHypWidth()<=curwidth) {
  280.   addhyp=true;
  281.   break;
  282. }
  283.       }
  284.       if (i>0)
  285. nch=i;
  286.       else
  287. addhyp=false;
  288.     }
  289.     // insert it into line list
  290.     if (pos.off==0 && nch==pp.str.size()) { // got full line
  291.       Line    l(str,len,false);
  292.       l.pos=pos;
  293.       l.flags=Line::first|Line::last;
  294.       l.ispace=ispace;
  295.       l.height=lh;
  296.       l.base=lbase;
  297.       if (dx[nch]<curwidth) {
  298. if (pp.flags&Paragraph::center)
  299.   l.ispace+=(curwidth-dx[nch])/2;
  300. else if (pp.flags&Paragraph::right)
  301.   l.ispace+=curwidth-dx[nch];
  302.       }
  303.       CopyAttr(l.attr,pp.cflags,len);
  304.       for (int j=0;j<len;++j)
  305. l.dx[j]=dx[j+1]-dx[j];
  306.       la.Add(l);
  307.       pos.off=len;
  308.     } else {
  309.       Line    l(str+pos.off,nch,addhyp);
  310.       if (addhyp)
  311. l.str[nch]=L'-';
  312.       l.pos=pos;
  313.       l.ispace=ispace;
  314.       l.height=lh;
  315.       l.base=lbase;
  316.       l.flags=0;
  317.       if (pos.off==0)
  318. l.flags|=Line::first;
  319.       if (pos.off+nch==pp.str.size())
  320. l.flags|=Line::last;
  321.       for (int j=0;j<nch;++j)
  322. l.dx[j]=dx[j+1]-dx[j];
  323.       int   extra_width=0;
  324.       if (addhyp)
  325. l.dx[nch]=extra_width=dc.GetHypWidth();
  326.       // 3. justify/center text if needed
  327.       if (dx[nch]<curwidth) {
  328. if (addhyp)
  329.   curwidth-=extra_width;
  330. if (pp.flags&Paragraph::center) {
  331.   l.ispace+=(curwidth-dx[nch])/2;
  332. } else if (pp.flags&Paragraph::right) {
  333.   l.ispace+=curwidth-dx[nch];
  334. } else if ((m_justified || pp.flags&Paragraph::justify) &&
  335.     !(l.flags&Line::last))
  336. {
  337.   // count spaces in string
  338.   int   nspc=0,i;
  339.   for (i=0;i<nch;++i)
  340.     if (L' '==str[pos.off+i])
  341.       ++nspc;
  342.   // and distribute extra width to them
  343.   if (nspc>0) {
  344.     int   addw=(curwidth-dx[nch])/nspc;
  345.     int   extraddw=curwidth-dx[nch]-addw*nspc;
  346.     for (i=0;i<nch;++i) {
  347.       if (str[pos.off+i]==L' ') {
  348. l.dx[i]+=addw;
  349. if (extraddw) {
  350.   ++l.dx[i];
  351.   --extraddw;
  352. }
  353.       }
  354.     }
  355.   }
  356. }
  357.       }
  358.       CopyAttr(l.attr,pp.cflags+pos.off,nch);
  359.       if (addhyp)
  360. l.attr[nch]=l.attr[nch-1];
  361.       la.Add(l);
  362.       pos.off+=nch;
  363.       while (pos.off<len && str[pos.off]==L' ')
  364. ++pos.off;
  365.     }
  366.     toth+=lh;
  367.   }
  368.   return toth;
  369. }
  370. bool     TextFormatter::FormatFwd(CFDC& dc,FilePos start) {
  371.   AdjustPos(start); // just to be safe
  372.   if (start.para>=m_tf->Length(start.docid))
  373.     return false; // at eof
  374.   m_lines.RemoveAll();
  375.   m_top=start;
  376.   bool header=true;
  377.   for (int page=0;page<m_pages;++page) {
  378.     int h=0;
  379.     int beg=m_lines.GetSize();
  380.     while (h<m_height && start.para<m_tf->Length(start.docid)) {
  381.       Paragraph   para(m_tf->GetParagraph(start.docid,start.para));
  382.       bool empty=para.len==0 || para.len==1 && para.str[0]==_T(' ');
  383.       if (h==0 && empty) {
  384. start.off=para.len;
  385. AdjustPos(start);
  386. continue;
  387.       }
  388.       if (para.flags & Paragraph::header) {
  389. if (!header)
  390.   break;
  391.       } else
  392. if (!empty && !(para.flags & Paragraph::image))
  393.   header=false;
  394.       int lh=WrapLine(dc,para,start,m_lines,h,m_height-h);
  395.       if (lh<0)
  396. break;
  397.       AdjustPos(start);
  398.       h+=lh;
  399.     }
  400.     m_pagelen.SetAtGrow(page,m_lines.GetSize()-beg);
  401.   }
  402.   m_bot=start;
  403.   Highlight();
  404.   // add one dummy line always
  405.   Line l;
  406.   l.pos=m_bot;
  407.   m_lines.Add(l);
  408.   return true;
  409. }
  410. bool     TextFormatter::FormatBack(CFDC& dc,FilePos start,FilePos prev_top) {
  411.   AdjustPos(start,true);
  412.   if (start.para==0 && start.off==0)
  413.     return false; // at the top
  414.   m_lines.RemoveAll();
  415.   m_bot=start;
  416.   for (int page=m_pages-1;page>=0;--page) {
  417.     LineArray   tmp;
  418.     FilePos     pos=start;
  419.     int       h=0;
  420.     // while there are still paragrahs before
  421.     while (h<m_height && (pos.para>0 || pos.off>0)) {
  422.       // format entire paragraph
  423.       LineArray cp;
  424.       Paragraph   para(m_tf->GetParagraph(pos.docid,pos.para));
  425.       if (pos.off<para.len) // double check args
  426. para.len=pos.off;
  427.       else
  428. pos.off=para.len;
  429.       WrapLine(dc,para,FilePos(pos.para,0,pos.docid),cp,0,32768);
  430.       // insert the formatted paragraph at start of list
  431.       tmp.InsertAt(0,&cp);
  432.       for (int i=0;i<cp.GetSize();++i)
  433. h+=cp[i].height;
  434.       pos.off=0;
  435.       AdjustPos(pos,true);
  436.     }
  437.     // delete extra lines
  438.     int j;
  439.     // remove top lines
  440.     for (h=0,j=tmp.GetUpperBound();j>=0 && h+tmp[j].height<=m_height;--j)
  441.       h+=tmp[j].height;
  442.     if (j<tmp.GetUpperBound()) {
  443.       if (j>=0 && prev_top!=0 && tmp[j+1].pos>=prev_top) {
  444. --j;
  445. tmp.RemoveAt(0,j+1);
  446. // now remove bottom lines
  447. for (h=j=0;j<tmp.GetSize() && h+tmp[j].height<=m_height;++j)
  448.   h+=tmp[j].height;
  449. if (j<tmp.GetSize())
  450.   tmp.RemoveAt(j,tmp.GetSize()-j);
  451.       } else
  452. tmp.RemoveAt(0,j+1);
  453.     }
  454.     // save lines
  455.     m_lines.InsertAt(0,&tmp);
  456.     m_pagelen.SetAtGrow(page,tmp.GetSize());
  457.     start=m_lines[0].pos;
  458.     if (start.para==0 && start.off==0) // we reached the top of file
  459.       return FormatFwd(dc,FilePos(0,0,start.docid));
  460.   }
  461.   // save positions
  462.   m_top=m_lines[0].pos;
  463.   Highlight();
  464.   // add one dummy line always
  465.   Line l;
  466.   l.pos=m_bot;
  467.   m_lines.Add(l);
  468.   return true;
  469. }
  470. void     TextFormatter::AdjustPos(FilePos& p,bool back) {
  471.   if (back) {
  472.     if (p.para>0) {
  473.       if (p.para>=m_tf->Length(p.docid)) {
  474. if (m_tf->Length(p.docid)>0) {
  475.   p.para=m_tf->Length(p.docid)-1;
  476.   p.off=m_tf->GetPLength(p.docid,p.para);
  477. }
  478.       } else {
  479. if (p.off==0) {
  480.   --p.para;
  481.   p.off=m_tf->GetPLength(p.docid,p.para);
  482. }
  483.       }
  484.     }
  485.   } else {
  486.     if (p.para>=0 && p.para<m_tf->Length(p.docid) && p.off>=m_tf->GetPLength(p.docid,p.para)) {
  487.       p.off=0;
  488.       p.para++;
  489.     }
  490.   }
  491. }
  492. void     TextFormatter::SetSize(int width,int margin,int height,int pages,
  493.    int angle)
  494. {
  495.   if (height<1)
  496.     height=1;
  497.   if (pages<1)
  498.     pages=1;
  499.   m_total_width=width;
  500.   m_margin=margin;
  501.   m_width=m_total_width-2*m_margin;
  502.   if (m_width<1)
  503.     m_width=1;
  504.   m_height=height;
  505.   m_pages=pages;
  506.   m_angle=angle;
  507. }
  508. bool TextFormatter::AtTop()
  509. {
  510.   FilePos   p(m_top);
  511.   AdjustPos(p,true);
  512.   return p.off==0 && p.para==0;
  513. }
  514. bool TextFormatter::AtEof()
  515. {
  516.   FilePos   p(m_bot);
  517.   AdjustPos(p);
  518.   return p.para>=m_tf->Length(p.docid);
  519. }
  520. static bool  intersect(int a,int la,int b,int lb,int& i,int& li) {
  521.   if (a>=b && a<b+lb) {
  522.     i=a-b;
  523.     if (la>b+lb-a)
  524.       li=b+lb-a;
  525.     else
  526.       li=la;
  527.     return true;
  528.   }
  529.   if (b>=a && b<a+la) {
  530.     i=0;
  531.     if (lb>a+la-b)
  532.       li=a+la-b;
  533.     else
  534.       li=lb;
  535.     return true;
  536.   }
  537.   return false;
  538. }
  539. void TextFormatter::Highlight() {
  540.   if (!m_hllen || m_hlstart.docid!=DocId())
  541.     return;
  542.   FilePos   hls(m_hlstart);
  543.   int     hll=m_hllen;
  544.   for (int i=0;i<m_lines.GetSize();++i)
  545.     if (hls.para==m_lines[i].pos.para) {
  546.       int beg,len;
  547.       if (hls.off-m_lines[i].pos.off<m_lines[i].real_len &&
  548. intersect(hls.off,hll,m_lines[i].pos.off,m_lines[i].str.size(),beg,len))
  549.       {
  550. int top=beg+len;
  551. while (beg<top)
  552.   m_lines[i].attr[beg++].hibg=true;
  553. m_lines[i].flags&=~Line::defstyle;
  554.       } else
  555. m_lines[i].CheckStyle();
  556.       // advance our pointer
  557.       if (i<m_lines.GetSize()-1 && m_lines[i+1].pos.para>hls.para &&
  558.     hls.off+hll>m_tf->GetPLength(hls.docid,hls.para))
  559.       {
  560. hll-=m_tf->GetPLength(hls.docid,hls.para)-hls.off;
  561. ++hls.para;
  562. hls.off=0;
  563.       }
  564.     } else
  565.       m_lines[i].CheckStyle();
  566. }
  567. bool TextFormatter::SetHighlight(FilePos pos,int len) {
  568.   if (m_hllen==len && (!len || pos==m_hlstart)) // avoid extra work
  569.     return false;
  570.   // remove highlighting
  571.   for (int i=0;i<m_lines.GetSize();++i) {
  572.     for (int j=0;j<m_lines[i].attr.size();++j)
  573.       m_lines[i].attr[j].hibg=false;
  574.   }
  575.   m_hlstart=pos;
  576.   m_hllen=len;
  577.   Highlight();
  578.   return true;
  579. }
  580. void  Line::CheckStyle() {
  581.   Attr *p=attr;
  582.   Attr *e=p+attr.size();
  583.   while (p<e)
  584.     if ((*p++).wa) {
  585.       flags&=~defstyle;
  586.       return;
  587.     }
  588.   flags|=defstyle;
  589. }
  590. int TextFormatter::Distance(const FilePos& a,const FilePos& b)
  591. {
  592.   FilePos start(a), end(b);
  593.   bool   sign=false;
  594.   if (a>b) {
  595.     start=b;
  596.     end=a;
  597.     sign=true;
  598.   }
  599.   // check bounds
  600.   if (start.para<0)
  601.     start.para=0;
  602.   if (start.para>m_tf->Length(start.docid)) {
  603.     start.para=m_tf->Length(start.docid);
  604.     start.off=0;
  605.   }
  606.   if (start.off<0)
  607.     start.off=0;
  608.   if (start.off>m_tf->GetPLength(start.docid,start.para))
  609.     start.off=m_tf->GetPLength(start.docid,start.para);
  610.   if (end.para<0)
  611.     end.para=0;
  612.   if (end.para>m_tf->Length(end.docid)) {
  613.     end.para=m_tf->Length(end.docid);
  614.     end.off=0;
  615.   }
  616.   if (end.off<0)
  617.     end.off=0;
  618.   if (end.off>m_tf->GetPLength(end.docid,end.para))
  619.     end.off=m_tf->GetPLength(end.docid,end.para);
  620.   // calc distance now
  621.   int dist;
  622.   if (start.para==end.para)
  623.     dist=end.off-start.off;
  624.   else {
  625.     dist=m_tf->GetPLength(start.docid,start.para)-start.off;
  626.     ++start.para;
  627.     while (start.para<end.para) {
  628.       dist+=m_tf->GetPLength(start.docid,start.para);
  629.       ++start.para;
  630.     }
  631.     dist+=end.off;
  632.   }
  633.   return sign ? -dist : dist;
  634. }
  635. bool  TextFormatter::EnsureVisible(CFDC& dc,FilePos pos) {
  636.   if (pos>=m_top && pos<m_bot)
  637.     return false;
  638.   FilePos   ptop(pos);
  639.   ptop.off=0;
  640.   if (!FormatFwd(dc,ptop))
  641.     return true;
  642.   while (m_top.para==pos.para && pos>=m_bot)
  643.     if (!FormatFwd(dc))
  644.       break;
  645.   return true;
  646. }
  647. void  TextFormatter::FormatPlainText(CFDC& dc,
  648.      int& width,int& height,
  649.      int fontsize,
  650.      const wchar_t *text,int len,
  651.      LineArray& lines)
  652. {
  653.   lines.RemoveAll();
  654.   int   save_width=m_width;
  655.   bool   save_justified=m_justified;
  656.   m_width=width;
  657.   m_justified=false;
  658.   
  659.   const wchar_t   *top=text+len;
  660.   Attr   attr;
  661.   attr.wa=0;
  662.   attr.fsize=fontsize;
  663.   
  664.   int curh=0;
  665.   while (text<top && curh<height) {
  666.     const wchar_t   *p_end=text;
  667.     while (p_end<top && *p_end!='r' && *p_end!='n')
  668.       ++p_end;
  669.     Paragraph     p(p_end-text);
  670.     memcpy(p.str,text,(p_end-text)*sizeof(wchar_t));
  671.     for (int i=0;i<p.cflags.size();++i)
  672.       p.cflags[i].wa=attr.wa;
  673.     p.findent=3; // XXX
  674.     int last=lines.GetSize();
  675.     int lh=WrapLine(dc,p,FilePos(),lines,curh,height-curh);
  676.     if (lh<0) { // it still might add something
  677.       while (last<lines.GetSize())
  678. curh+=lines[last++].height;
  679.       break;
  680.     }
  681.     curh+=lh;
  682.     while (p_end<top && (*p_end=='r' || *p_end=='n'))
  683.       ++p_end;
  684.     text=p_end;
  685.   }
  686.   m_width=save_width;
  687.   m_justified=save_justified;
  688.   // deduct ispace
  689.   int min_ispace=-1,max_width=0;
  690.   for (int i=0;i<lines.GetSize();++i) {
  691.     const Line&   ll=lines[i];
  692.     int   w=0;
  693.     for (int j=0;j<ll.dx.size();++j)
  694.       w+=ll.dx[j];
  695.     w+=ll.ispace;
  696.     if (max_width<w)
  697.       max_width=w;
  698.     if (min_ispace<0 || min_ispace>ll.ispace)
  699.       min_ispace=ll.ispace;
  700.   }
  701.   if (min_ispace>0) {
  702.     for (int i=0;i<lines.GetSize();++i)
  703.       lines[i].ispace-=min_ispace;
  704.     max_width-=min_ispace;
  705.   }
  706.   height=curh;
  707.   width=max_width;
  708. }
  709. bool  TextFormatter::FormatFwdAdj(CFDC& dc) {
  710.   // if an image crosses the bottom of the window, and does not start
  711.   // at the top of window, then move down only until the image is
  712.   // fully visible
  713.   AdjustPos(m_bot);
  714.   Paragraph   p(m_tf->GetParagraph(m_bot.docid,m_bot.para));
  715.   if (p.flags&Paragraph::image && // image
  716.       m_bot.off>0 && // crosses window border
  717.       (m_top.docid!=m_bot.docid || m_top.para!=m_bot.para)) // is not visible at top
  718.   // then do a partial move forward to show a whole image
  719.     m_bot.off=0;
  720.   return FormatFwd(dc,m_bot);
  721. }
  722. void  TextFormatter::SetTop(FilePos pos) {
  723.   if (pos.docid >= m_tf->GetSubDocCount()) {
  724.     pos.docid = m_tf->GetSubDocCount() - 1;
  725.     if (pos.docid < 0)
  726.       pos.docid=0;
  727.   }
  728.   if (pos.para >= m_tf->Length(pos.docid)) {
  729.     pos.para=m_tf->Length(pos.docid)-1;
  730.     if (pos.para<0)
  731.       pos.para=0;
  732.   }
  733.   if (pos.off >= m_tf->GetPLength(pos.docid,pos.para)) {
  734.     pos.off = m_tf->GetPLength(pos.docid,pos.para) - 1;
  735.     if (pos.off<0)
  736.       pos.off=0;
  737.   }
  738.   m_top=pos;
  739. }