diff --git a/BasicHTMLParser.java b/BasicHTMLParser.java
index d55c2eb..fc5009e 100644
--- a/BasicHTMLParser.java
+++ b/BasicHTMLParser.java
@@ -15,10 +15,10 @@ BasicHTMLParser {
{
List segments;
segments = distinguishTagsFromPcdata(html);
- segments = evaluateHtmlEscapes(segments);
-
+
Tree document;
document = toNodes(segments);
+ document = evaluateHtmlEscapes(document);
document = distinguishEmojisFromText(document);
document = hierarchise(document);
@@ -61,51 +61,6 @@ BasicHTMLParser {
return returnee;
}
- private static List
- evaluateHtmlEscapes(List strings)
- {
- List returnee = new ArrayList<>();
-
- for (String string: strings)
- {
- StringBuilder whole = new StringBuilder();
- StringBuilder part = new StringBuilder();
- boolean inEscape = false;
- for (char c: string.toCharArray())
- {
- if (inEscape && c == ';')
- {
- part.append(c);
- inEscape = false;
- String v = empty(part);
- if (v.equals("<")) part.append('<');
- if (v.equals(">")) part.append('>');
- if (v.equals("&")) part.append('&');
- if (v.equals(""")) part.append('"');
- if (v.equals("'")) part.append('\'');
- if (v.equals("'")) part.append('\'');
- }
- else if (!inEscape && c == '&')
- {
- String v = empty(part);
- if (!v.isEmpty()) whole.append(v);
- part.append(c);
- inEscape = true;
- }
- else
- {
- part.append(c);
- }
- }
- String v = empty(part);
- if (!v.isEmpty()) whole.append(v);
-
- returnee.add(empty(whole));
- }
-
- return returnee;
- }
-
private static Tree
toNodes(List segments)
{
@@ -181,6 +136,22 @@ BasicHTMLParser {
return returnee;
}
+ private static Tree
+ evaluateHtmlEscapes(Tree nodes)
+ {
+ for (Tree node: nodes)
+ {
+ node.value = evaluateHtmlEscapes(node.value);
+ for (Tree attr: node)
+ {
+ attr.key = evaluateHtmlEscapes(attr.key);
+ attr.value = evaluateHtmlEscapes(attr.value);
+ }
+ }
+
+ return nodes;
+ }
+
private static Tree
distinguishEmojisFromText(Tree nodes)
{
@@ -233,10 +204,10 @@ BasicHTMLParser {
hierarchise(Tree nodes)
{
Tree root = new Tree();
- root.add(new Tree<>("attributes", null));
- root.get(0).add(new Tree<>("html", null));
+ root.key = "tag";
+ root.add(new Tree<>("html", null));
root.add(new Tree<>("children", null));
-
+
Deque> parents = new LinkedList<>();
parents.push(root);
for (Tree node: nodes)
@@ -246,6 +217,9 @@ BasicHTMLParser {
assert node.size() > 0;
String tagName = node.get(0).key;
+ assert node.get("children") == null;
+ node.add(new Tree<>("children", null));
+
boolean isClosing, selfClosing;
isClosing = tagName.startsWith("/");
selfClosing = node.get("/") != null;
@@ -258,30 +232,18 @@ BasicHTMLParser {
parent = parents.pop();
grandparent = parents.peek();
- assert tagName.equals(
- "/"
- + parent.get("attributes").get(0).key
- );
+ String pTagName = parent.get(0).key;
+ assert tagName.equals("/" + pTagName);
grandparent.get("children").add(parent);
}
else if (selfClosing)
{
- Tree elem = new Tree();
- node.key = "attributes";
- elem.add(node);
- elem.add(new Tree<>("children", null));
-
- parents.peek().get("children").add(elem);
+ parents.peek().get("children").add(node);
}
else
{
- Tree elem = new Tree();
- node.key = "attributes";
- elem.add(node);
- elem.add(new Tree<>("children", null));
-
- parents.push(elem);
+ parents.push(node);
}
}
else
@@ -325,4 +287,42 @@ BasicHTMLParser {
return returnee;
}
+ private static String
+ evaluateHtmlEscapes(String string)
+ {
+ if (string == null) return string;
+
+ StringBuilder whole = new StringBuilder();
+ StringBuilder part = new StringBuilder();
+ boolean inEscape = false;
+ for (char c: string.toCharArray())
+ {
+ if (inEscape && c == ';')
+ {
+ part.append(c);
+ inEscape = false;
+ String v = empty(part);
+ if (v.equals("<")) part.append('<');
+ if (v.equals(">")) part.append('>');
+ if (v.equals("&")) part.append('&');
+ if (v.equals(""")) part.append('"');
+ if (v.equals("'")) part.append('\'');
+ if (v.equals("'")) part.append('\'');
+ }
+ else if (!inEscape && c == '&')
+ {
+ String v = empty(part);
+ if (!v.isEmpty()) whole.append(v);
+ part.append(c);
+ inEscape = true;
+ }
+ else
+ {
+ part.append(c);
+ }
+ }
+ String v = empty(part);
+ if (!v.isEmpty()) whole.append(v);
+ return whole.toString();
+ }
}
\ No newline at end of file
diff --git a/ClipboardApi.java b/ClipboardApi.java
old mode 100755
new mode 100644
diff --git a/ComposeWindow.java b/ComposeWindow.java
old mode 100755
new mode 100644
diff --git a/ImageApi.java b/ImageApi.java
old mode 100755
new mode 100644
diff --git a/ImageWindow.java b/ImageWindow.java
old mode 100755
new mode 100644
diff --git a/JKomasto.java b/JKomasto.java
old mode 100755
new mode 100644
diff --git a/KDE_Dialog_Appear.wav b/KDE_Dialog_Appear.wav
old mode 100755
new mode 100644
diff --git a/LoginWindow.java b/LoginWindow.java
old mode 100755
new mode 100644
diff --git a/MastodonApi.java b/MastodonApi.java
old mode 100755
new mode 100644
index 137af65..3810092
--- a/MastodonApi.java
+++ b/MastodonApi.java
@@ -581,15 +581,23 @@ MastodonApi {
debugPrint(Tree tree, String prefix)
{
System.err.print(prefix);
- System.err.print(tree.key);
+ System.err.print(deescape(tree.key));
System.err.print(": ");
- System.err.println(tree.value);
+ System.err.println(deescape(tree.value));
for (Tree child: tree)
debugPrint(child, prefix + " ");
}
// - -%- -
+ private static String
+ deescape(String string)
+ {
+ if (string == null) return string;
+ string = string.replaceAll("\n", "\\\\n");
+ return string;
+ }
+
private static Tree
fromPlain(Reader r)
throws IOException
diff --git a/NotificationsWindow.java b/NotificationsWindow.java
old mode 100755
new mode 100644
diff --git a/PostWindow.java b/PostWindow.java
old mode 100755
new mode 100644
index 19e08fb..5a8d501
--- a/PostWindow.java
+++ b/PostWindow.java
@@ -58,6 +58,9 @@ PostWindow extends JFrame {
private PostComponent
display;
+ private static JFrame
+ temp;
+
// - -%- -
private static final DateTimeFormatter
@@ -98,6 +101,28 @@ PostWindow extends JFrame {
display.setEmojiUrls(post.emojiUrls);
+ {
+ Tree html = BasicHTMLParser.parse(post.text);
+ Tree 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);
@@ -428,8 +453,6 @@ implements ActionListener {
public void
setHtml(String n)
{
- BasicHTMLParser.parse(n);
-
RichTextPane.Builder b = new RichTextPane.Builder();
Tree nodes = RudimentaryHTMLParser.depthlessRead(n);
for (Tree node: nodes)
diff --git a/ProfileWindow.java b/ProfileWindow.java
old mode 100755
new mode 100644
diff --git a/RepliesWindow.java b/RepliesWindow.java
old mode 100755
new mode 100644
diff --git a/RequestListener.java b/RequestListener.java
old mode 100755
new mode 100644
diff --git a/RichTextPane.java b/RichTextPane.java
old mode 100755
new mode 100644
diff --git a/RichTextPane2.java b/RichTextPane2.java
new file mode 100644
index 0000000..64571ac
--- /dev/null
+++ b/RichTextPane2.java
@@ -0,0 +1,480 @@
+
+import javax.swing.JComponent;
+import java.text.AttributedString;
+import java.text.AttributedCharacterIterator;
+import java.awt.Graphics;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Image;
+import java.awt.geom.Rectangle2D;
+import java.awt.font.TextAttribute;
+import java.awt.event.ComponentListener;
+import java.awt.event.ComponentEvent;
+import java.util.List;
+import java.util.ArrayList;
+import cafe.biskuteri.hinoki.Tree;
+
+class
+RichTextPane2 extends JComponent
+implements ComponentListener {
+
+ private AttributedString
+ text;
+
+// ---%-@-%---
+
+ public void
+ setText(Tree html, Tree emojiMap)
+ {
+ Tree commands = turnIntoCommands(html);
+
+ class AStrSegment {
+ String text;
+ int offset;
+ Object[] values = new Object[Attribute.COUNT];
+ /*
+ {
+ values[3] = (Boolean)true;
+ values[4] = (Integer)0;
+ values[5] = (Boolean)true;
+ values[6] = (Boolean)false;
+ }
+ */
+ }
+ List segments = new ArrayList<>();
+
+ int offset = 0;
+ for (Tree command: commands)
+ {
+ if (command.key.equals("text"))
+ {
+ StringBuilder b = new StringBuilder();
+ Boolean cibl = null;
+ Boolean cwhi = null;
+ for (char c: command.value.toCharArray())
+ {
+ Boolean ibl = isBasicLatin(c);
+ Boolean whi = Character.isWhitespace(c);
+ if (!ibl.equals(cibl) || !whi.equals(cwhi))
+ {
+ if (b.length() > 0)
+ {
+ assert cibl != null && cwhi != null;
+ AStrSegment s = new AStrSegment();
+ s.offset = offset;
+ s.text = b.toString();
+ s.values[3] = cibl;
+ s.values[6] = cwhi;
+ segments.add(s);
+ offset += s.text.length();
+ b.delete(0, b.length());
+ }
+ cibl = ibl;
+ cwhi = whi;
+ }
+
+ b.append(c);
+ }
+ if (b.length() > 0)
+ {
+ AStrSegment s = new AStrSegment();
+ s.offset = offset;
+ s.text = b.toString();
+ s.values[3] = cibl;
+ s.values[6] = cwhi;
+ segments.add(s);
+ offset += s.text.length();
+ }
+ }
+ else if (command.key.equals("emoji"))
+ {
+ AStrSegment s = new AStrSegment();
+ s.offset = offset;
+ s.values[3] = true;
+ s.values[6] = false;
+
+ String shortcode = command.value;
+ String url = null;
+ Tree m = emojiMap.get(shortcode);
+ if (m != null) url = m.value;
+ Image img = ImageApi.remote(url);
+ if (img != null)
+ {
+ s.text = " ";
+ s.values[0] = img;
+ s.values[1] = shortcode;
+ segments.add(s);
+ offset += 1;
+ }
+ else
+ {
+ s.text = shortcode;
+ s.values[0] = null;
+ s.values[1] = null;
+ segments.add(s);
+ offset += shortcode.length();
+ }
+ }
+ else if (command.key.equals("link"))
+ {
+ AStrSegment s = new AStrSegment();
+ s.offset = offset;
+ s.text = command.value;
+ s.values[2] = command.get("url").value;
+ s.values[3] = true;
+ s.values[6] = false;
+ /*
+ * Technically we're supposed to treat
+ * the anchor text like a text node.
+ * As in, it could be non-Basic-Latin..
+ * I'll be Mastodon-specific again, and
+ * assume it's a URL or some @ string.
+ */
+ }
+ }
+
+ AttributedString astr;
+ StringBuilder b = new StringBuilder();
+ for (AStrSegment segment: segments)
+ {
+ b.append(segment.text);
+ }
+ astr = new AttributedString(b.toString());
+ for (AStrSegment segment: segments)
+ {
+ Object[] v = segment.values;
+ astr.addAttribute(
+ Attribute.IMAGE, segment.values[0],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ astr.addAttribute(
+ Attribute.ALT, segment.values[1],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ astr.addAttribute(
+ Attribute.LINK, segment.values[2],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ astr.addAttribute(
+ Attribute.BASICLATIN, segment.values[3],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ astr.addAttribute(
+ Attribute.Y, segment.values[4],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ astr.addAttribute(
+ Attribute.OFFSCREEN, segment.values[5],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ astr.addAttribute(
+ Attribute.WHITESPACE, segment.values[6],
+ segment.offset,
+ segment.offset + segment.text.length()
+ );
+ }
+
+ this.text = astr;
+ componentResized(null);
+ }
+
+// - -%- -
+
+ public void
+ componentResized(ComponentEvent eC)
+ {
+ int w = getWidth(), h = getHeight();
+
+ // We're going to evaluate the
+ // line and off-screen attributes.
+
+ FontMetrics fm = getFontMetrics(getFont());
+ Graphics g = getGraphics();
+ int x = 0, y = fm.getAscent();
+
+ AttributedCharacterIterator it;
+ it = text.getIterator();
+
+ while (it.getIndex() < it.getEndIndex())
+ {
+ int start = it.getIndex();
+ int end = it.getRunLimit();
+
+ Image img = (Image)
+ it.getAttribute(Attribute.IMAGE);
+ Boolean ibl = (Boolean)
+ it.getAttribute(Attribute.BASICLATIN);
+ Boolean whi = (Boolean)
+ it.getAttribute(Attribute.WHITESPACE);
+
+ assert ibl != null;
+ assert whi != null;
+
+ if (img != null)
+ {
+ int ow = img.getWidth(this);
+ int oh = img.getHeight(this);
+ int nh = fm.getAscent() + fm.getDescent();
+ int nw = ow * nh/oh;
+ if (x + nw > w)
+ {
+ y += fm.getAscent() + fm.getDescent();
+ x = nw;
+ }
+ text.addAttribute(
+ Attribute.Y, (Integer)y,
+ start, end
+ );
+ text.addAttribute(
+ Attribute.OFFSCREEN, (Boolean)(y > h),
+ start, end
+ );
+ it.setIndex(end);
+ }
+ else
+ {
+ int p, xOff = 0;
+ for (p = end; p > start; --p)
+ {
+ Rectangle2D r;
+ r = fm.getStringBounds(it, start, p, g);
+ xOff = (int)r.getWidth();
+ if (x + xOff < w) break;
+ }
+ if (p == end || whi)
+ {
+ x += xOff;
+ text.addAttribute(
+ Attribute.Y, (Integer)y,
+ start, end
+ );
+ text.addAttribute(
+ Attribute.OFFSCREEN, (Boolean)(y > h),
+ start, end
+ );
+ it.setIndex(end);
+ }
+ else if (p <= start)
+ {
+ y += fm.getAscent() + fm.getDescent();
+ x = xOff;
+ text.addAttribute(
+ Attribute.Y, (Integer)y,
+ start, end
+ );
+ text.addAttribute(
+ Attribute.OFFSCREEN, (Boolean)(y > h),
+ start, end
+ );
+ it.setIndex(end);
+ }
+ else
+ {
+ text.addAttribute(
+ Attribute.Y, (Integer)y,
+ start, p
+ );
+ text.addAttribute(
+ Attribute.OFFSCREEN, (Boolean)(y > h),
+ start, p
+ );
+ y += fm.getAscent() + fm.getDescent();
+ x = 0;
+ it.setIndex(p);
+ }
+ }
+ }
+
+ text.addAttribute(TextAttribute.FONT, getFont());
+
+ repaint();
+ }
+
+ protected void
+ paintComponent(Graphics g)
+ {
+ int w = getWidth(), h = getHeight();
+ g.clearRect(0, 0, w, h);
+
+ FontMetrics fm = g.getFontMetrics();
+
+ AttributedCharacterIterator it;
+ it = text.getIterator();
+
+ ((java.awt.Graphics2D)g).setRenderingHint(
+ java.awt.RenderingHints.KEY_ANTIALIASING,
+ java.awt.RenderingHints.VALUE_ANTIALIAS_ON
+ );
+
+ int x = 0, y = fm.getAscent();
+ while (it.getIndex() < it.getEndIndex())
+ {
+ int start = it.getIndex();
+ int end = it.getRunLimit();
+
+ Image img = (Image)
+ it.getAttribute(Attribute.IMAGE);
+ Boolean ibl = (Boolean)
+ it.getAttribute(Attribute.BASICLATIN);
+ Integer ny = (Integer)
+ it.getAttribute(Attribute.Y);
+
+ if (ny > y)
+ {
+ y = ny;
+ x = 0;
+ }
+
+ if (img != null)
+ {
+ int ow = img.getWidth(this);
+ int oh = img.getHeight(this);
+ int nh = fm.getAscent() + fm.getDescent();
+ int nw = ow * nh/oh;
+ int iy = y + fm.getDescent() - nh;
+ g.drawImage(img, x, iy, nw, nh, this);
+ x += nw;
+ }
+ else
+ {
+ Rectangle2D r;
+ r = fm.getStringBounds(it, start, end, g);
+ AttributedCharacterIterator sit;
+ sit = text.getIterator(null, start, end);
+ g.drawString(sit, x, y);
+ x += (int)r.getWidth();
+ }
+ it.setIndex(end);
+ }
+ }
+
+ public void
+ componentMoved(ComponentEvent eC) { }
+
+ public void
+ componentShown(ComponentEvent eC) { }
+
+ public void
+ componentHidden(ComponentEvent eC) { }
+
+// - -%- -
+
+ private static Boolean
+ isBasicLatin(char c)
+ {
+ return true;
+ }
+
+ private static String
+ toText(Tree node)
+ {
+ Tree children = node.get("children");
+ if (children == null)
+ {
+ boolean text = node.key.equals("text");
+ boolean emoji = node.key.equals("emoji");
+ assert text || emoji;
+ return node.value;
+ }
+
+ StringBuilder b = new StringBuilder();
+ for (Tree child: children)
+ {
+ b.append(toText(child));
+ }
+ return b.toString();
+ }
+
+ private static Tree
+ turnIntoCommands(Tree tag)
+ {
+ assert tag.key.equals("tag");
+ Tree returnee = new Tree();
+
+ String tagName = tag.get(0).key;
+ Tree children = tag.get("children");
+
+ if (tagName.equals("a"))
+ {
+ String url = tag.get("href").value;
+ Tree addee = new Tree<>();
+ addee.key = "link";
+ addee.value = toText(tag);
+ addee.add(new Tree<>("url", url));
+ returnee.add(addee);
+ }
+ else if (tagName.equals("span"))
+ {
+ Tree addee = new Tree<>();
+ addee.key = "text";
+ addee.value = toText(tag);
+ returnee.add(addee);
+ }
+ else if (tagName.equals("br"))
+ {
+ returnee.add(new Tree<>("text", "\n"));
+ }
+ else
+ {
+ for (Tree child: children)
+ {
+ if (!child.key.equals("tag"))
+ {
+ returnee.add(child);
+ continue;
+ }
+ child = turnIntoCommands(child);
+ for (Tree command: child)
+ {
+ returnee.add(command);
+ }
+ }
+ if (tagName.equals("p"))
+ {
+ returnee.add(new Tree<>("text", "\n"));
+ returnee.add(new Tree<>("text", "\n"));
+ }
+ }
+
+ return returnee;
+ }
+
+// ---%-@-%---
+
+ public static class
+ Attribute extends AttributedCharacterIterator.Attribute {
+
+ public static final Attribute
+ IMAGE = new Attribute("IMAGE"),
+ ALT = new Attribute("ALT"),
+ LINK = new Attribute("LINK"),
+ BASICLATIN = new Attribute("BASICLATIN"),
+ Y = new Attribute("Y"),
+ OFFSCREEN = new Attribute("OFFSCREEN"),
+ WHITESPACE = new Attribute("WHITESPACE");
+
+ public static final int
+ COUNT = 7;
+
+// -=%=-
+
+ private
+ Attribute(String name) { super(name); }
+
+ }
+
+// ---%-@-%---
+
+ RichTextPane2()
+ {
+ this.addComponentListener(this);
+ text = new AttributedString("");
+ }
+
+}
\ No newline at end of file
diff --git a/RudimentaryHTMLParser.java b/RudimentaryHTMLParser.java
old mode 100755
new mode 100644
diff --git a/TimelineWindow.java b/TimelineWindow.java
old mode 100755
new mode 100644
diff --git a/TwoToggleButton.java b/TwoToggleButton.java
old mode 100755
new mode 100644
diff --git a/WindowUpdater.java b/WindowUpdater.java
old mode 100755
new mode 100644
diff --git a/notifOptions.txt b/notifOptions.txt
old mode 100755
new mode 100644
diff --git a/notifOptions.txt~ b/notifOptions.txt~
old mode 100755
new mode 100644
diff --git a/run b/run
index 5920a19..3cf3ccb 100755
--- a/run
+++ b/run
@@ -1,13 +1,14 @@
#!/usr/bin/make -f
CLASSPATH=.:../Hinoki:/usr/share/java/javax.json.jar
-OPTIONS=
+COMPILE_OPTIONS=-Xlint:deprecation
+RUNTIME_OPTIONS=-ea
c:
- javac -cp $(CLASSPATH) $(OPTIONS) *.java
+ javac -cp $(CLASSPATH) $(COMPILE_OPTIONS) *.java
r:
- java -cp $(CLASSPATH) $(OPTIONS) -ea JKomasto
+ java -cp $(CLASSPATH) $(RUNTIME_OPTIONS) JKomasto
cr: c r