|
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
########################################################################
|
|
# version 1.0.0
|
|
#
|
|
# GimpPythonFuVerticalWriting-2-8.py
|
|
# Copyright (C) 2013 かんら・から http://www.pixiv.net/member.php?id=3098715
|
|
#
|
|
# GimpPythonFuVerticalWriting-2-8.py is Python-fu plugin for GIMP 2.8
|
|
#
|
|
# GimpPythonFuVerticalWriting-2-8.py is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU General Public License as published by the
|
|
# Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# GimpPythonFuVerticalWriting-2-8.py is distributed in the hope that it will be useful, but
|
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along
|
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# GPLv3 ライセンス
|
|
# かんら・から http://www.pixiv.net/member.php?id=3098715
|
|
# バグレポート・アイデアなどは pixiv メッセージでお願いします。
|
|
#
|
|
# ダウンロード
|
|
# http://www.magic-object.mydns.jp/
|
|
#
|
|
# このスクリプトを使用して発生した問題に関し、作成者は如何なる保証などを行う事はありません。
|
|
# 自己責任でのみの使用を許可します。
|
|
########################################################################
|
|
from gimpfu import *
|
|
import gimpcolor
|
|
import gimpui
|
|
|
|
import gtk
|
|
import gobject
|
|
import re
|
|
import math
|
|
import xml.dom.minidom
|
|
# import chardet
|
|
import os
|
|
import ConfigParser
|
|
|
|
import pygtk
|
|
|
|
pygtk.require('2.0')
|
|
|
|
|
|
########################################################################
|
|
########################################################################
|
|
# Pangoクラス
|
|
#
|
|
# Pango を操作する
|
|
#
|
|
########################################################################
|
|
class PangoAccess:
|
|
########################################################################
|
|
document = None
|
|
fontList = []
|
|
lines = []
|
|
########################################################################
|
|
defaultFont = 'Sans'
|
|
|
|
########################################################################
|
|
def __init__(self, markUp, defaultText=None):
|
|
|
|
if markUp:
|
|
markUp = markUp.rstrip()
|
|
try:
|
|
self.document = xml.dom.minidom.parseString(markUp)
|
|
except:
|
|
self.document = xml.dom.minidom.parseString('<markup></markup>')
|
|
if defaultText:
|
|
defaultText = defaultText.rstrip()
|
|
textNode = self.document.createTextNode(defaultText)
|
|
self.document.firstChild.appendChild(textNode)
|
|
elif markUp:
|
|
textNode = self.document.createTextNode(markUp)
|
|
self.document.firstChild.appendChild(textNode)
|
|
else:
|
|
self.document = xml.dom.minidom.parseString('<markup></markup>')
|
|
if defaultText:
|
|
textNode = self.document.createTextNode(defaultText)
|
|
self.document.firstChild.appendChild(textNode)
|
|
self.parse()
|
|
|
|
########################################################################
|
|
def __del__(self):
|
|
if self.document:
|
|
self.document.unlink()
|
|
|
|
########################################################################
|
|
def getAllText(self):
|
|
if not self.document:
|
|
return ''
|
|
########################################################################
|
|
return self.getTextNodeValue(self.document.firstChild)
|
|
|
|
########################################################################
|
|
def toUnicode(self, text):
|
|
if isinstance(text, unicode):
|
|
return text
|
|
if not isinstance(text, str):
|
|
text = str(text)
|
|
# codecInfo = chardet.detect( text )
|
|
# return unicode( text, codecInfo['encoding'] )
|
|
try:
|
|
return unicode(text, 'utf-8')
|
|
except:
|
|
pass
|
|
try:
|
|
return unicode(text, 'euc-jp')
|
|
except:
|
|
pass
|
|
try:
|
|
return unicode(text, 'cp932')
|
|
except:
|
|
return text
|
|
|
|
########################################################################
|
|
def getTextNodeValue(self, element):
|
|
if not element:
|
|
return ''
|
|
elif element.nodeType == element.TEXT_NODE:
|
|
return element.nodeValue
|
|
elif element.nodeType != element.ELEMENT_NODE:
|
|
return ''
|
|
########################################################################
|
|
text = ''
|
|
for node in element.childNodes:
|
|
if node.hasChildNodes():
|
|
text += self.getTextNodeValue(node)
|
|
elif node.nodeType == node.TEXT_NODE:
|
|
text += node.nodeValue
|
|
########################################################################
|
|
return text
|
|
|
|
########################################################################
|
|
def setFontList(self, fontList):
|
|
self.fontList = sorted(fontList)
|
|
|
|
########################################################################
|
|
def getFontName(self, name, bold=False, italic=False):
|
|
if name not in self.fontList:
|
|
return name
|
|
########################################################################
|
|
regExpBoldItalic = re.compile('( bold italic$)|( italic bold$)|(-P?BI?$)', re.IGNORECASE)
|
|
regExpBold = re.compile('( bold$)|(-P?B$)', re.IGNORECASE)
|
|
regExpItalic = re.compile('( italic$)|(-I$)', re.IGNORECASE)
|
|
if bold and italic:
|
|
if regExpBoldItalic.search(name):
|
|
return name
|
|
elif bold:
|
|
if regExpBold.search(name):
|
|
return name
|
|
elif italic:
|
|
if regExpItalic.search(name):
|
|
return name
|
|
########################################################################
|
|
orgFontName = re.sub('-[PBSI]+$', '', name)
|
|
|
|
regExpBoldItalic = re.compile('^(' + re.escape(orgFontName) + '\\s*)(' + regExpBoldItalic.pattern + ')',
|
|
regExpBoldItalic.flags)
|
|
regExpBold = re.compile('^(' + re.escape(orgFontName) + '\\s*)(' + regExpBold.pattern + ')', regExpBold.flags)
|
|
regExpItalic = re.compile('^(' + re.escape(orgFontName) + '\\s*)(' + regExpItalic.pattern + ')',
|
|
regExpItalic.flags)
|
|
########################################################################
|
|
for fontName in self.fontList:
|
|
if bold and italic:
|
|
if regExpBoldItalic.search(fontName):
|
|
return fontName
|
|
elif bold:
|
|
if regExpBold.search(fontName):
|
|
return fontName
|
|
elif italic:
|
|
if regExpItalic.search(fontName):
|
|
return fontName
|
|
########################################################################
|
|
return name
|
|
|
|
########################################################################
|
|
def setDefaultFont(self, font):
|
|
self.defaultFont = font
|
|
|
|
########################################################################
|
|
def parse(self):
|
|
self.lines = []
|
|
if not self.document:
|
|
return self.lines
|
|
########################################################################
|
|
nodeList = self.document.firstChild.childNodes
|
|
for node in nodeList:
|
|
self.parseNode(node, self.defaultFont, False, False, {})
|
|
|
|
########################################################################
|
|
def parseNode(self, node, font=None, bold=False, italic=False, attribute={}):
|
|
if not node:
|
|
return
|
|
if not isinstance(node, xml.dom.minidom.Node):
|
|
return
|
|
if node.nodeType == node.TEXT_NODE:
|
|
self.wordAdd(node.nodeValue, font, bold, italic, attribute)
|
|
return
|
|
elif node.nodeType == node.ELEMENT_NODE:
|
|
if not node.hasChildNodes():
|
|
return
|
|
elif node.tagName.lower() == 'b':
|
|
bold = True
|
|
elif node.tagName.lower() == 'i':
|
|
italic = True
|
|
elif node.tagName.lower() == 's':
|
|
attribute['strikethrough'] = True
|
|
elif node.tagName.lower() == 'u':
|
|
attribute['underline'] = 'single'
|
|
elif node.tagName.lower() == 'span':
|
|
if node.attributes:
|
|
for index in range(node.attributes.length):
|
|
attrNode = node.attributes.item(index)
|
|
if attrNode.name.lower() in ('font', 'font_desc'):
|
|
font = attrNode.nodeValue
|
|
elif attrNode.name.lower() in ('size', 'font_size'):
|
|
if attrNode.nodeValue.isdigit():
|
|
attribute['point'] = int(attrNode.nodeValue) / 1024.0
|
|
elif attrNode.name.lower() == 'xx-large':
|
|
attribute['point'] = 32
|
|
elif attrNode.name.lower() == 'x-large':
|
|
attribute['point'] = 18
|
|
elif attrNode.name.lower() == 'large':
|
|
attribute['point'] = 19.2
|
|
elif attrNode.name.lower() == 'midium':
|
|
attribute['point'] = 16
|
|
elif attrNode.name.lower() == 'small':
|
|
attribute['point'] = 14.2
|
|
elif attrNode.name.lower() == 'x-small':
|
|
attribute['point'] = 12
|
|
elif attrNode.name.lower() == 'xx-small':
|
|
attribute['point'] = 9.6
|
|
else:
|
|
attribute['size'] = attrNode.nodeValue
|
|
elif attrNode.name.lower() in ('foreground', 'fgcolor', 'color'):
|
|
|
|
color = {'red': 0.0, 'green': 0.0, 'blue': 0.0}
|
|
|
|
if attrNode.nodeValue.startswith('#'):
|
|
match = re.match('^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$',
|
|
attrNode.nodeValue)
|
|
if match:
|
|
color['red'] = int(match.group(1), 16) / 255.0
|
|
color['green'] = int(match.group(2), 16) / 255.0
|
|
color['blue'] = int(match.group(3), 16) / 255.0
|
|
elif re.match('^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$', attrNode.nodeValue):
|
|
match = re.match('^#([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])$', attrNode.nodeValue)
|
|
color['red'] = int(match.group(1), 16) / 15.0
|
|
color['green'] = int(match.group(2), 16) / 15.0
|
|
color['blue'] = int(match.group(3), 16) / 15.0
|
|
elif attrNode.nodeValue.lower() == 'white':
|
|
color['red'] = 1.0
|
|
color['green'] = 1.0
|
|
color['blue'] = 1.0
|
|
elif attrNode.nodeValue.lower() == 'black':
|
|
color['red'] = 0.0
|
|
color['green'] = 0.0
|
|
color['blue'] = 0.0
|
|
elif attrNode.nodeValue.lower() == 'gray':
|
|
color['red'] = 0.5
|
|
color['green'] = 0.5
|
|
color['blue'] = 0.5
|
|
elif attrNode.nodeValue.lower() == 'red':
|
|
color['red'] = 1.0
|
|
color['green'] = 0.0
|
|
color['blue'] = 0.0
|
|
elif attrNode.nodeValue.lower() == 'green':
|
|
color['red'] = 0.0
|
|
color['green'] = 1.0
|
|
color['blue'] = 0.0
|
|
elif attrNode.nodeValue.lower() == 'blue':
|
|
color['red'] = 0.0
|
|
color['green'] = 0.0
|
|
color['blue'] = 1.0
|
|
elif attrNode.nodeValue.lower() == 'cyan':
|
|
color['red'] = 0.0
|
|
color['green'] = 1.0
|
|
color['blue'] = 1.0
|
|
elif attrNode.nodeValue.lower() == 'magenta':
|
|
color['red'] = 1.0
|
|
color['green'] = 0.0
|
|
color['blue'] = 1.0
|
|
elif attrNode.nodeValue.lower() == 'yellow':
|
|
color['red'] = 1.0
|
|
color['green'] = 1.0
|
|
color['blue'] = 0.0
|
|
|
|
attribute['color'] = color
|
|
else:
|
|
attribute[attrNode.name.lower()] = attrNode.nodeValue.lower()
|
|
|
|
for child in node.childNodes:
|
|
self.parseNode(child, font, bold, italic, attribute)
|
|
return
|
|
|
|
########################################################################
|
|
########################################################################
|
|
def wordAdd(self, text, font=None, bold=False, italic=False, attribute={}):
|
|
########################################################################
|
|
if re.search('\\r?\\n', text):
|
|
lfCount = len(re.findall('\\r?\\n', text))
|
|
restLf = lfCount
|
|
for line in re.split('\\r?\\n', text):
|
|
self.wordAdd(line, font, bold, italic, attribute)
|
|
if restLf > 0:
|
|
self.lines.append([])
|
|
restLf -= 1
|
|
return
|
|
########################################################################
|
|
if self.lines is None or not isinstance(self.lines, list):
|
|
self.lines = [[]]
|
|
########################################################################
|
|
lineIndex = len(self.lines) - 1
|
|
if lineIndex < 0:
|
|
self.lines.append([])
|
|
lineIndex = 0
|
|
elif self.lines[lineIndex] is None or not isinstance(self.lines[lineIndex], list):
|
|
self.lines[lineIndex] = []
|
|
########################################################################
|
|
wordData = {
|
|
'font': self.getFontName(font, bold, italic),
|
|
'text': self.toUnicode(text)
|
|
}
|
|
if isinstance(attribute, dict):
|
|
for key in attribute.keys():
|
|
if key not in wordData.keys():
|
|
wordData[key] = attribute[key]
|
|
|
|
self.lines[lineIndex].append(wordData)
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
########################################################################
|
|
# Preference クラス
|
|
#
|
|
# GIMP の初期設定ファイルを操作する
|
|
#
|
|
########################################################################
|
|
class GimpPreference:
|
|
########################################################################
|
|
gimpPreferenceDirPath = None
|
|
preferenceDirPath = None
|
|
preferenceFilePath = None
|
|
########################################################################
|
|
config = None
|
|
|
|
########################################################################
|
|
def __init__(self, majorVersion, minorVersion, dirName, fileName):
|
|
|
|
homePath = os.path.expanduser('~')
|
|
gimpPreferenceDirName = '.gimp-' + str(majorVersion) + '.' + str(minorVersion)
|
|
|
|
self.gimpPreferenceDirPath = os.path.join(homePath, gimpPreferenceDirName)
|
|
if not os.path.exists(self.gimpPreferenceDirPath):
|
|
return
|
|
|
|
self.preferenceDirPath = os.path.join(self.gimpPreferenceDirPath, dirName)
|
|
if not os.path.exists(self.preferenceDirPath):
|
|
os.mkdir(self.preferenceDirPath)
|
|
|
|
self.preferenceFilePath = os.path.join(self.preferenceDirPath, fileName)
|
|
|
|
########################################################################
|
|
def __del__(self):
|
|
pass
|
|
|
|
########################################################################
|
|
def load(self):
|
|
if not os.path.exists(self.preferenceFilePath):
|
|
return False
|
|
|
|
self.config = ConfigParser.SafeConfigParser(allow_no_value=True)
|
|
try:
|
|
self.config.read(self.preferenceFilePath)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
########################################################################
|
|
def save(self):
|
|
if not self.preferenceFilePath:
|
|
return False
|
|
if not self.config:
|
|
return False
|
|
|
|
try:
|
|
with open(self.preferenceFilePath, 'wb') as configfile:
|
|
self.config.write(configfile)
|
|
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
########################################################################
|
|
def getAll(self, section):
|
|
if not section:
|
|
return {}
|
|
if not self.config:
|
|
return {}
|
|
try:
|
|
results = {}
|
|
pairList = self.config.items(section)
|
|
for pair in pairList:
|
|
results[pair[0]] = pair[1]
|
|
return pair
|
|
except:
|
|
return {}
|
|
|
|
########################################################################
|
|
def getOne(self, section, option, defaultValue=None, isString=False):
|
|
if not section:
|
|
return defaultValue
|
|
if not option:
|
|
return defaultValue
|
|
if not self.config:
|
|
return defaultValue
|
|
try:
|
|
value = self.config.get(section, option, isString)
|
|
if isString:
|
|
return value
|
|
elif value.lower() == 'true':
|
|
return True
|
|
elif value.lower() == 'false':
|
|
return False
|
|
else:
|
|
try:
|
|
if int(value) == float(value):
|
|
return int(value)
|
|
else:
|
|
return float(value)
|
|
except:
|
|
return value
|
|
except:
|
|
return defaultValue
|
|
|
|
########################################################################
|
|
def setOne(self, section, option, value):
|
|
if not section:
|
|
return False
|
|
if not option:
|
|
return False
|
|
|
|
if not self.config:
|
|
self.config = ConfigParser.SafeConfigParser(allow_no_value=True)
|
|
if not self.config:
|
|
return False
|
|
|
|
if value is None:
|
|
value = ''
|
|
elif isinstance(value, bool):
|
|
if value:
|
|
value = '1'
|
|
else:
|
|
value = '0'
|
|
elif not isinstance(value, unicode) and not isinstance(value, str):
|
|
value = str(value)
|
|
|
|
try:
|
|
self.config.add_section(section)
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
self.config.set(section, option, value)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
########################################################################
|
|
# 縦書クラス情報取得
|
|
#
|
|
# テキストレイヤーから縦書きレイヤー用の情報を入手するダイアログ
|
|
#
|
|
########################################################################
|
|
class VerticalTextAccess:
|
|
########################################################################
|
|
(
|
|
_LineNumber,
|
|
_ColumnNumber,
|
|
_RubyTargetNumcer,
|
|
_RubyNumber
|
|
) = range(4)
|
|
########################################################################
|
|
image = None
|
|
targetTextLayer = None
|
|
doPutMessage = True
|
|
lastMessage = ''
|
|
usableVersion = (2, 8, 0)
|
|
canUseLayerGroup = False
|
|
pango = None
|
|
########################################################################
|
|
preferenceDirName = 'VerticalWriting'
|
|
preferenceFileName = 'preference.conf'
|
|
########################################################################
|
|
lineSpaceRate = 1.0
|
|
charSpaceRate = 0.0
|
|
rubySizeRate = 1.0 / 3.0
|
|
rightShiftRate = 0.5
|
|
upShiftRate = 0.5
|
|
forceSmallerScaleRate = 0.8
|
|
########################################################################
|
|
useSizeBool = True
|
|
useColorBool = True
|
|
useRubyBool = True
|
|
########################################################################
|
|
defaultRotateRightTartget = u'[]{}()<>|-~_|「」『』(){}【】<>《》〔〕〘〙〚〛‥…ー−―〜~'
|
|
defaultRightShiftTarget = u'\'"、。’”'
|
|
defaultUpShiftTarget = u'、。'
|
|
defaultJoinBeforeTarget = u'゜゛'
|
|
defaultConsiderOneCharTarget = u'!?'
|
|
defaultForceSmallerTarget = u'ぁぃぅぇぉゃゅょっゎァィゥェォヵヶャュョッヮ'
|
|
########################################################################
|
|
rotateRightTartget = defaultRotateRightTartget
|
|
rightShiftTarget = defaultRightShiftTarget
|
|
upShiftTarget = defaultUpShiftTarget
|
|
joinBeforeTarget = defaultJoinBeforeTarget
|
|
considerOneCharTarget = defaultConsiderOneCharTarget
|
|
forceSmallerTarget = defaultForceSmallerTarget
|
|
flags = {}
|
|
|
|
########################################################################
|
|
def __init__(self, image):
|
|
self.results = gtk.RESPONSE_CANCEL
|
|
self.image = image
|
|
versionLength = len(self.usableVersion)
|
|
for index in range(0, versionLength):
|
|
if self.usableVersion[index] < gimp.version[index]:
|
|
self.canUseLayerGroup = True
|
|
break
|
|
elif self.usableVersion[index] == gimp.version[index]:
|
|
if index == (versionLength - 1):
|
|
self.canUseLayerGroup = True
|
|
break
|
|
continue
|
|
else:
|
|
self.canUseLayerGroup = False
|
|
break
|
|
return
|
|
|
|
########################################################################
|
|
def checkUsable(self):
|
|
if not self.canUseLayerGroup:
|
|
self.errorMessage('GIMP的版本太旧了。')
|
|
return False
|
|
else:
|
|
layer = pdb.gimp_image_get_active_layer(self.image)
|
|
if not pdb.gimp_item_is_text_layer(layer):
|
|
self.errorMessage('文本图层未被选中。')
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
########################################################################
|
|
# エラーメッセージ機能
|
|
#
|
|
def errorMessage(self, message=None):
|
|
if self.doPutMessage:
|
|
if message is None:
|
|
message = self.lastMessage
|
|
else:
|
|
self.lastMessage = message
|
|
|
|
print message
|
|
pdb.gimp_message_set_handler(MESSAGE_BOX)
|
|
pdb.gimp_message(message)
|
|
# MESSAGE_BOX CONSOLE ERROR_CONSOLE
|
|
# gimp.message( message )
|
|
return
|
|
|
|
########################################################################
|
|
def getLayerText(self):
|
|
if not self.image:
|
|
return ''
|
|
|
|
layer = pdb.gimp_image_get_active_layer(self.image)
|
|
if not pdb.gimp_item_is_text_layer(layer):
|
|
return ''
|
|
|
|
self.targetTextLayer = layer
|
|
|
|
markUp = pdb.gimp_text_layer_get_markup(layer)
|
|
if markUp:
|
|
self.pango = PangoAccess(markUp)
|
|
return self.pango.getAllText()
|
|
|
|
text = pdb.gimp_text_layer_get_text(layer)
|
|
if text:
|
|
self.pango = PangoAccess(text)
|
|
return text
|
|
|
|
return ''
|
|
|
|
########################################################################
|
|
def dialogMake(self, proc_name=None, defaultText=None):
|
|
|
|
self.loadPreference()
|
|
|
|
self.dialog = gimpui.Dialog(proc_name, "python-fu", None, 0, None, proc_name,
|
|
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)
|
|
)
|
|
self.dialog.set_modal(True)
|
|
self.dialog.set_alternative_button_order((gtk.RESPONSE_OK, gtk.RESPONSE_CANCEL))
|
|
# self.dialog.se_transient()
|
|
|
|
self.tooltips = gtk.Tooltips()
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('行间距(pt):')
|
|
hBox.pack_start(label, expand=False)
|
|
|
|
self.lineSpacePointSpin = gtk.SpinButton()
|
|
self.lineSpacePointSpin.set_digits(1)
|
|
self.lineSpacePointSpin.set_range(1, 72)
|
|
self.lineSpacePointSpin.set_increments(1, 10)
|
|
self.setDafultLineSpace()
|
|
hBox.pack_start(self.lineSpacePointSpin, expand=False)
|
|
|
|
self.tooltips.set_tip(self.lineSpacePointSpin, '请指定行之间的间距。')
|
|
|
|
label = gtk.Label(' 文字间距(pt):')
|
|
hBox.pack_start(label, expand=False)
|
|
|
|
self.charSpacePointSpin = gtk.SpinButton()
|
|
self.charSpacePointSpin.set_digits(1)
|
|
self.charSpacePointSpin.set_range(0, 72)
|
|
self.charSpacePointSpin.set_increments(1, 10)
|
|
self.setDafultCharSpace()
|
|
hBox.pack_start(self.charSpacePointSpin, expand=False)
|
|
|
|
self.tooltips.set_tip(self.charSpacePointSpin, '请指定文字之间的间距。')
|
|
|
|
self.dialog.vbox.pack_start(hBox, expand=False)
|
|
|
|
label = gtk.Label('')
|
|
hBox.pack_start(label, expand=True)
|
|
self.resetButton = gtk.Button('重置')
|
|
self.resetButton.connect('clicked', self.clickResetButton, None)
|
|
hBox.pack_start(self.resetButton, expand=False)
|
|
|
|
self.tooltips.set_tip(self.resetButton, '重置除ruby项目之外的所有项目。')
|
|
|
|
self.useSizeCheckButton = gtk.CheckButton('使用单独字体大小信息')
|
|
self.useSizeCheckButton.set_active(self.useSizeBool)
|
|
self.useSizeCheckButton.show()
|
|
self.dialog.vbox.pack_start(self.useSizeCheckButton, expand=False)
|
|
|
|
self.tooltips.set_tip(self.useSizeCheckButton, '每个字符的大小不同。')
|
|
|
|
self.useColorCheckButton = gtk.CheckButton('使用单独的字体颜色信息')
|
|
self.useColorCheckButton.set_active(self.useColorBool)
|
|
self.useColorCheckButton.show()
|
|
self.dialog.vbox.pack_start(self.useColorCheckButton, expand=False)
|
|
|
|
self.tooltips.set_tip(self.useColorCheckButton, '每个字符的颜色不同。')
|
|
|
|
self.useRubyCheckButton = gtk.CheckButton('使用ruby')
|
|
self.useRubyCheckButton.set_active(self.useRubyBool)
|
|
self.useRubyCheckButton.show()
|
|
self.useRubyCheckButton.connect('toggled', self.tggleUseRubyCheckButton, None)
|
|
self.dialog.vbox.pack_start(self.useRubyCheckButton, expand=False)
|
|
|
|
self.tooltips.set_tip(self.useRubyCheckButton, '请确定是否使用ruby。')
|
|
|
|
self.rubyVBox = gtk.VBox()
|
|
self.rubyVBox.show()
|
|
|
|
self.rubyHBox = gtk.HBox()
|
|
self.rubyHBox.show()
|
|
|
|
self.rubyHBox.pack_start(self.rubyVBox, expand=True, fill=True, padding=0)
|
|
|
|
### テキスト表示エリア ###
|
|
self.textView = gtk.TextView()
|
|
self.textView.set_editable(False)
|
|
textBuffer = self.textView.get_buffer()
|
|
if defaultText:
|
|
textBuffer.set_text(defaultText)
|
|
else:
|
|
layerText = self.getLayerText()
|
|
if layerText:
|
|
textBuffer.set_text(layerText)
|
|
|
|
textBuffer.connect('notify::has-selection', self.notify_has_selection)
|
|
textBuffer.connect_after('notify::cursor-position', self.notify_cursor_position)
|
|
|
|
self.textView.connect('button-release-event', self.button_release_event)
|
|
self.textView.connect('button-press-event', self.button_press_event)
|
|
|
|
# self.textView.connect( 'move-cursor', self.textSelectChange, None )
|
|
self.textView.show()
|
|
|
|
self.tooltips.set_tip(self.textView, '选择想要添加ruby的文字,然后按“添加ruby”按扭。')
|
|
|
|
self.textScrollArea = gtk.ScrolledWindow()
|
|
self.textScrollArea.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
|
self.textScrollArea.add(self.textView)
|
|
self.textScrollArea.show()
|
|
|
|
hSeparator = gtk.HSeparator()
|
|
hSeparator.show()
|
|
|
|
# self.dialog.vbox.pack_start( self.textScrollArea )
|
|
# self.dialog.vbox.pack_start( hSeparator )
|
|
|
|
self.rubyVBox.pack_start(self.textScrollArea)
|
|
self.rubyVBox.pack_start(hSeparator)
|
|
|
|
### ルビリストエリア ###
|
|
self.rubyListStore = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_INT, gobject.TYPE_STRING, gobject.TYPE_STRING)
|
|
self.listBox = gtk.TreeView(self.rubyListStore)
|
|
self.listSelection = self.listBox.get_selection()
|
|
self.listSelection.connect('changed', self.changedRubySlection, self.listBox)
|
|
|
|
renderer = gtk.CellRendererText()
|
|
column = gtk.TreeViewColumn('行数', renderer, text=self._LineNumber)
|
|
column.set_sort_column_id(self._LineNumber)
|
|
self.listBox.append_column(column)
|
|
|
|
renderer = gtk.CellRendererText()
|
|
column = gtk.TreeViewColumn('起始位置', renderer, text=self._ColumnNumber)
|
|
column.set_sort_column_id(self._ColumnNumber)
|
|
self.listBox.append_column(column)
|
|
|
|
renderer = gtk.CellRendererText()
|
|
column = gtk.TreeViewColumn('对象', renderer, text=self._RubyTargetNumcer)
|
|
column.set_sort_column_id(self._RubyTargetNumcer)
|
|
self.listBox.append_column(column)
|
|
|
|
renderer = gtk.CellRendererText()
|
|
renderer.set_property('editable', True)
|
|
renderer.connect('edited', self.rubyChanged, self.rubyListStore, self._RubyNumber)
|
|
column = gtk.TreeViewColumn('ruby', renderer, text=self._RubyNumber)
|
|
column.set_sort_column_id(self._RubyNumber)
|
|
self.listBox.append_column(column)
|
|
|
|
self.listBox.show()
|
|
|
|
self.tooltips.set_tip(self.listBox, '如果要更改ruby字符,请双击“对象”并更改它。')
|
|
|
|
self.rubyScrollArea = gtk.ScrolledWindow()
|
|
self.rubyScrollArea.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
|
self.rubyScrollArea.add(self.listBox)
|
|
self.rubyScrollArea.show()
|
|
|
|
hBox = gtk.HBox()
|
|
hBox.show()
|
|
|
|
hBox.pack_start(self.rubyScrollArea)
|
|
|
|
vBox = gtk.VBox()
|
|
vBox.show()
|
|
|
|
self.rubyAddButton = gtk.Button('添加ruby')
|
|
self.rubyAddButton.set_sensitive(False)
|
|
self.rubyAddButton.connect('clicked', self.clickAddRubyButton, None)
|
|
self.rubyAddButton.show()
|
|
vBox.pack_start(self.rubyAddButton, expand=False)
|
|
|
|
self.tooltips.set_tip(self.rubyAddButton, '请从上方区域选择需要添加ruby的文字,然后按下此按扭。')
|
|
|
|
self.rubyRemoveButton = gtk.Button('删除ruby')
|
|
self.rubyRemoveButton.set_sensitive(False)
|
|
self.rubyRemoveButton.connect('clicked', self.clickRemoveRubyButton, None)
|
|
self.rubyRemoveButton.show()
|
|
vBox.pack_start(self.rubyRemoveButton, expand=False)
|
|
|
|
self.tooltips.set_tip(self.rubyRemoveButton, '请选择左档并按下此按扭。')
|
|
|
|
hSeparator = gtk.HSeparator()
|
|
hSeparator.show()
|
|
vBox.pack_start(hSeparator, expand=True)
|
|
|
|
########################################################################
|
|
label = gtk.Label('ruby大小(pt)')
|
|
vBox.pack_start(label, expand=False)
|
|
|
|
self.rubyPointSpin = gtk.SpinButton()
|
|
self.rubyPointSpin.set_digits(1)
|
|
self.rubyPointSpin.set_range(1, 100)
|
|
self.rubyPointSpin.set_increments(1, 10)
|
|
self.setDefaultRubySize()
|
|
vBox.pack_start(self.rubyPointSpin, expand=False)
|
|
|
|
self.tooltips.set_tip(self.rubyPointSpin, '请输入ruby字体大小。')
|
|
|
|
hBox.pack_start(vBox, expand=False)
|
|
########################################################################
|
|
|
|
self.rubyVBox.pack_start(hBox)
|
|
|
|
self.rubyFrame = gtk.Frame('ruby')
|
|
self.rubyFrame.show()
|
|
self.rubyFrame.add(self.rubyHBox)
|
|
|
|
self.dialog.vbox.pack_start(self.rubyFrame, padding=20)
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('向右旋转90°的字符')
|
|
hBox.pack_start(label, expand=False)
|
|
self.rotateRightLineEdit = gtk.Entry()
|
|
self.rotateRightLineEdit.set_text(self.rotateRightTartget)
|
|
hBox.pack_start(self.rotateRightLineEdit)
|
|
self.dialog.vbox.pack_start(hBox)
|
|
|
|
self.tooltips.set_tip(self.rotateRightLineEdit, '请指定在竖直文本下需要向右旋转90°的字符。')
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('向右侧移动的字符')
|
|
hBox.pack_start(label, expand=False)
|
|
self.rightShiftLineEdit = gtk.Entry()
|
|
self.rightShiftLineEdit.set_text(self.rightShiftTarget)
|
|
hBox.pack_start(self.rightShiftLineEdit)
|
|
|
|
self.tooltips.set_tip(self.rightShiftLineEdit, '请指定在竖直文本下需要向右侧移动的字符。')
|
|
|
|
label = gtk.Label(' 移动比率:')
|
|
hBox.pack_start(label, expand=False)
|
|
self.rightShiftScaleSpin = gtk.SpinButton()
|
|
self.rightShiftScaleSpin.set_digits(2)
|
|
self.rightShiftScaleSpin.set_range(0.05, 1)
|
|
self.rightShiftScaleSpin.set_increments(0.05, 0.1)
|
|
self.rightShiftScaleSpin.set_value(self.rightShiftRate)
|
|
hBox.pack_start(self.rightShiftScaleSpin, expand=False)
|
|
|
|
self.tooltips.set_tip(self.rightShiftScaleSpin, '请指定相对于字符宽度的移动比率。(0~1)')
|
|
|
|
self.dialog.vbox.pack_start(hBox)
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('向上移动的字符')
|
|
hBox.pack_start(label, expand=False)
|
|
self.upShiftLineEdit = gtk.Entry()
|
|
self.upShiftLineEdit.set_text(self.upShiftTarget)
|
|
hBox.pack_start(self.upShiftLineEdit)
|
|
|
|
self.tooltips.set_tip(self.upShiftLineEdit, '请指定在竖直文本下需要向上移动的字符。')
|
|
|
|
label = gtk.Label(' 移动比率:')
|
|
hBox.pack_start(label, expand=False)
|
|
self.upShiftScaleSpin = gtk.SpinButton()
|
|
self.upShiftScaleSpin.set_digits(2)
|
|
self.upShiftScaleSpin.set_range(0.05, 1)
|
|
self.upShiftScaleSpin.set_increments(0.05, 0.1)
|
|
self.upShiftScaleSpin.set_value(self.upShiftRate)
|
|
hBox.pack_start(self.upShiftScaleSpin, expand=False)
|
|
|
|
self.tooltips.set_tip(self.upShiftScaleSpin, '请指定相对于字符高度的移动比率。(0~1)')
|
|
|
|
self.dialog.vbox.pack_start(hBox)
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('与前一个字符结合的字符')
|
|
hBox.pack_start(label, expand=False)
|
|
self.joinBeforeLineEdit = gtk.Entry()
|
|
self.joinBeforeLineEdit.set_text(self.joinBeforeTarget)
|
|
hBox.pack_start(self.joinBeforeLineEdit)
|
|
self.dialog.vbox.pack_start(hBox)
|
|
|
|
self.tooltips.set_tip(self.joinBeforeLineEdit, '请指定您需要与前一个字符结合使用的内容,例如“゛”或“゜”。')
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('连续字符被识别为一个字符')
|
|
hBox.pack_start(label, expand=False)
|
|
self.considerOneCharLineEdit = gtk.Entry()
|
|
self.considerOneCharLineEdit.set_text(self.considerOneCharTarget)
|
|
hBox.pack_start(self.considerOneCharLineEdit)
|
|
|
|
self.tooltips.set_tip(self.considerOneCharLineEdit, '请指定在竖直文本下想要识别的连续字符,如“!!?”。')
|
|
|
|
self.dialog.vbox.pack_start(hBox)
|
|
|
|
hBox = gtk.HBox()
|
|
label = gtk.Label('被强制缩小的字符')
|
|
hBox.pack_start(label, expand=False)
|
|
self.forceSmallerTargetLineEdit = gtk.Entry()
|
|
self.forceSmallerTargetLineEdit.set_text(self.forceSmallerTarget)
|
|
hBox.pack_start(self.forceSmallerTargetLineEdit)
|
|
|
|
self.tooltips.set_tip(self.forceSmallerTargetLineEdit, '请指定在竖直文本下略微缩小的字符,例如“っ”或“ぁ”。')
|
|
|
|
label = gtk.Label(' 缩小比率:')
|
|
hBox.pack_start(label, expand=False)
|
|
self.forceSmallerScaleSpin = gtk.SpinButton()
|
|
self.forceSmallerScaleSpin.set_digits(2)
|
|
self.forceSmallerScaleSpin.set_range(0.05, 1)
|
|
self.forceSmallerScaleSpin.set_increments(0.05, 0.1)
|
|
self.forceSmallerScaleSpin.set_value(self.forceSmallerScaleRate)
|
|
hBox.pack_start(self.forceSmallerScaleSpin, expand=False)
|
|
|
|
self.tooltips.set_tip(self.forceSmallerScaleSpin, '请指定相对于字符大小的缩小比率。(0~1)')
|
|
|
|
self.dialog.vbox.pack_start(hBox)
|
|
|
|
self.dialog.set_default_size(700, 600)
|
|
self.dialog.show_all()
|
|
|
|
########################################################################
|
|
def notify_has_selection(self, buffer, *args):
|
|
self.textViewSelectionCheck()
|
|
|
|
########################################################################
|
|
def notify_cursor_position(self, buffer, position):
|
|
self.textViewSelectionCheck()
|
|
|
|
########################################################################
|
|
def button_release_event(self, textview, event):
|
|
self.textViewSelectionCheck()
|
|
|
|
########################################################################
|
|
def button_press_event(self, *args):
|
|
self.textViewSelectionCheck()
|
|
|
|
########################################################################
|
|
def textViewSelectionCheck(self):
|
|
textbuffer = self.textView.get_buffer()
|
|
selection = textbuffer.get_selection_bounds()
|
|
if len(selection) < 2:
|
|
self.rubyAddButton.set_sensitive(False)
|
|
return
|
|
else:
|
|
text = textbuffer.get_text(selection[0], selection[1], include_hidden_chars=True)
|
|
if re.match('^[^\\s]+[\\r]?[\\n].', text):
|
|
self.rubyAddButton.set_sensitive(False)
|
|
else:
|
|
self.rubyAddButton.set_sensitive(True)
|
|
|
|
########################################################################
|
|
def tggleUseRubyCheckButton(self, checkBox, data=None):
|
|
if self.useRubyCheckButton.get_active():
|
|
self.rubyFrame.show()
|
|
else:
|
|
self.rubyFrame.hide()
|
|
|
|
########################################################################
|
|
def clickAddRubyButton(self, button, data=None):
|
|
########################################################################
|
|
textbuffer = self.textView.get_buffer()
|
|
selection = textbuffer.get_selection_bounds()
|
|
if len(selection) < 2:
|
|
return
|
|
text = textbuffer.get_text(selection[0], selection[1], include_hidden_chars=True)
|
|
if re.match('^[^\\s]+[\\r]?[\\n].', text):
|
|
return
|
|
########################################################################
|
|
lineNo = selection[0].get_line() + 1
|
|
columnNo = selection[0].get_line_offset() + 1
|
|
self.rubyListStore.append([lineNo, columnNo, text, '请在此输入ruby文本'])
|
|
|
|
########################################################################
|
|
def rubyChanged(self, cellrenderertext, path, new_text, listStore, column):
|
|
new_text = new_text.strip()
|
|
if new_text:
|
|
listStore[path][column] = new_text
|
|
|
|
########################################################################
|
|
def changedRubySlection(self, treeselection, listBox):
|
|
if treeselection.count_selected_rows() < 1:
|
|
self.rubyRemoveButton.set_sensitive(False)
|
|
else:
|
|
self.rubyRemoveButton.set_sensitive(True)
|
|
|
|
########################################################################
|
|
def clickRemoveRubyButton(self, button, data=None):
|
|
if self.listSelection.count_selected_rows() < 1:
|
|
return
|
|
########################################################################
|
|
selected = self.listSelection.get_selected()
|
|
if len(selected) < 2:
|
|
return
|
|
########################################################################
|
|
self.rubyListStore.remove(selected[1])
|
|
|
|
########################################################################
|
|
def clickResetButton(self, button, data=None):
|
|
self.resetValues()
|
|
self.resetWidgetsValue()
|
|
|
|
########################################################################
|
|
def resetValues(self):
|
|
self.lineSpaceRate = 1.0
|
|
self.charSpaceRate = 0.0
|
|
self.rubySizeRate = 1.0 / 3.0
|
|
self.rightShiftRate = 0.5
|
|
self.upShiftRate = 0.5
|
|
self.forceSmallerScaleRate = 0.8
|
|
|
|
self.useSizeBool = True
|
|
self.useColorBool = True
|
|
self.useRubyBool = True
|
|
|
|
self.rotateRightTartget = self.defaultRotateRightTartget
|
|
self.rightShiftTarget = self.defaultRightShiftTarget
|
|
self.upShiftTarget = self.defaultUpShiftTarget
|
|
self.joinBeforeTarget = self.defaultJoinBeforeTarget
|
|
self.considerOneCharTarget = self.defaultConsiderOneCharTarget
|
|
self.forceSmallerTarget = self.defaultForceSmallerTarget
|
|
|
|
########################################################################
|
|
def resetWidgetsValue(self):
|
|
self.setDafultLineSpace()
|
|
self.setDafultCharSpace()
|
|
self.useSizeCheckButton.set_active(self.useSizeBool)
|
|
self.useColorCheckButton.set_active(self.useColorBool)
|
|
self.useRubyCheckButton.set_active(self.useRubyBool)
|
|
self.setDefaultRubySize()
|
|
self.rotateRightLineEdit.set_text(self.rotateRightTartget)
|
|
self.rightShiftLineEdit.set_text(self.rightShiftTarget)
|
|
self.rightShiftScaleSpin.set_value(self.rightShiftRate)
|
|
self.upShiftLineEdit.set_text(self.upShiftTarget)
|
|
self.upShiftScaleSpin.set_value(self.upShiftRate)
|
|
self.joinBeforeLineEdit.set_text(self.joinBeforeTarget)
|
|
self.considerOneCharLineEdit.set_text(self.considerOneCharTarget)
|
|
self.forceSmallerTargetLineEdit.set_text(self.forceSmallerTarget)
|
|
self.forceSmallerScaleSpin.set_value(self.forceSmallerScaleRate)
|
|
|
|
########################################################################
|
|
def loadPreference(self):
|
|
|
|
self.resetValues()
|
|
|
|
pref = GimpPreference(gimp.version[0], gimp.version[1], self.preferenceDirName, self.preferenceFileName)
|
|
if not pref.load():
|
|
return
|
|
|
|
self.lineSpaceRate = float(pref.getOne('space', 'lineSpaceRate', self.lineSpaceRate))
|
|
self.charSpaceRate = float(pref.getOne('space', 'charSpaceRate', self.charSpaceRate))
|
|
|
|
self.useSizeBool = bool(pref.getOne('bool', 'useSizeBool', self.useSizeBool))
|
|
self.useColorBool = bool(pref.getOne('bool', 'useColorBool', self.useColorBool))
|
|
self.useRubyBool = bool(pref.getOne('bool', 'useRubyBool', self.useRubyBool))
|
|
|
|
self.rubySizeRate = float(pref.getOne('ruby', 'rubySizeRate', self.rubySizeRate))
|
|
|
|
self.rightShiftRate = float(pref.getOne('shift', 'rightShiftRate', self.rightShiftRate))
|
|
self.upShiftRate = float(pref.getOne('shift', 'upShiftRate', self.upShiftRate))
|
|
|
|
self.forceSmallerScaleRate = float(pref.getOne('scale', 'forceSmallerScaleRate', self.forceSmallerScaleRate))
|
|
|
|
dummy = PangoAccess(None)
|
|
|
|
self.rotateRightTartget = dummy.toUnicode(
|
|
pref.getOne('text', 'rotateRightTartget', self.rotateRightTartget, True))
|
|
self.rightShiftTarget = dummy.toUnicode(pref.getOne('text', 'rightShiftTarget', self.rightShiftTarget, True))
|
|
self.upShiftTarget = dummy.toUnicode(pref.getOne('text', 'upShiftTarget', self.upShiftTarget, True))
|
|
self.joinBeforeTarget = dummy.toUnicode(pref.getOne('text', 'joinBeforeTarget', self.joinBeforeTarget, True))
|
|
self.considerOneCharTarget = dummy.toUnicode(
|
|
pref.getOne('text', 'considerOneCharTarget', self.considerOneCharTarget, True))
|
|
self.forceSmallerTarget = dummy.toUnicode(
|
|
pref.getOne('text', 'forceSmallerTarget', self.forceSmallerTarget, True))
|
|
|
|
########################################################################
|
|
def savePreference(self):
|
|
|
|
pointSize = self.getDefaultFontSizeInPoint()
|
|
if pointSize <= 0.0:
|
|
self.lineSpaceRate = 1.0
|
|
self.charSpaceRate = 0.0
|
|
self.rubySizeRate = 1.0 / 3.0
|
|
else:
|
|
self.lineSpaceRate = self.lineSpacePointSpin.get_value() / float(pointSize)
|
|
self.charSpaceRate = self.charSpacePointSpin.get_value() / float(pointSize)
|
|
self.rubySizeRate = self.rubyPointSpin.get_value() / float(pointSize)
|
|
|
|
self.rightShiftRate = float(self.rightShiftScaleSpin.get_value())
|
|
self.upShiftRate = float(self.upShiftScaleSpin.get_value())
|
|
self.forceSmallerScaleRate = float(self.forceSmallerScaleSpin.get_value())
|
|
|
|
pref = GimpPreference(gimp.version[0], gimp.version[1], self.preferenceDirName, self.preferenceFileName)
|
|
|
|
if not pref.setOne('space', 'lineSpaceRate', self.lineSpaceRate):
|
|
return False
|
|
|
|
if not pref.setOne('space', 'charSpaceRate', self.charSpaceRate):
|
|
return False
|
|
|
|
if not pref.setOne('bool', 'useSizeBool', self.useSizeBool):
|
|
return False
|
|
|
|
if not pref.setOne('bool', 'useColorBool', self.useColorBool):
|
|
return False
|
|
|
|
if not pref.setOne('bool', 'useRubyBool', self.useRubyBool):
|
|
return False
|
|
|
|
if not pref.setOne('ruby', 'rubySizeRate', self.rubySizeRate):
|
|
return False
|
|
|
|
if not pref.setOne('shift', 'rightShiftRate', self.rightShiftRate):
|
|
return False
|
|
|
|
if not pref.setOne('shift', 'upShiftRate', self.upShiftRate):
|
|
return False
|
|
|
|
if not pref.setOne('scale', 'forceSmallerScaleRate', self.forceSmallerScaleRate):
|
|
return False
|
|
|
|
if not pref.setOne('text', 'rotateRightTartget', self.rotateRightTartget):
|
|
return False
|
|
|
|
if not pref.setOne('text', 'rightShiftTarget', self.rightShiftTarget):
|
|
return False
|
|
|
|
if not pref.setOne('text', 'upShiftTarget', self.upShiftTarget):
|
|
return False
|
|
|
|
if not pref.setOne('text', 'joinBeforeTarget', self.joinBeforeTarget):
|
|
return False
|
|
|
|
if not pref.setOne('text', 'considerOneCharTarget', self.considerOneCharTarget):
|
|
return False
|
|
|
|
if not pref.setOne('text', 'forceSmallerTarget', self.forceSmallerTarget):
|
|
return False
|
|
|
|
return pref.save()
|
|
|
|
########################################################################
|
|
def setDafultLineSpace(self, defaultValue=12):
|
|
|
|
try:
|
|
pointSize = self.getDefaultFontSizeInPoint()
|
|
if pointSize < 0:
|
|
self.lineSpacePointSpin.set_value(defaultValue)
|
|
return
|
|
|
|
# 行間は文字のポイントサイズで
|
|
self.lineSpacePointSpin.set_value(pointSize * self.lineSpaceRate)
|
|
except:
|
|
return
|
|
|
|
########################################################################
|
|
def setDafultCharSpace(self, defaultValue=0):
|
|
|
|
try:
|
|
pointSize = self.getDefaultFontSizeInPoint()
|
|
if pointSize < 0:
|
|
self.charSpacePointSpin.set_value(defaultValue)
|
|
return
|
|
|
|
# 文字の間隔はポイントサイズで
|
|
self.charSpacePointSpin.set_value(pointSize * self.charSpaceRate)
|
|
except:
|
|
return
|
|
|
|
########################################################################
|
|
def getDefaultFontSizeInPoint(self):
|
|
|
|
if not self.image:
|
|
return -1.0
|
|
|
|
try:
|
|
layer = pdb.gimp_image_get_active_layer(self.image)
|
|
if not pdb.gimp_item_is_text_layer(layer):
|
|
return -1.0
|
|
|
|
sizeInInch = 0
|
|
(fontSize, fontUnit) = pdb.gimp_text_layer_get_font_size(layer)
|
|
if pdb.gimp_unit_get_identifier(fontUnit).lower() in ['px', 'pix', 'pixel', 'pixels']:
|
|
(xResolution, yResoLution) = pdb.gimp_image_get_resolution(self.image)
|
|
sizeInInch = fontSize / xResolution
|
|
else:
|
|
sizeInInch = fontSize / pdb.gimp_unit_get_factor(fontUnit)
|
|
|
|
point = sizeInInch * 72
|
|
|
|
return point
|
|
except:
|
|
return -1.0
|
|
|
|
########################################################################
|
|
def setDefaultRubySize(self, defaultValue=6):
|
|
|
|
try:
|
|
pointSize = self.getDefaultFontSizeInPoint()
|
|
if pointSize < 0:
|
|
self.rubyPointSpin.set_value(defaultValue)
|
|
return
|
|
|
|
# ルビは 1/3 サイズで
|
|
self.rubyPointSpin.set_value(pointSize * self.rubySizeRate)
|
|
except:
|
|
return
|
|
|
|
########################################################################
|
|
def setResults(self):
|
|
|
|
self.flags['lineSpacePoint'] = self.lineSpacePointSpin.get_value()
|
|
self.flags['charSpacePoint'] = self.charSpacePointSpin.get_value()
|
|
self.flags['useFontSize'] = self.useSizeCheckButton.get_active()
|
|
self.flags['useFontColor'] = self.useColorCheckButton.get_active()
|
|
self.flags['useRuby'] = self.useRubyCheckButton.get_active()
|
|
self.flags['rubyPointSize'] = self.rubyPointSpin.get_value()
|
|
|
|
self.rotateRightTartget = self.pango.toUnicode(self.rotateRightLineEdit.get_text().strip())
|
|
|
|
self.rightShiftTarget = self.pango.toUnicode(self.rightShiftLineEdit.get_text().strip())
|
|
self.rightShiftScale = float(self.rightShiftScaleSpin.get_value())
|
|
|
|
self.upShiftTarget = self.pango.toUnicode(self.upShiftLineEdit.get_text().strip())
|
|
self.upShiftScale = float(self.upShiftScaleSpin.get_value())
|
|
|
|
self.joinBeforeTarget = self.pango.toUnicode(self.joinBeforeLineEdit.get_text().strip())
|
|
|
|
self.considerOneCharTarget = self.pango.toUnicode(self.considerOneCharLineEdit.get_text().strip())
|
|
|
|
self.forceSmallerTarget = self.pango.toUnicode(self.forceSmallerTargetLineEdit.get_text().strip())
|
|
self.forceSmallerScale = float(self.forceSmallerScaleSpin.get_value())
|
|
|
|
self.rubyInfo = []
|
|
|
|
# 配列としてアクセス
|
|
for row in self.rubyListStore:
|
|
rowDictionary = {}
|
|
rowDictionary['lineNum'] = row[0]
|
|
rowDictionary['columnNum'] = row[1]
|
|
rowDictionary['targetText'] = self.pango.toUnicode(row[2])
|
|
rowDictionary['rubyText'] = self.pango.toUnicode(row[3])
|
|
|
|
rowDictionary['lineIndex'] = rowDictionary['lineNum'] - 1
|
|
rowDictionary['columnIndex'] = rowDictionary['columnNum'] - 1
|
|
|
|
self.rubyInfo.append(rowDictionary)
|
|
|
|
self.savePreference()
|
|
|
|
########################################################################
|
|
def run(self):
|
|
if self.dialog:
|
|
self.results = self.dialog.run()
|
|
self.setResults()
|
|
self.dialog.destroy()
|
|
self.dialog = None
|
|
return self.results
|
|
else:
|
|
return gtk.RESPONSE_CANCEL
|
|
|
|
|
|
########################################################################
|
|
|
|
|
|
########################################################################
|
|
# 縦書クラス情報取得
|
|
#
|
|
# テキストレイヤーから縦書きレイヤーを生成
|
|
#
|
|
########################################################################
|
|
class VerticalTextLayer:
|
|
groupNameFormat = '竖排文本组 #'
|
|
lineGroupNameFormat = '行 #'
|
|
rubyGroupNameFormat = 'Ruby组 #'
|
|
|
|
########################################################################
|
|
def __init__(self, verticalTextAccess):
|
|
self.verticalTextAccess = verticalTextAccess
|
|
|
|
########################################################################
|
|
def __del__(self):
|
|
pass
|
|
|
|
########################################################################
|
|
def create(self):
|
|
if not self.verticalTextAccess:
|
|
return
|
|
if not self.verticalTextAccess.image:
|
|
return
|
|
if not self.verticalTextAccess.targetTextLayer:
|
|
return
|
|
if not self.verticalTextAccess.pango:
|
|
return
|
|
|
|
(fontCount, fontList) = pdb.gimp_fonts_get_list(None)
|
|
self.verticalTextAccess.pango.setFontList(fontList)
|
|
self.verticalTextAccess.pango.parse()
|
|
|
|
if not self.verticalTextAccess.pango.lines:
|
|
return
|
|
if len(self.verticalTextAccess.pango.lines) < 1:
|
|
return
|
|
|
|
self.image = self.verticalTextAccess.image
|
|
self.targetTextLayer = self.verticalTextAccess.targetTextLayer
|
|
|
|
rubyInfo = {}
|
|
if self.verticalTextAccess.rubyInfo and len(self.verticalTextAccess.rubyInfo) > 0:
|
|
for info in self.verticalTextAccess.rubyInfo:
|
|
if 'lineNum' in info.keys():
|
|
if info['lineNum'] in rubyInfo.keys():
|
|
rubyInfo[info['lineNum']].append(info)
|
|
else:
|
|
rubyInfo[info['lineNum']] = [info]
|
|
|
|
self.useRubyFlag = self.verticalTextAccess.flags['useRuby']
|
|
self.rubyPointSize = self.verticalTextAccess.flags['rubyPointSize']
|
|
|
|
pdb.gimp_layer_set_visible(self.targetTextLayer, False)
|
|
|
|
(xResolution, yResolution) = pdb.gimp_image_get_resolution(self.image)
|
|
|
|
pointId = 1
|
|
unitList = []
|
|
for unitId in range(pdb.gimp_unit_get_number_of_units()):
|
|
name = pdb.gimp_unit_get_identifier(unitId).lower()
|
|
unitList.append(name)
|
|
if name in ('pt', 'point', 'points'):
|
|
pointId = unitId
|
|
|
|
(targetLayerOffsetX, targetLayerOffsetY) = pdb.gimp_drawable_offsets(self.targetTextLayer)
|
|
targetLayerWidth = pdb.gimp_drawable_width(self.targetTextLayer)
|
|
targetLayerHeight = pdb.gimp_drawable_height(self.targetTextLayer)
|
|
|
|
rightX = targetLayerOffsetX + targetLayerWidth;
|
|
topY = targetLayerOffsetY
|
|
|
|
defaultFont = pdb.gimp_text_layer_get_font(self.targetTextLayer)
|
|
(defaultFontSize, defaultFontUnit) = pdb.gimp_text_layer_get_font_size(self.targetTextLayer)
|
|
|
|
self.verticalTextAccess.pango.setDefaultFont(defaultFont)
|
|
self.verticalTextAccess.pango.parse()
|
|
self.lines = self.verticalTextAccess.pango.lines
|
|
|
|
defaultColor = pdb.gimp_text_layer_get_color(self.targetTextLayer)
|
|
antialias = pdb.gimp_text_layer_get_antialias(self.targetTextLayer)
|
|
(hinting, autoHint) = pdb.gimp_text_layer_get_hinting(self.targetTextLayer)
|
|
|
|
# '縦書グループ'
|
|
index = 0
|
|
while pdb.gimp_image_get_layer_by_name(self.image, self.groupNameFormat + str(index)):
|
|
index += 1
|
|
|
|
topLayerGroup = pdb.gimp_layer_group_new(self.image)
|
|
pdb.gimp_item_set_name(topLayerGroup, self.groupNameFormat + str(index))
|
|
position = pdb.gimp_image_get_layer_position(self.image, self.targetTextLayer)
|
|
parent = pdb.gimp_item_get_parent(self.targetTextLayer)
|
|
|
|
pdb.gimp_image_insert_layer(self.image, topLayerGroup, parent, position + 1)
|
|
|
|
lineNumber = 0
|
|
|
|
self.useRubyFlag = self.verticalTextAccess.flags['useRuby']
|
|
self.rubyPointSize = self.verticalTextAccess.flags['rubyPointSize']
|
|
self.rubyPixcelSize = float(self.rubyPointSize / 72.0) * xResolution
|
|
|
|
lineSpacePoint = self.verticalTextAccess.flags['lineSpacePoint']
|
|
lineSpacePixcel = float(lineSpacePoint / 72.0) * xResolution
|
|
|
|
charSpacePoint = self.verticalTextAccess.flags['charSpacePoint']
|
|
charSpacePixcel = float(charSpacePoint / 72.0) * yResolution
|
|
|
|
########################################################################
|
|
for line in self.lines:
|
|
|
|
progressVule = float(lineNumber) / float(len(self.lines))
|
|
pdb.gimp_progress_update(progressVule)
|
|
|
|
lineNumber += 1
|
|
|
|
if not line:
|
|
continue
|
|
if len(line) < 1:
|
|
continue
|
|
|
|
(count, idList) = pdb.gimp_item_get_children(topLayerGroup)
|
|
lineLayerGroup = pdb.gimp_layer_group_new(self.image)
|
|
|
|
index = 0
|
|
while pdb.gimp_image_get_layer_by_name(self.image,
|
|
self.lineGroupNameFormat + str(lineNumber) + ' #' + str(index)):
|
|
index += 1
|
|
|
|
pdb.gimp_item_set_name(lineLayerGroup, self.lineGroupNameFormat + str(lineNumber) + ' #' + str(index))
|
|
parent = topLayerGroup
|
|
|
|
pdb.gimp_image_insert_layer(self.image, lineLayerGroup, parent, count)
|
|
|
|
maxWidth = 0
|
|
|
|
for word in line:
|
|
|
|
fontName = defaultFont
|
|
fontSize = defaultFontSize
|
|
unit = defaultFontUnit
|
|
|
|
if 'point' in word.keys():
|
|
fontSize = float(word['point'])
|
|
unit = pointId
|
|
|
|
if unit == 0:
|
|
if fontSize > maxWidth:
|
|
maxWidth = fontSize
|
|
else:
|
|
width = (fontSize / pdb.gimp_unit_get_factor(unit)) * xResolution
|
|
if width > maxWidth:
|
|
maxWidth = width
|
|
|
|
offsetY = topY
|
|
|
|
charsIndex = 0;
|
|
########################################################################
|
|
for wordIndex in range(len(line)):
|
|
|
|
word = line[wordIndex]
|
|
|
|
progressWordVule = float(float(wordIndex) / float(len(line))) / 10.0
|
|
pdb.gimp_progress_update(progressVule + progressWordVule)
|
|
|
|
fontName = defaultFont
|
|
fontSize = defaultFontSize
|
|
unit = defaultFontUnit
|
|
color = defaultColor
|
|
|
|
# width = fontSize
|
|
|
|
if 'font' in word.keys():
|
|
fontName = word['font']
|
|
if 'point' in word.keys() and self.verticalTextAccess.flags['useFontSize']:
|
|
fontSize = float(word['point'])
|
|
unit = pointId
|
|
if 'color' in word.keys() and self.verticalTextAccess.flags['useFontColor']:
|
|
color = gimpcolor.RGB(float(word['color']['red']), float(word['color']['green']),
|
|
float(word['color']['blue']), 1.0)
|
|
|
|
fontPixcelSize = fontSize
|
|
|
|
if unit == 0:
|
|
width = fontSize
|
|
fontPixcelSize = fontSize
|
|
else:
|
|
width = (fontSize / pdb.gimp_unit_get_factor(unit)) * xResolution
|
|
fontPixcelSize = (fontSize / pdb.gimp_unit_get_factor(unit)) * xResolution
|
|
|
|
wordTextCheck = word['text']
|
|
if wordTextCheck.isspace():
|
|
wordTextCheck = 'DummyText'
|
|
|
|
textFontExtents = pdb.gimp_text_get_extents_fontname(
|
|
wordTextCheck,
|
|
fontPixcelSize,
|
|
PIXELS,
|
|
fontName)
|
|
|
|
textFontWidth = textFontExtents[0]
|
|
textFontHeight = textFontExtents[1]
|
|
textFontAscent = textFontExtents[2]
|
|
textFontDescent = textFontExtents[3]
|
|
|
|
charsList = []
|
|
chaceText = u''
|
|
|
|
##########################################################################
|
|
# 1文字ごとに分解
|
|
for oneChar in word['text']:
|
|
if self.useRubyFlag and lineNumber in rubyInfo.keys():
|
|
# 行に対応するルビの準備
|
|
for info in rubyInfo[lineNumber]:
|
|
|
|
targetText = info['targetText']
|
|
rubyText = info['rubyText']
|
|
|
|
if info['columnIndex'] == charsIndex and len(targetText) > 0 and len(rubyText) > 0:
|
|
listIndex = len(charsList)
|
|
info['startIndex'] = len(charsList)
|
|
|
|
joinBeforeTarget = re.escape(self.verticalTextAccess.joinBeforeTarget)
|
|
considerOneCharTarget = self.verticalTextAccess.considerOneCharTarget
|
|
|
|
pattern = u'(.[' + joinBeforeTarget + '])'
|
|
pattern += u'|(' + considerOneCharTarget + ']+)'
|
|
|
|
dummyTarget = re.sub(pattern, u'X', targetText)
|
|
|
|
info['endIndex'] = info['startIndex'] + len(dummyTarget) - 1
|
|
if 'ruby' in word.keys():
|
|
word['ruby'][listIndex] = info
|
|
else:
|
|
word['ruby'] = {listIndex: info}
|
|
|
|
if oneChar in self.verticalTextAccess.joinBeforeTarget:
|
|
# 直前の文字に結合させる文字(濁点など)
|
|
if len(chaceText) > 0:
|
|
charsList.append(chaceText)
|
|
chaceText = u''
|
|
|
|
index = len(charsList) - 1
|
|
if index < 0:
|
|
charsList.append(oneChar)
|
|
else:
|
|
charsList[index] += oneChar
|
|
elif oneChar in self.verticalTextAccess.considerOneCharTarget:
|
|
# 連続しても1文字と認識させる文字(!? など)
|
|
chaceText += oneChar
|
|
else:
|
|
if len(chaceText) > 0:
|
|
charsList.append(chaceText)
|
|
chaceText = u''
|
|
charsList.append(oneChar)
|
|
|
|
charsIndex += 1
|
|
|
|
if len(chaceText) > 0:
|
|
charsList.append(chaceText)
|
|
chaceText = u''
|
|
|
|
##########################################################################
|
|
ruby = None
|
|
for listIndex in range(len(charsList)):
|
|
|
|
progressCharVule = float(float(listIndex) / float(len(charsList))) / 100.0
|
|
pdb.gimp_progress_update(progressVule + progressWordVule + progressCharVule)
|
|
|
|
oneChar = charsList[listIndex]
|
|
|
|
if 'ruby' in word.keys() and listIndex in word['ruby'].keys():
|
|
# ルビの開始位置設定
|
|
ruby = word['ruby'][listIndex]
|
|
ruby['top'] = offsetY
|
|
ruby['left'] = rightX + (self.rubyPixcelSize / 2.0)
|
|
|
|
fontSizeRate = 1.0
|
|
feedSizePoint = fontSize * fontSizeRate
|
|
feedSizePixcel = feedSizePoint
|
|
if unit == 0:
|
|
feedSizePixcel = feedSizePoint
|
|
else:
|
|
feedSizePixcel = (feedSizePoint / pdb.gimp_unit_get_factor(unit)) * yResolution
|
|
|
|
if oneChar in self.verticalTextAccess.forceSmallerTarget:
|
|
fontSizeRate = self.verticalTextAccess.forceSmallerScale
|
|
|
|
if oneChar.isspace():
|
|
offsetY += (feedSizePixcel * fontSizeRate) + charSpacePixcel
|
|
continue
|
|
|
|
layer = pdb.gimp_text_layer_new(self.image, oneChar, fontName, fontSize * fontSizeRate, unit)
|
|
|
|
index = 0
|
|
while pdb.gimp_image_get_layer_by_name(self.image, oneChar + ' #' + str(index)):
|
|
index += 1
|
|
|
|
width = pdb.gimp_drawable_width(layer)
|
|
if width > maxWidth:
|
|
width = maxWidth
|
|
|
|
offsetX = rightX - maxWidth + ((maxWidth - width) / 2.0)
|
|
pdb.gimp_item_set_name(layer, oneChar + ' #' + str(index))
|
|
|
|
pdb.gimp_layer_set_offsets(layer, offsetX, offsetY)
|
|
|
|
(count, idList) = pdb.gimp_item_get_children(lineLayerGroup)
|
|
pdb.gimp_image_insert_layer(self.image, layer, lineLayerGroup, count)
|
|
|
|
pdb.gimp_text_layer_set_color(layer, color)
|
|
pdb.gimp_text_layer_set_antialias(layer, antialias)
|
|
pdb.gimp_text_layer_set_hinting(layer, hinting, autoHint)
|
|
pdb.gimp_text_layer_set_hint_style(layer, TEXT_HINT_STYLE_FULL)
|
|
|
|
if oneChar in self.verticalTextAccess.rotateRightTartget:
|
|
centerX = float(offsetX) + (pdb.gimp_drawable_width(layer) / 2.0)
|
|
centerY = float(offsetY) + (pdb.gimp_drawable_height(layer) / 2.0)
|
|
pdb.gimp_item_transform_rotate_simple(layer, ROTATE_90, False, centerX, centerY)
|
|
|
|
if oneChar in self.verticalTextAccess.rightShiftTarget or oneChar in self.verticalTextAccess.upShiftTarget:
|
|
newOffsetX = offsetX
|
|
newOffsetY = offsetY
|
|
|
|
if oneChar in self.verticalTextAccess.rightShiftTarget:
|
|
newOffsetX += pdb.gimp_drawable_width(layer) * self.verticalTextAccess.rightShiftScale
|
|
if oneChar in self.verticalTextAccess.upShiftTarget:
|
|
newOffsetY -= pdb.gimp_drawable_height(layer) * self.verticalTextAccess.upShiftScale
|
|
|
|
pdb.gimp_layer_set_offsets(layer, int(newOffsetX), int(newOffsetY))
|
|
|
|
########################################################################
|
|
# 次の文字の開始位置
|
|
nextOffsetY = offsetY + (feedSizePixcel * fontSizeRate) + charSpacePixcel
|
|
########################################################################
|
|
if ruby and ruby['endIndex'] <= listIndex and len(ruby['rubyText']) > 0:
|
|
# ルビの埋め込み
|
|
|
|
rubyGroup = pdb.gimp_layer_group_new(self.image)
|
|
|
|
index = 0
|
|
while True:
|
|
rubyGroupName = self.rubyGroupNameFormat + str(index)
|
|
if not pdb.gimp_image_get_layer_by_name(self.image, rubyGroupName):
|
|
break
|
|
|
|
index += 1
|
|
|
|
pdb.gimp_item_set_name(rubyGroup, rubyGroupName)
|
|
|
|
(count, idList) = pdb.gimp_item_get_children(lineLayerGroup)
|
|
pdb.gimp_image_insert_layer(self.image, rubyGroup, lineLayerGroup, count)
|
|
|
|
rubyFontExtents = pdb.gimp_text_get_extents_fontname(
|
|
ruby['rubyText'],
|
|
self.rubyPixcelSize,
|
|
PIXELS,
|
|
defaultFont)
|
|
|
|
rubyFontWidth = rubyFontExtents[0]
|
|
rubyFontHeight = rubyFontExtents[1]
|
|
rubyFontAscent = rubyFontExtents[2]
|
|
rubyFontDescent = rubyFontExtents[3]
|
|
|
|
rubyTop = ruby['top'] + math.ceil(textFontDescent * (-1) * fontSizeRate / 2.0)
|
|
rubyLeft = ruby['left']
|
|
rubyBottom = nextOffsetY - charSpacePixcel
|
|
rubyCharCount = len(ruby['rubyText'])
|
|
restRubyPixcel = (rubyBottom - rubyTop) - (rubyFontHeight * rubyCharCount)
|
|
rubyFeed = 0
|
|
if restRubyPixcel > 0:
|
|
rubyFeed = restRubyPixcel / (rubyCharCount + 1)
|
|
rubyTop += rubyFeed
|
|
|
|
########################################################################
|
|
for rubyChar in ruby['rubyText']:
|
|
if rubyChar.isspace():
|
|
rubyTop += self.rubyPixcelSize + rubyFeed
|
|
|
|
rubyLayer = pdb.gimp_text_layer_new(self.image, rubyChar, defaultFont, self.rubyPointSize,
|
|
pointId)
|
|
index = 0
|
|
while True:
|
|
rubyName = rubyChar + ' #' + str(index)
|
|
if not pdb.gimp_image_get_layer_by_name(self.image, rubyName):
|
|
break
|
|
|
|
index += 1
|
|
|
|
pdb.gimp_item_set_name(rubyLayer, rubyName)
|
|
pdb.gimp_layer_set_offsets(rubyLayer, rubyLeft, rubyTop)
|
|
(count, idList) = pdb.gimp_item_get_children(rubyGroup)
|
|
pdb.gimp_image_insert_layer(self.image, rubyLayer, rubyGroup, count)
|
|
|
|
pdb.gimp_text_layer_set_hinting(rubyLayer, True, True)
|
|
pdb.gimp_text_layer_set_hint_style(rubyLayer, TEXT_HINT_STYLE_FULL)
|
|
|
|
rubyTop += pdb.gimp_drawable_height(rubyLayer) + rubyFeed
|
|
########################################################################
|
|
ruby = None
|
|
########################################################################
|
|
# 開始位置を次の行へ
|
|
offsetY = nextOffsetY
|
|
########################################################################
|
|
########################################################################
|
|
rightX -= maxWidth + lineSpacePixcel
|
|
########################################################################
|
|
|
|
pdb.gimp_image_set_active_layer(self.image, topLayerGroup)
|
|
|
|
pdb.gimp_progress_update(1.0)
|
|
|
|
|
|
########################################################################
|
|
|
|
########################################################################
|
|
# テキストレイヤーから縦書きレイヤーを作成する
|
|
#
|
|
def python_fu_vertical_writing_2_8(image, drawable):
|
|
proc_name = 'python-fu-vertical-writing-2-8'
|
|
verticalWriting = VerticalTextAccess(image)
|
|
|
|
if not verticalWriting.checkUsable():
|
|
# 使用不可
|
|
return
|
|
|
|
# 縦書き処理を実行
|
|
verticalWriting.dialogMake(proc_name)
|
|
results = verticalWriting.run()
|
|
if results == gtk.RESPONSE_CANCEL:
|
|
return
|
|
|
|
layerCreator = VerticalTextLayer(verticalWriting)
|
|
|
|
pdb.gimp_image_undo_group_start(image)
|
|
|
|
pdb.gimp_progress_init(u'「縦書き写植」を処理中です...', None)
|
|
|
|
try:
|
|
layerCreator.create()
|
|
except:
|
|
pdb.gimp_message('処理中にエラーが発生しました。')
|
|
|
|
pdb.gimp_progress_end()
|
|
|
|
pdb.gimp_image_undo_group_end(image)
|
|
return
|
|
|
|
|
|
########################################################################
|
|
register(
|
|
'python-fu-vertical-writing-2-8', # プロシジャの名前
|
|
'テキストレイヤーから縦書きレイヤーを作成する。',
|
|
# プロシジャの説明文
|
|
'文本竖写脚本,根据文本图层创建竖排文本图层,适用于GIMP 2.8版以上。',
|
|
# PDBに登録する追加情報
|
|
'かんら・から', # 作者名
|
|
'GPLv3', # ライセンス情報
|
|
'2013.08.30', # 作成日
|
|
'文本竖书(Python)', # メニューアイテム
|
|
'*', # 対応する画像タイプ
|
|
|
|
[
|
|
(PF_IMAGE, 'image', 'Input image', None),
|
|
(PF_DRAWABLE, 'drawable', 'Input drawable', None)
|
|
], # プロシジャの引数
|
|
[], # 戻り値の定義
|
|
|
|
python_fu_vertical_writing_2_8, # 処理を受け持つ関数名
|
|
menu='<Image>/Layer/图层操作(Python-fu)' # メニュー表示場所
|
|
)
|
|
|
|
main() # プラグインを駆動させるための関数
|