Skip to content

Commit

Permalink
Merge branch 'JFormDesigner:main' into classx
Browse files Browse the repository at this point in the history
  • Loading branch information
dar-dev authored Nov 11, 2024
2 parents b98270b + b97424f commit 27969e0
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 7 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
FlatLaf Change Log
==================

## 3.5.3-SNAPSHOT

#### Fixed bugs

- HTML: Fixed wrong rendering if HTML text contains `<style>` tag with
attributes (e.g. `<style type='text/css'>`). (issue #905; regression in 3.5.1)


## 3.5.2

#### Fixed bugs
Expand Down
46 changes: 40 additions & 6 deletions flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatHTML.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.function.BiConsumer;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
Expand Down Expand Up @@ -93,23 +93,22 @@ else if( c instanceof JToolTip )
text = ((JToolTip)c).getTipText();
else
return;
if( text == null )
if( text == null || !BasicHTML.isHTMLString( text ) )
return;

// BASE_SIZE rule is parsed in javax.swing.text.html.StyleSheet.addRule()
String style = "<style>BASE_SIZE " + c.getFont().getSize() + "</style>";
String openTag = "";
String closeTag = "";

String lowerText = text.toLowerCase( Locale.ENGLISH );
int headIndex;
int styleIndex;

int insertIndex;
if( (headIndex = lowerText.indexOf( "<head>" )) >= 0 ) {
if( (headIndex = indexOfTag( text, "head", true )) >= 0 ) {
// there is a <head> tag --> insert after <head> tag
insertIndex = headIndex + "<head>".length();
} else if( (styleIndex = lowerText.indexOf( "<style>" )) >= 0 ) {
insertIndex = headIndex;
} else if( (styleIndex = indexOfTag( text, "style", false )) >= 0 ) {
// there is a <style> tag --> insert before <style> tag
insertIndex = styleIndex;
} else {
Expand All @@ -124,6 +123,41 @@ else if( c instanceof JToolTip )
+ text.substring( insertIndex );

BasicHTML.updateRenderer( c, newText );

// for unit tests
if( testUpdateRenderer != null )
testUpdateRenderer.accept( c, newText );
}

// for unit tests
static BiConsumer<JComponent, String> testUpdateRenderer;

/**
* Returns start or end index of a HTML tag.
* Checks only for leading '<' character and (case-ignore) tag name.
*/
private static int indexOfTag( String html, String tag, boolean endIndex ) {
int tagLength = tag.length();
int maxLength = html.length() - tagLength - 2;
char lastTagChar = tag.charAt( tagLength - 1 );

for( int i = "<html>".length(); i < maxLength; i++ ) {
// check for leading '<' and last tag name character
if( html.charAt( i ) == '<' && Character.toLowerCase( html.charAt( i + tagLength ) ) == lastTagChar ) {
// compare tag characters from last to first
for( int j = tagLength - 2; j >= 0; j-- ) {
if( Character.toLowerCase( html.charAt( i + 1 + j ) ) != tag.charAt( j ) )
break; // not equal

if( j == 0 ) {
// tag found
return endIndex ? html.indexOf( '>', i + tagLength ) + 1 : i;
}
}
}
}

return -1;
}

private static final Set<String> absoluteSizeKeywordsSet = new HashSet<>( Arrays.asList(
Expand Down
113 changes: 113 additions & 0 deletions flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatHTML.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright 2024 FormDev Software GmbH
*
* 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
*
* https://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.
*/

package com.formdev.flatlaf.ui;

import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Locale;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.View;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

/**
* @author Karl Tauber
*/
public class TestFlatHTML
{
private final String body = "some <small>small</small> text";
private final String bodyInBody = "<body>" + body + "</body>";
private final String bodyPlain = "some small text";

@BeforeAll
static void setup() {
TestUtils.setup( false );
TestUtils.scaleFont( 2 );
}

@AfterAll
static void cleanup() {
TestUtils.cleanup();
}

@Test
void simple() {
testHtmlBaseSize( "<html>${BASE_SIZE_IN_HEAD}" + body + "</html>", bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE_IN_HEAD}" + bodyInBody + "</html>", bodyPlain );
}

@Test
void htmlWithHeadTag() {
testHtmlBaseSize( "<html><head>${BASE_SIZE}<title>test</title><head>" + body + "</html>", bodyPlain );
testHtmlBaseSize( "<html><head>${BASE_SIZE}<title>test</title><head>" + bodyInBody + "</html>", bodyPlain );

testHtmlBaseSize( "<html><head id=\"abc\">${BASE_SIZE}<title>test</title><head>" + body + "</html>", bodyPlain );
testHtmlBaseSize( "<html><head id=\"abc\">${BASE_SIZE}<title>test</title><head>" + bodyInBody + "</html>", bodyPlain );
}

@Test
void htmlWithStyleTag() {
testHtmlBaseSize( "<html>${BASE_SIZE}<style>body { color: #f00; }</style>" + bodyInBody + "</html>", bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE}<style>body { color: #f00; }</style><h1>header1</h1>" + body + "</html>", "header1\n" + bodyPlain );

testHtmlBaseSize( "<html>${BASE_SIZE}<style type='text/css'>body { color: #f00; }</style>" + bodyInBody + "</html>", bodyPlain );
testHtmlBaseSize( "<html>${BASE_SIZE}<style type='text/css'>body { color: #f00; }</style><h1>header1</h1>" + body + "</html>", "header1\n" + bodyPlain );
}

private void testHtmlBaseSize( String html, String expectedPlain ) {
testHtmlBaseSizeImpl( html, expectedPlain );
testHtmlBaseSizeImpl( html.toUpperCase( Locale.ENGLISH ), expectedPlain.toUpperCase( Locale.ENGLISH ) );
}

private void testHtmlBaseSizeImpl( String html, String expectedPlain ) {
String baseSize = "<style>BASE_SIZE " + UIManager.getFont( "Label.font" ).getSize() + "</style>";
String baseSizeInHead = "<head>" + baseSize + "</head>";

String expectedHtml = html.replace( "${BASE_SIZE}", baseSize ).replace( "${BASE_SIZE_IN_HEAD}", baseSizeInHead );
html = html.replace( "${BASE_SIZE}", "" ).replace( "${BASE_SIZE_IN_HEAD}", "" );

testHtml( html, expectedHtml, expectedPlain );
}

private void testHtml( String html, String expectedHtml, String expectedPlain ) {
FlatHTML.testUpdateRenderer = (c, newHtml) -> {
assertEquals( expectedHtml, newHtml );
assertEquals( expectedPlain, getPlainText( c ) );
};
new JLabel( html );
FlatHTML.testUpdateRenderer = null;
}

private String getPlainText( JComponent c ) {
View view = (View) c.getClientProperty( BasicHTML.propertyKey );
if( view == null )
return null;

Document doc = view.getDocument();
try {
return doc.getText( 0, doc.getLength() ).trim();
} catch( BadLocationException ex ) {
ex.printStackTrace();
return null;
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#

flatlaf.releaseVersion = 3.5.2
flatlaf.developmentVersion = 3.6-SNAPSHOT
flatlaf.developmentVersion = 3.5.3-SNAPSHOT

org.gradle.parallel = true
# org.gradle.warning.mode = all

0 comments on commit 27969e0

Please sign in to comment.