Преглед изворни кода

assira.ui: CSS: support for "strings"
assira.ui: CSS: support font

Ranides Atterwim пре 1 година
родитељ
комит
85a15c2e27

+ 5 - 0
assira.ui/src/main/antlr4/imports/BasePrimitives.g4

@@ -30,8 +30,10 @@ fragment DIGIT_NZ : [1-9] ;
 fragment DIGIT_Z : '0' ;
 fragment DIGIT_HEX: [0-9a-fA-F] ;
 
+fragment CHAR_QUOTE: '"';
 fragment CHAR_WS: [ \t\r\n,?!.;]+;
 
+
 // ------------------------------------
 
 LP   : '(' CHAR_WS*;
@@ -41,6 +43,9 @@ RP   : CHAR_WS* ')';
 RB   : CHAR_WS* ']';
 GT   : CHAR_WS* '>';
 HP   : '-';
+FSLASH   : '/';
+BSLASH   : '\\';
+TEXT: CHAR_QUOTE .*? CHAR_QUOTE;
 
 CS    : CHAR_WS* ',' CHAR_WS*;
 DOT   : CHAR_WS* '.' CHAR_WS*;

+ 81 - 0
assira.ui/src/main/antlr4/imports/CssFont.g4

@@ -0,0 +1,81 @@
+grammar CssFont;
+
+import BasePrimitives, CssUnit;
+
+fontCss
+    : (fontStyle WS) (fontWeight WS)? fontSize WS fontFamily
+    | (fontWeight WS) (fontStyle WS)? fontSize WS fontFamily
+    | fontSize WS fontFamily
+    ;
+
+fontAttribute
+    : fontStyle
+    | fontWeight
+    | CSS3_FONT_ATTR_NORMAL
+    ;
+
+fontFamily
+    : fontFamilyName CS fontFamilyGeneric
+    | fontFamilyName
+    | fontFamilyGeneric
+    ;
+
+fontFamilyName
+    : TEXT
+    | word
+    ;
+
+fontFamilyGeneric
+    : CSS3_FONT_GENERIC_SERIF
+    | CSS3_FONT_GENERIC_SAN_SERIF
+    | CSS3_FONT_GENERIC_MONOSPACE
+    | CSS3_FONT_GENERIC_CURSIVE
+    | CSS3_FONT_GENERIC_FANTASY
+    | CSS3_FONT_GENERIC_SYSTEM_UI
+    | CSS3_FONT_GENERIC_UI_SERIF
+    | CSS3_FONT_GENERIC_UI_SANS_SERIF
+    | CSS3_FONT_GENERIC_UI_MONOSPACE
+    | CSS3_FONT_GENERIC_UI_ROUNDED
+    | CSS3_FONT_GENERIC_MATH
+    | CSS3_FONT_GENERIC_EMOJI
+    | CSS3_FONT_GENERIC_FANGSONG
+    ;
+
+fontSize
+    : length (FSLASH length)?
+    ;
+
+fontStyle
+    : CSS3_FONT_STYLE_ITALIC
+    | CSS3_FONT_STYLE_OBLIQUE (WS angle)?
+    ;
+
+fontWeight
+    : CSS3_FONT_WEIGHT_BOLD
+    | CSS3_FONT_WEIGHT_LIGHTER
+    | CSS3_FONT_WEIGHT_BOLDER
+    | numberInt
+    ;
+
+CSS3_FONT_GENERIC_SERIF         : 'serif';
+CSS3_FONT_GENERIC_SAN_SERIF     : 'sans-serif';
+CSS3_FONT_GENERIC_MONOSPACE     : 'monospace';
+CSS3_FONT_GENERIC_CURSIVE       : 'cursive';
+CSS3_FONT_GENERIC_FANTASY       : 'fantasy';
+CSS3_FONT_GENERIC_SYSTEM_UI     : 'system-ui';
+CSS3_FONT_GENERIC_UI_SERIF      : 'ui-serif';
+CSS3_FONT_GENERIC_UI_SANS_SERIF : 'ui-sans-serif';
+CSS3_FONT_GENERIC_UI_MONOSPACE  : 'ui-monospace';
+CSS3_FONT_GENERIC_UI_ROUNDED    : 'ui-rounded';
+CSS3_FONT_GENERIC_MATH          : 'math';
+CSS3_FONT_GENERIC_EMOJI         : 'emoji';
+CSS3_FONT_GENERIC_FANGSONG      : 'fangsong';
+
+CSS3_FONT_ATTR_NORMAL           : 'normal';
+
+CSS3_FONT_STYLE_ITALIC          : 'italic';
+CSS3_FONT_STYLE_OBLIQUE         : 'oblique';
+
+CSS3_FONT_WEIGHT_BOLD           : 'bold';
+CSS3_FONT_WEIGHT_LIGHTER        : 'lighter';
+CSS3_FONT_WEIGHT_BOLDER         : 'bolder';

