Lazy Text Tool Plugin and japanese vertical text

Okay, so let me explain how this works. You hook it up to Ten Scripts and can execute it via a shortcut.

When run, it will open up a window asking you to input font name, font size, line width and line height. These are global values for the entire thing. (It is possible to make it none global but that would be more work and I think it would be better spent on the Lazy Text Tool)

You write the text like normal, from left to right, but the output will be how you want it, vertical and right to left.

To edit, text, you have to select it with the Select Shapes Tool

Note: You want to change the Font name in this script defaultFont = 'Open Sans Extrabold' to whatever font you want to use as default (like the japanese font)

Important: This script plays around with the clipboard tricks, aka, it backs up your clipboard, then uses copy and paste for editing internally. So normally it isn’t a problem, just try not to have a million record excel file in the clipboard or something while using.

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import re
import time

def openWindow():
    defaultFont = 'Open Sans Extrabold'
    defaultFontSize = 10.0
    defaultLineWidth = 100
    defaultLineHeight = 100
    defaultTransform = ''   
    
    w = QDialog()
    layout = QVBoxLayout(w)
    
    fontCmb = QFontComboBox()
    layout.addWidget(fontCmb)
   
    hlayout = QHBoxLayout()
    
    fontSize = QDoubleSpinBox()
    hlayout.addWidget(QLabel("Font Size:"))
    hlayout.addWidget(fontSize)

    widthSize = QDoubleSpinBox()
    widthSize.setMaximum(200)

    hlayout.addWidget(QLabel("Line Width:"))
    hlayout.addWidget(widthSize) 

    heightSize = QDoubleSpinBox()
    heightSize.setMaximum(200)

    hlayout.addWidget(QLabel("Line Height:"))
    hlayout.addWidget(heightSize)
    
    layout.addLayout(hlayout)

    textBox = QPlainTextEdit()
    layout.addWidget(textBox)

    hlayout2 = QHBoxLayout()

    button1 = QPushButton("Add Text")
    button1.clicked.connect(w.accept)
    hlayout2.addWidget(button1)

    editContent = readSvgContent()
    #print (editContent)
    if editContent is not None:

        match = re.compile('^.*?\<text.*?id="(vf_.*?)".*$', re.DOTALL).search(editContent)
        
        if match:
            matchData = match.group(1).split('_')
            defaultLineWidth = float(matchData[1])
            defaultLineHeight = float(matchData[2])
            matchTransform = re.compile('^.*?\<text.*?transform="(.*?)".*$', re.DOTALL).search(editContent)
            if matchTransform: defaultTransform = matchTransform.group(1)
            matchFont = re.compile('^.*?\<text.*?font-family="(.*?)".*$', re.DOTALL).search(editContent)
            if matchFont: defaultFont = matchFont.group(1)    
            matchFontSize = re.compile('^.*?\<text.*?font-size="(\d+)".*$', re.DOTALL).search(editContent)
            if matchFontSize: defaultFontSize = float(matchFontSize.group(1))
    
            editContent = editContent.replace('<tspan>','<p>')
            #print (editContent)
            textBox.document().setHtml(editContent)
            button1.setText("Edit Text")
        else:
            editContent = None




    button2 = QPushButton("Cancel")
    button2.clicked.connect(w.reject)
    hlayout2.addWidget(button2)
    
    layout.addLayout(hlayout2) 
    
    def changeFont():
        font = textBox.document().defaultFont()
        font.setFamily(fontCmb.currentFont().family())
        textBox.document().setDefaultFont(font)
        textBox.setPlainText(textBox.toPlainText())

    fontCmb.currentFontChanged.connect(changeFont)

    def changeFontSize():
        font = textBox.document().defaultFont()
        font.setPointSizeF(fontSize.value())
        textBox.document().setDefaultFont(font)
        textBox.setPlainText(textBox.toPlainText())

    fontSize.valueChanged.connect(changeFontSize)

    fontCmb.setCurrentFont(QFont(defaultFont))
    changeFont()
    fontSize.setValue(defaultFontSize)
    changeFontSize()
    widthSize.setValue(defaultLineWidth)
    heightSize.setValue(defaultLineHeight)

    w.show()
    if w.exec_() == 0: return
   
    lines = textBox.toPlainText().split("\n")
    fontWidth = 0
    hPos = 0
    pretty = "\n"

    blockCount = textBox.document().blockCount()
    iblock = textBox.document().begin()

    
    output = '<text '
    output += 'id="vf_'+ str(widthSize.value()) +'_'+ str(heightSize.value()) +'_'+str(time.time())+'" '
    output += 'transform = "'+defaultTransform+'" '
    output += 'font-family="'+fontCmb.currentFont().family()+'" '
    output += 'font-size="'+str(fontSize.value())+'">' + pretty    
    while iblock != textBox.document().end():
        blockText = list(iblock.text())
        blockLineCount = iblock.layout().lineCount()
        fontHeight = 0
        output += '<tspan y="0">' + pretty
        for i in range(blockLineCount):
            line = iblock.layout().lineAt(i)

            for i2 in range(line.textLength()):
                glyph = line.glyphRuns(line.textStart()+i2, 1)[0]
                
                if fontWidth == 0:
                    rawFont = glyph.rawFont()
                    fontWidth = rawFont.averageCharWidth()*2
                    hPos = (blockCount * fontWidth * widthSize.value()) / 100
                    
                r = glyph.boundingRect()
                output += '<tspan dy="'+str( (fontHeight*heightSize.value())/100 )+'" x="'+str(hPos + (( (fontWidth-r.width()/2) * widthSize.value())/100)  )+'">'+blockText[i2]+'</tspan>' + pretty
                fontHeight = r.height() 
       
        iblock = iblock.next()
        hPos -= (fontWidth * widthSize.value()) / 100
        output += '</tspan>' + pretty
    output += '</text>' + pretty    

    
    
    doc = Krita.instance().activeDocument()    

    svgWidth = str( (doc.width()/72)*doc.resolution() )
    svgHeight = str( (doc.height()/72)*doc.resolution() )
 

    svgContent = '''<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<!-- Created using Krita: https://krita.org -->
<svg xmlns="http://www.w3.org/2000/svg" 
    xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:krita="http://krita.org/namespaces/svg/krita"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    width="''' + svgWidth + '''pt"
    height="''' + svgHeight + '''pt"
    viewBox="0 0 ''' + svgWidth + ' ' + svgHeight + '''">
<defs/>''' + output + "</svg>"    
    #print( svgContent )
    writeSvgContent(svgContent, doc.activeNode(), (editContent is not None) )

