Ansi4j
Techsenger ANSI4J is a library that includes Java parser for processing text with ANSI escape codes (ISO/IEC 6429 control functions) and a CSS extension for styling the text.
Install / Use
/learn @techsenger/Ansi4jREADME
Techsenger ANSI4J
Techsenger ANSI4J is a Java library that parses ANSI escape codes in full accordance with ISO/IEC 6429:1992. It supports all functions of all five types. At the same time parser architecture allows to add easily any other custom types of functions and mechanism to process them.
Techsenger ANSI4J consists of a core that includes parser and CSS extension that allows to create CSS declaration using function arguments.
Table of Contents
Core <a name="core"></a>
Overview <a name="core-overview"></a>
Core contains all base classes for working with control functions, parsers and text.
Base components <a name="core-base"></a>
ParserFactoryis thread-safe instance of factory, that can be used for creating N parsers for parsing N texts. So, usually there is only one factory.Parseris a non thread-safe object that reads text, manages finder and handlers and returns parsed fragment. There are two types of parsers:StringParserfor parsingString.StringParseris very light, so it is possible to create it for every text line.StreamParserfor parsingInputStream. One instance ofStreamParseris created for one instance ofInputStream.
FunctionFinderfinds function in a text and resolves found function.FragmentHandleris a thread-safe object for processing fragment of text. There are two types of handlers:TextHandleris a handler for processing a text that doesn't contain any control functions in it. This handler allows to modify this text within system. Default implementation doesn't modify text and just wraps it inTextFragment.FunctionHandleris a handler for processing functions in a text. For every type of function separate function handler exists. As a resultFunctionHandlerreturnsFunctionFragment.
Fragmentis a processed piece of text. There are two types of fragments:TextFragmentthat contains information about text pieces without functions.FunctionFragmentthat contains information about functions in text.
ControlFunctionis the base interface implemented by all enumerations used for ANSI function identification. These enumerations contain short function names in accordance with the standard. At the same time, for each enumeration X, there is an XAlias interface that provides the full function names. These aliases can be used by those who do not remember the short names.
Dependencies <a name="core-dependencies"></a>
This project is available on Maven Central:
<dependency>
<groupId>com.techsenger.ansi4j</groupId>
<artifactId>ansi4j-core-api</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.techsenger.ansi4j</groupId>
<artifactId>ansi4j-core-impl</artifactId>
<version>2.0.0</version>
</dependency>
Usage <a name="core-usage"></a>
Step 0 - Creating ParserFactory
ParserFactory factory = new ParserFactory.Builder()
.environment(Environment._7_BIT)
//specify only the types you need
.functionTypes(
ControlFunctionType.C0_SET,
ControlFunctionType.C1_SET,
ControlFunctionType.CONTROL_SEQUENCE,
ControlFunctionType.INDEPENDENT_FUNCTION,
ControlFunctionType.CONTROL_STRING)
.build();
Step 1A - Creating StringParser
//this is the text we are going to parse
String text = ...;
//we need a parser
var parser = factory.createParser(text);
Step 1B - Creating StreamParser
//this is the stream we are going to parse
InputStream stream = ...;
//we need a parser, one parser for one stream
try (var parser = factory.createParser(stream, StandardCharsets.UTF_8, 1024)) {
...
} catch (IOException ex) {
...
}
Step 2 - Parsing
//so, let's go
Fragment fragment = null;
while ((fragment = parser.parse()) != null) {
if (fragment.getType() == FragmentType.TEXT) {
TextFragment textFragment = (TextFragment) fragment;
...
} else if (fragment.getType() == FragmentType.FUNCTION) {
FunctionFragment functionFragment = (FunctionFragment) fragment;
//or functionFragment.getFunction() == ControlSequenceFunctionAlias.SELECT_GRAPHIC_RENDITION
if (functionFragment.getFunction() == ControlSequenceFunction.SGR) {
...
}
}
}
Thread-safety <a name="core-thread"></a>
ParserFactory is thread-safe. StringParser and StreamParser are not thread-safe. FunctionFinder, TextHandler and
FunctionHandlers are thread-safe. Detailed information about thread-safety is provided in every interface in core API
module.
CSS extension <a name="css"></a>
Overview <a name="css-overview"></a>
The CSS extension allows you to style text using CSS in a way that mirrors the styling applied by SGR functions. For example, this extension can be used to style program outputs, log messages, documentation, and more.
To display styled text, you can use components from any platform (JavaFX, Swing), as neither the core library nor the CSS extension (except for the demo module) depend on the classes of these platforms.
Out of the box, the library provides three style generators for JavaFX WebView, JavaFX TextFlow, and
RichTextFX InlineCssTextArea. These generators are located in the API module and can be easily modified. A table
listing the features supported by each of these components is available in the demo application. We recommend using
InlineCssTextArea because: 1) it supports nearly all features, 2) it is lightweight, and 3) it allows text editing.
Currently, the following text attributes are supported: intensity, italic, underline, blink, reverse video, visibility, strikethrough, font, foreground color, and background color.
Demo <a name="css-demo"></a>
The demo application showcases examples of styling the output of common programs:

The demo application also allows you to see which styles are generated for each attribute across different controls

To run the demo application, execute the following commands in the project root:
cd ansi4j-css-demo
mvn javafx:run
Base components <a name="css-base"></a>
AttributeRegistrystores model attributes, separated into groups.AttributeGroupcontains a group of logically related attributes. Currently, there is only one group for the SGR function.GroupStyleGeneratorgenerates CSS declarations based on attribute value changes of a specific group. Thus, each generator is used for only one attribute group.StyleProcessor, which is called for each SGR function, updates the attribute values and returns the generated CSS declarations.Palette- ISO 6429 supports only 8 colors (3 bits). However, today many terminals supports 4, 8 and 24 bit colors. However, these extra colors are not included in the standard. So, to support them useextraColorsEnabled()method in config.Paletteis an interface, so it is easy to add custom 8, 16, 256 color palette.
How it works:
Each attribute in the registry has a default value. Therefore, initially, you need to set values that correspond to
your settings. For example, if the default is italicized text with a red color, you should set the default values for
the italic and fgColor attributes:
TextAttributeGroupConfig.Builder()
.defaultItalic(true)
.defaultFgColor(new Color(0xff0000))
...
When text parsing begins, each SGR function is passed to the StyleProcessor. Based on the arguments of this function,
the attribute values are updated. Then, the generator identifies the attributes whose values differ from the default
ones and generates a style for a single span, which will be closed before the next SGR function. Thus, the generated
styles will only be applied to the text between the current SGR function and the next one.
Important notes:
-
To enable blink for
WebViewis is necessary to add the following keyframes to your stylesheet:@keyframes ansi4j-blinker {50% { opacity: 0; }}. -
RichTextFX
InlineCssTextAreawill support blink when this issue is resolved.
Dependencies <a name="css-dependencies"></a>
This project is available on Maven Central:
<dependency>
<groupId>com.techsenger.ansi4j</groupId>
<artifactId>ansi4j-css-api</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.techsenger.ansi4j</groupId>
<artifactId>ansi4j-css-impl</artifactId>
<version>2.0.0</version>
</dependency>
Usage <a name="css-usage"></a>
//First of all we
