added test for text selection functionality
authorMatthias Kramm <kramm@quiss.org>
Fri, 14 Aug 2009 18:50:03 +0000 (20:50 +0200)
committerMatthias Kramm <kramm@quiss.org>
Fri, 14 Aug 2009 18:50:03 +0000 (20:50 +0200)
lib/devices/polyops.c
spec/edit_spec.py
spec/spec_helper.rb
spec/textarea.pdf [new file with mode: 0644]
spec/textarea.py [new file with mode: 0644]
spec/textarea.spec.rb [new file with mode: 0644]
src/swfdump.c
src/swfstrings.c

index 70fd99b..fea200b 100644 (file)
@@ -320,6 +320,10 @@ void polyops_drawchar(struct _gfxdevice*dev, gfxfont_t*font, int glyphnr, gfxcol
                /* notable change in character size: character was clipped 
                   TODO: how to deal with diagonal cuts?
                 */
+               msg("<trace> Character %d was clipped: (%f,%f,%f,%f) -> (%f,%f,%f,%f)",
+                       glyphnr, 
+                       bbox.xmin,bbox.ymin,bbox.xmax,bbox.ymax,
+                       bbox2.xmin,bbox2.ymin,bbox2.xmax,bbox2.ymax);
                polyops_fill(dev, glyph, color);
            } else {
                if(i->out) i->out->drawchar(i->out, font, glyphnr, color, matrix);
index 2ab4453..b270d18 100755 (executable)
@@ -60,7 +60,12 @@ class AreaPlain(AreaCheck):
 class AreaNotPlain(AreaCheck):
     pass
 
-checktypes = [PixelColorCheck,PixelBrighterThan,PixelDarkerThan,PixelEqualTo,AreaPlain,AreaNotPlain]
+class AreaText(AreaCheck):
+    def __init__(self, x,y, x2, y2, text=""):
+        AreaCheck.__init__(self,x,y,x2,y2)
+        self.text = text
+
+checktypes = [PixelColorCheck,PixelBrighterThan,PixelDarkerThan,PixelEqualTo,AreaPlain,AreaNotPlain,AreaText]
 
 global TESTMODE
 
@@ -146,6 +151,7 @@ class Model:
         r_pixelequalto = re.compile(r"^pixel_at\(([0-9]+),([0-9]+)\).should_be_the_same_as pixel_at\(([0-9]+),([0-9]+)\)")
         r_areaplain = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_be_plain_colored")
         r_areanotplain = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_not_be_plain_colored")
+        r_areatext = re.compile(r"^area_at\(([0-9]+),([0-9]+),([0-9]+),([0-9]+)\).should_contain_text '(.*)'")
         r_width = re.compile(r"^width.should be ([0-9]+)")
         r_height = re.compile(r"^height.should be ([0-9]+)")
         r_describe = re.compile(r"^describe \"pdf conversion\"")
@@ -176,6 +182,8 @@ class Model:
             if m: model.append(AreaPlain(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
             m = r_areanotplain.match(line)
             if m: model.append(AreaNotPlain(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4))));continue
+            m = r_areatext.match(line)
+            if m: model.append(AreaText(int(m.group(1)),int(m.group(2)),int(m.group(3)),int(m.group(4)),m.group(5)));continue
             if r_width.match(line) or r_height.match(line):
                 continue # compatibility
             if r_describe.match(line) or r_end.match(line) or r_header.match(line):
@@ -205,6 +213,8 @@ class Model:
                 fi.write("        area_at(%d,%d,%d,%d).should_be_plain_colored\n" % (check.x,check.y,check.x2,check.y2))
             elif c == AreaNotPlain:
                 fi.write("        area_at(%d,%d,%d,%d).should_not_be_plain_colored\n" % (check.x,check.y,check.x2,check.y2))
+            elif c == AreaText:
+                fi.write("        area_at(%d,%d,%d,%d).should_contain_text '%s'\n" % (check.x,check.y,check.x2,check.y2,check.text))
         fi.write("    end\n")
         fi.write("end\n")
         fi.close()
@@ -362,6 +372,10 @@ class EntryPanel(scrolled.ScrolledPanel):
     def delete(self, event):
         self.model.delete(self.id2check[event.Id])
 
+    def text(self, event):
+        check = self.id2check[event.GetEventObject().Id]
+        check.text = event.GetString()
+
     def append(self, check):
         self.vbox = wx.BoxSizer(wx.VERTICAL)
         self.vbox.Add(wx.StaticLine(self, -1, size=(500,-1)), 0, wx.ALL, 5)
@@ -381,13 +395,20 @@ class EntryPanel(scrolled.ScrolledPanel):
 
             hbox.Add(desc, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
             if isinstance(check,AreaCheck):
-                choices = ["is plain","is not plain"]
+                choices = ["is plain","is not plain","contains text"]
                 lb = wx.Choice(self, -1, (100, 50), choices = choices)
-                if isinstance(check,AreaPlain):
+                hbox.Add(lb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
+                if isinstance(check, AreaPlain):
                     setdefault(lb,0)
-                else:
+                elif isinstance(check, AreaNotPlain):
                     setdefault(lb,1)
-                hbox.Add(lb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
+                else:
+                    setdefault(lb,2)
+                    tb = wx.TextCtrl(self, -1, check.text, size=(100, 25))
+                    self.id2check[tb.Id] = check
+                    self.Bind(wx.EVT_TEXT, self.text, tb)
+
+                    hbox.Add(tb, 0, wx.ALIGN_LEFT|wx.ALL, 5)
             elif isinstance(check,TwoPixelCheck):
                 choices = ["is the same as","is brighter than","is darker than"]
                 lb = wx.Choice(self, -1, (100, 50), choices = choices)
index d1c1458..1ee0b39 100644 (file)
@@ -41,16 +41,20 @@ class ConversionFailed < Exception
 end
 
 class Area
-    def initialize(x1,y1,x2,y2,data)
-       @x1,@y1,@x2,@y2 = x1,y1,x2,y2
-       @rgb = Array.new(data.size/3) do |i| data.slice(i*3,3) end
+    def initialize(x1,y1,x2,y2,file)
+       @x1,@y1,@x2,@y2,@file = x1,y1,x2,y2,file
     end
     def should_be_plain_colored
+       @rgb = @file.get_area(@x1,@y1,@x2,@y2) unless @rgb
        @rgb.minmax == [@rgb[0],@rgb[0]] or raise AreaError.new(self,"is not plain colored")
     end
     def should_not_be_plain_colored
+       @rgb = @file.get_area(@x1,@y1,@x2,@y2) unless @rgb
        @rgb.minmax != [@rgb[0],@rgb[0]] or raise AreaError.new(self,"is plain colored")
     end
+    def should_contain_text(text)
+       @file.get_text(@x1,@y1,@x2,@y2) == text or raise AreaError.new(self, "doesn't contain text #{text}")
+    end
     def to_s
        "(#{@x1},#{@y1},#{@x2},#{@y2})"
     end
@@ -92,15 +96,21 @@ class DocFile
        @filename = filename
        @page = page
     end
-    def load()
+    def convert()
+       return if @swfname
        @swfname = @filename.gsub(/.pdf$/i,"")+".swf"
+       `pdfinfo #{@filename}` =~ /Page size:\s*([0-9]+) x ([0-9]+) pts/
+       width,height = $1,$2
+       dpi = (72.0 * 612 / width.to_i).to_i
+       output = `pdf2swf --flatten -s zoom=#{dpi} -p #{@page} #{@filename} -o #{@swfname} 2>&1`
+       #output = `pdf2swf -s zoom=#{dpi} --flatten -p #{@page} #{@filename} -o #{@swfname} 2>&1`
+       raise ConversionFailed.new(output,@swfname) unless File.exists?(@swfname)
+    end
+    def render()
+       return if @img
+       convert()
        @pngname = @filename.gsub(/.pdf$/i,"")+".png"
        begin
-           `pdfinfo #{@filename}` =~ /Page size:\s*([0-9]+) x ([0-9]+) pts/
-           width,height = $1,$2
-           dpi = (72.0 * 612 / width.to_i).to_i
-           output = `pdf2swf -s zoom=#{dpi} --flatten -p #{@page} #{@filename} -o #{@swfname} 2>&1`
-           raise ConversionFailed.new(output,@swfname) unless File.exists?(@swfname)
            output = `swfrender --legacy #{@swfname} -o #{@pngname} 2>&1`
            raise ConversionFailed.new(output,@pngname) unless File.exists?(@pngname)
            @img = Magick::Image.read(@pngname).first
@@ -109,21 +119,30 @@ class DocFile
            `rm -f #{@pngname}`
        end
     end
-    def area_at(x1,y1,x2,y2)
-       self.load()
+    def get_text(x1,y1,x2,y2)
+       self.convert()
+       puts "swfstrings -x #{x1} -y #{y1} -W #{x2-x1} -H #{y2-y1} #{@swfname}"
+       puts `swfstrings -x #{x1} -y #{y1} -W #{x2-x1} -H #{y2-y1} #{@swfname}`
+       `swfstrings -x #{x1} -y #{y1} -W #{x2-x1} -H #{y2-y1} #{@swfname}`
+    end
+    def get_area(x1,y1,x2,y2)
+       self.render()
        data = @img.export_pixels(x1, y1, x2-x1, y2-y1, "RGB")
-       return Area.new(x1,y1,x2,y2,data)
+       Array.new(data.size/3) do |i| data.slice(i*3,3) end
+    end
+    def area_at(x1,y1,x2,y2)
+       return Area.new(x1,y1,x2,y2,self)
     end
     def width()
-       self.load()
+       self.render()
        return @img.columns
     end
     def height()
-       self.load()
+       self.render()
        return @img.rows
     end
     def pixel_at(x,y)
-       self.load()
+       self.render()
        data = @img.export_pixels(x, y, 1, 1, "RGB")
        return Pixel.new(x,y,data)
     end
diff --git a/spec/textarea.pdf b/spec/textarea.pdf
new file mode 100644 (file)
index 0000000..196a613
Binary files /dev/null and b/spec/textarea.pdf differ
diff --git a/spec/textarea.py b/spec/textarea.py
new file mode 100644 (file)
index 0000000..4a77043
--- /dev/null
@@ -0,0 +1,26 @@
+from sys import * 
+from pdflib_py import * 
+import md5
+p = PDF_new() 
+PDF_open_file(p, "textarea.pdf")
+
+PDF_set_parameter(p, "usercoordinates", "true")
+
+PDF_set_info(p, "Creator", "smalltext.py")
+PDF_begin_page(p, 612, 3000)
+font = PDF_load_font(p, "Courier", "host", "")
+
+PDF_setfont(p, font, 12.0)
+i = 0
+# the idea is to overflow the placetext matrix once, so that
+# we have at least two different ty values
+for y in range(3000 / 9):
+    PDF_set_text_pos(p, 0, y*9);
+    text = "".join([md5.md5(str(i+j*732849)).hexdigest() for j in range(3)])
+    print text
+    PDF_show(p, text)
+    i = i + 1
+
+PDF_end_page(p)
+PDF_close(p)
+PDF_delete(p);
diff --git a/spec/textarea.spec.rb b/spec/textarea.spec.rb
new file mode 100644 (file)
index 0000000..dcaeea6
--- /dev/null
@@ -0,0 +1,10 @@
+require File.dirname(__FILE__) + '/spec_helper'
+
+describe "pdf conversion" do
+    convert_file "textarea.pdf" do
+        area_at(460,94,610,106).should_contain_text '97924ff65f9dfc75450ba'
+        area_at(467,373,525,384).should_contain_text '29cf24e47'
+        area_at(474,592,543,601).should_contain_text '0afa27099a'
+        area_at(59,798,131,808).should_contain_text '4c28e489b4'
+    end
+end
index a082a5f..12c653d 100644 (file)
@@ -415,10 +415,24 @@ void textcallback(void*self, int*glyphs, int*ypos, int nr, int fontid, int fonts
     printf("\n");
 }
 
-void handleText(TAG*tag) 
+void handleText(TAG*tag, char*prefix) 
 {
   printf("\n");
-  swf_ParseDefineText(tag,textcallback, 0);
+  if(placements) {
+      swf_SetTagPos(tag, 0);
+      swf_GetU16(tag);
+      swf_GetRect(tag, 0);
+      swf_ResetReadBits(tag);
+      MATRIX m;
+      swf_GetMatrix(tag, &m);
+      printf("%s| Matrix\n",prefix);
+      printf("%s| %5.3f %5.3f %6.2f\n", prefix, m.sx/65536.0, m.r1/65536.0, m.tx/20.0);
+      printf("%s| %5.3f %5.3f %6.2f\n", prefix, m.r0/65536.0, m.sy/65536.0, m.ty/20.0);
+      swf_SetTagPos(tag, 0);
+  }
+  if(showtext) {
+      swf_ParseDefineText(tag,textcallback, 0);
+  }
 }
            
 void handleDefineSound(TAG*tag)
@@ -1363,10 +1377,7 @@ int main (int argc,char ** argv)
            printf(" URL: %s\n", s);
        }
        else if(tag->id == ST_DEFINETEXT || tag->id == ST_DEFINETEXT2) {
-           if(showtext)
-               handleText(tag);
-           else
-               printf("\n");
+           handleText(tag, myprefix);
        }
        else if(tag->id == ST_DEFINESCALINGGRID) {
            U16 id = swf_GetU16(tag);
index bfca4fa..1bbd52f 100644 (file)
@@ -131,13 +131,21 @@ void textcallback(void*self, int*glyphs, int*advance, int nr, int fontid, int fo
 
     for(t=0;t<nr;t++)
     {
-       int xx = startx + advance[t]/20;
-       if(x|y|w|h) {
-           /* TODO: this does not do any matrix handling yet */
+       int xx = startx + advance[t];
+       int yy = starty;
+       MATRIX*m = (MATRIX*)self;
+       
+       SPOINT p = {xx,yy};
+       p = swf_TurnPoint(p, m);
+       xx = p.x / 20;
+       yy = p.y / 20;
 
-           if(xx < x || starty < y || xx > x+w || starty > y+h) {
+       if(x|y|w|h) {
+           if(xx < x || yy < y || xx > x+w || yy > y+h) {
                /* outside of bounding box */
-               continue;
+               ///printf("(%d+%d,%d) -> (%d,%d)\n", startx, advance[t]/20, starty, xx, yy);
+               if(t==nr-1) return;
+               else continue;
            }
        }
 
@@ -178,6 +186,7 @@ void fontcallback(void*self,U16 id,U8 * name)
   swf_FontFree(font);
 }
 
+TAG**id2tag = 0;
 
 int main (int argc,char ** argv)
 { 
@@ -199,6 +208,8 @@ int main (int argc,char ** argv)
        if(!h) h = (swf.movieSize.ymax - swf.movieSize.ymin) / 20;
     }
 
+    id2tag = malloc(sizeof(TAG)*65536);
+
     fontnum = 0;
     swf_FontEnumerate(&swf,&fontcallback1, 0);
     fonts = (SWFFONT**)malloc(fontnum*sizeof(SWFFONT*));
@@ -209,7 +220,22 @@ int main (int argc,char ** argv)
     while (tag)
     { 
        if(swf_isTextTag(tag)) {
-           swf_ParseDefineText(tag, textcallback, 0);
+           id2tag[swf_GetDefineID(tag)] = tag;
+       } else if(swf_isPlaceTag(tag)) {
+           SWFPLACEOBJECT po;
+           swf_SetTagPos(tag, 0);
+           swf_GetPlaceObject(tag, &po);
+           if(!po.move && id2tag[po.id]) {
+               TAG*text = id2tag[po.id];
+               swf_SetTagPos(text, 0);
+               swf_GetU16(text);
+               swf_GetRect(text, NULL);
+               swf_ResetReadBits(text);
+               MATRIX m,tm;
+               swf_GetMatrix(text, &tm);
+               swf_MatrixJoin(&m, &po.matrix, &tm);
+               swf_ParseDefineText(text, textcallback, &m);
+           }
        }
        tag = tag->next;
     }