def writeSvgContent(svgContent, layer, editMode):
                mimeOldContent=QGuiApplication.clipboard().mimeData();
                mimeStoreContent=QMimeData() 
                for mimeType in mimeOldContent.formats(): 
                    mimeStoreContent.setData(mimeType,QByteArray(mimeOldContent.data(mimeType))) 
                if editMode: Krita.instance().action('edit_cut').trigger()
                mimeNewContent=QMimeData()
                mimeNewContent.setData('image/svg', svgContent.encode())
                QGuiApplication.clipboard().setMimeData(mimeNewContent)
                Krita.instance().action('edit_paste').trigger()
                QGuiApplication.clipboard().setMimeData(mimeStoreContent)
                return None

def readSvgContent():
    returnContent = None
    node = Krita.instance().activeDocument().activeNode()
    if node.type() != 'vectorlayer': return None
    mimeOldContent=QGuiApplication.clipboard().mimeData();
    mimeStoreContent=QMimeData() 
    for mimeType in mimeOldContent.formats(): 
        mimeStoreContent.setData(mimeType,QByteArray(mimeOldContent.data(mimeType))) 
                    
    Krita.instance().action('edit_copy').trigger()
    mimeContent=QGuiApplication.clipboard().mimeData();

    for mimeType in mimeContent.formats(): 
        if mimeType.startswith('image/svg'):
            returnContent = str( QByteArray(mimeContent.data(mimeType)) , 'utf-8')
            break
    
    QGuiApplication.clipboard().setMimeData(mimeStoreContent)    
    return returnContent


openWindow()

Now question is, should I add it to ShapesAndLayers plugin :confused:

Well, yes and no. The current SVG system does not support SVG2 so it isn’t convenient, but it is possible and pretty cleanly through utilizing the tspans y, x and dy properties. I even made them all in order so technically the current text tool can add support with little programming.

But it would definitely be nice to get a better fully functioning on canvas text tool.

2 Likes