changeset 56:22725d4d7849

An attempt to get it to troff-ize styled text.
author David Barts <n5jrn@me.com>
date Sat, 19 Mar 2022 23:04:40 -0700
parents 7ad2b29a7f60
children c6cccbe2f393
files setup.sh src/name/blackcap/clipman/CoerceDialog.kt src/name/blackcap/clipman/Menus.kt src/name/blackcap/clipman/Troff.kt
diffstat 4 files changed, 132 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/setup.sh	Tue Apr 13 10:34:51 2021 -0700
+++ b/setup.sh	Sat Mar 19 23:04:40 2022 -0700
@@ -1,7 +1,11 @@
 #!/bin/bash
 
 export JRE_HOME="$(/usr/libexec/java_home)"
-export KOTLIN_HOME="/usr/local/Cellar/kotlin/1.3.71/libexec"
+export KOTLIN_HOME="/Users/davidb/kotlin/1.6.10/kotlinc"
+if [[ "$PATH" != *$KOTLIN_HOME/bin* ]]
+then
+    export PATH="$KOTLIN_HOME/bin:$PATH"
+fi
 
 export ANT_HOME="$HOME/java/apache-ant-1.10.1"
 if [[ "$PATH" != *$ANT_HOME/bin* ]]
--- a/src/name/blackcap/clipman/CoerceDialog.kt	Tue Apr 13 10:34:51 2021 -0700
+++ b/src/name/blackcap/clipman/CoerceDialog.kt	Sat Mar 19 23:04:40 2022 -0700
@@ -162,36 +162,28 @@
 
     private fun coerce() {
         val selected = Application.queue.getSelected()
+        if (!suitedForCoercing(selected)) {
+            return
+        }
         if (selected == null) {
-            JOptionPane.showMessageDialog(Application.frame,
-                "No item selected.",
-                "Error",
-                JOptionPane.ERROR_MESSAGE)
-        } else {
-            val (plain, html) = when (selected.contents) {
-                is PasteboardItem.Plain ->
-                    Pair(selected.contents.plain, null)
-                is PasteboardItem.HTML ->
-                    Pair(selected.contents.plain, selected.contents.html)
-                is PasteboardItem.RTF ->
-                    Pair(selected.contents.plain, selected.contents.html)
-            }
-            if (html == null) {
-                JOptionPane.showMessageDialog(Application.frame,
-                    "Only styled texts may be coerced.",
-                    "Error",
-                    JOptionPane.ERROR_MESSAGE)
-            } else {
-                if (badSize(_pSize, PROP_SIZE, "proportionally-spaced") || badSize(_mSize, MONO_SIZE, "monospaced")) {
-                    return
-                }
-                PasteboardItem.write(
-                    PasteboardItem.HTML(
-                        plain,
-                        coerceHTML(html, normalizeFont(pFamily), pSize,
-                            normalizeFont(mFamily), mSize)))
-            }
+            return /* redundant, but makes kotlinc happy */
+        }
+        if (badSize(_pSize, PROP_SIZE, "proportionally-spaced") || badSize(_mSize, MONO_SIZE, "monospaced")) {
+            return
         }
+        val (plain, html) = when (selected.contents) {
+            is PasteboardItem.Plain ->
+                Pair(selected.contents.plain, null)
+            is PasteboardItem.HTML ->
+                Pair(selected.contents.plain, selected.contents.html)
+            is PasteboardItem.RTF ->
+                Pair(selected.contents.plain, selected.contents.html)
+        }
+        PasteboardItem.write(
+            PasteboardItem.HTML(
+                plain,
+                coerceHTML(html!!, normalizeFont(pFamily), pSize,
+                    normalizeFont(mFamily), mSize)))
     }
 
     private fun badSize(control: JComboBox<Float>, default: Int, fontType: String): Boolean {
@@ -226,3 +218,25 @@
         }
     }
 }