+ 2 - 1
assira.ui/src/main/antlr4/net/ranides/grammars/generated/UiGrammar.g4

@@ -1,6 +1,6 @@
 grammar UiGrammar;
 
-import BasePrimitives, CssColor, CssUnit, CssBorder, SwingBorder, SwingFont;
+import BasePrimitives, CssColor, CssUnit, CssBorder, SwingBorder, SwingFont, CssFont;
 
 root_colors
     : color (WS color)+ EOF    #color_list
@@ -24,4 +24,5 @@ root_font
 
 font
     : fontSwing
+    | fontCss
     ;

+ 13 - 0
assira.ui/src/main/java/net/ranides/assira/ui/model/CssFontStyle.java

@@ -0,0 +1,13 @@
+package net.ranides.assira.ui.model;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+public class CssFontStyle {
+    public static CssFontStyle PLAIN = new CssFontStyle(false, false, 0);
+
+    private final boolean italic;
+    private final boolean oblique;
+    private final double obliqueAngle;
+}

+ 13 - 0
assira.ui/src/main/java/net/ranides/assira/ui/model/CssFontWeight.java

@@ -0,0 +1,13 @@
+package net.ranides.assira.ui.model;
+
+import lombok.Data;
+
+@Data
+public class CssFontWeight {
+    private static final CssFontWeight PLAIN = new CssFontWeight(false, false, false, 400);
+
+    private final boolean bold;
+    private final boolean bolder;
+    private final boolean lighter;
+    private final int absolute;
+}

+ 86 - 7
assira.ui/src/main/java/net/ranides/assira/ui/parsers/ParseCssFont.java

@@ -1,9 +1,13 @@
 package net.ranides.assira.ui.parsers;
 
 import lombok.experimental.UtilityClass;
+import net.ranides.assira.generic.Ref;
 import net.ranides.assira.grammar.G4Node;
 import net.ranides.assira.grammar.G4Selector;
 import net.ranides.assira.grammar.G4SelectorBuilder;
+import net.ranides.assira.text.StringUtils;
+import net.ranides.assira.ui.model.CssFontStyle;
+import net.ranides.assira.ui.model.CssFontWeight;
 import net.ranides.grammars.generated.UiGrammarParser;
 import org.jetbrains.annotations.NotNull;
 
