OSDN Git Service

1e4a57e0c081bec75548b6e86b56f59cc52a8eb6
[dvibrowser/dvi2epub.git] / src / jp / sourceforge / dvibrowser / dvicore / font / RunLengthEncodedGlyph.java
1 /*
2  * Copyright (c) 2009, Takeyuki Nagao
3  * All rights reserved.
4  * 
5  * Redistribution and use in source and binary forms, with or
6  * without modification, are permitted provided that the
7  * following conditions are met:
8  * 
9  *  * Redistributions of source code must retain the above
10  *    copyright notice, this list of conditions and the
11  *    following disclaimer.
12  *  * Redistributions in binary form must reproduce the above
13  *    copyright notice, this list of conditions and the
14  *    following disclaimer in the documentation and/or other
15  *    materials provided with the distribution.
16  *    
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
18  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
19  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
29  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
30  * OF SUCH DAMAGE.
31  */
32
33 package jp.sourceforge.dvibrowser.dvicore.font;
34
35 import java.io.PrintWriter;
36 import java.io.StringWriter;
37 import java.util.ArrayList;
38
39 import jp.sourceforge.dvibrowser.dvicore.DviException;
40 import jp.sourceforge.dvibrowser.dvicore.DviPoint;
41 import jp.sourceforge.dvibrowser.dvicore.DviRect;
42 import jp.sourceforge.dvibrowser.dvicore.DviResolution;
43 import jp.sourceforge.dvibrowser.dvicore.api.BinaryDevice;
44 import jp.sourceforge.dvibrowser.dvicore.render.AbstractDevice;
45
46
47 // mutable.
48
49 public final class RunLengthEncodedGlyph
50 {
51   private static final int DEFAULT_ARRAY_SIZE = 256;
52
53   private int width;
54   private int height;
55   private int xOffset;
56   private int yOffset;
57   private ArrayList<RunLengthEncodedLine> lines;
58
59   public RunLengthEncodedGlyph()
60   {
61     this(0, 0, 0, 0);
62   }
63
64   private RunLengthEncodedGlyph(int width, int height, int xOffset, int yOffset)
65   {
66     this.width = width;
67     this.height = height;
68     this.xOffset = xOffset;
69     this.yOffset = yOffset;
70     this.lines = new ArrayList<RunLengthEncodedLine>(DEFAULT_ARRAY_SIZE);
71   }
72
73   public static RunLengthEncodedGlyph readByteBinary(
74     byte [] buf, int width, int height,
75     int xOffset, int yOffset)
76   {
77     RunLengthEncodedGlyph rlg = new RunLengthEncodedGlyph(width, height, xOffset, yOffset);
78
79     RunLengthEncodedLine prev = null;
80     int pitch = ((width + 7) >>> 3) << 3;
81     int bitOffset = 0;
82     for (int i=0; i<height; i++) {
83       RunLengthEncodedLine line = new RunLengthEncodedLine();
84       line.append(buf, bitOffset, width);
85       if (line.equals(prev)) {
86         rlg.lines.add(prev);
87       } else {
88         rlg.lines.add(line);
89         prev = line;
90       }
91       bitOffset += pitch;
92     }
93
94     rlg.compact();
95
96     return rlg;
97   }
98
99   public static RunLengthEncodedGlyph readRasterByBits(
100     byte [] buf, int width, int height,
101     int xOffset, int yOffset)
102   {
103     RunLengthEncodedGlyph rlg = new RunLengthEncodedGlyph(width, height, xOffset, yOffset);
104
105     int bitOffset = 0;
106     RunLengthEncodedLine prev = null;
107     for (int i=0; i<height; i++) {
108       RunLengthEncodedLine line = new RunLengthEncodedLine();
109       line.append(buf, bitOffset, width);
110       bitOffset += width;
111       if (line.equals(prev)) {
112         rlg.lines.add(prev);
113       } else {
114         rlg.lines.add(line);
115         prev = line;
116       }
117     }
118     rlg.compact();
119
120     return rlg;
121   }
122
123   public static RunLengthEncodedGlyph readByteGray(
124     byte [] buf, int width, int height,
125     int xOffset, int yOffset)
126   {
127     RunLengthEncodedGlyph rlg = new RunLengthEncodedGlyph(width, height, xOffset, yOffset);
128
129     int ptr = 0;
130     RunLengthEncodedLine prev = null;
131     for (int i=0; i<height; i++) {
132       final RunLengthEncodedLine line = new RunLengthEncodedLine();
133
134       int last = 314; // A MAGIC value to denote the begin of line.
135       int count = 0;
136       for (int j=0; j<width; j++) {
137         final int c = buf[ptr++];
138         if (c == last) {
139           count++;
140         } else {
141           if (last != 314)
142             line.append(count, (0 != last));
143           count = 1;
144           last = c;
145         }
146       }
147       if (count > 0)
148         line.append(count, (0 != last));
149
150       if (line.equals(prev)) {
151         rlg.lines.add(prev);
152       } else {
153         rlg.lines.add(line);
154         prev = line;
155       }
156     }
157     rlg.compact();
158
159     return rlg;
160   }
161
162
163   public DviRect getBounds()
164   {
165     return new DviRect(-xOffset, -yOffset, width, height);
166   }
167   public int width()   { return width; }
168   public int height()  { return height; }
169   public int xOffset() { return xOffset; }
170   public int yOffset() { return yOffset; }
171
172   public boolean isEmpty()
173   {
174     return (width <= 0 || height <= 0 || lines.size() == 0);
175   }
176
177   public void rasterizeTo(BinaryDevice out)
178   throws DviException
179   {
180     out.save();
181     try {
182       out.translate(-xOffset, -yOffset);
183       if (out.beginRaster(width, height)) {
184       // TODO: use repeat.
185         for (int i=0; i<height; i++) {
186           RunLengthEncodedLine line = lines.get(i);
187           out.beginLine();
188           line.rasterizeTo(out);
189           out.endLine(0);
190         }
191       }
192       out.endRaster();
193     } finally {
194       out.restore();
195     }
196   }
197
198   public void compact()
199   {
200     int lskip = Integer.MAX_VALUE;
201     int rskip = Integer.MAX_VALUE;
202
203     RunLengthEncodedLine prev = null;
204     for (int i=0; i<height; i++) {
205       RunLengthEncodedLine line = lines.get(i);
206       if (prev == line) continue;
207       if (lskip > 0) {
208         if (line.headOn()) {
209           lskip = 0;
210         } else {
211           lskip = Math.min(lskip, line.head());
212         }
213       }
214     }
215
216     prev = null;
217     for (int i=0; i<height; i++) {
218       RunLengthEncodedLine line = lines.get(i);
219       if (prev == line) continue;
220       line.cropHead(lskip);
221       prev = line;
222     }
223     width -= lskip;
224     xOffset -= lskip;
225
226     if (width <= 0) {
227       width = height = 0;
228       lines.clear();
229       xOffset = yOffset = 0;
230       return;
231     }
232
233     prev = null;
234     for (int i=0; i<height; i++) {
235       RunLengthEncodedLine line = lines.get(i);
236       if (prev == line) continue;
237       if (rskip > 0) {
238         if (line.tailOn()) {
239           rskip = 0;
240         } else {
241           rskip = Math.min(rskip, line.tail());
242         }
243       }
244       prev = line;
245     }
246
247     prev = null;
248     for (int i=0; i<height; i++) {
249       RunLengthEncodedLine line = lines.get(i);
250       if (prev == line) continue;
251       line.cropTail(rskip);
252       prev = line;
253     }
254     width -= rskip;
255
256     if (width <= 0) {
257       width = height = 0;
258       lines.clear();
259       xOffset = yOffset = 0;
260       return;
261     }
262
263     int tskip = 0;
264     int bskip = 0;
265
266     while (height > 0) {
267       RunLengthEncodedLine line = lines.get(0);
268       if (!line.allOff())
269         break;
270       lines.remove(0);
271       height--;
272       yOffset--;
273       tskip++;
274     }
275
276     while (height > 0) {
277       RunLengthEncodedLine line = lines.get(height-1);
278       if (!line.allOff())
279         break;
280       lines.remove(height-1);
281       height--;
282       bskip++;
283     }
284
285     for (int i=0; i<height; i++) {
286       RunLengthEncodedLine line = lines.get(i);
287       if (line.isEmpty()) {
288         throw new IllegalStateException
289           ("width=" + width + " height=" + height);
290       }
291     }
292     if (width <= 0 || height <= 0) {
293       throw new IllegalStateException
294         ("width=" + width + " height=" + height);
295     }
296   }
297
298   public String dump()
299   {
300     StringWriter sw = new StringWriter();
301     PrintWriter pw = new PrintWriter(sw);
302     for (int i=0; i<height; i++) {
303       RunLengthEncodedLine line = lines.get(i);
304       boolean duplicate = (
305         (i > 0) && lines.get(i-1) == line
306       );
307       pw.println((duplicate ? "+ " : "  " ) + line.dump());
308     }
309     return sw.toString();
310   }
311
312   public String toString()
313   {
314     return getClass().getName()
315       + "[width=" + width
316       + " height=" +height
317       + "]";
318   }
319
320   public PkGlyph toPkGlyph()
321   {
322     if (isEmpty())
323       return PkGlyph.EMPTY;
324
325     ArrayList<Integer> counts = new ArrayList<Integer>(DEFAULT_ARRAY_SIZE);
326
327     int count=0;
328     int repeat=0;
329     RunLengthEncodedLine line = lines.get(0);
330     boolean turnOn = line.headOn();
331     int i = 0;
332     while (line != null) {
333       ArrayList<Integer> data = line.getData();
334       final int ds = data.size();
335       for (int j=0; j<ds; j++) {
336         int c = data.get(j);
337         boolean needFlush = true;
338         if (j == ds - 1) {
339           // at the end of the line.
340           int r = 0;
341           RunLengthEncodedLine next = null;
342           while (++i < height) {
343             next = lines.get(i);
344             if (next != line)
345               break;
346             r++;
347           }
348           needFlush = (
349             next == null ||
350             next.headOn() != line.tailOn()
351           );
352           line = next;
353           if (count > 0) {
354             count += data.get(j) + r * width;
355             // repeat is unchanged.
356           } else {
357             count = data.get(j);
358             repeat = r;
359           }
360         } else {
361           count += c;
362         }
363         if (needFlush) {
364           if (repeat > 0) {
365             counts.add(-repeat);
366             repeat = 0;
367           }
368           counts.add(count);
369           count = 0;
370         }
371       }
372     }
373
374     PackedSequence ps = new SequencePacker(counts).pack();
375
376     return new PkGlyph(
377       width, height,
378       ps.data(), ps.dynF(),
379       turnOn,
380       xOffset, yOffset
381     );
382   }
383
384   public DviRect bounds()
385   throws DviException
386   {
387     if (isEmpty())
388       return DviRect.EMPTY;
389     else
390       return new DviRect(-xOffset, -yOffset, width, height);
391   }
392
393   public void unite(RunLengthEncodedGlyph rlg)
394   {
395     DviRect u = rlg.getBounds().union(getBounds());
396
397     ArrayList<RunLengthEncodedLine> newLines
398       = new ArrayList<RunLengthEncodedLine>(DEFAULT_ARRAY_SIZE);
399     RunLengthEncodedLine last = null;
400     int ey = u.bottom();
401     for (int y = u.top(); y<=ey; y++) {
402       RunLengthEncodedLine a = new RunLengthEncodedLine();
403       RunLengthEncodedLine b = new RunLengthEncodedLine();
404       int ia = y + yOffset;
405       int ib = y + rlg.yOffset;
406
407       if (0 <= ia && ia < height) {
408         int w = u.width();
409         int lpad = -xOffset-u.left();
410         if (lpad > 0) {
411           a.append(lpad, false);
412           w -= lpad;
413         }
414         a.append(lines.get(ia));
415         w -= width;
416         a.append(w, false);
417       } else {
418         a.append(u.width(), false);
419       }
420
421       if (0 <= ib && ib < rlg.height) {
422         int w = u.width();
423         int lpad = -rlg.xOffset-u.left();
424         if (lpad > 0) {
425           b.append(lpad, false);
426           w -= lpad;
427         }
428         b.append(rlg.lines.get(ib));
429         w -= rlg.width;
430         b.append(w, false);
431       } else {
432         b.append(u.width(), false);
433       }
434
435       a = RunLengthEncodedLine.union(a, b);
436
437       if (a.equals(last)) {
438         newLines.add(last);
439       } else {
440         newLines.add(a);
441         last = a;
442       }
443     }
444
445     this.width   = u.width();
446     this.height  = u.height();
447     this.xOffset = -u.x();
448     this.yOffset = -u.y();
449     this.lines = newLines;
450     compact();
451   }
452
453
454   public BinaryDevice getBinaryDevice(DviResolution res)
455   throws DviException
456   {
457     return new BinaryDeviceImpl(res);
458   }
459
460 //  private static String dumpCounts(ArrayList<Integer> counts, boolean flag)
461 //  {
462 //    String str = "";
463 //
464 //    for (int k=0; k<counts.size(); k++) {
465 //      int c = counts.get(k);
466 //      
467 //      if (c < 0) {
468 //        str += "[" + (-c) + "]";
469 //      } else if (flag) {
470 //        str += String.valueOf(c);
471 //        flag = !flag;
472 //      } else {
473 //        str += "(" + c + ")";
474 //        flag = !flag;
475 //      }
476 //    }
477 //
478 //    return str;
479 //  }
480
481   private class BinaryDeviceImpl
482   extends AbstractDevice
483   implements BinaryDevice
484   {
485     private BinaryDeviceImpl(DviResolution res)
486     {
487       super(res);
488     }
489
490     public void begin()
491     throws DviException
492     {
493     }
494
495     public void end()
496     throws DviException
497     {
498     }
499
500     private RunLengthEncodedGlyph rlg = null;
501     public boolean beginRaster(int w, int h)
502     throws DviException
503     {
504       rlg = new RunLengthEncodedGlyph();
505       rlg.width = w;
506       rlg.height = h;
507       return true;
508     }
509
510     public void endRaster()
511     throws DviException
512     {
513       DviPoint p = getReferencePoint();
514       // The reference point of rlg is at the origin of this device.
515       rlg.xOffset = -p.x;
516       rlg.yOffset = -p.y;
517       rlg.compact();
518
519       unite(rlg);
520       rlg = null;
521     }
522
523     private RunLengthEncodedLine line = null;
524     public void beginLine()
525     throws DviException
526     {
527       line = new RunLengthEncodedLine();
528     }
529
530     public void endLine(int repeat)
531     throws DviException
532     {
533       for (int i=0; i<=repeat; i++) {
534         rlg.lines.add(line);
535       }
536       line = null;
537     }
538
539     public void putBits(int count, boolean paintFlag)
540     {
541       line.append(count, paintFlag);
542     }
543   }
544 }