001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020package org.crsh.text;
021
022import java.util.Iterator;
023
024/**
025 * A line oriented renderer.
026 */
027public abstract class LineRenderer {
028
029  public static final LineRenderer NULL = new LineRenderer() {
030    @Override
031    public int getActualWidth() {
032      return 0;
033    }
034    @Override
035    public int getMinWidth() {
036      return 0;
037    }
038    @Override
039    public int getMinHeight(int width) {
040      return 0;
041    }
042    @Override
043    public int getActualHeight(int width) {
044      return 0;
045    }
046    @Override
047    public LineReader reader(int width) {
048      return new LineReader() {
049        public boolean hasLine() {
050          return false;
051        }
052        public void renderLine(RenderAppendable to) throws IllegalStateException {
053          throw new IllegalStateException();
054        }
055      };
056    }
057  };
058
059  public static LineRenderer vertical(Iterable<? extends LineRenderer> renderers) {
060    Iterator<? extends LineRenderer> i = renderers.iterator();
061    if (i.hasNext()) {
062      LineRenderer renderer = i.next();
063      if (i.hasNext()) {
064        return new Composite(renderers);
065      } else {
066        return renderer;
067      }
068    } else {
069      return NULL;
070    }
071  }
072
073  /**
074   * Returns the element actual width.
075   *
076   * @return the actual width
077   */
078  public abstract int getActualWidth();
079
080  /**
081   * Returns the element minimum width.
082   *
083   * @return the minimum width
084   */
085  public abstract int getMinWidth();
086
087  /**
088   * Return the minimum height for the specified with.
089   *
090   * @param width the width
091   * @return the actual height
092   */
093  public abstract int getMinHeight(int width);
094
095  /**
096   * Return the actual height for the specified with.
097   *
098   * @param width the width
099   * @return the minimum height
100   */
101  public abstract int getActualHeight(int width);
102
103  /**
104   * Create a renderer for the specified width and height or return null if the element does not provide any output
105   * for the specified dimensions. The default implementation delegates to the {@link #reader(int)} method when the
106   * <code>height</code> argument is not positive otherwise it returns null. Subclasses should override this method
107   * when they want to provide content that can adapts to the specified height.
108   *
109   * @param width the width
110   * @param height the height
111   * @return the renderer
112   */
113  public LineReader reader(int width, int height) {
114    if (height > 0) {
115      return null;
116    } else {
117      return reader(width);
118    }
119  }
120
121  /**
122   * Create a renderer for the specified width or return null if the element does not provide any output.
123   *
124   * @param width the width
125   * @return the renderer
126   */
127  public abstract LineReader reader(int width);
128
129  /**
130   * Renders this object to the provided output.
131   *
132   * @param out the output
133   */
134  public final void render(RenderAppendable out) {
135    LineReader renderer = reader(out.getWidth());
136    if (renderer != null) {
137      while (renderer.hasLine()) {
138        renderer.renderLine(out);
139        out.append('\n');
140      }
141    }
142  }
143
144  private static class Composite extends LineRenderer {
145
146    /** . */
147    private final Iterable<? extends LineRenderer> renderers;
148
149    /** . */
150    private final int actualWidth;
151
152    /** . */
153    private final int minWidth;
154
155    private Composite(Iterable<? extends LineRenderer> renderers) {
156
157      int actualWidth = 0;
158      int minWidth = 0;
159      for (LineRenderer renderer : renderers) {
160        actualWidth = Math.max(actualWidth, renderer.getActualWidth());
161        minWidth = Math.max(minWidth, renderer.getMinWidth());
162      }
163
164      this.actualWidth = actualWidth;
165      this.minWidth = minWidth;
166      this.renderers = renderers;
167    }
168
169    @Override
170    public int getActualWidth() {
171      return actualWidth;
172    }
173
174    @Override
175    public int getMinWidth() {
176      return minWidth;
177    }
178
179    @Override
180    public int getActualHeight(int width) {
181      int actualHeight = 0;
182      for (LineRenderer renderer : renderers) {
183        actualHeight += renderer.getActualHeight(width);
184      }
185      return actualHeight;
186    }
187
188    @Override
189    public int getMinHeight(int width) {
190      return 1;
191    }
192
193    @Override
194    public LineReader reader(final int width, final int height) {
195
196      final Iterator<? extends LineRenderer> i = renderers.iterator();
197
198      //
199      return new LineReader() {
200
201        /** . */
202        private LineReader current;
203
204        /** . */
205        private int index = 0;
206
207        public boolean hasLine() {
208          if (height > 0 && index >= height) {
209            return false;
210          } else {
211            if (current == null || !current.hasLine()) {
212              while (i.hasNext()) {
213                LineRenderer next = i.next();
214                LineReader reader = next.reader(width);
215                if (reader != null && reader.hasLine()) {
216                  current = reader;
217                  return true;
218                }
219              }
220              return false;
221            } else {
222              return true;
223            }
224          }
225        }
226
227        public void renderLine(RenderAppendable to) throws IllegalStateException {
228          if (hasLine()) {
229            current.renderLine(to);
230            index++;
231          } else {
232            throw new IllegalStateException();
233          }
234        }
235      };
236    }
237
238    @Override
239    public LineReader reader(final int width) {
240      return reader(width, -1);
241    }
242  }
243}