/******************************************************************************* * Copyright (c) 2012 Original authors and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Original authors and others - initial API and implementation ******************************************************************************/ package org.eclipse.nebula.widgets.nattable.painter.cell; import java.util.Map; import java.util.WeakHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; public class TextPainterOptimisationHarness { public static final String EMPTY = ""; public static final String DOT = "..."; private static Map<String, Integer> temporaryMap = new WeakHashMap<String, Integer>(); private static Map<org.eclipse.swt.graphics.Font, FontData[]> fontDataCache = new WeakHashMap<org.eclipse.swt.graphics.Font, FontData[]>(); private static boolean wrapText = false; private static final Pattern endOfPreviousWordPattern = Pattern .compile("\\S\\s+\\S+\\s*$"); private static int expensiveMethodCallCounter = 0; private static boolean useOptimiser = true; // you can adjust the width of the label and the label text here!! private static final int labelWidth = 50; private static final String labelMessage = "This is the text to fit into the label"; /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { Display display = new Display(); Shell shell = new Shell(display); Label label = new Label(shell, SWT.BORDER); GC gc = new GC(label); FontMetrics fm = gc.getFontMetrics(); int height = fm.getHeight(); label.setSize(label.computeSize(labelWidth, height)); useOptimiser = true; expensiveMethodCallCounter = 0; String textUsingOptimiser = getAvailableTextToDisplay(gc, label.getBounds(), labelMessage); System.out .println("number of expensive method calls when optimised = " + expensiveMethodCallCounter); useOptimiser = false; expensiveMethodCallCounter = 0; String textWithoutUsingOptimiser = getAvailableTextToDisplay(gc, label.getBounds(), labelMessage); System.out .println("number of expensive method calls when not optimised = " + expensiveMethodCallCounter); if (!textWithoutUsingOptimiser.equals(textUsingOptimiser)) { throw new Exception("The end result strings were not consistent"); } label.setText(textUsingOptimiser); gc.dispose(); shell.pack(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } private static int getWidthFromCache(GC gc, String text) { expensiveMethodCallCounter++; String originalString = text; StringBuilder buffer = new StringBuilder(); buffer.append(text); if (gc.getFont() != null) { FontData[] datas = fontDataCache.get(gc.getFont()); if (datas == null) { datas = gc.getFont().getFontData(); fontDataCache.put(gc.getFont(), datas); } if (datas != null && datas.length > 0) { buffer.append(datas[0].getName()); buffer.append(","); buffer.append(datas[0].getHeight()); buffer.append(","); buffer.append(datas[0].getStyle()); } } text = buffer.toString(); Integer width = temporaryMap.get(text); if (width == null) { width = Integer.valueOf(gc.textExtent(originalString).x); temporaryMap.put(text, width); } return width.intValue(); } private static String getAvailableTextToDisplay(GC gc, Rectangle bounds, String text) { StringBuilder output = new StringBuilder(); text = text.trim(); while (text.length() > 0) { String line; int nextLineBreakIndex; int indexOfNewline = text.indexOf('\n'); if (indexOfNewline > 0) { nextLineBreakIndex = indexOfNewline; line = text.substring(0, nextLineBreakIndex); } else { nextLineBreakIndex = -1; line = text; } int textWidth = getWidthFromCache(gc, line); if (wrapText) { while (textWidth > bounds.width + 1) { Matcher matcher = endOfPreviousWordPattern.matcher(line); if (matcher.find()) { nextLineBreakIndex = matcher.start() + 1; line = line.substring(0, nextLineBreakIndex); textWidth = getWidthFromCache(gc, line); } else { nextLineBreakIndex = -1; break; } } } if (textWidth > bounds.width + 1) { expensiveMethodCallCounter = 0; if (useOptimiser) { // if we reached this bit of the code, we know we are going // to have to truncate the string String nextTrialString = line; int numExtraChars = 0; int newStringLength = nextTrialString.length() - numExtraChars; String trialLabelText = nextTrialString + DOT; int newTextExtent = getWidthFromCache(gc, trialLabelText); while (newTextExtent > bounds.width + 1 && newStringLength > 0) { int avgWidthPerChar = newTextExtent / trialLabelText.length(); numExtraChars = 1 + (newTextExtent - bounds.width) / avgWidthPerChar; newStringLength = nextTrialString.length() - numExtraChars; if (newStringLength > 0) { nextTrialString = nextTrialString.substring(0, newStringLength); trialLabelText = nextTrialString + DOT; newTextExtent = getWidthFromCache(gc, trialLabelText); } } if (numExtraChars > line.length()) { numExtraChars = line.length(); } // now we have gone too short, lets add chars one at a time // to exceed the width... String testString = line; for (int i = 0; i < line.length(); i++) { testString = line.substring(0, line.length() + i - numExtraChars) + DOT; textWidth = getWidthFromCache(gc, testString); if (textWidth >= bounds.width) { // now roll back one as this was the first number // that exceeded if (line.length() + i - numExtraChars < 1) { line = EMPTY; } else { line = line.substring(0, line.length() + i - numExtraChars - 1) + DOT; } break; } } } else { // this is the expensive non-optimised codebase int textLen = line.length(); for (int i = textLen - 1; i >= 0; i--) { String temp = line.substring(0, i) + DOT; textWidth = getWidthFromCache(gc, temp); if (textWidth < bounds.width) { line = temp; break; } else if (i == 0) { line = EMPTY; } } } } output.append(line); if (nextLineBreakIndex > 0) { text = text.substring(nextLineBreakIndex).trim(); if (text.length() > 0) { output.append("\n"); } } else { break; } } return output.toString(); } }