From 5c336b4f7017fb121fc2cbe2a02c1fa56986b560 Mon Sep 17 00:00:00 2001 From: Matthias Kramm Date: Fri, 14 Aug 2009 20:50:03 +0200 Subject: [PATCH] added test for text selection functionality --- lib/devices/polyops.c | 4 ++++ spec/edit_spec.py | 31 ++++++++++++++++++++++++++----- spec/spec_helper.rb | 49 ++++++++++++++++++++++++++++++++++--------------- spec/textarea.pdf | Bin 0 -> 22078 bytes spec/textarea.py | 26 ++++++++++++++++++++++++++ spec/textarea.spec.rb | 10 ++++++++++ src/swfdump.c | 23 +++++++++++++++++------ src/swfstrings.c | 38 ++++++++++++++++++++++++++++++++------ 8 files changed, 149 insertions(+), 32 deletions(-) create mode 100644 spec/textarea.pdf create mode 100644 spec/textarea.py create mode 100644 spec/textarea.spec.rb diff --git a/lib/devices/polyops.c b/lib/devices/polyops.c index 70fd99b..fea200b 100644 --- a/lib/devices/polyops.c +++ b/lib/devices/polyops.c @@ -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(" 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); diff --git a/spec/edit_spec.py b/spec/edit_spec.py index 2ab4453..b270d18 100755 --- a/spec/edit_spec.py +++ b/spec/edit_spec.py @@ -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) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d1c1458..1ee0b39 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -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 index 0000000000000000000000000000000000000000..196a613140ef1ee93294d4f2a2bced6f90e3975e GIT binary patch literal 22078 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 YbOzCz@g?pEI9d}0+PnAAvBRMM0G3b$O#lD@ literal 0 HcmV?d00001 diff --git a/spec/textarea.py b/spec/textarea.py new file mode 100644 index 0000000..4a77043 --- /dev/null +++ b/spec/textarea.py @@ -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 index 0000000..dcaeea6 --- /dev/null +++ b/spec/textarea.spec.rb @@ -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 diff --git a/src/swfdump.c b/src/swfdump.c index a082a5f..12c653d 100644 --- a/src/swfdump.c +++ b/src/swfdump.c @@ -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); diff --git a/src/swfstrings.c b/src/swfstrings.c index bfca4fa..1bbd52f 100644 --- a/src/swfstrings.c +++ b/src/swfstrings.c @@ -131,13 +131,21 @@ void textcallback(void*self, int*glyphs, int*advance, int nr, int fontid, int fo for(t=0;t 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; } -- 1.7.10.4