001: package jimm.datavision.layout;
002:
003: import jimm.datavision.*;
004: import jimm.datavision.field.*;
005: import jimm.datavision.layout.LineDrawer;
006: import java.io.*;
007:
008: /**
009: * A LaTeX2e layout engine.
010: *
011: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
012: */
013: public class LaTeXLE extends LayoutEngine implements LineDrawer {
014:
015: protected double linethickness;
016:
017: public LaTeXLE(PrintWriter out) {
018: super (out);
019: linethickness = 0;
020: }
021:
022: /**
023: * Outputs the beginning of the document.
024: */
025: protected void doStart() {
026: String latexPaperSize = report.getPaperFormat()
027: .getLaTeXPaperSizeString();
028: out
029: .println("\\documentclass[11pt"
030: + (latexPaperSize == null ? ""
031: : ("," + latexPaperSize))
032: + "]{article}\n" + "\n"
033: + "\\oddsidemargin -1in\n"
034: + "\\evensidemargin -1in\n" + "\\textwidth "
035: + pageWidth() + "pt\n"
036: + "\\headheight -0.5pt\n" + "\\headsep 0pt\n"
037: + "\\topmargin 0.0in\n" + "\\textheight "
038: + pageHeight() + "pt\n" + "\n"
039: + "\\begin{document}\n"
040: + "\\pagestyle{empty}\n"
041: + "\\setlength{\\unitlength}{1pt}\n"
042: + "% Generated by DataVision version "
043: + info.Version + "\n" + "% " + info.URL);
044: }
045:
046: /**
047: * Outputs the end of the document.
048: */
049: protected void doEnd() {
050: out.println("\\end{document}");
051: }
052:
053: /**
054: * Start a new page.
055: */
056: protected void doStartPage() {
057: if (pageNumber > 1)
058: out.println("\\newpage");
059: out.println("% ======== Page " + pageNumber + " ========");
060: out.println("\\begin{picture}(" + pageWidth() + ","
061: + pageHeight() + ")");
062: }
063:
064: /**
065: * End a page.
066: */
067: protected void doEndPage() {
068: out.println("\\end{picture}");
069: }
070:
071: /**
072: * Outputs a field.
073: *
074: * @param field the field to output
075: */
076: protected void doOutputField(Field field) {
077: String fieldAsString = field.toString();
078: if (fieldAsString == null || fieldAsString.length() == 0) {
079: makeBorders(field);
080: out.println();
081: return;
082: }
083:
084: Format format = field.getFormat();
085: Rectangle bounds = field.getBounds();
086:
087: // \put(x, y) {
088: putField(field);
089:
090: // Calculate font size. Return array; first element for now, second
091: // element for later.
092: String[] size = selectFontSize(format);
093: if (size != null)
094: out.print(size[0]);
095:
096: // Align
097: String align = "";
098: switch (format.getAlign()) {
099: case Format.ALIGN_CENTER:
100: align = "\\begin{center}";
101: break;
102: case Format.ALIGN_RIGHT:
103: align = "\\raggedleft ";
104: break;
105: }
106: out.print("\\begin{minipage}[t]{" + bounds.width + "pt}"
107: + align);
108:
109: // // [NOTE: BROKEN; BALANCING '}' BELOW DOESN'T BALANCE PROPERLY
110: // // Nice for debugging: frame the box
111: // out.print("\\framebox(" + bounds.width + "," + bounds.height + "){"
112: // + align + "}{");
113:
114: // Bold, italic, underline
115: if (format.isBold())
116: out.print("\\textbf{");
117: if (format.isItalic())
118: out.print("\\textit{");
119: if (format.isUnderline())
120: out.print("\\underline{");
121:
122: // The actual, real, this-is-it field data
123: out.print(makeSafe(fieldAsString));
124:
125: // Close align, bold, italic, underline, and font size
126: if (format.isUnderline())
127: out.print("}");
128: if (format.isItalic())
129: out.print("}");
130: if (format.isBold())
131: out.print("}");
132:
133: if (format.getAlign() == Format.ALIGN_CENTER)
134: out.print("\\end{center}");
135:
136: // // [NOTE: BROKEN; THIS BALANCING '}' DOESN'T BALANCE PROPERLY
137: // // Nice for debugging: close the frame box
138: // out.print("}");
139:
140: out.print("\\end{minipage}");
141:
142: if (size != null)
143: out.print(size[1]);
144:
145: // Close \put
146: out.print("}");
147:
148: // Borders
149: makeBorders(field);
150:
151: out.println();
152: }
153:
154: /**
155: * Ignores image output
156: *
157: * @param field an image field
158: */
159: protected void doOutputImage(ImageField field) {
160: }
161:
162: /**
163: * Outputs a line. Calls {@link #drawLine}.
164: *
165: * @param line a line
166: */
167: protected void doOutputLine(Line line) {
168: drawLine(line, null);
169: }
170:
171: /**
172: * Returns array of two strings that set/reset font size. If format is
173: * <code>null</code> or has no size, return <code>null</code>.
174: *
175: * @return an array of two strings
176: */
177: protected String[] selectFontSize(Format format) {
178: if (format == null || format.getSize() == 0)
179: return null;
180: String[] size = new String[2];
181: size[0] = "{\\fontsize{" + format.getSize() + "pt}{"
182: + (format.getSize() * 1.2) + "pt}";
183: size[1] = "}";
184: return size;
185: }
186:
187: /**
188: * Outputs borders.
189: */
190: protected void makeBorders(Field field) {
191: field.getBorderOrDefault().eachLine(this , null);
192: }
193:
194: /**
195: * Draw a single line.
196: *
197: * @param line a line
198: */
199: public void drawLine(Line line, Object arg) {
200: Point p0 = line.getPoint(0);
201: Point p1 = line.getPoint(1);
202: double xdiff = p1.x - p0.x;
203: double ydiff = p1.y - p0.y;
204:
205: if (xdiff == 0 && ydiff == 0)
206: return;
207:
208: double xslope, yslope;
209: if (ydiff == 0) { // Horizontal
210: if (p1.x < p0.x)
211: xslope = -1;
212: else if (p1.x > p0.x)
213: xslope = 1;
214: else
215: xslope = 0;
216: yslope = 0;
217: } else if (xdiff == 0) { // Vertical
218: xslope = 0;
219: if (p1.y < p0.y)
220: yslope = 1;
221: else if (p1.y > p0.y)
222: yslope = -1;
223: else
224: yslope = 0;
225: } else {
226: // xslope = xdiff / ydiff;
227: // yslope = ydiff / xdiff;
228: xslope = xdiff;
229: yslope = ydiff;
230: // FIX
231: double[] slopes = pickNearestSlope(xslope / yslope);
232: if (slopes == null) {
233: xslope = 1;
234: yslope = 0;
235: } else {
236: xslope = slopes[0];
237: yslope = slopes[1];
238: }
239: }
240: setLineThickness(line.getThickness());
241: putLine(line, xslope, yslope);
242: }
243:
244: /**
245: * Returns an array containing two doubleing-point values representing
246: * the x and y values needed for a LaTeX2e line. LaTeX2e insists that
247: * these two values be integers, but we use doubles for accuracy and
248: * let <code>putLine</code> truncate them.
249: *
250: * @param slope a double
251: * @return an array containing x and y values
252: */
253: protected double[] pickNearestSlope(double slope) {
254: boolean found = false;
255: double xySlopeAbs = 0, xSlope = 0, ySlope = 0;
256: for (int x = -6; x <= 6; ++x) {
257: for (int y = -6; y <= 6; ++y) {
258: if (!found || xySlopeAbs > (slope - Math.abs(x / y))) {
259: xySlopeAbs = Math.abs(slope - (x / y));
260: xSlope = x;
261: ySlope = y;
262: found = true;
263: }
264: }
265: }
266: if (!found)
267: return null;
268:
269: double[] answer = new double[2];
270: answer[0] = xSlope;
271: answer[1] = ySlope;
272: return answer;
273: }
274:
275: /**
276: * Sets the line thickness for drawing.
277: *
278: * @param t thickness
279: */
280: protected void setLineThickness(double t) {
281: if (linethickness != t) {
282: linethickness = t;
283: out.print("\\linethickness{" + linethickness + "pt}");
284: }
285: }
286:
287: /**
288: * Outputs the LaTeX2e code that places the field on the page.
289: */
290: protected void putField(Field field) {
291: double y;
292: Rectangle bounds = field.getBounds();
293: if (currentSection.getArea().getArea() == SectionArea.PAGE_FOOTER) {
294: // Page footers are anchored to the bottom of the page
295: y = currentSection.getOutputHeight() - bounds.y;
296: } else {
297: y = pageHeight() - (pageHeightUsed + bounds.y);
298: }
299:
300: // In a \put command, x and y determine the bottom left corner of
301: // whatever you are placing.
302: y -= field.getOutputHeight();
303:
304: out.print("\\put(" + bounds.x + "," + y + "){");
305: }
306:
307: /**
308: * Outputs the LaTeX2e code that places the line on the page. Both xslope
309: * and yslope are cast to ints because that's what LaTeX2e requires.
310: *
311: * @param line the line to draw
312: * @param xslope the X slope of the line; must be from -6 through 6
313: * @param yslope the Y slope of the line; must be from -6 through 6
314: */
315: protected void putLine(Line line, double xslope, double yslope) {
316: double y;
317: Point p0 = line.getPoint(0);
318: if (currentSection.getArea().getArea() == SectionArea.PAGE_FOOTER) {
319: // Page footers are anchored to the bottom of the page
320: y = p0.y;
321: } else {
322: y = pageHeight() - (pageHeightUsed + p0.y);
323: }
324:
325: // In a \put command, x and y determine the bottom left corner of
326: // whatever you are placing.
327: out.println("\\put(" + p0.x + "," + y + "){\\line("
328: + (int) xslope + "," + (int) yslope + "){"
329: + line.length() + "}}");
330: }
331:
332: /**
333: * Returns a new string with all LaTeX2e special characters replaced
334: * by their printable equivalents.
335: *
336: * @param str
337: * @return a string safe for LaTeX2e output
338: */
339: protected String makeSafe(String str) {
340: StringBuffer buf = new StringBuffer();
341: int len = str.length();
342: for (int i = 0; i < len; ++i) {
343: char c = str.charAt(i);
344: switch (c) {
345: case '\\':
346: buf.append("$\\backslash$");
347: break;
348: case '~':
349: buf.append("\\textasciitilde");
350: break;
351: case '#':
352: case '$':
353: case '%':
354: case '&':
355: case '_':
356: case '^':
357: case '{':
358: case '}':
359: buf.append('\\');
360: buf.append(c);
361: break;
362: default:
363: buf.append(c);
364: break;
365: }
366: }
367: return buf.toString();
368: }
369:
370: }
|