It’s fine, I need to do it anyways. That said I have a few questions concerning the outcome:
That is at 52.5% height and 75.60% width
It is “near” pixel perfect, just the punctuation is off, and can you explain the ABC stuff and the 3 exclamation marks?
Edit: Also, I had to add an extra square cause space acted weirdly so it needs a workaround. Which isn’t a problem. That said, I’ll probably call it a day here, we are getting pretty close
Here is the latest script:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import re
import time
def openWindow():
defaultFont = 'Source Han Serif Vertical'
defaultFontSize = 10.0
defaultLineWidth = 100
defaultLineHeight = 100
defaultAlign = 'center'
defaultTransform = ''
defaultCharMode = 1
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 y="0"></tspan>','<p> </p>')
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
def align(fw,gw):
if defaultAlign == 'center':
return (fw-gw/2)
elif defaultAlign == 'right':
return (fw-gw/2)
elif defaultAlign == 'left':
return fw
lines = textBox.toPlainText().split("\n")
fontWidth = 0
hPos = 0
pretty = "\n"
blockCount = textBox.document().blockCount()
iblock = textBox.document().begin()
outFontSize = fontSize.value()
metrics = QFontMetricsF(QFont(fontCmb.currentFont().family(), outFontSize))
fontWidth = metrics.averageCharWidth()
hPos = (blockCount * fontWidth * widthSize.value()) / 100
print ( "Font Metrics", metrics.minLeftBearing(), metrics.minRightBearing(), metrics.ascent(), metrics.capHeight() )
print ( metrics.tightBoundingRect('.'), metrics.tightBoundingRect('a'), metrics.averageCharWidth() )
#return
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(outFontSize)+'">' + 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)
storeChar = None
for i2 in range(line.textLength()):
glyph = line.glyphRuns(line.textStart()+i2, 1)[0]
#print ("G", glyph.rawFont().maxCharWidth(), glyph.rawFont().averageCharWidth(), glyph.boundingRect().width(), glyph.boundingRect().height(), blockText[i2], glyph.glyphIndexes(), glyph.rawFont().glyphIndexesForString(blockText[i2]) )
#return
rawFont = glyph.rawFont()
r = glyph.boundingRect()
r = QRectF( 0,0, r.width()*(outFontSize/10), r.height()*(outFontSize/10) )
print ( blockText[i2], r, metrics.averageCharWidth() )
#r = glyph.boundingRect()
#if defaultCharMode == 0 or r.width() > rawFont.averageCharWidth()/1.5:
if defaultCharMode == 0 or r.width() > metrics.averageCharWidth()/1.5:
if storeChar is not None:
output += '<tspan dy="'+str( (fontHeight*heightSize.value())/100 )+'" x="'+str(hPos + (( align(fontWidth,storeChar[1].width()) * widthSize.value())/100) )+'">'+storeChar[0]+'</tspan>' + pretty
output += '<tspan dy="'+str( (fontHeight*heightSize.value())/100 )+'" x="'+str(hPos + (( align(fontWidth,r.width()) * widthSize.value())/100) )+'">'+blockText[i2]+'</tspan>' + pretty
storeChar = None
elif storeChar is None:
storeChar = [blockText[i2],r]
else:
output += '<tspan dy="'+str( (fontHeight*heightSize.value())/100 )+'" x="'+str(hPos + (( align(fontWidth, r.width()+storeChar[1].width() ) * widthSize.value())/100) )+'">'+storeChar[0]+blockText[i2]+'</tspan>' + pretty
storeChar = None
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()