001: /*
002: Copyright © 2007 Stefano Chizzolini. http://clown.stefanochizzolini.it
003:
004: Contributors:
005: * Stefano Chizzolini (original code developer, http://www.stefanochizzolini.it):
006: contributed code is Copyright © 2007 by Stefano Chizzolini.
007:
008: This file should be part of the source code distribution of "PDF Clown library"
009: (the Program): see the accompanying README files for more info.
010:
011: This Program is free software; you can redistribute it and/or modify it under
012: the terms of the GNU General Public License as published by the Free Software
013: Foundation; either version 2 of the License, or (at your option) any later version.
014:
015: This Program is distributed in the hope that it will be useful, but WITHOUT ANY
016: WARRANTY, either expressed or implied; without even the implied warranty of
017: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more details.
018:
019: You should have received a copy of the GNU General Public License along with this
020: Program (see README files); if not, go to the GNU website (http://www.gnu.org/).
021:
022: Redistribution and use, with or without modification, are permitted provided that such
023: redistributions retain the above copyright notice, license and disclaimer, along with
024: this list of conditions.
025: */
026:
027: package it.stefanochizzolini.clown.documents.contents.composition;
028:
029: import it.stefanochizzolini.clown.documents.contents.fonts.Font;
030:
031: import java.util.regex.Matcher;
032: import java.util.regex.Pattern;
033:
034: /**
035: Text fitter.
036:
037: @author Stefano Chizzolini
038: @version 0.0.4
039: @since 0.0.3
040: */
041: final class TextFitter {
042: // <class>
043: // <dynamic>
044: // <fields>
045: private Font font;
046: private double fontSize;
047: private boolean hyphenation;
048: private String text;
049: private double width;
050:
051: private int beginIndex = 0;
052: private int endIndex = -1;
053: private String fittedText;
054: private double fittedWidth;
055:
056: // </fields>
057:
058: // <constructors>
059: TextFitter(String text, double width, Font font, double fontSize,
060: boolean hyphenation) {
061: this .text = text;
062: this .width = width;
063: this .font = font;
064: this .fontSize = fontSize;
065: this .hyphenation = hyphenation;
066: }
067:
068: // </constructors>
069:
070: // <interface>
071: // <public>
072: /**
073: Fits the text inside the specified width.
074: @return Whether the operation was successful.
075: */
076: public boolean fit() {
077: return fit(endIndex + 1, width);
078: }
079:
080: /**
081: Fits the text inside the specified width.
082: @param index Beginning index, inclusive.
083: @param width Available width.
084: @return Whether the operation was successful.
085: @version 0.0.4
086: */
087: public boolean fit(int index, double width) {
088: beginIndex = index;
089: this .width = width;
090:
091: fittedText = null;
092: fittedWidth = 0;
093:
094: String hyphen = "";
095:
096: fitting:
097: // Fitting the text within the available width...
098: {
099: Pattern pattern = Pattern.compile("(\\s*)(\\S*)");
100: Matcher matcher = pattern.matcher(text);
101: matcher.region(beginIndex, text.length());
102: while (matcher.find()) {
103: // Scanning for the presence of a line break...
104: /*
105: NOTE: This text fitting algorithm returns everytime it finds a line break character,
106: as it's intended to evaluate the width of just a single line of text at a time.
107: */
108: for (int spaceIndex = matcher.start(1), spaceEnd = matcher
109: .end(1); spaceIndex < spaceEnd; spaceIndex++) {
110: switch (text.charAt(spaceIndex)) {
111: case '\n':
112: case '\r':
113: index = spaceIndex;
114: break fitting;
115: }
116: }
117:
118: // Get the limit of the current word!
119: int wordEndIndex = matcher.end(0);
120: // Add the current word!
121: double wordWidth = font.getKernedWidth(
122: matcher.group(0), fontSize); // Current word's width.
123: fittedWidth += wordWidth;
124: // Does the fitted text's width exceed the available width?
125: if (fittedWidth > width) {
126: // Remove the current (unfitting) word!
127: fittedWidth -= wordWidth;
128: wordEndIndex = index;
129: if (wordEndIndex == 0 // Fitted text is empty.
130: || !hyphenation) // No hyphenation.
131: break fitting;
132:
133: /*
134: NOTE: We need to hyphenate the current (unfitting) word.
135: */
136: /*
137: TODO: This hyphenation algorithm is quite primitive (to improve!).
138: */
139: hyphenating: while (true) {
140: // Add the current character!
141: char textChar = text.charAt(wordEndIndex); // Current character.
142: wordWidth = (font.getKerning(text
143: .charAt(wordEndIndex - 1), textChar) + font
144: .getWidth(textChar))
145: * font.getScalingFactor(fontSize); // Current character's width.
146: wordEndIndex++;
147: fittedWidth += wordWidth;
148: // Does fitted text's width exceed the available width?
149: if (fittedWidth > width) {
150: // Remove the current character!
151: fittedWidth -= wordWidth;
152: wordEndIndex--;
153: // Is hyphenation to be applied?
154: if (wordEndIndex > index + 4) // Long-enough word chunk.
155: {
156: // Make room for the hyphen character!
157: wordEndIndex--;
158: index = wordEndIndex;
159: textChar = text.charAt(wordEndIndex);
160: fittedWidth -= (font.getKerning(text
161: .charAt(wordEndIndex - 1),
162: textChar) + font
163: .getWidth(textChar))
164: * font
165: .getScalingFactor(fontSize);
166:
167: // Add the hyphen character!
168: textChar = '-'; // hyphen.
169: fittedWidth += (font.getKerning(text
170: .charAt(wordEndIndex - 1),
171: textChar) + font
172: .getWidth(textChar))
173: * font
174: .getScalingFactor(fontSize);
175:
176: hyphen = String.valueOf(textChar);
177: } else // No hyphenation.
178: {
179: // Removing the current word chunk...
180: while (wordEndIndex > index) {
181: wordEndIndex--;
182: textChar = text
183: .charAt(wordEndIndex);
184: fittedWidth -= (font
185: .getKerning(
186: text
187: .charAt(wordEndIndex - 1),
188: textChar) + font
189: .getWidth(textChar))
190: * font
191: .getScalingFactor(fontSize);
192: }
193: }
194: break hyphenating;
195: }
196: }
197: break fitting;
198: }
199: index = wordEndIndex;
200: }
201: }
202: fittedText = text.substring(beginIndex, index) + hyphen;
203: endIndex = index;
204:
205: return (fittedWidth > 0);
206: }
207:
208: /**
209: Gets the begin index of the fitted text inside the available text.
210: */
211: public int getBeginIndex() {
212: return beginIndex;
213: }
214:
215: /**
216: Gets the end index of the fitted text inside the available text.
217: */
218: public int getEndIndex() {
219: return endIndex;
220: }
221:
222: /**
223: Gets the fitted text.
224: */
225: public String getFittedText() {
226: return fittedText;
227: }
228:
229: /**
230: Gets the fitted text's width.
231: */
232: public double getFittedWidth() {
233: return fittedWidth;
234: }
235:
236: /**
237: Gets the font used to fit the text.
238: */
239: public Font getFont() {
240: return font;
241: }
242:
243: /**
244: Gets the size of the font used to fit the text.
245: */
246: public double getFontSize() {
247: return fontSize;
248: }
249:
250: /**
251: Gets whether the hyphenation algorithm has to be applied.
252: */
253: public boolean getHyphenation() {
254: return hyphenation;
255: }
256:
257: /**
258: Gets the available text.
259: */
260: public String getText() {
261: return text;
262: }
263:
264: /**
265: Gets the available width.
266: */
267: public double getWidth() {
268: return width;
269: }
270: // </public>
271: // </interface>
272: // </dynamic>
273: // </class>
274: }
|