1# -*- coding: utf-8 -*-2# Copyright 2011 Takeshi KOMIYA3#4# Licensed under the Apache License, Version 2.0 (the "License");5# you may not use this file except in compliance with the License.6# You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing, software11# distributed under the License is distributed on an "AS IS" BASIS,12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.13# See the License for the specific language governing permissions and14# limitations under the License.1516import math17import unicodedata18from functools import wraps1920from blockdiag.utils import Size212223def is_zenkaku(char):24 """Detect given character is Japanese ZENKAKU character"""25 char_width = unicodedata.east_asian_width(char)26 return char_width in "WFA"272829def zenkaku_len(string):30 """Count Japanese ZENKAKU characters from string"""31 return len([x for x in string if is_zenkaku(x)])323334def hankaku_len(string):35 """Count non Japanese ZENKAKU characters from string"""36 return len([x for x in string if not is_zenkaku(x)])373839def string_width(string):40 """Measure rendering width of string.41 Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point42 """43 widthmap = {'Na': 1, 'N': 1, 'H': 1, 'W': 2, 'F': 2, 'A': 2}44 return sum(widthmap[unicodedata.east_asian_width(c)] for c in string)454647def textsize(string, font):48 """Measure rendering size (width and height) of line.49 Returned size will not be exactly as rendered text size,50 Because this method does not use fonts to measure size.51 """52 width = (zenkaku_len(string) * font.size +53 hankaku_len(string) * font.size * 0.55)5455 return Size(int(math.ceil(width)), font.size)565758def memoize(fn):59 fn.cache = {}606162 def func(*args, **kwargs):63 key = str(args) + str(kwargs)64 if key not in fn.cache:65 fn.cache[key] = fn(*args, **kwargs)6667 return fn.cache[key]6869 return func