Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ object RenderOp:
def clip(rect: Rect): DrawText = copy(area = area & rect)

/** Converts a DrawText operation into a sequence of simpler DrawChar operations. */
def asDrawChars: List[DrawChar] = TextLayout.asDrawChars(this)
def asDrawChars: List[DrawChar] = TextLayout.asDrawCharsIter(this).toList

/** Converts a DrawText operation into a sequence of simpler DrawChar operations and applies a function to each one. */
def foreachDrawChar(f: DrawChar => Unit): Unit = TextLayout.asDrawCharsIter(this).foreach(f)

/** Operation to draw a custom element on the screen
*
Expand Down
35 changes: 19 additions & 16 deletions core/shared/src/main/scala/eu/joaocosta/interim/TextLayout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,41 @@ object TextLayout:
val spaceCharWidth = charWidth(' ')
val (selectedWords, remainingWords) =
cumulativeSum(words)(spaceCharWidth + textSize(_)).span(_._2 <= lineSize)
val lineStr = selectedWords.map(_._1).mkString("")
val remainderStr = remainingWords.map(_._1).mkString("")
val lineStr = selectedWords.map(_._1).mkString(" ")
val remainderStr = remainingWords.map(_._1).mkString(" ")
(lineStr, remainderStr ++ remainingLines)

private def alignH(
chars: List[RenderOp.DrawChar],
charsIt: Iterator[RenderOp.DrawChar],
areaWidth: Int,
alignment: HorizontalAlignment
): Iterator[RenderOp.DrawChar] =
if (chars.isEmpty) Iterator.empty
if (charsIt.isEmpty) Iterator.empty
else if (alignment.ordinal == 0) charsIt
else
val chars = charsIt.toList
val minX = chars.foldLeft(Int.MaxValue)((acc, c) => math.min(acc, c.area.x))
val maxX = chars.foldLeft(0)((acc, c) => math.max(acc, c.area.x + c.area.w))
val deltaX = alignment.ordinal * (areaWidth - (maxX - minX)) / 2
chars.iterator.map(c => c.copy(area = c.area.copy(x = c.area.x + deltaX)))

private def alignV(
chars: List[RenderOp.DrawChar],
charsIt: Iterator[RenderOp.DrawChar],
areaHeight: Int,
alignment: VerticalAlignment
): Iterator[RenderOp.DrawChar] =
if (chars.isEmpty) Iterator.empty
if (charsIt.isEmpty) Iterator.empty
else if (alignment.ordinal == 0) charsIt
else
val chars = charsIt.toList
val minY = chars.foldLeft(Int.MaxValue)((acc, c) => math.min(acc, c.area.y))
val maxY = chars.foldLeft(0)((acc, c) => math.max(acc, c.area.y + c.area.h))
val deltaY = alignment.ordinal * (areaHeight - (maxY - minY)) / 2
chars.iterator.map(c => c.copy(area = c.area.copy(y = c.area.y + deltaY)))

private[interim] def asDrawChars(
private[interim] def asDrawCharsIter(
textOp: RenderOp.DrawText
): List[RenderOp.DrawChar] =
): Iterator[RenderOp.DrawChar] =
@tailrec
def layout(
remaining: String,
Expand All @@ -68,15 +72,15 @@ object TextLayout:
remaining match
case "" =>
alignV(
textAcc.flatten,
textAcc.iterator.flatten,
textOp.textArea.h,
textOp.verticalAlignment
)
case str =>
if (dy + textOp.font.fontSize > textOp.textArea.h) layout("", dy, textAcc) // Can't fit this line, end here
else
val (thisLine, nextLine) = getNextLine(str, textOp.textArea.w, textOp.font.charWidth)
val ops = cumulativeSum(thisLine)(textOp.font.charWidth).iterator.map { case (char, dx) =>
val ops = cumulativeSum(thisLine)(textOp.font.charWidth).map { case (char, dx) =>
val width = textOp.font.charWidth(char)
val charArea = Rect(
x = textOp.textArea.x + dx - width,
Expand All @@ -91,9 +95,9 @@ object TextLayout:
layout(
nextLine,
dy + textOp.font.lineHeight,
alignH(ops.toList, textOp.textArea.w, textOp.horizontalAlignment) :: textAcc
alignH(ops, textOp.textArea.w, textOp.horizontalAlignment) :: textAcc
)
layout(textOp.text, 0, Nil).filter(char => (char.area & textOp.area) == char.area).toList
layout(textOp.text, 0, Nil).filter(char => (char.area & textOp.area) == char.area)

/** Computes the area that some text will occupy
*
Expand All @@ -118,15 +122,14 @@ object TextLayout:
if (dy + font.fontSize > boundingArea.h) layout("", dy, areaAcc) // Can't fit this line, end here
else
val (thisLine, nextLine) = getNextLine(str, boundingArea.w, font.charWidth)
val charAreas = cumulativeSum(thisLine)(font.charWidth).iterator.map { case (char, dx) =>
val width = font.charWidth(char)
val charArea = Rect(
val charAreas = cumulativeSum(thisLine)(font.charWidth).map { case (char, dx) =>
val width = font.charWidth(char)
Rect(
x = boundingArea.x + dx - width,
y = boundingArea.y + dy,
w = width,
h = font.fontSize
)
charArea
}
if (charAreas.isEmpty && nextLine == remaining)
layout("", dy, areaAcc) // Can't fit a single character, end here
Expand Down
2 changes: 1 addition & 1 deletion examples/snapshot/example-minart-backend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ object MinartBackend:
canvas.fillRegion(x, y, w, h, MinartColor(color.r, color.g, color.b))
case op: RenderOp.DrawText =>
val font = gloop.withSize(op.font.fontSize)
op.asDrawChars.foreach { case RenderOp.DrawChar(Rect(x, y, _, _), color, char) =>
op.foreachDrawChar { case RenderOp.DrawChar(Rect(x, y, _, _), color, char) =>
val charSprite = font.coloredChar(char, MinartColor(color.r, color.g, color.b))
canvas
.blit(charSprite, BlendMode.ColorMask(MinartColor(255, 0, 255)))(x, y)
Expand Down