mirror of
https://gitlab.com/biskuteri-cafe/JKomasto2.git
synced 2025-01-08 22:04:45 +01:00
Added basic version of HTML text pane.
This commit is contained in:
parent
e4f13ad8c8
commit
44efddbd5c
@ -18,8 +18,8 @@ BasicHTMLParser {
|
||||
|
||||
Tree<String> document;
|
||||
document = toNodes(segments);
|
||||
document = splitText(document);
|
||||
document = evaluateHtmlEscapes(document);
|
||||
document = distinguishEmojisFromText(document);
|
||||
document = hierarchise(document);
|
||||
|
||||
return document;
|
||||
@ -136,6 +136,104 @@ BasicHTMLParser {
|
||||
return returnee;
|
||||
}
|
||||
|
||||
private static Tree<String>
|
||||
splitText(Tree<String> nodes)
|
||||
{
|
||||
Tree<String> returnee = new Tree<>();
|
||||
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (node.key.equals("tag"))
|
||||
{
|
||||
returnee.add(node);
|
||||
continue;
|
||||
}
|
||||
assert node.key.equals("text");
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
boolean letter = false, cletter;
|
||||
boolean space = false, cspace;
|
||||
boolean emoji = false;
|
||||
for (char c: node.value.toCharArray())
|
||||
{
|
||||
cletter = isLetter(c);
|
||||
cspace = Character.isWhitespace(c);
|
||||
|
||||
if (c == ':' && !emoji && !letter)
|
||||
{
|
||||
if (b.length() > 0)
|
||||
{
|
||||
Tree<String> addee = new Tree<>();
|
||||
addee.key = space ? "space" : "text";
|
||||
addee.value = empty(b);
|
||||
returnee.add(addee);
|
||||
}
|
||||
emoji = true;
|
||||
b.append(c);
|
||||
}
|
||||
else if (c == ':' && emoji)
|
||||
{
|
||||
assert letter;
|
||||
b.append(c);
|
||||
Tree<String> addee = new Tree<>();
|
||||
addee.key = "emoji";
|
||||
addee.value = empty(b);
|
||||
returnee.add(addee);
|
||||
/*
|
||||
* Technically, addee.value.length()
|
||||
* could be zero, which probably means
|
||||
* someone just put two colons in a row,
|
||||
* maybe for Haskell source code. I'd
|
||||
* be surprised if Mastodon didn't escape
|
||||
* it. (If they did, the next step will
|
||||
* handle them.) Anyways treating it as
|
||||
* an empty emoji is the correct action.
|
||||
*/
|
||||
emoji = false;
|
||||
cletter = false;
|
||||
}
|
||||
else if (cspace && letter)
|
||||
{
|
||||
assert b.length() > 0;
|
||||
Tree<String> addee = new Tree<>();
|
||||
addee.key = "text";
|
||||
addee.value = empty(b);
|
||||
returnee.add(addee);
|
||||
b.append(c);
|
||||
}
|
||||
else if (cletter && space)
|
||||
{
|
||||
assert b.length() > 0;
|
||||
Tree<String> addee = new Tree<>();
|
||||
addee.key = "space";
|
||||
addee.value = empty(b);
|
||||
returnee.add(addee);
|
||||
b.append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.append(c);
|
||||
}
|
||||
/*
|
||||
* We can specially handle special
|
||||
* characters like \n, but I'll opt not to.
|
||||
*/
|
||||
|
||||
letter = cletter;
|
||||
space = cspace;
|
||||
}
|
||||
if (b.length() > 0)
|
||||
{
|
||||
Tree<String> addee = new Tree<>();
|
||||
addee.key = space ? "space" : "text";
|
||||
addee.value = empty(b);
|
||||
returnee.add(addee);
|
||||
}
|
||||
}
|
||||
|
||||
return returnee;
|
||||
}
|
||||
|
||||
private static Tree<String>
|
||||
evaluateHtmlEscapes(Tree<String> nodes)
|
||||
{
|
||||
@ -152,54 +250,6 @@ BasicHTMLParser {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private static Tree<String>
|
||||
distinguishEmojisFromText(Tree<String> nodes)
|
||||
{
|
||||
Tree<String> returnee = new Tree<String>();
|
||||
|
||||
for (Tree<String> node: nodes)
|
||||
{
|
||||
if (!node.key.equals("text"))
|
||||
{
|
||||
returnee.add(node);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> segments;
|
||||
segments = distinguishWhitespaceFromText(node.value);
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (String segment: segments)
|
||||
{
|
||||
boolean starts = segment.startsWith(":");
|
||||
boolean ends = segment.endsWith(":");
|
||||
if (starts && ends)
|
||||
{
|
||||
Tree<String> text = new Tree<String>();
|
||||
text.key = "text";
|
||||
text.value = empty(b);
|
||||
returnee.add(text);
|
||||
Tree<String> emoji = new Tree<String>();
|
||||
emoji.key = "emoji";
|
||||
emoji.value = segment;
|
||||
returnee.add(emoji);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.append(segment);
|
||||
}
|
||||
}
|
||||
if (b.length() > 0)
|
||||
{
|
||||
Tree<String> text = new Tree<String>();
|
||||
text.key = "text";
|
||||
text.value = empty(b);
|
||||
returnee.add(text);
|
||||
}
|
||||
}
|
||||
|
||||
return returnee;
|
||||
}
|
||||
|
||||
private static Tree<String>
|
||||
hierarchise(Tree<String> nodes)
|
||||
{
|
||||
@ -264,27 +314,26 @@ BasicHTMLParser {
|
||||
return s;
|
||||
}
|
||||
|
||||
private static List<String>
|
||||
distinguishWhitespaceFromText(String text)
|
||||
private static boolean
|
||||
isPunctuation(char c)
|
||||
{
|
||||
List<String> returnee = new ArrayList<>();
|
||||
|
||||
StringBuilder segment = new StringBuilder();
|
||||
boolean inWhitespace = false;
|
||||
for (char c: text.toCharArray())
|
||||
switch (Character.getType(c))
|
||||
{
|
||||
boolean w = Character.isWhitespace(c);
|
||||
boolean change = w ^ inWhitespace;
|
||||
if (change)
|
||||
{
|
||||
returnee.add(empty(segment));
|
||||
inWhitespace = !inWhitespace;
|
||||
case Character.START_PUNCTUATION:
|
||||
case Character.END_PUNCTUATION:
|
||||
case Character.DASH_PUNCTUATION:
|
||||
case Character.CONNECTOR_PUNCTUATION:
|
||||
case Character.INITIAL_QUOTE_PUNCTUATION:
|
||||
case Character.FINAL_QUOTE_PUNCTUATION:
|
||||
case Character.OTHER_PUNCTUATION: return true;
|
||||
default: return false;
|
||||
}
|
||||
segment.append(c);
|
||||
}
|
||||
returnee.add(empty(segment));
|
||||
|
||||
return returnee;
|
||||
private static boolean
|
||||
isLetter(char c)
|
||||
{
|
||||
return Character.isLetter(c) || isPunctuation(c);
|
||||
}
|
||||
|
||||
private static String
|
||||
|
@ -39,6 +39,9 @@ import java.time.ZonedDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
||||
class
|
||||
PostWindow extends JFrame {
|
||||
@ -59,7 +62,10 @@ PostWindow extends JFrame {
|
||||
display;
|
||||
|
||||
private static JFrame
|
||||
temp;
|
||||
test;
|
||||
|
||||
private static RichTextPane3
|
||||
test2;
|
||||
|
||||
// - -%- -
|
||||
|
||||
@ -101,28 +107,6 @@ PostWindow extends JFrame {
|
||||
|
||||
display.setEmojiUrls(post.emojiUrls);
|
||||
|
||||
{
|
||||
Tree<String> html = BasicHTMLParser.parse(post.text);
|
||||
Tree<String> emojiMap = new Tree<>();
|
||||
for (String[] m: post.emojiUrls)
|
||||
{
|
||||
emojiMap.add(new Tree<>(m[0], m[1]));
|
||||
}
|
||||
|
||||
if (temp == null)
|
||||
{
|
||||
temp = new JFrame();
|
||||
temp.setSize(256, 256);
|
||||
temp.setLocationByPlatform(true);
|
||||
temp.setVisible(true);
|
||||
RichTextPane2 pane = new RichTextPane2();
|
||||
pane.setFont(new Font("Dialog", Font.PLAIN, 18));
|
||||
temp.setContentPane(pane);
|
||||
}
|
||||
((RichTextPane2)temp.getContentPane())
|
||||
.setText(html, emojiMap);
|
||||
}
|
||||
|
||||
display.setHtml(post.text);
|
||||
display.setFavourited(post.favourited);
|
||||
display.setBoosted(post.boosted);
|
||||
@ -139,6 +123,23 @@ PostWindow extends JFrame {
|
||||
|
||||
display.resetFocus();
|
||||
repaint();
|
||||
|
||||
if (test == null)
|
||||
{
|
||||
test = new JFrame();
|
||||
test.setSize(340, 256);
|
||||
test2 = new RichTextPane3();
|
||||
test2.setFont(new Font("Dialog", Font.PLAIN, 18));
|
||||
test.setContentPane(test2);
|
||||
test.setVisible(true);
|
||||
}
|
||||
test2.setText(BasicHTMLParser.parse(post.text));
|
||||
Map<String, Image> emojis = new HashMap<>();
|
||||
for (String[] entry: post.emojiUrls)
|
||||
{
|
||||
emojis.put(entry[0], ImageApi.remote(entry[1]));
|
||||
}
|
||||
test2.setEmojis(emojis);
|
||||
}
|
||||
|
||||
public void
|
||||
|
267
RichTextPane3.java
Normal file
267
RichTextPane3.java
Normal file
@ -0,0 +1,267 @@
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Point;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Font;
|
||||
import java.awt.Image;
|
||||
import java.awt.event.ComponentListener;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
import cafe.biskuteri.hinoki.Tree;
|
||||
|
||||
class
|
||||
RichTextPane3 extends JComponent
|
||||
implements ComponentListener {
|
||||
|
||||
private Tree<String>
|
||||
html;
|
||||
|
||||
private Map<String, Image>
|
||||
emojis;
|
||||
|
||||
private Map<Tree<String>, Point>
|
||||
layout;
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
public void
|
||||
setText(Tree<String> html)
|
||||
{
|
||||
assert html != null;
|
||||
|
||||
this.html = html;
|
||||
|
||||
if (!isValid()) return;
|
||||
|
||||
FontMetrics fm = getFontMetrics(getFont());
|
||||
int iy = fm.getAscent();
|
||||
int fph = (fm.getAscent() + fm.getDescent()) * 3/2;
|
||||
Point cursor = new Point(0, iy - fph);
|
||||
layout.clear();
|
||||
layout(html, fm, cursor);
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void
|
||||
setEmojis(Map<String, Image> emojis)
|
||||
{
|
||||
assert emojis != null;
|
||||
this.emojis = emojis;
|
||||
setText(html);
|
||||
}
|
||||
|
||||
// - -%- -
|
||||
|
||||
private void
|
||||
layout(Tree<String> node, FontMetrics fm, Point cursor)
|
||||
{
|
||||
int lh = fm.getAscent() + fm.getDescent();
|
||||
|
||||
if (node.key.equals("space"))
|
||||
{
|
||||
int w = fm.stringWidth(node.value);
|
||||
if (cursor.x + w < getWidth())
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.x += w;
|
||||
}
|
||||
else
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.y += lh;
|
||||
cursor.x = 0;
|
||||
}
|
||||
}
|
||||
else if (node.key.equals("text"))
|
||||
{
|
||||
int w = fm.stringWidth(node.value);
|
||||
if (cursor.x + w < getWidth())
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.x += w;
|
||||
}
|
||||
else if (w < getWidth())
|
||||
{
|
||||
cursor.y += lh;
|
||||
cursor.x = 0;
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.x += w;
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder rem = new StringBuilder();
|
||||
rem.append(node.value);
|
||||
int mw = getWidth();
|
||||
int aw = mw - cursor.x;
|
||||
|
||||
w = fm.charWidth(node.value.charAt(0));
|
||||
if (w >= aw)
|
||||
{
|
||||
cursor.y += lh;
|
||||
cursor.x = 0;
|
||||
}
|
||||
|
||||
while (rem.length() > 0)
|
||||
{
|
||||
int l = 2;
|
||||
for (; l <= rem.length(); ++l)
|
||||
{
|
||||
String substr = rem.substring(0, l);
|
||||
w = fm.stringWidth(substr);
|
||||
if (w >= aw) break;
|
||||
}
|
||||
String substr = rem.substring(0, --l);
|
||||
w = fm.stringWidth(substr);
|
||||
|
||||
Tree<String> temp = new Tree<>();
|
||||
temp.key = node.key;
|
||||
temp.value = substr;
|
||||
layout.put(temp, new Point(cursor));
|
||||
|
||||
rem.delete(0, l);
|
||||
if (rem.length() == 0)
|
||||
{
|
||||
cursor.x = w;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor.y += lh;
|
||||
cursor.x = 0;
|
||||
}
|
||||
aw = mw;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (node.key.equals("emoji"))
|
||||
{
|
||||
Image image = emojis.get(node.value);
|
||||
int w;
|
||||
if (image != null)
|
||||
{
|
||||
int ow = image.getWidth(this);
|
||||
int oh = image.getHeight(this);
|
||||
int h = lh;
|
||||
w = ow * h/oh;
|
||||
}
|
||||
else
|
||||
{
|
||||
w = fm.stringWidth(node.value);
|
||||
}
|
||||
if (cursor.x + w < getWidth())
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.x += w;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor.y += lh;
|
||||
cursor.x = 0;
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.x += w;
|
||||
}
|
||||
}
|
||||
else if (node.key.equals("tag"))
|
||||
{
|
||||
String tagName = node.get(0).key;
|
||||
Tree<String> children = node.get("children");
|
||||
|
||||
if (tagName.equals("br"))
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.y += lh;
|
||||
cursor.x = 0;
|
||||
}
|
||||
else if (tagName.equals("p"))
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
cursor.y += lh * 3/2;
|
||||
cursor.x = 0;
|
||||
}
|
||||
else if (tagName.equals("a"))
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
// For now we ignore the link.
|
||||
}
|
||||
else if (tagName.equals("span"))
|
||||
{
|
||||
layout.put(node, new Point(cursor));
|
||||
}
|
||||
|
||||
for (Tree<String> child: children)
|
||||
{
|
||||
layout(child, fm, cursor);
|
||||
}
|
||||
}
|
||||
else assert false;
|
||||
}
|
||||
|
||||
protected void
|
||||
paintComponent(Graphics g)
|
||||
{
|
||||
g.setFont(getFont());
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
|
||||
((java.awt.Graphics2D)g).setRenderingHint(
|
||||
java.awt.RenderingHints.KEY_ANTIALIASING,
|
||||
java.awt.RenderingHints.VALUE_ANTIALIAS_ON
|
||||
);
|
||||
|
||||
for (Tree<String> node: layout.keySet())
|
||||
{
|
||||
if (node.key.equals("text"))
|
||||
{
|
||||
Point point = layout.get(node);
|
||||
g.drawString(node.value, point.x, point.y);
|
||||
}
|
||||
else if (node.key.equals("emoji"))
|
||||
{
|
||||
Point point = layout.get(node);
|
||||
Image image = emojis.get(node.value);
|
||||
if (image != null)
|
||||
{
|
||||
int ow = image.getWidth(this);
|
||||
int oh = image.getHeight(this);
|
||||
int nh = fm.getAscent() + fm.getDescent();
|
||||
int nw = ow * nh/oh;
|
||||
int x = point.x;
|
||||
int y = point.y - fm.getAscent();
|
||||
g.drawImage(image, x, y, nw, nh, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
int x = point.x;
|
||||
int y = point.y;
|
||||
g.drawString(node.value, x, y);
|
||||
}
|
||||
}
|
||||
else continue;
|
||||
}
|
||||
}
|
||||
|
||||
public void
|
||||
componentResized(ComponentEvent eC) { setText(html); }
|
||||
|
||||
public void
|
||||
componentMoved(ComponentEvent eC) { }
|
||||
|
||||
public void
|
||||
componentShown(ComponentEvent eC) { }
|
||||
|
||||
public void
|
||||
componentHidden(ComponentEvent eC) { }
|
||||
|
||||
// ---%-@-%---
|
||||
|
||||
RichTextPane3()
|
||||
{
|
||||
layout = new HashMap<>();
|
||||
emojis = new HashMap<>();
|
||||
setText(new Tree<String>());
|
||||
this.addComponentListener(this);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user