Added proper code formatting renderer (using font renderer used for screens). Closes #1054.

Fixed text wrapping, strings without "wrap points" caused an infinite loop.
This commit is contained in:
Florian Nücke 2015-04-11 01:28:46 +02:00
parent a1283c9aa2
commit c7dfddd009
6 changed files with 131 additions and 4 deletions

View File

@ -22,7 +22,14 @@ over two* lines. But *this ... no *this is* **_bold italic_** *text*.
`test for code` `test for code`
`that's not code yet` `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*. 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](item:this is broken)
![broken item image](block:this is broken) ![broken item image](block:this is broken)
![broken item image](oredict:this is broken) ![broken item image](oredict:this is broken)
wrap testing
12345678901234567890.1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
`123456789012345678901234567890.12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890`

View File

@ -112,6 +112,33 @@ abstract class TextureFontRenderer {
RenderState.checkError(getClass.getName + ".drawBuffer: leaving") 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 charWidth: Int
protected def charHeight: Int protected def charHeight: Int

View File

@ -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 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 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)) 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( private val segmentTypes = Array(
"""^(#+)\s(.*)""".r -> HeaderSegment _, // headers: # ... """^(#+)\s(.*)""".r -> HeaderSegment _, // headers: # ...
"""(`)(\S.*?\S|$)\1""".r -> ItalicSegment _, // code: `...` """(`)(.*?)\1""".r -> CodeSegment _, // code: `...`
"""!\[([^\[]*)\]\(([^\)]+)\)""".r -> ImageSegment _, // images: ![...](...) """!\[([^\[]*)\]\(([^\)]+)\)""".r -> ImageSegment _, // images: ![...](...)
"""\[([^\[]+)\]\(([^\)]+)\)""".r -> LinkSegment _, // links: [...](...) """\[([^\[]+)\]\(([^\)]+)\)""".r -> LinkSegment _, // links: [...](...)
"""(\*\*|__)(\S.*?\S|$)\1""".r -> BoldSegment _, // bold: **...** | __...__ """(\*\*|__)(\S.*?\S|$)\1""".r -> BoldSegment _, // bold: **...** | __...__

View File

@ -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}"
}

View File

@ -127,7 +127,10 @@ private[markdown] class TextSegment(protected val parent: Segment, val text: Str
while (pos < s.length) { while (pos < s.length) {
pos += 1 pos += 1
val width = (stringWidth(s.take(pos), renderer) * fontScale).toInt 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 if (pos < s.length && breaks.contains(s.charAt(pos))) lastBreak = pos
} }
pos pos

View File

@ -209,7 +209,7 @@ object ModOpenComputers extends ModProxy {
api.Manual.addProvider("oredict", OreDictImageProvider) 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("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*) { private def blacklistHost(host: Class[_], itemNames: String*) {