@@ -13,7 +17,7 @@ import java.awt.*;
 @UtilityClass
 public class ParseCssFont {
 
-    public static final G4Selector<Integer> FONT_STYLE = new G4SelectorBuilder<Integer>()
+    public static final G4Selector<Integer> FONT_SWING_STYLE = new G4SelectorBuilder<Integer>()
         .withName("CssFont:FONT_STYLE")
         .withParser(GrammarBase.PARSER)
         .valueForToken(UiGrammarParser.SWING_FONT_STYLE_PLAIN, Font.PLAIN)
@@ -22,25 +26,100 @@ public class ParseCssFont {
         .valueForToken(UiGrammarParser.SWING_FONT_STYLE_BOLD_ITALIC, Font.BOLD | Font.ITALIC)
         .prepare();
 
+    public static final G4Selector<CssFontStyle> FONT_CSS_STYLE = new G4SelectorBuilder<CssFontStyle>()
+        .withName("CssFont:FONT_CSS_STYLE")
+        .withParser(GrammarBase.PARSER)
+        .mapRule("fontStyle", node -> {
+            boolean italic = !node.children("CSS3_FONT_STYLE_ITALIC").isEmpty();
+            boolean oblique = !node.children("CSS3_FONT_STYLE_OBLIQUE").isEmpty();
+            double angle = oblique ? node.children("angle").single().map(ParseCssUnit.ANGLE::single).orElse(10.0) : 0.0;
+            return new CssFontStyle(italic, oblique, angle);
+        })
+        .prepare();
+
+    public static final G4Selector<CssFontWeight> FONT_CSS_WEIGHT = new G4SelectorBuilder<CssFontWeight>()
+        .withName("CssFont:FONT_CSS_STYLE")
+        .withParser(GrammarBase.PARSER)
+        .mapRule("fontWeight", node -> {
+            boolean bold = !node.children("CSS3_FONT_WEIGHT_BOLD").isEmpty();
+            boolean bolder = !node.children("CSS3_FONT_WEIGHT_LIGHTER").isEmpty();
+            boolean lighter = !node.children("CSS3_FONT_WEIGHT_BOLDER").isEmpty();
+            int absolute = node.children("numberInt").single().map(ParsePrimitives::parseInt).orElse(400);
+            return new CssFontWeight(bold, bolder, lighter, absolute);
+        })
+        .prepare();
+
+    public static final G4Selector<String> FONT_CSS_FAMILY = new G4SelectorBuilder<String>()
+        .withName("CssFont:FONT_CSS_FAMILY")
+        .withParser(GrammarBase.PARSER)
+        .mapRule(UiGrammarParser.RULE_word, node -> {
+            return node.text();
+        })
+        .mapToken(UiGrammarParser.TEXT, node -> {
+            return StringUtils.substr(node.text(), 1, -2);
+        })
+        .mapToken(UiGrammarParser.CSS3_FONT_GENERIC_SERIF, node -> {
+            return Font.SERIF;
+        })
+        .mapToken(UiGrammarParser.CSS3_FONT_GENERIC_SAN_SERIF, node -> {
+            return Font.SANS_SERIF;
+        })
+        .mapToken(UiGrammarParser.CSS3_FONT_GENERIC_MONOSPACE, node -> {
+            return Font.MONOSPACED;
+        })
+        .prepare();
+
     public static final G4Selector<Font> FONT = new G4SelectorBuilder<Font>()
         .withName("CssColor:FONT")
         .withParser(GrammarBase.PARSER)
         .mapRule("fontSwingLong", node -> {
-            return createFont(node);
+            return createSwingFont(node);
         })
         .mapRule("fontSwingShort", node -> {
-            return createFont(node);
+            return createSwingFont(node);
+        })
+        .mapRule("fontCss", node -> {
+            return createCssFont(node);
         })
-
-
         .prepare();
 
     @NotNull
-    private static Font createFont(G4Node node) {
+    private static Font createCssFont(G4Node node) {
+        int style = fontStyle(FONT_CSS_STYLE.optional(node), FONT_CSS_WEIGHT.optional(node));
+
+        int size = node.children("fontSize")
+            .map(ParseCssUnit.LENGTH::single).first()
+            .orElseGet( () -> GrammarBase.component(node).getParent().getFont().getSize());
+
+        String name = FONT_CSS_FAMILY.optional(node)
+            .orElseGet(() -> GrammarBase.component(node).getParent().getFont().getName());
+
+        return new Font(name, style, size);
+    }
+
+    private static int fontStyle(Ref<CssFontStyle> style, Ref<CssFontWeight> weight) {
+        int attrs = 0;
+
+        if(style.isPresent()) {
+            CssFontStyle s = style.get();
+            if(s.isItalic() || (s.isOblique() && s.getObliqueAngle()>0)) {
+                attrs |= Font.ITALIC;
+            }
+        }
+        if(weight.isPresent()) {
+            CssFontWeight w = weight.get();
+            if(w.isBold() || w.isBolder() || w.getAbsolute() > 400) {
+                attrs |= Font.BOLD;
+            }
+        }
+        return attrs;
+    }
+
+    private static Font createSwingFont(G4Node node) {
         String name = node.child("@name").text();
 
         int style = node.children("@style")
-            .map(FONT_STYLE::single).first()
+            .map(FONT_SWING_STYLE::single).first()
             .orElse(Font.PLAIN);
 
         int size = node.children("@size")

+ 5 - 0
assira.ui/src/test/java/net/ranides/assira/ui/parsers/GrammarUtilsTest.java

@@ -123,6 +123,11 @@ public class GrammarUtilsTest {
         info.use(ParseCssBorder.BORDER_SIZE);
         info.use(ParseCssBorder.SIZE_COMPONENT);
         info.use(ParseCssBorder.BORDER);
+        info.use(ParseCssFont.FONT);
+        info.use(ParseCssFont.FONT_SWING_STYLE);
+        info.use(ParseCssFont.FONT_CSS_STYLE);
+        info.use(ParseCssFont.FONT_CSS_WEIGHT);
+        info.use(ParseCssFont.FONT_CSS_FAMILY);
 
         StringWriter sw = new StringWriter();
         printCoverage(info, new StrAppender(sw));

+ 64 - 1
assira.ui/src/test/java/net/ranides/assira/ui/parsers/ParseCssFontTest.java

@@ -1,8 +1,15 @@
 package net.ranides.assira.ui.parsers;
 
+import net.ranides.assira.events.EventLock;
+import net.ranides.assira.events.EventReactor;
+import net.ranides.assira.events.EventRouter;
+import net.ranides.assira.observable.BeanEvent;
 import org.junit.Test;
 
+import javax.swing.*;
 import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
@@ -10,7 +17,7 @@ import static org.junit.Assert.assertThrows;
 public class ParseCssFontTest {
 
     @Test
-    public void basic() {
+    public void swing() {
 
         assertEquals(new Font("Arial Strange", Font.PLAIN, 20), ParseCssFont.font("Arial Strange-PLAIN-20") );
 
@@ -26,4 +33,60 @@ public class ParseCssFontTest {
 
         assertEquals(new Font("Verdana", Font.ITALIC, 18), ParseCssFont.font("Verdana ITALIC", ExampleComponents.PANEL) );
     }
+
+    @Test
+    public void css() {
+        assertEquals(new Font("Arial", Font.ITALIC | Font.BOLD, 16), ParseCssFont.font("bold italic 12pt Arial"));
+
+        assertEquals(new Font("Arial Black", Font.ITALIC | Font.BOLD, 24), ParseCssFont.font("italic bold 18pt \"Arial Black\""));
+        assertEquals(new Font("Arial Black", Font.ITALIC, 24), ParseCssFont.font("oblique 30deg 18pt \"Arial Black\""));
+        assertEquals(new Font("Arial Black", Font.ITALIC, 12), ParseCssFont.font("oblique 30deg 300 9pt \"Arial Black\""));
+        assertEquals(new Font("Arial Black", Font.BOLD, 12), ParseCssFont.font("oblique 0deg 800 9pt \"Arial Black\""));
+        assertEquals(new Font("Arial Black", Font.PLAIN, 12), ParseCssFont.font("oblique 0deg 100 9pt \"Arial Black\""));
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            ParseCssFont.font("italic oblique 30deg bold 12pt Arial");
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            ParseCssFont.font("italic oblique bold 12pt \"Arial Black\"");
+        });
+
+        assertEquals(new Font("Verdana", Font.ITALIC, 16), ParseCssFont.font("oblique 12pt Verdana"));
+        assertEquals(new Font("Verdana", Font.PLAIN, 16), ParseCssFont.font("12pt Verdana"));
+        assertEquals(new Font("Arial Black", Font.PLAIN, 16), ParseCssFont.font("12pt \"Arial Black\""));
+
+        assertEquals(new Font("Arial Black", Font.PLAIN, 32), ParseCssFont.font("24pt \"Arial Black\", sans-serif"));
+
+        assertEquals(new Font(Font.SANS_SERIF, Font.PLAIN, 32), ParseCssFont.font("24pt sans-serif"));
+        assertEquals(new Font(Font.SERIF, Font.PLAIN, 32), ParseCssFont.font("24pt serif"));
+    }
+
+    private void showPreview(Font font) {
+        EventRouter router = EventReactor.newInstance("me");
+
+        JFrame frame = new JFrame();
+        frame.setSize(400, 300);
+        JLabel label = new JLabel();
+        label.setText("Hello world. This is font.");
+        label.setFont(font);
+        label.setHorizontalAlignment(SwingConstants.CENTER);
+        frame.add(label);
+
+        frame.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                router.signalEvent(BeanEvent.change(this, "close", false, true));
+            }
+        });
+        frame.setVisible(true);
+
+        try {
+            EventLock.lock(BeanEvent.class, router).waitForEvent();
+            router.dispose();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+    }
 }

+ 38 - 8
assira.ui/src/test/resources/grammars/CssCoverage.txt

@@ -1,4 +1,5 @@
 1: ANY
+0: BSLASH
 1: CHAR_Q
 1: CODE_HEX3
 1: CODE_HEX4
@@ -43,11 +44,31 @@
 1: CSS2_UNIT_VMIN
 1: CSS2_UNIT_VW
 1: CSS2_none
+0: CSS3_FONT_ATTR_NORMAL
+0: CSS3_FONT_GENERIC_CURSIVE
+0: CSS3_FONT_GENERIC_EMOJI
+0: CSS3_FONT_GENERIC_FANGSONG
+0: CSS3_FONT_GENERIC_FANTASY
+0: CSS3_FONT_GENERIC_MATH
+1: CSS3_FONT_GENERIC_MONOSPACE
+1: CSS3_FONT_GENERIC_SAN_SERIF
+1: CSS3_FONT_GENERIC_SERIF
+0: CSS3_FONT_GENERIC_SYSTEM_UI
+0: CSS3_FONT_GENERIC_UI_MONOSPACE
+0: CSS3_FONT_GENERIC_UI_ROUNDED
+0: CSS3_FONT_GENERIC_UI_SANS_SERIF
+0: CSS3_FONT_GENERIC_UI_SERIF
+0: CSS3_FONT_STYLE_ITALIC
+0: CSS3_FONT_STYLE_OBLIQUE
+0: CSS3_FONT_WEIGHT_BOLD
+0: CSS3_FONT_WEIGHT_BOLDER
+0: CSS3_FONT_WEIGHT_LIGHTER
 1: CSS4_DEG
 1: CSS4_GRAD
 1: CSS4_RAD
 1: CSS4_TURN
 1: DOT
+0: FSLASH
 1: GT
 0: HP
 1: LB
@@ -68,10 +89,11 @@
 1: SWING_BORDER_etched
 0: SWING_BORDER_lowered
 0: SWING_BORDER_raised
-0: SWING_FONT_STYLE_BOLD
-0: SWING_FONT_STYLE_BOLD_ITALIC
-0: SWING_FONT_STYLE_ITALIC
-0: SWING_FONT_STYLE_PLAIN
+1: SWING_FONT_STYLE_BOLD
+1: SWING_FONT_STYLE_BOLD_ITALIC
+1: SWING_FONT_STYLE_ITALIC
+1: SWING_FONT_STYLE_PLAIN
+1: TEXT
 1: WS
 1: angle
 0: border
@@ -110,11 +132,19 @@
 1: componentLightness <= numberPct
 1: componentRGB <= numberInt
 1: componentSaturation <= numberPct
-0: font <= fontSwing
+0: font
+0: fontAttribute
+1: fontCss
+0: fontFamily
+0: fontFamilyGeneric
+0: fontFamilyName
+0: fontSize
+1: fontStyle
 0: fontSwing
-0: fontSwingLong
-0: fontSwingShort
+1: fontSwingLong
+1: fontSwingShort
 0: fontSwingStyle
+1: fontWeight
 1: length
 0: number
 1: numberHex <= NUMBER_HEX
@@ -128,5 +158,5 @@
 0: root_font
 0: unitAngle
 0: unitLength
-0: word
+1: word
 0: words