Skia(Skija)でParagraphを描画する


Skiaでテキストを描画するのにParagraphを利用したい

なぜParagraph?

A. FlutterのText描画に使われているから

環境

  • Kotlin/JVM
  • lwjgl(OpenGLのラッパー)
  • Skija(SkiaのJavaラッパー)
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("jvm") version "1.5.21"
}

group = "dev.fastriver"
version = "1.0-SNAPSHOT"
val skArtifact = "skija-windows"
val skVersion = "0.92.20"
val lwjglVersion = "3.2.3"
val lwjglNatives = "natives-windows"

repositories {
    mavenCentral()
    maven (url = "https://packages.jetbrains.team/maven/p/skija/maven")
}

dependencies {
    testImplementation(kotlin("test"))
    api("org.jetbrains.skija:${skArtifact}:${skVersion}")

    implementation("org.lwjgl:lwjgl:$lwjglVersion")
    implementation("org.lwjgl:lwjgl-glfw:$lwjglVersion")
    implementation("org.lwjgl:lwjgl-opengl:$lwjglVersion")
    runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:$lwjglNatives")
    runtimeOnly("org.lwjgl:lwjgl-glfw:$lwjglVersion:$lwjglNatives")
    runtimeOnly("org.lwjgl:lwjgl-opengl:$lwjglVersion:$lwjglNatives")
}

tasks.test {
    useJUnit()
}

tasks.withType<KotlinCompile>() {
    kotlinOptions.jvmTarget = "1.8"
}

手順

画面表示の下準備

OpenGLの初期化とSkiaのSurfaceを作るまで

main.kt
fun main() {
    val width = 640
    val height = 480

    glfwInit()
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE)
    glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE)
    val windowHandle = glfwCreateWindow(width,height,"Skija Sample", 0, 0)
    glfwMakeContextCurrent(windowHandle)
    glfwSwapInterval(1)
    glfwShowWindow(windowHandle)

    GL.createCapabilities()

    val context = DirectContext.makeGL()

    val fbId = GL11.glGetInteger(0x8CA6)
    val renderTarget = BackendRenderTarget.makeGL(
        width,height,0,8,fbId,FramebufferFormat.GR_GL_RGBA8
    )

    val surface = Surface.makeFromBackendRenderTarget(
        context, renderTarget,
        SurfaceOrigin.BOTTOM_LEFT,
        SurfaceColorFormat.RGBA_8888,
        ColorSpace.getSRGB()
    )

    val canvas = surface.canvas

    while(!glfwWindowShouldClose(windowHandle)) {
        /*
        ここで描画処理 
        */
        context.flush()
        glfwSwapBuffers(windowHandle)
        glfwPollEvents()
    }
}

ParagraphBuilderの作成

ParagraphBuilderの作成にはParagraphStyleとFontCollectionが必要なので同時に作る。FontCollectionはデフォルトに設定(未設定だと描画できない)。ParagraphStyleのプロパティはFlutterのTextの設定に似ている。

これらはcanvasと無関係なのでループ外の記述でOK。

val style = ParagraphStyle().apply {
    alignment = Alignment.START
    direction = Direction.LTR
    textStyle = textStyle.apply {
        fontSize = 50.0f
        maxLinesCount = 2L
        color = 0xFFFF0000.toInt()
    }
}
val font = FontCollection().apply {
    setDefaultFontManager(FontMgr.getDefault())
}
val builder = ParagraphBuilder(style, font)

Paragraphのビルドとレイアウト

  • addText: 表示する文字の指定
  • ParagraphBuilder.build: Paragraphを作成
  • Paragraph.layout: 幅を指定してレイアウト
    • これを忘れるとまた描画されない
builder.addText("Draw Paragraph")
val paragraph = builder.build()
paragraph.layout(800f)

表示

Paragraph.paintでcanvasとOffsetを指定して描画できる。

while(!glfwWindowShouldClose(windowHandle)) {

    paragraph.paint(canvas, 100f,100f)

    context.flush()
    glfwSwapBuffers(windowHandle)
    glfwPollEvents()
}

参考