From c7dfddd009804beb55a0da5abbcd6a18cef3ae19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 11 Apr 2015 01:28:46 +0200 Subject: [PATCH] Added proper code formatting renderer (using font renderer used for screens). Closes #1054. Fixed text wrapping, strings without "wrap points" caused an infinite loop. --- .../doc/en_US/general/example.md | 13 ++- .../renderer/font/TextureFontRenderer.scala | 27 ++++++ .../client/renderer/markdown/Document.scala | 4 +- .../markdown/segment/CodeSegment.scala | 84 +++++++++++++++++++ .../markdown/segment/TextSegment.scala | 5 +- .../opencomputers/ModOpenComputers.scala | 2 +- 6 files changed, 131 insertions(+), 4 deletions(-) create mode 100644 src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala diff --git a/src/main/resources/assets/opencomputers/doc/en_US/general/example.md b/src/main/resources/assets/opencomputers/doc/en_US/general/example.md index 108016eba..8044e0ecd 100644 --- a/src/main/resources/assets/opencomputers/doc/en_US/general/example.md +++ b/src/main/resources/assets/opencomputers/doc/en_US/general/example.md @@ -22,7 +22,14 @@ over two* lines. But *this ... no *this is* **_bold italic_** *text*. `test for code` `that's not code yet` -this is some `code` that's inline. +`function f(a)` +` testingIndent(a)` +` do` +` lalala()` +` end` +`end` +yeah, line spacing is a bit low, but otherwise too little text fits on one screen. +this is some `code` that's inline. then `some more CODE that` line wraps and so on. isn't*. @@ -35,3 +42,7 @@ And finally, [this is a link!](https://avatars1.githubusercontent.com/u/514903). ![broken item image](item:this is broken) ![broken item image](block:this is broken) ![broken item image](oredict:this is broken) + +wrap testing +12345678901234567890.1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +`123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890` \ No newline at end of file diff --git a/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala index b1aef75f7..2c5571bca 100644 --- a/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/font/TextureFontRenderer.scala @@ -112,6 +112,33 @@ abstract class TextureFontRenderer { RenderState.checkError(getClass.getName + ".drawBuffer: leaving") } + def drawString(s: String, x: Int, y: Int): Unit = { + GL11.glPushMatrix() + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS) + + GL11.glTranslatef(x, y, 0) + GL11.glScalef(0.5f, 0.5f, 1) + GL11.glDepthMask(false) + + for (i <- 0 until textureCount) { + bindTexture(i) + GL11.glBegin(GL11.GL_QUADS) + var tx = 0f + for (n <- 0 until s.length) { + val ch = s.charAt(n) + // Don't render whitespace. + if (ch != ' ') { + drawChar(tx, 0, ch) + } + tx += charWidth + } + GL11.glEnd() + } + + GL11.glPopAttrib() + GL11.glPopMatrix() + } + protected def charWidth: Int protected def charHeight: Int diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/Document.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/Document.scala index 0a82eba25..a138b51d9 100644 --- a/src/main/scala/li/cil/oc/client/renderer/markdown/Document.scala +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/Document.scala @@ -106,6 +106,8 @@ object Document { private def HeaderSegment(s: Segment, m: Regex.Match) = new segment.HeaderSegment(s, m.group(2), m.group(1).length) + private def CodeSegment(s: Segment, m: Regex.Match) = new segment.CodeSegment(s, m.group(2)) + private def LinkSegment(s: Segment, m: Regex.Match) = new segment.LinkSegment(s, m.group(1), m.group(2)) private def BoldSegment(s: Segment, m: Regex.Match) = new segment.BoldSegment(s, m.group(2)) @@ -127,7 +129,7 @@ object Document { private val segmentTypes = Array( """^(#+)\s(.*)""".r -> HeaderSegment _, // headers: # ... - """(`)(\S.*?\S|$)\1""".r -> ItalicSegment _, // code: `...` + """(`)(.*?)\1""".r -> CodeSegment _, // code: `...` """!\[([^\[]*)\]\(([^\)]+)\)""".r -> ImageSegment _, // images: ![...](...) """\[([^\[]+)\]\(([^\)]+)\)""".r -> LinkSegment _, // links: [...](...) """(\*\*|__)(\S.*?\S|$)\1""".r -> BoldSegment _, // bold: **...** | __...__ diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala new file mode 100644 index 000000000..8bf4d0d85 --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala @@ -0,0 +1,84 @@ +package li.cil.oc.client.renderer.markdown.segment + +import li.cil.oc.client.renderer.TextBufferRenderCache +import li.cil.oc.client.renderer.markdown.Document +import net.minecraft.client.gui.FontRenderer +import org.lwjgl.opengl.GL11 + +private[markdown] class CodeSegment(protected val parent: Segment, val text: String) extends Segment { + override def height(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = { + var lines = 0 + var chars = text + var lineChars = maxChars(chars, maxWidth - indent) + while (chars.length > lineChars) { + lines += 1 + chars = chars.drop(lineChars).dropWhile(_.isWhitespace) + lineChars = maxChars(chars, maxWidth) + } + lines * Document.lineHeight(renderer) + } + + override def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = { + var currentX = indent + var chars = text + if (indent == 0) chars = chars.dropWhile(_.isWhitespace) + var lineChars = maxChars(chars, maxWidth - indent) + while (chars.length > lineChars) { + chars = chars.drop(lineChars).dropWhile(_.isWhitespace) + lineChars = maxChars(chars, maxWidth) + currentX = 0 + } + currentX + stringWidth(chars) + } + + override def render(x: Int, y: Int, indent: Int, maxWidth: Int, minY: Int, maxY: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = { + TextBufferRenderCache.renderer.generateChars(text.toCharArray) + + var currentX = x + indent + var currentY = y + var chars = text + var numChars = maxChars(chars, maxWidth - indent) + while (chars.length > 0 && (currentY - y) < maxY) { + val part = chars.take(numChars).reverse.dropWhile(_.isWhitespace).reverse + GL11.glColor4f(0.75f, 0.8f, 1, 1) + TextBufferRenderCache.renderer.drawString(part, currentX, currentY) + currentX = x + currentY += Document.lineHeight(renderer) + chars = chars.drop(numChars).dropWhile(_.isWhitespace) + numChars = maxChars(chars, maxWidth) + } + + None + } + + private def drawBox(x: Int, y: Int, width: Int, height: Int): Unit = { + GL11.glDisable(GL11.GL_TEXTURE_2D) + GL11.glBegin(GL11.GL_QUADS) + GL11.glVertex2f(x, y) + GL11.glVertex2f(x, y + height) + GL11.glVertex2f(x + width, y + height) + GL11.glVertex2f(x + width, y) + GL11.glEnd() + GL11.glEnable(GL11.GL_TEXTURE_2D) + } + + private def stringWidth(s: String): Int = s.length * TextBufferRenderCache.renderer.charRenderWidth + + private def maxChars(s: String, maxWidth: Int): Int = { + val breaks = Set(' ', '-', '.', '+', '*', '_', '/') + var pos = 0 + var lastBreak = -1 + while (pos < s.length) { + pos += 1 + val width = stringWidth(s.take(pos)) + if (width >= maxWidth) { + if (lastBreak > 0 || stringWidth(s) <= maxWidth) return lastBreak + 1 + else return pos - 1 + } + if (pos < s.length && breaks.contains(s.charAt(pos))) lastBreak = pos + } + pos + } + + override def toString: String = s"{CodeSegment: text = $text}" +} diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala index f37dadc12..f7cb1ec0b 100644 --- a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala @@ -127,7 +127,10 @@ private[markdown] class TextSegment(protected val parent: Segment, val text: Str while (pos < s.length) { pos += 1 val width = (stringWidth(s.take(pos), renderer) * fontScale).toInt - if (width >= maxWidth) return lastBreak + 1 + if (width >= maxWidth) { + if (lastBreak > 0 || stringWidth(s, renderer) <= maxWidth) return lastBreak + 1 + else return pos - 1 + } if (pos < s.length && breaks.contains(s.charAt(pos))) lastBreak = pos } pos diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala index 8a9337709..a05991463 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -209,7 +209,7 @@ object ModOpenComputers extends ModProxy { api.Manual.addProvider("oredict", OreDictImageProvider) api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("case1").createItemStack(1)), "oc:gui.Manual.Blocks", "%LANGUAGE%/block/index.md") - api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("chip1").createItemStack(1)), "oc:gui.Manual.Items", "%LANGUAGE%/item/index.md") + api.Manual.addTab(new ItemStackTabIconRenderer(api.Items.get("cpu1").createItemStack(1)), "oc:gui.Manual.Items", "%LANGUAGE%/item/index.md") } private def blacklistHost(host: Class[_], itemNames: String*) {