+
+/**
+ * See if the selected pasteboard item is suitable for coercing. If not,
+ * issue an error dialog.
+ */
+fun suitedForCoercing(selected: QueueItem?): Boolean {
+    if (selected == null) {
+        JOptionPane.showMessageDialog(Application.frame,
+            "No item selected.",
+            "Error",
+            JOptionPane.ERROR_MESSAGE)
+        return false
+    }
+    if (selected.contents is PasteboardItem.Plain) {
+        JOptionPane.showMessageDialog(Application.frame,
+            "Only styled texts may be coerced.",
+            "Error",
+            JOptionPane.ERROR_MESSAGE)
+        return false
+    }
+    return true
+}
--- a/src/name/blackcap/clipman/Menus.kt	Tue Apr 13 10:34:51 2021 -0700
+++ b/src/name/blackcap/clipman/Menus.kt	Sat Mar 19 23:04:40 2022 -0700
@@ -28,6 +28,7 @@
             "Edit.Coerce" -> onlyIfSelected { Application.coerceDialog.setVisible(true) }
             "Edit.Find" -> Application.searchDialog.setVisible(true)
             "Edit.FindAgain" -> Application.searchDialog.find()
+            "Edit.Troff" -> onlyIfSelected { if (suitedForCoercing(it)) { troffize(it.contents) } }
             "Help.About" -> showAboutDialog()
             else -> throw RuntimeException("unexpected actionCommand!")
         }
@@ -102,6 +103,12 @@
                 addActionListener(Application.menuItemListener)
                 makeShortcut(KeyEvent.VK_K)
             }))
+            add(Application.styledRequired.add(JMenuItem("Convert to Troff").apply {
+                setEnabled(false)
+                actionCommand = "Edit.Troff"
+                addActionListener(Application.menuItemListener)
+                makeShortcut(KeyEvent.VK_T)
+            }))
             add(JMenuItem("Find…").apply {
                 actionCommand = "Edit.Find"
                 addActionListener(Application.menuItemListener)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/name/blackcap/clipman/Troff.kt	Sat Mar 19 23:04:40 2022 -0700
@@ -0,0 +1,78 @@
+/*
+ * Coercion to troff input.
+ */
+package name.blackcap.clipman
+
+import org.jsoup.Jsoup
+import org.jsoup.nodes.*
+import org.jsoup.select.NodeVisitor
+
+class Troffizer: NodeVisitor {
+    private enum class Typeface(val pos: Int) {
+        ROMAN(1),
+        ITALIC(2),
+        BOLD(3)
+    }
+
+    private val tfStack = mutableListOf<Typeface>(Typeface.ROMAN);
+
+    private val TF_TAGS = mapOf<String, Typeface>(
+        "em" to Typeface.ITALIC,
+        "i" to Typeface.ITALIC,
+        "b" to Typeface.BOLD,
+        "strong" to Typeface.BOLD)
+
+    private val accum = StringBuilder();
+
+    override fun head(node: Node, depth: Int): Unit {
+        when (node) {
+            is TextNode -> accum.append(node.text())
+            is Element -> enterElement(node)
+        }
+    }
+
+    override fun tail(node: Node, depth: Int): Unit {
+        if (node is Element) {
+            leaveElement(node)
+        }
+    }
+
+    private fun enterElement(element: Element) {
+        var newFace = TF_TAGS[element.normalName()]
+        if (newFace != null) {
+            tfStack.add(newFace)
+            accum.append("\\f")
+            accum.append(newFace.pos)
+        }
+    }
+
+    private fun leaveElement(element: Element) {
+        if (element.normalName() in TF_TAGS) {
+            tfStack.removeLast()
+            accum.append("\\f")
+            accum.append(tfStack.lastOrNull()?.pos ?: Typeface.ROMAN.pos)
+        }
+    }
+
+    fun getTroff(): String = accum.toString()
+}
+
+private fun _troffize(html: String): String {
+    val troffizer = Troffizer()
+    Jsoup.parse(html).traverse(troffizer)
+    return troffizer.getTroff()
+}
+
+fun troffize(item: PasteboardItem): Unit {
+    val (plain, html) = when (item) {
+        is PasteboardItem.Plain ->
+            Pair(item.plain, null)
+        is PasteboardItem.HTML ->
+            Pair(item.plain, item.html)
+        is PasteboardItem.RTF ->
+            Pair(item.plain, item.html)
+    }
+    PasteboardItem.write(
+        PasteboardItem.Plain(
+            if (html == null) { plain } else { _troffize(html) }))
+}