[FOray-commit] SF.net SVN: foray:[12553] trunk/foray
Modular XSL-FO Implementation for Java.
Status: Alpha
Brought to you by:
victormote
|
From: <vic...@us...> - 2022-02-03 20:27:07
|
Revision: 12553
http://sourceforge.net/p/foray/code/12553
Author: victormote
Date: 2022-02-03 20:27:04 +0000 (Thu, 03 Feb 2022)
Log Message:
-----------
Move legacy line-breaking code from foray-linebreak to foray-pioneer, effectively isolating it from other projects.
Modified Paths:
--------------
trunk/foray/foray-app/src/main/java/org/foray/app/ForaySpecific.java
Added Paths:
-----------
trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/
trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/EagerLineBreaker.java
trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/LineBreaker.java
trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/SolitaryLineBreaker.java
trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/TextServer4a.java
trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/package-info.java
Removed Paths:
-------------
trunk/foray/foray-linebreak/src/main/java/org/foray/text/
Modified: trunk/foray/foray-app/src/main/java/org/foray/app/ForaySpecific.java
===================================================================
--- trunk/foray/foray-app/src/main/java/org/foray/app/ForaySpecific.java 2022-02-03 20:09:49 UTC (rev 12552)
+++ trunk/foray/foray-app/src/main/java/org/foray/app/ForaySpecific.java 2022-02-03 20:27:04 UTC (rev 12553)
@@ -155,7 +155,7 @@
* @throws ForayException For errors creating the server.
*/
public static TextServer makeTextServer() throws ForayException {
- return new org.foray.text.TextServer4a();
+ return new org.foray.pioneer.lb.TextServer4a();
}
/**
Copied: trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/EagerLineBreaker.java (from rev 12552, trunk/foray/foray-linebreak/src/main/java/org/foray/text/line/EagerLineBreaker.java)
===================================================================
--- trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/EagerLineBreaker.java (rev 0)
+++ trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/EagerLineBreaker.java 2022-02-03 20:27:04 UTC (rev 12553)
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2004 The FOray Project.
+ * http://www.foray.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This work is in part derived from the following work(s), used with the
+ * permission of the licensor:
+ * Apache FOP, licensed by the Apache Software Foundation
+ *
+ */
+
+/*
+ * $LastChangedRevision$
+ * $LastChangedDate$
+ * $LastChangedBy$
+ */
+
+package org.foray.pioneer.lb;
+
+import org.axsl.font.FontConsumer;
+import org.axsl.kpModel.ParaContext;
+import org.axsl.linebreak.OutputLine;
+import org.axsl.text.TextException;
+import org.axsl.text.line.LineBreakHandler;
+import org.axsl.text.line.LineContent;
+import org.axsl.text.line.LineNonText;
+import org.axsl.text.line.LineText;
+import org.axsl.value.group.TextModifiers;
+
+import org.slf4j.LoggerFactory;
+
+/**
+ * Abstract superclass for LineBreakers that act in an "eager" manner.
+ * Much of the usefulness of this class comes from its ability to adapt an eager line-breaking system to be used by
+ * patient clients.
+ */
+public abstract class EagerLineBreaker extends LineBreaker implements org.axsl.text.line.EagerLineBreaker {
+
+ /** The current handler for line output. */
+ private OutputLine currentOutput;
+
+ /** The current line content being processed. */
+ private LineContent currentLineContent;
+
+ /**
+ * Constructor.
+ * @param server The "parent" TextServer.
+ * @param context The client object that provides process-time information, specifically by providing line length
+ * information.
+ * @param handler The client object that is responsible for taking the results of the line-breaking work and doing
+ * something with it.
+ * @param fontConsumer Provides the line-breaking system with the FontConsumer instance that should be used for
+ * interfacing with the Font subsystem.
+ */
+ public EagerLineBreaker(final TextServer4a server, final ParaContext context, final LineBreakHandler handler,
+ final FontConsumer fontConsumer) {
+ super(server, context, handler, fontConsumer);
+ }
+
+ /**
+ * Run a piece of content through the line-breaking system.
+ * @param lineContent The line content to be processed.
+ * @param textModifiers The modification parameters to be applied to the text.
+ * @param start The staring index in the line content to be processed.
+ * @param end The ending index in the line content to be processed.
+ * @return The index in the line to the last character processed.
+ * @throws TextException For errors during line-breaking.
+ */
+ protected int processInput(final LineContent lineContent, final TextModifiers textModifiers, final int start,
+ final int end) throws TextException {
+ int status = 0;
+ if (lineContent instanceof LineText) {
+ final LineText lineText = (LineText) lineContent;
+ final CharSequence text = lineText.inlineText(textModifiers);
+ if (text.length() < 1) {
+ return -1;
+ }
+ if (lineText.inlineIsFauxSmallCaps()) {
+ /* If this is small caps, break the text up into pieces,
+ * starting a new piece when the case of the text changes. */
+ int subsetStart = start;
+ this.setInLowerCase(isLowerCase(text.charAt(start)));
+ for (int i = start; i < end; i++) {
+ final char c = text.charAt(i);
+ boolean shouldSwitch = false;
+ if (isLowerCase(c)
+ && ! isInLowerCase()) {
+ shouldSwitch = true;
+ } else if (isUpperCase(c)
+ && isInLowerCase()) {
+ shouldSwitch = true;
+ }
+ if (shouldSwitch) {
+ // Process this chunk.
+ status = processLineText(lineText, textModifiers, subsetStart, i - 1);
+ // If it can't process the whole chunk, we need to exit.
+ if (status != i) {
+ return status;
+ }
+ subsetStart = i;
+ setInLowerCase(! isInLowerCase());
+ }
+ }
+ return processLineText(lineText, textModifiers, subsetStart, end);
+ }
+ this.setInLowerCase(false);
+ status = processLineText(lineText, textModifiers, start, end);
+ } else if (lineContent instanceof LineNonText) {
+ status = processLineNonText((LineNonText) lineContent);
+ } else {
+ throw new TextException("Invalid LineContent.");
+ }
+ return status;
+ }
+
+ /**
+ * Standard processing of non-text items for eager line breaking systems.
+ * @param nonTextItem The non-text item that should be added to the current
+ * line.
+ * @return If the item was successfully added to the current line, returns
+ * 1, or returns 0 if there is not enough room on the line for it.
+ * @throws TextException For errors during processing.
+ */
+ protected int processLineNonText(final LineNonText nonTextItem)
+ throws TextException {
+ final int lineLength = lineReceivingContent().capacityTotal();
+ final int itemSize = nonTextItem.inlineSizeMinimum(lineLength);
+ if (itemSize > lineReceivingContent().capacityRemaining()) {
+ // It doesn't fit on the current line.
+ if (lineReceivingContent().capacityUsed() == 0) {
+ // There is nothing on the line. Therefore it won't fit any
+ // better on the next line. Put it on this one.
+ getHandler().handleLineBreakNonText(lineReceivingContent(),
+ nonTextItem, itemSize);
+ LoggerFactory.getLogger(this.getClass()).error("Content too large for any line.");
+ return 1;
+ }
+ // It should fit better on the next line. Try that.
+ return 0;
+ }
+ // It fits on the line. Add it.
+ getHandler().handleLineBreakNonText(lineReceivingContent(), nonTextItem,
+ itemSize);
+ return 1;
+ }
+
+ /**
+ * Processes a line-text item.
+ * @param lineText The line-text item to be processed.
+ * @param textModifiers The modification parameters to be applied to the text.
+ * @param start The index to the first character in the line-text item that should be processed.
+ * @param end The index to the last character in the line-text item that should be processed.
+ * @return The index to the last character in the line-text item that was actually processed.
+ * @throws TextException For errors during line-breaking.
+ */
+ protected abstract int processLineText(LineText lineText, TextModifiers textModifiers, int start, int end)
+ throws TextException;
+
+ /**
+ * Add new line-content for processing.
+ * @param content The line-content to be processed.
+ * @param textModifiers The modification parameters to be applied to the text.
+ * @param start The index to the first element in the line-content to be processed.
+ * @param end The index to the last element in the line-content to be processed.
+ * @param output The line-output onto which the text should be placed.
+ * @return The index to the last element in the line-content that was actually processed.
+ * @throws TextException For errors during text processing.
+ */
+ public int addLineContent(final LineContent content, final TextModifiers textModifiers, final int start,
+ final int end, final OutputLine output) throws TextException {
+ setCurrentLine(output);
+ return processInput(content, textModifiers, start, end);
+ }
+
+ /**
+ * Return the current line-output.
+ * @return The current line-output.
+ */
+ protected OutputLine lineReceivingContent() {
+ return this.getCurrentLine();
+ }
+
+ @Override
+ public int processLineContent(final LineContent contentItem, final TextModifiers textModifiers,
+ final int start, final int end, final OutputLine output)
+ throws TextException {
+ validateEagerContent(contentItem, output);
+ this.currentOutput = output;
+ this.currentLineContent = contentItem;
+ return addLineContent(contentItem, textModifiers, start, end, output);
+ }
+
+ /**
+ * Validates the parameters to be laid out.
+ * @param contentItem The content to be laid out.
+ * @param output The output on which the content will be laid out.
+ * @throws TextException If either the input or output are null.
+ */
+ private void validateEagerContent(final LineContent contentItem,
+ final OutputLine output) throws TextException {
+ if (contentItem == null) {
+ throw new TextException("Null LineContent.");
+ }
+ if (output == null) {
+ throw new TextException("Null LineOutput.");
+ }
+ }
+
+ /**
+ * Sets the current line-output item.
+ * @param line The new line-output item.
+ */
+ public void setCurrentLine(final OutputLine line) {
+ this.currentOutput = line;
+ }
+
+ /**
+ * Returns the current line-output item.
+ * @return The current line-output item.
+ */
+ public OutputLine getCurrentLine() {
+ return this.currentOutput;
+ }
+
+ /**
+ * Returns the current line content.
+ * @return The current line content.
+ */
+ public LineContent getLineContent() {
+ return this.currentLineContent;
+ }
+
+ /**
+ * Writes a standard overflow message to the logger.
+ */
+ protected void overflowMessage() {
+ LoggerFactory.getLogger(this.getClass()).info("Content overflows line " + this.lineReceivingContent());
+ }
+
+}
Copied: trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/LineBreaker.java (from rev 12552, trunk/foray/foray-linebreak/src/main/java/org/foray/text/line/LineBreaker.java)
===================================================================
--- trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/LineBreaker.java (rev 0)
+++ trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/LineBreaker.java 2022-02-03 20:27:04 UTC (rev 12553)
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2004 The FOray Project.
+ * http://www.foray.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This work is in part derived from the following work(s), used with the
+ * permission of the licensor:
+ * Apache FOP, licensed by the Apache Software Foundation
+ *
+ */
+
+/*
+ * $LastChangedRevision$
+ * $LastChangedDate$
+ * $LastChangedBy$
+ */
+
+package org.foray.pioneer.lb;
+
+import org.axsl.font.FontConsumer;
+import org.axsl.font.FontUse;
+import org.axsl.kpModel.ParaContext;
+import org.axsl.text.line.LineBreakHandler;
+import org.axsl.text.line.LineText;
+
+/**
+ * Abstract superclass for all line breakers.
+ */
+public abstract class LineBreaker {
+
+ /** The "parent" TextServer. */
+ private TextServer4a server;
+
+ /** The client object that provides process-time information, specifically
+ * by providing line length information. */
+ private ParaContext context;
+
+ /** Provides the line-breaking system with the FontConsumer instance that
+ * should be used for interfacing with the Font subsystem. */
+ private FontConsumer fontConsumer;
+
+ /** The client object that is responsible for taking the results of the
+ * line-breaking work and doing something with it. */
+ private LineBreakHandler handler;
+
+ /** Keeps track of whether the current chunk of text is lower-case for
+ * purposes of faux small-caps or not. */
+ private boolean inLowerCase = false;
+
+ /**
+ * Constructor.
+ * @param server The "parent" TextServer.
+ * @param context The client object that provides process-time information,
+ * specifically by providing line length information.
+ * @param handler The client object that is responsible for taking the
+ * results of the line-breaking work and doing something with it.
+ * @param fontConsumer Provides the line-breaking system with the
+ * FontConsumer instance that should be used for interfacing with the Font
+ * subsystem.
+ */
+ protected LineBreaker(final TextServer4a server, final ParaContext context, final LineBreakHandler handler,
+ final FontConsumer fontConsumer) {
+ this.server = server;
+ this.context = context;
+ this.handler = handler;
+ this.fontConsumer = fontConsumer;
+ }
+
+ /**
+ * Returns the width of a character.
+ * @param lineText The object containing information about font and font
+ * size needed to get the width.
+ * @param codePoint The Unicode code point whose width is needed.
+ * @return The width of {@code codePoint}, in millipoints.
+ */
+ public int getCharWidth(final LineText lineText, final int codePoint) {
+ final FontUse fontUse = lineText.inlinePrimaryFont();
+ int fontSize = lineText.inlineFontSize();
+ int codePointToUse = codePoint;
+ if (lineText.inlineIsFauxSmallCaps()
+ && isLowerCase(codePoint)) {
+ fontSize = lineText.inlineFauxSmallCapsFontSize();
+ codePointToUse = java.lang.Character.toUpperCase((char) codePoint);
+ }
+ fontUse.registerCharUsed(codePointToUse);
+ return fontUse.width(codePointToUse, fontSize) + lineText.inlineLetterSpacingOptimum();
+ }
+
+ /**
+ * Indicates whether a given Unicode code point should be considered to be
+ * lower case.
+ * @param codePoint The Unicode code point to be tested.
+ * @return True if and only if {@code codePoint} is lower case.
+ */
+ public boolean isLowerCase(final int codePoint) {
+ if (java.lang.Character.isLowerCase(codePoint)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether a given Unicode code point should be considered to be
+ * upper case.
+ * @param codePoint The Unicode code point to be tested.
+ * @return True if and only if {@code codePoint} is upper case.
+ */
+ public boolean isUpperCase(final int codePoint) {
+ if (java.lang.Character.isUpperCase(codePoint)) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the width of the hyphenation character.
+ * @param lineText The object containing information about font, font size,
+ * and hyphenation character needed to get the width.
+ * @return The width of the hyphenation character, in millipoints.
+ */
+ public int getHyphenWidth(final LineText lineText) {
+ final int hyphenChar = lineText.inlineHyphenationCharacter();
+ return getCharWidth(lineText, hyphenChar);
+ }
+
+ /**
+ * Helper method to determine if the character is a space with normal
+ * behavior. Normal behaviour means that it's not non-breaking, that is,
+ * that we are permitted to use it as a line-breaking opportunity.
+ * @param codePoint The Unicode code point to be tested.
+ * @return True if and only if {@code codePoint} is a normal space.
+ */
+ public static boolean isSpace(final int codePoint) {
+ if (codePoint == ' '
+ || codePoint == '\u2000' // en quad
+ || codePoint == '\u2001' // em quad
+ || codePoint == '\u2002' // en space
+ || codePoint == '\u2003' // em space
+ || codePoint == '\u2004' // three-per-em space
+ || codePoint == '\u2005' // four--per-em space
+ || codePoint == '\u2006' // six-per-em space
+ || codePoint == '\u2007' // figure space
+ || codePoint == '\u2008' // punctuation space
+ || codePoint == '\u2009' // thin space
+ || codePoint == '\u200A' // hair space
+ || codePoint == '\u200B') { // zero width space
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determine if a given character is a non-breaking space.
+ * @param codePoint The Unicode code point to be tested.
+ * @return True if and only if {@code codePoint} is a non-breaking space.
+ */
+ public static boolean isNonBreakingSpace(final int codePoint) {
+ if (codePoint == '\u00A0'
+ || codePoint == '\u202F' // narrow no-break space
+ || codePoint == '\u3000' // ideographic space
+ || codePoint == '\uFEFF') { // zero width no-break space
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether a character forces a line break.
+ * @param codePoint The character to be tested.
+ * @return True if and only if this character forces a line break.
+ */
+ public static boolean forcesLineBreak(final int codePoint) {
+ if (codePoint == '\u2028' // Unicode line separator
+ || codePoint == '\n') { // linefeed
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether a character allows (but does not force) a line break.
+ * @param codePoint The character to be tested.
+ * @return True if and only if this character allows (but does not force) a line break.
+ */
+ public static boolean allowsLineBreak(final int codePoint) {
+ if (isWhitespace(codePoint)) {
+ return true;
+ }
+ if (codePoint == '-' // Regular hyphen
+ || codePoint == '\u00ad') { // Unicode soft hyphen
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether a given character should be considered whitespace.
+ * @param codePoint The Unicode code point to be tested.
+ * @return True if and only if {@code codePoint} is whitespace.
+ */
+ public static boolean isWhitespace(final int codePoint) {
+ if (isSpace(codePoint)
+ || (codePoint == '\n')
+ || (codePoint == '\r')
+ || (codePoint == '\t')
+ || (codePoint == '\u2028')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether a given character is a zero-width space.
+ * @param codePoint The Unicode code point to be tested.
+ * @return True if and only if {@code codePoint} is a zero-width space.
+ */
+ public static boolean isZeroWidthSpace(final int codePoint) {
+ if ((codePoint == '\u200B')
+ || codePoint == '\uFEFF') {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Computes the width of a given character.
+ * @param lineText The object which knows the font and font size information
+ * for the text tested.
+ * @param codePoint The Unicode code point whose width is needed.
+ * @param whitespaceWidth The pre-computed whitespace width for this font.
+ * @return The computed width of the given code point.
+ */
+ protected int charWidth(final LineText lineText, final int codePoint,
+ final int whitespaceWidth) {
+ if (isZeroWidthSpace(codePoint)) {
+ return getCharWidth(lineText, codePoint);
+ }
+ int charWidth = 0;
+ if (isWhitespace(codePoint)) {
+ if ((codePoint == '\n')
+ || (codePoint == '\r')
+ || (codePoint == '\t')) {
+ charWidth = whitespaceWidth;
+ } else {
+ charWidth = getCharWidth(lineText, codePoint);
+ }
+ return charWidth;
+ }
+ charWidth = getCharWidth(lineText, codePoint);
+ if (charWidth <= 0) {
+ charWidth = whitespaceWidth;
+ }
+ return charWidth;
+ }
+
+ /**
+ * Returns the font consumer.
+ * @return The font consumer.
+ */
+ protected FontConsumer getFontConsumer() {
+ return this.fontConsumer;
+ }
+
+ /**
+ * Indicates the validity of breaking a word in the middle without
+ * hyphenation, based on a given language. The CJKV languages allow this,
+ * since the ideographs are themselves distinct words or word-like concepts.
+ * @param language The language code to be tested.
+ * @return True if and only if it is legal to break a word in the middle
+ */
+ public static boolean canBreakMidWord(final String language) {
+ if (language == null) {
+ return false;
+ }
+ final String lang = language.toLowerCase();
+ if (lang.equals("zh")) { // Chinese??
+ return true;
+ }
+ if (lang.equals("ja")) { // Japanese??
+ return true;
+ }
+ if (lang.equals("ko")) { // Korean??
+ return true;
+ }
+ if (lang.equals("vi")) { // Vietnamese??
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the text server.
+ * @return The text server.
+ */
+ public TextServer4a getTextServer() {
+ return this.server;
+ }
+
+ /**
+ * Compute the width of a given word.
+ * @param lineText The object providing font and font size information.
+ * @param word The word whose size is needed.
+ * @param offset The zero-based index into {@code chars} that is the start of what should be computed.
+ * @param length The number of chars in {@code chars} that should be computed.
+ * @return The width, in millipoints, of {@code word}.
+ */
+ public int getWordWidth(final LineText lineText, final CharSequence word, final int offset, final int length) {
+ final FontUse fontUse = lineText.inlinePrimaryFont();
+ fontUse.registerCharsUsed(word);
+ return fontUse.width(word, offset, length, lineText.inlineFontSize(), lineText.inlineLetterSpacingOptimum(), 0,
+ lineText.inlineFontContext(), lineText.inlineOrthography());
+ }
+
+ /**
+ * Indicates whether the current chunk of text is lower-case for purposes of
+ * faux small-caps or not.
+ * @return The "in-lower-case" value.
+ */
+ public boolean isInLowerCase() {
+ return this.inLowerCase;
+ }
+
+ /**
+ * Sets the "in-lower-case" value.
+ * @param inLowerCase Indicates whether the current chunk of text is
+ * lower-case for purposes of faux small-caps or not.
+ */
+ public void setInLowerCase(final boolean inLowerCase) {
+ this.inLowerCase = inLowerCase;
+ }
+
+ /**
+ * Returns the handler.
+ * @return The handler.
+ */
+ public LineBreakHandler getHandler() {
+ return this.handler;
+ }
+
+ /**
+ * Return the LineBreakControl instance.
+ * @return The line-break control.
+ */
+ public ParaContext getLineBreakControl() {
+ return this.context;
+ }
+
+}
Copied: trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/SolitaryLineBreaker.java (from rev 12539, trunk/foray/foray-linebreak/src/main/java/org/foray/text/line/solitary/SolitaryLineBreaker.java)
===================================================================
--- trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/SolitaryLineBreaker.java (rev 0)
+++ trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/SolitaryLineBreaker.java 2022-02-03 20:27:04 UTC (rev 12553)
@@ -0,0 +1,580 @@
+/*
+ * Copyright 2004 The FOray Project.
+ * http://www.foray.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This work is in part derived from the following work(s), used with the
+ * permission of the licensor:
+ * Apache FOP, licensed by the Apache Software Foundation
+ *
+ */
+
+/*
+ * $LastChangedRevision$
+ * $LastChangedDate$
+ * $LastChangedBy$
+ */
+
+package org.foray.pioneer.lb;
+
+import org.axsl.font.FontConsumer;
+import org.axsl.kpModel.ParaContext;
+import org.axsl.orthography.Orthography;
+import org.axsl.orthography.Word;
+import org.axsl.text.TextException;
+import org.axsl.text.line.LineBreakHandler;
+import org.axsl.text.line.LineText;
+import org.axsl.unicode.block.General_Punctuation_Block;
+import org.axsl.value.group.TextModifiers;
+
+/**
+ * An "eager" line-breaking implementaton which considers only the current line in the line-breaking computation (hence
+ * the name "solitary").
+ * It look neither forward nor backward in its computation, but merely attempts to find the best break that it can for
+ * the current line.
+ */
+public class SolitaryLineBreaker extends EagerLineBreaker {
+
+ /** Possible value for {@link #previousCharacter}, indicating that there
+ * was no previous character. */
+ protected static final byte NOTHING = 0;
+
+ /** Possible value for {@link #previousCharacter}, indicating that the
+ * previous character was an interword connectors, like whitespace or a
+ * hyphen. */
+ protected static final byte CONNECTOR = 1;
+
+ /** Possible value for {@link #previousCharacter}, indicating that the
+ * previous character was text within a "word". */
+ protected static final byte TEXT = 2;
+
+ /** The width, in millipoints, of the content that has definitely made it
+ * onto the line. */
+ private int finalWidth = 0;
+
+ /** One of {@link #NOTHING}, {@link #CONNECTOR}, or {@link #TEXT},
+ * indicating the class of the previous character. */
+ private byte previousCharacter = SolitaryLineBreaker.NOTHING;
+
+ /** The accumulated width, in millipoints, of whitespace before the current
+ * word. */
+ private int spaceWidth = 0;
+
+ /** The width, in millipoints, of the current word so far. */
+ private int wordWidth = 0;
+
+ /** Index into the char array indicating the first character in this
+ * word. */
+ private int wordStart = 0;
+
+ /** The current line-text item being processed. */
+ private LineText currentLineText = null;
+
+ /** The current characters from {@link #currentLineText}, available here
+ * merely as a convenience. */
+ private CharSequence currentChars = null;
+
+ /** Indicates whether leading spaces can be ignored. */
+ private boolean canEatLeadingSpaces = true;
+
+ /**
+ * Constructor.
+ * @param server The "parent" TextServer.
+ * @param context The client object that provides process-time information, specifically by providing line length
+ * information.
+ * @param handler The client object that is responsible for taking the results of the line-breaking work and doing
+ * something with it.
+ * @param fontConsumer Provides the line-breaking system with the FontConsumer instance that should be used for
+ * interfacing with the Font subsystem.
+ */
+ public SolitaryLineBreaker(final TextServer4a server, final ParaContext context, final LineBreakHandler handler,
+ final FontConsumer fontConsumer) {
+ super(server, context, handler, fontConsumer);
+ }
+
+ @Override
+ protected int processLineText(final LineText lineText, final TextModifiers textModifiers, final int start,
+ final int end) throws TextException {
+ this.currentLineText = lineText;
+ this.currentChars = this.currentLineText.inlineText(textModifiers);
+// final WritingSystem orthography = lineText.inlineWritingSystem();
+// final String language = orthography.getLanguage().getAlpha3Code();
+
+ this.finalWidth = 0;
+ this.wordWidth = 0;
+ this.wordStart = 0;
+ this.spaceWidth = 0;
+ if (getCurrentLine().hasAnyContent()) {
+ this.canEatLeadingSpaces = false;
+ } else {
+ this.canEatLeadingSpaces = true;
+ }
+ int whitespaceWidth = getCharWidth(lineText, ' ');
+ whitespaceWidth += lineText.inlineWordSpacingOptimum();
+
+ // Bound start and end by the limits of the array.
+ int startIndex = start;
+ int endIndex = end;
+ if (start < 0) {
+ startIndex = 0;
+ }
+ if (end > this.currentChars.length() - 1) {
+ endIndex = this.currentChars.length() - 1;
+ }
+
+ /* iterate over each character */
+ for (int i = startIndex; i <= endIndex; i++) {
+ /* get the character */
+ final char c = this.currentChars.charAt(i);
+ final int thisCharStarts = i;
+ final int codePoint = Character.codePointAt(this.currentChars,
+ i);
+// int charCount = 1;
+ if (Character.isHighSurrogate(c)) {
+ i++;
+// charCount++;
+ }
+ if (forcesLineBreak(codePoint)) {
+ createLineContent(textModifiers, start, i, this.finalWidth, false);
+ return i + 1;
+ }
+ if (allowsLineBreak(codePoint)) {
+ processLineBreakPossibility(lineText, codePoint);
+ continue;
+ }
+
+ // If it got this far, it is TEXT.
+ this.canEatLeadingSpaces = false;
+ final int charWidth = charWidth(lineText, codePoint,
+ whitespaceWidth);
+ processTextChar(lineText.inlineOrthography(), thisCharStarts, charWidth);
+
+ if ((this.finalWidth + this.spaceWidth + this.wordWidth)
+ <= lineReceivingContent().capacityRemaining()) {
+ // Content fits on the current line. We are done with this char.
+ continue;
+ }
+
+ // Content does not fit on current line.
+ if (! lineText.inlineWrapOption()) {
+ continue;
+ }
+ if (lineText.inlineHyphenate()) {
+ final int ret = tryHyphenation();
+ if (ret != this.wordStart) {
+ this.finalWidth += this.spaceWidth;
+ this.finalWidth += this.wordWidth;
+ this.finalWidth += getHyphenWidth(lineText);
+ createLineContent(textModifiers, start, ret - 1, this.finalWidth, true);
+ return ret;
+ }
+ }
+ if (this.wordStart == start
+ && ! getCurrentLine().hasAnyContent()) {
+ /* This is the first word on this line. It doesn't fit on this
+ * line, which means that it won't fit on any line, unless
+ * subsequent pages are wider. We will break the line anyway. */
+ /* TODO: Review this policy. */
+ overflowMessage();
+ this.finalWidth += this.spaceWidth;
+ this.finalWidth += this.wordWidth;
+ this.finalWidth -= charWidth;
+ createLineContent(textModifiers, start, i - 1, this.finalWidth, false);
+ /* Return the index at the start of this character. */
+ return i - 1;
+ }
+ // Break the line at the end of the previous word.
+ createLineContent(textModifiers, start, this.wordStart - 1, this.finalWidth, false);
+ return this.wordStart;
+ }
+ /* We have now read through each character. */
+ /* If this ends with white space, place remaining contents on this
+ * line. */
+ if (this.previousCharacter == SolitaryLineBreaker.CONNECTOR) {
+ return remainingContentOnThisLine(textModifiers, startIndex, endIndex);
+ }
+ /* Otherwise the word may continue into the next text. See if the
+ * entire word fits. */
+ if (this.finalWidth + this.spaceWidth + this.wordWidth
+ + sizeFirstWordNextText(lineText, textModifiers, startIndex, endIndex)
+ <= lineReceivingContent().capacityRemaining()) {
+ return remainingContentOnThisLine(textModifiers, startIndex, endIndex);
+ }
+
+ /* TODO: If the entire words doesn't fit, see if it can be
+ * hyphenated. Need to add code here to handle this.*/
+
+ /* If nothing else works, the content doesn't fit on this line.
+ * Break the line at the end of the previous word. */
+ createLineContent(textModifiers, start, this.wordStart - 1, this.finalWidth, false);
+ return this.wordStart;
+ }
+
+ /**
+ * Returns the status of layout.
+ * @param textModifiers The modification parameters to be applied to the text.
+ * @param startIndex The index of the first character eligible for layout.
+ * @param endIndex The index of the last character eligible for layout.
+ * @return The index through which layout has been completed. In this case,
+ * it is one past endIndex, indicating that all has been laid out.
+ * @throws TextException For error layout out input text.
+ */
+ private int remainingContentOnThisLine(final TextModifiers textModifiers, final int startIndex,
+ final int endIndex) throws TextException {
+ this.finalWidth += this.spaceWidth;
+ this.finalWidth += this.wordWidth;
+ createLineContent(textModifiers, startIndex, endIndex, this.finalWidth, false);
+ return endIndex + 1;
+ }
+
+ /**
+ * Returns the size of the first word in the next text item.
+ * @param lineText The line-text item being processed.
+ * @param textModifiers The modification parameters to be applied to the text.
+ * @param start The index of the first character eligible for layout.
+ * @param end The index of the last character eligible for layout.
+ * @return The index through which layout was actually completed.
+ */
+ private int sizeFirstWordNextText(final LineText lineText, final TextModifiers textModifiers, final int start,
+ final int end) {
+ int size = 0;
+ LineText nextText = lineText;
+ int whitespaceWidth = getCharWidth(nextText, ' ');
+ /* First see if there is additional text in the current item. Remember
+ * that faux small-caps breaks up the text item. */
+ CharSequence text = nextText.inlineText(textModifiers);
+ for (int i = end + 1; i < text.length(); i++) {
+ final char c = text.charAt(i);
+ if (forcesLineBreak(c) || allowsLineBreak(c)) {
+ return size;
+ }
+ size += charWidth(nextText, c, whitespaceWidth);
+ }
+ while (true) {
+ nextText = nextText.nextContiguousLineText();
+ if (nextText == null) {
+ return size;
+ }
+ text = nextText.inlineText(textModifiers);
+ whitespaceWidth = getCharWidth(nextText, ' ');
+ for (int i = 0; i < text.length(); i++) {
+ final char c = text.charAt(i);
+ if (forcesLineBreak(c) || allowsLineBreak(c)) {
+ return size;
+ }
+ size += charWidth(nextText, c, whitespaceWidth);
+ }
+ }
+ }
+
+ /**
+ * Processes one character.
+ * @param orthography The orthography.
+ * @param i Index to the character.
+ * @param charWidth The width, in millipoints, of the character.
+ */
+ private void processTextChar(final Orthography orthography, final int i, final int charWidth) {
+ if (this.previousCharacter == SolitaryLineBreaker.CONNECTOR) {
+ // Current is TEXT, previous is WHITESPACE.
+ this.wordWidth = charWidth;
+ this.wordStart = i;
+ } else if (this.previousCharacter == SolitaryLineBreaker.TEXT) {
+ if (orthography.canBreakLineMidWord()) {
+ this.finalWidth += this.spaceWidth;
+ this.spaceWidth = 0;
+ // add the current word
+ if (i > this.wordStart) {
+ this.finalWidth += this.wordWidth;
+ }
+ this.spaceWidth = 0;
+ this.wordStart = i;
+ this.wordWidth = charWidth;
+ } else {
+ this.wordWidth += charWidth;
+ }
+ } else { // nothing previous
+ this.wordStart = i;
+ this.wordWidth = charWidth;
+ }
+ this.previousCharacter = SolitaryLineBreaker.TEXT;
+ }
+
+ /**
+ * For characters that allows (but do not demand) a line break, reset the
+ * word-related variables to that everything up to this point is included
+ * on the current line.
+ * @param lineText The line-text item being processed.
+ * @param codePoint The current Unicode code point being processed.
+ */
+ private void processLineBreakPossibility(final LineText lineText,
+ final int codePoint) {
+ if (this.previousCharacter == SolitaryLineBreaker.TEXT) {
+ // Current is WHITESPACE and previous TEXT.
+ /*
+ * The current word made it, so add any accumulated space
+ * before the current word.
+ */
+ this.finalWidth += this.spaceWidth;
+ // Reset space width.
+ this.spaceWidth = 0;
+ // Add the current word.
+ this.finalWidth += this.wordWidth;
+ // Reset word width.
+ this.wordWidth = 0;
+ }
+ if (! this.canEatLeadingSpaces) {
+ this.spaceWidth += getCharWidth(lineText, codePoint);
+ this.spaceWidth += lineText.inlineWordSpacingOptimum();
+ }
+ this.previousCharacter = SolitaryLineBreaker.CONNECTOR;
+ }
+
+ /**
+ * Outputs all or part of the current text component to the text handler.
+ * @param textModifiers The modification parameters to be applied to the text.
+ * @param startIndex The index to the first character that should be
+ * considered part of this line.
+ * @param endIndex The index to the last character that should be
+ * considered part of this line.
+ * @param sizeInline The size, in millipoints, of the content being created.
+ * @param isHyphenated Indicates whether an end-of-line hyphen should be
+ * added to this content.
+ * @throws TextException For errors disposing of the line-breaking
+ * results.
+ */
+ private void createLineContent(final TextModifiers textModifiers, final int startIndex, final int endIndex,
+ final int sizeInline, final boolean isHyphenated)
+ throws TextException {
+ final LineText lineText = (LineText) this.getLineContent();
+ final boolean everythingWritten = endIndex >=
+ lineText.inlineText(textModifiers).length();
+ boolean isLastItemOnLine = true;
+ if (everythingWritten) {
+ /* If everything was written, this may not be the last item on
+ * the line, unless this is the last item in the block. */
+ if (! lineText.isLastItemInBlock()) {
+ isLastItemOnLine = false;
+ }
+ }
+ final boolean isFauxSmallCaps = this.isInLowerCase();
+ getHandler().handleLineBreakText(getCurrentLine(), lineText,
+ startIndex, endIndex - startIndex + 1, sizeInline, isHyphenated,
+ isFauxSmallCaps, isLastItemOnLine);
+ }
+
+ /**
+ * Extracts a word for hyphenation and calls the hyphenation package.
+ * Handles quotation marks at the beginning of words, but not in an
+ * internationalized way.
+ * @return The index to the last element in the current line that has been
+ * processed.
+ * @throws TextException For errors during the hyphenation.
+ */
+ public int tryHyphenation() throws TextException {
+ /*
+ * TODO: This is not quite right yet. If the current word starts at
+ * the beginning of the text being considered, we need to look
+ * backward at any text in the same LineText item, and then to prior
+ * LineText items to find the beginning of the word.
+ */
+
+ final Orthography orthographyConfig = this.currentLineText.inlineOrthography();
+
+ // Count the number of chars at the beginning that should be ignored.
+ final int actualWordStart = wordStarts(this.currentChars, this.wordStart);
+ if (actualWordStart < 0) {
+ return this.wordStart;
+ }
+ final int nonWordChars = actualWordStart - this.wordStart;
+ // Extract the word that should be evaluated by the hyphenation system.
+ final int wordSize = wordSize(this.currentChars, actualWordStart);
+ // See if there are discretionary hyphenation points.
+ Word hyph = orthographyConfig.recognizeWord(this.currentChars, actualWordStart, wordSize, null, null);
+ if (hyph == null) {
+ hyph = orthographyConfig.hyphenateUnrecognizedWord(this.currentChars, actualWordStart, wordSize);
+ }
+ // If none, the word cannot be hyphenated.
+ if (hyph == null) {
+ return this.wordStart;
+ }
+ // Select a hyphenation point.
+ final int index = selectDiscretionaryHyphenationPoint(this.currentLineText, hyph);
+ // If none fit, then the word cannot be hyphenated.
+ if (index < 0) {
+ return this.wordStart;
+ }
+ // Compute the number of characters that should be included.
+ final int charsToInclude = hyph.hyphenationPointOffsetAt(index);
+ // Add it and the non-word characters to the count to be returned.
+ return this.wordStart + nonWordChars + charsToInclude;
+ }
+
+ /**
+ * Extracts from a hyphenated word the best (most greedy) fit.
+ * @param lineText The line-text item containing font and font size
+ * information.
+ * @param hyph The hyphenation object containing the list of hyphenation
+ * opportunities.
+ * @return The index to the selected hyphenation opportunity, or -1 if no
+ * opportunity was selected.
+ * @throws TextException For errors during break selection.
+ */
+ private int selectDiscretionaryHyphenationPoint(final LineText lineText, final Word hyph) throws TextException {
+ final int remainingWidth
+ = this.lineReceivingContent().capacityRemaining()
+ - this.finalWidth
+ - this.spaceWidth - getHyphenWidth(this.currentLineText);
+
+ int index = -1;
+
+ for (int i = 0; i < hyph.qtyHyphenationPoints(); i++) {
+ final int provisionalWordWidth =
+ getWordWidth(lineText, hyph, 0, hyph.hyphenationPointOffsetAt(i));
+ if (provisionalWordWidth > remainingWidth) {
+ break;
+ }
+ index = i;
+ this.wordWidth = provisionalWordWidth;
+ }
+ return index;
+ }
+
+ /**
+ * Finds the size of a word in the text being evaluated.
+ * @param characters The input being evaluated.
+ * @param wordStart Index into the first character in characters that should be evaluated.
+ * Characters at indexes before this will not be considered.
+ * Passing null is permitted.
+ * Implementations are not required to do anything with this information.
+ * @return The size of the word.
+ */
+ public int wordSize(final CharSequence characters, final int wordStart) {
+ if (characters == null) {
+ return 0;
+ }
+ int counter = 0;
+ while ((wordStart + counter) < characters.length()) {
+ final int charIndex = wordStart + counter;
+ final char c = characters.charAt(charIndex);
+ if (isWordChar(c)) {
+ counter ++;
+ } else if (isSometimesWordChar(c)
+ && characters.length() >= counter
+ && isWordChar(characters.charAt(charIndex + 1))) {
+ counter ++;
+ } else {
+ break;
+ }
+ }
+ return counter;
+ }
+
+ /**
+ * Finds the start of a word in the text being evaluated.
+ * Characters like " and ' which are not really part of the word, but which must be passed through to the output,
+ * should be skipped.
+ * @param characters The input being evaluated.
+ * @param startIndex Index into the first character in characters that should be evaluated.
+ * Characters at indexes before this will not be considered.
+ * Passing null is permitted.
+ * Implementations are not required to do anything with this information.
+ * @return The index to the start of the first word found, or -1 if no word was found.
+ */
+ public int wordStarts(final CharSequence characters, final int startIndex) {
+ if (characters == null) {
+ return -1;
+ }
+ for (int i = startIndex; i < characters.length(); i++) {
+ final char c = characters.charAt(i);
+ if (isWordChar(c)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Indicates whether a given character is a word character.
+ * @param codePoint The character to be tested.
+ * @return True if and only if {@code} should be considered part of a word.
+ */
+ private boolean isWordChar(final int codePoint) {
+ /* TODO: This may need to be moved to Orthography/Language/Country/Script. Leave it here for now, as the
+ * Unicode types may be sufficient. */
+ final int type = Character.getType(codePoint);
+
+ switch (type) {
+ /* Ordered by expected frequency of use, for performance. */
+ case Character.LOWERCASE_LETTER:
+ case Character.UPPERCASE_LETTER:
+ case Character.TITLECASE_LETTER:
+ case Character.MODIFIER_LETTER:
+ case Character.OTHER_LETTER:
+ case Character.DECIMAL_DIGIT_NUMBER:
+ case Character.CURRENCY_SYMBOL:
+ case Character.MATH_SYMBOL:
+ case Character.LETTER_NUMBER:
+ case Character.OTHER_NUMBER:
+ case Character.OTHER_SYMBOL: {
+ return true;
+ }
+ /* Comment out the "false" values, for performance. */
+// case Character.COMBINING_SPACING_MARK:
+// case Character.CONNECTOR_PUNCTUATION:
+// case Character.CONTROL:
+// case Character.DASH_PUNCTUATION:
+// case Character.ENCLOSING_MARK:
+// case Character.END_PUNCTUATION:
+// case Character.FINAL_QUOTE_PUNCTUATION:
+// case Character.FORMAT:
+// case Character.INITIAL_QUOTE_PUNCTUATION:
+// case Character.LINE_SEPARATOR:
+// case Character.MODIFIER_SYMBOL:
+// case Character.NON_SPACING_MARK:
+// case Character.OTHER_PUNCTUATION:
+// case Character.PARAGRAPH_SEPARATOR:
+// case Character.PRIVATE_USE:
+// case Character.SPACE_SEPARATOR:
+// case Character.START_PUNCTUATION:
+// case Character.SURROGATE:
+// case Character.UNASSIGNED: {
+// return false;
+// }
+ default: {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Indicates whether a char is a possible word character, depending on context.
+ * For example, a single quote or typographic apostrophe could be the end of a quotation (not a word character,
+ * or mark a contraction or possession (is a word character).
+ * @param c The character to be tested.
+ * @return True if and only if {@code c} is sometimes (but not always) a word character.
+ */
+ private boolean isSometimesWordChar(final char c) {
+ switch (c) {
+ /* Typographic apostrophe. */
+ case General_Punctuation_Block.RIGHT_SINGLE_QUOTATION_MARK:
+ case '\'': {
+ return true;
+ }
+ default: {
+ return false;
+ }
+ }
+ }
+
+}
Copied: trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/TextServer4a.java (from rev 12552, trunk/foray/foray-linebreak/src/main/java/org/foray/text/TextServer4a.java)
===================================================================
--- trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/TextServer4a.java (rev 0)
+++ trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/TextServer4a.java 2022-02-03 20:27:04 UTC (rev 12553)
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2004 The FOray Project.
+ * http://www.foray.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This work is in part derived from the following work(s), used with the
+ * permission of the licensor:
+ * Apache FOP, licensed by the Apache Software Foundation
+ *
+ */
+
+/*
+ * $LastChangedRevision$
+ * $LastChangedDate$
+ * $LastChangedBy$
+ */
+
+package org.foray.pioneer.lb;
+
+import org.axsl.font.FontConsumer;
+import org.axsl.kpModel.ParaContext;
+import org.axsl.text.line.EagerLineBreaker;
+import org.axsl.text.line.LineBreakHandler;
+
+/**
+ * This class encapsulates the various text processing functions.
+ */
+public class TextServer4a implements org.axsl.text.TextServer {
+
+ @Override
+ public EagerLineBreaker provideEagerLineBreaker(final ParaContext context, final LineBreakHandler handler,
+ final FontConsumer fontConsumer) {
+ return new SolitaryLineBreaker(this, context, handler, fontConsumer);
+ }
+
+}
Added: trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/package-info.java
===================================================================
--- trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/package-info.java (rev 0)
+++ trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/package-info.java 2022-02-03 20:27:04 UTC (rev 12553)
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2022 The FOray Project.
+ * http://www.foray.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This work is in part derived from the following work(s), used with the
+ * permission of the licensor:
+ * Apache FOP, licensed by the Apache Software Foundation
+ *
+ */
+
+/*
+ * $LastChangedRevision$
+ * $LastChangedDate$
+ * $LastChangedBy$
+ */
+
+/**
+ * Legacy line-breaking classes.
+ */
+package org.foray.pioneer.lb;
+/* TODO: This package should be removed as soon as the Pioneer layout system has switched over to the new line-breaking
+ * scheme. */
Property changes on: trunk/foray/foray-pioneer/src/main/java/org/foray/pioneer/lb/package-info.java
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+Author Date Id Rev
\ No newline at end of property
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|