blockdiag-1.3.2/0000755000076600000240000000000012242633535014242 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/blockdiag.10000644000076600000240000000441611716343703016250 0ustar tkomiyastaff00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH BLOCKDIAG 1 "May 9, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME blockdiag \- generate block-diagram image file from spec-text file. .SH SYNOPSIS .B blockdiag .RI [ options ] " file" .SH DESCRIPTION This manual page documents briefly the .B blockdiag commands. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBblockdiag\fP is a program that generate block-diagram image file from spec-text file. .SH OPTIONS These programs follow the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. For a complete description, see the \fBSEE ALSO\fP. .TP .B \-\-version show program's version number and exit .TP .B \-h, \-\-help Show summary of options .TP .B \-a, \-\-antialias Pass diagram image to anti-alias filter .TP .B \-c FILE, \-\-config=FILE read configurations from FILE .TP .B \-o FILE write diagram to FILE .TP .B \-f FONT, \-\-font=FONT use FONT to draw diagram .TP .B \-\-fontmap=FONT use FONTMAP file to draw diagram .TP .B \-s, \-\-separate Separate diagram images for each group .TP .B \-T TYPE Output diagram as TYPE format .TP .B \-\-nodoctype Do not output doctype definition tags (SVG only) .TP .B \-\-no-transparency do not make transparent background of diagram (PNG only) .SH SEE ALSO The programs are documented fully by .br .BR http://tk0miya.bitbucket.org/blockdiag/build/html/index.html .SH AUTHOR blockdiag was written by Takeshi Komiya .PP This manual page was written by Kouhei Maeda , for the Debian project (and may be used by others). blockdiag-1.3.2/bootstrap.py0000644000076600000240000001306612216056572016640 0ustar tkomiyastaff00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os import shutil import sys import tempfile from optparse import OptionParser tmpeggs = tempfile.mkdtemp() usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", help="use a specific zc.buildout version") parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used.")) parser.add_option("-f", "--find-links", help=("Specify a URL to search for buildout releases")) options, args = parser.parse_args() ###################################################################### # load/install setuptools to_reload = False try: import pkg_resources import setuptools except ImportError: ez = {} try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen # XXX use a more permanent ez_setup.py URL when available. exec(urlopen('https://bitbucket.org/pypa/setuptools/raw/0.7.2/ez_setup.py' ).read(), ez) setup_args = dict(to_dir=tmpeggs, download_delay=0) ez['use_setuptools'](**setup_args) if to_reload: reload(pkg_resources) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set cmd = [sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] find_links = os.environ.get( 'bootstrap-testing-find-links', options.find_links or ('http://downloads.buildout.org/' if options.accept_buildout_test_releases else None) ) if find_links: cmd.extend(['-f', find_links]) setuptools_path = ws.find( pkg_resources.Requirement.parse('setuptools')).location requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: raise Exception( "Failed to execute command:\n%s", repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if '=' not in a]: args.append('bootstrap') # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ['-c', options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) blockdiag-1.3.2/buildout.cfg0000644000076600000240000000205012234217506016544 0ustar tkomiyastaff00000000000000[buildout] parts = blockdiag test tox static_analysis develop = . [blockdiag] recipe = zc.recipe.egg eggs = blockdiag[rst] interpreter = py [test] recipe = pbp.recipe.noserunner eggs = blockdiag[rst] blockdiag[test] coverage unittest-xml-reporting [tox] recipe = zc.recipe.egg eggs = tox detox [static_analysis] recipe = zc.recipe.egg eggs = coverage flake8 pylint [test-extra] recipe = iw.recipe.cmd:py on_install = true cmds = >>> url = "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fvlgothic%2F46966%2FVLGothic-20100416.zip" >>> buildout_dir = buildout.get('directory', '.') >>> path = os.path.join(buildout_dir, 'src/blockdiag/tests/truetype') >>> if not os.path.exists(path): ... os.makedirs(path) ... import cStringIO, urllib2, zipfile ... archive = urllib2.urlopen(url).read() ... zip = zipfile.ZipFile(cStringIO.StringIO(archive)) ... ttf = zip.read('VLGothic/VL-PGothic-Regular.ttf') ... open(os.path.join(path, 'VL-PGothic-Regular.ttf'), 'wb').write(ttf) blockdiag-1.3.2/examples/0000755000076600000240000000000012242633534016057 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/examples/blockdiagrc0000644000076600000240000000011011635000760020230 0ustar tkomiyastaff00000000000000[blockdiag] fontpath = /usr/share/fonts/truetype/kochi/kochi-mincho.ttf blockdiag-1.3.2/examples/group.diag0000644000076600000240000000017011635000760020031 0ustar tkomiyastaff00000000000000diagram { group { A -> B -> C -> D; } C -> E; A -> F -> G -> H; group { G; H; } } blockdiag-1.3.2/examples/group.png0000644000076600000240000002364211635000760017722 0ustar tkomiyastaff00000000000000PNG  IHDRD'iIDATxO$I130->x ^7"-+q Z{;3=pi ĭETNUWeEF==#"#)D-'anf&Crc7?0808080808080phfGc7b [XGGw= \,} 00/V@@@ D׳Ǿ8"N4+   ND׳8+  ƹࢰ~c091b_\G7%c>qxO8he8uyAYwvxgMj+xu @<&3p13~e@XWہڃ/k`ަmaMPk֗Y競#jIKkTU4M+T:si4ge4~X5xI!/*vffb 83p=~@1mcFH4F7 >Igfo^ĉ"NDE\Q8;+B{O3ZbeVm$VbB >QrUv\-Ԃ Ebg3p =~e@_fbōĵĵwR6F:oZ/ŕgJS'~I@b?r[1\'*hVYXI\KTVJz-q#VmPB~pu\!?װjg/s-+.grUeb&6$ lV?Y^)ǕEHBOھ9I++?SMDՙzr8w2 ;EwRݍ+7bH o 9Q/?Z*Q悺 ҅G_, 3p?~@w 22nmG[n^߯"G^|?_ƯKQ9Զ#EZ+G{#;$nĢсu>x޷R:Y~j'pP?pmq-"J K^K/ik0>_x_9l56R w~kwovY܈t~Kҥot&A΋KW"puպ{ s̟_y݉Ģ+nzgwI7f3|IT?<ڙ~J0}P?xUWH܈R+mV].y%\̟ t.W4k*(VmN+?HׁbÇ'^TdiP7C[*JDZYz#tE31X=3e 8nēlb֔Ư DD18bčMXmM|1x{U'}<'~i}Џہa~TTUiVKfQҼ-Sy/ĈX`<_t ҺmEbfY-Vo;Ψ}tkκ_El? >9%F  s:zdl?w#`<_x_wa?C3h͓,T$ھҶY#է^~=x-2 NA^HMTTM$FgͺLwN`$RІĨ1i` Mvj@tMƶd^?m*j3n{mShNF^_ Az?Q%ݙ8|`͹to޼чS RcB vb5׶HU׵8ԍ6}~Gsq*HAPMN9ը#9^ٽtIJU̟ }Ltje@tVs!DSmAMLsZ|#bi҇ A0cĞfo:_bp̂q{%[>P?A7>/ѩ:#m{hYADsl9aB&@=s(}8̧[CmרSsVǴƯS GLhM.+]u[Uiu6T$~>oBT?ؙYFS}7c`,zl꺮z`I`'/1렜< %R*Wut:PIrRPAU*ݦv@TLbDjZ=>s>DǯdD]`rdouW(aD~G&v/Nr?hH6MUU;_ݴ`/15¤x0&u1qE6>QExlʳ|Z^1A?g }Lw&Sxv/CJmc Ì;$]}P?Dܿk4C3Kc܏2?:̟>&:~%S HnkdLTyިA,{/R`ci̟>&=~M1HF1bGAٟ^a Džs6/dlL6V~""`v}P?8\h6ȇnOgsO&e3~vS?dٴ^,5k~_}W_/__}_|quu5ϋpiG{c33hom[۞0FĈ-dGJn>p1ִYά~|NfZ~`ju{n)@-(?kQ60dΦ~5COnoocUZݴ7Ƭ@`q b&4O}ᴜS>9s9 'dZqooǟ8ӱr; /p`ñ[pl씨qfcݜwww7v9_݄# :'CGGGGGGGGGGѱI~"/\y}P?8/JpS+#6G^A#'9P՝q'Xt/Hȳ;'̤V>߷4GQA9~>59th8\8 1vv0nc/zԶ5b}JvID,5穟QQ?A*O/9s/g\9N0Aa<_cWmOiNv}!x[x;Y~Z6.:j4M/b~'s)SS\\w;q*.pN;#3{zFGK7Q:Pa9*~xFx[18Nĥv?_w߽~իW?ëW^~ۻ,Ƙ!NթFNwR?C~7c`HīܟA>8m sBQEQeYeUU"⽏1O{l'?S 9_xsZ9tsNԩǩsNS]]Yy'I1I9 Nī^A - -wm/&nLt4#TDjQ3/v~*jb"Qw>"(3P?CL3+eZ}:Hţ9O3|>7"8bX=pd>$̟Ǖg&ǯ$}5^\Hk(BYyUĹridbTj,u37/}Y"{uVoLT>ȿ\~áUin?UEU7B(Pr'ZC3]O׏^f.m9E^Lu<6 Ӈr҇(u]G]xrD1Zyd9_O~Wj:(b sRL\EV&C(`$cqOETͫ+^^|̅E|T5?W=|Kd%~u3֝5}5]&&jNs޹0b)ƨ6ԏx,E)s].bVeQ9ΫKoǛ/(b6-tEQj٤BR1O~}>쳫b1R1̟s}#:q^\!0Z덚z- sK&,*]e.NTT~94>C\UY,jVlʲ>ufwo}֍?ҐזlۋLTM\T_K_!hI DC AqRz",|Qs_\( ^5soLDJBb^]@qi*.?iמ\]]]__c5sZy=EUh!pވ:(dQyb_n]m-XK($MFjK}h=/eE]|9gM}ϻ؆6u?$;TęZ5~^:x31gAVn1/|Y1҅R}>nd+ t_>p@ bX,rb62qWWNXzO׭R\•k\҅[+־emRWܼvUS_x #Sqb .>B8uӍaSqbˏvPy|7}ij δإkL$:*S?C~3!?c~9b60L+ su8WԪuH'6- ڇvѵV{p:0`&n1ASp4mk.+5,Lb%"JZIHl_kݬTGF5=>V| Μ?Mp;Ё>o=Zg*&b׎9㨟iAmHz]Q2̯ϸڅR]!,df3"ޔa>!-.@(E()huӇ1'̟}L~j@-,,j$,ԕ͜uhEŔwP*"jN̩xg^w来˓tR4B?6KDi~s2'~2A>t& _}qkW^kP? m7'҄42#ڹE"I;P2?cWW6q^-+5DgR\kQ-Fgf&$myMYcS~.TM^2mj|^tGG߁|=Uק93~Nd2OjnhJ s W,4,ԕAoh8t-4g>AuwLIPk'z.̟}LtڑUeBUg.hS NuO ՚zIw.OuǿZVCfJOHSj+P9#O4J'z8ԛ>50AZY "*ՉzuABVE3&iV-O?L}hyο~>ЇzWR?c~^l;$N8. A5 ~ޖǴmCJrquӶt0Ms2Iَ^T5:`V"&foW1Ayh~e*WQ?~^9KTtD8۫g]nKXSwb!~0 /7gѬ%&✈m_%]|<^}FbygRb"f{k;s0Mu\6^5I: ja+_Ci@'y~h3b೯Ed'83-۩sn4kJև( y%&:~@lAѦUUM;=hO,uG+ǐ 3!/;Fŕ}x?[\W_}x&Q?YjGjqyKocĉnwC#'0~e)ld?n铊}u0y"rXl-?:il{8>&oS2 Z`̮lN_t883̟ 1y^A~r5fMpAp@?nwS 뱛5z"{&9Bwsm3z}`p_"C@ D@SJ 080q.`! V8m8q^н#8##;\F. >+  ƹ 'zJLp<Ӱ1`Őg 'xX0I^3@LI5?ɘ~@O#c6R T5bx\y`Dñ% f Ţ/Do޼чS qރ.a4XCX0*U]׮Cc r׽!7B)&}8"83.1ȻwD96i Ǜ7oftD%}'@d{6}jvAnnnbZD@ȐG htƋq~*AiD "|ܤ!PeYEQ"@%9x捈f|>u]"a#W1D]0zl6u]usaCn๺jZV ]?vC )zl6UU\|1v _S)n<dp~@&e 1v` s(Lf|>v+(B$a)BD޽{7vC@Urp}}_݊#`Xof&BVQt_Y,BሺI˲m~{V /.XpssZ޿?vCx 7  Z Swwwgv~f9On8`p`p`p`p`p`p`p`p`p`pKM)!Nzs;E|7h- c7՞r;.sn'} DUׯ_z~xׯ߾}{{{{wwWU7ٸ6SHqCa6-K؏!!D9!EQEYeYVU%"=QFzCLNq{)z|وH* EzS |@9mJ2EQ뺮 $= <@n⋢(fbJW^EZ6M *E 퓟r>jXfAp!<`>zND (w= n/`W@u-" )}`tHbX,r؍L@dJUͬ@:nonI> ݃c֑nѽ]71v #}^Dڛ2>h%en0vK _=[\@qD;hA$)}`J(ܥk1s;48AuwLIPk'z  =NYC:&z8M &`CJrquyxƘ@LC:݉!b$<`Jr"p2ݣ@D0I'^ _OvW=p><"|x&I0-nG%y Yb{@}g`p`p`p0}\V@@@b   Nl63 0808080808080808080y IENDB`blockdiag-1.3.2/examples/group.svg0000644000076600000240000001262211635000760017731 0ustar tkomiyastaff00000000000000 blockdiag A B C D F G H E blockdiag-1.3.2/examples/multibyte.diag0000644000076600000240000000033111635000760020712 0ustar tkomiyastaff00000000000000diagram { A [label = "朝食"]; B [label = "昼食"]; C [label = "おやつ"]; D [label = "夕飯"]; E [label = "夜食"]; "起" -> "承" -> "転" -> "結"; A -> B -> D -> E; B -> C -> D; } blockdiag-1.3.2/examples/multibyte.png0000644000076600000240000001354511635000760020605 0ustar tkomiyastaff00000000000000PNG  IHDR],IDATxQnTa;uҤ-BhYX#*FZ&iLO~ T$u۶0 7L&b 1`B 0!L&b 1`B 0!L&b 1`B ڶ:}A?9Cs&b {yߛ~8B?9C L&2{Gqߛ~CC?}}`B 0!pr3)!~ȡr\>W`B 0!~Q}o%C?9LH}#W QεB!~H7V`~Gvdž~ʤr!G~^i8*B!~@\L۶݇8SC?9So۶b͚{}](0OQCC?O'o}Z 9SX[tx (!~ȡr/Pu4֯B!~NK%XW/IARiC~Jr!G~ϟ?_m4MUU٬SC?9r^@ _G$]+yO9CC?O'.jZmUUk)~ȡr!yK;gXi1JUOQCC?O_*@9RM,b1w][[SC?9"Ӊ7/9JXUU4r\nt$E )~ȡriH{]ilv`U^?e9C#J?  h^/VC?9D+CoUJǟYsIj#*P?{^=;D<< %o}w}9oCow[.ŕOtީv{A!Vz+n.y~bqǏ什gS|{3lMl_y4Ml<\?.#f.y<f8ÓpGnz_7;֢s+áqތ?ɅOW/6N%J?B_$xٽzP̜||n!zC[%~.#\?Ąpryka9rqӊI gw^=un9Nk^o7[?ߵ\~j'doM^[:CEkНyd5쏔9XuK#tOY=G^ވXJqꢄ:xzNzBu!% H'GEr׀"0L&b 1`B 0!LӨzM 0C?9{G"e;M?9CTJr{""9C#t?~ݎg6fPm!~$PUU]OO>_|s۶.}s۶nmt])ionn^%gحN?nmtvk_?Uѳi|>bx~~j۵m+v{4i7M0PCC?G?@H+\.fig*t#D~ȡr'4}j~T|>zznxH1P!~WEEz jMWzl6vR)nu~]V(B~r!G~ 4jzzzJ@7:N}U0I?9Cc4tBv*/;zQnZNCo+3,C?9fmۺw[UznJo7:N݃xݓ߿n~ r!G~fªޔ\.Si\:.t"~Co3 C?9FO'ߒ8I ~r!7uݾ|V[ðތgխAݩ^)~ȡrqoH _aW~Jr!G~:! 1VHzy 4!~ȡr/Pٽàr OQCC?O'9.(D>O CC?O}c2?!q`Gk;?B\w~ȎOJ)_;_?+y|%|/Ώ4nSC?9̆rFrUe׶ȡvU/Ͳ5ևxc!~8+`B 0!7r!~ȡ.LHI9OL~ȡrcJ b ) SŮxgC;C"[`BFrB=B!G~}" ߸JZL/h)~ȡrDN.pD(*B!ǹ 'c{/m׵+~JrqLQm!7kr-u]eT?E9CpIkjzsi-p OQC#\?p*!唉&i_F]=SC?DYz}iV/gi'QSC?P>vCo ϟ/ڶmlVm)~ȡrN%P<>>^HHZC?9"'fh{ЭCojZVm5SK?9Bft2dnLS.)~ȡrN%PUilfX,|ڂ⦟r!G~$ K<>>VU4r\.6-IQ'QSC?P -%HZ6vn{VU)L!~8~CzY9!~u. !Til6{O޴7ӡr:$l_[:l!G~ SQZnwt蔿rA| HCC?LVZE3e;M?9bc _4g<I?4ŧe3 !n<\7-CgZg\8I񤿶=G:Suj\@wͱk*~駡"[ZR_|*w?_\~:ӥRu7Ov_!0+wo ӽ{y +n?0E3Co´t'JN?[|"V ;|֢dpx$_>㏷r>f'+uG}׋pϏ?~O[H?7J??P~d4p<<<g y]G%t?ޫ%g dѿ`S˗nlސ; 8O6~/gps#'9dzbƫWQ9z=:y g4O샏 =zF(ׯ_Gcߺg4?`OG:h9)z|nwSQ|#`B 0!L&b 1`B 0!N7CC?LPA[(K7C?d=P\=Co!~f x@uk6fճ%Ц~ȡr?_ӧ˗/_~}~~nvۥonvح[r4Ջep$ gحn|@@wfi|>_,n׶4EMDYF3C?G?pQHN{Z:rl6UU5Ml=tZݞpX=3C?M?/ɴzܤs>?==m[ O\~wwwsssd -d=O C#n?pr<|~}}Znooӕ|^7MZ@+wOTZ ~ju}}b/gX!~8x@rճij5$'].Co3C?M?p!3(z|޻\V777gPWgX!~80@]mֽ}PZwK_:wҝ>\wm'}BjwemO!C#b?pa=!M2It b:NzKߤ!9߷zzBX(IZ= ~r#N%PuIjoKnt䬺TVgo,m O C#n?pB;ZaK-}UK~Jrq9onV5T?9CX#so,9\1 _:SC?D 9 blockdiag 朝食 昼食 おやつ 夕飯 夜食 blockdiag-1.3.2/examples/numbered.diag0000644000076600000240000000016611635000760020503 0ustar tkomiyastaff00000000000000diagram { A [numbered = 1]; B [numbered = 2]; C [numbered = 3]; D [numbered = 4]; A -> B -> C; A -> D; } blockdiag-1.3.2/examples/numbered.png0000644000076600000240000001062711635000760020366 0ustar tkomiyastaff00000000000000PNG  IHDR^IDATx͎ D(dlG:t6}1sYs1ٌ"f+HK4 Yt &UBIT~\.AQ t+CD @ 0(`P@ @ 0(`P@ @ 0(`D(: +a8f@ 5`1u ho 0(`HXB~f 0(`l{v=&7~xϿ :_Xq>Yu6$xfg*m:goӻ/3g ?Ei7koƜ6>֏G~CNJּ %:O<_~{1du. ?h뗠8.FpwTWZMgɏ_h;?seHџ5sF{Q(y|ۑ/l~pz駾ӡ NJj**9nS1?Vy8ȏ.8gwŇgߒO;iOQ债yi)?>(wɏ .yHnB\Yqy7 ? ?hs!j ]U.K0Z~-nxUi n-(_ i}e1(COE͏Fdỳўz?)""T/о1?_β,hnZM% gS$m>G0iPq2I$IǣR^1:<͏pW6hX|av+1ȏp>wNBɏpeu (`H*X,:Ϗtt+O~?gYv8\C~D~P/9˧VJmۢ(<ν[O܏>qA̳6eݻwm^}ǟ:|̏0^XGu~.`u_C8|/`Џd"= AG(`I7i%I2NGь'm͢Ő{)ңhY"K]A~t`ȏ8_}O/۷C|Ϗ}$F]L\> ,L|{0 ߼y?믿˛7o~l6L8?}0 ަ:g?Nu8q|:^ȏ 7?gTş8q$L_8M,X}iիl$x<ȏ,:| X+:Q%I`N?Xwl>wA~PGocxY3'z%&ItZ>GAMAP}Y_?F$=';@̵6K@UvuO'AJ)sSd2ק?-0֓vGCGA=ȏeO~I'@I};~D~PGc eg<ͺ !8kqA'@g]?Dc'M}:͏e"Ƚ,{OPVȏk+?6/ XYJsDxɏS.?rBς/ȏ 7?6}҃߃?_==/lhw.o|>7>qwߧA~pPz  /AsmuA~*f@ 5`]PAa .^x? n|㇏!=\1\)`"t c,lHpó_v}L 0ss|Q~J*:?49n>5` B5_cfpT#3`x4j~wWzѬW1u3,`Y `g5R@E\qu? N95nW'S fR{3`W^4.F~[#? eBAJ!O3`E +Uy^vV1^.Wz/X@3` ٷ*<=EQ P.=y?ukJ\o0|.=ZVc0٥_[~-v Ӿ7 C/;A,:icP(` v-"{;sq|Bbh(` rLtټ{ý<˓!` $ݵEzD@G(` zKAcd2~ID)^Rɻ"^cpׯGQhʧ>bz  1{s!  ]?EGQM ^7x||H3>1,&,ˤ.1i鍖 Av}j>-`:`P@ @ 0(`P@g:a(` }ApD` (`t1P@_Va/O pAӧbq˗b۷fCQy/#xk26xO L(` 8q$L_y8M,t+Vl|}իl$x<̅h !*n($n#l6/. SG/W/ u'I$t:-π  AP ,I깯R3זآo/AyQ"{27N&ݾzS"`n_18v$}ln_@P}-ZYܬ2mU?ꥃc0ZToǂ0A18]Yv/SK h! XO(7.Ջ!1\壼Y!FggŠP}Il| pg 6>8J!%hז80(`P` }ú//0@ Xf@ 5`u_^c 0(0@ 0(`P@ @ 8'-IENDB`blockdiag-1.3.2/examples/numbered.svg0000644000076600000240000000703311635000760020376 0ustar tkomiyastaff00000000000000 blockdiag A 1 B 2 D 4 C 3 blockdiag-1.3.2/examples/screen.diag0000644000076600000240000000244111635000760020157 0ustar tkomiyastaff00000000000000diagram admin { top_page [label = "Top page", color = "pink"]; group foo { foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO", style = "dashed"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; } group bar { bar_detail [label = "Detail of BAR", style = "dotted"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; } logout; top_page -> foo_index [color = "red", dir = none, label = "button"]; top_page -> bar_detail [style = dashed, label = "link"]; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm; foo_add_confirm -> foo_index [label = "added"]; foo_index -> foo_edit -> foo_edit_confirm; foo_edit_confirm -> foo_index [label = "changed"]; foo_index -> foo_delete_confirm; foo_delete_confirm -> foo_index [label = "deleted"]; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail [dir = both, style = dotted]; foo_index -> foo_edit [style = dotted]; } blockdiag-1.3.2/examples/screen.png0000644000076600000240000005436111635000760020047 0ustar tkomiyastaff00000000000000PNG  IHDR_XIDATxM$[vs~~ hXd 6l)hc AQ$a"CedZ^$ e0h^_Uxq#"?+2+F7="?**T97f&΃{pF(3B Pg8#`wX|7g+$Rl! Py}uҕڟ4<\-u]q8#00lՕKkT6=-o}[<4 [ kWi.@8ms6 Nǖ|gn+sx!tcMWv)!3g{ c޿}wm X]>l?:v0O `mUpJa;?K0\$'vP #~(0H٬M> :o`v0t7 Nn%%84&듍tz+QX蚥4QY-l!}t~]OjR)91k2XX{#Σ[ڬo 1Py4%!YoVJ9p򍈶Z?NPfAB%̪N x8*қWǏ?D?"'* Y)6PUuPW2?٫7N U.\&"Re^$tjX0~J&VYKIYu-a&LlnaR+Ů>{p\fN7 p-bDm]Hv 0wR^Zy)ՕT3 sdӃV[*ћ~ԉfrfSyT;%@8I&f*Rw2mW2{m[)HN:<7N Uܢ9mD +TJEV]KyiWvB_Hu%\i; aUܢnw^|.~,#)""她L]&ŎFN ۤӬ]k~aW^Yy)Рɏ7WO}sz!v {}H=?`+9kzf ɦZ<0V^j602WQ'fCIHvUwFZIu%[r\_JFk s`C+6yw]ꮇǭN-v{) !~ݽXJ-\~$# 3цYu3PsJ@d+T3)^Kb Zsyڮczán' )t vpJ /.² 8*]l0!'RS(طĂܪUWR^JodZwf球xWۨu.w7x2KUx?6pJ|TKB >ɒ8!;fGM|ǭ 4wbp-ՕU\)o~ƪw}m̱œ;nkXJ*+ED\յU3"R0 b&I![tN qa2 J00oUÍ!@ YYO[b[޴-ʚu/5XRQU b"Kby 6_]јj]3E7.ՔA$4*ʬ7C|]>RmًX3 v wˍs ~McV?HEDUTԜYPI77,`RofBub&wDM_B@.6t|kBzn4kϵ0[Iך,쪻m5r.F@gc}ž"~IKA\29˗(e@Lng?_>}o~yWUtn)9.ݒm6xx'6Rس֟R6ŏ6?5Qgw&nvP-Yt89_#?<\G H"Y[[U?a6~{wi9L?]/"'?8MoJ`3UWDS [oIq7 v!SpuuY2@쿻ڮAeK(uUWj^;q"N$hjkBHzp!~S#~p{q޹,SmTWH? xM0,D94 Nk e Yf9WJ*7L\`#'6~$?۹Z3N&ֵm!~@`kG!~|HQrY8ߥ feY]/^L&dRUt t`i~uZ;=*g$B."k&lL73얂p[{!~W8dBr.8H1mc(I)'O?X^@;$V!ݿ]1˪Pq*ޙwM퀛 '?g?͋w|2-|^ilXDo`Pŋ1ϲ((<!$8jb&Ϯ28ro4[_ B$aA򜬍fD?xq]eyW PȺR>HD/_~۷޽UUN35oк ,3.Ug+.YED>5[?[?:~IvG @7)M&l6k vuo9w`bD@γ̅q}4ֽ;)so6~nγ|.1~U+Gz-T,>%ue7MT)mҏ3? n X-7iR@`kGץ+7\U"&Huڽ',u `\Yi4%WDG6~G$ n.EۆA읡Q)9JED+ hZMfMI7݂?}A G4çk sBXMa6 G~xidž`3]s ,%[(,=sIo lvϾ,"kwާ -Q!Ê".n-Ar #`_{%"K _>~V>7?'0|x}& rP]l_/A~}_E)ODH|g__u?iFCz&()^VHX]MkXnCK\6EC7=sҀqvC~ADy7VcQRQ%ݏ92؀uww&lO[a'v{lqݯZ4RM5 }1x ݣ֑i[}7~8?=ǎ[vX }¦V,%~N};}x)er4R٠{&~ޯᵍ۟|[V5?>gϞ}裏>{b<yӎU;O꽯JV,Ԯ> o>o 8vu_L|eyG;M]\?\?Ӽ9ɝ8'**`/ʫoެNwZ~3kw_.w=b r@9ďts?%/~}MǛeˎSfU}k?G-o?ǞNF=}\<}h(:xv m"f ܤ|+rrfvu{ &.@OpzP)v% ԝgڮ >4h v..?Qt>l҉E<;xhvڦ/5.^ܲru;>Р>_;!ksQt4?G[,uZ?Z[{Drm0CO;}hv3aAp;v @7o$nyo}w{|_Oem3恢K{jv ?w=߭p[m_yúk$gD 8> o ${&N>S}o '8#c 7Wj?c vHpF(8 $sch X#xɗy?R@0F.H:SK;b- RF`bw6vt_;ޢHR7ig e;O[Rs1O ;v61m @_hprsι?2@o4M=yNM\s&n)\ G돳^۟____^^|O?O|'~W.//˲O^}[C{~eh:>yӧϞ=裏={{o:F<ϻ`ݢL,X(%̬+~+8[ջׯ^W2{~9u̮+W "f*\58&i6'❍|dQ'ǣ4 #/_Yu7owwZ6_MI互ҽ_+_>}rzD>.d2gYlg Əe2o"~'}OnG2g I̫81՛j M&<|."YeIppKd2{,H?)yYa8ʠ"S37-(!aUh^iR9 LP0Ͼ!m?ď$ F^s3TTM4~r$ 9e=ۺ:?lVUu/ďѣGtK O=཯0[m;qƾe}t6 擃Һ;]gE^NjU'`sqb,h[e Y5ͪ2B.4בq)DC*?yFdrqq{y~uu5c ?ʷ~?~|qq1Lb;5OZ8sӼ,̅qJ+Sh|5ͪGE9nbWI/*bM䈈o>?S?AB8ɼ3i.&hx@Z?˲d2b6SDwG}o:**bNūF^&YU"YYpe`$N0KBn^rU#AS0ɪ׎D`Ǐ?Xэy+sEeEiK$K<0XL&dӽ.QVFYU{3PO[|2/Wc $y5UvNi TJl䫐yE3e ӏď,ZgIe&NjSM4 N Vv϶Kzlo)ӎuVgLUūe>A).c#\֌jTwmL |M?9 BUi [G֋{9',s< 7]}o`VS,6BH;)x<l:<6ab fCTĚS1'xS Y 2LJ; xZ's\oQB.@M9*5~;_Mw..,˼GduUdM@eKڶjZۧ?rӂTDM81,7,|mOJqMU|Uw;jh3ωt' ~>vu8u;Bw5%9 t4mBhc%ZJSzx` yҎh㊓Nޖ '"*ND8o9d1O ngIA?jΉͽ3 n,d[=}?og{Cuqg8]k{tW. XP;w=p)荭Yl-}7a(FǷc ] 8v2lw NPwu^8aplV&/lG񴉾5p/mX0,mû.iH˽n D"Vg&w7#)@>[h7:ĩ8Z28auSpUWj^ū8'(eNGaMIHY;˝e2X(Nv@b BWyȇF>]p&(x@|mͤ @>Y5ɪ& f^  nHËw4/BEĩxg5Qp<&D*SfgWExgknm R(v @*^r\ea2P}gP ip;$<\~V2he5C$8=Ӏו7 ;'j~̓++-VA_p&(4}ߛv2jf~1Qp<鈭KCGbN;=/8,\Q>|7# iixeou.wU-guW.U;-/K(pf(8NO˧w]}-lZjtQpT? "r˛[5( b>ݮЎvMnwe_Ws 潧' Y:6]m>oyV?wSʾeKwJ@%00N{ p*Y坖̚w__^7~}-?w.Mtߟ\8hzɨs@|+rr~W—un}$c$;YzvyGW覑[ b&w~vrңfj/myyz؂Ic03uK= g>aӬAY} w{"){N pF(3B P i(3B $qQ3$8K,\Qpld)մ8Y9Q%SS5 $8)mOŻw/G/Gwev]*h1S~7TԟF>Lһҫ柪Zl4 JU4&jY,󖻐PZR5ZLB/N-sy˜y #ǙxNCūdr FgUTD2u`jFH**Nk}d8F*\]Ȝy i@Ҽ0t1|lwj^-sV0ΪiV<*+4T;]gE^NjU95if"VgS?/D$sab@ p |XzyjUrM |v^ GEW롨UUʠ4\3 pu('Y5!w^78 .^%w6UE4YU&bP3ɪi^|;Wb7-Q@7#G>SɽP?8Xċq ȇF0=p(4F81EOk\eZ<${*^-s!wX8W?8m6NwB0 1hGęX]Sx\3 hDE1h;8uLI &&f"RsK28M]~&p(4?b&jDD]L۴|ύp(8M M͓Ug{?d8yS{g0+C7% 9`'`Kѽ/h] N s}o }@-QpxTžɣ pFN?}{82 z}(;⟷C $  08#|c`q7 p8U]j_]ʵO+jhڧp-}hʥsXI:Z-=ۗdpIc0`fv}i 8qwV/ ܺ2_4`X(`/f GK\Q itORpd*7Z@R(`/\qP}XNgy ~Zp7fnӹEm&~|Ə+څ,m!~ :Ͼ@*bu2@,ŜIʦT8 $H-TUUTSQ]HMވDlY۵ 8-+,RռWqNdqOjO#vfMI KY;˝e2qbepObƏ,I>HPB{; :/|0Subw"~R6~ 5_[3hMgW8&YrԜ5sOƏHZ< . /Ӽ ygD-ыORƏ DHel(,{M!~ұ6~s@ip",̅QY(,G={}ԬPB< $Hup41@ nγ̅qg+V*ޓ7'MkGҋRL )8ו71` vtI;~;k~̓++-VA[O"p(4}ߛ,j~nY_ @dx A:bmhgwPwVƏp(>HRP'mC/C6D,5[|%K}o6HG xI>Hdz/|}oGCUm|7kZ]05>]~toۜUO{|Vޡ_xhL>~â;ω/KܽMڕnCݽ'=~0١>P/HϗbMֶ޵̥!'=׮i%6oW?u= E(4=?Rzeҫ!Pۿ&&=ѫ bB\XM&+e$?p^·lNOf͛//|M}ֶ}|rճ맓'rW#'DUTEE߾1i_ȥqޡp?gwGwuy@_ kN;_dގ}~<]?> 󯋙$լy~ [9>[?0=P iF g _;g;>tVtYvYMmY 07" P i7Pg8#}8#@ҸhA#~pf($oD,r7ҕi0$RM;v>oi8C$?c%{p$|[[^}HD,MVZjmҸoٍ`%?8%l$!HaPݛ5C?F//W׳_W 1!)!k>2U1qjȅI^y7μ?n]>箞dr߮E$-)ؐW!6 lgMWP5ZZnl;TթN3k-VIe`fg` 9L-~ڹk$s:uQś⬵TöI9QWvUĉ8$q*=ݜli͆ b"ATUs9|L\G+-MD%sR&!毲v"^%2d("'%&ω:ٸnP ijqu8/.qh6ioUEU7eY^eE+? '奛L.@|n*Nū΍2{Tȍ _Y,J3ՅY:o1;vOpMRL+@(pnRUgH*1 smp[:uy2dcɧZϲ*8}:~yu4GNG2k1rYQr> w jqi43)+J@3NdPt&r/ď%JH򙈈%* p6^ca78Ubjޏ2-|MJ7\i.\<(8^2 ,LȧE9EQyY3KUoeCJ&~0xڽ0d$/b IC4_H6O$DD|!յ\,p|lJZݡm˫.>CE͂ʏ gs/e̓%AIXÙW]q^MrWy2_9gΙՃw9V%~ii5?쑌ޓ$/DsUf#}o8gxx__KvqB 3 X` YWvXǥbn NJtEƕ+-,Hi!Sqb1\(|ea 2gĺ±᤼\M T"Rw3+$JDFKD  JhP $#cn2""B+ s HSp=?>;S1S5ՠrypJcn$òSor[BCi'u n}@ S*/Hx#)})K6W˴һ9NՏ,U&Y6T3 D1ؑ3Aś7INx(*"jN̩xg^wZMI[_'b'$lj &]HX'MՏD8%hSQ$jB`"^HGR$?P;,x1SolSkzn%y?&MkVfn"~x yms~lŹFkdD\.i݌ V[[蹁%>bfLٙ)(I8U_DNmk=c ӑSn_NX3^;Rr Ćs7@ri_ٺ A Hgcgt?INsjI)8 n6[ϭK=֢ ےvӡ*]\1|8Uݩ~y?m!:.|4c+cq(pr}kq,%bmf0QDgMoAI@ykM-.oONOXfeYwǏ?߾M߻|UDT00nG7 lL,XPYӭW?/&Vt֤NՏx楨U;zNү8Mqp&7$f\B)Ve@] @S^ 0DMZ.\&"2/D~qz, ffV]KI5[Xмj6;u^4_+ԏ u8/T]#qvho߂RL++IyiTWR$ʺ_:ٷ{+`X:c|ՉfrfwWͣZ;fLP$IJ 3+_쵕o|H"Ah}o MݘĹ?hvac3Hqj(p?ܪk)/mʮ_ U"mG7 ;KH'bHyi.Sd{)|pb1b`$bڮ>r]=2{eT^@+xc`C:o)y3ƽ>psMx"uC?dSu#qg f<cppտ3ׂX%L+)]rR7R]KZ˿[}ožv=m}^wwnSO!tN]Տ${da&"?T3ɪb"͘17NN5sUbsfRri (Ūۏ]=?`So)ݮKy~ۓ:b+,ĬAܪUWR^JodZwf}G@E1.w7٘% *<;Gyq/JD$%]5RCcȚ@Xx*R]YNʕ`8>kz]\L؉{[X@TT%xq˵j&1 q`4QtY A+%-ۯb5pOϿClgȕ*[jm˛v[Yfz@TTUfjX$1TQ`w.M#[3֭6 " PUfXU s w.}^H ]#;]_5wmA>[9nn#&O F>NzDknL5y:(F!NW]3Iܦs%i%jٽA+нNԍ58&*jzvy858:V7۳i؛İ?o>:oS]ێj ۫_Fqh6Ͼ|O#~_UQ'fLge0IZjY ^^c,xYDf8iLT\۠{mi9a1~p|JTu^uN5hlW_z/ND$7n?.lz@sF**^ͫx'NĉnyDVG`p`^w. *AT=uHc5c33{;.tJT΍,m2 ?۹]W .kƕ'iiHU]i~=)c[@̞,gWWZ)[-p:h{}2uf]γ*W5_^Z({@bc>WLϮWٛWʕA۩LnC|k&AUx3uUA?~Ankqib ҵ6˿߳~g U޿i^VSμ3omĴWHR@`/S˴ya/0\^DM4TW-gWEW8LCMۙH %>^\]]g ]V~FU_͖K n"U;?;D_uif<,};2 ̕..[w3id?yv9'6?vTQ wZ{먵խwUςzgTM1c ץWn^. ED>;0'+pξ<2hl{d%;!~2yкtm;8y8S&*蛪6n?-VܛnO ,^8Te_T8 LvLVp%yg>Ab xp}Z큳{_U-pZܜCF`qϛnc R {}op tBbCy=[*0}AiqmݒwXz-|X&?^ݳ/|;1r:IpxPqU7""&PP[?ȿS=No[Wtk k~o|r9德u:Ij0yG/o{088SϽ^0 Wqyu͎WWnϠ|9y~ADyyV~w 5qӈU -L~L,X(%̬+~?_t|m/'>>mW$w✨H]$NNmCB֗HOq3ڗw)[6{MT[{釮xĹB\DUEۘX*W +8G7>GKgljo>ݒ#$yuy0R [?{z=B`c VϯoyZ:n9[>v~h\[ٳ'r$rf"  /<‰jt ~]Mvo.KO[vCҁ bWFOF_3I0Y 0mhRI'<'=zX贏?[P,g]x[NKg-ZmSYeÎ&I6~=nI63R[pǗJyV_<3 " g3G8> 66>8#Yz8> 5pF(p/>g>Z;3%yasBWt{P1. N NR꟩ #Z.*7)̏-j^j!>r?|<[k\k?~yu4GNG2k1rYQr> w jqiS..ew[LD:sBS!~}tGZmI!:dQ^yyu^]h\&LN/4s5ZdUFM}VeY>&U4b TTĩ8LC8 Ӽ(iQyQT.fo|ե[Cvِ}XXݝmjޏ2-G`qRx2I6GI^}>rY\&E]#%N NR݃SՋ &=bb0?˲*e+\m"IPVyh?m E"Fɣ|^6~Ņfcuys1AL?2qfSK(ED\GR^IKj;4nTiG8wg.+4k6qDB5SBq3F3ωYՙ4_ jQ< KMYz17oCj͔9> eeҹ!@;ߍ'G;W'"jeFUjZ03Lmr3N9O\;4Zi~?} ~poͬ@uNSD3L\o~~ Q/HfR "&fM^,m<ڣC妗6+ws/ooDkQqNꤡ9+9b!WqҫOŁ pJeumNWS꾷kӉ$wzn m #'Iu %NNjˍv~ ~ž)9J;V$p.,-T| Nq{ZW Ζ{pFRxȁ` g8#Ie @ +pFRxDp8#I Ў):$nHY>u->R0`nI#Rv hsTijYfIAA\29˗(eAj Cd M?Sl:e;@jWt;7,e4. WH?@޼y7,2q1 A,(RQ# feY]/^L&dRUt}1-bƉ~}x  ),D /^eEQEy! ^D/_Heh<㪪bq C*~e Y&l6UUUUހ>ڔ )C-wFb?˲\`P 3U }.2BXM;ipDޣ-F.5S_5  c{`0(-? YvlzZ۸~ >7djSvy˂lIof3{ 1k'pQ۝ZpX%o7\4 XLg!uL;]^}7!|TQg8#pF(3B Pg8#8 U{pa$)Pե~Hְ ` 6$s9S4@DTw~w>O>;'|駟z,K3 !'Y[;tmN,Ft:2j&9}eyy^EQeY>`f~YgYFd9x<sɲ,K [*&Icw?@TĜ2fkt@P1o?Xw=~b2FX M+ fYM&l  3~v\\\ blockdiag Top page List of FOOs Detail FOO Add FOO Edit FOO Delete FOO (confirm) Add FOO (confirm) Edit FOO (confirm) Detail of BAR Edit BAR Edit BAR (confirm) logout link button added changed deleted blockdiag-1.3.2/examples/simple.diag0000644000076600000240000000011011635000760020160 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> D; C -> E; A -> F -> G -> H; } blockdiag-1.3.2/examples/simple.png0000644000076600000240000001452511635000760020057 0ustar tkomiyastaff00000000000000PNG  IHDR@(9 IDATx]nFPR~((E{ߥuYS74 iX?.&Ndlq^+(1pp8*☍=Ls_u?2u]wr9:+p(p(p\zܐ|B~!?x}oCVQQsܐTJ~#?̾!+p(p(pn~96d    O0I7 ?$?rm @0 @0?B?Y.D9kD~ !?rOpa~mOC(yVQ)OS!#\~b>sJ9qS-p'ﺮ0氮U{Ӊ(29 YYov\K韧ˠk@~"?rPW=ݲk]]׏ePlC!G`Ǯ-S!#b~z p PY.jZt$EOiCX99(Azmnw8ɪ xG~$?rDϳfc 1#n+ C~4?I/o=nOڋr9"%!Ċ~%?er9 ]i;/?+!?rOQ;*9:Su(%8M~!?䈛*z>}أEC!Gy72Zfڶ]cucZ9&Kcz)pOc6d&S}iL~/1LO~~@`3vLA=?@#ן9TC~Fg%b, 8O ǻw~__~yݏ?lV|>f%K_<͎M>/߿ez|1p70v=1ۀX&.lb1Epdܑ\O>n7@^ b&afb&_#RFp2]Z>vn{࢘R~Ҝ#?4T_P(p#8 }ybyb?Nϔ~|@0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 c[9A=Sɉr^sC8J^=""r9B{߯?бflvOJNC~4?IWUU]}ӧO?]/w]7h34r\7O/%ϸN~mtOz64M3bX,u迢7Mx~4Q.C~1LZVni~xu'@۶鰧 闟Q9OcluJ|>||<.W7qLv^(z9qH.79mfVvvxU)O?lڶ].(v!Z~%?rDωx.9Ri1]kUԃ7{/Q9O/d;9UUJRm x;O{oNm؃}K~!?䈞0ctGޥ}q~mñH q,?r9"%a ܉adZۗn"5쑾H~F$?rL ?nx/i?{xEÛ}L_PC~1 +pu]obWOߪNx }ySJN_5 ?%r9D' e=ѿ`*;S!#n~z! \}!T5A˼4C!G ,pӑ= *H_x(C!G(O; YD~J ?rO)~&p'#G*p_9û#/?+OH+w"?rg66 @0?BL!C~xSVQQ)q}r9䇋LP "ļg9*@0 @0+p%Homp89YU\Wvn_joSrqӱ8aObUD x~8@^-@Y 駟+olJEoc8x^C#cn <+'~bq)mSr;@MKxvc)Pmmۉi8xC?Xř؏FFFFFFFFFFFW]c{(ppiAKu}r"Fc4cK_b5f줽E9) \Z]}ӧO?]/w]7h;YkM,z}ƕOl6if>bX,}UU777:iœt W8TOnjZv ܫ;)pmۦÞj\:Q[[K gzNm>?>> >a]_p%9.|>_.mn6t>on \e۫JU?7ͦmr:\ \yKimStW1Ɛf=/pc)pp!+pá^djo66ڶmv^=ᶪT / d~M{u~r :|گۍ=g(pp9E"WUjoi ,-G8aܒڛN-ޢP)ppYj5xfM_Iu+)pp9}H]-u?c_ \ z۸M1?(pp9_e8 ([-Q[BC(ٍwxzÔ> }E>]h 1{|+pDe?_WX| >-7*@0 @023Qe<*O1=pL(t}'3ICr) @0 @0'(7B9GZ†WsI~%?R:2g$?rˏ\LufT~J ?䈛J:̘]ֵJe?iuNDӨE~.?=3+۶{8Ri-p9gHuO*wq60F=C)p\ Sg ͓i4Z=͞OisBqP}q뺦ifnC9gH@|HzQC~1?=0|۫=㢺o۶mPUpJiT~%?(p\~MW,㺻l6YچNS!G )p\#h94bX,x<[~!?䈘u>0ᡪijZV!MIQ7S!GPj~6 ֶn;dU&?erDϳfc1Dvn/͞ 1k ?Dc*Mnߟ<{h/*C~1?=ÓXSg%?erOq]v<ϧN#CI8`Lw=""rΏuND9R;~/*|Ҕ9 K8H۶zQKc*L5?cZL&?'(p\۟~iQ6@~o><~_C!%o6,z~ E%P(偗 LC 0i EAyXzyqKݻ__޽{?n6j5gn/o,?=8(?27ktn߱BHSOWꞪ))p\O>n7@i2:[h%P |)'y:=QL)?B*|ybS~9&`b|@0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 0Vo%?ONDe&?䈛Jƕz`#t~/XWflv2{R uA(p\믻?߿çO>ﻮ;_nFωilf\'hgF7T שPgM4|>/b﫪9]י:_Q؛Y{ϐq9gH*uup[C-޷5Dh9^>B~1?/QFÛVU=o'Ĵp{/9&^՗["5">8Ig;gDC gH㺤gu2>o&Nz'Sgis@~7?'8Q2]æ ;FN)#n~z W>Plp&SgsF~+?C ר~Vk:S~"?:J*\,D7)#n~*LOGk?B\ǵʅ: }=GW||["?lm8` blockdiag A B F C D E G H blockdiag-1.3.2/LICENSE0000644000076600000240000002613611635000760015250 0ustar tkomiyastaff00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. blockdiag-1.3.2/MANIFEST.in0000644000076600000240000000034712230473245016001 0ustar tkomiyastaff00000000000000include buildout.cfg include bootstrap.py include MANIFEST.in include README.rst include LICENSE include blockdiag.1 include tox.ini recursive-include examples blockdiagrc *.diag *.png *.svg recursive-include src *.py *.diag *.gif blockdiag-1.3.2/PKG-INFO0000644000076600000240000003547712242633535015357 0ustar tkomiyastaff00000000000000Metadata-Version: 1.0 Name: blockdiag Version: 1.3.2 Summary: blockdiag generates block-diagram image from text Home-page: http://blockdiag.com/ Author: Takeshi Komiya Author-email: i.tkomiya at gmail.com License: Apache License 2.0 Download-URL: http://pypi.python.org/pypi/blockdiag Description: `blockdiag` generate block-diagram image file from spec-text file. Features ======== * Generate block-diagram from dot like text (basic feature). * Multilingualization for node-label (utf-8 only). You can get some examples and generated images on `blockdiag.com `_ . Setup ===== by easy_install ---------------- Make environment:: $ easy_install blockdiag If you want to export as PDF format, give pdf arguments:: $ easy_install "blockdiag[pdf]" by buildout ------------ Make environment:: $ hg clone http://bitbucket.org/tk0miya/blockdiag $ cd blockdiag $ python bootstrap.py $ bin/buildout Copy and modify ini file. example:: $ cp /blockdiag/examples/simple.diag . $ vi simple.diag Please refer to `spec-text setting sample`_ section for the format of the `simpla.diag` configuration file. spec-text setting sample ======================== Few examples are available. You can get more examples at `blockdiag.com `_ . simple.diag ------------ simple.diag is simply define nodes and transitions by dot-like text format:: diagram admin { top_page -> config -> config_edit -> config_confirm -> top_page; } screen.diag ------------ screen.diag is more complexly sample. diaglam nodes have a alternative label and some transitions:: diagram admin { top_page [label = "Top page"]; foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; bar_detail [label = "Detail of BAR"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; logout; top_page -> foo_index; top_page -> bar_detail; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm -> foo_index; foo_index -> foo_edit -> foo_edit_confirm -> foo_index; foo_index -> foo_delete_confirm -> foo_index; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail; } Usage ===== Execute blockdiag command:: $ blockdiag simple.diag $ ls simple.png simple.png Requirements ============ * Python 2.6, 2.7, 3.2, 3.3 * Pillow 2.2.1 * funcparserlib 0.3.6 * setuptools License ======= Apache License 2.0 History ======= 1.3.2 (2013-11-19) ------------------ * Fix bugs 1.3.1 (2013-10-22) ------------------ * Fix bugs 1.3.0 (2013-10-05) ------------------ * Support python 3.2 and 3.3 (thanks to @masayuko) * Drop supports for python 2.4 and 2.5 * Replace dependency: PIL -> Pillow 1.2.4 (2012-11-21) ------------------ * Fix bugs 1.2.3 (2012-11-05) ------------------ * Fix bugs 1.2.2 (2012-10-28) ------------------ * Fix bugs 1.2.1 (2012-10-28) ------------------ * Add external imagedraw plugin supports * Add node attribute: label_orientation* * Fix bugs 1.2.0 (2012-10-22) ------------------ * Optimize algorithm for rendering shadow * Add options to docutils directive * Fix bugs 1.1.8 (2012-09-28) ------------------ * Add --ignore-pil option * Fix bugs 1.1.7 (2012-09-20) ------------------ * Add diagram attribute: shadow_style * Add font path for centos 6.2 * Add a setting 'antialias' in the configuration file * Fix bugs 1.1.6 (2012-06-06) ------------------ * Support for readthedocs.org * reST directive supports :caption: option * Fix bugs 1.1.5 (2012-04-22) ------------------ * Embed source code to SVG document as description * Fix bugs 1.1.4 (2012-03-15) ------------------ * Add new edge.hstyles: oneone, onemany, manyone, manymany * Add edge attribute: description (for build description-tables) * Fix bugs 1.1.3 (2012-02-13) ------------------ * Add new edge type for data-models (thanks to David Lang) * Add --no-transparency option * Fix bugs 1.1.2 (2011-12-26) ------------------ * Support font-index for TrueType Font Collections (.ttc file) * Allow to use reST syntax in descriptions of nodes * Fix bugs 1.1.1 (2011-11-27) ------------------ * Add node attribute: href (thanks to @r_rudi!) * Fix bugs 1.1.0 (2011-11-19) ------------------ * Add shape: square and circle * Add fontfamily attribute for switching fontface * Fix bugs 1.0.3 (2011-11-13) ------------------ * Add plugin: attributes * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value]) * Fix bugs 1.0.2 (2011-11-07) ------------------ * Fix bugs 1.0.1 (2011-11-06) ------------------ * Add group attribute: shape * Fix bugs 1.0.0 (2011-11-04) ------------------ * Add node attribute: linecolor * Rename diagram attributes: * fontsize -> default_fontsize * default_line_color -> default_linecolor * default_text_color -> default_textcolor * Add docutils extention * Fix bugs 0.9.7 (2011-11-01) ------------------ * Add node attribute: fontsize * Add edge attributes: thick, fontsize * Add group attribute: fontsize * Change color of shadow in PDF mode * Add class feature (experimental) * Add handler-plugin framework (experimental) 0.9.6 (2011-10-22) ------------------ * node.style supports dashed_array format style * Fix bugs 0.9.5 (2011-10-19) ------------------ * Add node attributes: width and height * Fix bugs 0.9.4 (2011-10-07) ------------------ * Fix bugs 0.9.3 (2011-10-06) ------------------ * Replace SVG core by original's (simplesvg.py) * Refactored * Fix bugs 0.9.2 (2011-09-30) ------------------ * Add node attribute: textcolor * Add group attribute: textcolor * Add edge attribute: textcolor * Add diagram attributes: default_text_attribute * Fix beginpoint shape and endpoint shape were reversed * Fix bugs 0.9.1 (2011-09-26) ------------------ * Add diagram attributes: default_node_color, default_group_color and default_line_color * Fix bugs 0.9.0 (2011-09-25) ------------------ * Add icon attribute to node * Make transparency to background of PNG images * Fix bugs 0.8.9 (2011-08-09) ------------------ * Fix bugs 0.8.8 (2011-08-08) ------------------ * Fix bugs 0.8.7 (2011-08-06) ------------------ * Fix bugs 0.8.6 (2011-08-01) ------------------ * Support Pillow as replacement of PIL (experimental) * Fix bugs 0.8.5 (2011-07-31) ------------------ * Allow dot characters in node_id * Fix bugs 0.8.4 (2011-07-05) ------------------ * Fix bugs 0.8.3 (2011-07-03) ------------------ * Support input from stdin * Fix bugs 0.8.2 (2011-06-29) ------------------ * Add node.stacked * Add node shapes: dots, none * Add hiragino-font to font search list * Support background image fetching from web * Add diagram.edge_layout (experimental) * Fix bugs 0.8.1 (2011-05-14) ------------------ * Change license to Apache License 2.0 * Fix bugs 0.8.0 (2011-05-04) ------------------ * Add --separate option and --version option * Fix bugs 0.7.8 (2011-04-19) ------------------ * Update layout engine * Update requirements: PIL >= 1.1.5 * Update parser for tokenize performance * Add --nodoctype option * Fix bugs * Add many testcases 0.7.7 (2011-03-29) ------------------ * Fix bugs 0.7.6 (2011-03-26) ------------------ * Add new layout manager for portrait edges * Fix bugs 0.7.5 (2011-03-20) ------------------ * Support multiple nodes relations (cf. A -> B, C) * Support node group declaration at attribute of nodes * Fix bugs 0.7.4 (2011-03-08) ------------------ * Fix bugs 0.7.3 (2011-03-02) ------------------ * Use UTF-8 characters as Name token (by @swtw7466) * Fix htmlentities included in labels was not escaped on SVG images * Fix bugs 0.7.2 (2011-02-28) ------------------ * Add default_shape attribute to diagram 0.7.1 (2011-02-27) ------------------ * Fix edge has broken with antialias option 0.7.0 (2011-02-25) ------------------ * Support node shape 0.6.7 (2011-02-12) ------------------ * Change noderenderer interface to new style * Render dashed ellipse more clearly (contributed by @cocoatomo) * Support PDF exporting 0.6.6 (2011-01-31) ------------------ * Support diagram.shape_namespace * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor * Support plug-in structure to install node shapes * Fix bugs 0.6.5 (2011-01-18) ------------------ * Support node shape (experimental) 0.6.4 (2011-01-17) ------------------ * Fix bugs 0.6.3 (2011-01-15) ------------------ * Fix bugs 0.6.2 (2011-01-08) ------------------ * Fix bugs 0.6.1 (2011-01-07) ------------------ * Implement 'folded' attribute for edge * Refactor layout engine 0.6 (2011-01-02) ------------------ * Support nested groups. 0.5.5 (2010-12-24) ------------------ * Specify direction of edges as syntax (->, --, <-, <->) * Fix bugs. 0.5.4 (2010-12-23) ------------------ * Remove debug codes. 0.5.3 (2010-12-23) ------------------ * Support NodeGroup.label. * Implement --separate option (experimental) * Fix right-up edge overrapped on other nodes. * Support configration file: .blockdiagrc 0.5.2 (2010-11-06) ------------------ * Fix unicode errors for UTF-8'ed SVG exportion. * Refactoring codes for running on GAE. 0.5.1 (2010-10-26) ------------------ * Fix license text on diagparser.py * Update layout engine. 0.5 (2010-10-15) ------------------ * Support background-image of node (SVG) * Support labels for edge. * Fix bugs. 0.4.2 (2010-10-10) ------------------ * Support background-color of node groups. * Draw edge has jumped at edge's cross-points. * Fix bugs. 0.4.1 (2010-10-07) ------------------ * Fix bugs. 0.4 (2010-10-07) ------------------ * Support SVG exporting. * Support dashed edge drawing. * Support background image of nodes (PNG only) 0.3.1 (2010-09-29) ------------------ * Fasten anti-alias process. * Fix text was broken on windows. 0.3 (2010-09-26) ------------------ * Add --antialias option. * Fix bugs. 0.2.2 (2010-09-25) ------------------ * Fix edge bugs. 0.2.1 (2010-09-25) ------------------ * Fix bugs. * Fix package style. 0.2 (2010-09-23) ------------------ * Update layout engine. * Support group { ... } sentence for create Node-Groups. * Support numbered badge on node (cf. A [numbered = 5]) 0.1 (2010-09-20) ----------------- * first release Keywords: diagram,generator Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Text Processing :: Markup blockdiag-1.3.2/README.rst0000644000076600000240000002414112242633512015726 0ustar tkomiyastaff00000000000000`blockdiag` generate block-diagram image file from spec-text file. Features ======== * Generate block-diagram from dot like text (basic feature). * Multilingualization for node-label (utf-8 only). You can get some examples and generated images on `blockdiag.com `_ . Setup ===== by easy_install ---------------- Make environment:: $ easy_install blockdiag If you want to export as PDF format, give pdf arguments:: $ easy_install "blockdiag[pdf]" by buildout ------------ Make environment:: $ hg clone http://bitbucket.org/tk0miya/blockdiag $ cd blockdiag $ python bootstrap.py $ bin/buildout Copy and modify ini file. example:: $ cp /blockdiag/examples/simple.diag . $ vi simple.diag Please refer to `spec-text setting sample`_ section for the format of the `simpla.diag` configuration file. spec-text setting sample ======================== Few examples are available. You can get more examples at `blockdiag.com `_ . simple.diag ------------ simple.diag is simply define nodes and transitions by dot-like text format:: diagram admin { top_page -> config -> config_edit -> config_confirm -> top_page; } screen.diag ------------ screen.diag is more complexly sample. diaglam nodes have a alternative label and some transitions:: diagram admin { top_page [label = "Top page"]; foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; bar_detail [label = "Detail of BAR"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; logout; top_page -> foo_index; top_page -> bar_detail; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm -> foo_index; foo_index -> foo_edit -> foo_edit_confirm -> foo_index; foo_index -> foo_delete_confirm -> foo_index; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail; } Usage ===== Execute blockdiag command:: $ blockdiag simple.diag $ ls simple.png simple.png Requirements ============ * Python 2.6, 2.7, 3.2, 3.3 * Pillow 2.2.1 * funcparserlib 0.3.6 * setuptools License ======= Apache License 2.0 History ======= 1.3.2 (2013-11-19) ------------------ * Fix bugs 1.3.1 (2013-10-22) ------------------ * Fix bugs 1.3.0 (2013-10-05) ------------------ * Support python 3.2 and 3.3 (thanks to @masayuko) * Drop supports for python 2.4 and 2.5 * Replace dependency: PIL -> Pillow 1.2.4 (2012-11-21) ------------------ * Fix bugs 1.2.3 (2012-11-05) ------------------ * Fix bugs 1.2.2 (2012-10-28) ------------------ * Fix bugs 1.2.1 (2012-10-28) ------------------ * Add external imagedraw plugin supports * Add node attribute: label_orientation* * Fix bugs 1.2.0 (2012-10-22) ------------------ * Optimize algorithm for rendering shadow * Add options to docutils directive * Fix bugs 1.1.8 (2012-09-28) ------------------ * Add --ignore-pil option * Fix bugs 1.1.7 (2012-09-20) ------------------ * Add diagram attribute: shadow_style * Add font path for centos 6.2 * Add a setting 'antialias' in the configuration file * Fix bugs 1.1.6 (2012-06-06) ------------------ * Support for readthedocs.org * reST directive supports :caption: option * Fix bugs 1.1.5 (2012-04-22) ------------------ * Embed source code to SVG document as description * Fix bugs 1.1.4 (2012-03-15) ------------------ * Add new edge.hstyles: oneone, onemany, manyone, manymany * Add edge attribute: description (for build description-tables) * Fix bugs 1.1.3 (2012-02-13) ------------------ * Add new edge type for data-models (thanks to David Lang) * Add --no-transparency option * Fix bugs 1.1.2 (2011-12-26) ------------------ * Support font-index for TrueType Font Collections (.ttc file) * Allow to use reST syntax in descriptions of nodes * Fix bugs 1.1.1 (2011-11-27) ------------------ * Add node attribute: href (thanks to @r_rudi!) * Fix bugs 1.1.0 (2011-11-19) ------------------ * Add shape: square and circle * Add fontfamily attribute for switching fontface * Fix bugs 1.0.3 (2011-11-13) ------------------ * Add plugin: attributes * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value]) * Fix bugs 1.0.2 (2011-11-07) ------------------ * Fix bugs 1.0.1 (2011-11-06) ------------------ * Add group attribute: shape * Fix bugs 1.0.0 (2011-11-04) ------------------ * Add node attribute: linecolor * Rename diagram attributes: * fontsize -> default_fontsize * default_line_color -> default_linecolor * default_text_color -> default_textcolor * Add docutils extention * Fix bugs 0.9.7 (2011-11-01) ------------------ * Add node attribute: fontsize * Add edge attributes: thick, fontsize * Add group attribute: fontsize * Change color of shadow in PDF mode * Add class feature (experimental) * Add handler-plugin framework (experimental) 0.9.6 (2011-10-22) ------------------ * node.style supports dashed_array format style * Fix bugs 0.9.5 (2011-10-19) ------------------ * Add node attributes: width and height * Fix bugs 0.9.4 (2011-10-07) ------------------ * Fix bugs 0.9.3 (2011-10-06) ------------------ * Replace SVG core by original's (simplesvg.py) * Refactored * Fix bugs 0.9.2 (2011-09-30) ------------------ * Add node attribute: textcolor * Add group attribute: textcolor * Add edge attribute: textcolor * Add diagram attributes: default_text_attribute * Fix beginpoint shape and endpoint shape were reversed * Fix bugs 0.9.1 (2011-09-26) ------------------ * Add diagram attributes: default_node_color, default_group_color and default_line_color * Fix bugs 0.9.0 (2011-09-25) ------------------ * Add icon attribute to node * Make transparency to background of PNG images * Fix bugs 0.8.9 (2011-08-09) ------------------ * Fix bugs 0.8.8 (2011-08-08) ------------------ * Fix bugs 0.8.7 (2011-08-06) ------------------ * Fix bugs 0.8.6 (2011-08-01) ------------------ * Support Pillow as replacement of PIL (experimental) * Fix bugs 0.8.5 (2011-07-31) ------------------ * Allow dot characters in node_id * Fix bugs 0.8.4 (2011-07-05) ------------------ * Fix bugs 0.8.3 (2011-07-03) ------------------ * Support input from stdin * Fix bugs 0.8.2 (2011-06-29) ------------------ * Add node.stacked * Add node shapes: dots, none * Add hiragino-font to font search list * Support background image fetching from web * Add diagram.edge_layout (experimental) * Fix bugs 0.8.1 (2011-05-14) ------------------ * Change license to Apache License 2.0 * Fix bugs 0.8.0 (2011-05-04) ------------------ * Add --separate option and --version option * Fix bugs 0.7.8 (2011-04-19) ------------------ * Update layout engine * Update requirements: PIL >= 1.1.5 * Update parser for tokenize performance * Add --nodoctype option * Fix bugs * Add many testcases 0.7.7 (2011-03-29) ------------------ * Fix bugs 0.7.6 (2011-03-26) ------------------ * Add new layout manager for portrait edges * Fix bugs 0.7.5 (2011-03-20) ------------------ * Support multiple nodes relations (cf. A -> B, C) * Support node group declaration at attribute of nodes * Fix bugs 0.7.4 (2011-03-08) ------------------ * Fix bugs 0.7.3 (2011-03-02) ------------------ * Use UTF-8 characters as Name token (by @swtw7466) * Fix htmlentities included in labels was not escaped on SVG images * Fix bugs 0.7.2 (2011-02-28) ------------------ * Add default_shape attribute to diagram 0.7.1 (2011-02-27) ------------------ * Fix edge has broken with antialias option 0.7.0 (2011-02-25) ------------------ * Support node shape 0.6.7 (2011-02-12) ------------------ * Change noderenderer interface to new style * Render dashed ellipse more clearly (contributed by @cocoatomo) * Support PDF exporting 0.6.6 (2011-01-31) ------------------ * Support diagram.shape_namespace * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor * Support plug-in structure to install node shapes * Fix bugs 0.6.5 (2011-01-18) ------------------ * Support node shape (experimental) 0.6.4 (2011-01-17) ------------------ * Fix bugs 0.6.3 (2011-01-15) ------------------ * Fix bugs 0.6.2 (2011-01-08) ------------------ * Fix bugs 0.6.1 (2011-01-07) ------------------ * Implement 'folded' attribute for edge * Refactor layout engine 0.6 (2011-01-02) ------------------ * Support nested groups. 0.5.5 (2010-12-24) ------------------ * Specify direction of edges as syntax (->, --, <-, <->) * Fix bugs. 0.5.4 (2010-12-23) ------------------ * Remove debug codes. 0.5.3 (2010-12-23) ------------------ * Support NodeGroup.label. * Implement --separate option (experimental) * Fix right-up edge overrapped on other nodes. * Support configration file: .blockdiagrc 0.5.2 (2010-11-06) ------------------ * Fix unicode errors for UTF-8'ed SVG exportion. * Refactoring codes for running on GAE. 0.5.1 (2010-10-26) ------------------ * Fix license text on diagparser.py * Update layout engine. 0.5 (2010-10-15) ------------------ * Support background-image of node (SVG) * Support labels for edge. * Fix bugs. 0.4.2 (2010-10-10) ------------------ * Support background-color of node groups. * Draw edge has jumped at edge's cross-points. * Fix bugs. 0.4.1 (2010-10-07) ------------------ * Fix bugs. 0.4 (2010-10-07) ------------------ * Support SVG exporting. * Support dashed edge drawing. * Support background image of nodes (PNG only) 0.3.1 (2010-09-29) ------------------ * Fasten anti-alias process. * Fix text was broken on windows. 0.3 (2010-09-26) ------------------ * Add --antialias option. * Fix bugs. 0.2.2 (2010-09-25) ------------------ * Fix edge bugs. 0.2.1 (2010-09-25) ------------------ * Fix bugs. * Fix package style. 0.2 (2010-09-23) ------------------ * Update layout engine. * Support group { ... } sentence for create Node-Groups. * Support numbered badge on node (cf. A [numbered = 5]) 0.1 (2010-09-20) ----------------- * first release blockdiag-1.3.2/setup.cfg0000644000076600000240000000020612242633535016061 0ustar tkomiyastaff00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [build] build-base = _build [sdist] formats = gztar [flake8] ignore = _ blockdiag-1.3.2/setup.py0000644000076600000240000000646112227707660015767 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import os import sys from setuptools import setup, find_packages sys.path.insert(0, 'src') import blockdiag classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: System Administrators", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Topic :: Software Development", "Topic :: Software Development :: Documentation", "Topic :: Text Processing :: Markup", ] requires = ['setuptools', 'funcparserlib', 'webcolors', 'Pillow'] test_requires = ['Nose', 'pep8>=1.3'] # only for Python2.6 if sys.version_info > (2, 6) and sys.version_info < (2, 7): requires.append('OrderedDict') test_requires.append('unittest2') setup( name='blockdiag', version=blockdiag.__version__, description='blockdiag generates block-diagram image from text', long_description=open("README.rst").read(), classifiers=classifiers, keywords=['diagram', 'generator'], author='Takeshi Komiya', author_email='i.tkomiya at gmail.com', url='http://blockdiag.com/', download_url='http://pypi.python.org/pypi/blockdiag', license='Apache License 2.0', py_modules=['blockdiag_sphinxhelper'], packages=find_packages('src'), package_dir={'': 'src'}, package_data={'': ['buildout.cfg']}, include_package_data=True, install_requires=requires, extras_require=dict( test=test_requires, pdf=[ 'reportlab', ], rst=[ 'docutils', ], ), test_suite='nose.collector', tests_require=test_requires, entry_points=""" [console_scripts] blockdiag = blockdiag.command:main [blockdiag_noderenderer] box = blockdiag.noderenderer.box square = blockdiag.noderenderer.square roundedbox = blockdiag.noderenderer.roundedbox diamond = blockdiag.noderenderer.diamond minidiamond = blockdiag.noderenderer.minidiamond mail = blockdiag.noderenderer.mail note = blockdiag.noderenderer.note cloud = blockdiag.noderenderer.cloud circle = blockdiag.noderenderer.circle ellipse = blockdiag.noderenderer.ellipse beginpoint = blockdiag.noderenderer.beginpoint endpoint = blockdiag.noderenderer.endpoint actor = blockdiag.noderenderer.actor flowchart.database = blockdiag.noderenderer.flowchart.database flowchart.input = blockdiag.noderenderer.flowchart.input flowchart.loopin = blockdiag.noderenderer.flowchart.loopin flowchart.loopout = blockdiag.noderenderer.flowchart.loopout flowchart.terminator = blockdiag.noderenderer.flowchart.terminator textbox = blockdiag.noderenderer.textbox dots = blockdiag.noderenderer.dots none = blockdiag.noderenderer.none [blockdiag_plugins] attributes = blockdiag.plugins.attributes autoclass = blockdiag.plugins.autoclass [blockdiag_imagedrawers] imagedraw_png = blockdiag.imagedraw.png imagedraw_svg = blockdiag.imagedraw.svg imagedraw_pdf = blockdiag.imagedraw.pdf """, ) blockdiag-1.3.2/src/0000755000076600000240000000000012242633534015030 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/0000755000076600000240000000000012242633534016747 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/__init__.py0000644000076600000240000000117312242633471021062 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __version__ = '1.3.2' blockdiag-1.3.2/src/blockdiag/builder.py0000644000076600000240000006247012234663754020770 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag import parser from blockdiag.elements import Diagram, DiagramNode, NodeGroup, DiagramEdge from blockdiag.utils import unquote, XY from blockdiag.utils.compat import cmp_to_key class DiagramTreeBuilder: def build(self, tree): self.diagram = Diagram() self.instantiate(self.diagram, tree) for subgroup in self.diagram.traverse_groups(): if len(subgroup.nodes) == 0: subgroup.group.nodes.remove(subgroup) self.bind_edges(self.diagram) return self.diagram def is_related_group(self, group1, group2): if group1.is_parent(group2) or group2.is_parent(group1): return True else: return False def belong_to(self, node, group): if node.group and node.group.level > group.level: override = False else: override = True if node.group and node.group != group and override: if not self.is_related_group(node.group, group): msg = "could not belong to two groups: %s" % node.id raise RuntimeError(msg) old_group = node.group parent = group.parent(old_group.level + 1) if parent: if parent in old_group.nodes: old_group.nodes.remove(parent) index = old_group.nodes.index(node) old_group.nodes.insert(index + 1, parent) old_group.nodes.remove(node) node.group = None if node.group is None: node.group = group if node not in group.nodes: group.nodes.append(node) def instantiate(self, group, tree): for stmt in tree.stmts: # Translate Node having group attribute to Group if isinstance(stmt, parser.Node): group_attr = [a for a in stmt.attrs if a.name == 'group'] if group_attr: group_id = group_attr[-1] stmt.attrs.remove(group_id) if group_id.value != group.id: stmt = parser.Group(group_id.value, [stmt]) # Instantiate statements if isinstance(stmt, parser.Node): node = DiagramNode.get(stmt.id) node.set_attributes(stmt.attrs) self.belong_to(node, group) elif isinstance(stmt, parser.Edge): from_nodes = [DiagramNode.get(n) for n in stmt.from_nodes] to_nodes = [DiagramNode.get(n) for n in stmt.to_nodes] for node in from_nodes + to_nodes: self.belong_to(node, group) for node1 in from_nodes: for node2 in to_nodes: edge = DiagramEdge.get(node1, node2) edge.set_dir(stmt.edge_type) edge.set_attributes(stmt.attrs) elif isinstance(stmt, parser.Group): subgroup = NodeGroup.get(stmt.id) subgroup.level = group.level + 1 self.belong_to(subgroup, group) self.instantiate(subgroup, stmt) elif isinstance(stmt, parser.Attr): group.set_attribute(stmt) elif isinstance(stmt, parser.Extension): if stmt.type == 'class': name = unquote(stmt.name) Diagram.classes[name] = stmt elif stmt.type == 'plugin': self.diagram.set_plugin(stmt.name, stmt.attrs) elif isinstance(stmt, parser.Statements): self.instantiate(group, stmt) group.update_order() return group def bind_edges(self, group): for node in group.nodes: if isinstance(node, DiagramNode): group.edges += DiagramEdge.find(node) else: self.bind_edges(node) class DiagramLayoutManager: def __init__(self, diagram): self.diagram = diagram self.circulars = [] self.heightRefs = [] self.coordinates = [] def run(self): if isinstance(self.diagram, Diagram): for group in self.diagram.traverse_groups(): self.__class__(group).run() self.edges = DiagramEdge.find_by_level(self.diagram.level) self.do_layout() self.diagram.fixiate() if self.diagram.orientation == 'portrait': self.rotate_diagram() def rotate_diagram(self): for node in self.diagram.traverse_nodes(): node.xy = XY(node.xy.y, node.xy.x) node.colwidth, node.colheight = (node.colheight, node.colwidth) if isinstance(node, NodeGroup): if node.orientation == 'portrait': node.orientation = 'landscape' else: node.orientation = 'portrait' xy = (self.diagram.colheight, self.diagram.colwidth) self.diagram.colwidth, self.diagram.colheight = xy def do_layout(self): self.detect_circulars() self.set_node_xpos() self.adjust_node_order() height = 0 for node in self.diagram.nodes: if node.xy.x == 0: self.set_node_ypos(node, height) height = max(xy.y for xy in self.coordinates) + 1 def get_related_nodes(self, node, parent=False, child=False): uniq = {} for edge in self.edges: if edge.folded: continue if parent and edge.node2 == node: uniq[edge.node1] = 1 elif child and edge.node1 == node: uniq[edge.node2] = 1 related = [] for uniq_node in uniq.keys(): if uniq_node == node: pass elif uniq_node.group != node.group: pass else: related.append(uniq_node) related.sort(key=lambda x: x.order) return related def get_parent_nodes(self, node): return self.get_related_nodes(node, parent=True) def get_child_nodes(self, node): return self.get_related_nodes(node, child=True) def detect_circulars(self): for node in self.diagram.nodes: if not [x for x in self.circulars if node in x]: self.detect_circulars_sub(node, [node]) # remove part of other circular for c1 in self.circulars[:]: for c2 in self.circulars: intersect = set(c1) & set(c2) if c1 != c2 and set(c1) == intersect: if c1 in self.circulars: self.circulars.remove(c1) break if c1 != c2 and intersect: if c1 in self.circulars: self.circulars.remove(c1) self.circulars.remove(c2) self.circulars.append(c1 + c2) break def detect_circulars_sub(self, node, parents): for child in self.get_child_nodes(node): if child in parents: i = parents.index(child) if parents[i:] not in self.circulars: self.circulars.append(parents[i:]) else: self.detect_circulars_sub(child, parents + [child]) def is_circular_ref(self, node1, node2): for circular in self.circulars: if node1 in circular and node2 in circular: parents = [] for node in circular: for parent in self.get_parent_nodes(node): if not parent in circular: parents.append(parent) for parent in sorted(parents, key=lambda x: x.order): children = self.get_child_nodes(parent) if node1 in children and node2 in children: if circular.index(node1) > circular.index(node2): return True elif node2 in children: return True elif node1 in children: return False else: if circular.index(node1) > circular.index(node2): return True return False def set_node_xpos(self, depth=0): for node in self.diagram.nodes: if node.xy.x != depth: continue for child in self.get_child_nodes(node): if self.is_circular_ref(node, child): pass elif node == child: pass elif child.xy.x > node.xy.x + node.colwidth: pass else: child.xy = XY(node.xy.x + node.colwidth, 0) depther_node = [x for x in self.diagram.nodes if x.xy.x > depth] if len(depther_node) > 0: self.set_node_xpos(depth + 1) def adjust_node_order(self): for node in list(self.diagram.nodes): parents = self.get_parent_nodes(node) if len(set(parents)) > 1: for i in range(1, len(parents)): node1 = parents[i - 1] node2 = parents[i] if node1.xy.x == node2.xy.x: idx1 = self.diagram.nodes.index(node1) idx2 = self.diagram.nodes.index(node2) if idx1 < idx2: self.diagram.nodes.remove(node2) self.diagram.nodes.insert(idx1 + 1, node2) else: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) children = self.get_child_nodes(node) if len(set(children)) > 1: for i in range(1, len(children)): node1 = children[i - 1] node2 = children[i] idx1 = self.diagram.nodes.index(node1) idx2 = self.diagram.nodes.index(node2) if node1.xy.x == node2.xy.x: if idx1 < idx2: self.diagram.nodes.remove(node2) self.diagram.nodes.insert(idx1 + 1, node2) else: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) elif self.is_circular_ref(node1, node2): pass else: if node1.xy.x < node2.xy.x: self.diagram.nodes.remove(node2) self.diagram.nodes.insert(idx1 + 1, node2) else: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) if isinstance(node, NodeGroup): children = self.get_child_nodes(node) if len(set(children)) > 1: while True: exchange = 0 for i in range(1, len(children)): node1 = children[i - 1] node2 = children[i] idx1 = self.diagram.nodes.index(node1) idx2 = self.diagram.nodes.index(node2) ret = self.compare_child_node_order(node, node1, node2) if ret > 0 and idx1 < idx2: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) exchange += 1 if exchange == 0: break self.diagram.update_order() def compare_child_node_order(self, parent, node1, node2): def compare(x, y): x = x.duplicate() y = y.duplicate() while x.node1 == y.node1 and x.node1.group is not None: x.node1 = x.node1.group y.node1 = y.node1.group # cmp x.node1.order and y.node1.order if x.node1.order < y.node1.order: return -1 elif x.node1.order == y.node1.order: return 0 else: return 1 edges = (DiagramEdge.find(parent, node1) + DiagramEdge.find(parent, node2)) edges.sort(key=cmp_to_key(compare)) if len(edges) == 0: return 0 elif edges[0].node2 == node2: return 1 else: return -1 def mark_xy(self, xy, width, height): for w in range(width): for h in range(height): self.coordinates.append(XY(xy.x + w, xy.y + h)) def set_node_ypos(self, node, height=0): for x in range(node.colwidth): for y in range(node.colheight): xy = XY(node.xy.x + x, height + y) if xy in self.coordinates: return False node.xy = XY(node.xy.x, height) self.mark_xy(node.xy, node.colwidth, node.colheight) def cmp(x, y): if x.xy.x < y.xy.y: return -1 elif x.xy.x == y.xy.y: return 0 else: return 1 count = 0 children = self.get_child_nodes(node) children.sort(key=cmp_to_key(cmp)) grandchild = 0 for child in children: if self.get_child_nodes(child): grandchild += 1 prev_child = None for child in children: if child.id in self.heightRefs: pass elif node.xy.x >= child.xy.x: pass else: if isinstance(node, NodeGroup): parent_height = self.get_parent_node_ypos(node, child) if parent_height and parent_height > height: height = parent_height if (prev_child and grandchild > 1 and (not self.is_rhombus(prev_child, child))): coord = [p.y for p in self.coordinates if p.x > child.xy.x] if coord and max(coord) >= node.xy.y: height = max(coord) + 1 while True: if self.set_node_ypos(child, height): child.xy = XY(child.xy.x, height) self.mark_xy(child.xy, child.colwidth, child.colheight) self.heightRefs.append(child.id) count += 1 break else: if count == 0: return False height += 1 height += 1 prev_child = child return True def is_rhombus(self, node1, node2): ret = False while True: if node1 == node2: ret = True break child1 = self.get_child_nodes(node1) child2 = self.get_child_nodes(node2) if len(child1) != 1 or len(child2) != 1: break elif node1.xy.x > child1[0].xy.x or node2.xy.x > child2[0].xy.x: break else: node1 = child1[0] node2 = child2[0] return ret def get_parent_node_ypos(self, parent, child): heights = [] for e in DiagramEdge.find(parent, child): y = parent.xy.y node = e.node1 while node != parent: y += node.xy.y node = node.group heights.append(y) if heights: return min(heights) else: return None class EdgeLayoutManager(object): def __init__(self, diagram): self.diagram = diagram @property def groups(self): if self.diagram.separated: seq = self.diagram.nodes else: seq = self.diagram.traverse_groups(preorder=True) for group in seq: if not group.drawable: yield group @property def nodes(self): if self.diagram.separated: seq = self.diagram.nodes else: seq = self.diagram.traverse_nodes() for node in seq: if node.drawable: yield node @property def edges(self): for edge in (e for e in self.diagram.edges if e.style != 'none'): yield edge for group in self.groups: for edge in (e for e in group.edges if e.style != 'none'): yield edge def run(self): for edge in self.edges: _dir = edge.direction if edge.node1.group.orientation == 'landscape': if _dir == 'right': r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'right-up': r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'right-down': if self.diagram.edge_layout == 'flowchart': r = range(edge.node1.xy.y, edge.node2.xy.y) for y in r: xy = (edge.node1.xy.x, y + 1) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node2.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir in ('left-down', 'down'): r = range(edge.node1.xy.y + 1, edge.node2.xy.y) for y in r: xy = (edge.node1.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'up': r = range(edge.node2.xy.y + 1, edge.node1.xy.y) for y in r: xy = (edge.node1.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 else: if _dir == 'right': r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir in ('left-down', 'down'): r = range(edge.node1.xy.y + 1, edge.node2.xy.y) for y in r: xy = (edge.node1.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'right-down': if self.diagram.edge_layout == 'flowchart': r = range(edge.node1.xy.x, edge.node2.xy.x) for x in r: xy = (x + 1, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 r = range(edge.node1.xy.y + 1, edge.node2.xy.y) for y in r: xy = (edge.node2.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 class ScreenNodeBuilder: @classmethod def build(cls, tree, layout=True): DiagramNode.clear() DiagramEdge.clear() NodeGroup.clear() Diagram.clear() return cls(tree, layout).run() def __init__(self, tree, layout): self.diagram = DiagramTreeBuilder().build(tree) self.layout = layout def run(self): if self.layout: DiagramLayoutManager(self.diagram).run() self.diagram.fixiate(True) EdgeLayoutManager(self.diagram).run() return self.diagram class SeparateDiagramBuilder(ScreenNodeBuilder): @property def _groups(self): # Store nodes and edges of subgroups nodes = {self.diagram: self.diagram.nodes} edges = {self.diagram: self.diagram.edges} levels = {self.diagram: self.diagram.level} for group in self.diagram.traverse_groups(): nodes[group] = group.nodes edges[group] = group.edges levels[group] = group.level groups = {} orders = {} for node in self.diagram.traverse_nodes(): groups[node] = node.group orders[node] = node.order for group in self.diagram.traverse_groups(): yield group # Restore nodes, groups and edges for g in nodes: g.nodes = nodes[g] g.edges = edges[g] g.level = levels[g] for n in groups: n.group = groups[n] n.order = orders[n] n.xy = XY(0, 0) n.colwidth = 1 n.colheight = 1 n.separated = False for edge in DiagramEdge.find_all(): edge.skipped = False edge.crosspoints = [] yield self.diagram def _filter_edges(self, edges, parent, level): filtered = {} for e in edges: if e.node1.group.is_parent(parent): if e.node1.group.level > level: e = e.duplicate() if isinstance(e.node1, NodeGroup): e.node1 = e.node1.parent(level + 1) else: e.node1 = e.node1.group.parent(level + 1) else: continue if e.node2.group.is_parent(parent): if e.node2.group.level > level: e = e.duplicate() if isinstance(e.node2, NodeGroup): e.node2 = e.node2.parent(level + 1) else: e.node2 = e.node2.group.parent(level + 1) else: continue filtered[(e.node1, e.node2)] = e return filtered.values() def run(self): for i, group in enumerate(self._groups): base = self.diagram.duplicate() base.level = group.level - 1 # bind edges on base diagram (outer the group) edges = (DiagramEdge.find(None, group) + DiagramEdge.find(group, None)) base.edges = self._filter_edges(edges, self.diagram, group.level) # bind edges on target group (inner the group) subgroups = group.traverse_groups() edges = sum([g.edges for g in subgroups], group.edges) group.edges = [] for e in self._filter_edges(edges, group, group.level): if isinstance(e.node1, NodeGroup) and e.node1 == e.node2: pass else: group.edges.append(e) # clear subgroups in the group for g in group.nodes: if isinstance(g, NodeGroup): g.nodes = [] g.edges = [] g.separated = True # pick up nodes to base diagram nodes1 = [e.node1 for e in DiagramEdge.find(None, group)] nodes1.sort(key=lambda x: x.order) nodes2 = [e.node2 for e in DiagramEdge.find(group, None)] nodes2.sort(key=lambda x: x.order) nodes = nodes1 + [group] + nodes2 for i, n in enumerate(nodes): n.order = i if n not in base.nodes: base.nodes.append(n) n.group = base if isinstance(group, Diagram): base = group DiagramLayoutManager(base).run() base.fixiate(True) EdgeLayoutManager(base).run() yield base blockdiag-1.3.2/src/blockdiag/command.py0000644000076600000240000000420312222421703020725 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import sys import blockdiag import blockdiag.builder import blockdiag.drawer import blockdiag.parser from blockdiag.utils.bootstrap import Application, Options class BlockdiagOptions(Options): def build_parser(self): super(BlockdiagOptions, self).build_parser() self.parser.add_option( '-s', '--separate', action='store_true', help='Separate diagram images for each group (SVG only)' ) class BlockdiagApp(Application): module = blockdiag def parse_options(self, args): self.options = BlockdiagOptions(self.module).parse(args) def build_diagram(self, tree): if not self.options.separate: return super(BlockdiagApp, self).build_diagram(tree) else: DiagramBuilder = self.module.builder.SeparateDiagramBuilder DiagramDraw = self.module.drawer.DiagramDraw basename = re.sub('.svg$', '', self.options.output) for i, group in enumerate(DiagramBuilder.build(tree)): outfile = '%s_%d.svg' % (basename, i + 1) draw = DiagramDraw(self.options.type, group, outfile, fontmap=self.fontmap, antialias=self.options.antialias, nodoctype=self.options.nodoctype, transparency=self.options.transparency) draw.draw() draw.save() return 0 def main(args=sys.argv[1:]): return BlockdiagApp().run(args) blockdiag-1.3.2/src/blockdiag/drawer.py0000644000076600000240000001503512222512434020602 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from collections import defaultdict from blockdiag import imagedraw, noderenderer from blockdiag.metrics import AutoScaler, DiagramMetrics class DiagramDraw(object): shadow_colors = defaultdict(lambda: (0, 0, 0)) shadow_colors['PNG'] = (64, 64, 64) shadow_colors['PDF'] = (144, 144, 144) def __init__(self, _format, diagram, filename=None, **kwargs): self.format = _format.upper() self.diagram = diagram self.fill = kwargs.get('fill', (0, 0, 0)) self.badgeFill = kwargs.get('badgeFill', 'pink') self.filename = filename self.shadow = self.shadow_colors[self.format.upper()] if self.format == 'PNG' and kwargs.get('antialias'): self.scale_ratio = 2 else: self.scale_ratio = 1 self.drawer = imagedraw.create(self.format, self.filename, filters=['linejump'], scale_ratio=self.scale_ratio, **kwargs) self.metrics = self.create_metrics(kwargs.get('basediagram', diagram), drawer=self.drawer, **kwargs) if self.scale_ratio == 2: self.metrics = AutoScaler(self.metrics, scale_ratio=self.scale_ratio) self.drawer.set_canvas_size(self.pagesize()) self.drawer.set_options(jump_radius=self.metrics.cellsize / 2) def create_metrics(self, *args, **kwargs): return DiagramMetrics(*args, **kwargs) @property def nodes(self): for node in self.diagram.traverse_nodes(): if node.drawable: yield node @property def groups(self): for group in self.diagram.traverse_groups(preorder=True): if not group.drawable: yield group @property def edges(self): edges = self.diagram.traverse_edges(preorder=True) for edge in (e for e in edges if e.style != 'none'): yield edge def pagesize(self, scaled=False): if scaled: metrics = self.metrics else: metrics = self.metrics.original_metrics width = self.diagram.colwidth height = self.diagram.colheight return metrics.pagesize(width, height) def draw(self, **kwargs): # switch metrics object during draw backgrounds temp, self.metrics = self.metrics, self.metrics.original_metrics self._draw_background() self.metrics = temp if self.scale_ratio > 1: pagesize = self.pagesize(scaled=True) self.drawer.resizeCanvas(pagesize) self._draw_elements(**kwargs) def _draw_background(self): # Draw node groups. for group in self.groups: if group.shape == 'box': box = self.metrics.group(group).marginbox if group.href and self.format == 'SVG': drawer = self.drawer.anchor(group.href) else: drawer = self.drawer drawer.rectangle(box, fill=group.color, filter='blur') # Drop node shadows. for node in self.nodes: if node.color != 'none' and self.diagram.shadow_style != 'none': r = noderenderer.get(node.shape) shape = r(node, self.metrics) if node.href and self.format == 'SVG': drawer = self.drawer.anchor(node.href) else: drawer = self.drawer shape.render(drawer, self.format, fill=self.shadow, shadow=True, style=self.diagram.shadow_style) def _draw_elements(self, **kwargs): for node in self.nodes: self.node(node, **kwargs) for edge in self.edges: self.edge(edge) for edge in self.edges: self.edge_label(edge) for group in self.groups: if group.shape == 'line': box = self.metrics.group(group).marginbox self.drawer.rectangle(box, fill='none', outline=group.color, style=group.style, thick=group.thick) for node in self.groups: self.group_label(node, **kwargs) def node(self, node, **kwargs): r = noderenderer.get(node.shape) shape = r(node, self.metrics) if node.href and self.format == 'SVG': drawer = self.drawer.anchor(node.href) else: drawer = self.drawer shape.render(drawer, self.format, fill=self.fill, badgeFill=self.badgeFill) def group_label(self, group): m = self.metrics.group(group) font = self.metrics.font_for(group) if group.label and not group.separated: self.drawer.textarea(m.grouplabelbox, group.label, font=font, fill=group.textcolor) elif group.label: self.drawer.textarea(m.corebox, group.label, font=font, fill=group.textcolor) def edge(self, edge): metrics = self.metrics.edge(edge) for line in metrics.shaft.polylines: self.drawer.line(line, fill=edge.color, thick=edge.thick, style=edge.style, jump=True) for head in metrics.heads: if edge.hstyle in ('generalization', 'aggregation'): self.drawer.polygon(head, outline=edge.color, fill='white') else: self.drawer.polygon(head, outline=edge.color, fill=edge.color) def edge_label(self, edge): if edge.label: metrics = self.metrics.edge(edge) font = self.metrics.font_for(edge) self.drawer.textarea(metrics.labelbox, edge.label, font=font, fill=edge.textcolor, outline=self.fill) def save(self, size=None): return self.drawer.save(self.filename, size, self.format) blockdiag-1.3.2/src/blockdiag/elements.py0000644000076600000240000004661512234612663021152 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re import sys import copy from blockdiag.utils import images, unquote, urlutil, uuid, XY from blockdiag.utils.compat import u from blockdiag import noderenderer, plugins class Base(object): basecolor = (255, 255, 255) textcolor = (0, 0, 0) fontfamily = None fontsize = None style = None int_attrs = ['colwidth', 'colheight', 'fontsize'] @classmethod def set_default_color(cls, color): cls.basecolor = images.color_to_rgb(color) @classmethod def set_default_text_color(cls, color): cls.textcolor = images.color_to_rgb(color) @classmethod def set_default_fontfamily(cls, fontfamily): cls.fontfamily = fontfamily @classmethod def set_default_fontsize(cls, fontsize): cls.fontsize = int(fontsize) @classmethod def clear(cls): cls.basecolor = (255, 255, 255) cls.textcolor = (0, 0, 0) cls.fontfamily = None cls.fontsize = None def duplicate(self): return copy.copy(self) def set_attribute(self, attr): name = attr.name value = unquote(attr.value) if name == 'class': if value in Diagram.classes: klass = Diagram.classes[value] self.set_attributes(klass.attrs) else: msg = "Unknown class: %s" % value raise AttributeError(msg) elif hasattr(self, "set_%s" % name): getattr(self, "set_%s" % name)(value) elif name in self.int_attrs: setattr(self, name, int(value)) elif hasattr(self, name) and not callable(getattr(self, name)): setattr(self, name, value) else: class_name = self.__class__.__name__ msg = "Unknown attribute: %s.%s" % (class_name, attr.name) raise AttributeError(msg) def set_attributes(self, attrs): for attr in attrs: self.set_attribute(attr) def set_style(self, value): if re.search('^(?:none|solid|dotted|dashed|\d+(,\d+)*)$', value, re.I): self.style = value.lower() else: class_name = self.__class__.__name__ msg = "WARNING: unknown %s style: %s\n" % (class_name, value) raise AttributeError(msg) class Element(Base): namespace = {} int_attrs = Base.int_attrs + ['width', 'height'] @classmethod def get(cls, elemid): if not elemid: elemid = uuid.generate() unquote_id = unquote(elemid) if unquote_id not in cls.namespace: obj = cls(elemid) cls.namespace[unquote_id] = obj return cls.namespace[unquote_id] @classmethod def clear(cls): super(Element, cls).clear() cls.namespace = {} cls.basecolor = (255, 255, 255) cls.textcolor = (0, 0, 0) def __init__(self, elemid): self.id = unquote(elemid) self.label = '' self.xy = XY(0, 0) self.group = None self.drawable = False self.order = 0 self.color = self.basecolor self.width = None self.height = None self.colwidth = 1 self.colheight = 1 self.stacked = False def __repr__(self): _format = "<%s '%s' %s %dx%d at 0x%08x>" params = (self.__class__.__name__, self.id, str(self.xy), self.colwidth, self.colheight, id(self)) return _format % params def set_color(self, color): self.color = images.color_to_rgb(color) def set_textcolor(self, color): self.textcolor = images.color_to_rgb(color) class DiagramNode(Element): shape = 'box' int_attrs = Element.int_attrs + ['rotate'] linecolor = (0, 0, 0) label_orientation = 'horizontal' desctable = [] attrname = {} @classmethod def set_default_shape(cls, shape): cls.shape = shape @classmethod def set_default_linecolor(cls, color): cls.linecolor = images.color_to_rgb(color) @classmethod def clear(cls): super(DiagramNode, cls).clear() cls.shape = 'box' cls.linecolor = (0, 0, 0) cls.label_orientation = 'horizontal' cls.desctable = ['numbered', 'label', 'description'] cls.attrname = dict(numbered='No', label='Name', description='Description') def __init__(self, elemid): super(DiagramNode, self).__init__(elemid) self.label = unquote(elemid) or '' self.numbered = None self.icon = None self.background = None self.description = None self.rotate = 0 self.drawable = True self.href = None plugins.fire_node_event(self, 'created') def set_attribute(self, attr): super(DiagramNode, self).set_attribute(attr) plugins.fire_node_event(self, 'attr_changed', attr) def set_linecolor(self, color): self.linecolor = images.color_to_rgb(color) def set_shape(self, value): if noderenderer.get(value): self.shape = value else: msg = "WARNING: unknown node shape: %s\n" % value raise AttributeError(msg) def set_icon(self, value): if urlutil.isurl(value) or os.path.isfile(value): self.icon = value else: msg = "WARNING: icon image not found: %s\n" % value sys.stderr.write(msg) def set_background(self, value): if urlutil.isurl(value) or os.path.isfile(value): self.background = value else: msg = "WARNING: background image not found: %s\n" % value sys.stderr.write(msg) def set_stacked(self, _): self.stacked = True def set_label_orientation(self, value): value = value.lower() if value in ('horizontal', 'vertical'): self.label_orientation = value else: msg = "WARNING: unknown label orientation: %s\n" % value raise AttributeError(msg) def to_desctable(self): attrs = [] for name in self.desctable: value = getattr(self, name) if value is None: attrs.append(u("")) else: attrs.append(value) return attrs class NodeGroup(Element): basecolor = (243, 152, 0) @classmethod def clear(cls): super(NodeGroup, cls).clear() cls.basecolor = (243, 152, 0) def __init__(self, elemid): super(NodeGroup, self).__init__(elemid) self.level = 0 self.separated = False self.shape = 'box' self.thick = 3 self.nodes = [] self.edges = [] self.icon = None self.orientation = 'landscape' self.href = None def duplicate(self): copied = super(NodeGroup, self).duplicate() copied.nodes = [] copied.edges = [] return copied def parent(self, level): if self.level < level: return None group = self while group.level != level: group = group.group return group def is_parent(self, other): parent = self.parent(other.level) return parent == other def traverse_nodes(self, preorder=False): for node in self.nodes: if isinstance(node, NodeGroup): if preorder: yield node for subnode in node.traverse_nodes(preorder=preorder): yield subnode if not preorder: yield node else: yield node def traverse_edges(self, preorder=False): if preorder: for edge in self.edges: yield edge for group in self.traverse_groups(preorder): for edge in group.traverse_edges(preorder): yield edge if not preorder: for edge in self.edges: yield edge def traverse_groups(self, preorder=False): for node in self.traverse_nodes(preorder=preorder): if isinstance(node, NodeGroup): yield node def fixiate(self, fixiate_nodes=False): if self.separated: self.colwidth = 1 self.colheight = 1 return elif len(self.nodes) > 0: self.colwidth = max(x.xy.x + x.colwidth for x in self.nodes) self.colheight = max(x.xy.y + x.colheight for x in self.nodes) for node in self.nodes: if fixiate_nodes: node.xy = XY(self.xy.x + node.xy.x, self.xy.y + node.xy.y) if isinstance(node, NodeGroup): node.fixiate(fixiate_nodes) def update_order(self): for i, node in enumerate(self.nodes): node.order = i def set_orientation(self, value): value = value.lower() if value in ('landscape', 'portrait'): self.orientation = value else: msg = "WARNING: unknown diagram orientation: %s\n" % value raise AttributeError(msg) def set_shape(self, value): value = value.lower() if value in ('box', 'line'): self.shape = value else: msg = "WARNING: unknown group shape: %s\n" % value raise AttributeError(msg) class DiagramEdge(Base): basecolor = (0, 0, 0) namespace = {} @classmethod def get(cls, node1, node2): if node1 not in cls.namespace: cls.namespace[node1] = {} if node2 not in cls.namespace[node1]: obj = cls(node1, node2) cls.namespace[node1][node2] = obj return cls.namespace[node1][node2] @classmethod def find(cls, node1, node2=None): if node1 is None and node2 is None: return cls.find_all() elif isinstance(node1, NodeGroup): edges = cls.find(None, node2) edges = (e for e in edges if e.node1.group.is_parent(node1)) return [e for e in edges if not e.node2.group.is_parent(node1)] elif isinstance(node2, NodeGroup): edges = cls.find(node1, None) edges = (e for e in edges if e.node2.group.is_parent(node2)) return [e for e in edges if not e.node1.group.is_parent(node2)] elif node1 is None: return [e for e in cls.find_all() if e.node2 == node2] else: if node1 not in cls.namespace: return [] if node2 is None: return cls.namespace[node1].values() if node2 not in cls.namespace[node1]: return [] return cls.namespace[node1][node2] @classmethod def find_all(cls): for v1 in cls.namespace.values(): for v2 in v1.values(): yield v2 @classmethod def find_by_level(cls, level): edges = [] for e in cls.find_all(): edge = e.duplicate() skips = 0 if edge.node1.group.level < level: skips += 1 else: while edge.node1.group.level != level: edge.node1 = edge.node1.group if edge.node2.group.level < level: skips += 1 else: while edge.node2.group.level != level: edge.node2 = edge.node2.group if skips == 2: continue edges.append(edge) return edges @classmethod def clear(cls): super(DiagramEdge, cls).clear() cls.namespace = {} cls.basecolor = (0, 0, 0) def __init__(self, node1, node2): self.node1 = node1 self.node2 = node2 self.crosspoints = [] self.skipped = 0 self.label = None self.description = None self.dir = 'forward' self.color = self.basecolor self.hstyle = None self.folded = None self.thick = None def __repr__(self): _format = "<%s '%s' %s - '%s' %s at 0x%08x>" params = (self.__class__.__name__, self.node1.id, self.node1.xy, self.node2.id, self.node2.xy, id(self)) return _format % params def set_dir(self, value): value = value.lower() if value in ('back', 'both', 'none', 'forward'): self.dir = value elif value == '-<': self.dir = 'forward' self.hstyle = 'onemany' elif value == '>-': self.dir = 'back' self.hstyle = 'manyone' elif value == '>-<': self.dir = 'both' self.hstyle = 'manymany' elif value == '->': self.dir = 'forward' elif value == '<-': self.dir = 'back' elif value == '<->': self.dir = 'both' elif value == '--': self.dir = 'none' else: msg = "WARNING: unknown edge dir: %s\n" % value raise AttributeError(msg) def set_color(self, color): self.color = images.color_to_rgb(color) def set_hstyle(self, value): value = value.lower() if value in ('generalization', 'composition', 'aggregation'): self.hstyle = value elif value == 'oneone': self.dir = 'none' self.hstyle = value elif value == 'onemany': self.dir = 'forward' self.hstyle = value elif value == 'manyone': self.dir = 'back' self.hstyle = value elif value == 'manymany': self.dir = 'both' self.hstyle = value else: msg = "WARNING: unknown edge hstyle: %s\n" % value raise AttributeError(msg) def set_folded(self, _): self.folded = True def set_nofolded(self, _): self.folded = False def set_thick(self, _): self.thick = 3 @property def direction(self): node1 = self.node1.xy node2 = self.node2.xy if node1.x > node2.x: if node1.y > node2.y: _dir = 'left-up' elif node1.y == node2.y: _dir = 'left' else: _dir = 'left-down' elif node1.x == node2.x: if node1.y > node2.y: _dir = 'up' elif node1.y == node2.y: _dir = 'same' else: _dir = 'down' else: if node1.y > node2.y: _dir = 'right-up' elif node1.y == node2.y: _dir = 'right' else: _dir = 'right-down' return _dir def to_desctable(self): label = "%s -> %s" % (self.node1.label, self.node2.label) return [label, self.description] class Diagram(NodeGroup): _DiagramNode = DiagramNode _DiagramEdge = DiagramEdge _NodeGroup = NodeGroup classes = {} shadow_style = 'blur' linecolor = (0, 0, 0) int_attrs = (NodeGroup.int_attrs + ['node_width', 'node_height', 'span_width', 'span_height']) @classmethod def clear(cls): super(Diagram, cls).clear() cls.shadow_style = 'blur' cls.linecolor = (0, 0, 0) cls.classes = {} def __init__(self): super(Diagram, self).__init__(None) self.node_width = None self.node_height = None self.span_width = None self.span_height = None self.page_padding = None self.edge_layout = None def set_plugin(self, name, attrs): try: kwargs = dict([str(unquote(attr.name)), unquote(attr.value)] for attr in attrs) plugins.load([name], diagram=self, **kwargs) except: msg = "WARNING: fail to load plugin: %s\n" % name raise AttributeError(msg) def set_plugins(self, value): modules = [name.strip() for name in value.split(',')] plugins.load(modules, diagram=self) def set_default_shape(self, value): if noderenderer.get(value): DiagramNode.set_default_shape(value) else: msg = "WARNING: unknown node shape: %s\n" % value raise AttributeError(msg) def set_default_label_orientation(self, value): value = value.lower() if value in ('horizontal', 'vertical'): DiagramNode.label_orientation = value else: msg = "WARNING: unknown label orientation: %s\n" % value raise AttributeError(msg) def set_default_text_color(self, color): msg = "WARNING: default_text_color is obsoleted; " + \ "use default_textcolor\n" sys.stderr.write(msg) self.set_default_textcolor(color) def set_default_textcolor(self, color): self.textcolor = images.color_to_rgb(color) self._DiagramNode.set_default_text_color(self.textcolor) self._NodeGroup.set_default_text_color(self.textcolor) self._DiagramEdge.set_default_text_color(self.textcolor) def set_default_node_color(self, color): color = images.color_to_rgb(color) self._DiagramNode.set_default_color(color) def set_default_line_color(self, color): msg = "WARNING: default_line_color is obsoleted; " + \ "use default_linecolor\n" sys.stderr.write(msg) self.set_default_linecolor(color) def set_default_linecolor(self, color): self.linecolor = images.color_to_rgb(color) self._DiagramNode.set_default_linecolor(self.linecolor) self._DiagramEdge.set_default_color(self.linecolor) def set_default_group_color(self, color): color = images.color_to_rgb(color) self._NodeGroup.set_default_color(color) def set_shape_namespace(self, value): noderenderer.set_default_namespace(value) def set_default_fontfamily(self, fontfamily): self._DiagramNode.set_default_fontfamily(fontfamily) self._NodeGroup.set_default_fontfamily(fontfamily) self._DiagramEdge.set_default_fontfamily(fontfamily) def set_default_fontsize(self, fontsize): self._DiagramNode.set_default_fontsize(fontsize) self._NodeGroup.set_default_fontsize(fontsize) self._DiagramEdge.set_default_fontsize(fontsize) def set_shadow_style(self, value): value = value.lower() if value in ('solid', 'blur', 'none'): self.shadow_style = value else: msg = "WARNING: unknown shadow style: %s\n" % value raise AttributeError(msg) def set_edge_layout(self, value): value = value.lower() if value in ('normal', 'flowchart'): msg = u("WARNING: edge_layout is very experimental feature!\n") sys.stderr.write(msg) self.edge_layout = value else: msg = "WARNING: unknown edge layout: %s\n" % value raise AttributeError(msg) def set_fontsize(self, value): msg = "WARNING: fontsize is obsoleted; use default_fontsize\n" sys.stderr.write(msg) self.set_default_fontsize(int(value)) blockdiag-1.3.2/src/blockdiag/imagedraw/0000755000076600000240000000000012242633534020707 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/imagedraw/__init__.py0000644000076600000240000000277012220764222023021 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import pkg_resources drawers = {} def init_imagedrawers(): for drawer in pkg_resources.iter_entry_points('blockdiag_imagedrawers'): try: module = drawer.load() if hasattr(module, 'setup'): module.setup(module) except: pass def install_imagedrawer(ext, drawer): drawers[ext] = drawer def create(_format, filename, **kwargs): if len(drawers) == 0: init_imagedrawers() _format = _format.lower() if _format in drawers: drawer = drawers[_format](filename, **kwargs) else: msg = 'failed to load %s image driver' % _format raise RuntimeError(msg) if 'linejump' in kwargs.get('filters', []): from blockdiag.imagedraw.filters.linejump import LineJumpDrawFilter jumpsize = kwargs.get('jumpsize', 0) drawer = LineJumpDrawFilter(drawer, jumpsize) return drawer blockdiag-1.3.2/src/blockdiag/imagedraw/base.py0000644000076600000240000000375012216056572022202 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from functools import partial from blockdiag.imagedraw import textfolder from blockdiag.utils import Box class ImageDraw(object): self_generative_methods = [] nosideeffect_methods = ['set_canvas_size', 'textsize', 'textlinesize'] supported_path = False baseline_text_rendering = False _method_cache = {} def set_canvas_size(self, size): pass def set_options(self, **kwargs): pass def line(self, xy, **kwargs): pass def rectangle(self, box, **kwargs): pass def polygon(self, xy, **kwargs): pass def arc(self, xy, start, end, **kwargs): pass def ellipse(self, xy, **kwargs): pass def textsize(self, string, font, maxwidth=None, **kwargs): if maxwidth is None: maxwidth = 65535 box = Box(0, 0, maxwidth, 65535) textbox = self.textfolder(box, string, font, **kwargs) return textbox.outlinebox.size @property def textfolder(self): return partial(textfolder.get, self, adjustBaseline=self.baseline_text_rendering) def textlinesize(self, string, font, **kwargs): pass def text(self, xy, string, font, **kwargs): pass def textarea(self, box, string, font, **kwargs): pass def image(self, box, url): pass def save(self, filename, size, _format): pass blockdiag-1.3.2/src/blockdiag/imagedraw/filters/0000755000076600000240000000000012242633534022357 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/imagedraw/filters/__init__.py0000644000076600000240000000114412227701542024466 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. blockdiag-1.3.2/src/blockdiag/imagedraw/filters/linejump.py0000644000076600000240000001327612216056572024567 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import functools from blockdiag.utils import Box, XY class LazyReceiver(object): def __init__(self, target): self.target = target self.calls = [] self.nested = [] def __getattr__(self, name): return self.get_lazy_method(name) def get_lazy_method(self, name): method = self._find_method(name) def _(*args, **kwargs): if name in self.target.self_generative_methods: ret = method(self.target, *args, **kwargs) receiver = LazyReceiver(ret) self.nested.append(receiver) return receiver else: self.calls.append((method, args, kwargs)) return self if method.__name__ in self.target.nosideeffect_methods: return functools.partial(method, self.target) else: return _ def _find_method(self, name): for p in self.target.__class__.__mro__: if name in p.__dict__: return p.__dict__[name] raise AttributeError("%s instance has no attribute '%s'" % (self.target.__class__.__name__, name)) def _run(self): for recv in self.nested: recv._run() for method, args, kwargs in self.calls: method(self.target, *args, **kwargs) class LineJumpDrawFilter(LazyReceiver): def __init__(self, target, jump_radius): super(LineJumpDrawFilter, self).__init__(target) self.ytree = [] self.x_cross = {} self.y_cross = {} self.forward = 'holizonal' self.jump_radius = jump_radius self.jump_shift = 0 def set_options(self, **kwargs): if 'jump_forward' in kwargs: self.forward = kwargs['jump_forward'] if 'jump_radius' in kwargs: self.jump_radius = kwargs['jump_radius'] if 'jump_shift' in kwargs: self.jump_shift = kwargs['jump_shift'] def _run(self): for recv in self.nested: recv._run() line_method = self._find_method("line") for method, args, kwargs in self.calls: if method == line_method and kwargs.get('jump'): ((x1, y1), (x2, y2)) = args[0] if self.forward == 'holizonal' and y1 == y2: self._holizonal_jumpline(x1, y1, x2, y2, **kwargs) continue elif self.forward == 'vertical' and x1 == x2: self._vertical_jumpline(x1, y1, x2, y2, **kwargs) continue method(self.target, *args, **kwargs) def _holizonal_jumpline(self, x1, y1, x2, y2, **kwargs): y = y1 if x2 < x1: x1, x2 = x2, x1 for x in sorted(self.x_cross.get(y, [])): if x1 < x and x < x2: arckwargs = dict(kwargs) del arckwargs['jump'] x += self.jump_shift r = self.jump_radius line = (XY(x1, y), XY(x - r, y)) self.target.line(line, **kwargs) box = Box(x - r, y - r, x + r, y + r) self.target.arc(box, 180, 0, **arckwargs) x1 = x + r self.target.line((XY(x1, y), XY(x2, y)), **kwargs) def _vertical_jumpline(self, x1, y1, x2, y2, **kwargs): x = x1 if y2 < y1: y1, y2 = y2, y1 for y in sorted(self.y_cross.get(x, [])): if y1 < y and y < y2: arckwargs = dict(kwargs) del arckwargs['jump'] y += self.jump_shift r = self.jump_radius line = (XY(x, y1), XY(x, y - r)) self.target.line(line, **kwargs) box = Box(x - r, y - r, x + r, y + r) self.target.arc(box, 270, 90, **arckwargs) y1 = y + r self.target.line((XY(x, y1), XY(x, y2)), **kwargs) def line(self, xy, **kwargs): from bisect import insort for st, ed in zip(xy[:-1], xy[1:]): self.get_lazy_method("line")((st, ed), **kwargs) if 'jump' in kwargs and kwargs['jump'] is True: if st.y == ed.y: # horizonal insort(self.ytree, (st.y, 0, (st, ed))) elif st.x == ed.x: # vertical insort(self.ytree, (max(st.y, ed.y), -1, (st, ed))) insort(self.ytree, (min(st.y, ed.y), +1, (st, ed))) def save(self, *args, **kwargs): # Search crosspoints from bisect import insort, bisect_left, bisect_right xtree = [] for y, _, ((x1, y1), (x2, y2)) in self.ytree: if x2 < x1: x1, x2 = x2, x1 if y2 < y1: y1, y2 = y2, y1 if y == y1: insort(xtree, x1) if y == y2: del xtree[bisect_left(xtree, x1)] for x in xtree[bisect_right(xtree, x1):bisect_left(xtree, x2)]: self.x_cross.setdefault(y, set()).add(x) self.y_cross.setdefault(x, set()).add(y) self._run() return self.target.save(*args, **kwargs) blockdiag-1.3.2/src/blockdiag/imagedraw/pdf.py0000644000076600000240000001745112227702767022052 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import sys import math from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from blockdiag.imagedraw import base from blockdiag.imagedraw.utils import cached from blockdiag.utils import urlutil, Box, Size from blockdiag.utils.fontmap import parse_fontpath from blockdiag.utils.compat import string_types class PDFImageDraw(base.ImageDraw): baseline_text_rendering = True def __init__(self, filename, **kwargs): self.filename = filename self.canvas = None self.fonts = {} self.set_canvas_size(Size(1, 1)) # This line make textsize() workable def set_canvas_size(self, size): self.canvas = canvas.Canvas(self.filename, pagesize=size) self.size = size def set_font(self, font): if font.path is None: msg = "Could not detect fonts, use --font opiton\n" raise RuntimeError(msg) if font.path not in self.fonts: path, index = parse_fontpath(font.path) if index: ttfont = TTFont(font.path, path, subfontIndex=index) else: ttfont = TTFont(font.path, path) pdfmetrics.registerFont(ttfont) self.fonts[font.path] = ttfont self.canvas.setFont(font.path, font.size) def set_render_params(self, **kwargs): self.set_stroke_color(kwargs.get('outline')) self.set_fill_color(kwargs.get('fill', 'none')) self.set_style(kwargs.get('style'), kwargs.get('thick')) params = {} if kwargs.get('fill', 'none') == 'none': params['fill'] = 0 else: params['fill'] = 1 if kwargs.get('outline') is None: params['stroke'] = 0 else: params['stroke'] = 1 return params def set_style(self, style, thick): if thick is None: thick = 1 if style == 'dotted': self.canvas.setDash([2 * thick, 2 * thick]) elif style == 'dashed': self.canvas.setDash([4 * thick, 4 * thick]) elif style == 'none': self.canvas.setDash([0, 65535 * thick]) elif re.search('^\d+(,\d+)*$', style or ""): self.canvas.setDash([int(n) * thick for n in style.split(',')]) else: self.canvas.setDash() def set_stroke_color(self, color="black"): if isinstance(color, string_types): self.canvas.setStrokeColor(color) elif color: rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0) self.canvas.setStrokeColorRGB(*rgb) else: self.set_stroke_color() def set_fill_color(self, color="white"): if isinstance(color, string_types): if color != 'none': self.canvas.setFillColor(color) elif color: rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0) self.canvas.setFillColorRGB(*rgb) else: self.set_fill_color() def path(self, pd, **kwargs): params = self.set_render_params(**kwargs) self.canvas.drawPath(pd, **params) def rectangle(self, box, **kwargs): x = box[0] y = self.size[1] - box[3] width = box[2] - box[0] height = box[3] - box[1] if 'thick' in kwargs and kwargs['thick'] is not None: self.canvas.setLineWidth(kwargs['thick']) params = self.set_render_params(**kwargs) self.canvas.rect(x, y, width, height, **params) if 'thick' in kwargs: self.canvas.setLineWidth(1) @cached def textlinesize(self, string, font): self.set_font(font) width = self.canvas.stringWidth(string, font.path, font.size) return Size(int(math.ceil(width)), font.size) def text(self, xy, string, font, **kwargs): self.set_font(font) self.set_fill_color(kwargs.get('fill')) self.canvas.drawString(xy[0], self.size[1] - xy[1], string) def textarea(self, box, string, font, **kwargs): self.canvas.saveState() if 'rotate' in kwargs and kwargs['rotate'] != 0: angle = 360 - int(kwargs['rotate']) % 360 self.canvas.rotate(angle) if angle == 90: box = Box(-box.y2, box.x1, -box.y1, box.x1 + box.width) box = box.shift(x=self.size.y, y=self.size.y) elif angle == 180: box = Box(-box.x2, -box.y2, -box.x1, -box.y2 + box.height) box = box.shift(y=self.size.y * 2) elif angle == 270: box = Box(box.y1, -box.x2, box.y2, -box.x1) box = box.shift(x=-self.size.y, y=self.size.y) self.set_font(font) lines = self.textfolder(box, string, font, **kwargs) if kwargs.get('outline'): outline = kwargs.get('outline') self.rectangle(lines.outlinebox, fill='white', outline=outline) rendered = False for string, xy in lines.lines: self.text(xy, string, font, **kwargs) rendered = True self.canvas.restoreState() if not rendered and font.size > 0: font.size = int(font.size * 0.8) self.textarea(box, string, font, **kwargs) def line(self, xy, **kwargs): self.set_stroke_color(kwargs.get('fill', 'none')) self.set_style(kwargs.get('style'), kwargs.get('thick')) if 'thick' in kwargs and kwargs['thick'] is not None: self.canvas.setLineWidth(kwargs['thick']) p1 = xy[0] y = self.size[1] for p2 in xy[1:]: self.canvas.line(p1.x, y - p1.y, p2.x, y - p2.y) p1 = p2 if 'thick' in kwargs: self.canvas.setLineWidth(1) def arc(self, xy, start, end, **kwargs): start, end = 360 - end, 360 - start r = (360 + end - start) % 360 self.set_render_params(**kwargs) y = self.size[1] self.canvas.arc(xy[0], y - xy[3], xy[2], y - xy[1], start, r) def ellipse(self, xy, **kwargs): params = self.set_render_params(**kwargs) y = self.size[1] self.canvas.ellipse(xy[0], y - xy[3], xy[2], y - xy[1], **params) def polygon(self, xy, **kwargs): pd = self.canvas.beginPath() y = self.size[1] pd.moveTo(xy[0][0], y - xy[0][1]) for p in xy[1:]: pd.lineTo(p[0], y - p[1]) params = self.set_render_params(**kwargs) self.canvas.drawPath(pd, **params) def image(self, box, url): if urlutil.isurl(url): from reportlab.lib.utils import ImageReader try: url = ImageReader(url) except: msg = "WARNING: Could not retrieve: %s\n" % url sys.stderr.write(msg) return y = self.size[1] - box[3] self.canvas.drawImage(url, box.x1, y, box.width, box.height, mask='auto', preserveAspectRatio=True) def save(self, filename, size, _format): # Ignore size and format parameter; compatibility for ImageDrawEx. self.canvas.showPage() self.canvas.save() def setup(self): from blockdiag.imagedraw import install_imagedrawer install_imagedrawer('pdf', PDFImageDraw) blockdiag-1.3.2/src/blockdiag/imagedraw/png.py0000644000076600000240000003455512242633443022060 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import re import sys import math from itertools import tee try: from future_builtins import zip except ImportError: pass from functools import partial, wraps from PIL import Image, ImageDraw, ImageFont, ImageFilter from blockdiag.imagedraw import base from blockdiag.imagedraw.utils import cached from blockdiag.imagedraw.utils.ellipse import dots as ellipse_dots from blockdiag.utils import urlutil, Box, Size, XY from blockdiag.utils.fontmap import parse_fontpath, FontMap from blockdiag.utils.myitertools import istep, stepslice def point_pairs(xylist): iterable = iter(xylist) for pt in iterable: if isinstance(pt, int): yield (pt, next(iterable)) else: yield pt def line_segments(xylist): p1, p2 = tee(point_pairs(xylist)) next(p2) return zip(p1, p2) def dashize_line(line, length): pt1, pt2 = line if pt1[0] == pt2[0]: # holizonal if pt1[1] > pt2[1]: pt2, pt1 = line r = stepslice(range(pt1[1], pt2[1]), length) for y1, y2 in istep(n for n in r): yield [(pt1[0], y1), (pt1[0], y2)] elif pt1[1] == pt2[1]: # vertical if pt1[0] > pt2[0]: pt2, pt1 = line r = stepslice(range(pt1[0], pt2[0]), length) for x1, x2 in istep(n for n in r): yield [(x1, pt1[1]), (x2, pt1[1])] else: # diagonal if pt1[0] > pt2[0]: pt2, pt1 = line # DDA (Digital Differential Analyzer) Algorithm locus = [] m = float(pt2[1] - pt1[1]) / float(pt2[0] - pt1[0]) x = pt1[0] y = pt1[1] while x <= pt2[0]: locus.append((int(x), int(round(y)))) x += 1 y += m for p1, p2 in istep(stepslice(locus, length)): yield (p1, p2) def style2cycle(style, thick): if thick is None: thick = 1 if style == 'dotted': length = [2 * thick, 2 * thick] elif style == 'dashed': length = [4 * thick, 4 * thick] elif style == 'none': length = [0, 65535 * thick] elif re.search('^\d+(,\d+)*$', style or ""): length = [int(n) * thick for n in style.split(',')] else: length = None return length def ttfont_for(font): if font.path: path, index = parse_fontpath(font.path) if index: ttfont = ImageFont.truetype(path, font.size, index=index) else: ttfont = ImageFont.truetype(path, font.size) else: ttfont = None return ttfont class ImageDrawExBase(base.ImageDraw): def __init__(self, filename, **kwargs): self.filename = filename self.transparency = kwargs.get('transparency') self.bgcolor = kwargs.get('color', (256, 256, 256)) self._image = None self.draw = None if kwargs.get('parent'): self.scale_ratio = kwargs.get('parent').scale_ratio else: self.scale_ratio = kwargs.get('scale_ratio', 1) self.set_canvas_size(Size(1, 1)) # This line make textsize() workable def paste(self, image, pt, mask=None): self._image.paste(image, pt, mask) self.draw = ImageDraw.Draw(self._image) def set_canvas_size(self, size): if self.transparency: mode = 'RGBA' else: mode = 'RGB' self._image = Image.new(mode, size, self.bgcolor) # set transparency to background if self.transparency: alpha = Image.new('L', size, 1) self._image.putalpha(alpha) self.draw = ImageDraw.Draw(self._image) def resizeCanvas(self, size): self._image = self._image.resize(size, Image.ANTIALIAS) self.draw = ImageDraw.Draw(self._image) def arc(self, box, start, end, **kwargs): style = kwargs.get('style') if 'style' in kwargs: del kwargs['style'] if 'thick' in kwargs: del kwargs['thick'] if style: while start > end: end += 360 cycle = style2cycle(style, kwargs.get('width')) for pt in ellipse_dots(box, cycle, start, end): self.draw.line([pt, pt], fill=kwargs['fill']) else: self.draw.arc(box.to_integer_point(), start, end, **kwargs) def ellipse(self, box, **kwargs): if 'filter' in kwargs: del kwargs['filter'] style = kwargs.get('style') if 'style' in kwargs: del kwargs['style'] if style: if kwargs.get('fill') != 'none': kwargs2 = dict(kwargs) if 'outline' in kwargs2: del kwargs2['outline'] self.draw.ellipse(box, **kwargs2) if 'outline' in kwargs: kwargs['fill'] = kwargs['outline'] del kwargs['outline'] cycle = style2cycle(style, kwargs.get('width')) for pt in ellipse_dots(box, cycle): self.draw.line([pt, pt], fill=kwargs['fill']) else: if kwargs.get('fill') == 'none': del kwargs['fill'] self.draw.ellipse(box.to_integer_point(), **kwargs) def line(self, xy, **kwargs): if 'jump' in kwargs: del kwargs['jump'] if 'thick' in kwargs: if kwargs['thick'] is not None: kwargs['width'] = kwargs['thick'] del kwargs['thick'] style = kwargs.get('style') if kwargs.get('fill') == 'none': pass elif (style in ('dotted', 'dashed', 'none') or re.search('^\d+(,\d+)*$', style or "")): self.dashed_line(xy, **kwargs) else: if 'style' in kwargs: del kwargs['style'] self.draw.line(xy, **kwargs) def dashed_line(self, xy, **kwargs): style = kwargs.get('style') del kwargs['style'] cycle = style2cycle(style, kwargs.get('width')) for line in line_segments(xy): for subline in dashize_line(line, cycle): self.line(subline, **kwargs) def rectangle(self, box, **kwargs): thick = kwargs.get('thick', self.scale_ratio) fill = kwargs.get('fill') outline = kwargs.get('outline') style = kwargs.get('style') if thick == 1: d = 0 else: d = int(math.ceil(thick / 2.0)) if fill and fill != 'none': self.draw.rectangle(box, fill=fill) x1, y1, x2, y2 = box lines = (((x1, y1), (x2, y1)), ((x1, y2), (x2, y2)), # horizonal ((x1, y1 - d), (x1, y2 + d)), # vettical (left) ((x2, y1 - d), (x2, y2 + d))) # vertical (right) for line in lines: self.line(line, fill=outline, width=thick, style=style) def polygon(self, xy, **kwargs): if 'filter' in kwargs: del kwargs['filter'] if kwargs.get('fill') != 'none': kwargs2 = dict(kwargs) if 'style' in kwargs2: del kwargs2['style'] if 'outline' in kwargs2: del kwargs2['outline'] self.draw.polygon(xy, **kwargs2) if kwargs.get('outline'): kwargs['fill'] = kwargs['outline'] del kwargs['outline'] self.line(xy, **kwargs) @property def textfolder(self): textfolder = super(ImageDrawExBase, self).textfolder return partial(textfolder, scale=self.scale_ratio) @cached def textlinesize(self, string, font): ttfont = ttfont_for(font) if ttfont is None: size = self.draw.textsize(string, font=None) font_ratio = font.size * 1.0 / FontMap.BASE_FONTSIZE size = Size(int(size[0] * font_ratio), int(size[1] * font_ratio)) else: size = self.draw.textsize(string, font=ttfont) size = Size(*size) return size def text(self, xy, string, font, **kwargs): fill = kwargs.get('fill') ttfont = ttfont_for(font) if ttfont is None: if self.scale_ratio == 1 and font.size == FontMap.BASE_FONTSIZE: self.draw.text(xy, string, fill=fill) else: size = self.draw.textsize(string) image = Image.new('RGBA', size) draw = ImageDraw.Draw(image) draw.text((0, 0), string, fill=fill) del draw basesize = (size[0] * self.scale_ratio, size[1] * self.scale_ratio) text_image = image.resize(basesize, Image.ANTIALIAS) self.paste(text_image, xy, text_image) else: size = self.draw.textsize(string, font=ttfont) # Generate mask to support BDF(bitmap font) mask = Image.new('1', size) draw = ImageDraw.Draw(mask) draw.text((0, 0), string, fill='white', font=ttfont) # Rendering text filler = Image.new('RGB', size, fill) self.paste(filler, xy, mask) def textarea(self, box, string, font, **kwargs): if 'rotate' in kwargs and kwargs['rotate'] != 0: angle = 360 - int(kwargs['rotate']) % 360 del kwargs['rotate'] if angle in (90, 270): _box = Box(0, 0, box.height, box.width) else: _box = box text = ImageDrawEx(None, parent=self, transparency=True) text.set_canvas_size(_box.size) textbox = Box(0, 0, _box.width, _box.height) text.textarea(textbox, string, font, **kwargs) filler = Image.new('RGB', box.size, kwargs.get('fill')) self.paste(filler, box.topleft, text._image.rotate(angle)) return lines = self.textfolder(box, string, font, **kwargs) if kwargs.get('outline'): outline = kwargs.get('outline') self.rectangle(lines.outlinebox, fill='white', outline=outline) rendered = False for string, xy in lines.lines: self.text(xy, string, font, **kwargs) rendered = True if not rendered and font.size > 0: _font = font.duplicate() _font.size = int(font.size * 0.8) self.textarea(box, string, _font, **kwargs) def image(self, box, url): if urlutil.isurl(url): try: from io import StringIO except ImportError: from cStringIO import StringIO import urllib try: url = StringIO(urllib.urlopen(url).read()) except: msg = "WARNING: Could not retrieve: %s\n" % url sys.stderr.write(msg) return image = Image.open(url) # resize image. w = min([box.width, image.size[0] * self.scale_ratio]) h = min([box.height, image.size[1] * self.scale_ratio]) image.thumbnail((w, h), Image.ANTIALIAS) # centering image. w, h = image.size if box.width > w: x = box[0] + (box.width - w) // 2 else: x = box[0] if box.height > h: y = box[1] + (box.height - h) // 2 else: y = box[1] self.paste(image, (x, y)) def save(self, filename, size, _format): if filename: self.filename = filename if size is None: x = int(self._image.size[0] / self.scale_ratio) y = int(self._image.size[1] / self.scale_ratio) size = (x, y) self._image.thumbnail(size, Image.ANTIALIAS) if self.filename: self._image.save(self.filename, _format) image = None else: try: from io import StringIO except ImportError: from cStringIO import StringIO tmp = StringIO() self._image.save(tmp, _format) image = tmp.getvalue() return image def blurred(fn): PADDING = 16 def get_shape_box(*args): if fn.__name__ == 'polygon': xlist = [pt.x for pt in args[0]] ylist = [pt.y for pt in args[0]] return Box(min(xlist), min(ylist), max(xlist), max(ylist)) else: return args[0] def get_abs_coordinate(box, *args): dx = box.x1 - PADDING dy = box.y1 - PADDING if fn.__name__ == 'polygon': return [pt.shift(-dx, -dy) for pt in args[0]] else: return box.shift(-dx, -dy) def create_shadow(self, size, *args, **kwargs): drawer = ImageDrawExBase(self.filename, transparency=True) drawer.set_canvas_size(size) getattr(drawer, fn.__name__)(*args, **kwargs) for _ in range(15): drawer._image = drawer._image.filter(ImageFilter.SMOOTH_MORE) return drawer._image @wraps(fn) def func(self, *args, **kwargs): args = list(args) if kwargs.get('filter') not in ('blur', 'transp-blur'): return fn(self, *args, **kwargs) else: box = get_shape_box(*args) args[0] = get_abs_coordinate(box, *args) size = Size(box.width + PADDING * 2, box.height + PADDING * 2) shadow = create_shadow(self, size, *args, **kwargs) xy = XY(box.x1 - PADDING, box.y1 - PADDING) self.paste(shadow, xy, shadow) return func class ImageDrawEx(ImageDrawExBase): @blurred def ellipse(self, box, **kwargs): super(ImageDrawEx, self).ellipse(box, **kwargs) @blurred def rectangle(self, box, **kwargs): super(ImageDrawEx, self).rectangle(box, **kwargs) @blurred def polygon(self, xy, **kwargs): super(ImageDrawEx, self).polygon(xy, **kwargs) def setup(self): from blockdiag.imagedraw import install_imagedrawer install_imagedrawer('png', ImageDrawEx) blockdiag-1.3.2/src/blockdiag/imagedraw/simplesvg.py0000644000076600000240000001543112224140066023267 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from blockdiag.utils.compat import u, string_types try: from io import StringIO except ImportError: from cStringIO import StringIO def _escape(s): if not isinstance(s, string_types): s = str(s) return s.replace("&", "&").replace("<", "<").replace(">", ">") def _quote(s): return '"%s"' % _escape(s).replace('"', """) class base(object): def __init__(self, *args, **kwargs): self.text = None self.elements = [] self.attributes = {} for key, value in kwargs.items(): self.add_attribute(key, value) def add_attribute(self, key, value): setter = 'set_%s' % key if hasattr(self, setter): getattr(self, setter)(value) else: key = re.sub('_', '-', key) self.attributes[key] = value def addElement(self, element): self.elements.append(element) def set_text(self, text): self.text = text def to_xml(self, io, level=0): clsname = self.__class__.__name__ indent = ' ' * level io.write(u('%s<%s') % (indent, clsname)) for key in sorted(self.attributes): value = self.attributes[key] if value is not None: io.write(u(' %s=%s') % (_escape(key), _quote(value))) if self.elements == []: if self.text is not None: io.write(u(">%s\n") % (_escape(self.text), clsname)) else: io.write(u(" />\n")) elif self.elements: if self.text is not None: io.write(u(">%s\n") % (_escape(self.text),)) else: io.write(u(">\n")) for e in self.elements: e.to_xml(io, level + 1) io.write(u('%s\n') % (indent, clsname)) class element(base): def __init__(self, x, y, width=None, height=None, *args, **kwargs): super(element, self).__init__(*args, **kwargs) self.attributes['x'] = x self.attributes['y'] = y if width is not None: self.attributes['width'] = width if height is not None: self.attributes['height'] = height class svg(base): def __init__(self, x, y, width, height, **kwargs): if kwargs.get('noviewbox'): super(svg, self).__init__(width=(width - x), height=(height - y)) else: viewbox = "%d %d %d %d" % (x, y, width, height) super(svg, self).__init__(viewBox=viewbox) self.nodoctype = kwargs.get('nodoctype', False) self.add_attribute('xmlns', 'http://www.w3.org/2000/svg') def to_xml(self): io = StringIO() if not self.nodoctype: url = "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" io.write(u("\n")) io.write(u('\n') % url) super(svg, self).to_xml(io) return io.getvalue() class title(base): def __init__(self, _title): super(title, self).__init__(text=_title) class desc(base): def __init__(self, _title): super(desc, self).__init__(text=_title) class text(element): def __init__(self, x, y, _text, **kwargs): super(text, self).__init__(x, y, text=_text, **kwargs) class rect(element): pass class ellipse(base): def __init__(self, cx, cy, rx, ry, **kwargs): super(ellipse, self).__init__(cx=cx, cy=cy, rx=rx, ry=ry, **kwargs) class image(element): def __init__(self, uri, x, y, width, height, **kwargs): super(image, self).__init__(x, y, width, height, **kwargs) self.add_attribute('xlink:href', uri) class polygon(base): def __init__(self, points, **kwargs): xylist = " ".join('%d,%d' % pt for pt in points) super(polygon, self).__init__(points=xylist, **kwargs) class path(base): def __init__(self, data, **kwargs): super(path, self).__init__(d=data, **kwargs) class pathdata: def __init__(self, x=None, y=None): self.path = [] if x is not None and y is not None: self.move(x, y) def closepath(self): self.path.append('z') def move(self, x, y): self.path.append('M %s %s' % (x, y)) def relmove(self, x, y): self.path.append('m %s %s' % (x, y)) def line(self, x, y): self.path.append('L %s %s' % (x, y)) def relline(self, x, y): self.path.append('l %s %s' % (x, y)) def hline(self, x): self.path.append('H%s' % (x,)) def relhline(self, x): self.path.append('h%s' % (x,)) def vline(self, y): self.path.append('V%s' % (y,)) def relvline(self, y): self.path.append('v%s' % (y,)) def bezier(self, x1, y1, x2, y2, x, y): self.path.append('C%s,%s %s,%s %s,%s' % (x1, y1, x2, y2, x, y)) def relbezier(self, x1, y1, x2, y2, x, y): self.path.append('c%s,%s %s,%s %s,%s' % (x1, y1, x2, y2, x, y)) def smbezier(self, x2, y2, x, y): self.path.append('S%s,%s %s,%s' % (x2, y2, x, y)) def relsmbezier(self, x2, y2, x, y): self.path.append('s%s,%s %s,%s' % (x2, y2, x, y)) def qbezier(self, x1, y1, x, y): self.path.append('Q%s,%s %s,%s' % (x1, y1, x, y)) def qrelbezier(self, x1, y1, x, y): self.path.append('q%s,%s %s,%s' % (x1, y1, x, y)) def smqbezier(self, x, y): self.path.append('T%s %s' % (x, y)) def relsmqbezier(self, x, y): self.path.append('t%s %s' % (x, y)) def ellarc(self, rx, ry, xrot, laf, sf, x, y): self.path.append('A%s,%s %s %s %s %s %s' % (rx, ry, xrot, laf, sf, x, y)) def relellarc(self, rx, ry, xrot, laf, sf, x, y): self.path.append('a%s,%s %s %s %s %s %s' % (rx, ry, xrot, laf, sf, x, y)) def __repr__(self): return ' '.join(self.path) class defs(base): pass class g(base): pass class a(base): pass class filter(element): def __init__(self, x, y, width, height, **kwargs): super(filter, self).__init__(x, y, width, height, **kwargs) def svgclass(name): """ svg class generating function """ return type(name, (base,), {}) blockdiag-1.3.2/src/blockdiag/imagedraw/svg.py0000644000076600000240000002243412234055721022062 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import base64 from blockdiag.imagedraw import base as _base from blockdiag.imagedraw.simplesvg import ( svg, svgclass, filter, title, desc, defs, g, a, text, rect, polygon, ellipse, path, pathdata, image ) from blockdiag.imagedraw.utils import cached from blockdiag.imagedraw.utils.ellipse import endpoints as ellipse_endpoints from blockdiag.utils import urlutil, Box, XY, is_Pillow_available feGaussianBlur = svgclass('feGaussianBlur') def rgb(color): if isinstance(color, tuple): color = 'rgb(%d,%d,%d)' % color return color def style(name): if name == 'blur': value = "filter:url(#filter_blur)" elif name == 'transp-blur': value = "filter:url(#filter_blur);opacity:0.7;fill-opacity:1" else: value = None return value def dasharray(pattern, thick): if thick is None: thick = 1 if pattern == 'dotted': value = 2 * thick elif pattern == 'dashed': value = 4 * thick elif pattern == 'none': value = "%d %d" % (0, 65535 * thick) elif re.search('^\d+(,\d+)*$', pattern or ""): l = [int(n) * thick for n in pattern.split(",")] value = " ".join(str(n) for n in l) else: value = None return value def drawing_params(kwargs): params = {} if 'style' in kwargs: params['stroke_dasharray'] = dasharray(kwargs.get('style'), kwargs.get('thick')) if 'filter' in kwargs: params['style'] = style(kwargs.get('filter')) return params class SVGImageDrawElement(_base.ImageDraw): self_generative_methods = ['group', 'anchor'] supported_path = True baseline_text_rendering = True def __init__(self, svg, parent=None): self.svg = svg def path(self, pd, **kwargs): fill = kwargs.get('fill') outline = kwargs.get('outline') p = path(pd, fill=rgb(fill), stroke=rgb(outline), **drawing_params(kwargs)) self.svg.addElement(p) def rectangle(self, box, **kwargs): thick = kwargs.get('thick') fill = kwargs.get('fill', 'none') outline = kwargs.get('outline') r = rect(box.x, box.y, box.width, box.height, fill=rgb(fill), stroke=rgb(outline), stroke_width=thick, **drawing_params(kwargs)) self.svg.addElement(r) @cached def textlinesize(self, string, font, **kwargs): if is_Pillow_available(): if not hasattr(self, '_pil_drawer'): from blockdiag.imagedraw import png self._pil_drawer = png.ImageDrawEx(None) return self._pil_drawer.textlinesize(string, font) else: from blockdiag.imagedraw.utils import textsize return textsize(string, font) def text(self, point, string, font, **kwargs): fill = kwargs.get('fill') t = text(point.x, point.y, string, fill=rgb(fill), font_family=font.generic_family, font_size=font.size, font_weight=font.weight, font_style=font.style) self.svg.addElement(t) def textarea(self, box, string, font, **kwargs): if 'rotate' in kwargs and kwargs['rotate'] != 0: self.rotated_textarea(box, string, font, **kwargs) else: lines = self.textfolder(box, string, font, **kwargs) if kwargs.get('outline'): outline = kwargs.get('outline') self.rectangle(lines.outlinebox, fill='white', outline=outline) rendered = False for string, point in lines.lines: self.text(point, string, font, **kwargs) rendered = True if not rendered and font.size > 0: _font = font.duplicate() _font.size = int(font.size * 0.8) self.textarea(box, string, _font, **kwargs) def rotated_textarea(self, box, string, font, **kwargs): angle = int(kwargs['rotate']) % 360 del kwargs['rotate'] if angle in (90, 270): _box = Box(box[0], box[1], box[0] + box.height, box[1] + box.width) if angle == 90: _box = _box.shift(x=box.width) elif angle == 270: _box = _box.shift(y=box.height) elif angle == 180: _box = Box(box[2], box[3], box[2] + box.width, box[3] + box.height) else: _box = Box(box[0], box[1], box[0] + box.width, box[1] + box.height) rotate = "rotate(%d,%d,%d)" % (angle, _box[0], _box[1]) group = g(transform="%s" % rotate) self.svg.addElement(group) elem = SVGImageDrawElement(group, self) elem.textarea(_box, string, font, **kwargs) def line(self, points, **kwargs): fill = kwargs.get('fill') thick = kwargs.get('thick') pd = pathdata(points[0].x, points[0].y) for pt in points[1:]: pd.line(pt.x, pt.y) p = path(pd, fill="none", stroke=rgb(fill), stroke_width=thick, **drawing_params(kwargs)) self.svg.addElement(p) def arc(self, box, start, end, **kwargs): fill = kwargs.get('fill') w = box.width / 2 h = box.height / 2 if start > end: end += 360 endpoints = ellipse_endpoints(1, w, h, start, end) pt1 = XY(box.x + w + round(endpoints[0].x, 0), box.y + h + round(endpoints[0].y, 0)) pt2 = XY(box.x + w + round(endpoints[1].x, 0), box.y + h + round(endpoints[1].y, 0)) if end - start > 180: largearc = 1 else: largearc = 0 pd = pathdata(pt1[0], pt1[1]) pd.ellarc(w, h, 0, largearc, 1, pt2[0], pt2[1]) p = path(pd, fill="none", stroke=rgb(fill), **drawing_params(kwargs)) self.svg.addElement(p) def ellipse(self, box, **kwargs): fill = kwargs.get('fill') outline = kwargs.get('outline') w = box.width / 2 h = box.height / 2 pt = box.center e = ellipse(pt.x, pt.y, w, h, fill=rgb(fill), stroke=rgb(outline), **drawing_params(kwargs)) self.svg.addElement(e) def polygon(self, points, **kwargs): fill = kwargs.get('fill') outline = kwargs.get('outline') pg = polygon(points, fill=rgb(fill), stroke=rgb(outline), **drawing_params(kwargs)) self.svg.addElement(pg) def image(self, box, url): if not urlutil.isurl(url): string = open(url, 'rb').read() url = "data:;base64," + str(base64.b64encode(string)) im = image(url, box.x1, box.y1, box.width, box.height) self.svg.addElement(im) def anchor(self, url): a_node = a(url) a_node.add_attribute('xlink:href', url) self.svg.addElement(a_node) return SVGImageDrawElement(a_node, self) def group(self): group = g() self.svg.addElement(group) return SVGImageDrawElement(group, self) class SVGImageDraw(SVGImageDrawElement): def __init__(self, filename, **kwargs): super(SVGImageDraw, self).__init__(None) self.filename = filename self.options = kwargs self.set_canvas_size((0, 0)) def set_canvas_size(self, size): self.svg = svg(0, 0, size[0], size[1], **self.options) uri = 'http://www.inkscape.org/namespaces/inkscape' self.svg.add_attribute('xmlns:inkspace', uri) uri = 'http://www.w3.org/1999/xlink' self.svg.add_attribute('xmlns:xlink', uri) # inkspace's Gaussian filter if self.options.get('style') != 'blur': fgb = feGaussianBlur(id='feGaussianBlur3780', stdDeviation=4.2) fgb.add_attribute('inkspace:collect', 'always') f = filter(-0.07875, -0.252, 1.1575, 1.504, id='filter_blur') f.add_attribute('inkspace:collect', 'always') f.addElement(fgb) d = defs(id='defs_block') d.addElement(f) self.svg.addElement(d) self.svg.addElement(title('blockdiag')) self.svg.addElement(desc(self.options.get('code'))) def save(self, filename, size, _format): # Ignore format parameter; compatibility for ImageDrawEx. if filename: self.filename = filename if size: self.svg.attributes['width'] = size[0] self.svg.attributes['height'] = size[1] image = self.svg.to_xml() if self.filename: open(self.filename, 'wb').write(image.encode('utf-8')) return image def setup(self): from blockdiag.imagedraw import install_imagedrawer install_imagedrawer('svg', SVGImageDraw) blockdiag-1.3.2/src/blockdiag/imagedraw/textfolder.py0000644000076600000240000002336412234223041023436 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from blockdiag.utils import Box, Size, XY from blockdiag.utils.compat import u, string_types def splitlabel(string): """Split text to lines as generator. Every line will be stripped. If text includes characters "\n", treat as line separator. """ string = re.sub('^\s*', '', string) string = re.sub('\s*$', '', string) string = re.sub('\xa5', '\\\\', string) string = re.sub('(\\\\){2}', '\x00', string) string = re.sub('\\\\n', '\n', string) for line in string.splitlines(): yield re.sub('\x00', '\\\\', line).strip() def splittext(metrics, text, bound, measure='width'): folded = [] if text == '': folded.append(u(' ')) for i in range(len(text), 0, -1): textsize = metrics.textsize(text[0:i]) if getattr(textsize, measure) <= bound: folded.append(text[0:i]) if text[i:]: folded += splittext(metrics, text[i:], bound, measure) break return folded def truncate_text(metrics, text, bound, measure='width'): for i in range(len(text), 0, -1): textsize = metrics.textsize(text[0:i] + ' ...') if getattr(textsize, measure) <= bound: return text[0:i] + ' ...' return text def get(*args, **kwargs): if kwargs.get('orientation') == 'vertical': return VerticalTextFolder(*args, **kwargs) else: return HorizontalTextFolder(*args, **kwargs) class VerticalTextFolder(object): def __init__(self, drawer, box, string, font, **kwargs): self.drawer = drawer self.box = box self.string = string self.font = font self.scale = 1 self.halign = kwargs.get('halign', 'center') self.valign = kwargs.get('valign', 'center') self.padding = kwargs.get('padding', 8) self.line_spacing = kwargs.get('line_spacing', 2) if kwargs.get('adjustBaseline'): self.adjustBaseline = True else: self.adjustBaseline = False self._result = self._lines() def textsize(self, text, scaled=False): if isinstance(text, string_types): size = [self.drawer.textlinesize(c, self.font) for c in text] width = max(s.width for s in size) height = (sum(s.height for s in size) + self.line_spacing * (len(text) - 1)) textsize = Size(width, height) else: if text: size = [self.textsize(s) for s in text] height = max(s.height for s in size) width = (sum(s.width for s in size) + self.line_spacing * (len(text) - 1)) textsize = Size(width, height) else: textsize = Size(0, 0) if scaled: textsize = Size(textsize.width * self.scale, textsize.height * self.scale) return textsize @property def lines(self): textsize = self.textsize(self._result, scaled=True) dx, _ = self.box.get_padding_for(textsize, halign=self.halign, padding=self.padding) width = self.box.width - dx + self.line_spacing base_xy = XY(self.box.x1, self.box.y1) for string in self._result: textsize = self.textsize(string, scaled=True) _, dy = self.box.get_padding_for(textsize, valign=self.valign, padding=self.line_spacing) height = dy width -= textsize.width + self.line_spacing for char in string: charsize = self.textsize(char, scaled=True) if self.adjustBaseline: draw_xy = base_xy.shift(width, height + charsize.height) else: draw_xy = base_xy.shift(width, height) yield char, draw_xy height += charsize.height + self.line_spacing @property def outlinebox(self): corners = [] for string, xy in self.lines: textsize = self.textsize(string) width = textsize[0] * self.scale height = textsize[1] * self.scale if self.adjustBaseline: xy = XY(xy.x, xy.y - textsize[1]) corners.append(xy) corners.append(XY(xy.x + width, xy.y + height)) if corners: box = Box(min(p.x for p in corners) - self.padding, min(p.y for p in corners) - self.line_spacing, max(p.x for p in corners) + self.padding, max(p.y for p in corners) + self.line_spacing) else: box = Box(self.box[0], self.box[1], self.box[0], self.box[1]) return box def _lines(self): lines = [] measure = 'height' maxwidth, maxheight = self.box.size width = 0 finished = False for line in splitlabel(self.string): for folded in splittext(self, line, maxheight, measure): textsize = self.textsize(folded) if width + textsize.width + self.line_spacing < maxwidth: lines.append(folded) width += textsize.width + self.line_spacing elif len(lines) > 0: lines[-1] = truncate_text(self, lines[-1], maxheight, measure) finished = True break if finished: break return lines class HorizontalTextFolder(object): def __init__(self, drawer, box, string, font, **kwargs): self.drawer = drawer self.box = box self.string = string self.font = font self.scale = 1 self.halign = kwargs.get('halign', 'center') self.valign = kwargs.get('valign', 'center') self.padding = kwargs.get('padding', 8) self.line_spacing = kwargs.get('line_spacing', 2) if kwargs.get('adjustBaseline'): self.adjustBaseline = True else: self.adjustBaseline = False self._result = self._lines() def textsize(self, text, scaled=False): if isinstance(text, string_types): textsize = self.drawer.textlinesize(text, self.font) else: if text: size = [self.textsize(s) for s in text] width = max(s.width for s in size) height = (sum(s.height for s in size) + self.line_spacing * (len(text) - 1)) textsize = Size(width, height) else: textsize = Size(0, 0) if scaled: textsize = Size(textsize.width * self.scale, textsize.height * self.scale) return textsize @property def lines(self): textsize = self.textsize(self._result, scaled=True) _, dy = self.box.get_padding_for(textsize, valign=self.valign, padding=self.line_spacing) height = dy base_xy = XY(self.box.x1, self.box.y1) for string in self._result: textsize = self.textsize(string, scaled=True) dx, _ = self.box.get_padding_for(textsize, halign=self.halign, padding=self.padding) if self.adjustBaseline: draw_xy = base_xy.shift(dx, height + textsize.height) else: draw_xy = base_xy.shift(dx, height) yield string, draw_xy height += textsize.height + self.line_spacing @property def outlinebox(self): corners = [] for string, xy in self.lines: textsize = self.textsize(string) width = textsize[0] * self.scale height = textsize[1] * self.scale if self.adjustBaseline: xy = XY(xy.x, xy.y - textsize[1]) corners.append(xy) corners.append(XY(xy.x + width, xy.y + height)) if corners: box = Box(min(p.x for p in corners) - self.padding, min(p.y for p in corners) - self.line_spacing, max(p.x for p in corners) + self.padding, max(p.y for p in corners) + self.line_spacing) else: box = Box(self.box[0], self.box[1], self.box[0], self.box[1]) return box def _lines(self): lines = [] measure = 'width' maxwidth, maxheight = self.box.size height = 0 finished = False for line in splitlabel(self.string): for folded in splittext(self, line, maxwidth, measure): textsize = self.textsize(folded) if height + textsize.height + self.line_spacing < maxheight: lines.append(folded) height += textsize.height + self.line_spacing else: if len(lines) > 0: lines[-1] = truncate_text(self, lines[-1], maxwidth, measure) finished = True break if finished: break return lines blockdiag-1.3.2/src/blockdiag/imagedraw/utils/0000755000076600000240000000000012242633534022047 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/imagedraw/utils/__init__.py0000644000076600000240000000426712233333104024157 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math import unicodedata from blockdiag.utils import Size from blockdiag.utils.compat import u def is_zenkaku(char): """Detect given character is Japanese ZENKAKU character""" char_width = unicodedata.east_asian_width(char) return char_width in u("WFA") def zenkaku_len(string): """Count Japanese ZENKAKU characters from string""" return len([x for x in string if is_zenkaku(x)]) def hankaku_len(string): """Count non Japanese ZENKAKU characters from string""" return len([x for x in string if not is_zenkaku(x)]) def string_width(string): """Measure rendering width of string. Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point """ widthmap = {'Na': 1, 'N': 1, 'H': 1, 'W': 2, 'F': 2, 'A': 2} return sum(widthmap[unicodedata.east_asian_width(c)] for c in string) def textsize(string, font): """Measure rendering size (width and height) of line. Returned size will not be exactly as rendered text size, Because this method does not use fonts to measure size. """ width = (zenkaku_len(string) * font.size + hankaku_len(string) * font.size * 0.55) return Size(int(math.ceil(width)), font.size) def cached(fn): def func(self, *args, **kwargs): name = fn.__name__ key = args + tuple(kwargs.values()) if name not in self._method_cache: self._method_cache[name] = {} if key not in self._method_cache[name]: self._method_cache[name][key] = fn(self, *args, **kwargs) return self._method_cache[name][key] return func blockdiag-1.3.2/src/blockdiag/imagedraw/utils/ellipse.py0000644000076600000240000000350112217202617024051 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math from blockdiag.utils import XY DIVISION = 1000.0 CYCLE = 10 def _angles(du, a, b, start, end): phi = (start / 180.0) * math.pi while phi <= (end / 180.0) * math.pi: yield phi phi += du / math.sqrt((a * math.sin(phi)) ** 2 + (b * math.cos(phi)) ** 2) def _coordinates(du, a, b, start, end): for angle in _angles(du, a, b, start, end): yield (a * math.cos(angle), b * math.sin(angle)) def endpoints(du, a, b, start, end): pt1 = next(iter(_coordinates(du, a, b, start, start + 1))) pt2 = next(iter(_coordinates(du, a, b, end, end + 1))) return [XY(*pt1), XY(*pt2)] def dots(box, cycle, start=0, end=360): # calcrate rendering pattern from cycle base = 0 rendered = [] for index in range(0, len(cycle), 2): i, j = cycle[index:index + 2] for n in range(base * 2, (base + i) * 2): rendered.append(n) base += i + j a = float(box.width) / 2 b = float(box.height) / 2 du = 1 _max = sum(cycle) * 2 center = box.center for i, coord in enumerate(_coordinates(du, a, b, start, end)): if i % _max in rendered: yield XY(center.x + coord[0], center.y + coord[1]) blockdiag-1.3.2/src/blockdiag/metrics.py0000644000076600000240000011102012217202617020756 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import copy from collections import defaultdict from blockdiag import noderenderer from blockdiag.elements import DiagramNode from blockdiag.utils import Box, Size, XY from blockdiag.utils.fontmap import FontInfo, FontMap cellsize = 8 class EdgeLines(object): def __init__(self): self.xy = None self.stroking = False self.polylines = [] def moveTo(self, x, y=None): self.stroking = False if y is None: self.xy = x else: self.xy = XY(x, y) def lineTo(self, x, y=None): if y is None: elem = x else: elem = XY(x, y) if self.stroking is False: self.stroking = True polyline = [] if self.xy: polyline.append(self.xy) self.polylines.append(polyline) if len(self.polylines[-1]) > 0: if self.polylines[-1][-1] == elem: return self.polylines[-1].append(elem) def lines(self): lines = [] for line in self.polylines: start = line[0] for elem in list(line[1:]): lines.append((start, elem)) start = elem return lines class AutoScaler(object): def __init__(self, subject, scale_ratio): self.subject = subject self.scale_ratio = scale_ratio def __getattr__(self, name): ratio = self.scale_ratio return self.scale(getattr(self.subject, name), ratio) def __getitem__(self, name): ratio = self.scale_ratio return self.scale(self.subject[name], ratio) @classmethod def scale(cls, value, ratio): if not callable(value): return cls._scale(value, ratio) else: def _(*args, **kwargs): ret = value(*args, **kwargs) return cls._scale(ret, ratio) return _ @classmethod def _scale(cls, value, ratio): if ratio == 1: return value klass = value.__class__ if klass == XY: ret = XY(value.x * ratio, value.y * ratio) elif klass == Size: ret = Size(value.width * ratio, value.height * ratio) elif klass == Box: ret = Box(value[0] * ratio, value[1] * ratio, value[2] * ratio, value[3] * ratio) elif klass == tuple: ret = tuple([cls.scale(x, ratio) for x in value]) elif klass == list: ret = [cls.scale(x, ratio) for x in value] elif klass == EdgeLines: ret = EdgeLines() ret.polylines = cls.scale(value.polylines, ratio) elif klass == FontInfo: ret = FontInfo(value.familyname, value.path, value.size * ratio) elif klass == int: ret = value * ratio elif klass == str: ret = value else: ret = cls(value, ratio) return ret @property def original_metrics(self): return self.subject class DiagramMetrics(object): cellsize = cellsize edge_layout = 'normal' node_padding = 4 line_spacing = 2 shadow_offset = XY(3, 6) page_margin = XY(0, 0) page_padding = [0, 0, 0, 0] node_width = cellsize * 16 node_height = cellsize * 5 span_width = cellsize * 8 span_height = cellsize * 5 def __init__(self, diagram, **kwargs): self.drawer = kwargs.get('drawer') if diagram.node_width is not None: self.node_width = diagram.node_width if diagram.node_height is not None: self.node_height = diagram.node_height if diagram.span_width is not None: self.span_width = diagram.span_width if diagram.span_height is not None: self.span_height = diagram.span_height fontmap = kwargs.get('fontmap') if fontmap is not None: self.fontmap = fontmap else: self.fontmap = FontMap() if diagram.page_padding is not None: self.page_padding = diagram.page_padding if diagram.edge_layout is not None: self.edge_layout = diagram.edge_layout # setup spreadsheet sheet = self.spreadsheet = SpreadSheetMetrics(self) nodes = [n for n in diagram.traverse_nodes() if n.drawable] node_width = self.node_width for x in range(diagram.colwidth): widths = [n.width for n in nodes if n.xy.x == x] if widths: width = max(n or node_width for n in widths) sheet.set_node_width(x, width) node_height = self.node_height for y in range(diagram.colheight): heights = [n.height for n in nodes if n.xy.y == y] if heights: height = max(n or node_height for n in heights) sheet.set_node_height(y, height) @property def original_metrics(self): return self def shift(self, x, y): metrics = copy.copy(self) metrics.spreadsheet = copy.copy(self.spreadsheet) metrics.spreadsheet.metrics = metrics metrics.page_margin = XY(x, y) return metrics def textsize(self, string, font=None, width=65535): return self.drawer.textsize(string, font, maxwidth=width) def node(self, node): renderer = noderenderer.get(node.shape) if hasattr(renderer, 'render'): return renderer(node, self) else: return self.cell(node) def cell(self, node, use_padding=True): return self.spreadsheet.node(node, use_padding) def group(self, group): return self.spreadsheet.node(group, use_padding=False) def edge(self, edge): if self.edge_layout == 'flowchart': if edge.node1.group.orientation == 'landscape': return FlowchartLandscapeEdgeMetrics(edge, self) else: return FlowchartPortraitEdgeMetrics(edge, self) else: if edge.node1.group.orientation == 'landscape': return LandscapeEdgeMetrics(edge, self) else: return PortraitEdgeMetrics(edge, self) def font_for(self, element): return self.fontmap.find(element) def pagesize(self, width, height): return self.spreadsheet.pagesize(width, height) class SubMetrics(object): def __getattr__(self, name): # avoid recursion-error on Python 2.6 if 'metrics' not in self.__dict__: raise AttributeError() return getattr(self.metrics, name) class SpreadSheetMetrics(SubMetrics): def __init__(self, metrics): self.metrics = metrics self.node_width = defaultdict(lambda: metrics.node_width) self.node_height = defaultdict(lambda: metrics.node_height) self.span_width = defaultdict(lambda: metrics.span_width) self.span_height = defaultdict(lambda: metrics.span_height) def set_node_width(self, x, width): if (width is not None and 0 < width and (x not in self.node_width or self.node_width[x] < width)): self.node_width[x] = width def set_node_height(self, y, height): if (height is not None and 0 < height and (y not in self.node_height or self.node_height[y] < height)): self.node_height[y] = height def set_span_width(self, x, width): if (width is not None and 0 < width and (x not in self.span_width or self.span_width[x] < width)): self.span_width[x] = width def add_span_width(self, x, width): self.span_width[x] += width def set_span_height(self, y, height): if (height is not None and 0 < height and (y not in self.span_height or self.span_height[y] < height)): self.span_height[y] = height def add_span_height(self, y, height): self.span_height[y] += height def node(self, node, use_padding=True): x1, y1 = self._node_topleft(node, use_padding) x2, y2 = self._node_bottomright(node, use_padding) return NodeMetrics(self.metrics, x1, y1, x2, y2) def _node_topleft(self, node, use_padding=True): x, y = node.xy margin = self.page_margin padding = self.page_padding node_width = sum(self.node_width[i] for i in range(x)) node_height = sum(self.node_height[i] for i in range(y)) span_width = sum(self.span_width[i] for i in range(x + 1)) span_height = sum(self.span_height[i] for i in range(y + 1)) if use_padding: width = node.width or self.metrics.node_width xdiff = (self.node_width[x] - width) // 2 if xdiff < 0: xdiff = 0 height = node.height or self.metrics.node_height ydiff = (self.node_height[y] - height) // 2 if ydiff < 0: ydiff = 0 else: xdiff = 0 ydiff = 0 x1 = margin.x + padding[3] + node_width + span_width + xdiff y1 = margin.y + padding[0] + node_height + span_height + ydiff return XY(x1, y1) def _node_bottomright(self, node, use_padding=True): x = node.xy.x + node.colwidth - 1 y = node.xy.y + node.colheight - 1 margin = self.page_margin padding = self.page_padding node_width = sum(self.node_width[i] for i in range(x + 1)) node_height = sum(self.node_height[i] for i in range(y + 1)) span_width = sum(self.span_width[i] for i in range(x + 1)) span_height = sum(self.span_height[i] for i in range(y + 1)) if use_padding: width = node.width or self.metrics.node_width xdiff = (self.node_width[x] - width) // 2 if xdiff < 0: xdiff = 0 height = node.height or self.metrics.node_height ydiff = (self.node_height[y] - height) // 2 if ydiff < 0: ydiff = 0 else: xdiff = 0 ydiff = 0 x2 = margin.x + padding[3] + node_width + span_width - xdiff y2 = margin.y + padding[0] + node_height + span_height - ydiff return XY(x2, y2) def pagesize(self, width, height): margin = self.metrics.page_margin padding = self.metrics.page_padding dummy = DiagramNode(None) dummy.xy = XY(width - 1, height - 1) x, y = self._node_bottomright(dummy, use_padding=False) x_span = self.span_width[width] y_span = self.span_height[height] return XY(x + margin.x + padding[1] + x_span, y + margin.y + padding[2] + y_span) class NodeMetrics(SubMetrics): def __init__(self, metrics, x1, y1, x2, y2): self.metrics = metrics self._box = Box(x1, y1, x2, y2) def __getattr__(self, name): if hasattr(self._box, name): return getattr(self._box, name) else: return getattr(self.metrics, name) def __getitem__(self, key): return self.box[key] @property def box(self): return self._box @property def marginbox(self): return Box(self._box.x1 - self.span_width // 8, self._box.y1 - self.span_height // 4, self._box.x2 + self.span_width // 8, self._box.y2 + self.span_height // 4) @property def corebox(self): return Box(self._box.x1 + self.node_padding, self._box.y1 + self.node_padding, self._box.x2 - self.node_padding * 2, self._box.y2 - self.node_padding * 2) @property def grouplabelbox(self): return Box(self._box.x1, self._box.y1 - self.span_height // 2, self._box.x2, self._box.y1) class EdgeMetrics(SubMetrics): def __init__(self, edge, metrics): self.metrics = metrics self.edge = edge @property def headshapes(self): pass @property def _shaft(self): pass @property def heads(self): heads = [] head1, head2 = self.headshapes if head1: heads.append(self._head(self.edge.node1, head1)) if head2: heads.append(self._head(self.edge.node2, head2)) return heads def _head(self, node, direct): head = [] cell = self.cellsize node = self.node(node) if direct == 'up': xy = node.bottom head.append(XY(xy.x, xy.y + 1)) head.append(XY(xy.x - cell // 2, xy.y + cell)) head.append(XY(xy.x, xy.y + cell * 2)) head.append(XY(xy.x + cell // 2, xy.y + cell)) head.append(XY(xy.x, xy.y + 1)) elif direct == 'down': xy = node.top head.append(XY(xy.x, xy.y - 1)) head.append(XY(xy.x - cell // 2, xy.y - cell)) head.append(XY(xy.x, xy.y - cell * 2)) head.append(XY(xy.x + cell // 2, xy.y - cell)) head.append(XY(xy.x, xy.y - 1)) elif direct == 'right': xy = node.left head.append(XY(xy.x - 1, xy.y)) head.append(XY(xy.x - cell, xy.y - cell // 2)) head.append(XY(xy.x - cell * 2, xy.y)) head.append(XY(xy.x - cell, xy.y + cell // 2)) head.append(XY(xy.x - 1, xy.y)) elif direct == 'left': xy = node.right head.append(XY(xy.x + 1, xy.y)) head.append(XY(xy.x + cell, xy.y - cell // 2)) head.append(XY(xy.x + cell * 2, xy.y)) head.append(XY(xy.x + cell, xy.y + cell // 2)) head.append(XY(xy.x + 1, xy.y)) elif direct == 'rup': xy = node.bottom head.append(XY(xy.x, xy.y + cell)) head.append(XY(xy.x - cell, xy.y + 1)) head.append(XY(xy.x, xy.y + 1 * 2)) head.append(XY(xy.x + cell, xy.y + 1)) head.append(XY(xy.x, xy.y + cell)) elif direct == 'rdown': xy = node.top head.append(XY(xy.x, xy.y - cell)) head.append(XY(xy.x - cell, xy.y - 1)) head.append(XY(xy.x, xy.y - 1 * 2)) head.append(XY(xy.x + cell, xy.y - 1)) head.append(XY(xy.x, xy.y - cell)) elif direct == 'rright': xy = node.left head.append(XY(xy.x - cell, xy.y)) head.append(XY(xy.x - 1, xy.y - cell)) head.append(XY(xy.x - 1 * 2, xy.y)) head.append(XY(xy.x - 1, xy.y + cell)) head.append(XY(xy.x - cell, xy.y)) elif direct == 'rleft': xy = node.right head.append(XY(xy.x + cell, xy.y)) head.append(XY(xy.x + 1, xy.y - cell)) head.append(XY(xy.x + 1 * 2, xy.y)) head.append(XY(xy.x + 1, xy.y + cell)) head.append(XY(xy.x + cell, xy.y)) if self.edge.hstyle not in ('composition', 'aggregation'): head.pop(2) return head @property def shaft(self): cell = self.cellsize lines = self._shaft head1, head2 = self.headshapes if head1: pt = lines.polylines[0].pop(0) if head1 == 'up': lines.polylines[0].insert(0, XY(pt.x, pt.y + cell)) elif head1 == 'right': lines.polylines[0].insert(0, XY(pt.x - cell, pt.y)) elif head1 == 'left': lines.polylines[0].insert(0, XY(pt.x + cell, pt.y)) elif head1 == 'down': lines.polylines[0].insert(0, XY(pt.x, pt.y - cell)) elif head1 == 'rup': lines.polylines[0].insert(0, XY(pt.x, pt.y + cell)) elif head1 == 'rright': lines.polylines[0].insert(0, XY(pt.x - cell, pt.y)) elif head1 == 'rleft': lines.polylines[0].insert(0, XY(pt.x + cell, pt.y)) elif head1 == 'rdown': lines.polylines[0].insert(0, XY(pt.x, pt.y - cell)) if head2: pt = lines.polylines[-1].pop() if head2 == 'up': lines.polylines[-1].append(XY(pt.x, pt.y + cell)) elif head2 == 'right': lines.polylines[-1].append(XY(pt.x - cell, pt.y)) elif head2 == 'left': lines.polylines[-1].append(XY(pt.x + cell, pt.y)) elif head2 == 'down': lines.polylines[-1].append(XY(pt.x, pt.y - cell)) elif head2 == 'rup': lines.polylines[-1].append(XY(pt.x, pt.y + cell)) elif head2 == 'rright': lines.polylines[-1].append(XY(pt.x - cell, pt.y)) elif head2 == 'rleft': lines.polylines[-1].append(XY(pt.x + cell, pt.y)) elif head2 == 'rdown': lines.polylines[-1].append(XY(pt.x, pt.y - cell)) return lines @property def labelbox(self): pass class LandscapeEdgeMetrics(EdgeMetrics): @property def headshapes(self): heads = [] _dir = self.edge.direction if self.edge.dir in ('back', 'both'): if _dir in ('left-up', 'left', 'same', 'right-up', 'right', 'right-down'): heads.append('left') elif _dir == 'up': if self.edge.skipped: heads.append('left') else: heads.append('down') elif _dir in ('left-down', 'down'): if self.edge.skipped: heads.append('left') else: heads.append('up') if self.edge.hstyle in ('manyone', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) if self.edge.dir in ('forward', 'both'): if _dir in ('right-up', 'right', 'right-down'): heads.append('right') elif _dir == 'up': heads.append('up') elif _dir in ('left-up', 'left', 'left-down', 'down', 'same'): heads.append('down') if self.edge.hstyle in ('onemany', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) return heads @property def _shaft(self): span = XY(self.span_width, self.span_height) _dir = self.edge.direction node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() if _dir == 'right': shaft.moveTo(node1.right) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell1.bottomright.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.bottomright.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) shaft.lineTo(node2.left) elif _dir == 'right-up': shaft.moveTo(node1.right) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell2.bottomleft.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.bottomleft.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell2.left.x - span.x // 4, cell1.right.y) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) shaft.lineTo(node2.left) elif _dir == 'right-down': shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x // 2, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell1.right.x + span.x // 2, cell2.left.y) shaft.lineTo(node2.left) elif _dir == 'up': if self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 4, cell2.bottom.y + span.y // 2) shaft.lineTo(cell2.bottom.x, cell2.bottom.y + span.y // 2) else: shaft.moveTo(node1.top) shaft.lineTo(node2.bottom) elif _dir in ('left-up', 'left', 'same'): shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 4, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(node2.top) elif _dir == 'left-down': if self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) else: shaft.moveTo(node1.bottom) shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) shaft.lineTo(node2.top) elif _dir == 'down': if self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) else: shaft.moveTo(node1.bottom) shaft.lineTo(node2.top) return shaft @property def labelbox(self): span = XY(self.span_width, self.span_height) node = XY(self.node_width, self.node_height) _dir = self.edge.direction node1 = self.cell(self.edge.node1, use_padding=False) node2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'right': if self.edge.skipped: box = Box(node1.bottomright.x + span.x, node1.bottomright.y, node2.bottomleft.x - span.x, node2.bottomleft.y + span.y // 2) else: box = Box(node1.topright.x, node1.topright.y - span.y // 8, node2.left.x, node2.left.y - span.y // 8) elif _dir == 'right-up': box = Box(node2.left.x - span.x, node1.top.y - node.y // 2, node2.bottomleft.x, node1.top.y) elif _dir == 'right-down': box = Box(node1.right.x, node2.topleft.y - span.y // 8, node1.right.x + span.x, node2.left.y - span.y // 8) elif _dir in ('up', 'left-up', 'left', 'same'): if self.edge.node2.xy.y < self.edge.node1.xy.y: box = Box(node1.topright.x - span.x // 2 + span.x // 4, node1.topright.y - span.y // 2, node1.topright.x + span.x // 2 + span.x // 4, node1.topright.y) else: box = Box(node1.top.x + span.x // 4, node1.top.y - span.y, node1.topright.x + span.x // 4, node1.topright.y - span.y // 2) elif _dir in ('left-down', 'down'): box = Box(node2.top.x + span.x // 4, node2.top.y - span.y, node2.topright.x + span.x // 4, node2.topright.y - span.y // 2) # shrink box box = Box(box[0] + span.x // 8, box[1], box[2] - span.x // 8, box[3]) return box class PortraitEdgeMetrics(EdgeMetrics): @property def headshapes(self): heads = [] _dir = self.edge.direction if self.edge.dir in ('back', 'both'): if _dir == 'right': if self.edge.skipped: heads.append('up') else: heads.append('left') elif _dir in ('up', 'right-up', 'same'): heads.append('up') elif _dir in ('left-up', 'left'): heads.append('left') elif _dir in ('left-down', 'down', 'right-down'): if self.edge.skipped: heads.append('left') else: heads.append('up') if self.edge.hstyle in ('manyone', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) if self.edge.dir in ('forward', 'both'): if _dir == 'right': if self.edge.skipped: heads.append('down') else: heads.append('right') elif _dir in ('up', 'right-up', 'same'): heads.append('down') elif _dir in ('left-up', 'left', 'left-down', 'down', 'right-down'): heads.append('down') if self.edge.hstyle in ('onemany', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) return heads @property def _shaft(self): span = XY(self.span_width, self.span_height) _dir = self.edge.direction node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() if _dir in ('up', 'right-up', 'same', 'right'): if _dir == 'right' and not self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(node2.left) else: shaft.moveTo(node1.bottom) shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 4, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 4, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(node2.top) elif _dir == 'right-down': shaft.moveTo(node1.bottom) shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) if self.edge.skipped: shaft.lineTo(cell2.left.x - span.x // 2, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.topleft.x - span.x // 2, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) else: shaft.lineTo(cell2.top.x, cell1.bottom.y + span.y // 2) shaft.lineTo(node2.top) elif _dir in ('left-up', 'left', 'same'): shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 4, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(node2.top) elif _dir == 'left-down': shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 2, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 2, cell2.top.y - span.y // 2) else: shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) shaft.lineTo(node2.top) elif _dir == 'down': shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell1.right.x + span.x // 2, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 2, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) shaft.lineTo(node2.top) return shaft @property def labelbox(self): span = XY(self.span_width, self.span_height) _dir = self.edge.direction node1 = self.cell(self.edge.node1, use_padding=False) node2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'right': if self.edge.skipped: box = Box(node1.bottomright.x + span.x, node1.bottomright.y, node2.bottomleft.x - span.x, node2.bottomleft.y + span.y // 2) else: box = Box(node1.topright.x, node1.topright.y - span.y // 8, node2.left.x, node2.left.y - span.y // 8) elif _dir == 'right-up': box = Box(node2.left.x - span.x, node2.left.y, node2.bottomleft.x, node2.bottomleft.y) elif _dir == 'right-down': box = Box(node2.topleft.x, node2.topleft.y - span.y // 2, node2.top.x, node2.top.y) elif _dir in ('up', 'left-up', 'left', 'same'): if self.edge.node2.xy.y < self.edge.node1.xy.y: box = Box(node1.topright.x - span.x // 2 + span.x // 4, node1.topright.y - span.y // 2, node1.topright.x + span.x // 2 + span.x // 4, node1.topright.y) else: box = Box(node1.top.x + span.x // 4, node1.top.y - span.y, node1.topright.x + span.x // 4, node1.topright.y - span.y // 2) elif _dir == 'down': box = Box(node2.top.x + span.x // 4, node2.top.y - span.y // 2, node2.topright.x + span.x // 4, node2.topright.y) elif _dir == 'left-down': box = Box(node1.bottomleft.x, node1.bottomleft.y, node1.bottom.x, node1.bottom.y + span.y // 2) # shrink box box = Box(box[0] + span.x // 8, box[1], box[2] - span.x // 8, box[3]) return box class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics): @property def headshapes(self): heads = [] if self.edge.direction == 'right-down': if self.edge.dir in ('back', 'both'): if self.edge.hstyle in ('manyone', 'manymany'): heads.append('rup') else: heads.append('up') else: heads.append(None) if self.edge.dir in ('forward', 'both'): if self.edge.hstyle in ('onemany', 'manymany'): heads.append('rright') else: heads.append('right') else: heads.append(None) else: heads = super(FlowchartLandscapeEdgeMetrics, self).headshapes return heads @property def _shaft(self): if self.edge.direction == 'right-down': span = XY(self.span_width, self.span_height) node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell1.bottom.x, cell2.left.y) shaft.lineTo(node2.left) else: shaft = super(FlowchartLandscapeEdgeMetrics, self)._shaft return shaft @property def labelbox(self): _dir = self.edge.direction if _dir == 'right': span = XY(self.span_width, self.span_height) cell1 = self.cell(self.edge.node1, use_padding=False) cell2 = self.cell(self.edge.node2, use_padding=False) if self.edge.skipped: box = Box(cell1.bottom.x, cell1.bottom.y, cell1.bottomright.x, cell1.bottomright.y + span.y // 2) else: box = Box(cell1.bottom.x, cell2.left.y - span.y // 2, cell1.bottom.x, cell2.left.y) else: box = super(FlowchartLandscapeEdgeMetrics, self).labelbox return box class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics): @property def headshapes(self): heads = [] if self.edge.direction == 'right-down': if self.edge.dir in ('back', 'both'): if self.edge.hstyle in ('manyone', 'manymany'): heads.append('left') else: heads.append('left') else: heads.append(None) if self.edge.dir in ('forward', 'both'): if self.edge.dir in ('onemany', 'manymany'): heads.append('rdown') else: heads.append('down') else: heads.append(None) else: heads = super(FlowchartPortraitEdgeMetrics, self).headshapes return heads @property def _shaft(self): if self.edge.direction == 'right-down': span = XY(self.span_width, self.span_height) node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() shaft.moveTo(node1.right) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x * 3 // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x * 3 // 4, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) else: shaft.lineTo(cell2.top.x, cell1.right.y) shaft.lineTo(node2.top) else: shaft = super(FlowchartPortraitEdgeMetrics, self)._shaft return shaft @property def labelbox(self): _dir = self.edge.direction span = XY(self.span_width, self.span_height) cell1 = self.cell(self.edge.node1, use_padding=False) cell2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'down': box = Box(cell2.topleft.x, cell2.top.y - span.y // 2, cell2.top.x, cell2.top.y) elif _dir == 'right': if self.edge.skipped: box = Box(cell1.bottom.x, cell1.bottom.y, cell1.bottomright.x, cell1.bottomright.y + span.y // 2) else: box = Box(cell1.bottom.x, cell2.left.y - span.y // 2, cell1.bottom.x, cell2.left.y) else: box = super(FlowchartPortraitEdgeMetrics, self).labelbox return box blockdiag-1.3.2/src/blockdiag/noderenderer/0000755000076600000240000000000012242633535021424 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/noderenderer/__init__.py0000644000076600000240000001311212242633443023531 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import pkg_resources from blockdiag.utils import images, Box, XY renderers = {} searchpath = [] def init_renderers(): for plugin in pkg_resources.iter_entry_points('blockdiag_noderenderer'): module = plugin.load() if hasattr(module, 'setup'): module.setup(module) def install_renderer(name, renderer): renderers[name] = renderer def set_default_namespace(path): searchpath[:] = [] for path in path.split(','): searchpath.append(path) def get(shape): if not renderers: init_renderers() for path in searchpath: name = "%s.%s" % (path, shape) if name in renderers: return renderers[name] return renderers.get(shape) class NodeShape(object): def __init__(self, node, metrics=None): self.node = node self.metrics = metrics m = self.metrics.cell(self.node) self.textalign = 'center' self.connectors = [m.top, m.right, m.bottom, m.left] if node.icon is None: self.iconbox = None self.textbox = m.box else: image_size = images.get_image_size(node.icon) if image_size is None: iconsize = (0, 0) else: boundedbox = [metrics.node_width // 2, metrics.node_height] iconsize = images.calc_image_size(image_size, boundedbox) vmargin = (metrics.node_height - iconsize[1]) // 2 self.iconbox = Box(m.topleft.x, m.topleft.y + vmargin, m.topleft.x + iconsize[0], m.topleft.y + vmargin + iconsize[1]) self.textbox = Box(self.iconbox[2], m.top.y, m.bottomright.x, m.bottomright.y) def render(self, drawer, _format, **kwargs): if self.node.stacked and not kwargs.get('stacked'): node = self.node.duplicate() node.label = "" node.background = "" for i in range(2, 0, -1): # use original_metrics FORCE r = self.metrics.original_metrics.cellsize // 2 * i metrics = self.metrics.shift(r, r) self.__class__(node, metrics).render(drawer, _format, stacked=True, **kwargs) if hasattr(self, 'render_vector_shape') and _format == 'SVG': self.render_vector_shape(drawer, _format, **kwargs) else: self.render_shape(drawer, _format, **kwargs) self.render_icon(drawer, **kwargs) self.render_label(drawer, **kwargs) self.render_number_badge(drawer, **kwargs) def render_icon(self, drawer, **kwargs): if self.node.icon is not None and kwargs.get('shadow') is not True: drawer.image(self.iconbox, self.node.icon) def render_shape(self, drawer, _, **kwargs): pass def render_label(self, drawer, **kwargs): if not kwargs.get('shadow'): font = self.metrics.font_for(self.node) drawer.textarea(self.textbox, self.node.label, font, rotate=self.node.rotate, fill=self.node.textcolor, halign=self.textalign, line_spacing=self.metrics.line_spacing, orientation=self.node.label_orientation) def render_number_badge(self, drawer, **kwargs): if self.node.numbered is not None and kwargs.get('shadow') is None: badgeFill = kwargs.get('badgeFill') xy = self.metrics.cell(self.node).topleft r = self.metrics.cellsize * 3 // 2 box = Box(xy.x - r, xy.y - r, xy.x + r, xy.y + r) font = self.metrics.font_for(self.node) drawer.ellipse(box, outline=self.node.linecolor, fill=badgeFill) drawer.textarea(box, self.node.numbered, font, rotate=self.node.rotate, fill=self.node.textcolor) @property def top(self): return self.connectors[0] @property def left(self): return self.connectors[3] @property def right(self): point = self.connectors[1] if self.node.stacked: point = XY(point.x + self.metrics.cellsize, point.y) return point @property def bottom(self): point = self.connectors[2] if self.node.stacked: point = XY(point.x, point.y + self.metrics.cellsize) return point def shift_shadow(self, value): xdiff = self.metrics.shadow_offset.x ydiff = self.metrics.shadow_offset.y if isinstance(value, XY): ret = XY(value.x + xdiff, value.y + ydiff) elif isinstance(value, Box): ret = Box(value.x1 + xdiff, value.y1 + ydiff, value.x2 + xdiff, value.y2 + ydiff) elif isinstance(value, (list, tuple)): ret = [self.shift_shadow(x) for x in value] return ret blockdiag-1.3.2/src/blockdiag/noderenderer/actor.py0000644000076600000240000001053712242633443023112 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY, Box class Actor(NodeShape): def __init__(self, node, metrics=None): super(Actor, self).__init__(node, metrics) shortside = min(self.node.width or metrics.node_height, self.node.height or metrics.node_height) r = self.radius = shortside // 8 # radius of actor's head self.center = metrics.cell(node).center self.connectors[0] = XY(self.center.x, self.center.y - r * 9 // 2) self.connectors[1] = XY(self.center.x + r * 4, self.center.y) self.connectors[2] = XY(self.center.x, self.center.y + r * 4) self.connectors[3] = XY(self.center.x - r * 4, self.center.y) def head_part(self): r = self.radius * 3 // 2 pt = self.metrics.cell(self.node).center.shift(y=-self.radius * 3) return Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r) def body_part(self): r = self.radius m = self.metrics.cell(self.node) bodyC = m.center neckWidth = r * 2 // 3 # neck size arm = r * 4 # arm length armWidth = r bodyWidth = r * 2 // 3 # half of body width bodyHeight = r legXout = r * 7 // 2 # toe outer position legYout = bodyHeight + r * 3 legXin = r * 2 # toe inner position legYin = bodyHeight + r * 3 return [XY(bodyC.x + neckWidth, bodyC.y - r * 2), XY(bodyC.x + neckWidth, bodyC.y - armWidth), # neck end XY(bodyC.x + arm, bodyC.y - armWidth), XY(bodyC.x + arm, bodyC.y), # right arm end XY(bodyC.x + bodyWidth, bodyC.y), # right body end XY(bodyC.x + bodyWidth, bodyC.y + bodyHeight), XY(bodyC.x + legXout, bodyC.y + legYout), XY(bodyC.x + legXin, bodyC.y + legYin), XY(bodyC.x, bodyC.y + (bodyHeight * 2)), # body bottom center XY(bodyC.x - legXin, bodyC.y + legYin), XY(bodyC.x - legXout, bodyC.y + legYout), XY(bodyC.x - bodyWidth, bodyC.y + bodyHeight), XY(bodyC.x - bodyWidth, bodyC.y), # left body end XY(bodyC.x - arm, bodyC.y), XY(bodyC.x - arm, bodyC.y - armWidth), XY(bodyC.x - neckWidth, bodyC.y - armWidth), # left arm end XY(bodyC.x - neckWidth, bodyC.y - r * 2)] def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # FIXME: Actor does not support # - background image # - textarea # draw body part body = self.body_part() if kwargs.get('shadow'): body = self.shift_shadow(body) if kwargs.get('style') == 'blur': drawer.polygon(body, fill=fill, filter='transp-blur') else: drawer.polygon(body, fill=fill) else: drawer.polygon(body, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw head part head = self.head_part() if kwargs.get('shadow'): head = self.shift_shadow(head) if kwargs.get('style') == 'blur': drawer.ellipse(head, fill=fill, outline=self.node.linecolor, filter='transp-blur') else: drawer.ellipse(head, fill=fill, outline=self.node.linecolor) else: drawer.ellipse(head, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def render_label(self, drawer, **kwargs): pass def setup(self): install_renderer('actor', Actor) blockdiag-1.3.2/src/blockdiag/noderenderer/beginpoint.py0000644000076600000240000000437112047156046024141 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY, Box class BeginPoint(NodeShape): def __init__(self, node, metrics=None): super(BeginPoint, self).__init__(node, metrics) m = metrics.cell(node) self.radius = metrics.cellsize self.center = m.center self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' self.connectors = [XY(self.center.x, self.center.y - self.radius), XY(self.center.x + self.radius, self.center.y), XY(self.center.x, self.center.y + self.radius), XY(self.center.x - self.radius, self.center.y)] def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline r = self.radius box = Box(self.center.x - r, self.center.y - r, self.center.x + r, self.center.y + r) if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) else: if self.node.color == self.node.basecolor: color = self.node.linecolor else: color = self.node.color drawer.ellipse(box, fill=color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('beginpoint', BeginPoint) blockdiag-1.3.2/src/blockdiag/noderenderer/box.py0000644000076600000240000000334012115612446022563 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer class Box(NodeShape): def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(box, outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('box', Box) blockdiag-1.3.2/src/blockdiag/noderenderer/circle.py0000644000076600000240000000330112242633443023232 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class Circle(NodeShape): def __init__(self, node, metrics=None): super(Circle, self).__init__(node, metrics) r = min(metrics.node_width, metrics.node_height) // 2 + \ metrics.cellsize // 2 pt = metrics.cell(node).center self.connectors = [XY(pt.x, pt.y - r), # top XY(pt.x + r, pt.y), # right XY(pt.x, pt.y + r), # bottom XY(pt.x - r, pt.y)] # left self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): box = self.shift_shadow(self.textbox) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) elif self.node.background: drawer.ellipse(self.textbox, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.ellipse(self.textbox, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.ellipse(self.textbox, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('circle', Circle) blockdiag-1.3.2/src/blockdiag/noderenderer/cloud.py0000644000076600000240000001311712242633443023105 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box from blockdiag.imagedraw.simplesvg import pathdata class Cloud(NodeShape): def __init__(self, node, metrics=None): super(Cloud, self).__init__(node, metrics) pt = metrics.cell(node).topleft rx = (self.node.width or self.metrics.node_width) // 12 ry = (self.node.height or self.metrics.node_height) // 5 self.textbox = Box(pt.x + rx * 2, pt.y + ry, pt.x + rx * 11, pt.y + ry * 4) def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) if not kwargs.get('shadow') and self.node.background: drawer.image(self.textbox, self.node.background) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) pt = m.topleft rx = (self.node.width or self.metrics.node_width) // 12 ry = (self.node.height or self.metrics.node_height) // 5 ellipses = [Box(pt.x + rx * 2, pt.y + ry, pt.x + rx * 5, pt.y + ry * 3), Box(pt.x + rx * 4, pt.y, pt.x + rx * 9, pt.y + ry * 2), Box(pt.x + rx * 8, pt.y + ry, pt.x + rx * 11, pt.y + ry * 3), Box(pt.x + rx * 9, pt.y + ry * 2, pt.x + rx * 13, pt.y + ry * 4), Box(pt.x + rx * 8, pt.y + ry * 2, pt.x + rx * 11, pt.y + ry * 5), Box(pt.x + rx * 5, pt.y + ry * 2, pt.x + rx * 8, pt.y + ry * 5), Box(pt.x + rx * 2, pt.y + ry * 2, pt.x + rx * 5, pt.y + ry * 5), Box(pt.x + rx * 0, pt.y + ry * 2, pt.x + rx * 4, pt.y + ry * 4)] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rects = [Box(pt.x + rx * 2, pt.y + ry * 2, pt.x + rx * 11, pt.y + ry * 4), Box(pt.x + rx * 4, pt.y + ry, pt.x + rx * 9, pt.y + ry * 2)] for rect in rects: if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # create pathdata m = self.metrics.cell(self.node) rx = (self.node.width or self.metrics.node_width) // 12 ry = (self.node.height or self.metrics.node_height) // 5 pt = m.topleft if kwargs.get('shadow'): pt = self.shift_shadow(pt) path = pathdata(pt.x + rx * 2, pt.y + ry * 2) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 4, pt.y + ry) path.ellarc(rx * 2, ry * 3 // 4, 0, 0, 1, pt.x + rx * 9, pt.y + ry) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 11, pt.y + ry * 2) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 11, pt.y + ry * 4) path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 8, pt.y + ry * 4) path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 5, pt.y + ry * 4) path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 4) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 2) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('cloud', Cloud) blockdiag-1.3.2/src/blockdiag/noderenderer/diamond.py0000644000076600000240000000510012242633443023403 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class Diamond(NodeShape): def __init__(self, node, metrics=None): super(Diamond, self).__init__(node, metrics) r = metrics.cellsize m = metrics.cell(node) self.connectors = [XY(m.top.x, m.top.y - r), XY(m.right.x + r, m.right.y), XY(m.bottom.x, m.bottom.y + r), XY(m.left.x - r, m.left.y), XY(m.top.x, m.top.y - r)] self.textbox = Box((self.connectors[0].x + self.connectors[3].x) // 2, (self.connectors[0].y + self.connectors[3].y) // 2, (self.connectors[1].x + self.connectors[2].x) // 2, (self.connectors[1].y + self.connectors[2].y) // 2) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): diamond = self.shift_shadow(self.connectors) if kwargs.get('style') == 'blur': drawer.polygon(diamond, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(diamond, fill=fill, outline=fill) elif self.node.background: drawer.polygon(self.connectors, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(self.connectors, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(self.connectors, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('diamond', Diamond) install_renderer('flowchart.condition', Diamond) blockdiag-1.3.2/src/blockdiag/noderenderer/dots.py0000644000076600000240000000330412050127421022734 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class Dots(NodeShape): def render_label(self, drawer, **kwargs): pass def render_shape(self, drawer, _, **kwargs): if kwargs.get('shadow'): return m = self.metrics center = m.cell(self.node).center dots = [center] if self.node.group.orientation == 'landscape': pt = XY(center.x, center.y - m.node_height / 2) dots.append(pt) pt = XY(center.x, center.y + m.node_height / 2) dots.append(pt) else: pt = XY(center.x - m.node_width / 3, center.y) dots.append(pt) pt = XY(center.x + m.node_width / 3, center.y) dots.append(pt) r = m.cellsize / 2 for dot in dots: box = Box(dot.x - r, dot.y - r, dot.x + r, dot.y + r) drawer.ellipse(box, fill=self.node.linecolor, outline=self.node.linecolor) def setup(self): install_renderer('dots', Dots) blockdiag-1.3.2/src/blockdiag/noderenderer/ellipse.py0000644000076600000240000000373212047156046023440 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box class Ellipse(NodeShape): def __init__(self, node, metrics=None): super(Ellipse, self).__init__(node, metrics) r = metrics.cellsize box = metrics.cell(node).box self.textbox = Box(box[0] + r, box[1] + r, box[2] - r, box[3] - r) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) elif self.node.background: drawer.ellipse(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.ellipse(box, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.ellipse(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('ellipse', Ellipse) blockdiag-1.3.2/src/blockdiag/noderenderer/endpoint.py0000644000076600000240000000506512047156046023624 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY, Box class EndPoint(NodeShape): def __init__(self, node, metrics=None): super(EndPoint, self).__init__(node, metrics) m = metrics.cell(node) self.radius = metrics.cellsize self.center = m.center self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' self.connectors = [XY(self.center.x, self.center.y - self.radius), XY(self.center.x + self.radius, self.center.y), XY(self.center.x, self.center.y + self.radius), XY(self.center.x - self.radius, self.center.y)] def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outer circle r = self.radius box = Box(self.center.x - r, self.center.y - r, self.center.x + r, self.center.y + r) if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) else: drawer.ellipse(box, fill='white', outline=self.node.linecolor, style=self.node.style) # draw inner circle box = Box(self.center.x - r / 2, self.center.y - r / 2, self.center.x + r / 2, self.center.y + r / 2) if not kwargs.get('shadow'): if self.node.color == self.node.basecolor: color = self.node.linecolor else: color = self.node.color drawer.ellipse(box, fill=color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('endpoint', EndPoint) blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/0000755000076600000240000000000012242633535023415 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/__init__.py0000644000076600000240000000114411635000760025517 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/database.py0000644000076600000240000001215612242633443025536 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY, Box from blockdiag.imagedraw.simplesvg import pathdata class Database(NodeShape): def __init__(self, node, metrics=None): super(Database, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize self.textbox = Box(m.topleft.x, m.topleft.y + r * 3 // 2, m.bottomright.x, m.bottomright.y - r // 2) def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) # draw background image if self.node.background: drawer.image(self.textbox, self.node.background) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box ellipse = Box(box[0], box[3] - r * 2, box[2], box[3]) if kwargs.get('shadow'): ellipse = self.shift_shadow(ellipse) if kwargs.get('style') == 'blur': drawer.ellipse(ellipse, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(ellipse, fill=fill, outline=fill) else: drawer.ellipse(ellipse, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rect = Box(box[0], box[1] + r, box[2], box[3] - r) if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) ellipse = Box(box[0], box[1], box[2], box[1] + r * 2) if kwargs.get('shadow'): ellipse = self.shift_shadow(ellipse) if kwargs.get('style') == 'blur': drawer.ellipse(ellipse, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(ellipse, fill=fill, outline=fill) else: drawer.ellipse(ellipse, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # line both side lines = [(XY(box[0], box[1] + r), XY(box[0], box[3] - r)), (XY(box[2], box[1] + r), XY(box[2], box[3] - r))] for line in lines: if not kwargs.get('shadow'): drawer.line(line, fill=self.node.linecolor, style=self.node.style) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize width = self.metrics.node_width box = m.box if kwargs.get('shadow'): box = self.shift_shadow(box) path = pathdata(box[0], box[1] + r) path.ellarc(width // 2, r, 0, 0, 1, box[2], box[1] + r) path.line(box[2], box[3] - r) path.ellarc(width // 2, r, 0, 0, 1, box[0], box[3] - r) path.line(box[0], box[1] + r) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw cap of cylinder if not kwargs.get('shadow'): path = pathdata(box[2], box[1] + r) path.ellarc(width // 2, r, 0, 0, 1, box[0], box[1] + r) drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.database', Database) blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/input.py0000644000076600000240000000452412227702767025142 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class Input(NodeShape): def __init__(self, node, metrics=None): super(Input, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize * 3 self.textbox = Box(m.topleft.x + r, m.topleft.y, m.bottomright.x - r, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 3 shape = [XY(m.topleft.x + r, m.topleft.y), XY(m.topright.x, m.topright.y), XY(m.bottomright.x - r, m.bottomright.y), XY(m.bottomleft.x, m.bottomleft.y), XY(m.topleft.x + r, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.input', Input) blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/loopin.py0000644000076600000240000000505212242633443025267 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class LoopIn(NodeShape): def __init__(self, node, metrics=None): super(LoopIn, self).__init__(node, metrics) m = self.metrics.cell(self.node) ydiff = self.metrics.node_height // 4 self.textbox = Box(m.topleft.x, m.topleft.y + ydiff, m.bottomright.x, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) xdiff = self.metrics.node_width // 4 ydiff = self.metrics.node_height // 4 shape = [XY(m.topleft.x + xdiff, m.topleft.y), XY(m.topright.x - xdiff, m.topleft.y), XY(m.topright.x, m.topright.y + ydiff), XY(m.topright.x, m.bottomright.y), XY(m.topleft.x, m.bottomleft.y), XY(m.topleft.x, m.topleft.y + ydiff), XY(m.topleft.x + xdiff, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.loopin', LoopIn) blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/loopout.py0000644000076600000240000000507112242633443025471 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class LoopOut(NodeShape): def __init__(self, node, metrics=None): super(LoopOut, self).__init__(node, metrics) m = self.metrics.cell(self.node) ydiff = self.metrics.node_height // 4 self.textbox = Box(m.topleft.x, m.topleft.y, m.bottomright.x, m.bottomright.y - ydiff) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) xdiff = self.metrics.node_width // 4 ydiff = self.metrics.node_height // 4 shape = [XY(m.topleft.x, m.topleft.y), XY(m.topright.x, m.topright.y), XY(m.bottomright.x, m.bottomright.y - ydiff), XY(m.bottomright.x - xdiff, m.bottomright.y), XY(m.bottomleft.x + xdiff, m.bottomleft.y), XY(m.bottomleft.x, m.bottomleft.y - ydiff), XY(m.topleft.x, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.loopout', LoopOut) blockdiag-1.3.2/src/blockdiag/noderenderer/flowchart/terminator.py0000644000076600000240000001070412216056572026156 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY, Box from blockdiag.imagedraw.simplesvg import pathdata class Terminator(NodeShape): def __init__(self, node, metrics=None): super(Terminator, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 self.textbox = Box(m.topleft.x + r, m.topleft.y, m.bottomright.x - r, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) # draw outline if not kwargs.get('shadow') and self.node.background: drawer.image(self.textbox, self.node.background) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 box = m.box ellipses = [Box(box[0], box[1], box[0] + r * 2, box[3]), Box(box[2] - r * 2, box[1], box[2], box[3])] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rect = Box(box[0] + r, box[1], box[2] - r, box[3]) if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])), (XY(box[0] + r, box[3]), XY(box[2] - r, box[3]))] for line in lines: if not kwargs.get('shadow'): drawer.line(line, fill=self.node.linecolor, style=self.node.style) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # create pathdata m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 height = self.metrics.node_height box = Box(m.topleft.x + r, m.topleft.y, m.bottomright.x - r, m.bottomright.y) if kwargs.get('shadow'): box = self.shift_shadow(box) path = pathdata(box[0], box[1]) path.line(box[2], box[1]) path.ellarc(r, height / 2, 0, 0, 1, box[2], box[3]) path.line(box[0], box[3]) path.ellarc(r, height / 2, 0, 0, 1, box[0], box[1]) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.terminator', Terminator) blockdiag-1.3.2/src/blockdiag/noderenderer/mail.py0000644000076600000240000000451212047156046022722 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class Mail(NodeShape): def __init__(self, node, metrics=None): super(Mail, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 self.textbox = Box(m.topleft.x, m.topleft.y + r, m.bottomright.x, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(box, outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw flap if not kwargs.get('shadow'): flap = [m.topleft, XY(m.top.x, m.top.y + r), m.topright] drawer.line(flap, fill=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('mail', Mail) blockdiag-1.3.2/src/blockdiag/noderenderer/minidiamond.py0000644000076600000240000000364512047156046024276 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class MiniDiamond(NodeShape): def __init__(self, node, metrics=None): super(MiniDiamond, self).__init__(node, metrics) r = metrics.cellsize m = metrics.cell(node) c = m.center self.connectors = (XY(c.x, c.y - r), XY(c.x + r, c.y), XY(c.x, c.y + r), XY(c.x - r, c.y), XY(c.x, c.y - r)) self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): diamond = self.shift_shadow(self.connectors) if kwargs.get('style') == 'blur': drawer.polygon(diamond, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(diamond, fill=fill, outline=fill) else: drawer.polygon(self.connectors, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('minidiamond', MiniDiamond) blockdiag-1.3.2/src/blockdiag/noderenderer/none.py0000644000076600000240000000211312047156046022732 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer class NoneShape(NodeShape): def __init__(self, node, metrics=None): super(NoneShape, self).__init__(node, metrics) p = metrics.cell(node).center self.connectors = [p, p, p, p] def render_label(self, drawer, **kwargs): pass def render_shape(self, drawer, _, **kwargs): pass def setup(self): install_renderer('none', NoneShape) blockdiag-1.3.2/src/blockdiag/noderenderer/note.py0000644000076600000240000000435112047156046022746 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY class Note(NodeShape): def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 tr = m.topright note = [m.topleft, XY(tr.x - r, tr.y), XY(tr.x, tr.y + r), m.bottomright, m.bottomleft, m.topleft] box = self.metrics.cell(self.node).box # draw outline if kwargs.get('shadow'): note = self.shift_shadow(note) if kwargs.get('style') == 'blur': drawer.polygon(note, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(note, fill=fill, outline=fill) elif self.node.background: drawer.polygon(note, fill=self.node.color, outline=self.node.color) drawer.image(box, self.node.background) drawer.polygon(note, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(note, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw folded if not kwargs.get('shadow'): folded = [XY(tr.x - r, tr.y), XY(tr.x - r, tr.y + r), XY(tr.x, tr.y + r)] drawer.line(folded, fill=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('note', Note) blockdiag-1.3.2/src/blockdiag/noderenderer/roundedbox.py0000644000076600000240000001225512216056572024155 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import XY, Box from blockdiag.imagedraw.simplesvg import pathdata class RoundedBox(NodeShape): def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) # draw outline box = self.metrics.cell(self.node).box if not kwargs.get('shadow'): if self.node.background: drawer.image(box, self.node.background) self.render_shape_outline(drawer, **kwargs) def render_shape_outline(self, drawer, **kwargs): m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])), (XY(box[2], box[1] + r), XY(box[2], box[3] - r)), (XY(box[0] + r, box[3]), XY(box[2] - r, box[3])), (XY(box[0], box[1] + r), XY(box[0], box[3] - r))] for line in lines: drawer.line(line, fill=self.node.linecolor, style=self.node.style) r2 = r * 2 arcs = [(Box(box[0], box[1], box[0] + r2, box[1] + r2), 180, 270), (Box(box[2] - r2, box[1], box[2], box[1] + r2), 270, 360), (Box(box[2] - r2, box[3] - r2, box[2], box[3]), 0, 90), (Box(box[0], box[3] - r2, box[0] + r2, box[3]), 90, 180)] for arc in arcs: drawer.arc(arc[0], arc[1], arc[2], fill=self.node.linecolor, style=self.node.style) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box ellipses = [Box(box[0], box[1], box[0] + r * 2, box[1] + r * 2), Box(box[2] - r * 2, box[1], box[2], box[1] + r * 2), Box(box[0], box[3] - r * 2, box[0] + r * 2, box[3]), Box(box[2] - r * 2, box[3] - r * 2, box[2], box[3])] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.color) rects = [Box(box[0] + r, box[1], box[2] - r, box[3]), Box(box[0], box[1] + r, box[2], box[3] - r)] for rect in rects: if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # create pathdata box = self.metrics.cell(self.node).box r = self.metrics.cellsize if kwargs.get('shadow'): box = self.shift_shadow(box) path = pathdata(box[0] + r, box[1]) path.line(box[2] - r, box[1]) path.ellarc(r, r, 0, 0, 1, box[2], box[1] + r) path.line(box[2], box[3] - r) path.ellarc(r, r, 0, 0, 1, box[2] - r, box[3]) path.line(box[0] + r, box[3]) path.ellarc(r, r, 0, 0, 1, box[0], box[3] - r) path.line(box[0], box[1] + r) path.ellarc(r, r, 0, 0, 1, box[0] + r, box[1]) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('roundedbox', RoundedBox) blockdiag-1.3.2/src/blockdiag/noderenderer/square.py0000644000076600000240000000341512242633443023277 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import Box, XY class Square(NodeShape): def __init__(self, node, metrics=None): super(Square, self).__init__(node, metrics) r = min(metrics.node_width, metrics.node_height) // 2 + \ metrics.cellsize // 2 pt = metrics.cell(node).center self.connectors = [XY(pt.x, pt.y - r), # top XY(pt.x + r, pt.y), # right XY(pt.x, pt.y + r), # bottom XY(pt.x - r, pt.y)] # left self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): box = self.shift_shadow(self.textbox) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(self.textbox, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(self.textbox, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(self.textbox, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('square', Square) blockdiag-1.3.2/src/blockdiag/noderenderer/textbox.py0000644000076600000240000000343012242633443023471 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import NodeShape from blockdiag.noderenderer import install_renderer from blockdiag.utils import images, Box, XY class TextBox(NodeShape): def __init__(self, node, metrics=None): super(TextBox, self).__init__(node, metrics) if self.node.background: size = images.get_image_size(self.node.background) size = images.calc_image_size(size, self.textbox.size) pt = self.textbox.center self.textbox = Box(pt.x - size[0] // 2, pt.y - size[1] // 2, pt.x + size[0] // 2, pt.y + size[1] // 2) self.connectors[0] = XY(pt.x, self.textbox[1]) self.connectors[1] = XY(self.textbox[2], pt.y) self.connectors[2] = XY(pt.x, self.textbox[3]) self.connectors[3] = XY(self.textbox[0], pt.y) if self.node.icon: self.connectors[3] = XY(self.iconbox[0], pt.y) def render_shape(self, drawer, _, **kwargs): if not kwargs.get('shadow') and self.node.background: drawer.image(self.textbox, self.node.background) def setup(self): install_renderer('textbox', TextBox) blockdiag-1.3.2/src/blockdiag/parser.py0000644000076600000240000001661012236400230020605 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2008/2009 Andrey Vlasovskikh # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. r'''A DOT language parser using funcparserlib. The parser is based on [the DOT grammar][1]. It is pretty complete with a few not supported things: * Ports and compass points * XML identifiers At the moment, the parser builds only a parse tree, not an abstract syntax tree (AST) or an API for dealing with DOT. [1]: http://www.graphviz.org/doc/info/lang.html ''' import io from re import MULTILINE, DOTALL from collections import namedtuple from funcparserlib.lexer import make_tokenizer, Token, LexerError from funcparserlib.parser import (some, a, maybe, many, finished, skip, forward_decl) from blockdiag.utils.compat import u ENCODING = 'utf-8' Diagram = namedtuple('Diagram', 'type id stmts') Group = namedtuple('Group', 'id stmts') Node = namedtuple('Node', 'id attrs') Attr = namedtuple('Attr', 'name value') Edge = namedtuple('Edge', 'from_nodes edge_type to_nodes attrs') Extension = namedtuple('Extension', 'type name attrs') Statements = namedtuple('Statements', 'stmts') class ParseException(Exception): pass def tokenize(string): """str -> Sequence(Token)""" # flake8: NOQA specs = [ # NOQA ('Comment', (r'/\*(.|[\r\n])*?\*/', MULTILINE)), # NOQA ('Comment', (r'(//|#).*',)), # NOQA ('NL', (r'[\r\n]+',)), # NOQA ('Space', (r'[ \t\r\n]+',)), # NOQA ('Name', (u('[A-Za-z_0-9\u0080-\uffff]') + # NOQA u('[A-Za-z_\\-.0-9\u0080-\uffff]*'),)), # NOQA ('Op', (r'[{};,=\[\]]|(<->)|(<-)|(--)|(->)|(>-<)|(-<)|(>-)',)), # NOQA ('Number', (r'-?(\.[0-9]+)|([0-9]+(\.[0-9]*)?)',)), # NOQA ('String', (r'(?P"|\').*?(? object""" tokval = lambda x: x.value op = lambda s: a(Token('Op', s)) >> tokval op_ = lambda s: skip(op(s)) _id = some(lambda t: t.type in ['Name', 'Number', 'String']) >> tokval keyword = lambda s: a(Token('Name', s)) >> tokval def make_node_list(node_list, attrs): return Statements([Node(node, attrs) for node in node_list]) def make_edge(first, edge_type, second, followers, attrs): edges = [Edge(first, edge_type, second, attrs)] from_node = second for edge_type, to_node in followers: edges.append(Edge(from_node, edge_type, to_node, attrs)) from_node = to_node return Statements(edges) # # parts of syntax # node_list = ( _id + many(op_(',') + _id) >> create_mapper(oneplus_to_list) ) option_stmt = ( _id + maybe(op_('=') + _id) >> create_mapper(Attr) ) option_list = ( maybe(op_('[') + option_stmt + many(op_(',') + option_stmt) + op_(']')) >> create_mapper(oneplus_to_list, default_value=[]) ) # node (node list) statement:: # A; # B [attr = value, attr = value]; # C, D [attr = value, attr = value]; # node_stmt = ( node_list + option_list >> create_mapper(make_node_list) ) # edge statement:: # A -> B; # A <- B; # edge_relation = ( op('->') | op('--') | op('<-') | op('<->') | op('>-') | op('-<') | op('>-<') ) edge_stmt = ( node_list + edge_relation + node_list + many(edge_relation + node_list) + option_list >> create_mapper(make_edge) ) # attributes statement:: # default_shape = box; # default_fontsize = 16; # attribute_stmt = ( _id + op_('=') + _id >> create_mapper(Attr) ) # extension statement (class, plugin):: # class red [color = red]; # plugin attributes [name = Name]; # extension_stmt = ( (keyword('class') | keyword('plugin')) + _id + option_list >> create_mapper(Extension) ) # group statement:: # group { # A; # } # group_stmt = forward_decl() group_inline_stmt = ( edge_stmt | group_stmt | attribute_stmt | node_stmt ) group_inline_stmt_list = ( many(group_inline_stmt + skip(maybe(op(';')))) ) group_stmt.define( skip(keyword('group')) + maybe(_id) + op_('{') + group_inline_stmt_list + op_('}') >> create_mapper(Group) ) # # diagram statement:: # blockdiag { # A; # } # diagram_inline_stmt = ( extension_stmt | group_inline_stmt ) diagram_inline_stmt_list = ( many(diagram_inline_stmt + skip(maybe(op(';')))) ) diagram = ( maybe(keyword('diagram') | keyword('blockdiag')) + maybe(_id) + op_('{') + diagram_inline_stmt_list + op_('}') >> create_mapper(Diagram) ) dotfile = diagram + skip(finished) return dotfile.parse(seq) def sort_tree(tree): def weight(node): if isinstance(node, (Attr, Extension)): return 1 else: return 2 if hasattr(tree, 'stmts'): tree.stmts.sort(key=lambda x: weight(x)) for stmt in tree.stmts: sort_tree(stmt) return tree def parse_string(string): try: tree = parse(tokenize(string)) return sort_tree(tree) except LexerError as e: message = "Got unexpected token at line %d column %d" % e.place raise ParseException(message) except Exception as e: raise ParseException(str(e)) def parse_file(path): code = io.open(path, 'r', encoding='utf-8-sig').read() return parse_string(code) blockdiag-1.3.2/src/blockdiag/plugins/0000755000076600000240000000000012242633535020431 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/plugins/__init__.py0000644000076600000240000000276711657703171022561 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pkg_resources import iter_entry_points node_handlers = [] def load(plugins, diagram, **kwargs): for name in plugins: for ep in iter_entry_points('blockdiag_plugins', name): module = ep.load() if hasattr(module, 'setup'): module.setup(module, diagram, **kwargs) break else: msg = "WARNING: unknown plugin: %s\n" % name raise AttributeError(msg) def install_node_handler(handler): if handler not in node_handlers: node_handlers.append(handler) def fire_node_event(node, name, *args): method = "on_" + name for handler in node_handlers: getattr(handler, method)(node, *args) class NodeHandler(object): def __init__(self, diagram, **kwargs): self.diagram = diagram def on_created(self, node): pass def on_attr_changed(self, node, attr): pass blockdiag-1.3.2/src/blockdiag/plugins/attributes.py0000644000076600000240000000241211660721601023163 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag import plugins class NodeAttributes(plugins.NodeHandler): def __init__(self, diagram, **kwargs): super(NodeAttributes, self).__init__(diagram, **kwargs) node_klass = diagram._DiagramNode for name, label in kwargs.items(): if label is None: label = name if name not in node_klass.desctable: node_klass.desctable.insert(-1, name) node_klass.attrname[name] = label if not hasattr(node_klass, name): setattr(node_klass, name, None) def setup(self, diagram, **kwargs): plugins.install_node_handler(NodeAttributes(diagram, **kwargs)) blockdiag-1.3.2/src/blockdiag/plugins/autoclass.py0000644000076600000240000000215211657703171023004 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from blockdiag import plugins class AutoClass(plugins.NodeHandler): def on_created(self, node): if node.id is None: return for name, klass in self.diagram.classes.items(): pattern = "_%s$" % re.escape(name) if re.search(pattern, node.id): node.label = re.sub(pattern, '', node.id) node.set_attributes(klass.attrs) def setup(self, diagram, **kwargs): plugins.install_node_handler(AutoClass(diagram, **kwargs)) blockdiag-1.3.2/src/blockdiag/tests/0000755000076600000240000000000012242633535020112 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/tests/__init__.py0000644000076600000240000000000012217213441022201 0ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/tests/diagrams/0000755000076600000240000000000012242633535021701 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/tests/diagrams/auto_jumping_edge.diag0000644000076600000240000000004312227701012026176 0ustar tkomiyastaff00000000000000{ A -> B -> C, D; A, C -> E; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/background_url_image.diag0000644000076600000240000000023212227701016026660 0ustar tkomiyastaff00000000000000{ A [background = "http://python.org/images/python-logo.gif"]; B [background = "https://si2.twimg.com/profile_images/1287595787/tk0miya.jpg"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/beginpoint_color.diag0000644000076600000240000000005412227701020026046 0ustar tkomiyastaff00000000000000{ A [shape = beginpoint, color = pink]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/branched.diag0000644000076600000240000000005612227702770024277 0ustar tkomiyastaff00000000000000diagram { A -> B -> C; A -> D -> E; Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/circular_ref.diag0000644000076600000240000000006112227702770025165 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> B B -> D Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/circular_ref2.diag0000644000076600000240000000005212217213441025236 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D, E -> F -> C; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/circular_ref_and_parent_node.diag0000644000076600000240000000005212227701036030357 0ustar tkomiyastaff00000000000000{ A -> B, C; D -> B -> C -> D; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/circular_ref_to_root.diag0000644000076600000240000000006112227702770026732 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> A B -> D Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/circular_skipped_edge.diag0000644000076600000240000000006112227702770027034 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> A A -> C Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/define_class.diag0000644000076600000240000000017212227701045025141 0ustar tkomiyastaff00000000000000{ class emphasis [style = dashed, color = red]; A -> B -> C; A -> B [class = emphasis]; A [class = emphasis]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/diagram_attributes.diag0000644000076600000240000000050712227701050026372 0ustar tkomiyastaff00000000000000blockdiag { node_width = 160; node_height = 160; span_width = 32; span_height = 32; default_fontsize = 16; default_shape = diamond default_node_color = red default_group_color = blue default_linecolor = gray default_textcolor = green default_label_orientation = vertical A -> B; group { B; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/diagram_attributes_order.diag0000644000076600000240000000010512227701052027561 0ustar tkomiyastaff00000000000000{ A; default_node_color = red; default_linecolor = red; B; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/diagram_orientation.diag0000644000076600000240000000010212227701053026531 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B -> C; B -> D; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_attribute.diag0000644000076600000240000000011712227702770025516 0ustar tkomiyastaff00000000000000diagram { A -> B -> C [color = red] D -> E [dir = none] F -> G [thick] } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_datamodels.diag0000644000076600000240000000003112227701060025612 0ustar tkomiyastaff00000000000000{ A >- B >-< C -< D; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_label.diag0000644000076600000240000000004512227701062024563 0ustar tkomiyastaff00000000000000{ A -> B [label = "test label"]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_layout_landscape.diag0000644000076600000240000000010412227701064027031 0ustar tkomiyastaff00000000000000{ edge_layout = flowchart; A [shape = diamond]; A -> B, C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_layout_portrait.diag0000644000076600000240000000013512227701066026751 0ustar tkomiyastaff00000000000000{ orientation = portrait edge_layout = flowchart; A [shape = diamond]; A -> B, C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_shape.diag0000644000076600000240000000003612227701070024603 0ustar tkomiyastaff00000000000000{ A -- B -> C <- D <-> E; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/edge_styles.diag0000644000076600000240000000035112227702770025036 0ustar tkomiyastaff00000000000000diagram { A -> B [style = "none"]; B -> C [style = "solid"]; C -> D [style = "dashed"]; D -> E [style = "dotted"]; E -> F [hstyle = "generalization"]; F -> H [hstyle = "composition"]; H -> I [hstyle = "aggregation"]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/empty_group.diag0000644000076600000240000000003612227702770025101 0ustar tkomiyastaff00000000000000diagram { group { } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/empty_group_declaration.diag0000644000076600000240000000010612227701075027441 0ustar tkomiyastaff00000000000000{ group foo { color = red; } group foo { A; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/empty_nested_group.diag0000644000076600000240000000006112227702770026441 0ustar tkomiyastaff00000000000000diagram { group { group { } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/endpoint_color.diag0000644000076600000240000000005212227701104025531 0ustar tkomiyastaff00000000000000{ A [shape = endpoint, color = pink]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/0000755000076600000240000000000012242633535023215 5ustar tkomiyastaff00000000000000blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag0000644000076600000240000000007012227702770030464 0ustar tkomiyastaff00000000000000diagram { group { A } group { A } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/group_follows_node.diag0000644000076600000240000000005112227702770027746 0ustar tkomiyastaff00000000000000diagram { A -> group { B } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/lexer_error.diag0000644000076600000240000000001412227701474026367 0ustar tkomiyastaff00000000000000{ A - B } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/node_follows_group.diag0000644000076600000240000000005112227702770027746 0ustar tkomiyastaff00000000000000diagram { A -> group { B } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_diagram_default_shape.diag0000644000076600000240000000006712227701500032104 0ustar tkomiyastaff00000000000000{ default_shape = "test_unknown_shape"; A; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_diagram_edge_layout.diag0000644000076600000240000000003512227701503031577 0ustar tkomiyastaff00000000000000{ edge_layout = unknown; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_diagram_orientation.diag0000644000076600000240000000003512227701505031633 0ustar tkomiyastaff00000000000000{ orientation = unknown; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_edge_class.diag0000644000076600000240000000004012227701507027703 0ustar tkomiyastaff00000000000000{ A -> B [class = unknown]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_edge_dir.diag0000644000076600000240000000003612227701511027354 0ustar tkomiyastaff00000000000000{ A -> B [dir = unknown]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_edge_hstyle.diag0000644000076600000240000000004112217213441030101 0ustar tkomiyastaff00000000000000{ A -> B [hstyle = unknown]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_edge_style.diag0000644000076600000240000000004012217213441027730 0ustar tkomiyastaff00000000000000{ A -> B [style = unknown]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_group_class.diag0000644000076600000240000000005612217213441030134 0ustar tkomiyastaff00000000000000{ group { class = unknown; A; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_group_orientation.diag0000644000076600000240000000006412217213441031361 0ustar tkomiyastaff00000000000000{ group { orientation = unknown; A; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_group_shape.diag0000644000076600000240000000010112217213441030116 0ustar tkomiyastaff00000000000000{ group { shape = "test_unknown_shape"; A; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_node_attribute.diag0000644000076600000240000000004412217213441030620 0ustar tkomiyastaff00000000000000{ A [unknown_attribute = True]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_node_class.diag0000644000076600000240000000003312217213441027720 0ustar tkomiyastaff00000000000000{ A [class = unknown]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_node_shape.diag0000644000076600000240000000005612217213441027720 0ustar tkomiyastaff00000000000000{ A [shape = "test_unknown_shape"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_node_style.diag0000644000076600000240000000003312217213441027753 0ustar tkomiyastaff00000000000000{ A [style = unknown]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/errors/unknown_plugin.diag0000644000076600000240000000004312217213441027105 0ustar tkomiyastaff00000000000000{ plugin unknown_plugin; A; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/flowable_node.diag0000644000076600000240000000004212227702770025324 0ustar tkomiyastaff00000000000000diagram { B -> C A -> B Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/folded_edge.diag0000644000076600000240000000011512227702770024746 0ustar tkomiyastaff00000000000000diagram { A -> B -> C [nofolded] B -> D -> E[folded] D -> F Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_and_skipped_edge.diag0000644000076600000240000000010512227701112027172 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D; A -> E -> D; Z; group { B; C; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_attribute.diag0000644000076600000240000000014112227701113025731 0ustar tkomiyastaff00000000000000{ group { color = "red"; label = "group label"; shape = "line"; A; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_children_height.diag0000644000076600000240000000012112227701115027046 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } B -> E; D -> F; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_children_order.diag0000644000076600000240000000013212227701120026707 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } D -> G; B -> E; C -> F; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_children_order2.diag0000644000076600000240000000014512227701122026777 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } A -> F; D -> G; B -> E; C -> F; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_children_order3.diag0000644000076600000240000000017412227701124027004 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } group { Q; } D -> G; B -> E; C -> F; Q -> F; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_children_order4.diag0000644000076600000240000000014712227701126027007 0ustar tkomiyastaff00000000000000diagram { A -> B, C, D -> E; Z; group A { A } group B { B } group C { C } group D { D } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_declare_as_node_attribute.diag0000644000076600000240000000013512227701130031102 0ustar tkomiyastaff00000000000000{ C [group = foo]; D [group = foo]; A -> B -> C; D; group foo { E; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_height.diag0000644000076600000240000000011412227701131025176 0ustar tkomiyastaff00000000000000{ group { B; C; D; } A -> B -> C; B -> D; A -> E; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag0000644000076600000240000000006412227702770032544 0ustar tkomiyastaff00000000000000diagram { A -> B group B { C -> D } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_label.diag0000644000076600000240000000006412227701134025014 0ustar tkomiyastaff00000000000000{ group { label = "test label"; A; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_order.diag0000644000076600000240000000007712227701136025056 0ustar tkomiyastaff00000000000000diagram{ A; B; C; group { B; } A -> B; A -> C; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_order2.diag0000644000076600000240000000014012227701140025122 0ustar tkomiyastaff00000000000000{ A -> B; A -> C -> D; A -> E -> F; Z; group { C; D } group { E; F } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_order3.diag0000644000076600000240000000017012227701141025127 0ustar tkomiyastaff00000000000000diagram admin { A; group { B; C; D; } E; Z; A -> B; A -> E; B -> C -> B; B -> D -> B; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_orientation.diag0000644000076600000240000000012512227701145026270 0ustar tkomiyastaff00000000000000{ A -> B; Z; group { orientation = portrait B -> C; B -> D; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_sibling.diag0000644000076600000240000000010412227701147025363 0ustar tkomiyastaff00000000000000{ A -> B, C; B -> D, E; group { C -> F; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/group_works_node_decorator.diag0000644000076600000240000000012212227702770030153 0ustar tkomiyastaff00000000000000diagram { A -> B -> C A -> B -> D A -> E group { A; B; D; E } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/labeled_circular_ref.diag0000644000076600000240000000013012227701153026624 0ustar tkomiyastaff00000000000000{ A [label = "foo"]; B [label = "bar"]; C [label = "baz"]; A -> C -> B -> C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/large_group_and_node.diag0000644000076600000240000000012312227702770026661 0ustar tkomiyastaff00000000000000diagram { group { A -> B A -> C A -> D A -> E } B -> F Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/large_group_and_node2.diag0000644000076600000240000000007412227702770026750 0ustar tkomiyastaff00000000000000diagram { group { A -> B -> C } C -> D -> E Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag0000644000076600000240000000013412227702770027737 0ustar tkomiyastaff00000000000000diagram { group { A -> B A -> C A -> D A -> E } B -> F C -> G Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/merge_groups.diag0000644000076600000240000000010512227701161025213 0ustar tkomiyastaff00000000000000{ group hoge{ A -> B; } group hoge{ C -> D; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/multiple_groups.diag0000644000076600000240000000021312227702770025756 0ustar tkomiyastaff00000000000000diagram { group { A; B; C; D } group { E; F; G } group { H; I } group { J } A -> E -> H -> J Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/multiple_nested_groups.diag0000644000076600000240000000015312227702770027323 0ustar tkomiyastaff00000000000000diagram { group { A -> B; A -> C; group { B } group { C } } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/multiple_node_relation.diag0000644000076600000240000000003312227701170027252 0ustar tkomiyastaff00000000000000{ A -> B, C -> D; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/multiple_nodes_definition.diag0000644000076600000240000000003712227701171027755 0ustar tkomiyastaff00000000000000{ A, B [color = red]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/multiple_parent_node.diag0000644000076600000240000000004712227701203026730 0ustar tkomiyastaff00000000000000{ A -> B; C -> D; E -> B; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/nested_group_orientation.diag0000644000076600000240000000016612227701206027635 0ustar tkomiyastaff00000000000000{ group { group { orientation = portrait A -> B; group { C; } } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/nested_group_orientation2.diag0000644000076600000240000000020412227701207027711 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B; group { orientation = portrait C -> D; group { E -> F; } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/nested_groups.diag0000644000076600000240000000010112227702770025401 0ustar tkomiyastaff00000000000000diagram { group { A; group { B; } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/nested_groups_and_edges.diag0000644000076600000240000000012112227702770027374 0ustar tkomiyastaff00000000000000diagram { A -> B -> C; group { B; group { C; } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag0000644000076600000240000000010712227702770031520 0ustar tkomiyastaff00000000000000diagram { A; B; group { A group { B } } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/nested_skipped_circular.diag0000644000076600000240000000016012227702770027412 0ustar tkomiyastaff00000000000000diagram { A -> B -> F -> G B -> C -> E -> F C -> D -> E F -> A Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_attribute.diag0000644000076600000240000000055212227702770025542 0ustar tkomiyastaff00000000000000diagram { A [label="B", color="red"]; B [label="double quoted"]; C [label='single quoted', color = 'red']; D [label="'\"double\" quoted'", color = 'red']; E [label='"\'single\' quoted"', color = 'red', numbered = "1"]; F [textcolor = red]; G [stacked]; H [fontsize = 16]; I [linecolor = red]; J [label="Hello", label_orientation=vertical]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_attribute_and_group.diag0000644000076600000240000000026012227701222027563 0ustar tkomiyastaff00000000000000diagram { A [label = "foo", color = "red"]; B [label = "bar", color = "#888888"]; C [label = "baz", color = "blue"]; A -> B -> C; group { A; B; } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_has_multilined_label.diag0000644000076600000240000000004212227701223027661 0ustar tkomiyastaff00000000000000{ A [label="foo bar"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_height.diag0000644000076600000240000000006112227701224024773 0ustar tkomiyastaff00000000000000{ A -> B -> C; B -> D; A -> E; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_icon.diag0000644000076600000240000000031012227701226024452 0ustar tkomiyastaff00000000000000{ A -> B; A [label = "aaaaaaaaaaaaaaaaa", icon = "/usr/share/pixmaps/debian-logo.png"]; B [label = "aaaaaaaaaaaaaaaaa", icon = "https://si2.twimg.com/profile_images/1287595787/tk0miya.jpg"]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_id_includes_dot.diag0000644000076600000240000000002712227701227026660 0ustar tkomiyastaff00000000000000{ A.B -> C.D; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag0000644000076600000240000000006212227702770031345 0ustar tkomiyastaff00000000000000diagram { group { A -> B } B -> C Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_link.diag0000644000076600000240000000022312227701233024460 0ustar tkomiyastaff00000000000000{ A [href = "http://www.yahoo.co.jp/"]; B [href = "http://www.yahoo.co.jp/"]; group { href = "http://www.disney.co.jp"; C; D; } } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_rotated_labels.diag0000644000076600000240000000014512227701234026513 0ustar tkomiyastaff00000000000000{ A [rotate = 0]; B [rotate = 90]; C [rotate = 180]; D [rotate = 270]; E [rotate = 360]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_shape.diag0000644000076600000240000000114712227701236024634 0ustar tkomiyastaff00000000000000{ A [shape = "box"]; B [shape = "roundedbox"]; C [shape = "diamond"]; D [shape = "ellipse"]; E [shape = "note"]; F [shape = "cloud"]; G [shape = "mail"]; H [shape = "beginpoint"]; I [shape = "endpoint"]; J [shape = "minidiamond"]; K [shape = "flowchart.condition"]; L [shape = "flowchart.database"]; M [shape = "flowchart.input"]; N [shape = "flowchart.loopin"]; O [shape = "flowchart.loopout"]; P [shape = "actor"]; Q [shape = "flowchart.terminator"]; R [shape = "textbox"]; S [shape = "dots"]; T [shape = "none"]; U [shape = "square"]; V [shape = "circle"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_shape_background.diag0000644000076600000240000000344112227701237027033 0ustar tkomiyastaff00000000000000{ A [shape = "box", background = "src/blockdiag/tests/diagrams/white.gif"]; B [shape = "roundedbox", background = "src/blockdiag/tests/diagrams/white.gif"]; C [shape = "diamond", background = "src/blockdiag/tests/diagrams/white.gif"]; D [shape = "ellipse", background = "src/blockdiag/tests/diagrams/white.gif"]; E [shape = "note", background = "src/blockdiag/tests/diagrams/white.gif"]; F [shape = "cloud", background = "src/blockdiag/tests/diagrams/white.gif"]; G [shape = "mail", background = "src/blockdiag/tests/diagrams/white.gif"]; H [shape = "beginpoint", background = "src/blockdiag/tests/diagrams/white.gif"]; I [shape = "endpoint", background = "src/blockdiag/tests/diagrams/white.gif"]; J [shape = "minidiamond", background = "src/blockdiag/tests/diagrams/white.gif"]; K [shape = "flowchart.condition", background = "src/blockdiag/tests/diagrams/white.gif"]; L [shape = "flowchart.database", background = "src/blockdiag/tests/diagrams/white.gif"]; M [shape = "flowchart.input", background = "src/blockdiag/tests/diagrams/white.gif"]; N [shape = "flowchart.loopin", background = "src/blockdiag/tests/diagrams/white.gif"]; O [shape = "flowchart.loopout", background = "src/blockdiag/tests/diagrams/white.gif"]; P [shape = "actor", background = "src/blockdiag/tests/diagrams/white.gif"]; Q [shape = "flowchart.terminator", background = "src/blockdiag/tests/diagrams/white.gif"]; R [shape = "textbox", background = "src/blockdiag/tests/diagrams/white.gif"]; S [shape = "dots", background = "src/blockdiag/tests/diagrams/white.gif"]; T [shape = "none", background = "src/blockdiag/tests/diagrams/white.gif"]; U [shape = "square", background = "src/blockdiag/tests/diagrams/white.gif"]; V [shape = "circle", background = "src/blockdiag/tests/diagrams/white.gif"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_shape_namespace.diag0000644000076600000240000000015412227701241026641 0ustar tkomiyastaff00000000000000{ shape_namespace = "flowchart"; A [shape = "flowchart.condition"]; B [shape = "condition"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag0000644000076600000240000000120112227701254030242 0ustar tkomiyastaff00000000000000{ shadow_style = "none"; A [shape = "box"]; B [shape = "roundedbox"]; C [shape = "diamond"]; D [shape = "ellipse"]; E [shape = "note"]; F [shape = "cloud"]; G [shape = "mail"]; H [shape = "beginpoint"]; I [shape = "endpoint"]; J [shape = "minidiamond"]; K [shape = "flowchart.condition"]; L [shape = "flowchart.database"]; M [shape = "flowchart.input"]; N [shape = "flowchart.loopin"]; O [shape = "flowchart.loopout"]; P [shape = "actor"]; Q [shape = "flowchart.terminator"]; R [shape = "textbox"]; S [shape = "dots"]; T [shape = "none"]; U [shape = "square"]; V [shape = "circle"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag0000644000076600000240000000120212217213441030411 0ustar tkomiyastaff00000000000000{ shadow_style = "solid"; A [shape = "box"]; B [shape = "roundedbox"]; C [shape = "diamond"]; D [shape = "ellipse"]; E [shape = "note"]; F [shape = "cloud"]; G [shape = "mail"]; H [shape = "beginpoint"]; I [shape = "endpoint"]; J [shape = "minidiamond"]; K [shape = "flowchart.condition"]; L [shape = "flowchart.database"]; M [shape = "flowchart.input"]; N [shape = "flowchart.loopin"]; O [shape = "flowchart.loopout"]; P [shape = "actor"]; Q [shape = "flowchart.terminator"]; R [shape = "textbox"]; S [shape = "dots"]; T [shape = "none"]; U [shape = "square"]; V [shape = "circle"]; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_style_dasharray.diag0000644000076600000240000000024712227701260026727 0ustar tkomiyastaff00000000000000{ A [style = "2,2,4,2"]; B [shape = diamond, style = "2,2,4,2"]; C [shape = ellipse, style = "2,2,4,2"]; D [shape = flowchart.database, style = "2,2,4,2"]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_styles.diag0000644000076600000240000000023312227702770025056 0ustar tkomiyastaff00000000000000diagram { A [shape = "roundedbox", style = "dotted"]; B [shape = "ellipse", style = "dashed"]; C [shape = "flowchart.database", style = "dashed"]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/node_width_and_height.diag0000644000076600000240000000007012227701263027017 0ustar tkomiyastaff00000000000000{ A -> B, C; A [height = 80]; C [width = 256]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/non_rhombus_relation_height.diag0000644000076600000240000000011612227701264030301 0ustar tkomiyastaff00000000000000{ A -> B -> C; D; E -> F, G, J; G -> H, I; J -> K; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag0000644000076600000240000000006212227702770031345 0ustar tkomiyastaff00000000000000diagram { group { B -> C } A -> B Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/plugin_attributes.diag0000644000076600000240000000021012227701267026265 0ustar tkomiyastaff00000000000000{ plugin attributes [test_attr1, test_attr2 = name, test_attr3 = name]; A [test_attr1 = 1, test_attr2 = 2, test_attr3 = 3]; B; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/plugin_autoclass.diag0000644000076600000240000000017012227701271026075 0ustar tkomiyastaff00000000000000{ plugin autoclass; class emphasis [style = dashed, color = red]; A_emphasis -> B_emphasis; A_emphasis -> C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/portrait_dots.diag0000644000076600000240000000006312227701273025421 0ustar tkomiyastaff00000000000000{ orientation = portrait; A [shape = dots]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/quoted_node_id.diag0000644000076600000240000000005212227701274025505 0ustar tkomiyastaff00000000000000{ A -> "A" -> 'A' -> "'A'" -> B; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/reverse_multiple_groups.diag0000644000076600000240000000021312227702770027511 0ustar tkomiyastaff00000000000000diagram { group { A; B; C; D } group { E; F; G } group { H; I } group { J } J -> H -> E -> A Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/rhombus_relation_height.diag0000644000076600000240000000004312227701277027432 0ustar tkomiyastaff00000000000000{ A -> B, C -> D -> E, F; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/self_ref.diag0000644000076600000240000000003612227702770024314 0ustar tkomiyastaff00000000000000diagram { A -> B -> B Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/separate1.diag0000644000076600000240000000025212227701302024402 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D; group outer { label = "outer" B; D; group inner { label = "inner" color = skyblue; C -> E -> F; } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/separate2.diag0000644000076600000240000000033512227701303024406 0ustar tkomiyastaff00000000000000diagram { A -> B; A -> C; E -> F; C -> G; D -> H; group outer { label = "group 1"; B -> E -> C; group inner { label = "sub group 1"; color = skyblue C -> D; } } Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/simple_group.diag0000644000076600000240000000006412227702770025235 0ustar tkomiyastaff00000000000000diagram { group { A -> B A -> C } Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/single_edge.diag0000644000076600000240000000002612227702770024773 0ustar tkomiyastaff00000000000000diagram { A -> B; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/single_node.diag0000644000076600000240000000002112227702770025007 0ustar tkomiyastaff00000000000000diagram { A; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_circular.diag0000644000076600000240000000006512227702770026054 0ustar tkomiyastaff00000000000000diagram { A -> C A -> B -> C C -> A Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge.diag0000644000076600000240000000005412227702770025152 0ustar tkomiyastaff00000000000000diagram { A -> B -> C A -> C Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_down.diag0000644000076600000240000000004212227701315026170 0ustar tkomiyastaff00000000000000{ A; B; C; A -> C [folded]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown.diag0000644000076600000240000000010212227701316031275 0ustar tkomiyastaff00000000000000{ edge_layout = flowchart; A -> B, C; C -> D; A -> D; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown2.diag0000644000076600000240000000010312227701320031353 0ustar tkomiyastaff00000000000000{ edge_layout = flowchart; A; B -> C; A -> C [folded]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_leftdown.diag0000644000076600000240000000007112227701321027042 0ustar tkomiyastaff00000000000000{ A -> B -> C, D; E; F -> G; C -> G [folded]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_portrait_down.diag0000644000076600000240000000007012227701323030114 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B -> C; A -> C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown.diag0000644000076600000240000000013312227701331033222 0ustar tkomiyastaff00000000000000{ orientation = portrait; edge_layout = flowchart; A -> B, C; C -> D; A -> D; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown2.diag0000644000076600000240000000013512227701332033307 0ustar tkomiyastaff00000000000000{ orientation = portrait; edge_layout = flowchart; A; B -> C; A -> C [folded]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_portrait_leftdown.diag0000644000076600000240000000007312227701333030773 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B -> C; D -> C, E; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_portrait_right.diag0000644000076600000240000000007512217213441030265 0ustar tkomiyastaff00000000000000{ orientation = portrait; A; B; C; A -> C [folded]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_portrait_rightdown.diag0000644000076600000240000000011212227701340031146 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B, C; B -> D; C -> E; A -> E; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_right.diag0000644000076600000240000000003512227701342026340 0ustar tkomiyastaff00000000000000{ A -> B -> C; A -> C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_rightdown.diag0000644000076600000240000000004412227701343027231 0ustar tkomiyastaff00000000000000{ A -> B -> C, D; A -> C, D; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_rightup.diag0000644000076600000240000000004012217213441026676 0ustar tkomiyastaff00000000000000{ A -> B -> C; D -> C, E; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_edge_up.diag0000644000076600000240000000004312227701357025654 0ustar tkomiyastaff00000000000000{ A; B; C; C -> A [folded]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/skipped_twin_circular.diag0000644000076600000240000000011312217213441027076 0ustar tkomiyastaff00000000000000{ A -> B -> E -> B; B -> C -> E; B -> D -> E; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/slided_children.diag0000644000076600000240000000013012217213441025625 0ustar tkomiyastaff00000000000000{ C; F; A -> B -> C; B -> G -> H; B -> F -> H; A -> D -> E -> F -> H; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/stacked_node_and_edges.diag0000644000076600000240000000006112217213441027130 0ustar tkomiyastaff00000000000000{ A -> B; A -> C [folded]; A [stacked]; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/triple_branched.diag0000644000076600000240000000005312227702770025653 0ustar tkomiyastaff00000000000000diagram { A -> D B -> D C -> D Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/twin_circular_ref.diag0000644000076600000240000000006112217213441026215 0ustar tkomiyastaff00000000000000{ A -> B -> C -> B; A -> D -> C -> D; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag0000644000076600000240000000005412227702770027775 0ustar tkomiyastaff00000000000000diagram { A -> B -> A A -> C -> A Z } blockdiag-1.3.2/src/blockdiag/tests/diagrams/twin_forked.diag0000644000076600000240000000006612217213441025034 0ustar tkomiyastaff00000000000000{ A -> B, C; B -> D -> E, F; C, F -> G; Z; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/twin_multiple_parent_node.diag0000644000076600000240000000006112217213441027766 0ustar tkomiyastaff00000000000000{ A -> B; Z; E -> D; C -> D; C -> B; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/two_edges.diag0000644000076600000240000000003312227702770024504 0ustar tkomiyastaff00000000000000diagram { A -> B -> C; } blockdiag-1.3.2/src/blockdiag/tests/diagrams/white.gif0000644000076600000240000000005312217213441023476 0ustar tkomiyastaff00000000000000GIF89a!,D;blockdiag-1.3.2/src/blockdiag/tests/test_boot_params.py0000644000076600000240000001426412222256754024042 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest import os import io import tempfile from blockdiag.tests.utils import with_pdf import blockdiag from blockdiag.command import BlockdiagOptions from blockdiag.utils.bootstrap import detectfont from blockdiag.utils.compat import u class TestBootParams(unittest.TestCase): def setUp(self): self.parser = BlockdiagOptions(blockdiag) def test_type_option_svg(self): options = self.parser.parse(['-Tsvg', 'input.diag']) self.assertEqual(options.output, 'input.svg') options = self.parser.parse(['-TSVG', 'input.diag']) self.assertEqual(options.output, 'input.svg') options = self.parser.parse(['-TSvg', 'input.diag']) self.assertEqual(options.output, 'input.svg') options = self.parser.parse(['-TSvg', 'input.test.diag']) self.assertEqual(options.output, 'input.test.svg') def test_type_option_png(self): options = self.parser.parse(['-Tpng', 'input.diag']) self.assertEqual(options.output, 'input.png') @with_pdf def test_type_option_pdf(self): options = self.parser.parse(['-Tpdf', 'input.diag']) self.assertEqual(options.output, 'input.pdf') def test_invalid_type_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tsvgz', 'input.diag']) def test_separate_option_svg(self): self.parser.parse(['-Tsvg', '--separate', 'input.diag']) def test_separate_option_png(self): self.parser.parse(['-Tpng', '--separate', 'input.diag']) @with_pdf def test_separate_option_pdf(self): self.parser.parse(['-Tpdf', '--separate', 'input.diag']) def test_svg_nodoctype_option(self): self.parser.parse(['-Tsvg', '--nodoctype', 'input.diag']) def test_png_nodoctype_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tpng', '--nodoctype', 'input.diag']) def test_pdf_nodoctype_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tpdf', '--nodoctype', 'input.diag']) def test_svg_notransparency_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tsvg', '--no-transparency', 'input.diag']) def test_png_notransparency_option(self): self.parser.parse(['-Tpng', '--no-transparency', 'input.diag']) def test_pdf_notransparency_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tpdf', '--no-transparency', 'input.diag']) def test_config_option(self): try: tmp = tempfile.mkstemp() self.parser.parse(['-c', tmp[1], 'input.diag']) finally: os.close(tmp[0]) os.unlink(tmp[1]) def test_config_option_with_bom(self): try: tmp = tempfile.mkstemp() fp = io.open(tmp[0], 'wt', encoding='utf-8-sig') fp.write(u("[blockdiag]\n")) fp.close() self.parser.parse(['-c', tmp[1], 'input.diag']) finally: os.unlink(tmp[1]) def test_invalid_config_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-c', '/unknown_config_file', 'input.diag']) def test_invalid_dir_config_option(self): try: tmp = tempfile.mkdtemp() with self.assertRaises(RuntimeError): self.parser.parse(['-c', tmp, 'input.diag']) finally: os.rmdir(tmp) def test_config_option_fontpath(self): try: tmp = tempfile.mkstemp() config = u("[blockdiag]\nfontpath = /path/to/font\n") io.open(tmp[0], 'wt').write(config) options = self.parser.parse(['-c', tmp[1], 'input.diag']) self.assertEqual(options.font, ['/path/to/font']) finally: os.unlink(tmp[1]) def test_exist_font_config_option(self): try: tmp = tempfile.mkstemp() options = self.parser.parse(['-f', tmp[1], 'input.diag']) self.assertEqual(options.font, [tmp[1]]) fontpath = detectfont(options) self.assertEqual(fontpath, tmp[1]) finally: os.unlink(tmp[1]) def test_not_exist_font_config_option(self): with self.assertRaises(RuntimeError): args = ['-f', '/font_is_not_exist', 'input.diag'] options = self.parser.parse(args) detectfont(options) def test_not_exist_font_config_option2(self): with self.assertRaises(RuntimeError): args = ['-f', '/font_is_not_exist', '-f', '/font_is_not_exist2', 'input.diag'] options = self.parser.parse(args) detectfont(options) def test_no_size_option(self): options = self.parser.parse(['input.diag']) self.assertEqual(None, options.size) def test_size_option(self): options = self.parser.parse(['--size', '480x360', 'input.diag']) self.assertEqual([480, 360], options.size) def test_invalid_size_option1(self): with self.assertRaises(RuntimeError): self.parser.parse(['--size', '480-360', 'input.diag']) def test_invalid_size_option2(self): with self.assertRaises(RuntimeError): self.parser.parse(['--size', '480', 'input.diag']) def test_invalid_size_option3(self): with self.assertRaises(RuntimeError): self.parser.parse(['--size', 'foobar', 'input.diag']) def test_auto_font_detection(self): options = self.parser.parse(['input.diag']) fontpath = detectfont(options) self.assertTrue(fontpath) def test_not_exist_fontmap_config(self): with self.assertRaises(RuntimeError): args = ['--fontmap', '/fontmap_is_not_exist', 'input.diag'] options = self.parser.parse(args) fontpath = detectfont(options) self.assertTrue(fontpath) def test_unknown_image_driver(self): from blockdiag.drawer import DiagramDraw from blockdiag.elements import Diagram with self.assertRaises(RuntimeError): DiagramDraw('unknown', Diagram()) blockdiag-1.3.2/src/blockdiag/tests/test_builder.py0000644000076600000240000001662512220301432023143 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase class TestBuilder(BuilderTestCase): def test_diagram_attributes(self): diagram = self.build('diagram_attributes.diag') self.assertEqual(160, diagram.node_width) self.assertEqual(160, diagram.node_height) self.assertEqual(32, diagram.span_width) self.assertEqual(32, diagram.span_height) self.assertEqual((128, 128, 128), diagram.linecolor) # gray self.assertEqual('diamond', diagram.nodes[0].shape) self.assertEqual((255, 0, 0), diagram.nodes[0].color) # red self.assertEqual((0, 128, 0), diagram.nodes[0].textcolor) # green self.assertEqual(16, diagram.nodes[0].fontsize) self.assertEqual((0, 0, 255), diagram.nodes[1].color) # blue self.assertEqual((0, 128, 0), diagram.nodes[1].textcolor) # green self.assertEqual(16, diagram.nodes[1].fontsize) self.assertEqual((128, 128, 128), diagram.edges[0].color) # gray self.assertEqual((0, 128, 0), diagram.edges[0].textcolor) # green self.assertEqual(16, diagram.edges[0].fontsize) def test_diagram_attributes_order_diagram(self): diagram = self.build('diagram_attributes_order.diag') self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0)}) self.assertNodeLineColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0)}) def test_circular_ref_to_root_diagram(self): diagram = self.build('circular_ref_to_root.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'Z': (0, 2)}) def test_circular_ref_diagram(self): diagram = self.build('circular_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'Z': (0, 2)}) def test_circular_ref2_diagram(self): diagram = self.build('circular_ref2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0), 'E': (3, 1), 'F': (4, 0), 'Z': (0, 2)}) def test_circular_ref_and_parent_node_diagram(self): diagram = self.build('circular_ref_and_parent_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 1), 'Z': (0, 2)}) def test_labeled_circular_ref_diagram(self): diagram = self.build('labeled_circular_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (2, 0), 'C': (1, 0), 'Z': (0, 1)}) def test_twin_forked_diagram(self): diagram = self.build('twin_forked.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 2), 'D': (2, 0), 'E': (3, 0), 'F': (3, 1), 'G': (4, 1), 'Z': (0, 3)}) def test_skipped_edge_diagram(self): diagram = self.build('skipped_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_circular_skipped_edge_diagram(self): diagram = self.build('circular_skipped_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_triple_branched_diagram(self): diagram = self.build('triple_branched.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 0), 'Z': (0, 3)}) def test_twin_circular_ref_to_root_diagram(self): diagram = self.build('twin_circular_ref_to_root.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_twin_circular_ref_diagram(self): diagram = self.build('twin_circular_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 1), 'Z': (0, 2)}) def test_skipped_circular_diagram(self): diagram = self.build('skipped_circular.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 1), 'C': (2, 0), 'Z': (0, 2)}) def test_skipped_twin_circular_diagram(self): diagram = self.build('skipped_twin_circular.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 1), 'D': (2, 2), 'E': (3, 0), 'Z': (0, 3)}) def test_nested_skipped_circular_diagram(self): diagram = self.build('nested_skipped_circular.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 1), 'D': (3, 2), 'E': (4, 1), 'F': (5, 0), 'G': (6, 0), 'Z': (0, 3)}) def test_self_ref_diagram(self): diagram = self.build('self_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'Z': (0, 1)}) def test_diagram_orientation_diagram(self): diagram = self.build('diagram_orientation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 2), 'Z': (2, 0)}) def test_nested_group_orientation2_diagram(self): diagram = self.build('nested_group_orientation2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 2), 'E': (2, 2), 'F': (2, 3), 'Z': (3, 0)}) def test_slided_children_diagram(self): diagram = self.build('slided_children.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 3), 'E': (2, 3), 'F': (3, 2), 'G': (2, 1), 'H': (4, 1)}) def test_non_rhombus_relation_height_diagram(self): diagram = self.build('non_rhombus_relation_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (0, 1), 'E': (0, 2), 'F': (1, 2), 'G': (1, 3), 'H': (2, 3), 'I': (2, 4), 'J': (1, 5), 'K': (2, 5), 'Z': (0, 6)}) def test_define_class_diagram(self): diagram = self.build('define_class.diag') self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 255, 255)}) self.assertNodeStyle(diagram, {'A': 'dashed', 'B': None, 'C': None}) self.assertEdgeColor(diagram, {('A', 'B'): (255, 0, 0), ('B', 'C'): (0, 0, 0)}) self.assertEdgeStyle(diagram, {('A', 'B'): 'dashed', ('B', 'C'): None}) blockdiag-1.3.2/src/blockdiag/tests/test_builder_edge.py0000644000076600000240000001521512235371405024136 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase, capture_stderr class TestBuilderEdge(BuilderTestCase): def test_diagram_attributes(self): diagram = self.build('diagram_attributes.diag') self.assertEqual(2, len(diagram.nodes)) self.assertEqual(1, len(diagram.edges)) def test_single_edge_diagram(self): diagram = self.build('single_edge.diag') self.assertEqual(2, len(diagram.nodes)) self.assertEqual(1, len(diagram.edges)) self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0)}) self.assertNodeLabel(diagram, {'A': 'A', 'B': 'B'}) def test_two_edges_diagram(self): diagram = self.build('two_edges.diag') self.assertEqual(3, len(diagram.nodes)) self.assertEqual(2, len(diagram.edges)) self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0)}) def test_edge_shape(self): diagram = self.build('edge_shape.diag') self.assertEdgeDir(diagram, {('A', 'B'): 'none', ('B', 'C'): 'forward', ('C', 'D'): 'back', ('D', 'E'): 'both'}) def test_edge_attribute(self): diagram = self.build('edge_attribute.diag') self.assertEdgeDir(diagram, {('A', 'B'): 'forward', ('B', 'C'): 'forward', ('C', 'D'): 'forward', ('D', 'E'): 'none', ('E', 'F'): 'both', ('F', 'G'): 'forward'}) self.assertEdgeColor(diagram, {('A', 'B'): (255, 0, 0), # red ('B', 'C'): (255, 0, 0), # red ('C', 'D'): (255, 0, 0), # red ('D', 'E'): (0, 0, 0), ('E', 'F'): (255, 0, 0), # red ('F', 'G'): (0, 0, 0)}) self.assertEdgeThick(diagram, {('A', 'B'): None, ('B', 'C'): None, ('C', 'D'): None, ('D', 'E'): None, ('E', 'F'): None, ('F', 'G'): 3}) def test_folded_edge_diagram(self): diagram = self.build('folded_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (0, 1), 'E': (0, 2), 'F': (1, 1), 'Z': (0, 3)}) def test_skipped_edge_right_diagram(self): diagram = self.build('skipped_edge_right.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): True, ('B', 'C'): False}) def test_skipped_edge_rightdown_diagram(self): diagram = self.build('skipped_edge_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): True, ('A', 'D'): False, ('B', 'C'): False, ('B', 'D'): False}) def test_skipped_edge_up_diagram(self): diagram = self.build('skipped_edge_up.diag') self.assertEdgeSkipped(diagram, {('C', 'A'): True}) def test_skipped_edge_down_diagram(self): diagram = self.build('skipped_edge_down.diag') self.assertEdgeSkipped(diagram, {('A', 'C'): True}) def test_skipped_edge_leftdown_diagram(self): diagram = self.build('skipped_edge_leftdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('B', 'C'): False, ('B', 'D'): False, ('C', 'G'): True, ('F', 'G'): False}) @capture_stderr def test_skipped_edge_flowchart_rightdown_diagram(self): diagram = self.build('skipped_edge_flowchart_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): False, ('A', 'D'): True, ('C', 'D'): False}) @capture_stderr def test_skipped_edge_flowchart_rightdown2_diagram(self): diagram = self.build('skipped_edge_flowchart_rightdown2.diag') self.assertEdgeSkipped(diagram, {('B', 'C'): False, ('A', 'C'): True}) def test_skipped_edge_portrait_right_diagram(self): diagram = self.build('skipped_edge_portrait_right.diag') self.assertEdgeSkipped(diagram, {('A', 'C'): True}) def test_skipped_edge_portrait_rightdown_diagram(self): diagram = self.build('skipped_edge_portrait_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): False, ('A', 'E'): True, ('B', 'D'): False, ('C', 'E'): False}) def test_skipped_edge_portrait_leftdown_diagram(self): diagram = self.build('skipped_edge_portrait_leftdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('B', 'C'): False, ('D', 'C'): True, ('D', 'E'): False}) def test_skipped_edge_portrait_down_diagram(self): diagram = self.build('skipped_edge_portrait_down.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): True, ('B', 'C'): False}) @capture_stderr def test_skipped_edge_portrait_flowchart_rightdown_diagram(self): diagram = self.build('skipped_edge_portrait_flowchart_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): False, ('A', 'D'): True, ('C', 'D'): False}) @capture_stderr def test_skipped_edge_portrait_flowchart_rightdown2_diagram(self): diagram = self.build('skipped_edge_portrait_flowchart_rightdown2.diag') self.assertEdgeSkipped(diagram, {('B', 'C'): False, ('A', 'C'): True}) blockdiag-1.3.2/src/blockdiag/tests/test_builder_errors.py0000644000076600000240000000712012220762707024545 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase from blockdiag.parser import ParseException class TestBuilderError(BuilderTestCase): def test_unknown_diagram_default_shape_diagram(self): filename = 'errors/unknown_diagram_default_shape.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_diagram_edge_layout_diagram(self): filename = 'errors/unknown_diagram_edge_layout.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_diagram_orientation_diagram(self): filename = 'errors/unknown_diagram_orientation.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_shape_diagram(self): filename = 'errors/unknown_node_shape.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_attribute_diagram(self): filename = 'errors/unknown_node_attribute.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_style_diagram(self): filename = 'errors/unknown_node_style.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_class_diagram(self): filename = 'errors/unknown_node_class.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_dir_diagram(self): filename = 'errors/unknown_edge_dir.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_style_diagram(self): filename = 'errors/unknown_edge_style.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_hstyle_diagram(self): filename = 'errors/unknown_edge_hstyle.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_class_diagram(self): filename = 'errors/unknown_edge_class.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_group_shape_diagram(self): filename = 'errors/unknown_group_shape.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_group_class_diagram(self): filename = 'errors/unknown_group_class.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_group_orientation_diagram(self): filename = 'errors/unknown_group_orientation.diag' with self.assertRaises(AttributeError): self.build(filename) def test_belongs_to_two_groups_diagram(self): filename = 'errors/belongs_to_two_groups.diag' with self.assertRaises(RuntimeError): self.build(filename) def test_unknown_plugin_diagram(self): filename = 'errors/unknown_plugin.diag' with self.assertRaises(AttributeError): self.build(filename) def test_node_follows_group_diagram(self): filename = 'errors/node_follows_group.diag' with self.assertRaises(ParseException): self.build(filename) def test_group_follows_node_diagram(self): filename = 'errors/group_follows_node.diag' with self.assertRaises(ParseException): self.build(filename) def test_lexer_error_diagram(self): filename = 'errors/lexer_error.diag' with self.assertRaises(ParseException): self.build(filename) blockdiag-1.3.2/src/blockdiag/tests/test_builder_group.py0000644000076600000240000002403112220301432024345 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase class TestBuilderGroup(BuilderTestCase): def test_nested_groups_diagram(self): diagram = self.build('nested_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}) def test_nested_groups_and_edges_diagram(self): diagram = self.build('nested_groups_and_edges.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (2, 0), 'Z': (0, 1)}) def test_empty_group_diagram(self): diagram = self.build('empty_group.diag') self.assertNodeXY(diagram, {'Z': (0, 0)}) def test_empty_nested_group_diagram(self): diagram = self.build('empty_nested_group.diag') self.assertNodeXY(diagram, {'Z': (0, 0)}) def test_empty_group_declaration_diagram(self): diagram = self.build('empty_group_declaration.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'Z': (0, 1)}) def test_simple_group_diagram(self): diagram = self.build('simple_group.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_group_declare_as_node_attribute_diagram(self): diagram = self.build('group_declare_as_node_attribute.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (2, 2), 'Z': (0, 3)}) def test_group_attribute(self): diagram = self.build('group_attribute.diag') groups = list(diagram.traverse_groups()) self.assertEqual(1, len(groups)) self.assertEqual((255, 0, 0), groups[0].color) self.assertEqual('line', groups[0].shape) def test_merge_groups_diagram(self): diagram = self.build('merge_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 1), 'D': (1, 1), 'Z': (0, 2)}) def test_node_attribute_and_group_diagram(self): diagram = self.build('node_attribute_and_group.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) self.assertNodeLabel(diagram, {'A': 'foo', 'B': 'bar', 'C': 'baz', 'Z': 'Z'}) self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': '#888888', 'C': (0, 0, 255), 'Z': (255, 255, 255)}) def test_group_sibling_diagram(self): diagram = self.build('group_sibling.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 2), 'D': (2, 0), 'E': (2, 1), 'F': (2, 2), 'Z': (0, 3)}) def test_group_order_diagram(self): diagram = self.build('group_order.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_group_order2_diagram(self): diagram = self.build('group_order2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 1), 'E': (1, 2), 'F': (2, 2), 'Z': (0, 3)}) def test_group_order3_diagram(self): diagram = self.build('group_order3.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (1, 2), 'Z': (0, 3)}) def test_group_children_height_diagram(self): diagram = self.build('group_children_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'F': (2, 2), 'Z': (0, 3)}) def test_group_children_order_diagram(self): diagram = self.build('group_children_order.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'Z': (0, 3)}) def test_group_children_order2_diagram(self): diagram = self.build('group_children_order2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 1), 'F': (2, 0), 'G': (2, 2), 'Z': (0, 3)}) def test_group_children_order3_diagram(self): diagram = self.build('group_children_order3.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'Q': (0, 3), 'Z': (0, 4)}) def test_group_children_order4_diagram(self): diagram = self.build('group_children_order4.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'Z': (0, 3)}) def test_node_in_group_follows_outer_node_diagram(self): diagram = self.build('node_in_group_follows_outer_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_group_id_and_node_id_are_not_conflicted_diagram(self): diagram = self.build('group_id_and_node_id_are_not_conflicted.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 1), 'D': (1, 1), 'Z': (0, 2)}) def test_outer_node_follows_node_in_group_diagram(self): diagram = self.build('outer_node_follows_node_in_group.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_large_group_and_node_diagram(self): diagram = self.build('large_group_and_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (1, 3), 'F': (2, 0), 'Z': (0, 4)}) def test_large_group_and_node2_diagram(self): diagram = self.build('large_group_and_node2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0), 'E': (4, 0), 'F': (5, 0), 'Z': (0, 1)}) def test_large_group_and_two_nodes_diagram(self): diagram = self.build('large_group_and_two_nodes.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (1, 3), 'F': (2, 0), 'G': (2, 1), 'Z': (0, 4)}) def test_group_height_diagram(self): diagram = self.build('group_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (1, 2), 'Z': (0, 3)}) def test_multiple_groups_diagram(self): diagram = self.build('multiple_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (0, 3), 'E': (1, 0), 'F': (1, 1), 'G': (1, 2), 'H': (2, 0), 'I': (2, 1), 'J': (3, 0), 'Z': (0, 4)}) def test_multiple_nested_groups_diagram(self): diagram = self.build('multiple_nested_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_group_works_node_decorator_diagram(self): diagram = self.build('group_works_node_decorator.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (3, 0), 'D': (2, 0), 'E': (1, 1), 'Z': (0, 2)}) def test_nested_groups_work_node_decorator_diagram(self): diagram = self.build('nested_groups_work_node_decorator.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}) def test_reversed_multiple_groups_diagram(self): diagram = self.build('reverse_multiple_groups.diag') self.assertNodeXY(diagram, {'A': (3, 0), 'B': (3, 1), 'C': (3, 2), 'D': (3, 3), 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'H': (1, 0), 'I': (1, 1), 'J': (0, 0), 'Z': (0, 4)}) def test_group_and_skipped_edge_diagram(self): diagram = self.build('group_and_skipped_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0), 'E': (1, 1), 'Z': (0, 2)}) def test_group_orientation_diagram(self): diagram = self.build('group_orientation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 1), 'Z': (0, 2)}) def test_nested_group_orientation_diagram(self): diagram = self.build('nested_group_orientation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (1, 0), 'Z': (0, 2)}) blockdiag-1.3.2/src/blockdiag/tests/test_builder_node.py0000644000076600000240000001471712220301432024150 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from collections import defaultdict from blockdiag.tests.utils import BuilderTestCase class TestBuilderNode(BuilderTestCase): def test_single_node_diagram(self): diagram = self.build('single_node.diag') self.assertEqual(1, len(diagram.nodes)) self.assertEqual(0, len(diagram.edges)) self.assertEqual('A', diagram.nodes[0].label) self.assertEqual((0, 0), diagram.nodes[0].xy) def test_node_shape_diagram(self): expected = {'A': 'box', 'B': 'roundedbox', 'C': 'diamond', 'D': 'ellipse', 'E': 'note', 'F': 'cloud', 'G': 'mail', 'H': 'beginpoint', 'I': 'endpoint', 'J': 'minidiamond', 'K': 'flowchart.condition', 'L': 'flowchart.database', 'M': 'flowchart.input', 'N': 'flowchart.loopin', 'O': 'flowchart.loopout', 'P': 'actor', 'Q': 'flowchart.terminator', 'R': 'textbox', 'S': 'dots', 'T': 'none', 'U': 'square', 'V': 'circle', 'Z': 'box'} diagram = self.build('node_shape.diag') self.assertNodeShape(diagram, expected) def test_node_shape_namespace_diagram(self): diagram = self.build('node_shape_namespace.diag') self.assertNodeShape(diagram, {'A': 'flowchart.condition', 'B': 'condition', 'Z': 'box'}) def test_node_has_multilined_label_diagram(self): diagram = self.build('node_has_multilined_label.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'Z': (0, 1)}) self.assertNodeLabel(diagram, {'A': "foo\nbar", 'Z': 'Z'}) def test_quoted_node_id_diagram(self): diagram = self.build('quoted_node_id.diag') self.assertNodeXY(diagram, {'A': (0, 0), "'A'": (1, 0), 'B': (2, 0), 'Z': (0, 1)}) def test_node_id_includes_dot_diagram(self): diagram = self.build('node_id_includes_dot.diag') self.assertNodeXY(diagram, {'A.B': (0, 0), 'C.D': (1, 0), 'Z': (0, 1)}) def test_multiple_nodes_definition_diagram(self): diagram = self.build('multiple_nodes_definition.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}) self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0), 'Z': (255, 255, 255)}) def test_multiple_node_relation_diagram(self): diagram = self.build('multiple_node_relation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 0), 'Z': (0, 2)}) def test_node_attribute(self): labels = {'A': 'B', 'B': 'double quoted', 'C': 'single quoted', 'D': '\'"double" quoted\'', 'E': '"\'single\' quoted"', 'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I', 'J': 'Hello'} colors = {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 0, 0), 'D': (255, 0, 0), 'E': (255, 0, 0), 'F': (255, 255, 255), 'G': (255, 255, 255), 'H': (255, 255, 255), 'I': (255, 255, 255), 'J': (255, 255, 255)} textcolors = defaultdict(lambda: (0, 0, 0)) textcolors['F'] = (255, 0, 0) linecolors = defaultdict(lambda: (0, 0, 0)) linecolors['I'] = (255, 0, 0) numbered = defaultdict(lambda: None) numbered['E'] = '1' stacked = defaultdict(lambda: False) stacked['G'] = True fontsize = defaultdict(lambda: None) fontsize['H'] = 16 orientations = defaultdict(lambda: 'horizontal') orientations['J'] = 'vertical' diagram = self.build('node_attribute.diag') self.assertNodeLabel(diagram, labels) self.assertNodeColor(diagram, colors) self.assertNodeTextColor(diagram, textcolors) self.assertNodeLineColor(diagram, linecolors) self.assertNodeNumbered(diagram, numbered) self.assertNodeStacked(diagram, stacked) self.assertNodeFontsize(diagram, fontsize) self.assertNodeLabel_Orientation(diagram, orientations) def test_node_height_diagram(self): diagram = self.build('node_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (1, 1), 'Z': (0, 2)}) def test_branched_diagram(self): diagram = self.build('branched.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 1), 'E': (2, 1), 'Z': (0, 2)}) def test_multiple_parent_node_diagram(self): diagram = self.build('multiple_parent_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 2), 'D': (1, 2), 'E': (0, 1), 'Z': (0, 3)}) def test_twin_multiple_parent_node_diagram(self): diagram = self.build('twin_multiple_parent_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 1), 'D': (1, 1), 'E': (0, 2), 'Z': (0, 3)}) def test_flowable_node_diagram(self): diagram = self.build('flowable_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_plugin_autoclass_diagram(self): diagram = self.build('plugin_autoclass.diag') self.assertNodeXY(diagram, {'A_emphasis': (0, 0), 'B_emphasis': (1, 0), 'C': (1, 1)}) self.assertNodeStyle(diagram, {'A_emphasis': 'dashed', 'B_emphasis': 'dashed', 'C': None}) self.assertNodeColor(diagram, {'A_emphasis': (255, 0, 0), 'B_emphasis': (255, 0, 0), 'C': (255, 255, 255)}) def test_plugin_attributes_diagram(self): diagram = self.build('plugin_attributes.diag') self.assertNodeTest_Attr1(diagram, {'A': "1", 'B': None}) self.assertNodeTest_Attr2(diagram, {'A': "2", 'B': None}) self.assertNodeTest_Attr3(diagram, {'A': "3", 'B': None}) blockdiag-1.3.2/src/blockdiag/tests/test_builder_separate.py0000644000076600000240000000312712220762742025037 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from __future__ import print_function from blockdiag.builder import SeparateDiagramBuilder from blockdiag.elements import DiagramNode from blockdiag.tests.utils import BuilderTestCase class TestBuilderSeparated(BuilderTestCase): def _build(self, tree): return SeparateDiagramBuilder.build(tree) def test_separate1_diagram(self): diagram = self.build('separate1.diag') assert_pos = {0: {'B': (0, 0), 'C': (1, 0), 'D': (4, 0), 'E': (2, 0), 'F': (3, 0)}, 1: {'A': (0, 0), 'B': (1, 0), 'D': (3, 0)}, 2: {'A': (0, 0), 'Z': (0, 1)}} for i, diagram in enumerate(diagram): for node in diagram.traverse_nodes(): if isinstance(node, DiagramNode): print(node) self.assertEqual(assert_pos[i][node.id], node.xy) def test_separate2_diagram(self): diagram = self.build('separate2.diag') assert_pos = {0: {'A': (0, 0), 'C': (1, 0), 'D': (2, 0), 'E': (0, 2), 'G': (3, 0), 'H': (3, 1)}, 1: {'A': (0, 0), 'B': (1, 0), 'E': (2, 0), 'F': (4, 2), 'G': (4, 0), 'H': (4, 1)}, 2: {'A': (0, 0), 'F': (2, 2), 'G': (2, 0), 'H': (2, 1), 'Z': (0, 3)}} for i, diagram in enumerate(diagram): for node in diagram.traverse_nodes(): if isinstance(node, DiagramNode): print(node) self.assertEqual(assert_pos[i][node.id], node.xy) blockdiag-1.3.2/src/blockdiag/tests/test_generate_diagram.py0000644000076600000240000000636512235372224025010 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import os import re from nose.tools import nottest from blockdiag.tests.utils import capture_stderr, TemporaryDirectory from blockdiag.tests.utils import supported_pil, supported_pdf import blockdiag import blockdiag.command def get_fontpath(testdir=None): if testdir is None: testdir = os.path.dirname(__file__) return os.path.join(testdir, 'truetype', 'VL-PGothic-Regular.ttf') def get_diagram_files(testdir): diagramsdir = os.path.join(testdir, 'diagrams') skipped = ['errors', 'white.gif'] for file in os.listdir(diagramsdir): if file in skipped: pass else: yield os.path.join(diagramsdir, file) def test_generate(): mainfunc = blockdiag.command.main basepath = os.path.dirname(__file__) files = get_diagram_files(basepath) options = [] for testcase in testcase_generator(basepath, mainfunc, files, options): yield testcase def test_generate_with_separate(): mainfunc = blockdiag.command.main basepath = os.path.dirname(__file__) files = get_diagram_files(basepath) filtered = (f for f in files if re.search('separate', f)) options = ['--separate'] for testcase in testcase_generator(basepath, mainfunc, filtered, options): yield testcase @nottest def testcase_generator(basepath, mainfunc, files, options): fontpath = get_fontpath(basepath) if os.path.exists(fontpath): options = options + ['-f', fontpath] for source in files: yield generate, mainfunc, 'svg', source, options if supported_pil() and os.path.exists(fontpath): yield generate, mainfunc, 'png', source, options yield generate, mainfunc, 'png', source, options + ['--antialias'] if supported_pdf() and os.path.exists(fontpath): yield generate, mainfunc, 'pdf', source, options @capture_stderr def generate(mainfunc, filetype, source, options): try: tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) mainfunc(['-T', filetype, '-o', tmpfile, source] + list(options)) finally: tmpdir.clean() def not_exist_font_config_option_test(): fontpath = get_fontpath() if os.path.exists(fontpath): args = ['-f', '/font_is_not_exist', '-f', fontpath, 'input.diag'] options = blockdiag.command.BlockdiagOptions(blockdiag).parse(args) from blockdiag.utils.bootstrap import detectfont detectfont(options) @capture_stderr def svg_includes_source_code_tag_test(): from xml.etree import ElementTree testdir = os.path.dirname(__file__) diagpath = os.path.join(testdir, 'diagrams', 'single_edge.diag') try: tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) args = ['-T', 'SVG', '-o', tmpfile, diagpath] blockdiag.command.main(args) # compare embeded source code source_code = open(diagpath).read() tree = ElementTree.parse(tmpfile) desc = tree.find('{http://www.w3.org/2000/svg}desc') # strip spaces source_code = re.sub('\s+', ' ', source_code) embeded = re.sub('\s+', ' ', desc.text) assert source_code == embeded finally: tmpdir.clean() blockdiag-1.3.2/src/blockdiag/tests/test_imagedraw_textfolder.py0000644000076600000240000000606112217213441025716 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest from blockdiag.imagedraw.textfolder import splitlabel from blockdiag.imagedraw.textfolder import splittext from blockdiag.imagedraw.textfolder import truncate_text from blockdiag.utils import Size from blockdiag.utils.compat import u CHAR_WIDTH = 14 CHAR_HEIGHT = 10 class Metrics(object): def textsize(self, text): length = len(text) return Size(CHAR_WIDTH * length, CHAR_HEIGHT) class TestTextFolder(unittest.TestCase): def test_splitlabel(self): # single line text text = "abc" self.assertEqual(['abc'], list(splitlabel(text))) # text include \n (as char a.k.a. \x5c) text = "abc\ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text include \n (as mac yensign a.k.a. \xa5) text = "abc\xa5ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text includes \n (as text) text = "abc\\ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text includes escaped \n text = "abc\\\\ndef" self.assertEqual(['abc\\ndef'], list(splitlabel(text))) # text includes escaped \n (\x5c and mac yensign mixed) if sys.version_info[0] == 2: text = u("abc\xa5\\\\ndef") else: text = u("abc\xa5\\ndef") self.assertEqual(['abc\\ndef'], list(splitlabel(text))) # text include \n and spaces text = " abc \n def " self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text starts empty line text = " \nabc\ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text starts empty line with \n (as text) text = " \\nabc\\ndef" self.assertEqual(['', 'abc', 'def'], list(splitlabel(text))) def test_splittext_width(self): metrics = Metrics() # just fit text text = "abc" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual(['abc'], ret) # text should be folded (once) text = "abcdef" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual(['abc', 'def'], ret) # text should be folded (twice) text = "abcdefghi" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual(['abc', 'def', 'ghi'], ret) # empty text text = "" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual([' '], ret) def test_truncate_text(self): metrics = Metrics() # truncated text = "abcdef" ret = truncate_text(metrics, text, CHAR_WIDTH * 8) self.assertEqual("abcd ...", ret) # truncated text = "abcdef" ret = truncate_text(metrics, text, CHAR_WIDTH * 5) self.assertEqual("a ...", ret) # not truncated (too short) text = "abcdef" ret = truncate_text(metrics, text, CHAR_WIDTH * 4) self.assertEqual("abcdef", ret) blockdiag-1.3.2/src/blockdiag/tests/test_imagedraw_utils.py0000644000076600000240000000360112222146156024677 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest from blockdiag.imagedraw.utils import ( is_zenkaku, zenkaku_len, hankaku_len, string_width, textsize ) from blockdiag.utils.compat import u class TestUtils(unittest.TestCase): def test_is_zenkaku(self): # A self.assertEqual(False, is_zenkaku(u("A"))) # あ self.assertEqual(True, is_zenkaku(u("\u3042"))) def test_zenkaku_len(self): # abc self.assertEqual(0, zenkaku_len(u("abc"))) # あいう self.assertEqual(3, zenkaku_len(u("\u3042\u3044\u3046"))) # あいc self.assertEqual(2, zenkaku_len(u("\u3042\u3044c"))) def test_hankaku_len(self): # abc self.assertEqual(3, hankaku_len(u("abc"))) # あいう self.assertEqual(0, hankaku_len(u("\u3042\u3044\u3046"))) # あいc self.assertEqual(1, hankaku_len(u("\u3042\u3044c"))) def test_string_width(self): # abc self.assertEqual(3, string_width(u("abc"))) # あいう self.assertEqual(6, string_width(u("\u3042\u3044\u3046"))) # あいc self.assertEqual(5, string_width(u("\u3042\u3044c"))) def test_test_textsize(self): from blockdiag.utils.fontmap import FontInfo font = FontInfo('serif', None, 11) # abc self.assertEqual((19, 11), textsize(u("abc"), font)) # あいう self.assertEqual((33, 11), textsize(u("\u3042\u3044\u3046"), font)) # あいc self.assertEqual((29, 11), textsize(u("\u3042\u3044c"), font)) # abc font = FontInfo('serif', None, 24) self.assertEqual((40, 24), textsize(u("abc"), font)) # あいう font = FontInfo('serif', None, 18) self.assertEqual((54, 18), textsize(u("\u3042\u3044\u3046"), font)) blockdiag-1.3.2/src/blockdiag/tests/test_parser.py0000644000076600000240000001113512234663754023027 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from __future__ import print_function import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest from blockdiag.parser import parse_string, ParseException from blockdiag.parser import Diagram, Group, Statements, Node, Edge class TestParser(unittest.TestCase): def test_basic(self): # basic digram code = """ diagram test { A -> B -> C, D; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) def test_without_diagram_id(self): code = """ diagram { A -> B -> C, D; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) code = """ { A -> B -> C, D; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) def test_empty_diagram(self): code = """ diagram { } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) code = """ { } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) def test_diagram_includes_nodes(self): code = """ diagram { A; B [label = "foobar"]; C [color = "red"]; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(3, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Statements) self.assertIsInstance(tree.stmts[0].stmts[0], Node) self.assertIsInstance(tree.stmts[1], Statements) self.assertIsInstance(tree.stmts[1].stmts[0], Node) self.assertIsInstance(tree.stmts[2], Statements) self.assertIsInstance(tree.stmts[2].stmts[0], Node) def test_diagram_includes_edges(self): code = """ diagram { A -> B -> C; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(1, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Statements) self.assertEqual(2, len(tree.stmts[0].stmts)) self.assertIsInstance(tree.stmts[0].stmts[0], Edge) self.assertIsInstance(tree.stmts[0].stmts[1], Edge) code = """ diagram { A -> B -> C [style = dotted]; D -> E, F; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(2, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Statements) self.assertEqual(2, len(tree.stmts[0].stmts)) self.assertIsInstance(tree.stmts[0].stmts[0], Edge) self.assertIsInstance(tree.stmts[0].stmts[1], Edge) self.assertIsInstance(tree.stmts[1], Statements) self.assertEqual(1, len(tree.stmts[1].stmts)) self.assertIsInstance(tree.stmts[1].stmts[0], Edge) def test_diagram_includes_groups(self): code = """ diagram { group { A; B; } group { C -> D; } } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(2, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Group) self.assertEqual(2, len(tree.stmts[0].stmts)) self.assertIsInstance(tree.stmts[0].stmts[0], Statements) self.assertIsInstance(tree.stmts[0].stmts[0].stmts[0], Node) self.assertIsInstance(tree.stmts[0].stmts[1], Statements) self.assertIsInstance(tree.stmts[0].stmts[1].stmts[0], Node) self.assertIsInstance(tree.stmts[1], Group) self.assertEqual(1, len(tree.stmts[1].stmts)) self.assertIsInstance(tree.stmts[1].stmts[0], Statements) self.assertIsInstance(tree.stmts[1].stmts[0].stmts[0], Edge) def test_diagram_includes_diagram_attributes(self): code = """ diagram { fontsize = 12; node_width = 80; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(2, len(tree.stmts)) def test_parenthesis_ness(self): with self.assertRaises(ParseException): code = "" parse_string(code) blockdiag-1.3.2/src/blockdiag/tests/test_pep8.py0000644000076600000240000000324612227701000022366 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- from __future__ import print_function import os import sys import pep8 CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = os.path.dirname(CURRENT_DIR) def test_pep8(): arglist = [['statistics', True], ['show-source', True], ['repeat', True], ['paths', [BASE_DIR]]] pep8style = pep8.StyleGuide(arglist, parse_argv=False, config_file=True) options = pep8style.options if options.doctest: import doctest fail_d, done_d = doctest.testmod(report=False, verbose=options.verbose) fail_s, done_s = pep8.selftest(options) count_failed = fail_s + fail_d if not options.quiet: count_passed = done_d + done_s - count_failed print("%d passed and %d failed." % (count_passed, count_failed)) if count_failed: print("Test failed.") else: print("Test passed.") if count_failed: sys.exit(1) if options.testsuite: pep8.init_tests(pep8style) report = pep8style.check_files() if options.statistics: report.print_statistics() if options.benchmark: report.print_benchmark() if options.testsuite and not options.quiet: report.print_results() if report.total_errors: if options.count: sys.stderr.write(str(report.total_errors) + '\n') #sys.exit(1) # reporting errors (additional summary) errors = report.get_count('E') warnings = report.get_count('W') message = 'pep8: %d errors / %d warnings' % (errors, warnings) print(message) assert report.total_errors == 0, message blockdiag-1.3.2/src/blockdiag/tests/test_rst_directives.py0000644000076600000240000006034212235371405024556 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest import os import io from blockdiag.utils.compat import u from blockdiag.tests.utils import capture_stderr, with_pil, TemporaryDirectory from docutils import nodes from docutils.core import publish_doctree, publish_parts from docutils.parsers.rst import directives as docutils from blockdiag.utils.rst import directives class TestRstDirectives(unittest.TestCase): def setUp(self): docutils.register_directive('blockdiag', directives.BlockdiagDirectiveBase) self._tmpdir = TemporaryDirectory() def tearDown(self): if 'blockdiag' in docutils._directives: del docutils._directives['blockdiag'] self._tmpdir.clean() @property def tmpdir(self): return self._tmpdir.name def test_setup(self): directives.setup() options = directives.directive_options self.assertIn('blockdiag', docutils._directives) self.assertEqual(directives.BlockdiagDirective, docutils._directives['blockdiag']) self.assertEqual('PNG', options['format']) self.assertEqual(False, options['antialias']) self.assertEqual(None, options['fontpath']) self.assertEqual(False, options['nodoctype']) self.assertEqual(False, options['noviewbox']) self.assertEqual(False, options['inline_svg']) def test_setup_with_args(self): directives.setup(format='SVG', antialias=True, fontpath='/dev/null', nodoctype=True, noviewbox=True, inline_svg=True) options = directives.directive_options self.assertIn('blockdiag', docutils._directives) self.assertEqual(directives.BlockdiagDirective, docutils._directives['blockdiag']) self.assertEqual('SVG', options['format']) self.assertEqual(True, options['antialias']) self.assertEqual('/dev/null', options['fontpath']) self.assertEqual(True, options['nodoctype']) self.assertEqual(True, options['noviewbox']) self.assertEqual(True, options['inline_svg']) @capture_stderr def test_base_noargs(self): text = ".. blockdiag::" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) def test_base_with_block(self): text = ".. blockdiag::\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(directives.blockdiag, type(doctree[0])) self.assertEqual('{ A -> B }', doctree[0]['code']) self.assertEqual(None, doctree[0]['alt']) self.assertEqual({}, doctree[0]['options']) @capture_stderr def test_base_with_emptyblock(self): text = ".. blockdiag::\n\n \n" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) def test_base_with_filename(self): dirname = os.path.dirname(__file__) filename = os.path.join(dirname, 'diagrams/node_attribute.diag') text = ".. blockdiag:: %s" % filename doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(directives.blockdiag, type(doctree[0])) self.assertEqual(io.open(filename).read(), doctree[0]['code']) self.assertEqual(None, doctree[0]['alt']) self.assertEqual({}, doctree[0]['options']) @capture_stderr def test_base_with_filename_not_exists(self): text = ".. blockdiag:: unknown.diag" doctree = publish_doctree(text) self.assertEqual(nodes.system_message, type(doctree[0])) @capture_stderr def test_base_with_block_and_filename(self): text = ".. blockdiag:: unknown.diag\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) def test_base_with_options(self): text = ".. blockdiag::\n :alt: hello world\n :desctable:\n" + \ " :maxwidth: 100\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(directives.blockdiag, type(doctree[0])) self.assertEqual('{ A -> B }', doctree[0]['code']) self.assertEqual('hello world', doctree[0]['alt']) self.assertEqual(None, doctree[0]['options']['desctable']) self.assertEqual(100, doctree[0]['options']['maxwidth']) def test_block(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = ".. blockdiag::\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertFalse('alt' in doctree[0]) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) self.assertFalse('target' in doctree[0]) def test_block_alt(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = ".. blockdiag::\n :alt: hello world\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('hello world', doctree[0]['alt']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) self.assertFalse('target' in doctree[0]) def test_block_fontpath1(self): with self.assertRaises(RuntimeError): directives.setup(format='SVG', fontpath=['dummy.ttf'], outputdir=self.tmpdir) text = ".. blockdiag::\n :alt: hello world\n\n { A -> B }" publish_doctree(text) def test_block_fontpath2(self): with self.assertRaises(RuntimeError): directives.setup(format='SVG', fontpath='dummy.ttf', outputdir=self.tmpdir) text = ".. blockdiag::\n :alt: hello world\n\n { A -> B }" publish_doctree(text) def test_caption(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = ".. blockdiag::\n :caption: hello world\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual(2, len(doctree[0])) self.assertEqual(nodes.image, type(doctree[0][0])) self.assertEqual(nodes.caption, type(doctree[0][1])) self.assertEqual(1, len(doctree[0][1])) self.assertEqual(nodes.Text, type(doctree[0][1][0])) self.assertEqual('hello world', doctree[0][1][0]) def test_block_maxwidth(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = ".. blockdiag::\n :maxwidth: 100\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertFalse('alt' in doctree[0]) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) self.assertFalse(0, doctree[0]['target'].index(self.tmpdir)) def test_block_nodoctype_false(self): directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=False) text = ".. blockdiag::\n :alt: hello world\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) svg = open(doctree[0]['uri']).read() self.assertEqual("\n" " B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[-1])) svg = open(doctree[0]['uri']).read() self.assertNotEqual("\n" " B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) svg = open(doctree[0]['uri']).read() self.assertRegexpMatches(svg, '\n" " B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) def test_block_inline_svg_true_with_multibytes(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = u(".. blockdiag::\n :alt: hello world\n\n { あ -> い }") publish_parts(source=text) def test_block_max_width_inline_svg(self): directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=True, noviewbox=True, inline_svg=True) text = ".. blockdiag::\n :maxwidth: 100\n\n { A -> B }" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertRegexpMatches(doctree[0][0], ' B [description = \"foo\"]; " + \ " C -> D [description = \"bar\"]; " + \ " C [label = \"label_C\"]; " + \ " D [label = \"label_D\"]; }" doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) # tgroup self.assertEqual(4, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.thead, type(doctree[1][0][2])) self.assertEqual(nodes.tbody, type(doctree[1][0][3])) # colspec self.assertEqual(25, doctree[1][0][0]['colwidth']) self.assertEqual(50, doctree[1][0][1]['colwidth']) # thead thead = doctree[1][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[1][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('A -> B', tbody[0][0][0][0]) self.assertEqual(1, len(tbody[0][1][0])) self.assertEqual(nodes.Text, type(tbody[0][1][0][0])) self.assertEqual('foo', str(tbody[0][1][0][0])) self.assertEqual('label_C -> label_D', tbody[1][0][0][0]) self.assertEqual(1, len(tbody[1][1][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][0])) self.assertEqual('bar', str(tbody[1][1][0][0])) def test_desctable_for_nodes_and_edges(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = ".. blockdiag::\n :desctable:\n\n" + \ " { A -> B [description = \"foo\"]; " + \ " C -> D [description = \"bar\"]; " + \ " C [label = \"label_C\", description = foo]; " + \ " D [label = \"label_D\"]; }" doctree = publish_doctree(text) self.assertEqual(3, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) self.assertEqual(nodes.table, type(doctree[2])) # tgroup self.assertEqual(4, len(doctree[2][0])) self.assertEqual(nodes.colspec, type(doctree[2][0][0])) self.assertEqual(nodes.colspec, type(doctree[2][0][1])) self.assertEqual(nodes.thead, type(doctree[2][0][2])) self.assertEqual(nodes.tbody, type(doctree[2][0][3])) # colspec self.assertEqual(25, doctree[2][0][0]['colwidth']) self.assertEqual(50, doctree[2][0][1]['colwidth']) # thead thead = doctree[2][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[2][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('A -> B', tbody[0][0][0][0]) self.assertEqual(1, len(tbody[0][1][0])) self.assertEqual(nodes.Text, type(tbody[0][1][0][0])) self.assertEqual('foo', str(tbody[0][1][0][0])) self.assertEqual('label_C -> label_D', tbody[1][0][0][0]) self.assertEqual(1, len(tbody[1][1][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][0])) self.assertEqual('bar', str(tbody[1][1][0][0])) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/tests/test_utils_fontmap.py�������������������������������������������0000644�0000766�0000024�00000030557�12235371405�024416� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest import os import tempfile from blockdiag.utils.compat import u from blockdiag.tests.utils import capture_stderr try: from io import StringIO except ImportError: from cStringIO import StringIO from collections import namedtuple from blockdiag.utils.fontmap import FontInfo, FontMap FontElement = namedtuple('FontElement', 'fontfamily fontsize') class TestUtilsFontmap(unittest.TestCase): def setUp(self): fontpath1 = __file__ fontpath2 = os.path.join(os.path.dirname(__file__), 'utils.py') self.fontpath = [fontpath1, fontpath2] def test_fontinfo_new(self): FontInfo("serif", None, 11) FontInfo("sansserif", None, 11) FontInfo("monospace", None, 11) FontInfo("cursive", None, 11) FontInfo("fantasy", None, 11) FontInfo("serif-bold", None, 11) FontInfo("sansserif-italic", None, 11) FontInfo("monospace-oblique", None, 11) FontInfo("my-cursive", None, 11) FontInfo("-fantasy", None, 11) def test_fontinfo_invalid_familyname1(self): with self.assertRaises(AttributeError): FontInfo("unknown", None, 11) def test_fontinfo_invalid_familyname2(self): with self.assertRaises(AttributeError): FontInfo("sansserif-", None, 11) def test_fontinfo_invalid_familyname3(self): with self.assertRaises(AttributeError): FontInfo("monospace-unkown", None, 11) def test_fontinfo_invalid_familyname4(self): with self.assertRaises(AttributeError): FontInfo("cursive-bold-bold", None, 11) def test_fontinfo_invalid_familyname5(self): with self.assertRaises(AttributeError): FontInfo("SERIF", None, 11) def test_fontinfo_invalid_fontsize1(self): with self.assertRaises(TypeError): FontInfo("serif", None, None) def test_fontinfo_invalid_fontsize2(self): with self.assertRaises(ValueError): FontInfo("serif", None, '') def test_fontinfo_parse(self): font = FontInfo("serif", None, 11) self.assertEqual('', font.name) self.assertEqual('serif', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('normal', font.style) font = FontInfo("sansserif-bold", None, 11) self.assertEqual('', font.name) self.assertEqual('sansserif', font.generic_family) self.assertEqual('bold', font.weight) self.assertEqual('normal', font.style) font = FontInfo("monospace-italic", None, 11) self.assertEqual('', font.name) self.assertEqual('monospace', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('italic', font.style) font = FontInfo("my-cursive-oblique", None, 11) self.assertEqual('my', font.name) self.assertEqual('cursive', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('oblique', font.style) font = FontInfo("my-fantasy-bold", None, 11) self.assertEqual('my', font.name) self.assertEqual('fantasy', font.generic_family) self.assertEqual('bold', font.weight) self.assertEqual('normal', font.style) font = FontInfo("serif-serif", None, 11) self.assertEqual('serif', font.name) self.assertEqual('serif', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('normal', font.style) def test_fontinfo_familyname(self): font = FontInfo("serif", None, 11) self.assertEqual('serif-normal', font.familyname) font = FontInfo("sansserif-bold", None, 11) self.assertEqual('sansserif-bold', font.familyname) font = FontInfo("monospace-italic", None, 11) self.assertEqual('monospace-italic', font.familyname) font = FontInfo("my-cursive-oblique", None, 11) self.assertEqual('my-cursive-oblique', font.familyname) font = FontInfo("my-fantasy-bold", None, 11) self.assertEqual('my-fantasy-bold', font.familyname) font = FontInfo("serif-serif", None, 11) self.assertEqual('serif-serif-normal', font.familyname) font = FontInfo("-serif", None, 11) self.assertEqual('serif-normal', font.familyname) @capture_stderr def test_fontmap_empty_config(self): config = StringIO(u("")) fmap = FontMap(config) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sansserif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) element = FontElement('sansserif', 11) font2 = fmap.find(element) self.assertEqual(font1.familyname, font2.familyname) self.assertEqual(font1.path, font2.path) self.assertEqual(font1.size, font2.size) element = FontElement('sansserif-normal', 11) font3 = fmap.find(element) self.assertEqual(font1.familyname, font3.familyname) self.assertEqual(font1.path, font3.path) self.assertEqual(font1.size, font3.size) # non-registered familyname element = FontElement('my-sansserif-normal', 11) font4 = fmap.find(element) self.assertEqual(font1.familyname, font4.familyname) self.assertEqual(font1.path, font4.path) self.assertEqual(font1.size, font4.size) @capture_stderr def test_fontmap_none_config(self): fmap = FontMap() font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sansserif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) def test_fontmap_normal_config(self): _config = u("[fontmap]\nsansserif: %s\nsansserif-bold: %s\n") % \ (self.fontpath[0], self.fontpath[1]) config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sansserif', font1.generic_family) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) element = FontElement('sansserif', 11) font2 = fmap.find(element) self.assertEqual(font1.familyname, font2.familyname) self.assertEqual(font1.path, font2.path) self.assertEqual(font1.size, font2.size) element = FontElement('sansserif-normal', 11) font3 = fmap.find(element) self.assertEqual(font1.familyname, font3.familyname) self.assertEqual(font1.path, font3.path) self.assertEqual(font1.size, font3.size) element = FontElement('sansserif-bold', 11) font4 = fmap.find(element) self.assertEqual('sansserif-bold', font4.familyname) self.assertEqual(self.fontpath[1], font4.path) self.assertEqual(font1.size, font4.size) element = FontElement(None, None) font5 = fmap.find(element) self.assertEqual(font1.familyname, font5.familyname) self.assertEqual(font1.path, font5.path) self.assertEqual(font1.size, font5.size) element = object() font6 = fmap.find(element) self.assertEqual(font1.familyname, font6.familyname) self.assertEqual(font1.path, font6.path) self.assertEqual(font1.size, font6.size) def test_fontmap_duplicated_fontentry1(self): _config = u("[fontmap]\nsansserif: %s\nsansserif: %s\n") % \ (self.fontpath[0], self.fontpath[1]) config = StringIO(_config) if sys.version_info[0] == 2: fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sansserif', font1.generic_family) self.assertEqual(self.fontpath[1], font1.path) self.assertEqual(11, font1.size) else: import configparser with self.assertRaises(configparser.DuplicateOptionError): FontMap(config) def test_fontmap_duplicated_fontentry2(self): # this testcase is only for python2.6 or later if sys.version_info > (2, 6): _config = u("[fontmap]\nsansserif: %s\nsansserif-normal: %s\n") % \ (self.fontpath[0], self.fontpath[1]) config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sansserif', font1.generic_family) self.assertEqual(self.fontpath[1], font1.path) self.assertEqual(11, font1.size) @capture_stderr def test_fontmap_with_nodefault_fontentry(self): _config = u("[fontmap]\nserif: %s\n") % self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sansserif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) element = FontElement('serif', 11) font2 = fmap.find(element) self.assertEqual('serif', font2.generic_family) self.assertEqual(self.fontpath[0], font2.path) self.assertEqual(font1.size, font2.size) element = FontElement('fantasy', 20) font3 = fmap.find(element) self.assertEqual('sansserif', font3.generic_family) self.assertEqual(None, font3.path) self.assertEqual(20, font3.size) @capture_stderr def test_fontmap_with_nonexistence_fontpath(self): _config = u("[fontmap]\nserif: unknown_file\n") config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sansserif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) def test_fontmap_switch_defaultfamily(self): _config = u("[fontmap]\nserif-bold: %s\n") % self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sansserif-normal', font1.familyname) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) fmap.set_default_fontfamily('serif-bold') font2 = fmap.find() self.assertEqual('serif-bold', font2.familyname) self.assertEqual(self.fontpath[0], font2.path) self.assertEqual(11, font2.size) fmap.set_default_fontfamily('fantasy-italic') font3 = fmap.find() self.assertEqual('fantasy-italic', font3.familyname) self.assertEqual(None, font3.path) self.assertEqual(11, font3.size) fmap.fontsize = 20 font4 = fmap.find() self.assertEqual('fantasy-italic', font4.familyname) self.assertEqual(None, font4.path) self.assertEqual(20, font4.size) def test_fontmap_using_fontalias(self): _config = (u("[fontmap]\nserif-bold: %s\n") + u("[fontalias]\ntest = serif-bold\n")) % self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) element = FontElement('test', 20) font1 = fmap.find(element) self.assertEqual('serif-bold', font1.familyname) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(20, font1.size) def test_fontmap_by_file(self): tmp = tempfile.mkstemp() _config = u("[fontmap]\nsansserif: %s\nsansserif-bold: %s\n") % \ (self.fontpath[0], self.fontpath[1]) fp = os.fdopen(tmp[0], 'wt') fp.write(_config) fp.close() fmap = FontMap(tmp[1]) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sansserif', font1.generic_family) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) os.unlink(tmp[1]) def test_fontmap_including_bom_by_file(self): tmp = tempfile.mkstemp() _config = (u("[fontmap]\nsansserif: %s\n") + u("sansserif-bold: %s\n")) % \ (self.fontpath[0], self.fontpath[1]) try: fp = os.fdopen(tmp[0], 'wb') fp.write(_config.encode('utf-8-sig')) fp.close() fmap = FontMap(tmp[1]) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sansserif', font1.generic_family) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) finally: os.unlink(tmp[1]) �������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/tests/utils.py��������������������������������������������������������0000644�0000766�0000024�00000006151�12242633443�021625� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from __future__ import print_function import sys if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest import os import re import functools from shutil import rmtree from tempfile import mkdtemp, mkstemp from blockdiag.builder import ScreenNodeBuilder from blockdiag.parser import parse_file try: from io import StringIO except ImportError: from cStringIO import StringIO def supported_pil(): try: from PIL import _imagingft _imagingft return True except: return False def with_pil(fn): if not supported_pil(): fn.__test__ = False return fn def supported_pdf(): try: import reportlab reportlab return True except: return False def with_pdf(fn): if not supported_pdf(): fn.__test__ = False return fn def capture_stderr(func): def wrap(*args, **kwargs): try: stderr = sys.stderr sys.stderr = StringIO() func(*args, **kwargs) if re.search('ERROR', sys.stderr.getvalue()): raise AssertionError('Caught error') finally: if sys.stderr.getvalue(): print("---[ stderr ] ---") print(sys.stderr.getvalue()) sys.stderr = stderr return functools.wraps(func)(wrap) stderr_wrapper = capture_stderr # FIXME: deprecated class TemporaryDirectory(object): def __init__(self, suffix='', prefix='tmp', dir=None): self.name = mkdtemp(suffix, prefix, dir) def __del__(self): self.clean() def clean(self): if os.path.exists(self.name): rmtree(self.name) def mkstemp(self, suffix='', prefix='tmp', text=False): return mkstemp(suffix, prefix, self.name, text) class BuilderTestCase(unittest.TestCase): def build(self, filename): basedir = os.path.dirname(__file__) pathname = os.path.join(basedir, 'diagrams', filename) return self._build(parse_file(pathname)) def _build(self, tree): return ScreenNodeBuilder.build(tree) def __getattr__(self, name): if name.startswith('assertNode'): def asserter(diagram, attributes): attr_name = name.replace('assertNode', '').lower() print("[node.%s]" % attr_name) for node in (n for n in diagram.nodes if n.drawable): print(node) excepted = attributes[node.id] self.assertEqual(excepted, getattr(node, attr_name)) return asserter elif name.startswith('assertEdge'): def asserter(diagram, attributes): attr_name = name.replace('assertEdge', '').lower() print("[edge.%s]" % attr_name) for edge in diagram.edges: print(edge) expected = attributes[(edge.node1.id, edge.node2.id)] self.assertEqual(expected, getattr(edge, attr_name)) return asserter else: return getattr(super(BuilderTestCase, self), name) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/����������������������������������������������������������������0000755�0000766�0000024�00000000000�12242633535�020110� 5����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/__init__.py�����������������������������������������������������0000644�0000766�0000024�00000010313�12242633443�022215� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import re import math from collections import namedtuple Size = namedtuple('Size', 'width height') class XY(tuple): mapper = dict(x=0, y=1) def __new__(cls, x, y): return super(XY, cls).__new__(cls, (x, y)) def __getattr__(self, name): try: return self[self.mapper[name]] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): raise TypeError("'XY' object does not support item assignment") def shift(self, x=0, y=0): return self.__class__(self.x + x, self.y + y) class Box(list): mapper = dict(x1=0, y1=1, x2=2, y2=3, x=0, y=1) def __init__(self, x1, y1, x2, y2): super(Box, self).__init__((x1, y1, x2, y2)) def __getattr__(self, name): try: return self[self.mapper[name]] except KeyError: raise AttributeError(name) def __repr__(self): _format = "<%s (%s, %s) %dx%d at 0x%08x>" params = (self.__class__.__name__, self.x1, self.y1, self.width, self.height, id(self)) return _format % params def shift(self, x=0, y=0): return self.__class__(self.x1 + x, self.y1 + y, self.x2 + x, self.y2 + y) def get_padding_for(self, size, **kwargs): valign = kwargs.get('valign', 'center') halign = kwargs.get('halign', 'center') padding = kwargs.get('padding', 0) if halign == 'left': dx = padding elif halign == 'right': dx = self.size.width - size.width - padding else: dx = int(math.ceil((self.size.width - size.width) / 2.0)) if valign == 'top': dy = padding elif valign == 'bottom': dy = self.size.height - size.height - padding else: dy = int(math.ceil((self.size.height - size.height) / 2.0)) return dx, dy @property def size(self): return Size(self.width, self.height) @property def width(self): return self.x2 - self.x1 @property def height(self): return self.y2 - self.y1 @property def topleft(self): return XY(self.x1, self.y1) @property def top(self): return XY(self.x1 + self.width // 2, self.y1) @property def topright(self): return XY(self.x2, self.y1) @property def bottomleft(self): return XY(self.x1, self.y2) @property def bottom(self): return XY(self.x1 + self.width // 2, self.y2) @property def bottomright(self): return XY(self.x2, self.y2) @property def left(self): return XY(self.x1, self.y1 + self.height // 2) @property def right(self): return XY(self.x2, self.y1 + self.height // 2) @property def center(self): return XY(self.x1 + self.width // 2, self.y1 + self.height // 2) def to_integer_point(self): return Box(*[int(i) for i in self]) def unquote(string): """ Remove quotas from string >>> unquote('"test"') 'test' >>> unquote("'test'") 'test' >>> unquote("'half quoted") "'half quoted" >>> unquote('"half quoted') '"half quoted' """ if string: m = re.match('\A(?P"|\')((.|\s)*)(?P=quote)\Z', string, re.M) if m: return re.sub("\\\\" + m.group(1), m.group(1), m.group(2)) else: return string else: return string def is_Pillow_available(): try: from PIL import _imagingft _imagingft return True except ImportError: return False ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/bootstrap.py����������������������������������������������������0000644�0000766�0000024�00000022430�12234433313�022471� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re import sys import codecs from optparse import OptionParser, SUPPRESS_HELP from blockdiag import imagedraw from blockdiag.utils.config import ConfigParser from blockdiag.utils.fontmap import parse_fontpath, FontMap class Application(object): module = None options = None def run(self, args): try: self.parse_options(args) self.create_fontmap() parsed = self.parse_diagram() return self.build_diagram(parsed) except SystemExit as e: return e except UnicodeEncodeError as e: msg = "ERROR: UnicodeEncodeError caught " + \ "(check your font settings)\n" sys.stderr.write(msg) return -1 except Exception as e: if self.options and self.options.debug: import traceback traceback.print_exc() else: sys.stderr.write("ERROR: %s\n" % e) return -1 def parse_options(self, args): self.options = Options(self.module).parse(args) def create_fontmap(self): self.fontmap = create_fontmap(self.options) def parse_diagram(self): if self.options.input == '-': stream = codecs.getreader('utf-8-sig')(sys.stdin) self.code = stream.read() else: fp = codecs.open(self.options.input, 'r', 'utf-8-sig') self.code = fp.read() return self.module.parser.parse_string(self.code) def build_diagram(self, tree): DiagramDraw = self.module.drawer.DiagramDraw diagram = self.module.builder.ScreenNodeBuilder.build(tree) drawer = DiagramDraw(self.options.type, diagram, self.options.output, fontmap=self.fontmap, code=self.code, antialias=self.options.antialias, nodoctype=self.options.nodoctype, transparency=self.options.transparency) drawer.draw() if self.options.size: drawer.save(size=self.options.size) else: drawer.save() return 0 class Options(object): def __init__(self, module): self.module = module self.build_parser() def parse(self, args): self.options, self.args = self.parser.parse_args(args) self.validate() self.read_configfile() return self.options def build_parser(self): version = "%%prog %s" % self.module.__version__ usage = "usage: %prog [options] infile" self.parser = p = OptionParser(usage=usage, version=version) p.add_option('-a', '--antialias', action='store_true', help='Pass diagram image to anti-alias filter') p.add_option('-c', '--config', help='read configurations from FILE', metavar='FILE') p.add_option('--debug', action='store_true', help='Enable debug mode') p.add_option('-o', dest='output', help='write diagram to FILE', metavar='FILE') p.add_option('-f', '--font', default=[], action='append', help='use FONT to draw diagram', metavar='FONT') p.add_option('--fontmap', help='use FONTMAP file to draw diagram', metavar='FONT') p.add_option('--ignore-pil', dest='ignore_pil', default=False, action='store_true', help=SUPPRESS_HELP) p.add_option('--no-transparency', dest='transparency', default=True, action='store_false', help='do not make transparent background of diagram ' + '(PNG only)') p.add_option('--size', help='Size of diagram (ex. 320x240)') p.add_option('-T', dest='type', default='PNG', help='Output diagram as TYPE format') p.add_option('--nodoctype', action='store_true', help='Do not output doctype definition tags (SVG only)') return p def validate(self): if len(self.args) == 0: self.parser.print_help() sys.exit(0) self.options.input = self.args.pop(0) if self.options.output: pass elif self.options.output == '-': self.options.output = 'output.' + self.options.type.lower() else: basename = os.path.splitext(self.options.input)[0] ext = '.%s' % self.options.type.lower() self.options.output = basename + ext self.options.type = self.options.type.upper() try: imagedraw.create(self.options.type, None) except: msg = "unknown format: %s" % self.options.type raise RuntimeError(msg) if self.options.size: matched = re.match('^(\d+)x(\d+)$', self.options.size) if matched: self.options.size = [int(n) for n in matched.groups()] else: msg = "--size option must be formatted as WIDTHxHEIGHT." raise RuntimeError(msg) if self.options.type == 'PDF': try: import reportlab.pdfgen.canvas reportlab.pdfgen.canvas except ImportError: msg = "could not output PDF format; Install reportlab." raise RuntimeError(msg) if self.options.ignore_pil: msg = "WARNING: --ignore-pil option is deprecated " + \ "(detect automatically).\n" sys.stderr.write(msg) if self.options.nodoctype and self.options.type != 'SVG': msg = "--nodoctype option work in SVG images." raise RuntimeError(msg) if self.options.transparency is False and self.options.type != 'PNG': msg = "--no-transparency option work in PNG images." raise RuntimeError(msg) if self.options.config and not os.path.isfile(self.options.config): msg = "config file is not found: %s" % self.options.config raise RuntimeError(msg) if self.options.fontmap and not os.path.isfile(self.options.fontmap): msg = "fontmap file is not found: %s" % self.options.fontmap raise RuntimeError(msg) def read_configfile(self): if self.options.config: configpath = self.options.config elif os.environ.get('HOME'): configpath = '%s/.blockdiagrc' % os.environ.get('HOME') elif os.environ.get('USERPROFILE'): configpath = '%s/.blockdiagrc' % os.environ.get('USERPROFILE') else: configpath = '' appname = self.module.__name__ if os.path.isfile(configpath): config = ConfigParser() config.read(configpath) if config.has_option(appname, 'fontpath'): fontpath = config.get(appname, 'fontpath') self.options.font.append(fontpath) if config.has_option(appname, 'fontmap'): if self.options.fontmap is None: self.options.fontmap = config.get(appname, 'fontmap') if config.has_option(appname, 'antialias'): antialias = config.get(appname, 'antialias') if antialias.lower() == 'true': self.options.antialias = True if self.options.fontmap is None: self.options.fontmap = configpath def detectfont(options): import glob fontdirs = [ '/usr/share/fonts', '/Library/Fonts', '/System/Library/Fonts', 'c:/windows/fonts', '/usr/local/share/font-*', ] fontfiles = [ 'ipagp.ttf', 'ipagp.otf', 'VL-PGothic-Regular.ttf', 'Hiragino Sans GB W3.otf', 'AppleGothic.ttf', 'msgothic.ttf', 'msgoth04.ttf', 'msgothic.ttc', ] fontpath = None if options.font: for path in options.font: _path, _ = parse_fontpath(path) if os.path.isfile(_path): fontpath = path break else: msg = 'fontfile is not found: %s' % options.font raise RuntimeError(msg) if fontpath is None: globber = (glob.glob(d) for d in fontdirs) for fontdir in sum(globber, []): for root, _, files in os.walk(fontdir): for font in fontfiles: if font in files: fontpath = os.path.join(root, font) break return fontpath def create_fontmap(options): fontmap = FontMap(options.fontmap) if fontmap.find().path is None or options.font: fontpath = detectfont(options) fontmap.set_default_font(fontpath) return fontmap ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/compat.py�������������������������������������������������������0000644�0000766�0000024�00000003317�12227702767�021760� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys if sys.version_info[0] == 2: string_types = (str, unicode) # NOQA: pyflakes complains to unicode in py3 else: string_types = (str,) def u(string): if sys.version_info[0] == 2: return unicode(string, "unicode_escape") # NOQA: pyflakes complains to unicode in py3 else: return string def cmp_to_key(mycmp): """Convert a cmp= function into a key= function""" class K(object): __slots__ = ['obj'] def __init__(self, obj, *args): self.obj = obj def __lt__(self, other): return mycmp(self.obj, other.obj) < 0 def __gt__(self, other): return mycmp(self.obj, other.obj) > 0 def __eq__(self, other): return mycmp(self.obj, other.obj) == 0 def __le__(self, other): return mycmp(self.obj, other.obj) <= 0 def __ge__(self, other): return mycmp(self.obj, other.obj) >= 0 def __ne__(self, other): return mycmp(self.obj, other.obj) != 0 def __hash__(self): raise TypeError('hash not implemented') return K �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/config.py�������������������������������������������������������0000644�0000766�0000024�00000002502�12217202617�021721� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import io import sys try: from configparser import SafeConfigParser except ImportError: from ConfigParser import SafeConfigParser class ConfigParser(SafeConfigParser): def __init__(self): if sys.version_info > (2, 6) and sys.version_info < (2, 7): # only for Python2.6 # - dict_type argument is supported py2.6 or later # - SafeConfigParser of py2.7 uses OrderedDict as default from ordereddict import OrderedDict SafeConfigParser.__init__(self, dict_type=OrderedDict) else: SafeConfigParser.__init__(self) def read(self, path): fd = io.open(path, 'r', encoding='utf-8-sig') self.readfp(fd) fd.close() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/fontmap.py������������������������������������������������������0000644�0000766�0000024�00000011500�12217202617�022116� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import os import sys import copy from collections import namedtuple from blockdiag.utils.config import ConfigParser from blockdiag.utils.compat import u def parse_fontpath(path): if path is None: return (None, None) match = re.search('^(.*):(\d)$', path) if match: return (match.group(1), int(match.group(2))) else: return (path, None) class FontInfo(object): def __init__(self, family, path, size): self.path = path self.size = int(size) family = self._parse(family) self.name = family[0] self.generic_family = family[1] self.weight = family[2] self.style = family[3] @property def familyname(self): if self.name: name = self.name + "-" else: name = '' if self.weight == 'bold': return "%s%s-%s" % (name, self.generic_family, self.weight) else: return "%s%s-%s" % (name, self.generic_family, self.style) def _parse(self, familyname): pattern = '^(?:(.*)-)?' + \ '(serif|sansserif|monospace|fantasy|cursive)' + \ '(?:-(normal|bold|italic|oblique))?$' match = re.search(pattern, familyname or '') if match is None: msg = 'Unknown font family: %s' % familyname raise AttributeError(msg) name = match.group(1) or '' generic_family = match.group(2) style = match.group(3) or '' if style == 'bold': weight = 'bold' style = 'normal' elif style in ('italic', 'oblique'): weight = 'normal' style = style else: weight = 'normal' style = 'normal' return [name, generic_family, weight, style] def duplicate(self): return copy.copy(self) class FontMap(object): BASE_FONTSIZE = 11 fontsize = BASE_FONTSIZE default_fontfamily = 'sansserif' def __init__(self, filename=None): self.fonts = {} self.aliases = {} if filename: self._parse_config(filename) self.set_default_font(None) def set_default_fontfamily(self, fontfamily): self.default_fontfamily = fontfamily self.set_default_font(None) def _parse_config(self, conffile): config = ConfigParser() if hasattr(conffile, 'read'): config.readfp(conffile) elif os.path.isfile(conffile): config.read(conffile) else: msg = "fontmap file is not found: %s" % conffile raise RuntimeError(msg) if config.has_section('fontmap'): for name, path in config.items('fontmap'): self.append_font(name, path) if config.has_section('fontalias'): for name, family in config.items('fontalias'): self.aliases[name] = family def set_default_font(self, path): if path is None and self.find() is not None: return self.append_font(self.default_fontfamily, path) def append_font(self, fontfamily, path): _path, _ = parse_fontpath(path) if path is None or os.path.isfile(_path): font = FontInfo(fontfamily, path, self.fontsize) self.fonts[font.familyname] = font else: msg = 'fontfile `%s` is not found: %s' % (fontfamily, path) sys.stderr.write("WARNING: %s\n" % msg) def _regulate_familyname(self, name): return FontInfo(name, None, self.BASE_FONTSIZE).familyname def find(self, element=None): fontfamily = getattr(element, 'fontfamily', None) or \ self.default_fontfamily fontfamily = self.aliases.get(fontfamily, fontfamily) fontsize = getattr(element, 'fontsize', None) or self.fontsize name = self._regulate_familyname(fontfamily) if name in self.fonts: font = self.fonts[name].duplicate() font.size = fontsize elif element is not None: msg = "Unknown fontfamily: %s" % fontfamily sys.stderr.write(u("WARNING: %s\n") % msg) elem = namedtuple('Font', 'fontsize')(fontsize) font = self.find(elem) else: font = None return font ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/images.py�������������������������������������������������������0000644�0000766�0000024�00000005460�12242633443�021732� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import re from blockdiag.utils import urlutil from blockdiag.utils.compat import string_types try: from PIL import Image except ImportError: class Image: @classmethod def open(cls, filename): return cls(filename) def __init__(self, filename): self.filename = filename @property def size(self): from blockdiag.utils import jpeg import png try: size = jpeg.JpegFile.get_size(self.filename) except: try: if isinstance(self.filename, string_types): content = open(self.filename, 'r') else: self.filename.seek(0) content = self.filename image = png.Reader(file=content).read() size = (image[0], image[1]) except: size = None if hasattr(self.filename, 'seek'): self.filename.seek(0) return size _image_size_cache = {} def get_image_size(filename): if filename not in _image_size_cache: uri = filename if urlutil.isurl(filename): try: from io import StringIO except ImportError: from cStringIO import StringIO import urllib try: uri = StringIO(urllib.urlopen(filename).read()) except: return None _image_size_cache[filename] = Image.open(uri).size return _image_size_cache[filename] def calc_image_size(size, bounded): if bounded[0] < size[0] or bounded[1] < size[1]: if (size[0] * 1.0 // bounded[0]) < (size[1] * 1.0 // bounded[1]): size = (size[0] * bounded[1] // size[1], bounded[1]) else: size = (bounded[0], size[1] * bounded[0] // size[0]) return size def color_to_rgb(color): import webcolors if color == 'none' or isinstance(color, (list, tuple)): rgb = color elif re.match('#', color): rgb = webcolors.hex_to_rgb(color) else: rgb = webcolors.name_to_rgb(color) return rgb ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/jpeg.py���������������������������������������������������������0000644�0000766�0000024�00000005107�12227702767�021421� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.utils.compat import string_types class StreamReader(object): def __init__(self, stream): self.stream = stream self.pos = 0 def read_byte(self): byte = self.stream[self.pos] self.pos += 1 return ord(byte) def read_word(self): byte1, byte2 = self.stream[self.pos:self.pos + 2] self.pos += 2 return (ord(byte1) << 8) + ord(byte2) def read_bytes(self, n): _bytes = self.stream[self.pos:self.pos + n] self.pos += n return _bytes class JpegHeaderReader(StreamReader): M_SOI = 0xd8 M_SOS = 0xda def read_marker(self): if self.read_byte() != 255: raise ValueError("error reading marker") return self.read_byte() def skip_marker(self): """Skip over an unknown or uninteresting variable-length marker""" length = self.read_word() self.read_bytes(length - 2) def __iter__(self): while True: if self.read_byte() != 255: raise ValueError("error reading marker") marker = self.read_byte() if marker == self.M_SOI: length = 0 data = '' else: length = self.read_word() data = self.read_bytes(length - 2) yield (marker, data) if marker == self.M_SOS: raise StopIteration() class JpegFile(object): M_SOF0 = 0xc0 M_SOF1 = 0xc1 @classmethod def get_size(self, filename): if isinstance(filename, string_types): image = open(filename, 'rb').read() else: image = filename.read() headers = JpegHeaderReader(image) for header in headers: if header[0] in (self.M_SOF0, self.M_SOF1): data = header[1] height = (ord(data[1]) << 8) + ord(data[2]) width = (ord(data[3]) << 8) + ord(data[4]) return (width, height) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/myitertools.py��������������������������������������������������0000644�0000766�0000024�00000002360�12227702767�023064� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from itertools import cycle def istep(seq, step=2): iterable = iter(seq) while True: yield [next(iterable) for _ in range(step)] def stepslice(iterable, steps): iterable = iter(iterable) step = cycle(steps) while True: # skip (1) n = next(step) if n == 0: pass elif n == 1: o = next(iterable) yield o yield o else: yield next(iterable) for _ in range(n - 2): next(iterable) yield next(iterable) # skip (2) for _ in range(next(step)): next(iterable) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/rst/������������������������������������������������������������0000755�0000766�0000024�00000000000�12242633535�020720� 5����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/rst/__init__.py�������������������������������������������������0000644�0000766�0000024�00000001144�11655736213�023035� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/rst/directives.py�����������������������������������������������0000644�0000766�0000024�00000025404�12224140066�023430� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import io from collections import namedtuple from docutils import nodes from docutils.parsers import rst from docutils.statemachine import ViewList from blockdiag import parser from blockdiag.builder import ScreenNodeBuilder from blockdiag.drawer import DiagramDraw from blockdiag.utils.bootstrap import create_fontmap from blockdiag.utils.compat import string_types from blockdiag.utils.rst.nodes import blockdiag directive_options_default = dict(format='PNG', antialias=False, fontpath=None, outputdir=None, nodoctype=False, noviewbox=False, inline_svg=False) directive_options = {} def relfn2path(env, filename): if filename.startswith('/') or filename.startswith(os.sep): relfn = filename[1:] else: path = env.doc2path(env.docname, base=None) relfn = os.path.join(os.path.dirname(path), filename) return relfn, os.path.join(env.srcdir, relfn) class BlockdiagDirectiveBase(rst.Directive): """ Directive to insert arbitrary dot markup. """ name = "blockdiag" node_class = blockdiag has_content = True required_arguments = 0 optional_arguments = 1 final_argument_whitespace = False option_spec = { 'alt': rst.directives.unchanged, 'caption': rst.directives.unchanged, 'desctable': rst.directives.flag, 'maxwidth': rst.directives.nonnegative_int, } def run(self): if self.arguments: document = self.state.document if self.content: msg = ('%s directive cannot have both content and ' 'a filename argument' % self.name) return [document.reporter.warning(msg, line=self.lineno)] try: filename = self.source_filename(self.arguments[0]) fp = io.open(filename, 'r', encoding='utf-8-sig') try: dotcode = fp.read() finally: fp.close() except (IOError, OSError): msg = 'External %s file %r not found or reading it failed' % \ (self.name, filename) return [document.reporter.warning(msg, line=self.lineno)] else: dotcode = '\n'.join(self.content).strip() if not dotcode: msg = 'Ignoring "%s" directive without content.' % self.name return [self.state_machine.reporter.warning(msg, line=self.lineno)] node = self.node_class() node['code'] = dotcode node['alt'] = self.options.get('alt') if 'caption' in self.options: node['caption'] = self.options.get('caption') node['options'] = {} if 'maxwidth' in self.options: node['options']['maxwidth'] = self.options['maxwidth'] if 'desctable' in self.options: node['options']['desctable'] = self.options['desctable'] return [node] def source_filename(self, filename): if hasattr(self.state.document.settings, 'env'): env = self.state.document.settings.env rel_filename, filename = relfn2path(env, self.arguments[0]) env.note_dependency(rel_filename) return filename class BlockdiagDirective(BlockdiagDirectiveBase): def run(self): results = super(BlockdiagDirective, self).run() node = results[0] if not isinstance(node, self.node_class): return results try: diagram = self.node2diagram(node) except Exception as e: raise self.warning(e.message) if 'desctable' in node['options']: del node['options']['desctable'] results += self.description_tables(diagram) results[0] = self.node2image(node, diagram) if 'caption' in node: fig = nodes.figure() fig += results[0] fig += nodes.caption(text=node['caption']) results[0] = fig return results @property def global_options(self): return directive_options def node2diagram(self, node): tree = parser.parse_string(node['code']) return ScreenNodeBuilder.build(tree) def node2image(self, node, diagram): options = node['options'] filename = self.image_filename(node) fontmap = self.create_fontmap() _format = self.global_options['format'].lower() if _format == 'svg' and self.global_options['inline_svg'] is True: filename = None kwargs = dict(self.global_options) del kwargs['format'] drawer = DiagramDraw(_format, diagram, filename, fontmap=fontmap, **kwargs) if filename is None or not os.path.isfile(filename): drawer.draw() content = drawer.save() if _format == 'svg' and self.global_options['inline_svg'] is True: size = drawer.pagesize() if 'maxwidth' in options and options['maxwidth'] < size[0]: ratio = float(options['maxwidth']) / size[0] new_size = (options['maxwidth'], int(size[1] * ratio)) content = drawer.save(new_size) return nodes.raw('', content, format='html') size = drawer.pagesize() if 'maxwidth' in options and options['maxwidth'] < size[0]: ratio = float(options['maxwidth']) / size[0] thumb_size = (options['maxwidth'], int(size[1] * ratio)) thumb_filename = self.image_filename(node, prefix='_thumb') if not os.path.isfile(thumb_filename): drawer.filename = thumb_filename drawer.draw() drawer.save(thumb_size) image = nodes.image(uri=thumb_filename, target=filename) else: image = nodes.image(uri=filename) if node['alt']: image['alt'] = node['alt'] return image def create_fontmap(self): Options = namedtuple('Options', 'font fontmap') fontpath = self.global_options['fontpath'] if isinstance(fontpath, (list, tuple)): options = Options(fontpath, None) elif isinstance(fontpath, string_types): options = Options([fontpath], None) else: options = Options([], None) return create_fontmap(options) def image_filename(self, node, prefix='', ext='png'): try: from hashlib import sha1 sha = sha1 except ImportError: from sha import sha sha = sha options = dict(node['options']) options.update(font=self.global_options['fontpath'], antialias=self.global_options['antialias']) hashseed = (node['code'] + str(options)).encode('utf-8') hashed = sha(hashseed).hexdigest() _format = self.global_options['format'] outputdir = self.global_options['outputdir'] filename = "%s%s-%s.%s" % (self.name, prefix, hashed, _format.lower()) if outputdir: filename = os.path.join(outputdir, filename) return filename def description_tables(self, diagram): tables = [] desctable = self.node_description_table(diagram) if desctable: tables.append(desctable) desctable = self.edge_description_table(diagram) if desctable: tables.append(desctable) return tables def node_description_table(self, diagram): nodes = diagram.traverse_nodes() klass = diagram._DiagramNode widths = [25] + [50] * (len(klass.desctable) - 1) headers = [klass.attrname[n] for n in klass.desctable] def node_number(node): try: return int(node[0]) except (TypeError, ValueError): return 65535 descriptions = [n.to_desctable() for n in nodes if n.drawable] descriptions.sort(key=node_number) for i in reversed(range(len(headers))): if any(desc[i] for desc in descriptions): pass else: widths.pop(i) headers.pop(i) for desc in descriptions: desc.pop(i) if len(headers) == 1: return None else: return self._description_table(descriptions, widths, headers) def edge_description_table(self, diagram): edges = diagram.traverse_edges() widths = [25, 50] headers = ['Name', 'Description'] descriptions = [e.to_desctable() for e in edges if e.style != 'none'] if any(desc[1] for desc in descriptions): return self._description_table(descriptions, widths, headers) else: return None def _description_table(self, descriptions, widths, headers): # generate table-root tgroup = nodes.tgroup(cols=len(widths)) for width in widths: tgroup += nodes.colspec(colwidth=width) table = nodes.table() table += tgroup # generate table-header thead = nodes.thead() row = nodes.row() for header in headers: entry = nodes.entry() entry += nodes.paragraph(text=header) row += entry thead += row tgroup += thead # generate table-body tbody = nodes.tbody() for desc in descriptions: row = nodes.row() for attr in desc: entry = nodes.entry() if not isinstance(attr, string_types): attr = str(attr) self.state.nested_parse(ViewList([attr], source=attr), 0, entry) row += entry tbody += row tgroup += tbody return table def setup(**kwargs): global directive_options, directive_options_default for key, value in directive_options_default.items(): directive_options[key] = kwargs.get(key, value) rst.directives.register_directive("blockdiag", BlockdiagDirective) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/rst/nodes.py����������������������������������������������������0000644�0000766�0000024�00000001272�12051075021�022370� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from docutils import nodes class blockdiag(nodes.General, nodes.Element): pass ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/urlutil.py������������������������������������������������������0000644�0000766�0000024�00000000414�12216056572�022162� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- try: import urlparse except ImportError: import urllib.parse as urlparse def isurl(url): o = urlparse.urlparse(url) accpetable = ["http", "https"] if o[0] in accpetable: return True else: return False ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag/utils/uuid.py���������������������������������������������������������0000644�0000766�0000024�00000001454�12231365410�021424� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. try: from uuid import uuid1 uuid = uuid1 except ImportError: from random import random uuid = random from blockdiag.utils.compat import u def generate(): return u(str(uuid())) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/�������������������������������������������������������������0000755�0000766�0000024�00000000000�12242633534�020441� 5����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/dependency_links.txt�����������������������������������������0000644�0000766�0000024�00000000001�12242633534�024507� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/entry_points.txt���������������������������������������������0000644�0000766�0000024�00000002723�12242633534�023743� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [console_scripts] blockdiag = blockdiag.command:main [blockdiag_noderenderer] box = blockdiag.noderenderer.box square = blockdiag.noderenderer.square roundedbox = blockdiag.noderenderer.roundedbox diamond = blockdiag.noderenderer.diamond minidiamond = blockdiag.noderenderer.minidiamond mail = blockdiag.noderenderer.mail note = blockdiag.noderenderer.note cloud = blockdiag.noderenderer.cloud circle = blockdiag.noderenderer.circle ellipse = blockdiag.noderenderer.ellipse beginpoint = blockdiag.noderenderer.beginpoint endpoint = blockdiag.noderenderer.endpoint actor = blockdiag.noderenderer.actor flowchart.database = blockdiag.noderenderer.flowchart.database flowchart.input = blockdiag.noderenderer.flowchart.input flowchart.loopin = blockdiag.noderenderer.flowchart.loopin flowchart.loopout = blockdiag.noderenderer.flowchart.loopout flowchart.terminator = blockdiag.noderenderer.flowchart.terminator textbox = blockdiag.noderenderer.textbox dots = blockdiag.noderenderer.dots none = blockdiag.noderenderer.none [blockdiag_plugins] attributes = blockdiag.plugins.attributes autoclass = blockdiag.plugins.autoclass [blockdiag_imagedrawers] imagedraw_png = blockdiag.imagedraw.png imagedraw_svg = blockdiag.imagedraw.svg imagedraw_pdf = blockdiag.imagedraw.pdf ���������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/PKG-INFO�����������������������������������������������������0000644�0000766�0000024�00000035477�12242633534�021556� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Metadata-Version: 1.0 Name: blockdiag Version: 1.3.2 Summary: blockdiag generates block-diagram image from text Home-page: http://blockdiag.com/ Author: Takeshi Komiya Author-email: i.tkomiya at gmail.com License: Apache License 2.0 Download-URL: http://pypi.python.org/pypi/blockdiag Description: `blockdiag` generate block-diagram image file from spec-text file. Features ======== * Generate block-diagram from dot like text (basic feature). * Multilingualization for node-label (utf-8 only). You can get some examples and generated images on `blockdiag.com `_ . Setup ===== by easy_install ---------------- Make environment:: $ easy_install blockdiag If you want to export as PDF format, give pdf arguments:: $ easy_install "blockdiag[pdf]" by buildout ------------ Make environment:: $ hg clone http://bitbucket.org/tk0miya/blockdiag $ cd blockdiag $ python bootstrap.py $ bin/buildout Copy and modify ini file. example:: $ cp /blockdiag/examples/simple.diag . $ vi simple.diag Please refer to `spec-text setting sample`_ section for the format of the `simpla.diag` configuration file. spec-text setting sample ======================== Few examples are available. You can get more examples at `blockdiag.com `_ . simple.diag ------------ simple.diag is simply define nodes and transitions by dot-like text format:: diagram admin { top_page -> config -> config_edit -> config_confirm -> top_page; } screen.diag ------------ screen.diag is more complexly sample. diaglam nodes have a alternative label and some transitions:: diagram admin { top_page [label = "Top page"]; foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; bar_detail [label = "Detail of BAR"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; logout; top_page -> foo_index; top_page -> bar_detail; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm -> foo_index; foo_index -> foo_edit -> foo_edit_confirm -> foo_index; foo_index -> foo_delete_confirm -> foo_index; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail; } Usage ===== Execute blockdiag command:: $ blockdiag simple.diag $ ls simple.png simple.png Requirements ============ * Python 2.6, 2.7, 3.2, 3.3 * Pillow 2.2.1 * funcparserlib 0.3.6 * setuptools License ======= Apache License 2.0 History ======= 1.3.2 (2013-11-19) ------------------ * Fix bugs 1.3.1 (2013-10-22) ------------------ * Fix bugs 1.3.0 (2013-10-05) ------------------ * Support python 3.2 and 3.3 (thanks to @masayuko) * Drop supports for python 2.4 and 2.5 * Replace dependency: PIL -> Pillow 1.2.4 (2012-11-21) ------------------ * Fix bugs 1.2.3 (2012-11-05) ------------------ * Fix bugs 1.2.2 (2012-10-28) ------------------ * Fix bugs 1.2.1 (2012-10-28) ------------------ * Add external imagedraw plugin supports * Add node attribute: label_orientation* * Fix bugs 1.2.0 (2012-10-22) ------------------ * Optimize algorithm for rendering shadow * Add options to docutils directive * Fix bugs 1.1.8 (2012-09-28) ------------------ * Add --ignore-pil option * Fix bugs 1.1.7 (2012-09-20) ------------------ * Add diagram attribute: shadow_style * Add font path for centos 6.2 * Add a setting 'antialias' in the configuration file * Fix bugs 1.1.6 (2012-06-06) ------------------ * Support for readthedocs.org * reST directive supports :caption: option * Fix bugs 1.1.5 (2012-04-22) ------------------ * Embed source code to SVG document as description * Fix bugs 1.1.4 (2012-03-15) ------------------ * Add new edge.hstyles: oneone, onemany, manyone, manymany * Add edge attribute: description (for build description-tables) * Fix bugs 1.1.3 (2012-02-13) ------------------ * Add new edge type for data-models (thanks to David Lang) * Add --no-transparency option * Fix bugs 1.1.2 (2011-12-26) ------------------ * Support font-index for TrueType Font Collections (.ttc file) * Allow to use reST syntax in descriptions of nodes * Fix bugs 1.1.1 (2011-11-27) ------------------ * Add node attribute: href (thanks to @r_rudi!) * Fix bugs 1.1.0 (2011-11-19) ------------------ * Add shape: square and circle * Add fontfamily attribute for switching fontface * Fix bugs 1.0.3 (2011-11-13) ------------------ * Add plugin: attributes * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value]) * Fix bugs 1.0.2 (2011-11-07) ------------------ * Fix bugs 1.0.1 (2011-11-06) ------------------ * Add group attribute: shape * Fix bugs 1.0.0 (2011-11-04) ------------------ * Add node attribute: linecolor * Rename diagram attributes: * fontsize -> default_fontsize * default_line_color -> default_linecolor * default_text_color -> default_textcolor * Add docutils extention * Fix bugs 0.9.7 (2011-11-01) ------------------ * Add node attribute: fontsize * Add edge attributes: thick, fontsize * Add group attribute: fontsize * Change color of shadow in PDF mode * Add class feature (experimental) * Add handler-plugin framework (experimental) 0.9.6 (2011-10-22) ------------------ * node.style supports dashed_array format style * Fix bugs 0.9.5 (2011-10-19) ------------------ * Add node attributes: width and height * Fix bugs 0.9.4 (2011-10-07) ------------------ * Fix bugs 0.9.3 (2011-10-06) ------------------ * Replace SVG core by original's (simplesvg.py) * Refactored * Fix bugs 0.9.2 (2011-09-30) ------------------ * Add node attribute: textcolor * Add group attribute: textcolor * Add edge attribute: textcolor * Add diagram attributes: default_text_attribute * Fix beginpoint shape and endpoint shape were reversed * Fix bugs 0.9.1 (2011-09-26) ------------------ * Add diagram attributes: default_node_color, default_group_color and default_line_color * Fix bugs 0.9.0 (2011-09-25) ------------------ * Add icon attribute to node * Make transparency to background of PNG images * Fix bugs 0.8.9 (2011-08-09) ------------------ * Fix bugs 0.8.8 (2011-08-08) ------------------ * Fix bugs 0.8.7 (2011-08-06) ------------------ * Fix bugs 0.8.6 (2011-08-01) ------------------ * Support Pillow as replacement of PIL (experimental) * Fix bugs 0.8.5 (2011-07-31) ------------------ * Allow dot characters in node_id * Fix bugs 0.8.4 (2011-07-05) ------------------ * Fix bugs 0.8.3 (2011-07-03) ------------------ * Support input from stdin * Fix bugs 0.8.2 (2011-06-29) ------------------ * Add node.stacked * Add node shapes: dots, none * Add hiragino-font to font search list * Support background image fetching from web * Add diagram.edge_layout (experimental) * Fix bugs 0.8.1 (2011-05-14) ------------------ * Change license to Apache License 2.0 * Fix bugs 0.8.0 (2011-05-04) ------------------ * Add --separate option and --version option * Fix bugs 0.7.8 (2011-04-19) ------------------ * Update layout engine * Update requirements: PIL >= 1.1.5 * Update parser for tokenize performance * Add --nodoctype option * Fix bugs * Add many testcases 0.7.7 (2011-03-29) ------------------ * Fix bugs 0.7.6 (2011-03-26) ------------------ * Add new layout manager for portrait edges * Fix bugs 0.7.5 (2011-03-20) ------------------ * Support multiple nodes relations (cf. A -> B, C) * Support node group declaration at attribute of nodes * Fix bugs 0.7.4 (2011-03-08) ------------------ * Fix bugs 0.7.3 (2011-03-02) ------------------ * Use UTF-8 characters as Name token (by @swtw7466) * Fix htmlentities included in labels was not escaped on SVG images * Fix bugs 0.7.2 (2011-02-28) ------------------ * Add default_shape attribute to diagram 0.7.1 (2011-02-27) ------------------ * Fix edge has broken with antialias option 0.7.0 (2011-02-25) ------------------ * Support node shape 0.6.7 (2011-02-12) ------------------ * Change noderenderer interface to new style * Render dashed ellipse more clearly (contributed by @cocoatomo) * Support PDF exporting 0.6.6 (2011-01-31) ------------------ * Support diagram.shape_namespace * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor * Support plug-in structure to install node shapes * Fix bugs 0.6.5 (2011-01-18) ------------------ * Support node shape (experimental) 0.6.4 (2011-01-17) ------------------ * Fix bugs 0.6.3 (2011-01-15) ------------------ * Fix bugs 0.6.2 (2011-01-08) ------------------ * Fix bugs 0.6.1 (2011-01-07) ------------------ * Implement 'folded' attribute for edge * Refactor layout engine 0.6 (2011-01-02) ------------------ * Support nested groups. 0.5.5 (2010-12-24) ------------------ * Specify direction of edges as syntax (->, --, <-, <->) * Fix bugs. 0.5.4 (2010-12-23) ------------------ * Remove debug codes. 0.5.3 (2010-12-23) ------------------ * Support NodeGroup.label. * Implement --separate option (experimental) * Fix right-up edge overrapped on other nodes. * Support configration file: .blockdiagrc 0.5.2 (2010-11-06) ------------------ * Fix unicode errors for UTF-8'ed SVG exportion. * Refactoring codes for running on GAE. 0.5.1 (2010-10-26) ------------------ * Fix license text on diagparser.py * Update layout engine. 0.5 (2010-10-15) ------------------ * Support background-image of node (SVG) * Support labels for edge. * Fix bugs. 0.4.2 (2010-10-10) ------------------ * Support background-color of node groups. * Draw edge has jumped at edge's cross-points. * Fix bugs. 0.4.1 (2010-10-07) ------------------ * Fix bugs. 0.4 (2010-10-07) ------------------ * Support SVG exporting. * Support dashed edge drawing. * Support background image of nodes (PNG only) 0.3.1 (2010-09-29) ------------------ * Fasten anti-alias process. * Fix text was broken on windows. 0.3 (2010-09-26) ------------------ * Add --antialias option. * Fix bugs. 0.2.2 (2010-09-25) ------------------ * Fix edge bugs. 0.2.1 (2010-09-25) ------------------ * Fix bugs. * Fix package style. 0.2 (2010-09-23) ------------------ * Update layout engine. * Support group { ... } sentence for create Node-Groups. * Support numbered badge on node (cf. A [numbered = 5]) 0.1 (2010-09-20) ----------------- * first release Keywords: diagram,generator Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Text Processing :: Markup �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/requires.txt�������������������������������������������������0000644�0000766�0000024�00000000141�12242633534�023035� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������setuptools funcparserlib webcolors Pillow [rst] docutils [pdf] reportlab [test] Nose pep8>=1.3�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/SOURCES.txt��������������������������������������������������0000644�0000766�0000024�00000025044�12242633534�022332� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������LICENSE MANIFEST.in README.rst blockdiag.1 bootstrap.py buildout.cfg setup.cfg setup.py tox.ini examples/blockdiagrc examples/group.diag examples/group.png examples/group.svg examples/multibyte.diag examples/multibyte.png examples/multibyte.svg examples/numbered.diag examples/numbered.png examples/numbered.svg examples/screen.diag examples/screen.png examples/screen.svg examples/simple.diag examples/simple.png examples/simple.svg src/blockdiag_sphinxhelper.py src/blockdiag/__init__.py src/blockdiag/builder.py src/blockdiag/command.py src/blockdiag/drawer.py src/blockdiag/elements.py src/blockdiag/metrics.py src/blockdiag/parser.py src/blockdiag.egg-info/PKG-INFO src/blockdiag.egg-info/SOURCES.txt src/blockdiag.egg-info/dependency_links.txt src/blockdiag.egg-info/entry_points.txt src/blockdiag.egg-info/requires.txt src/blockdiag.egg-info/top_level.txt src/blockdiag/imagedraw/__init__.py src/blockdiag/imagedraw/base.py src/blockdiag/imagedraw/pdf.py src/blockdiag/imagedraw/png.py src/blockdiag/imagedraw/simplesvg.py src/blockdiag/imagedraw/svg.py src/blockdiag/imagedraw/textfolder.py src/blockdiag/imagedraw/filters/__init__.py src/blockdiag/imagedraw/filters/linejump.py src/blockdiag/imagedraw/utils/__init__.py src/blockdiag/imagedraw/utils/ellipse.py src/blockdiag/noderenderer/__init__.py src/blockdiag/noderenderer/actor.py src/blockdiag/noderenderer/beginpoint.py src/blockdiag/noderenderer/box.py src/blockdiag/noderenderer/circle.py src/blockdiag/noderenderer/cloud.py src/blockdiag/noderenderer/diamond.py src/blockdiag/noderenderer/dots.py src/blockdiag/noderenderer/ellipse.py src/blockdiag/noderenderer/endpoint.py src/blockdiag/noderenderer/mail.py src/blockdiag/noderenderer/minidiamond.py src/blockdiag/noderenderer/none.py src/blockdiag/noderenderer/note.py src/blockdiag/noderenderer/roundedbox.py src/blockdiag/noderenderer/square.py src/blockdiag/noderenderer/textbox.py src/blockdiag/noderenderer/flowchart/__init__.py src/blockdiag/noderenderer/flowchart/database.py src/blockdiag/noderenderer/flowchart/input.py src/blockdiag/noderenderer/flowchart/loopin.py src/blockdiag/noderenderer/flowchart/loopout.py src/blockdiag/noderenderer/flowchart/terminator.py src/blockdiag/plugins/__init__.py src/blockdiag/plugins/attributes.py src/blockdiag/plugins/autoclass.py src/blockdiag/tests/__init__.py src/blockdiag/tests/test_boot_params.py src/blockdiag/tests/test_builder.py src/blockdiag/tests/test_builder_edge.py src/blockdiag/tests/test_builder_errors.py src/blockdiag/tests/test_builder_group.py src/blockdiag/tests/test_builder_node.py src/blockdiag/tests/test_builder_separate.py src/blockdiag/tests/test_generate_diagram.py src/blockdiag/tests/test_imagedraw_textfolder.py src/blockdiag/tests/test_imagedraw_utils.py src/blockdiag/tests/test_parser.py src/blockdiag/tests/test_pep8.py src/blockdiag/tests/test_rst_directives.py src/blockdiag/tests/test_utils_fontmap.py src/blockdiag/tests/utils.py src/blockdiag/tests/diagrams/auto_jumping_edge.diag src/blockdiag/tests/diagrams/background_url_image.diag src/blockdiag/tests/diagrams/beginpoint_color.diag src/blockdiag/tests/diagrams/branched.diag src/blockdiag/tests/diagrams/circular_ref.diag src/blockdiag/tests/diagrams/circular_ref2.diag src/blockdiag/tests/diagrams/circular_ref_and_parent_node.diag src/blockdiag/tests/diagrams/circular_ref_to_root.diag src/blockdiag/tests/diagrams/circular_skipped_edge.diag src/blockdiag/tests/diagrams/define_class.diag src/blockdiag/tests/diagrams/diagram_attributes.diag src/blockdiag/tests/diagrams/diagram_attributes_order.diag src/blockdiag/tests/diagrams/diagram_orientation.diag src/blockdiag/tests/diagrams/edge_attribute.diag src/blockdiag/tests/diagrams/edge_datamodels.diag src/blockdiag/tests/diagrams/edge_label.diag src/blockdiag/tests/diagrams/edge_layout_landscape.diag src/blockdiag/tests/diagrams/edge_layout_portrait.diag src/blockdiag/tests/diagrams/edge_shape.diag src/blockdiag/tests/diagrams/edge_styles.diag src/blockdiag/tests/diagrams/empty_group.diag src/blockdiag/tests/diagrams/empty_group_declaration.diag src/blockdiag/tests/diagrams/empty_nested_group.diag src/blockdiag/tests/diagrams/endpoint_color.diag src/blockdiag/tests/diagrams/flowable_node.diag src/blockdiag/tests/diagrams/folded_edge.diag src/blockdiag/tests/diagrams/group_and_skipped_edge.diag src/blockdiag/tests/diagrams/group_attribute.diag src/blockdiag/tests/diagrams/group_children_height.diag src/blockdiag/tests/diagrams/group_children_order.diag src/blockdiag/tests/diagrams/group_children_order2.diag src/blockdiag/tests/diagrams/group_children_order3.diag src/blockdiag/tests/diagrams/group_children_order4.diag src/blockdiag/tests/diagrams/group_declare_as_node_attribute.diag src/blockdiag/tests/diagrams/group_height.diag src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag src/blockdiag/tests/diagrams/group_label.diag src/blockdiag/tests/diagrams/group_order.diag src/blockdiag/tests/diagrams/group_order2.diag src/blockdiag/tests/diagrams/group_order3.diag src/blockdiag/tests/diagrams/group_orientation.diag src/blockdiag/tests/diagrams/group_sibling.diag src/blockdiag/tests/diagrams/group_works_node_decorator.diag src/blockdiag/tests/diagrams/labeled_circular_ref.diag src/blockdiag/tests/diagrams/large_group_and_node.diag src/blockdiag/tests/diagrams/large_group_and_node2.diag src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag src/blockdiag/tests/diagrams/merge_groups.diag src/blockdiag/tests/diagrams/multiple_groups.diag src/blockdiag/tests/diagrams/multiple_nested_groups.diag src/blockdiag/tests/diagrams/multiple_node_relation.diag src/blockdiag/tests/diagrams/multiple_nodes_definition.diag src/blockdiag/tests/diagrams/multiple_parent_node.diag src/blockdiag/tests/diagrams/nested_group_orientation.diag src/blockdiag/tests/diagrams/nested_group_orientation2.diag src/blockdiag/tests/diagrams/nested_groups.diag src/blockdiag/tests/diagrams/nested_groups_and_edges.diag src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag src/blockdiag/tests/diagrams/nested_skipped_circular.diag src/blockdiag/tests/diagrams/node_attribute.diag src/blockdiag/tests/diagrams/node_attribute_and_group.diag src/blockdiag/tests/diagrams/node_has_multilined_label.diag src/blockdiag/tests/diagrams/node_height.diag src/blockdiag/tests/diagrams/node_icon.diag src/blockdiag/tests/diagrams/node_id_includes_dot.diag src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag src/blockdiag/tests/diagrams/node_link.diag src/blockdiag/tests/diagrams/node_rotated_labels.diag src/blockdiag/tests/diagrams/node_shape.diag src/blockdiag/tests/diagrams/node_shape_background.diag src/blockdiag/tests/diagrams/node_shape_namespace.diag src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag src/blockdiag/tests/diagrams/node_style_dasharray.diag src/blockdiag/tests/diagrams/node_styles.diag src/blockdiag/tests/diagrams/node_width_and_height.diag src/blockdiag/tests/diagrams/non_rhombus_relation_height.diag src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag src/blockdiag/tests/diagrams/plugin_attributes.diag src/blockdiag/tests/diagrams/plugin_autoclass.diag src/blockdiag/tests/diagrams/portrait_dots.diag src/blockdiag/tests/diagrams/quoted_node_id.diag src/blockdiag/tests/diagrams/reverse_multiple_groups.diag src/blockdiag/tests/diagrams/rhombus_relation_height.diag src/blockdiag/tests/diagrams/self_ref.diag src/blockdiag/tests/diagrams/separate1.diag src/blockdiag/tests/diagrams/separate2.diag src/blockdiag/tests/diagrams/simple_group.diag src/blockdiag/tests/diagrams/single_edge.diag src/blockdiag/tests/diagrams/single_node.diag src/blockdiag/tests/diagrams/skipped_circular.diag src/blockdiag/tests/diagrams/skipped_edge.diag src/blockdiag/tests/diagrams/skipped_edge_down.diag src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown2.diag src/blockdiag/tests/diagrams/skipped_edge_leftdown.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_down.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown2.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_leftdown.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_right.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_right.diag src/blockdiag/tests/diagrams/skipped_edge_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_rightup.diag src/blockdiag/tests/diagrams/skipped_edge_up.diag src/blockdiag/tests/diagrams/skipped_twin_circular.diag src/blockdiag/tests/diagrams/slided_children.diag src/blockdiag/tests/diagrams/stacked_node_and_edges.diag src/blockdiag/tests/diagrams/triple_branched.diag src/blockdiag/tests/diagrams/twin_circular_ref.diag src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag src/blockdiag/tests/diagrams/twin_forked.diag src/blockdiag/tests/diagrams/twin_multiple_parent_node.diag src/blockdiag/tests/diagrams/two_edges.diag src/blockdiag/tests/diagrams/white.gif src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag src/blockdiag/tests/diagrams/errors/group_follows_node.diag src/blockdiag/tests/diagrams/errors/lexer_error.diag src/blockdiag/tests/diagrams/errors/node_follows_group.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_default_shape.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_edge_layout.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_orientation.diag src/blockdiag/tests/diagrams/errors/unknown_edge_class.diag src/blockdiag/tests/diagrams/errors/unknown_edge_dir.diag src/blockdiag/tests/diagrams/errors/unknown_edge_hstyle.diag src/blockdiag/tests/diagrams/errors/unknown_edge_style.diag src/blockdiag/tests/diagrams/errors/unknown_group_class.diag src/blockdiag/tests/diagrams/errors/unknown_group_orientation.diag src/blockdiag/tests/diagrams/errors/unknown_group_shape.diag src/blockdiag/tests/diagrams/errors/unknown_node_attribute.diag src/blockdiag/tests/diagrams/errors/unknown_node_class.diag src/blockdiag/tests/diagrams/errors/unknown_node_shape.diag src/blockdiag/tests/diagrams/errors/unknown_node_style.diag src/blockdiag/tests/diagrams/errors/unknown_plugin.diag src/blockdiag/utils/__init__.py src/blockdiag/utils/bootstrap.py src/blockdiag/utils/compat.py src/blockdiag/utils/config.py src/blockdiag/utils/fontmap.py src/blockdiag/utils/images.py src/blockdiag/utils/jpeg.py src/blockdiag/utils/myitertools.py src/blockdiag/utils/urlutil.py src/blockdiag/utils/uuid.py src/blockdiag/utils/rst/__init__.py src/blockdiag/utils/rst/directives.py src/blockdiag/utils/rst/nodes.py��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag.egg-info/top_level.txt������������������������������������������������0000644�0000766�0000024�00000000041�12242633534�023166� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag_sphinxhelper blockdiag �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.3.2/src/blockdiag_sphinxhelper.py�������������������������������������������������������0000644�0000766�0000024�00000001640�12227702770�022115� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __all__ = [ 'core', 'utils' ] import blockdiag.parser import blockdiag.builder import blockdiag.drawer core = blockdiag import blockdiag.utils.bootstrap import blockdiag.utils.compat import blockdiag.utils.fontmap import blockdiag.utils.rst.nodes import blockdiag.utils.rst.directives utils = blockdiag.utils ������������������������������������������������������������������������������������������������blockdiag-1.3.2/tox.ini�����������������������������������������������������������������������������0000644�0000766�0000024�00000000322�12227702770�015553� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[tox] envlist=py26,py27,py32,py33 [testenv] deps= nose pep8 flake8 docutils commands= nosetests flake8 src [testenv:py26] deps= nose pep8 flake8 docutils unittest2 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������