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 020 package org.crsh.text.ui; 021 022 import java.util.Arrays; 023 024 /** 025 * The layout computes the lengths of a list of contiguous cells. 026 */ 027 public abstract class Layout { 028 029 public static Layout flow() { 030 return RTL; 031 } 032 033 public static Layout weighted(int... weights) throws NullPointerException, IllegalArgumentException { 034 return new Weighted(weights); 035 } 036 037 /** 038 * Computes the list of lengths for the specifid list of cells with the following constraints: 039 * 040 * <ul> 041 * <li>the sum of the returned array elements must be equals to the <code>totalLength</code> argument</li> 042 * <li>a cell length should never be lesser than the provided minimum length</li> 043 * </ul> 044 * 045 * The returned array is the list of lengths from left to right, the array size may be less than the 046 * number of cells (i.e the size of the <code>actualLengths</code> and <code>minLengths</code> arguments). Missing 047 * cells are just be discarded and not part of the resulting layout. Array should contain only positive values, 048 * any zero length cell should be discarded. When cells must be discarded it must begin with the tail of the 049 * list, i.e it is not allowed to discard a cell that does not have a successor. 050 * 051 * 052 * @param spaced true if the cells are separated by one char 053 * @param totalLength the total length of the line 054 * @param actualLengths the actual length : an estimation of what cell's desired length 055 * @param minLengths the minmum length : the length under which a cell cannot be rendered 056 * @return the list of length. 057 */ 058 abstract int[] compute(boolean spaced, int totalLength, int[] actualLengths, int[] minLengths); 059 060 public static class Weighted extends Layout { 061 062 /** The weights. */ 063 private final int[] weights; 064 065 /** 066 * Create a new weighted layout. 067 * 068 * @param weights the weights 069 * @throws NullPointerException if the weights argument is null 070 * @throws IllegalArgumentException if any weight is negative 071 */ 072 private Weighted(int... weights) throws NullPointerException, IllegalArgumentException { 073 if (weights == null) { 074 throw new NullPointerException("No null weights accepted"); 075 } 076 for (int weight : weights) { 077 if (weight < 0) { 078 throw new IllegalArgumentException("No negative weight accepted"); 079 } 080 } 081 this.weights = weights.clone(); 082 } 083 084 public int[] getWeights() { 085 return weights.clone(); 086 } 087 088 @Override 089 int[] compute(boolean spaced, int length, int[] actualLengths, int[] minLengths) { 090 091 // 092 int count = Math.min(actualLengths.length, weights.length); 093 094 // 095 for (int i = count;i > 0;i--) { 096 097 // 098 int totalLength = length; 099 int totalWeight = 0; 100 for (int j = 0;j < i;j++) { 101 totalWeight += weights[j]; 102 if (spaced) { 103 if (j > 0) { 104 totalLength--; 105 } 106 } 107 } 108 109 // Compute the length of each cell 110 int[] ret = new int[i]; 111 for (int j = 0;j < i;j++) { 112 int w = totalLength * weights[j]; 113 if (w < minLengths[j] * totalWeight) { 114 ret = null; 115 break; 116 } else { 117 ret[j] = w; 118 } 119 } 120 121 // 122 if (ret != null) { 123 // Error based scaling inspired from Brensenham algorithm: 124 // => sum of the weights == length 125 // => minimize error 126 // for instance with "foo","bar" scaled to 11 chars 127 // using floor + division gives "foobar_____" 128 // this methods gives "foo_bar____" 129 int err = 0; 130 for (int j = 0;j < ret.length;j++) { 131 132 // Compute base value 133 int value = ret[j] / totalWeight; 134 135 // Lower value 136 int lower = value * totalWeight; 137 int errLower = err + ret[j] - lower; 138 139 // Upper value 140 int upper = lower + totalWeight; 141 int errUpper = err + ret[j] - upper; 142 143 // We choose between lower/upper according to the accumulated error 144 // and we propagate the error 145 if (Math.abs(errLower) < Math.abs(errUpper)) { 146 ret[j] = value; 147 err = errLower; 148 } else { 149 ret[j] = value + 1; 150 err = errUpper; 151 } 152 } 153 return ret; 154 } 155 } 156 157 // 158 return null; 159 } 160 } 161 162 private static final Layout RTL = new Layout() { 163 164 @Override 165 int[] compute(boolean spaced, int length, int[] actualLengths, int[] minLengths) { 166 167 // 168 int[] ret = actualLengths.clone(); 169 170 // 171 int totalLength = 0; 172 for (int i = 0;i < actualLengths.length;i++) { 173 totalLength += actualLengths[i]; 174 if (spaced && i > 0) { 175 totalLength++; 176 } 177 } 178 179 // 180 int index = minLengths.length - 1; 181 while (totalLength > length && index >= 0) { 182 int delta = totalLength - length; 183 int bar = actualLengths[index] - minLengths[index]; 184 if (delta <= bar) { 185 // We are done 186 totalLength = length; 187 ret[index] -= delta; 188 } else { 189 int foo = actualLengths[index]; 190 if (spaced) { 191 if (index > 0) { 192 foo++; 193 } 194 } 195 totalLength -= foo; 196 ret[index] = 0; 197 index--; 198 } 199 } 200 201 // 202 if (totalLength > 0) { 203 if (index == minLengths.length - 1) { 204 return ret; 205 } else { 206 return Arrays.copyOf(ret, index + 1); 207 } 208 } else { 209 return null; 210 } 211 } 212 }; 213 /* 214 215 public static final ColumnLayout DISTRIBUTED = new ColumnLayout() { 216 @Override 217 public int[] compute(Border border, int width, int[] widths, int[] minWidths) { 218 int index = 0; 219 while (true) { 220 221 // Compute now the number of chars 222 boolean done = false; 223 int total = 0; 224 for (int i = 0;i < widths.length;i++) { 225 if (widths[i] >= minWidths[i]) { 226 total += widths[i]; 227 if (border != null) { 228 if (done) { 229 total++; 230 } 231 else { 232 total += 2; 233 done = true; 234 } 235 } 236 } 237 } 238 239 // It's not valid 240 if (total == 0) { 241 return null; 242 } 243 244 // 245 int delta = width - total; 246 247 // 248 if (delta == 0) { 249 break; 250 } else if (delta > 0) { 251 switch (widths[index]) { 252 case 0: 253 break; 254 default: 255 widths[index]++; 256 break; 257 } 258 index = (index + 1) % widths.length; 259 } else { 260 261 // First try to remove from a column above min size 262 int found = -1; 263 for (int i = widths.length - 1;i >= 0;i--) { 264 int p = (index + i) % widths.length; 265 if (widths[p] > minWidths[p]) { 266 found = p; 267 if (--index < 0) { 268 index += widths.length; 269 } 270 break; 271 } 272 } 273 274 // If we haven't found a victim then we consider removing a column 275 if (found == -1) { 276 for (int i = widths.length - 1;i >= 0;i--) { 277 if (widths[i] > 0) { 278 found = i; 279 break; 280 } 281 } 282 } 283 284 // We couldn't find any solution we give up 285 if (found == -1) { 286 break; 287 } else { 288 widths[found]--; 289 } 290 } 291 } 292 293 // 294 return widths; 295 } 296 }; 297 */ 298 }