1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357 |
- /// @file
- /// @brief [node shapes](https://graphviz.org/doc/info/shapes.html)
- /// @ingroup common_render
- /*************************************************************************
- * Copyright (c) 2011 AT&T Intellectual Property
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors: Details at https://graphviz.org
- *************************************************************************/
- #include <assert.h>
- #include <cgraph/gv_math.h>
- #include <common/render.h>
- #include <common/htmltable.h>
- #include <float.h>
- #include <limits.h>
- #include <math.h>
- #include <stddef.h>
- #include <stdbool.h>
- #include <string.h>
- #include <util/alloc.h>
- #include <util/streq.h>
- #include <util/unreachable.h>
- #define RBCONST 12
- #define RBCURVE .5
- typedef struct {
- pointf (*size_gen) (pointf);
- void (*vertex_gen) (pointf*, pointf*);
- } poly_desc_t;
- static port Center = {.theta = -1, .clip = true};
- #define ATTR_SET(a,n) ((a) && (*(agxget(n,a->index)) != '\0'))
- /* Default point size = 0.05 inches or 3.6 points */
- #define DEF_POINT 0.05
- /* Minimum point size = 0.0003 inches or 0.02 points
- * This will make the radius 0.01 points, which is the smallest
- * non-zero number output by gvprintdouble in gvdevice.c
- */
- #define MIN_POINT 0.0003
- /* extra null character needed to avoid style emitter from thinking
- * there are arguments.
- */
- static char *point_style[3] = { "invis\0", "filled\0", 0 };
- /* forward declarations of functions used in shapes tables */
- static void poly_init(node_t * n);
- static void poly_free(node_t * n);
- static port poly_port(node_t * n, char *portname, char *);
- static bool poly_inside(inside_t * inside_context, pointf p);
- static int poly_path(node_t * n, port * p, int side, boxf rv[], int *kptr);
- static void poly_gencode(GVJ_t * job, node_t * n);
- static void record_init(node_t * n);
- static void record_free(node_t * n);
- static port record_port(node_t * n, char *portname, char *);
- static bool record_inside(inside_t * inside_context, pointf p);
- static int record_path(node_t * n, port * p, int side, boxf rv[],
- int *kptr);
- static void record_gencode(GVJ_t * job, node_t * n);
- static void point_init(node_t * n);
- static void point_gencode(GVJ_t * job, node_t * n);
- static bool point_inside(inside_t * inside_context, pointf p);
- static bool epsf_inside(inside_t * inside_context, pointf p);
- static void epsf_gencode(GVJ_t * job, node_t * n);
- static pointf star_size (pointf);
- static void star_vertices (pointf*, pointf*);
- static bool star_inside(inside_t * inside_context, pointf p);
- static poly_desc_t star_gen = {
- star_size,
- star_vertices,
- };
- static pointf cylinder_size (pointf);
- static void cylinder_vertices (pointf*, pointf*);
- static void cylinder_draw(GVJ_t *job, pointf *AF, size_t sides, int filled);
- static poly_desc_t cylinder_gen = {
- cylinder_size,
- cylinder_vertices,
- };
- /* polygon descriptions. "polygon" with 0 sides takes all user control */
- /* regul perip sides orien disto skew */
- static polygon_t p_polygon = {.peripheries = 1};
- /* builtin polygon descriptions */
- static polygon_t p_ellipse = {.peripheries = 1, .sides = 1};
- static polygon_t p_circle = {.regular = true, .peripheries = 1, .sides = 1};
- static polygon_t p_egg = {.peripheries = 1, .sides = 1, .distortion = -0.3};
- static polygon_t p_triangle = {.peripheries = 1, .sides = 3};
- static polygon_t p_box = {.peripheries = 1, .sides = 4};
- static polygon_t p_square = {.regular = true, .peripheries = 1, .sides = 4};
- static polygon_t p_plaintext = {.sides = 4};
- static polygon_t p_plain = {.sides = 4};
- static polygon_t p_diamond = {.peripheries = 1, .sides = 4, .orientation = 45.0};
- static polygon_t p_trapezium = {.peripheries = 1, .sides = 4, .distortion = -0.4};
- static polygon_t p_parallelogram = {.peripheries = 1, .sides = 4, .skew = 0.6};
- static polygon_t p_house = {.peripheries = 1, .sides = 5, .distortion = -0.64};
- static polygon_t p_pentagon = {.peripheries = 1, .sides = 5};
- static polygon_t p_hexagon = {.peripheries = 1, .sides = 6};
- static polygon_t p_septagon = {.peripheries = 1, .sides = 7};
- static polygon_t p_octagon = {.peripheries = 1, .sides = 8};
- static polygon_t p_note = {
- .peripheries = 1, .sides = 4, .option = {.shape = DOGEAR}};
- static polygon_t p_tab = {
- .peripheries = 1, .sides = 4, .option = {.shape = TAB}};
- static polygon_t p_folder = {
- .peripheries = 1, .sides = 4, .option = {.shape = FOLDER}};
- static polygon_t p_box3d = {
- .peripheries = 1, .sides = 4, .option = {.shape = BOX3D}};
- static polygon_t p_component = {
- .peripheries = 1, .sides = 4, .option = {.shape = COMPONENT}};
- static polygon_t p_underline = {
- .peripheries = 1, .sides = 4, .option = {.underline = true}};
- static polygon_t p_cylinder = {.peripheries = 1,
- .sides = 19,
- .option = {.shape = CYLINDER},
- .vertices = (pointf *)&cylinder_gen};
- /* redundant and undocumented builtin polygons */
- static polygon_t p_doublecircle = {
- .regular = true, .peripheries = 2, .sides = 1};
- static polygon_t p_invtriangle = {
- .peripheries = 1, .sides = 3, .orientation = 180.0};
- static polygon_t p_invtrapezium = {
- .peripheries = 1, .sides = 4, .orientation = 180.0, .distortion = -0.4};
- static polygon_t p_invhouse = {
- .peripheries = 1, .sides = 5, .orientation = 180.0, .distortion = -0.64};
- static polygon_t p_doubleoctagon = {.peripheries = 2, .sides = 8};
- static polygon_t p_tripleoctagon = {.peripheries = 3, .sides = 8};
- static polygon_t p_Mdiamond = {
- .peripheries = 1,
- .sides = 4,
- .orientation = 45.0,
- .option = {.diagonals = true, .auxlabels = true}};
- static polygon_t p_Msquare = {.regular = true,
- .peripheries = 1,
- .sides = 4,
- .option = {.diagonals = true}};
- static polygon_t p_Mcircle = {.regular = true,
- .peripheries = 1,
- .sides = 1,
- .option = {.diagonals = true, .auxlabels = true}};
- /* non-convex polygons */
- static polygon_t p_star = {
- .peripheries = 1, .sides = 10, .vertices = (pointf *)&star_gen};
- /* biological circuit shapes, as specified by SBOLv*/
- /** gene expression symbols **/
- static polygon_t p_promoter = {
- .peripheries = 1, .sides = 4, .option = {.shape = PROMOTER}};
- static polygon_t p_cds = {
- .peripheries = 1, .sides = 4, .option = {.shape = CDS}};
- static polygon_t p_terminator = {
- .peripheries = 1, .sides = 4, .option = {.shape = TERMINATOR}};
- static polygon_t p_utr = {
- .peripheries = 1, .sides = 4, .option = {.shape = UTR}};
- static polygon_t p_insulator = {
- .peripheries = 1, .sides = 4, .option = {.shape = INSULATOR}};
- static polygon_t p_ribosite = {
- .peripheries = 1, .sides = 4, .option = {.shape = RIBOSITE}};
- static polygon_t p_rnastab = {
- .peripheries = 1, .sides = 4, .option = {.shape = RNASTAB}};
- static polygon_t p_proteasesite = {
- .peripheries = 1, .sides = 4, .option = {.shape = PROTEASESITE}};
- static polygon_t p_proteinstab = {
- .peripheries = 1, .sides = 4, .option = {.shape = PROTEINSTAB}};
- /** dna construction symbols **/
- static polygon_t p_primersite = {
- .peripheries = 1, .sides = 4, .option = {.shape = PRIMERSITE}};
- static polygon_t p_restrictionsite = {
- .peripheries = 1, .sides = 4, .option = {.shape = RESTRICTIONSITE}};
- static polygon_t p_fivepoverhang = {
- .peripheries = 1, .sides = 4, .option = {.shape = FIVEPOVERHANG}};
- static polygon_t p_threepoverhang = {
- .peripheries = 1, .sides = 4, .option = {.shape = THREEPOVERHANG}};
- static polygon_t p_noverhang = {
- .peripheries = 1, .sides = 4, .option = {.shape = NOVERHANG}};
- static polygon_t p_assembly = {
- .peripheries = 1, .sides = 4, .option = {.shape = ASSEMBLY}};
- static polygon_t p_signature = {
- .peripheries = 1, .sides = 4, .option = {.shape = SIGNATURE}};
- static polygon_t p_rpromoter = {
- .peripheries = 1, .sides = 4, .option = {.shape = RPROMOTER}};
- static polygon_t p_rarrow = {
- .peripheries = 1, .sides = 4, .option = {.shape = RARROW}};
- static polygon_t p_larrow = {
- .peripheries = 1, .sides = 4, .option = {.shape = LARROW}};
- static polygon_t p_lpromoter = {
- .peripheries = 1, .sides = 4, .option = {.shape = LPROMOTER}};
- static bool IS_BOX(node_t *n) {
- return ND_shape(n)->polygon == &p_box;
- }
- static bool IS_PLAIN(node_t *n) {
- return ND_shape(n)->polygon == &p_plain;
- }
- /// True if style requires processing through round_corners.
- static bool SPECIAL_CORNERS(graphviz_polygon_style_t style) {
- return style.rounded || style.diagonals || style.shape != 0;
- }
- /*
- * every shape has these functions:
- *
- * void SHAPE_init(node_t *n)
- * initialize the shape (usually at least its size).
- * void SHAPE_free(node_t *n)
- * free all memory used by the shape
- * port SHAPE_port(node_t *n, char *portname)
- * return the aiming point and slope (if constrained)
- * of a port.
- * int SHAPE_inside(inside_t *inside_context, pointf p, edge_t *e);
- * test if point is inside the node shape which is
- * assumed convex.
- * the point is relative to the node center. the edge
- * is passed in case the port affects spline clipping.
- * int SHAPE_path(node *n, edge_t *e, int pt, boxf path[], int *nbox)
- * create a path for the port of e that touches n,
- * return side
- * void SHAPE_gencode(GVJ_t *job, node_t *n)
- * generate graphics code for a node.
- *
- * some shapes, polygons in particular, use additional shape control data *
- *
- */
- static shape_functions poly_fns = {
- poly_init,
- poly_free,
- poly_port,
- poly_inside,
- poly_path,
- poly_gencode
- };
- static shape_functions point_fns = {
- point_init,
- poly_free,
- poly_port,
- point_inside,
- NULL,
- point_gencode
- };
- static shape_functions record_fns = {
- record_init,
- record_free,
- record_port,
- record_inside,
- record_path,
- record_gencode
- };
- static shape_functions epsf_fns = {
- epsf_init,
- epsf_free,
- poly_port,
- epsf_inside,
- NULL,
- epsf_gencode
- };
- static shape_functions star_fns = {
- poly_init,
- poly_free,
- poly_port,
- star_inside,
- poly_path,
- poly_gencode
- };
- static shape_functions cylinder_fns = {
- poly_init,
- poly_free,
- poly_port,
- poly_inside,
- poly_path,
- poly_gencode
- };
- static shape_desc Shapes[] = { /* first entry is default for no such shape */
- {.name = "box", .fns = &poly_fns, .polygon = &p_box},
- {.name = "polygon", .fns = &poly_fns, .polygon = &p_polygon},
- {.name = "ellipse", .fns = &poly_fns, .polygon = &p_ellipse},
- {.name = "oval", .fns = &poly_fns, .polygon = &p_ellipse},
- {.name = "circle", .fns = &poly_fns, .polygon = &p_circle},
- {.name = "point", .fns = &point_fns, .polygon = &p_circle},
- {.name = "egg", .fns = &poly_fns, .polygon = &p_egg},
- {.name = "triangle", .fns = &poly_fns, .polygon = &p_triangle},
- {.name = "none", .fns = &poly_fns, .polygon = &p_plaintext},
- {.name = "plaintext", .fns = &poly_fns, .polygon = &p_plaintext},
- {.name = "plain", .fns = &poly_fns, .polygon = &p_plain},
- {.name = "diamond", .fns = &poly_fns, .polygon = &p_diamond},
- {.name = "trapezium", .fns = &poly_fns, .polygon = &p_trapezium},
- {.name = "parallelogram", .fns = &poly_fns, .polygon = &p_parallelogram},
- {.name = "house", .fns = &poly_fns, .polygon = &p_house},
- {.name = "pentagon", .fns = &poly_fns, .polygon = &p_pentagon},
- {.name = "hexagon", .fns = &poly_fns, .polygon = &p_hexagon},
- {.name = "septagon", .fns = &poly_fns, .polygon = &p_septagon},
- {.name = "octagon", .fns = &poly_fns, .polygon = &p_octagon},
- {.name = "note", .fns = &poly_fns, .polygon = &p_note},
- {.name = "tab", .fns = &poly_fns, .polygon = &p_tab},
- {.name = "folder", .fns = &poly_fns, .polygon = &p_folder},
- {.name = "box3d", .fns = &poly_fns, .polygon = &p_box3d},
- {.name = "component", .fns = &poly_fns, .polygon = &p_component},
- {.name = "cylinder", .fns = &cylinder_fns, .polygon = &p_cylinder},
- {.name = "rect", .fns = &poly_fns, .polygon = &p_box},
- {.name = "rectangle", .fns = &poly_fns, .polygon = &p_box},
- {.name = "square", .fns = &poly_fns, .polygon = &p_square},
- {.name = "doublecircle", .fns = &poly_fns, .polygon = &p_doublecircle},
- {.name = "doubleoctagon", .fns = &poly_fns, .polygon = &p_doubleoctagon},
- {.name = "tripleoctagon", .fns = &poly_fns, .polygon = &p_tripleoctagon},
- {.name = "invtriangle", .fns = &poly_fns, .polygon = &p_invtriangle},
- {.name = "invtrapezium", .fns = &poly_fns, .polygon = &p_invtrapezium},
- {.name = "invhouse", .fns = &poly_fns, .polygon = &p_invhouse},
- {.name = "underline", .fns = &poly_fns, .polygon = &p_underline},
- {.name = "Mdiamond", .fns = &poly_fns, .polygon = &p_Mdiamond},
- {.name = "Msquare", .fns = &poly_fns, .polygon = &p_Msquare},
- {.name = "Mcircle", .fns = &poly_fns, .polygon = &p_Mcircle},
- /* biological circuit shapes, as specified by SBOLv*/
- /** gene expression symbols **/
- {.name = "promoter", .fns = &poly_fns, .polygon = &p_promoter},
- {.name = "cds", .fns = &poly_fns, .polygon = &p_cds},
- {.name = "terminator", .fns = &poly_fns, .polygon = &p_terminator},
- {.name = "utr", .fns = &poly_fns, .polygon = &p_utr},
- {.name = "insulator", .fns = &poly_fns, .polygon = &p_insulator},
- {.name = "ribosite", .fns = &poly_fns, .polygon = &p_ribosite},
- {.name = "rnastab", .fns = &poly_fns, .polygon = &p_rnastab},
- {.name = "proteasesite", .fns = &poly_fns, .polygon = &p_proteasesite},
- {.name = "proteinstab", .fns = &poly_fns, .polygon = &p_proteinstab},
- /** dna construction symbols **/
- {.name = "primersite", .fns = &poly_fns, .polygon = &p_primersite},
- {.name = "restrictionsite", .fns = &poly_fns, .polygon = &p_restrictionsite},
- {.name = "fivepoverhang", .fns = &poly_fns, .polygon = &p_fivepoverhang},
- {.name = "threepoverhang", .fns = &poly_fns, .polygon = &p_threepoverhang},
- {.name = "noverhang", .fns = &poly_fns, .polygon = &p_noverhang},
- {.name = "assembly", .fns = &poly_fns, .polygon = &p_assembly},
- {.name = "signature", .fns = &poly_fns, .polygon = &p_signature},
- {.name = "rpromoter", .fns = &poly_fns, .polygon = &p_rpromoter},
- {.name = "larrow", .fns = &poly_fns, .polygon = &p_larrow},
- {.name = "rarrow", .fns = &poly_fns, .polygon = &p_rarrow},
- {.name = "lpromoter", .fns = &poly_fns, .polygon = &p_lpromoter},
- /* *** shapes other than polygons *** */
- {.name = "record", .fns = &record_fns, .polygon = NULL},
- {.name = "Mrecord", .fns = &record_fns, .polygon = NULL},
- {.name = "epsf", .fns = &epsf_fns, .polygon = NULL},
- {.name = "star", .fns = &star_fns, .polygon = &p_star},
- {0}
- };
- static void unrecognized(node_t * n, char *p)
- {
- agwarningf("node %s, port %s unrecognized\n", agnameof(n), p);
- }
- static double quant(double val, double q)
- {
- return ceil(val / q) * q;
- }
- /* test if both p0 and p1 are on the same side of the line L0,L1 */
- static int same_side(pointf p0, pointf p1, pointf L0, pointf L1)
- {
- int s0, s1;
- double a, b, c;
- /* a x + b y = c */
- a = -(L1.y - L0.y);
- b = L1.x - L0.x;
- c = a * L0.x + b * L0.y;
- s0 = a * p0.x + b * p0.y - c >= 0;
- s1 = a * p1.x + b * p1.y - c >= 0;
- return s0 == s1;
- }
- static
- char* penColor(GVJ_t * job, node_t * n)
- {
- char *color;
- color = late_nnstring(n, N_color, "");
- if (!color[0])
- color = DEFAULT_COLOR;
- gvrender_set_pencolor(job, color);
- return color;
- }
- static
- char *findFillDflt(node_t * n, char *dflt)
- {
- char *color;
- color = late_nnstring(n, N_fillcolor, "");
- if (!color[0]) {
- /* for backward compatibility, default fill is same as pen */
- color = late_nnstring(n, N_color, "");
- if (!color[0]) {
- color = dflt;
- }
- }
- return color;
- }
- static
- char *findFill(node_t * n)
- {
- return findFillDflt(n, DEFAULT_FILL);
- }
- static bool isBox(node_t *n) {
- polygon_t *p;
- if ((p = ND_shape(n)->polygon)) {
- return p->sides == 4 && fabs(fmod(p->orientation, 90)) < 0.5 &&
- is_exactly_zero(p->distortion) && is_exactly_zero(p->skew);
- }
- return false;
- }
- static bool isEllipse(node_t *n) {
- polygon_t *p;
- if ((p = ND_shape(n)->polygon)) {
- return p->sides <= 2;
- }
- return false;
- }
- /// bitwise-OR styles
- static graphviz_polygon_style_t style_or(graphviz_polygon_style_t a,
- graphviz_polygon_style_t b) {
- // bitwise-or-ing the shape does not make sense, so there better only be one
- assert(a.shape == 0 || b.shape == 0);
- return (graphviz_polygon_style_t){
- .filled = a.filled || b.filled,
- .radial = a.radial || b.radial,
- .rounded = a.rounded || b.rounded,
- .diagonals = a.diagonals || b.diagonals,
- .auxlabels = a.auxlabels || b.auxlabels,
- .invisible = a.invisible || b.invisible,
- .striped = a.striped || b.striped,
- .dotted = a.dotted || b.dotted,
- .dashed = a.dashed || b.dashed,
- .wedged = a.wedged || b.wedged,
- .underline = a.underline || b.underline,
- .fixedshape = a.fixedshape || b.fixedshape,
- .shape = a.shape | b.shape,
- };
- }
- static char **checkStyle(node_t *n, graphviz_polygon_style_t *flagp) {
- char *style;
- char **pstyle = 0;
- graphviz_polygon_style_t istyle = {0};
- polygon_t *poly;
- style = late_nnstring(n, N_style, "");
- if (style[0]) {
- char **pp;
- char **qp;
- char *p;
- pp = pstyle = parse_style(style);
- while ((p = *pp)) {
- if (streq(p, "filled")) {
- istyle.filled = true;
- pp++;
- } else if (streq(p, "rounded")) {
- istyle.rounded = true;
- qp = pp; /* remove rounded from list passed to renderer */
- do {
- qp++;
- *(qp - 1) = *qp;
- } while (*qp);
- } else if (streq(p, "diagonals")) {
- istyle.diagonals = true;
- qp = pp; /* remove diagonals from list passed to renderer */
- do {
- qp++;
- *(qp - 1) = *qp;
- } while (*qp);
- } else if (streq(p, "invis")) {
- istyle.invisible = true;
- pp++;
- } else if (streq(p, "radial")) {
- istyle.radial = true;
- istyle.filled = true;
- qp = pp; /* remove radial from list passed to renderer */
- do {
- qp++;
- *(qp - 1) = *qp;
- } while (*qp);
- } else if (streq(p, "striped") && isBox(n)) {
- istyle.striped = true;
- qp = pp; /* remove striped from list passed to renderer */
- do {
- qp++;
- *(qp - 1) = *qp;
- } while (*qp);
- } else if (streq(p, "wedged") && isEllipse(n)) {
- istyle.wedged = true;
- qp = pp; /* remove wedged from list passed to renderer */
- do {
- qp++;
- *(qp - 1) = *qp;
- } while (*qp);
- } else
- pp++;
- }
- }
- if ((poly = ND_shape(n)->polygon))
- istyle = style_or(istyle, poly->option);
- *flagp = istyle;
- return pstyle;
- }
- static graphviz_polygon_style_t stylenode(GVJ_t *job, node_t *n) {
- char **pstyle, *s;
- graphviz_polygon_style_t istyle = {0};
- double penwidth;
- if ((pstyle = checkStyle(n, &istyle)))
- gvrender_set_style(job, pstyle);
- if (N_penwidth && (s = agxget(n, N_penwidth)) && s[0]) {
- penwidth = late_double(n, N_penwidth, 1.0, 0.0);
- gvrender_set_penwidth(job, penwidth);
- }
- return istyle;
- }
- static void Mcircle_hack(GVJ_t * job, node_t * n)
- {
- double x, y;
- pointf AF[2], p;
- y = .7500;
- x = .6614; /* x^2 + y^2 = 1.0 */
- p.y = y * ND_ht(n) / 2.0;
- p.x = ND_rw(n) * x; /* assume node is symmetric */
- AF[0] = add_pointf(p, ND_coord(n));
- AF[1].y = AF[0].y;
- AF[1].x = AF[0].x - 2 * p.x;
- gvrender_polyline(job, AF, 2);
- AF[0].y -= 2 * p.y;
- AF[1].y = AF[0].y;
- gvrender_polyline(job, AF, 2);
- }
- static pointf * alloc_interpolation_points(pointf *AF, size_t sides,
- graphviz_polygon_style_t style, bool rounded)
- {
- pointf *B = gv_calloc(4 * sides + 4, sizeof(pointf));
- size_t i = 0;
- pointf p0, p1;
- double dx, dy, t;
- /* rbconst is distance offset from a corner of the polygon.
- * It should be the same for every corner, and also never
- * bigger than one-third the length of a side.
- */
- double rbconst = RBCONST;
- for (size_t seg = 0; seg < sides; seg++) {
- p0 = AF[seg];
- if (seg + 1 < sides)
- p1 = AF[seg + 1];
- else
- p1 = AF[0];
- dx = p1.x - p0.x;
- dy = p1.y - p0.y;
- const double d = hypot(dx, dy);
- rbconst = fmin(rbconst, d / 3.0);
- }
- for (size_t seg = 0; seg < sides; seg++) {
- p0 = AF[seg];
- if (seg + 1 < sides)
- p1 = AF[seg + 1];
- else
- p1 = AF[0];
- dx = p1.x - p0.x;
- dy = p1.y - p0.y;
- const double d = hypot(dx, dy);
- t = rbconst / d;
- if (style.shape == BOX3D || style.shape == COMPONENT)
- t /= 3;
- else if (style.shape == DOGEAR)
- t /= 2;
- if (!rounded)
- B[i++] = p0;
- else
- B[i++] = interpolate_pointf(RBCURVE * t, p0, p1);
- B[i++] = interpolate_pointf(t, p0, p1);
- B[i++] = interpolate_pointf(1.0 - t, p0, p1);
- if (rounded)
- B[i++] = interpolate_pointf(1.0 - RBCURVE * t, p0, p1);
- }
- B[i++] = B[0];
- B[i++] = B[1];
- B[i++] = B[2];
- return B;
- }
- /**
- * @brief draws polygons with diagonals on corners
- *
- * Diagonals are weird. Rewrite someday.
- */
- static void diagonals_draw(GVJ_t *job, pointf *AF, size_t sides,
- graphviz_polygon_style_t style, int filled)
- {
- pointf *B = alloc_interpolation_points(AF, sides, style, false);
- gvrender_polygon(job, AF, sides, filled);
- for (size_t seg = 0; seg < sides; seg++) {
- pointf C[] = {B[3 * seg + 2], B[3 * seg + 4]};
- gvrender_polyline(job, C, 2);
- }
- free(B);
- }
- /**
- * @brief draws rounded polygons with
- * [Bézier curve](https://en.wikipedia.org/wiki/Bézier_curve)
- *
- * For example, a rounded star looks like a cartoon starfish.
- */
- static void rounded_draw(GVJ_t *job, pointf *AF, size_t sides,
- graphviz_polygon_style_t style, int filled)
- {
- size_t i = 0;
- pointf *B = alloc_interpolation_points(AF, sides, style, true);
- pointf *pts = gv_calloc(6 * sides + 2, sizeof(pointf));
- for (size_t seg = 0; seg < sides; seg++) {
- pts[i++] = B[4 * seg];
- pts[i++] = B[4 * seg + 1];
- pts[i++] = B[4 * seg + 1];
- pts[i++] = B[4 * seg + 2];
- pts[i++] = B[4 * seg + 2];
- pts[i++] = B[4 * seg + 3];
- }
- pts[i++] = pts[0];
- pts[i++] = pts[1];
- gvrender_beziercurve(job, pts + 1, i - 1, filled);
- free(pts);
- free(B);
- }
- /**
- * @file
- * ~~~~
- * y
- * 🡑
- * │
- * │
- * │ line[1]
- * │ ⟋
- * mid_y ┤ middle
- * │ ⟋
- * │line[0] x
- * ─┼─────┬───────────🡒
- * mid_x
- * ~~~~
- */
- /**
- * @brief X coordinate of line midpoint
- * @param line two points
- * @returns X coordinate of midpoint
- */
- static double mid_x(const pointf line[2]) {
- return (line[0].x + line[1].x) / 2;
- }
- /**
- * @brief Y coordinate of line midpoint
- * @param line two points
- * @returns Y coordinate of midpoint
- */
- static double mid_y(const pointf line[2]) {
- return (line[0].y + line[1].y) / 2;
- }
- /**
- * @brief Handle some special graphical cases, such as rounding the shape,
- * adding diagonals at corners, or drawing certain non-simple figures.
- *
- * Any drawing done here should assume fillcolors, pencolors, etc.
- * have been set by the calling routine. Normally, the drawing should
- * consist of a region, filled or unfilled, followed by additional line
- * segments. A single fill is necessary for gradient colors to work.
- */
- void round_corners(GVJ_t *job, pointf *AF, size_t sides,
- graphviz_polygon_style_t style, int filled) {
- assert(job != NULL);
- assert(AF != NULL);
- assert(sides > 0);
- assert(memcmp(&style, &(graphviz_polygon_style_t){0}, sizeof(style)) != 0);
- pointf *B, C[5], *D;
- struct {
- unsigned shape: 7;
- } mode = {0};
- if (style.diagonals)
- return diagonals_draw(job, AF, sides, style, filled);
- else if (style.shape != 0)
- mode.shape = style.shape;
- else if (style.rounded)
- return rounded_draw(job, AF, sides, style, filled);
- else
- UNREACHABLE();
- if (mode.shape == CYLINDER) {
- cylinder_draw(job, AF, sides, filled);
- return;
- }
- B = alloc_interpolation_points(AF, sides, style, false);
- switch (mode.shape) {
- case DOGEAR:
- /* Add the cutoff edge. */
- D = gv_calloc(sides + 1, sizeof(pointf));
- for (size_t seg = 1; seg < sides; seg++)
- D[seg] = AF[seg];
- D[0] = B[3 * (sides - 1) + 4];
- D[sides] = B[3 * (sides - 1) + 2];
- gvrender_polygon(job, D, sides + 1, filled);
- free(D);
- /* Draw the inner edge. */
- const size_t sseg = sides - 1;
- C[0] = B[3 * sseg + 2];
- C[1] = B[3 * sseg + 4];
- C[2].x = C[1].x + (C[0].x - B[3 * sseg + 3].x);
- C[2].y = C[1].y + (C[0].y - B[3 * sseg + 3].y);
- gvrender_polyline(job, C + 1, 2);
- C[1] = C[2];
- gvrender_polyline(job, C, 2);
- break;
- case TAB:
- /*
- * Adjust the perimeter for the protrusions.
- *
- * D[3] ×──× D[2]
- * │ │ B[1]
- * B[3] ×──×──────────×──× AF[0]=B[0]=D[0]
- * │ B[2]=D[1] │
- * B[4] × │
- * │ │
- * B[5] × │
- * └────────────────┘
- *
- */
- /* Add the tab edges. */
- D = gv_calloc(sides + 2, sizeof(pointf));
- D[0] = AF[0];
- D[1] = B[2];
- D[2].x = B[2].x + (B[3].x - B[4].x) / 3;
- D[2].y = B[2].y + (B[3].y - B[4].y) / 3;
- D[3].x = B[3].x + (B[3].x - B[4].x) / 3;
- D[3].y = B[3].y + (B[3].y - B[4].y) / 3;
- for (size_t seg = 4; seg < sides + 2; seg++)
- D[seg] = AF[seg - 2];
- gvrender_polygon(job, D, sides + 2, filled);
- free(D);
- /* Draw the inner edge. */
- C[0] = B[3];
- C[1] = B[2];
- gvrender_polyline(job, C, 2);
- break;
- case FOLDER:
- /*
- * Adjust the perimeter for the protrusions.
- *
- * D[2] ×────× D[1]
- * B[3]= ╱ ╲
- * D[4] ×──×────× × × AF[0]=B[0]=D[0]
- * │ B[2] D[3] B[1]│
- * B[4] × │
- * │ │
- * B[5] × │
- * └────────────────┘
- *
- */
- /* Add the folder edges. */
- D = gv_calloc(sides + 3, sizeof(pointf));
- D[0] = AF[0];
- D[1].x = AF[0].x - (AF[0].x - B[1].x) / 4;
- D[1].y = AF[0].y + (B[3].y - B[4].y) / 3;
- D[2].x = AF[0].x - 2 * (AF[0].x - B[1].x);
- D[2].y = D[1].y;
- D[3].x = AF[0].x - 2.25 * (AF[0].x - B[1].x);
- D[3].y = B[3].y;
- D[4].x = B[3].x;
- D[4].y = B[3].y;
- for (size_t seg = 4; seg < sides + 3; seg++)
- D[seg] = AF[seg - 3];
- gvrender_polygon(job, D, sides + 3, filled);
- free(D);
- break;
- case BOX3D:
- assert(sides == 4);
- /* Adjust for the cutoff edges. */
- D = gv_calloc(sides + 2, sizeof(pointf));
- D[0] = AF[0];
- D[1] = B[2];
- D[2] = B[4];
- D[3] = AF[2];
- D[4] = B[8];
- D[5] = B[10];
- gvrender_polygon(job, D, sides + 2, filled);
- free(D);
- /* Draw the inner vertices. */
- C[0].x = B[1].x + (B[11].x - B[0].x);
- C[0].y = B[1].y + (B[11].y - B[0].y);
- C[1] = B[4];
- gvrender_polyline(job, C, 2);
- C[1] = B[8];
- gvrender_polyline(job, C, 2);
- C[1] = B[0];
- gvrender_polyline(job, C, 2);
- break;
- case COMPONENT:
- assert(sides == 4);
- /*
- * Adjust the perimeter for the protrusions.
- *
- * D[1] ×────────────────× D[0]
- * │ │
- * 3×───×2──┐ │
- * │ │ │
- * 4×───×5──┘ │
- * │ │
- * 7×───×6──┐ │
- * │ │ │
- * 8×───×9──┘ │
- * │ │
- * 10×────────────────× D[11]
- *
- */
- D = gv_calloc(sides + 8, sizeof(pointf));
- D[0] = AF[0];
- D[1] = AF[1];
- D[2].x = B[3].x + (B[4].x - B[3].x);
- D[2].y = B[3].y + (B[4].y - B[3].y);
- D[3].x = D[2].x + (B[3].x - B[2].x);
- D[3].y = D[2].y + (B[3].y - B[2].y);
- D[4].x = D[3].x + (B[4].x - B[3].x);
- D[4].y = D[3].y + (B[4].y - B[3].y);
- D[5].x = D[4].x + (D[2].x - D[3].x);
- D[5].y = D[4].y + (D[2].y - D[3].y);
- D[9].x = B[6].x + (B[5].x - B[6].x);
- D[9].y = B[6].y + (B[5].y - B[6].y);
- D[8].x = D[9].x + (B[6].x - B[7].x);
- D[8].y = D[9].y + (B[6].y - B[7].y);
- D[7].x = D[8].x + (B[5].x - B[6].x);
- D[7].y = D[8].y + (B[5].y - B[6].y);
- D[6].x = D[7].x + (D[9].x - D[8].x);
- D[6].y = D[7].y + (D[9].y - D[8].y);
- D[10] = AF[2];
- D[11] = AF[3];
- gvrender_polygon(job, D, sides + 8, filled);
- /* Draw the internal vertices. */
- C[0] = D[2];
- C[1].x = D[2].x - (D[3].x - D[2].x);
- C[1].y = D[2].y - (D[3].y - D[2].y);
- C[2].x = C[1].x + (D[4].x - D[3].x);
- C[2].y = C[1].y + (D[4].y - D[3].y);
- C[3] = D[5];
- gvrender_polyline(job, C, 4);
- C[0] = D[6];
- C[1].x = D[6].x - (D[7].x - D[6].x);
- C[1].y = D[6].y - (D[7].y - D[6].y);
- C[2].x = C[1].x + (D[8].x - D[7].x);
- C[2].y = C[1].y + (D[8].y - D[7].y);
- C[3] = D[9];
- gvrender_polyline(job, C, 4);
- free(D);
- break;
- case PROMOTER:
- /*
- * L-shaped arrow on a center line, scales in the x direction
- *
- *
- * D[1] │╲
- * ×────────────────× ╲
- * │ D[0] ╲
- * │ ╲
- * │ ╱
- * │ D[5] ╱
- * │ ┌───────× ╱
- * │ │ │╱
- * ─────┴────────┴─────────────
- */
- /* Add the tab edges. */
- //the arrow's thickness is (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides + 5, sizeof(pointf));
- D[0].x = mid_x(AF) + (AF[0].x - AF[1].x)/8; //x_center + width
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)*3/2; //D[4].y + width
- D[1].x = mid_x(AF) - (AF[0].x - AF[1].x)/4; //x_center - 2*width
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = mid_y(&AF[1]);
- D[3].x = D[2].x + (B[2].x - B[3].x)/2; //D[2].x + width
- D[3].y = mid_y(&AF[1]);
- D[4].x = D[3].x;
- D[4].y = mid_y(&AF[1]) + (B[3].y-B[4].y); //highest cds point
- D[5].x = D[0].x;
- D[5].y = D[4].y; //highest cds point
- D[6].x = D[0].x;
- D[6].y = D[4].y - (B[3].y-B[4].y)/4; //D[4].y - width/2
- D[7].x = D[6].x + (B[2].x - B[3].x); //D[6].x + 2*width
- D[7].y = D[6].y + (B[3].y - B[4].y)/2; //D[6].y + width
- D[8].x = D[0].x;
- D[8].y = D[0].y + (B[3].y - B[4].y)/4;//D[0].y + width/2
- gvrender_polygon(job, D, sides + 5, filled);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case CDS:
- /*
- * arrow without the protrusions, scales normally
- *
- *
- * D[1] = AF[1]
- * ×────────────────×╲
- * │ D[0]╲
- * │ ╲
- * │ ╱
- * │ ╱
- * ×────────────────×╱
- * D[3]
- *
- */
- D = gv_calloc(sides + 1, sizeof(pointf));
- D[0].x = B[1].x;
- D[0].y = B[1].y - (B[3].y - B[4].y)/2;
- D[1].x = B[3].x;
- D[1].y = B[3].y - (B[3].y - B[4].y)/2;
- D[2].x = AF[2].x;
- D[2].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[3].x = B[1].x;
- D[3].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[4].y = AF[0].y - (AF[0].y - AF[3].y)/2;
- D[4].x = AF[0].x;
- gvrender_polygon(job, D, sides + 1, filled);
- free(D);
- break;
- case TERMINATOR:
- /*
- * T-shape, does not scale, always in the center
- *
- *
- * D[4]
- * ×────────────────×
- * │ D[3]
- * │ │
- * │ │
- * │ D[6] D[1] │
- * D[5]×───× ×────× D[2]
- * │ │
- * ─────────┴───────×─D[0]──────
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides + 4, sizeof(pointf));
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/4; //x_center + width/2
- D[0].y = mid_y(&AF[1]);
- D[1].x = D[0].x;
- D[1].y = D[0].y + (B[3].y-B[4].y)/2;
- D[2].x = D[1].x + (B[2].x-B[3].x)/2;
- D[2].y = D[1].y;
- D[3].x = D[2].x;
- D[3].y = D[2].y + (B[3].y-B[4].y)/2;
- D[4].x = mid_x(AF) - (B[2].x-B[3].x)*3/4; //D[3].y mirrored across the center
- D[4].y = D[3].y;
- D[5].x = D[4].x;
- D[5].y = D[2].y;
- D[6].x = mid_x(AF) - (B[2].x-B[3].x)/4; //D[1].x mirrored across the center
- D[6].y = D[1].y;
- D[7].x = D[6].x;
- D[7].y = D[0].y;
- gvrender_polygon(job, D, sides + 4, filled);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case UTR:
- /*
- * half-octagon with line, does not scale, always in center
- *
- * D[3]
- * ───── D[2]
- * ╱ ╲
- * ╱ ╲ D[1]
- * │ │
- * ─────┴───────┴───────
- * D[0]
- *
- *
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides + 2, sizeof(pointf));
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)*3/4; //x_center+width
- D[0].y = mid_y(&AF[1]);
- D[1].x = D[0].x;
- D[1].y = D[0].y + (B[3].y-B[4].y)/4; //D[0].y+width/2
- D[2].x = mid_x(AF) + (B[2].x-B[3].x)/4; //x_center+width/2
- D[2].y = D[1].y + (B[3].y-B[4].y)/2; //D[1].y+width
- D[3].x = mid_x(AF) - (B[2].x-B[3].x)/4; //D[2].x mirrored across the center
- D[3].y = D[2].y;
- D[4].x = mid_x(AF) - (B[2].x-B[3].x)*3/4;
- D[4].y = D[1].y;
- D[5].x = D[4].x;
- D[5].y = D[0].y;
- gvrender_polygon(job, D, sides + 2, filled);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case PRIMERSITE:
- /*
- * half arrow shape, scales in the x-direction
- * D[1]
- * │╲
- * │ ╲
- * │ ╲
- * ┌───────────┘ ╲
- * │ ╲
- * └─────────────────╲ D[0]
- *
- * ────────────────────────────────
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides + 1, sizeof(pointf));
- D[0].x = mid_x(AF) + (B[2].x-B[3].x);//x_center + width*2
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/4;//y_center + 1/2 width
- D[1].x = D[0].x - (B[2].x-B[3].x); //x_center
- D[1].y = D[0].y + (B[3].y-B[4].y);
- D[2].x = D[1].x;
- D[2].y = D[0].y + (B[3].y-B[4].y)/2;
- D[3].x = mid_x(AF) - (AF[0].x - AF[1].x)/4;//x_center - 2*(scalable width)
- D[3].y = D[2].y;
- D[4].x = D[3].x;
- D[4].y = D[0].y;
- gvrender_polygon(job, D, sides + 1, filled);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case RESTRICTIONSITE:
- /*
- * zigzag shape, scales in the x-direction (only the middle section)
- *
- *
- * ┌───D[2]
- * │ └──────── D[0]
- * ────┤ ├────
- * └────────┐ │
- * D[4] └─── D[7]
- *
- *
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides + 4, sizeof(pointf));
- D[0].x = mid_x(AF) + (AF[0].x - AF[1].x)/8 + (B[2].x-B[3].x)/2;//x_center + scalable_width + width
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/4;//y_center + 1/2 width
- D[1].x = mid_x(AF) - (AF[0].x - AF[1].x)/8; //x_center - width
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[2].x - (B[2].x-B[3].x)/2; //D[2].x - width
- D[3].y = D[2].y;
- D[4].x = D[3].x;
- D[4].y = mid_y(&AF[1]) - (B[3].y-B[4].y)/4; //y_center - 1/2(width)
- D[5].x = D[0].x - (B[2].x-B[3].x)/2;
- D[5].y = D[4].y;
- D[6].x = D[5].x;
- D[6].y = D[5].y - (B[3].y-B[4].y)/2;
- D[7].x = D[0].x;
- D[7].y = D[6].y;
- gvrender_polygon(job, D, sides + 4, filled);
- /*dsDNA line left half*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = D[4].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- /*dsDNA line right half*/
- C[0].x = D[7].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case FIVEPOVERHANG:
- /*
- * does not scale, on the left side
- *
- * D[3]──────D[2]
- * │ │
- * D[0]──────D[1]
- * ┌────┐ ────────────
- * │ │
- * D[0]──D[1]
- *
- *
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = AF[1].x;//the very left edge
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/8;//y_center + 1/4 width
- D[1].x = D[0].x + 2*(B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*second, lower shape*/
- free(D);
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = AF[1].x + (B[2].x-B[3].x);
- D[0].y = mid_y(&AF[1]) - (B[3].y-B[4].y)*5/8; //y_center - 5/4 width
- D[1].x = D[0].x + (B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*dsDNA line right half*/
- C[0].x = D[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case THREEPOVERHANG:
- /*
- * does not scale, on the right side
- *
- * D[2]──────D[1]
- * │ │
- * ─────── D[3]──────D[0]
- * ┌────┐ D[1]
- * │ │
- * D[3]──D[0]
- *
- *
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = AF[0].x;//the very right edge
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/8;//y_center + 1/4 width
- D[1].x = D[0].x;
- D[1].y = D[0].y + (B[3].y-B[4].y)/2;
- D[2].x = D[1].x - 2*(B[3].y-B[4].y);
- D[2].y = D[1].y;
- D[3].x = D[2].x;
- D[3].y = D[0].y;
- gvrender_polygon(job, D, sides, filled);
- /*second, lower shape*/
- free(D);
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = AF[0].x - (B[2].x-B[3].x);
- D[0].y = mid_y(&AF[1]) - (B[3].y-B[4].y)*5/8; //y_center - 5/4 width
- D[1].x = D[0].x;
- D[1].y = D[0].y + (B[3].y-B[4].y)/2;
- D[2].x = D[1].x - (B[3].y-B[4].y);
- D[2].y = D[1].y;
- D[3].x = D[2].x;
- D[3].y = D[0].y;
- gvrender_polygon(job, D, sides, filled);
- /*dsDNA line left half*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = D[3].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case NOVERHANG:
- /*
- * does not scale
- *
- * D[3]──────D[2] D[3]──────D[2]
- * │ │ │ │
- * ───D[0]──────D[1] D[0]──────D[1]────
- * D[3]──────D[2] D[3]──────D[2]
- * │ │ │ │
- * D[0]──────D[1] D[0]──────D[1]
- *
- *
- *
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- /*upper left rectangle*/
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) - (B[2].x-B[3].x)*9/8; //x_center - 2*width - 1/4*width
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/8;//y_center + 1/4 width
- D[1].x = D[0].x + (B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*lower, left rectangle*/
- free(D);
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) - (B[2].x-B[3].x)*9/8; //x_center - 2*width - 1/4*width
- D[0].y = mid_y(&AF[1]) - (B[3].y-B[4].y)*5/8;//y_center - width - 1/4 width
- D[1].x = D[0].x + (B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*lower, right rectangle*/
- free(D);
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/8; //x_center + 1/4*width
- D[0].y = mid_y(&AF[1]) - (B[3].y-B[4].y)*5/8;//y_center - width - 1/4 width
- D[1].x = D[0].x + (B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*upper, right rectangle*/
- free(D);
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/8; //x_center + 1/4*width
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/8;//y_center - width - 1/4 width
- D[1].x = D[0].x + (B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*dsDNA line right half*/
- C[0].x = D[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- /*dsDNA line left half*/
- C[0].x = mid_x(AF) - (B[2].x-B[3].x)*9/8; //D[0].x of of the left rectangles
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[1].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case ASSEMBLY:
- /*
- * does not scale
- *
- * D[3]──────────D[2]
- * │ │
- * D[0]──────────D[1]
- * ──── ─────────
- * D[3]──────────D[2]
- * │ │
- * D[0]──────────D[1]
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) - (B[2].x-B[3].x); //x_center - 2*width
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/8;//y_center + 1/4 width
- D[1].x = D[0].x + 2*(B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*second, lower shape*/
- free(D);
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) - (B[2].x-B[3].x); //x_center - 2*width
- D[0].y = mid_y(&AF[1]) - (B[3].y-B[4].y)*5/8;//y_center - width - 1/4 width
- D[1].x = D[0].x + 2*(B[2].x-B[3].x);
- D[1].y = D[0].y;
- D[2].x = D[1].x;
- D[2].y = D[1].y + (B[3].y-B[4].y)/2;
- D[3].x = D[0].x;
- D[3].y = D[2].y;
- gvrender_polygon(job, D, sides, filled);
- /*dsDNA line right half*/
- C[0].x = D[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- /*dsDNA line left half*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = D[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case SIGNATURE:
- /*
- *
- *
- * ┌──────────────┐
- * │ │
- * │x │
- * │_____________ │
- * └──────────────┘
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- // the thickness is substituted with (AF[0].x - AF[1].x)/8 to make it scalable
- // in the y with label length
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = AF[0].x;
- D[0].y = B[1].y - (B[3].y - B[4].y)/2;
- D[1].x = B[3].x;
- D[1].y = B[3].y - (B[3].y - B[4].y)/2;
- D[2].x = AF[2].x;
- D[2].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[3].x = AF[0].x;
- D[3].y = AF[2].y + (B[3].y - B[4].y)/2;
- gvrender_polygon(job, D, sides, filled);
- /* "\" of the X*/
- C[0].x = AF[1].x + (B[2].x-B[3].x)/4;
- C[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/8; //y_center + 1/4 width
- C[1].x = C[0].x + (B[2].x-B[3].x)/4;//C[0].x + width/2
- C[1].y = C[0].y - (B[3].y-B[4].y)/4;//C[0].y - width/2
- gvrender_polyline(job, C, 2);
- /*"/" of the X*/
- C[0].x = AF[1].x + (B[2].x-B[3].x)/4;
- C[0].y = mid_y(&AF[1]) - (B[3].y-B[4].y)/8; //y_center - 1/4 width
- C[1].x = C[0].x + (B[2].x-B[3].x)/4;//C[0].x + width/2
- C[1].y = C[0].y + (B[3].y-B[4].y)/4;//C[0].y + width/2
- gvrender_polyline(job, C, 2);
- /*bottom line*/
- C[0].x = AF[1].x + (B[2].x-B[3].x)/4;
- C[0].y = AF[2].y + (B[3].y-B[4].y)*3/4;
- C[1].x = AF[0].x - (B[2].x-B[3].x)/4;
- C[1].y = C[0].y;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case INSULATOR:
- /*
- * double square
- *
- * ┌─────┐
- *──┤ ┌─┐ ├───
- * │ └─┘ │
- * └─────┘
- *
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides, sizeof(pointf));
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/2; //x_center+width
- D[0].y = mid_y(&AF[1]) + (B[2].x-B[3].x)/2;
- D[1].x = D[0].x;
- D[1].y = mid_y(&AF[1]) - (B[2].x-B[3].x)/2; //D[0].y- width
- D[2].x = mid_x(AF) - (B[2].x-B[3].x)/2; //x_center-width
- D[2].y = D[1].y;
- D[3].x = D[2].x;
- D[3].y = D[0].y;
- gvrender_polygon(job, D, sides, filled);
- free(D);
- /*outer square line*/
- C[0].x = mid_x(AF) + (B[2].x-B[3].x)*3/4; //x_center+1.5*width
- C[0].y = mid_y(&AF[1]) + (B[2].x-B[3].x)*3/4; //y_center
- C[1].x = C[0].x;
- C[1].y = mid_y(&AF[1]) - (B[2].x-B[3].x)*3/4; //y_center- 1.5*width
- C[2].x = mid_x(AF) - (B[2].x-B[3].x)*3/4; //x_center-1.5*width
- C[2].y = C[1].y;
- C[3].x = C[2].x;
- C[3].y = C[0].y;
- C[4] = C[0];
- gvrender_polyline(job, C, 5);
- /*dsDNA line right half*/
- C[0].x = mid_x(AF) + (B[2].x-B[3].x)*3/4;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- /*dsDNA line left half*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = mid_x(AF) - (B[2].x-B[3].x)*3/4;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- break;
- case RIBOSITE:
- /*
- * X with a dashed line on the bottom
- *
- *
- * X
- * ╎
- * ─────┴──────
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides + 12, sizeof(pointf)); // 12-sided x
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/4; //x_center+width/2 , lower right corner of the x
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/2; //y_center + width
- D[1].x = D[0].x;
- D[1].y = D[0].y + (B[3].y-B[4].y)/8; //D[0].y +width/4
- D[2].x = D[0].x - (B[2].x-B[3].x)/8; //D[0].x- width/4 //right nook of the x
- D[2].y = D[1].y + (B[3].y-B[4].y)/8; //D[0].y+width/2 or D[1].y+width/4
- D[3].x = D[0].x;
- D[3].y = D[2].y + (B[3].y-B[4].y)/8; //D[2].y + width/4
- D[4].x = D[0].x;
- D[4].y = D[3].y + (B[3].y-B[4].y)/8; //top right corner of the x
- D[5].x = D[2].x;
- D[5].y = D[4].y;
- D[6].x = mid_x(AF);
- D[6].y = D[3].y; //top nook
- D[7].x = D[6].x - (B[2].x-B[3].x)/8; //D[5] mirrored across y
- D[7].y = D[5].y;
- D[8].x = D[7].x - (B[2].x-B[3].x)/8;//top left corner
- D[8].y = D[7].y;
- D[9].x = D[8].x;
- D[9].y = D[3].y;
- D[10].x = D[8].x + (B[2].x-B[3].x)/8;
- D[10].y = D[2].y;
- D[11].x = D[8].x;
- D[11].y = D[1].y;
- D[12].x = D[8].x;
- D[12].y = D[0].y;
- D[13].x = D[10].x;
- D[13].y = D[12].y;
- D[14].x = D[6].x; //bottom nook
- D[14].y = D[1].y;
- D[15].x = D[2].x;
- D[15].y = D[0].y;
- gvrender_polygon(job, D, sides + 12, filled);
- //2-part dash line
- /*line below the x, bottom dash*/
- C[0].x = D[14].x; //x_center
- C[0].y = mid_y(&AF[1]);
- C[1].x = C[0].x;
- C[1].y = C[0].y + (B[3].y-B[4].y)/8; //y_center + 1/4*width
- gvrender_polyline(job, C, 2);
- /*line below the x, top dash*/
- C[0].x = D[14].x; //x_center
- C[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/4;
- C[1].x = C[0].x;
- C[1].y = C[0].y + (B[3].y-B[4].y)/8;
- gvrender_polyline(job, C, 2);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case RNASTAB:
- /*
- * octagon with a dashed line on the bottom
- *
- *
- * O
- * ╎
- * ─────┴──────
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides + 4, sizeof(pointf)); // 12-sided x
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/8; //x_center+width/8 , lower right corner of the hexagon
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/2; //y_center + width
- D[1].x = D[0].x + (B[2].x-B[3].x)/8;
- D[1].y = D[0].y + (B[3].y-B[4].y)/8; //D[0].y +width/4
- D[2].x = D[1].x; //D[0].x- width/4
- D[2].y = D[1].y + (B[3].y-B[4].y)/4; //D[1].y+width/2
- D[3].x = D[0].x;
- D[3].y = D[2].y + (B[3].y-B[4].y)/8; //D[2].y + width/4
- D[4].x = D[3].x - (B[2].x-B[3].x)/4;
- D[4].y = D[3].y; //top of the hexagon
- D[5].x = D[4].x - (B[2].x-B[3].x)/8;
- D[5].y = D[2].y;
- D[6].x = D[5].x;
- D[6].y = D[1].y; //left side
- D[7].x = D[4].x;
- D[7].y = D[0].y; //bottom
- gvrender_polygon(job, D, sides + 4, filled);
- //2-part dash line
- /*line below the x, bottom dash*/
- C[0].x = mid_x(AF);
- C[0].y = mid_y(&AF[1]);
- C[1].x = C[0].x;
- C[1].y = C[0].y + (B[3].y-B[4].y)/8; //y_center + 1/4*width
- gvrender_polyline(job, C, 2);
- /*line below the x, top dash*/
- C[0].x = mid_x(AF);
- C[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/4;
- C[1].x = C[0].x;
- C[1].y = C[0].y + (B[3].y-B[4].y)/8;
- gvrender_polyline(job, C, 2);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case PROTEASESITE:
- /*
- * X with a solid line on the bottom
- *
- *
- * X
- * │
- * ─────┴──────
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides + 12, sizeof(pointf)); // 12-sided x
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/4; //x_center+width/2 , lower right corner of the x
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/2; //y_center + width
- D[1].x = D[0].x;
- D[1].y = D[0].y + (B[3].y-B[4].y)/8; //D[0].y +width/4
- D[2].x = D[0].x - (B[2].x-B[3].x)/8; //D[0].x- width/4 //right nook of the x
- D[2].y = D[1].y + (B[3].y-B[4].y)/8; //D[0].y+width/2 or D[1].y+width/4
- D[3].x = D[0].x;
- D[3].y = D[2].y + (B[3].y-B[4].y)/8; //D[2].y + width/4
- D[4].x = D[0].x;
- D[4].y = D[3].y + (B[3].y-B[4].y)/8; //top right corner of the x
- D[5].x = D[2].x;
- D[5].y = D[4].y;
- D[6].x = mid_x(AF);
- D[6].y = D[3].y; //top nook
- D[7].x = D[6].x - (B[2].x-B[3].x)/8; //D[5] mirrored across y
- D[7].y = D[5].y;
- D[8].x = D[7].x - (B[2].x-B[3].x)/8;//top left corner
- D[8].y = D[7].y;
- D[9].x = D[8].x;
- D[9].y = D[3].y;
- D[10].x = D[8].x + (B[2].x-B[3].x)/8;
- D[10].y = D[2].y;
- D[11].x = D[8].x;
- D[11].y = D[1].y;
- D[12].x = D[8].x;
- D[12].y = D[0].y;
- D[13].x = D[10].x;
- D[13].y = D[12].y;
- D[14].x = D[6].x; //bottom nook
- D[14].y = D[1].y;
- D[15].x = D[2].x;
- D[15].y = D[0].y;
- gvrender_polygon(job, D, sides + 12, filled);
- /*line below the x*/
- C[0] = D[14];
- C[1].x = C[0].x;
- C[1].y = mid_y(&AF[1]);
- gvrender_polyline(job, C, 2);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case PROTEINSTAB:
- /*
- * octagon with a solid line on the bottom
- *
- *
- * O
- * │
- * ─────┴──────
- */
- //width units are (B[2].x-B[3].x)/2 or (B[3].y-B[4].y)/2;
- D = gv_calloc(sides + 4, sizeof(pointf)); // 12-sided x
- D[0].x = mid_x(AF) + (B[2].x-B[3].x)/8; //x_center+width/8 , lower right corner of the hexagon
- D[0].y = mid_y(&AF[1]) + (B[3].y-B[4].y)/2; //y_center + width
- D[1].x = D[0].x + (B[2].x-B[3].x)/8;
- D[1].y = D[0].y + (B[3].y-B[4].y)/8; //D[0].y +width/4
- D[2].x = D[1].x; //D[0].x- width/4
- D[2].y = D[1].y + (B[3].y-B[4].y)/4; //D[1].y+width/2
- D[3].x = D[0].x;
- D[3].y = D[2].y + (B[3].y-B[4].y)/8; //D[2].y + width/4
- D[4].x = D[3].x - (B[2].x-B[3].x)/4;
- D[4].y = D[3].y; //top of the hexagon
- D[5].x = D[4].x - (B[2].x-B[3].x)/8;
- D[5].y = D[2].y;
- D[6].x = D[5].x;
- D[6].y = D[1].y; //left side
- D[7].x = D[4].x;
- D[7].y = D[0].y; //bottom
- gvrender_polygon(job, D, sides + 4, filled);
- /*line below the x*/
- C[0].x = mid_x(AF);
- C[0].y = D[0].y;
- C[1].x = C[0].x;
- C[1].y = mid_y(&AF[1]);
- gvrender_polyline(job, C, 2);
- /*dsDNA line*/
- C[0].x = AF[1].x;
- C[0].y = mid_y(&AF[1]);
- C[1].x = AF[0].x;
- C[1].y = AF[2].y + (AF[0].y - AF[3].y)/2;
- gvrender_polyline(job, C, 2);
- free(D);
- break;
- case RPROMOTER:
- /*
- * Adjust the perimeter for the protrusions.
- *
- *
- * D[1] = AF[1] │╲
- * ×────────────────× ╲
- * │ D[0] ╲
- * │ ╲
- * │ ╱
- * │ ╱
- * │ ┌───────┐ ╱
- * │ │ │╱
- * └────────┘
- */
- /* Add the tab edges. */
- D = gv_calloc(sides + 5, sizeof(pointf)); // 5 new points
- D[0].x = B[1].x - (B[2].x - B[3].x)/2;
- D[0].y = B[1].y - (B[3].y - B[4].y)/2;
- D[1].x = B[3].x;
- D[1].y = B[3].y - (B[3].y - B[4].y)/2;
- D[2].x = AF[2].x;
- D[2].y = AF[2].y;
- D[3].x = B[2].x + (B[2].x - B[3].x)/2;
- D[3].y = AF[2].y;
- D[4].x = B[2].x + (B[2].x - B[3].x)/2;
- D[4].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[5].x = B[1].x - (B[2].x - B[3].x)/2;
- D[5].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[6].x = B[1].x - (B[2].x - B[3].x)/2;
- D[6].y = AF[3].y;
- D[7].y = AF[0].y - (AF[0].y - AF[3].y)/2; /*triangle point */
- D[7].x = AF[0].x; /*triangle point */
- D[8].y = AF[0].y;
- D[8].x = B[1].x - (B[2].x - B[3].x)/2;
- gvrender_polygon(job, D, sides + 5, filled);
- free(D);
- break;
- case RARROW:
- /*
- * Adjust the perimeter for the protrusions.
- *
- *
- * D[1] = AF[1] │╲
- * ×────────────────× ╲
- * │ D[0] ╲
- * │ ╲
- * │ ╱
- * │ ╱
- * └────────────────┐ ╱
- * │╱
- *
- */
- /* Add the tab edges. */
- D = gv_calloc(sides + 3, sizeof(pointf)); // 3 new points
- D[0].x = B[1].x - (B[2].x - B[3].x)/2;
- D[0].y = B[1].y - (B[3].y - B[4].y)/2;
- D[1].x = B[3].x;
- D[1].y = B[3].y - (B[3].y - B[4].y)/2;
- D[2].x = AF[2].x;
- D[2].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[3].x = B[1].x - (B[2].x - B[3].x)/2;
- D[3].y = AF[2].y + (B[3].y - B[4].y)/2;
- D[4].x = B[1].x - (B[2].x - B[3].x)/2;
- D[4].y = AF[3].y;
- D[5].y = AF[0].y - (AF[0].y - AF[3].y)/2;/*triangle point*/
- D[5].x = AF[0].x; /*triangle point */
- D[6].y = AF[0].y;
- D[6].x = B[1].x - (B[2].x - B[3].x)/2;
- gvrender_polygon(job, D, sides + 3, filled);
- free(D);
- break;
- case LARROW:
- /*
- * Adjust the perimeter for the protrusions.
- *
- *
- * ╱│
- * ╱ └────────────────┐
- * ╱ │
- * ╲ │
- * ╲ ┌────────────────┘
- * ╲│
- *
- */
- /* Add the tab edges. */
- D = gv_calloc(sides + 3, sizeof(pointf)); // 3 new points
- D[0].x = AF[0].x;
- D[0].y = AF[0].y - (B[3].y-B[4].y)/2;
- D[1].x = B[2].x + (B[2].x - B[3].x)/2;
- D[1].y = AF[0].y - (B[3].y-B[4].y)/2;/*D[0].y*/
- D[2].x = B[2].x + (B[2].x - B[3].x)/2;/*D[1].x*/
- D[2].y = B[2].y;
- D[3].x = AF[1].x; /*triangle point*/
- D[3].y = AF[1].y - (AF[1].y - AF[2].y)/2; /*triangle point*/
- D[4].x = B[2].x + (B[2].x - B[3].x)/2;/*D[1].x*/
- D[4].y = AF[2].y;
- D[5].y = AF[2].y + (B[3].y-B[4].y)/2;
- D[5].x = B[2].x + (B[2].x - B[3].x)/2;/*D[1].x*/
- D[6].y = AF[3].y + (B[3].y - B[4].y)/2;
- D[6].x = AF[0].x;/*D[0]*/
- gvrender_polygon(job, D, sides + 3, filled);
- free(D);
- break;
- case LPROMOTER:
- /*
- * Adjust the perimeter for the protrusions.
- *
- *
- * ╱│
- * ╱ └────────────────×
- * ╱ D[0]
- * ╱ │
- * ╲ │
- * ╲ │
- * ╲ ┌────────┐ │
- * ╲│ │ │
- * └───────┘
- */
- /* Add the tab edges. */
- D = gv_calloc(sides + 5, sizeof(pointf)); // 3 new points
- D[0].x = AF[0].x;
- D[0].y = AF[0].y - (B[3].y-B[4].y)/2;
- D[1].x = B[2].x + (B[2].x - B[3].x)/2;
- D[1].y = AF[0].y - (B[3].y-B[4].y)/2;/*D[0].y*/
- D[2].x = B[2].x + (B[2].x - B[3].x)/2;/*D[1].x*/
- D[2].y = B[2].y;
- D[3].x = AF[1].x; /*triangle point*/
- D[3].y = AF[1].y - (AF[1].y - AF[2].y)/2; /*triangle point*/
- D[4].x = B[2].x + (B[2].x - B[3].x)/2;/*D[1].x*/
- D[4].y = AF[2].y;
- D[5].y = AF[2].y + (B[3].y-B[4].y)/2;
- D[5].x = B[2].x + (B[2].x - B[3].x)/2;/*D[1].x*/
- D[6].y = AF[3].y + (B[3].y - B[4].y)/2;
- D[6].x = B[1].x - (B[2].x - B[3].x)/2;
- D[7].x = B[1].x - (B[2].x - B[3].x)/2;/*D[6].x*/
- D[7].y = AF[3].y;
- D[8].x = AF[3].x;
- D[8].y = AF[3].y;
- gvrender_polygon(job, D, sides + 5, filled);
- free(D);
- break;
- }
- free(B);
- }
- /*=============================poly start=========================*/
- /* userSize;
- * Return maximum size, in points, of width and height supplied
- * by user, if any. Return 0 otherwise.
- */
- static double userSize(node_t * n)
- {
- double w, h;
- w = late_double(n, N_width, 0.0, MIN_NODEWIDTH);
- h = late_double(n, N_height, 0.0, MIN_NODEHEIGHT);
- return INCH2PS(fmax(w, h));
- }
- shape_kind shapeOf(node_t * n)
- {
- shape_desc *sh = ND_shape(n);
- void (*ifn) (node_t *);
- if (!sh)
- return SH_UNSET;
- ifn = ND_shape(n)->fns->initfn;
- if (ifn == poly_init)
- return SH_POLY;
- else if (ifn == record_init)
- return SH_RECORD;
- else if (ifn == point_init)
- return SH_POINT;
- else if (ifn == epsf_init)
- return SH_EPSF;
- else
- return SH_UNSET;
- }
- bool isPolygon(node_t * n)
- {
- return ND_shape(n) && ND_shape(n)->fns->initfn == poly_init;
- }
- static void poly_init(node_t * n)
- {
- pointf dimen, min_bb;
- pointf outline_bb;
- point imagesize;
- pointf *vertices;
- char *p, *sfile, *fxd;
- double temp, alpha, beta, gamma;
- double orientation, distortion, skew;
- double scalex, scaley;
- double width, height, marginx, marginy, spacex;
- polygon_t *poly = gv_alloc(sizeof(polygon_t));
- bool isPlain = IS_PLAIN(n);
- bool regular = !!ND_shape(n)->polygon->regular;
- size_t peripheries = ND_shape(n)->polygon->peripheries;
- size_t sides = ND_shape(n)->polygon->sides;
- orientation = ND_shape(n)->polygon->orientation;
- skew = ND_shape(n)->polygon->skew;
- distortion = ND_shape(n)->polygon->distortion;
- regular |= mapbool(agget(n, "regular"));
- /* all calculations in floating point POINTS */
- /* make x and y dimensions equal if node is regular
- * If the user has specified either width or height, use the max.
- * Else use minimum default value.
- * If node is not regular, use the current width and height.
- */
- if (isPlain) {
- width = height = 0;
- }
- else if (regular) {
- double sz = userSize(n);
- if (sz > 0.0)
- width = height = sz;
- else {
- width = ND_width(n);
- height = ND_height(n);
- width = height = INCH2PS(fmin(width, height));
- }
- } else {
- width = INCH2PS(ND_width(n));
- height = INCH2PS(ND_height(n));
- }
- peripheries = (size_t)late_int(n, N_peripheries, (int)peripheries, 0);
- orientation += late_double(n, N_orientation, 0.0, -360.0);
- if (sides == 0) { /* not for builtins */
- skew = late_double(n, N_skew, 0.0, -100.0);
- sides = (size_t)late_int(n, N_sides, 4, 0);
- distortion = late_double(n, N_distortion, 0.0, -100.0);
- }
- /* get label dimensions */
- dimen = ND_label(n)->dimen;
- /* minimal whitespace around label */
- if (dimen.x > 0 || dimen.y > 0) {
- /* padding */
- if (!isPlain) {
- if ((p = agget(n, "margin"))) {
- marginx = marginy = 0;
- const int i = sscanf(p, "%lf,%lf", &marginx, &marginy);
- marginx = fmax(marginx, 0);
- marginy = fmax(marginy, 0);
- if (i > 0) {
- dimen.x += 2 * INCH2PS(marginx);
- if (i > 1)
- dimen.y += 2 * INCH2PS(marginy);
- else
- dimen.y += 2 * INCH2PS(marginx);
- } else
- PAD(dimen);
- } else
- PAD(dimen);
- }
- }
- spacex = dimen.x - ND_label(n)->dimen.x;
- /* quantization */
- if ((temp = GD_drawing(agraphof(n))->quantum) > 0.0) {
- temp = INCH2PS(temp);
- dimen.x = quant(dimen.x, temp);
- dimen.y = quant(dimen.y, temp);
- }
- imagesize.x = imagesize.y = 0;
- if (ND_shape(n)->usershape) {
- /* custom requires a shapefile
- * not custom is an adaptable user shape such as a postscript
- * function.
- */
- if (streq(ND_shape(n)->name, "custom")) {
- sfile = agget(n, "shapefile");
- imagesize = gvusershape_size(agraphof(n), sfile);
- if (imagesize.x == -1 && imagesize.y == -1) {
- agwarningf(
- "No or improper shapefile=\"%s\" for node \"%s\"\n",
- sfile ? sfile : "<nil>", agnameof(n));
- imagesize.x = imagesize.y = 0;
- } else {
- GD_has_images(agraphof(n)) = true;
- imagesize.x += 2; /* some fixed padding */
- imagesize.y += 2;
- }
- }
- } else if ((sfile = agget(n, "image")) && *sfile != '\0') {
- imagesize = gvusershape_size(agraphof(n), sfile);
- if (imagesize.x == -1 && imagesize.y == -1) {
- agwarningf(
- "No or improper image=\"%s\" for node \"%s\"\n",
- sfile ? sfile : "<nil>", agnameof(n));
- imagesize.x = imagesize.y = 0;
- } else {
- GD_has_images(agraphof(n)) = true;
- imagesize.x += 2; /* some fixed padding */
- imagesize.y += 2;
- }
- }
- /* initialize node bb to labelsize */
- pointf bb = {.x = fmax(dimen.x, imagesize.x),
- .y = fmax(dimen.y, imagesize.y)};
- /* I don't know how to distort or skew ellipses in postscript */
- /* Convert request to a polygon with a large number of sides */
- if (sides <= 2 &&
- (!is_exactly_zero(distortion) || !is_exactly_zero(skew))) {
- sides = 120;
- }
- /* extra sizing depends on if label is centered vertically */
- p = agget(n, "labelloc");
- if (p && (p[0] == 't' || p[0] == 'b'))
- ND_label(n)->valign = p[0];
- else
- ND_label(n)->valign = 'c';
- const bool isBox = sides == 4 && fabs(fmod(orientation, 90)) < 0.5
- && is_exactly_zero(distortion) && is_exactly_zero(skew);
- if (isBox) {
- /* for regular boxes the fit should be exact */
- } else if (ND_shape(n)->polygon->vertices) {
- poly_desc_t* pd = (poly_desc_t*)ND_shape(n)->polygon->vertices;
- bb = pd->size_gen(bb);
- } else {
- /* for all other shapes, compute a smallest ellipse
- * containing bb centered on the origin, and then pad for that.
- * We assume the ellipse is defined by a scaling up of bb.
- */
- temp = bb.y * SQRT2;
- if (height > temp && ND_label(n)->valign == 'c') {
- /* if there is height to spare
- * and the label is centered vertically
- * then just pad x in proportion to the spare height */
- bb.x *= sqrt(1. / (1. - SQR(bb.y / height)));
- } else {
- bb.x *= SQRT2;
- bb.y = temp;
- }
- #if 1
- if (sides > 2) {
- temp = cos(M_PI / (double)sides);
- bb.x /= temp;
- bb.y /= temp;
- /* FIXME - for odd-sided polygons, e.g. triangles, there
- would be a better fit with some vertical adjustment of the shape */
- }
- #endif
- }
- /* at this point, bb is the minimum size of node that can hold the label */
- min_bb = bb;
- /* increase node size to width/height if needed */
- fxd = late_string(n, N_fixed, "false");
- if (*fxd == 's' && streq(fxd,"shape")) {
- bb = (pointf){.x = width, .y = height};
- poly->option.fixedshape = true;
- } else if (mapbool(fxd)) {
- /* check only label, as images we can scale to fit */
- if (width < ND_label(n)->dimen.x || height < ND_label(n)->dimen.y)
- agwarningf(
- "node '%s', graph '%s' size too small for label\n",
- agnameof(n), agnameof(agraphof(n)));
- bb = (pointf){.x = width, .y = height};
- } else {
- bb.x = width = fmax(width, bb.x);
- bb.y = height = fmax(height, bb.y);
- }
- /* If regular, make dimensions the same.
- * Need this to guarantee final node size is regular.
- */
- if (regular) {
- width = height = bb.x = bb.y = fmax(bb.x, bb.y);
- }
- /* Compute space available for label. Provides the justification borders */
- if (!mapbool(late_string(n, N_nojustify, "false"))) {
- if (isBox) {
- ND_label(n)->space.x = fmax(dimen.x, bb.x) - spacex;
- }
- else if (dimen.y < bb.y) {
- temp = bb.x * sqrt(1.0 - SQR(dimen.y) / SQR(bb.y));
- ND_label(n)->space.x = fmax(dimen.x, temp) - spacex;
- }
- else
- ND_label(n)->space.x = dimen.x - spacex;
- } else {
- ND_label(n)->space.x = dimen.x - spacex;
- }
- if (!poly->option.fixedshape) {
- temp = bb.y - min_bb.y;
- if (dimen.y < imagesize.y)
- temp += imagesize.y - dimen.y;
- ND_label(n)->space.y = dimen.y + temp;
- }
- const double penwidth = late_int(n, N_penwidth, DEFAULT_NODEPENWIDTH, MIN_NODEPENWIDTH);
- size_t outp = peripheries;
- if (peripheries < 1)
- outp = 1;
- if (peripheries >= 1 && penwidth > 0) {
- // allocate extra vertices representing the outline, i.e., the outermost
- // periphery with penwidth taken into account
- ++outp;
- }
- if (sides < 3) { /* ellipses */
- sides = 2;
- vertices = gv_calloc(outp * sides, sizeof(pointf));
- pointf P = {.x = bb.x / 2., .y = bb.y / 2.};
- vertices[0] = (pointf){.x = -P.x, .y = -P.y};
- vertices[1] = P;
- if (peripheries > 1) {
- for (size_t j = 1, i = 2; j < peripheries; j++) {
- P.x += GAP;
- P.y += GAP;
- vertices[i] = (pointf){.x = -P.x, .y = -P.y};
- i++;
- vertices[i] = P;
- i++;
- }
- bb.x = 2. * P.x;
- bb.y = 2. * P.y;
- }
- outline_bb = bb;
- if (outp > peripheries) {
- // add an outline at half the penwidth outside the outermost periphery
- P.x += penwidth / 2;
- P.y += penwidth / 2;
- size_t i = sides * peripheries;
- vertices[i] = (pointf){.x = -P.x, .y = -P.y};
- i++;
- vertices[i] = P;
- i++;
- outline_bb.x = 2. * P.x;
- outline_bb.y = 2. * P.y;
- }
- } else {
- /*
- * FIXME - this code is wrong - it doesn't work for concave boundaries.
- * (e.g. "folder" or "promoter")
- * I don't think it even needs sectorangle, or knowledge of skewed shapes.
- * (Concepts that only work for convex regular (modulo skew/distort) polygons.)
- *
- * I think it only needs to know inside v. outside (by always drawing
- * boundaries clockwise, say), and the two adjacent segments.
- *
- * It needs to find the point where the two lines, parallel to
- * the current segments, and outside by GAP distance, intersect.
- */
- double sinx = 0, cosx = 0, xmax, ymax;
- vertices = gv_calloc(outp * sides, sizeof(pointf));
- if (ND_shape(n)->polygon->vertices) {
- poly_desc_t* pd = (poly_desc_t*)ND_shape(n)->polygon->vertices;
- pd->vertex_gen (vertices, &bb);
- xmax = bb.x/2;
- ymax = bb.y/2;
- } else {
- double angle, sectorangle, sidelength, skewdist, gdistortion, gskew;
- sectorangle = 2. * M_PI / (double)sides;
- sidelength = sin(sectorangle / 2.);
- skewdist = hypot(fabs(distortion) + fabs(skew), 1.);
- gdistortion = distortion * SQRT2 / cos(sectorangle / 2.);
- gskew = skew / 2.;
- angle = (sectorangle - M_PI) / 2.;
- sinx = sin(angle);
- cosx = cos(angle);
- pointf R = {.x = .5 * cosx, .y = .5 * sinx};
- xmax = ymax = 0.;
- angle += (M_PI - sectorangle) / 2.;
- for (size_t i = 0; i < sides; i++) {
- /*next regular vertex */
- angle += sectorangle;
- sinx = sin(angle);
- cosx = cos(angle);
- R.x += sidelength * cosx;
- R.y += sidelength * sinx;
- /*distort and skew */
- pointf P = {
- .x = R.x * (skewdist + R.y * gdistortion) + R.y * gskew,
- .y = R.y};
- /*orient P.x,P.y */
- alpha = RADIANS(orientation) + atan2(P.y, P.x);
- sinx = sin(alpha);
- cosx = cos(alpha);
- P.x = P.y = hypot(P.x, P.y);
- P.x *= cosx;
- P.y *= sinx;
- /*scale for label */
- P.x *= bb.x;
- P.y *= bb.y;
- /*find max for bounding box */
- xmax = fmax(fabs(P.x), xmax);
- ymax = fmax(fabs(P.y), ymax);
- /* store result in array of points */
- vertices[i] = P;
- if (isBox) { /* enforce exact symmetry of box */
- vertices[1] = (pointf){.x = -P.x, .y = P.y};
- vertices[2] = (pointf){.x = -P.x, .y = -P.y};
- vertices[3] = (pointf){.x = P.x, .y = -P.y};
- break;
- }
- }
- }
- /* apply minimum dimensions */
- xmax *= 2.;
- ymax *= 2.;
- bb = (pointf){.x = fmax(width, xmax), .y = fmax(height, ymax)};
- outline_bb = bb;
- scalex = bb.x / xmax;
- scaley = bb.y / ymax;
- size_t i;
- for (i = 0; i < sides; i++) {
- pointf P = vertices[i];
- P.x *= scalex;
- P.y *= scaley;
- vertices[i] = P;
- }
- if (outp > 1) {
- pointf R = vertices[0];
- pointf Q;
- for (size_t j = 1; j < sides; j++) {
- Q = vertices[(i - j) % sides];
- if (!is_exactly_equal(Q.x, R.x) || !is_exactly_equal(Q.y, R.y)) {
- break;
- }
- }
- assert(!is_exactly_equal(R.x, Q.x) || !is_exactly_equal(R.y, Q.y));
- beta = atan2(R.y - Q.y, R.x - Q.x);
- pointf Qprev = Q;
- for (i = 0; i < sides; i++) {
- /*for each vertex find the bisector */
- Q = vertices[i];
- if (is_exactly_equal(Q.x, Qprev.x) && is_exactly_equal(Q.y, Qprev.y)) {
- // The vertex points for the side ending at Q are equal,
- // i.e. this side is actually a point and its angle is
- // undefined. Therefore we keep the same offset for the end
- // point as already calculated for the start point. This may
- // occur for shapes which are represented as polygons during
- // layout, but are drawn using bezier curves during
- // rendering, e.g. for the `cylinder` shape.
- } else {
- for (size_t j = 1; j < sides; j++) {
- R = vertices[(i + j) % sides];
- if (!is_exactly_equal(R.x, Q.x) || !is_exactly_equal(R.y, Q.y)) {
- break;
- }
- }
- assert(!is_exactly_equal(R.x, Q.x) || !is_exactly_equal(R.y, Q.y));
- alpha = beta;
- beta = atan2(R.y - Q.y, R.x - Q.x);
- gamma = (alpha + M_PI - beta) / 2.;
- /*find distance along bisector to */
- /*intersection of next periphery */
- temp = GAP / sin(gamma);
- /*convert this distance to x and y */
- sinx = sin(alpha - gamma) * temp;
- cosx = cos(alpha - gamma) * temp;
- }
- assert(cosx != 0 || sinx != 0);
- Qprev = Q;
- /*save the vertices of all the */
- /*peripheries at this base vertex */
- for (size_t j = 1; j < peripheries; j++) {
- Q.x += cosx;
- Q.y += sinx;
- vertices[i + j * sides] = Q;
- }
- if (outp > peripheries) {
- // add an outline at half the penwidth outside the outermost periphery
- Q.x += cosx * penwidth / 2 / GAP;
- Q.y += sinx * penwidth / 2 / GAP;
- vertices[i + peripheries * sides] = Q;
- }
- }
- for (i = 0; i < sides; i++) {
- pointf P = vertices[i + (peripheries - 1) * sides];
- bb = (pointf){.x = fmax(2.0 * fabs(P.x), bb.x),
- .y = fmax(2.0 * fabs(P.y), bb.y)};
- Q = vertices[i + (outp - 1) * sides];
- outline_bb = (pointf){.x = fmax(2.0 * fabs(Q.x), outline_bb.x),
- .y = fmax(2.0 * fabs(Q.y), outline_bb.y)};
- }
- }
- }
- poly->regular = regular;
- poly->peripheries = peripheries;
- poly->sides = sides;
- poly->orientation = orientation;
- poly->skew = skew;
- poly->distortion = distortion;
- poly->vertices = vertices;
- if (poly->option.fixedshape) {
- /* set width and height to reflect label and shape */
- ND_width(n) = PS2INCH(fmax(dimen.x, bb.x));
- ND_height(n) = PS2INCH(fmax(dimen.y, bb.y));
- ND_outline_width(n) = PS2INCH(fmax(dimen.x, outline_bb.x));
- ND_outline_height(n) = PS2INCH(fmax(dimen.y, outline_bb.y));
- } else {
- ND_width(n) = PS2INCH(bb.x);
- ND_height(n) = PS2INCH(bb.y);
- ND_outline_width(n) = PS2INCH(outline_bb.x);
- ND_outline_height(n) = PS2INCH(outline_bb.y);
- }
- ND_shape_info(n) = poly;
- }
- static void poly_free(node_t * n)
- {
- polygon_t *p = ND_shape_info(n);
- if (p) {
- free(p->vertices);
- free(p);
- }
- }
- /* poly_inside:
- * Return true if point p is inside polygonal shape of node inside_context->s.n.
- * Calculations are done using unrotated node shape. Thus, if p is in a rotated
- * coordinate system, it is reset as P in the unrotated coordinate system. Similarly,
- * the ND_rw, ND_lw and ND_ht values are rotated if the graph is flipped.
- */
- static bool poly_inside(inside_t * inside_context, pointf p)
- {
- size_t sides;
- const pointf O = {0};
- pointf *vertex = NULL;
- int s;
- pointf P, Q, R;
- boxf *bp;
- node_t *n;
- if (!inside_context) {
- return false;
- }
- bp = inside_context->s.bp;
- n = inside_context->s.n;
- P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n)));
- /* Quick test if port rectangle is target */
- if (bp) {
- boxf bbox = *bp;
- return INSIDE(P, bbox);
- }
- if (n != inside_context->s.lastn) {
- double n_width, n_height;
- double n_outline_width;
- double n_outline_height;
- inside_context->s.last_poly = ND_shape_info(n);
- vertex = inside_context->s.last_poly->vertices;
- sides = inside_context->s.last_poly->sides;
- double xsize, ysize;
- if (inside_context->s.last_poly->option.fixedshape) {
- boxf bb = polyBB(inside_context->s.last_poly);
- n_width = bb.UR.x - bb.LL.x;
- n_height = bb.UR.y - bb.LL.y;
- n_outline_width = n_width;
- n_outline_height = n_height;
- /* get point and node size adjusted for rankdir=LR */
- if (GD_flip(agraphof(n))) {
- ysize = n_width;
- xsize = n_height;
- } else {
- xsize = n_width;
- ysize = n_height;
- }
- } else {
- /* get point and node size adjusted for rankdir=LR */
- if (GD_flip(agraphof(n))) {
- ysize = ND_lw(n) + ND_rw(n);
- xsize = ND_ht(n);
- } else {
- xsize = ND_lw(n) + ND_rw(n);
- ysize = ND_ht(n);
- }
- n_width = INCH2PS(ND_width(n));
- n_height = INCH2PS(ND_height(n));
- n_outline_width = INCH2PS(ND_outline_width(n));
- n_outline_height = INCH2PS(ND_outline_height(n));
- }
- /* scale */
- inside_context->s.scalex = n_width;
- if (!is_exactly_zero(xsize))
- inside_context->s.scalex /= xsize;
- inside_context->s.scaley = n_height;
- if (!is_exactly_zero(ysize))
- inside_context->s.scaley /= ysize;
- inside_context->s.box_URx = n_outline_width / 2;
- inside_context->s.box_URy = n_outline_height / 2;
- const double penwidth = late_int(n, N_penwidth, DEFAULT_NODEPENWIDTH, MIN_NODEPENWIDTH);
- if (inside_context->s.last_poly->peripheries >= 1 && penwidth > 0) {
- /* index to outline, i.e., the outer-periphery with penwidth taken into account */
- inside_context->s.outp =
- (inside_context->s.last_poly->peripheries + 1 - 1) * sides;
- } else if (inside_context->s.last_poly->peripheries < 1) {
- inside_context->s.outp = 0;
- } else {
- /* index to outer-periphery */
- inside_context->s.outp =
- (inside_context->s.last_poly->peripheries - 1) * sides;
- }
- inside_context->s.lastn = n;
- } else {
- vertex = inside_context->s.last_poly->vertices;
- sides = inside_context->s.last_poly->sides;
- }
- /* scale */
- P.x *= inside_context->s.scalex;
- P.y *= inside_context->s.scaley;
- /* inside bounding box? */
- if (fabs(P.x) > inside_context->s.box_URx ||
- fabs(P.y) > inside_context->s.box_URy)
- return false;
- /* ellipses */
- if (sides <= 2)
- return hypot(P.x / inside_context->s.box_URx,
- P.y / inside_context->s.box_URy) < 1;
- /* use fast test in case we are converging on a segment */
- size_t i = inside_context->s.last % sides; // in case last left over from larger polygon
- size_t i1 = (i + 1) % sides;
- Q = vertex[i + inside_context->s.outp];
- R = vertex[i1 + inside_context->s.outp];
- if (!same_side(P, O, Q, R)) /* false if outside the segment's face */
- return false;
- /* else inside the segment face... */
- if ((s = same_side(P, Q, R, O)) && same_side(P, R, O, Q)) /* true if between the segment's sides */
- return true;
- /* else maybe in another segment */
- for (size_t j = 1; j < sides; j++) { // iterate over remaining segments
- if (s) { /* clockwise */
- i = i1;
- i1 = (i + 1) % sides;
- } else { /* counter clockwise */
- i1 = i;
- i = (i + sides - 1) % sides;
- }
- if (!same_side(P, O, vertex[i + inside_context->s.outp],
- vertex[i1 + inside_context->s.outp])) { // false if outside any other segment’s face
- inside_context->s.last = i;
- return false;
- }
- }
- /* inside all segments' faces */
- inside_context->s.last = i; // in case next edge is to same side
- return true;
- }
- static int poly_path(node_t * n, port * p, int side, boxf rv[], int *kptr)
- {
- (void)n;
- (void)p;
- (void)side;
- (void)rv;
- (void)kptr;
- return 0;
- }
- static unsigned char invflip_side(unsigned char side, int rankdir) {
- switch (rankdir) {
- case RANKDIR_TB:
- break;
- case RANKDIR_BT:
- switch (side) {
- case TOP:
- side = BOTTOM;
- break;
- case BOTTOM:
- side = TOP;
- break;
- default:
- break;
- }
- break;
- case RANKDIR_LR:
- switch (side) {
- case TOP:
- side = RIGHT;
- break;
- case BOTTOM:
- side = LEFT;
- break;
- case LEFT:
- side = TOP;
- break;
- case RIGHT:
- side = BOTTOM;
- break;
- default:
- break;
- }
- break;
- case RANKDIR_RL:
- switch (side) {
- case TOP:
- side = RIGHT;
- break;
- case BOTTOM:
- side = LEFT;
- break;
- case LEFT:
- side = BOTTOM;
- break;
- case RIGHT:
- side = TOP;
- break;
- default:
- break;
- }
- break;
- default:
- UNREACHABLE();
- }
- return side;
- }
- static double invflip_angle(double angle, int rankdir)
- {
- switch (rankdir) {
- case RANKDIR_TB:
- break;
- case RANKDIR_BT:
- angle *= -1;
- break;
- case RANKDIR_LR:
- angle -= M_PI * 0.5;
- break;
- case RANKDIR_RL:
- if (angle == M_PI)
- angle = -0.5 * M_PI;
- else if (angle == M_PI * 0.75)
- angle = -0.25 * M_PI;
- else if (angle == M_PI * 0.5)
- angle = 0;
- else if (angle == 0)
- angle = M_PI * 0.5;
- else if (angle == M_PI * -0.25)
- angle = M_PI * 0.75;
- else if (angle == M_PI * -0.5)
- angle = M_PI;
- break;
- default:
- UNREACHABLE();
- }
- return angle;
- }
- /* compassPoint:
- * Compute compass points for non-trivial shapes.
- * It finds where the ray ((0,0),(x,y)) hits the boundary and
- * returns it.
- * Assumes ictxt and ictxt->n are non-NULL.
- *
- * bezier_clip uses the shape's _inside function, which assumes the input
- * point is in the rotated coordinate system (as determined by rankdir), so
- * it rotates the point counterclockwise based on rankdir to get the node's
- * coordinate system.
- * To handle this, if rankdir is set, we rotate (x,y) clockwise, and then
- * rotate the answer counterclockwise.
- */
- static pointf compassPoint(inside_t * ictxt, double y, double x)
- {
- pointf curve[4]; /* bezier control points for a straight line */
- node_t *n = ictxt->s.n;
- graph_t* g = agraphof(n);
- int rd = GD_rankdir(g);
- pointf p;
- p.x = x;
- p.y = y;
- if (rd)
- p = cwrotatepf(p, 90 * rd);
- curve[0].x = curve[0].y = 0;
- curve[1] = curve[0];
- curve[3] = curve[2] = p;
- bezier_clip(ictxt, ND_shape(n)->fns->insidefn, curve, true);
- if (rd)
- curve[0] = ccwrotatepf(curve[0], 90 * rd);
- return curve[0];
- }
- /* compassPort:
- * Attach a compass point to a port pp, and fill in remaining fields.
- * n is the corresponding node; bp is the bounding box of the port.
- * compass is the compass point
- * Return 1 if unrecognized compass point, in which case we
- * use the center.
- *
- * This function also finishes initializing the port structure,
- * even if no compass point is involved.
- * The sides value gives the set of sides shared by the port. This
- * is used with a compass point to indicate if the port is exposed, to
- * set the port's side value.
- *
- * If ictxt is NULL, we are working with a simple rectangular shape (node or
- * port of record of HTML label), so compass points are trivial. If ictxt is
- * not NULL, it provides shape information so that the compass point can be
- * calculated based on the shape.
- *
- * The code assumes the node has its unrotated shape to find the points,
- * angles, etc. At the end, the parameters are adjusted to take into account
- * the rankdir attribute. In particular, the first if-else statement flips
- * the already adjusted ND_ht, ND_lw and ND_rw back to non-flipped values.
- *
- */
- static int compassPort(node_t *n, boxf *bp, port *pp, const char *compass,
- unsigned char sides, inside_t * ictxt) {
- boxf b;
- pointf p, ctr;
- int rv = 0;
- double theta = 0.0;
- bool constrain = false;
- bool dyna = false;
- unsigned char side = 0;
- bool clip = true;
- bool defined;
- double maxv; /* sufficiently large value outside of range of node */
- if (bp) {
- b = *bp;
- p = (pointf){(b.LL.x + b.UR.x) / 2, (b.LL.y + b.UR.y) / 2};
- defined = true;
- } else {
- p.x = p.y = 0.;
- if (GD_flip(agraphof(n))) {
- b.UR.x = ND_ht(n) / 2.;
- b.LL.x = -b.UR.x;
- b.UR.y = ND_lw(n);
- b.LL.y = -b.UR.y;
- } else {
- b.UR.y = ND_ht(n) / 2.;
- b.LL.y = -b.UR.y;
- b.UR.x = ND_lw(n);
- b.LL.x = -b.UR.x;
- }
- defined = false;
- }
- maxv = fmax(b.UR.x, b.UR.y);
- maxv *= 4.0;
- ctr = p;
- if (compass && *compass) {
- switch (*compass++) {
- case 'e':
- if (*compass)
- rv = 1;
- else {
- if (ictxt)
- p = compassPoint(ictxt, ctr.y, maxv);
- else
- p.x = b.UR.x;
- theta = 0.0;
- constrain = true;
- defined = true;
- clip = false;
- side = sides & RIGHT;
- }
- break;
- case 's':
- p.y = b.LL.y;
- constrain = true;
- clip = false;
- switch (*compass) {
- case '\0':
- theta = -M_PI * 0.5;
- defined = true;
- if (ictxt)
- p = compassPoint(ictxt, -maxv, ctr.x);
- else
- p.x = ctr.x;
- side = sides & BOTTOM;
- break;
- case 'e':
- theta = -M_PI * 0.25;
- defined = true;
- if (ictxt)
- p = compassPoint(ictxt, -maxv, maxv);
- else
- p.x = b.UR.x;
- side = sides & (BOTTOM | RIGHT);
- break;
- case 'w':
- theta = -M_PI * 0.75;
- defined = true;
- if (ictxt)
- p = compassPoint(ictxt, -maxv, -maxv);
- else
- p.x = b.LL.x;
- side = sides & (BOTTOM | LEFT);
- break;
- default:
- p.y = ctr.y;
- constrain = false;
- clip = true;
- rv = 1;
- break;
- }
- break;
- case 'w':
- if (*compass)
- rv = 1;
- else {
- if (ictxt)
- p = compassPoint(ictxt, ctr.y, -maxv);
- else
- p.x = b.LL.x;
- theta = M_PI;
- constrain = true;
- defined = true;
- clip = false;
- side = sides & LEFT;
- }
- break;
- case 'n':
- p.y = b.UR.y;
- constrain = true;
- clip = false;
- switch (*compass) {
- case '\0':
- defined = true;
- theta = M_PI * 0.5;
- if (ictxt)
- p = compassPoint(ictxt, maxv, ctr.x);
- else
- p.x = ctr.x;
- side = sides & TOP;
- break;
- case 'e':
- defined = true;
- theta = M_PI * 0.25;
- if (ictxt)
- p = compassPoint(ictxt, maxv, maxv);
- else
- p.x = b.UR.x;
- side = sides & (TOP | RIGHT);
- break;
- case 'w':
- defined = true;
- theta = M_PI * 0.75;
- if (ictxt)
- p = compassPoint(ictxt, maxv, -maxv);
- else
- p.x = b.LL.x;
- side = sides & (TOP | LEFT);
- break;
- default:
- p.y = ctr.y;
- constrain = false;
- clip = true;
- rv = 1;
- break;
- }
- break;
- case '_':
- dyna = true;
- side = sides;
- break;
- case 'c':
- break;
- default:
- rv = 1;
- break;
- }
- }
- p = cwrotatepf(p, 90 * GD_rankdir(agraphof(n)));
- if (dyna)
- pp->side = side;
- else
- pp->side = invflip_side(side, GD_rankdir(agraphof(n)));
- pp->bp = bp;
- pp->p = p;
- pp->theta = invflip_angle(theta, GD_rankdir(agraphof(n)));
- if (p.x == 0 && p.y == 0)
- pp->order = MC_SCALE / 2;
- else {
- /* compute angle with 0 at north pole, increasing CCW */
- double angle = atan2(p.y, p.x) + 1.5 * M_PI;
- if (angle >= 2 * M_PI)
- angle -= 2 * M_PI;
- pp->order = (int) (MC_SCALE * angle / (2 * M_PI));
- }
- pp->constrained = constrain;
- pp->defined = defined;
- pp->clip = clip;
- pp->dyna = dyna;
- return rv;
- }
- static port poly_port(node_t * n, char *portname, char *compass)
- {
- port rv;
- boxf *bp;
- unsigned char sides; // bitmap of which sides the port lies along
- if (portname[0] == '\0')
- return Center;
- if (compass == NULL)
- compass = "_";
- sides = BOTTOM | RIGHT | TOP | LEFT;
- if (ND_label(n)->html && (bp = html_port(n, portname, &sides))) {
- if (compassPort(n, bp, &rv, compass, sides, NULL)) {
- agwarningf(
- "node %s, port %s, unrecognized compass point '%s' - ignored\n",
- agnameof(n), portname, compass);
- }
- } else {
- inside_t *ictxtp;
- inside_t ictxt = {0};
- if (IS_BOX(n))
- ictxtp = NULL;
- else {
- ictxt.s.n = n;
- ictxt.s.bp = NULL;
- ictxtp = &ictxt;
- }
- if (compassPort(n, NULL, &rv, portname, sides, ictxtp))
- unrecognized(n, portname);
- }
- rv.name = NULL;
- return rv;
- }
- static bool multicolor(const char *f) {
- return strchr(f, ':') != NULL;
- }
- /* generic polygon gencode routine */
- static void poly_gencode(GVJ_t * job, node_t * n)
- {
- obj_state_t *obj = job->obj;
- polygon_t *poly;
- double xsize, ysize;
- pointf P, *vertices;
- int filled;
- bool usershape_p;
- bool pfilled; /* true if fill not handled by user shape */
- char *color, *name;
- int doMap = (obj->url || obj->explicit_tooltip);
- char* fillcolor=NULL;
- char* pencolor=NULL;
- if (doMap && !(job->flags & EMIT_CLUSTERS_LAST))
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- poly = ND_shape_info(n);
- vertices = poly->vertices;
- const size_t sides = poly->sides;
- size_t peripheries = poly->peripheries;
- pointf *AF = gv_calloc(sides + 5, sizeof(pointf));
- /* nominal label position in the center of the node */
- ND_label(n)->pos = ND_coord(n);
- xsize = (ND_lw(n) + ND_rw(n)) / INCH2PS(ND_width(n));
- ysize = ND_ht(n) / INCH2PS(ND_height(n));
- const graphviz_polygon_style_t style = stylenode(job, n);
- char *clrs[2] = {0};
- if (ND_gui_state(n) & GUI_STATE_ACTIVE) {
- pencolor = DEFAULT_ACTIVEPENCOLOR;
- gvrender_set_pencolor(job, pencolor);
- color = DEFAULT_ACTIVEFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- filled = FILL;
- } else if (ND_gui_state(n) & GUI_STATE_SELECTED) {
- pencolor = DEFAULT_SELECTEDPENCOLOR;
- gvrender_set_pencolor(job, pencolor);
- color = DEFAULT_SELECTEDFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- filled = FILL;
- } else if (ND_gui_state(n) & GUI_STATE_DELETED) {
- pencolor = DEFAULT_DELETEDPENCOLOR;
- gvrender_set_pencolor(job, pencolor);
- color = DEFAULT_DELETEDFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- filled = FILL;
- } else if (ND_gui_state(n) & GUI_STATE_VISITED) {
- pencolor = DEFAULT_VISITEDPENCOLOR;
- gvrender_set_pencolor(job, pencolor);
- color = DEFAULT_VISITEDFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- filled = FILL;
- } else {
- if (style.filled) {
- double frac;
- fillcolor = findFill (n);
- if (findStopColor (fillcolor, clrs, &frac)) {
- gvrender_set_fillcolor(job, clrs[0]);
- if (clrs[1])
- gvrender_set_gradient_vals(job,clrs[1],late_int(n,N_gradientangle,0,0), frac);
- else
- gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(n,N_gradientangle,0,0), frac);
- if (style.radial)
- filled = RGRADIENT;
- else
- filled = GRADIENT;
- }
- else {
- gvrender_set_fillcolor(job, fillcolor);
- filled = FILL;
- }
- }
- else if (style.striped || style.wedged) {
- fillcolor = findFill (n);
- filled = 1;
- }
- else {
- filled = 0;
- }
- pencolor = penColor(job, n); /* emit pen color */
- }
- pfilled = !ND_shape(n)->usershape || streq(ND_shape(n)->name, "custom");
- /* if no boundary but filled, set boundary color to transparent */
- if (peripheries == 0 && filled != 0 && pfilled) {
- peripheries = 1;
- gvrender_set_pencolor(job, "transparent");
- }
- /* draw peripheries first */
- size_t j;
- for (j = 0; j < peripheries; j++) {
- for (size_t i = 0; i < sides; i++) {
- P = vertices[i + j * sides];
- AF[i].x = P.x * xsize + ND_coord(n).x;
- AF[i].y = P.y * ysize + ND_coord(n).y;
- }
- if (sides <= 2) {
- if (style.wedged && j == 0 && multicolor(fillcolor)) {
- int rv = wedgedEllipse (job, AF, fillcolor);
- if (rv > 1)
- agerr (AGPREV, "in node %s\n", agnameof(n));
- filled = 0;
- }
- gvrender_ellipse(job, AF, filled);
- if (style.diagonals) {
- Mcircle_hack(job, n);
- }
- } else if (style.striped) {
- if (j == 0) {
- int rv = stripedBox (job, AF, fillcolor, 1);
- if (rv > 1)
- agerr (AGPREV, "in node %s\n", agnameof(n));
- }
- gvrender_polygon(job, AF, sides, 0);
- } else if (style.underline) {
- gvrender_set_pencolor(job, "transparent");
- gvrender_polygon(job, AF, sides, filled);
- gvrender_set_pencolor(job, pencolor);
- gvrender_polyline(job, AF+2, 2);
- } else if (SPECIAL_CORNERS(style)) {
- round_corners(job, AF, sides, style, filled);
- } else {
- gvrender_polygon(job, AF, sides, filled);
- }
- /* fill innermost periphery only */
- filled = 0;
- }
- usershape_p = false;
- if (ND_shape(n)->usershape) {
- name = ND_shape(n)->name;
- if (streq(name, "custom")) {
- if ((name = agget(n, "shapefile")) && name[0])
- usershape_p = true;
- } else
- usershape_p = true;
- } else if ((name = agget(n, "image")) && name[0]) {
- usershape_p = true;
- }
- if (usershape_p) {
- /* get coords of innermost periphery */
- for (size_t i = 0; i < sides; i++) {
- P = vertices[i];
- AF[i].x = P.x * xsize + ND_coord(n).x;
- AF[i].y = P.y * ysize + ND_coord(n).y;
- }
- /* lay down fill first */
- if (filled != 0 && pfilled) {
- if (sides <= 2) {
- if (style.wedged && j == 0 && multicolor(fillcolor)) {
- int rv = wedgedEllipse (job, AF, fillcolor);
- if (rv > 1)
- agerr (AGPREV, "in node %s\n", agnameof(n));
- filled = 0;
- }
- gvrender_ellipse(job, AF, filled);
- if (style.diagonals) {
- Mcircle_hack(job, n);
- }
- } else if (style.striped) {
- int rv = stripedBox (job, AF, fillcolor, 1);
- if (rv > 1)
- agerr (AGPREV, "in node %s\n", agnameof(n));
- gvrender_polygon(job, AF, sides, 0);
- } else if (style.rounded || style.diagonals) {
- round_corners(job, AF, sides, style, filled);
- } else {
- gvrender_polygon(job, AF, sides, filled);
- }
- }
- gvrender_usershape(job, name, AF, sides, filled != 0,
- late_string(n, N_imagescale, "false"),
- late_string(n, N_imagepos, "mc"));
- filled = 0; /* with user shapes, we have done the fill if needed */
- }
- free(AF);
- free (clrs[0]);
- free (clrs[1]);
- emit_label(job, EMIT_NLABEL, ND_label(n));
- if (doMap) {
- if (job->flags & EMIT_CLUSTERS_LAST)
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- gvrender_end_anchor(job);
- }
- }
- /*=======================end poly======================================*/
- /*===============================point start========================*/
- /* point_init:
- * shorthand for shape=circle, style=filled, width=0.05, label=""
- */
- static void point_init(node_t * n)
- {
- polygon_t *poly = gv_alloc(sizeof(polygon_t));
- size_t sides, outp, peripheries = ND_shape(n)->polygon->peripheries;
- double sz;
- pointf P, *vertices;
- size_t i, j;
- double w, h;
- /* set width and height, and make them equal
- * if user has set weight or height, use it.
- * if both are set, use smallest.
- * if neither, use default
- */
- w = late_double(n, N_width, DBL_MAX, MIN_NODEWIDTH);
- h = late_double(n, N_height, DBL_MAX, MIN_NODEHEIGHT);
- w = fmin(w, h);
- if (is_exactly_equal(w, DBL_MAX) &&
- is_exactly_equal(h, DBL_MAX)) // neither defined
- ND_width(n) = ND_height(n) = DEF_POINT;
- else {
- w = fmin(w, h);
- /* If w == 0, use it; otherwise, make w no less than MIN_POINT due
- * to the restrictions mentioned above.
- */
- if (w > 0.0)
- w = fmax(w, MIN_POINT);
- ND_width(n) = ND_height(n) = w;
- }
- sz = ND_width(n) * POINTS_PER_INCH;
- peripheries = (size_t)late_int(n, N_peripheries, (int)peripheries, 0);
- if (peripheries < 1)
- outp = 1;
- else
- outp = peripheries;
- sides = 2;
- const double penwidth = late_int(n, N_penwidth, DEFAULT_NODEPENWIDTH, MIN_NODEPENWIDTH);
- if (peripheries >= 1 && penwidth > 0) {
- // allocate extra vertices representing the outline, i.e., the outermost
- // periphery with penwidth taken into account
- ++outp;
- }
- vertices = gv_calloc(outp * sides, sizeof(pointf));
- P.y = P.x = sz / 2.;
- vertices[0].x = -P.x;
- vertices[0].y = -P.y;
- vertices[1] = P;
- if (peripheries > 1) {
- for (j = 1, i = 2; j < peripheries; j++) {
- P.x += GAP;
- P.y += GAP;
- vertices[i].x = -P.x;
- vertices[i].y = -P.y;
- i++;
- vertices[i].x = P.x;
- vertices[i].y = P.y;
- i++;
- }
- sz = 2. * P.x;
- } else {
- i = sides;
- }
- if (peripheries >= 1 && penwidth > 0 && outp > peripheries) {
- // add an outline at half the penwidth outside the outermost periphery
- P.x += penwidth / 2;
- P.y += penwidth / 2;
- vertices[i].x = -P.x;
- vertices[i].y = -P.y;
- i++;
- vertices[i].x = P.x;
- vertices[i].y = P.y;
- i++;
- }
- const double sz_outline = 2. * P.x;
- poly->regular = true;
- poly->peripheries = peripheries;
- poly->sides = 2;
- poly->orientation = 0;
- poly->skew = 0;
- poly->distortion = 0;
- poly->vertices = vertices;
- ND_height(n) = ND_width(n) = PS2INCH(sz);
- ND_outline_height(n) = ND_outline_width(n) = PS2INCH(sz_outline);
- ND_shape_info(n) = poly;
- }
- static bool point_inside(inside_t * inside_context, pointf p)
- {
- pointf P;
- node_t *n;
- if (!inside_context) {
- return false;
- }
- n = inside_context->s.n;
- P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n)));
- if (n != inside_context->s.lastn) {
- size_t outp;
- polygon_t *poly = ND_shape_info(n);
- const size_t sides = 2;
- const double penwidth = late_int(n, N_penwidth, DEFAULT_NODEPENWIDTH, MIN_NODEPENWIDTH);
- if (poly->peripheries >= 1 && penwidth > 0) {
- /* index to outline, i.e., the outer-periphery with penwidth taken into account */
- outp = sides * (poly->peripheries + 1 - 1);
- } else if (poly->peripheries < 1) {
- outp = 0;
- } else {
- /* index to outer-periphery */
- outp = sides * (poly->peripheries - 1);
- }
- inside_context->s.radius = poly->vertices[outp + 1].x;
- inside_context->s.lastn = n;
- }
- /* inside bounding box? */
- if (fabs(P.x) > inside_context->s.radius ||
- fabs(P.y) > inside_context->s.radius)
- return false;
- return hypot(P.x, P.y) <= inside_context->s.radius;
- }
- static void point_gencode(GVJ_t * job, node_t * n)
- {
- obj_state_t *obj = job->obj;
- polygon_t *poly;
- pointf P, *vertices;
- bool filled;
- char *color;
- int doMap = obj->url || obj->explicit_tooltip;
- if (doMap && !(job->flags & EMIT_CLUSTERS_LAST))
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- poly = ND_shape_info(n);
- vertices = poly->vertices;
- const size_t sides = poly->sides;
- size_t peripheries = poly->peripheries;
- graphviz_polygon_style_t style = {0};
- checkStyle(n, &style);
- if (style.invisible)
- gvrender_set_style(job, point_style);
- else
- gvrender_set_style(job, &point_style[1]);
- if (N_penwidth)
- gvrender_set_penwidth(job, late_double(n, N_penwidth, 1.0, 0.0));
- if (ND_gui_state(n) & GUI_STATE_ACTIVE) {
- color = DEFAULT_ACTIVEPENCOLOR;
- gvrender_set_pencolor(job, color);
- color = DEFAULT_ACTIVEFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- } else if (ND_gui_state(n) & GUI_STATE_SELECTED) {
- color = DEFAULT_SELECTEDPENCOLOR;
- gvrender_set_pencolor(job, color);
- color = DEFAULT_SELECTEDFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- } else if (ND_gui_state(n) & GUI_STATE_DELETED) {
- color = DEFAULT_DELETEDPENCOLOR;
- gvrender_set_pencolor(job, color);
- color = DEFAULT_DELETEDFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- } else if (ND_gui_state(n) & GUI_STATE_VISITED) {
- color = DEFAULT_VISITEDPENCOLOR;
- gvrender_set_pencolor(job, color);
- color = DEFAULT_VISITEDFILLCOLOR;
- gvrender_set_fillcolor(job, color);
- } else {
- color = findFillDflt(n, "black");
- gvrender_set_fillcolor(job, color); /* emit fill color */
- penColor(job, n); /* emit pen color */
- }
- filled = true;
- /* if no boundary but filled, set boundary color to fill color */
- if (peripheries == 0) {
- peripheries = 1;
- if (color[0])
- gvrender_set_pencolor(job, color);
- }
- for (size_t j = 0; j < peripheries; j++) {
- enum {A_size = 2};
- pointf AF[A_size] = {{0}};
- for (size_t i = 0; i < sides; i++) {
- P = vertices[i + j * sides];
- if (i < A_size) {
- AF[i].x = P.x + ND_coord(n).x;
- AF[i].y = P.y + ND_coord(n).y;
- }
- }
- gvrender_ellipse(job, AF, filled);
- /* fill innermost periphery only */
- filled = false;
- }
- if (doMap) {
- if (job->flags & EMIT_CLUSTERS_LAST)
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- gvrender_end_anchor(job);
- }
- }
- /* the "record" shape is a rudimentary table formatter */
- #define HASTEXT 1
- #define HASPORT 2
- #define HASTABLE 4
- #define INTEXT 8
- #define INPORT 16
- static bool ISCTRL(int c) {
- return c == '{' || c == '}' || c == '|' || c == '<' || c == '>';
- }
- static char *reclblp;
- static void free_field(field_t * f)
- {
- int i;
- for (i = 0; i < f->n_flds; i++) {
- free_field(f->fld[i]);
- }
- free(f->id);
- free_label(f->lp);
- free(f->fld);
- free(f);
- }
- /* parse_error:
- * Clean up memory allocated in parse_reclbl, then return NULL
- */
- static field_t *parse_error(field_t *rv, char *portname) {
- free_field(rv);
- free(portname);
- return NULL;
- }
- static field_t *parse_reclbl(node_t *n, bool LR, bool flag, char *text) {
- field_t *fp, *rv = gv_alloc(sizeof(field_t));
- char *tsp, *psp=NULL, *hstsp, *hspsp=NULL, *sp;
- char *tmpport = NULL;
- int cnt, mode, fi;
- textlabel_t *lbl = ND_label(n);
- unsigned char uc;
- fp = NULL;
- size_t maxf;
- for (maxf = 1, cnt = 0, sp = reclblp; *sp; sp++) {
- if (*sp == '\\') {
- sp++;
- if (*sp && (*sp == '{' || *sp == '}' || *sp == '|' || *sp == '\\'))
- continue;
- }
- if (*sp == '{')
- cnt++;
- else if (*sp == '}')
- cnt--;
- else if (*sp == '|' && cnt == 0)
- maxf++;
- if (cnt < 0)
- break;
- }
- rv->fld = gv_calloc(maxf, sizeof(field_t*));
- rv->LR = LR;
- mode = 0;
- fi = 0;
- hstsp = tsp = text;
- bool wflag = true;
- bool ishardspace = false;
- while (wflag) {
- if ((uc = *(unsigned char*)reclblp) && uc < ' ') { /* Ignore non-0 control characters */
- reclblp++;
- continue;
- }
- switch (*reclblp) {
- case '<':
- if (mode & (HASTABLE | HASPORT))
- return parse_error(rv, tmpport);
- if (lbl->html)
- goto dotext;
- mode |= (HASPORT | INPORT);
- reclblp++;
- hspsp = psp = text;
- break;
- case '>':
- if (lbl->html)
- goto dotext;
- if (!(mode & INPORT))
- return parse_error(rv, tmpport);
- if (psp > text + 1 && psp - 1 != hspsp && *(psp - 1) == ' ')
- psp--;
- *psp = '\000';
- tmpport = gv_strdup(text);
- mode &= ~INPORT;
- reclblp++;
- break;
- case '{':
- reclblp++;
- if (mode != 0 || !*reclblp)
- return parse_error(rv, tmpport);
- mode = HASTABLE;
- if (!(rv->fld[fi++] = parse_reclbl(n, !LR, false, text)))
- return parse_error(rv, tmpport);
- break;
- case '}':
- case '|':
- case '\000':
- if ((!*reclblp && !flag) || (mode & INPORT))
- return parse_error(rv, tmpport);
- if (!(mode & HASTABLE))
- fp = rv->fld[fi++] = gv_alloc(sizeof(field_t));
- if (tmpport) {
- fp->id = tmpport;
- tmpport = NULL;
- }
- if (!(mode & (HASTEXT | HASTABLE))) {
- mode |= HASTEXT;
- *tsp++ = ' ';
- }
- if (mode & HASTEXT) {
- if (tsp > text + 1 && tsp - 1 != hstsp && *(tsp - 1) == ' ')
- tsp--;
- *tsp = '\000';
- fp->lp =
- make_label(n, text,
- lbl->html ? LT_HTML : LT_NONE,
- lbl->fontsize, lbl->fontname, lbl->fontcolor);
- fp->LR = true;
- hstsp = tsp = text;
- }
- if (*reclblp) {
- if (*reclblp == '}') {
- reclblp++;
- rv->n_flds = fi;
- return rv;
- }
- mode = 0;
- reclblp++;
- } else
- wflag = false;
- break;
- case '\\':
- if (*(reclblp + 1)) {
- if (ISCTRL(*(reclblp + 1))) {
- // nothing
- } else if (*(reclblp + 1) == ' ' && !lbl->html)
- ishardspace = true;
- else {
- *tsp++ = '\\';
- mode |= INTEXT | HASTEXT;
- }
- reclblp++;
- }
- /* fall through */
- default:
- dotext:
- if ((mode & HASTABLE) && *reclblp != ' ')
- return parse_error(rv, tmpport);
- if (!(mode & (INTEXT | INPORT)) && *reclblp != ' ')
- mode |= INTEXT | HASTEXT;
- if (mode & INTEXT) {
- if (!(*reclblp == ' ' && !ishardspace && *(tsp - 1) == ' '
- && !lbl->html))
- *tsp++ = *reclblp;
- if (ishardspace)
- hstsp = tsp - 1;
- } else if (mode & INPORT) {
- if (!(*reclblp == ' ' && !ishardspace &&
- (psp == text || *(psp - 1) == ' ')))
- *psp++ = *reclblp;
- if (ishardspace)
- hspsp = psp - 1;
- }
- reclblp++;
- while ((*reclblp & 0xc0) == 0x80)
- *tsp++ = *reclblp++;
- break;
- }
- }
- rv->n_flds = fi;
- return rv;
- }
- static pointf size_reclbl(node_t * n, field_t * f)
- {
- int i;
- char *p;
- double marginx, marginy;
- pointf d, d0;
- pointf dimen;
- if (f->lp) {
- dimen = f->lp->dimen;
- /* minimal whitespace around label */
- if (dimen.x > 0.0 || dimen.y > 0.0) {
- /* padding */
- if ((p = agget(n, "margin"))) {
- i = sscanf(p, "%lf,%lf", &marginx, &marginy);
- if (i > 0) {
- dimen.x += 2 * INCH2PS(marginx);
- if (i > 1)
- dimen.y += 2 * INCH2PS(marginy);
- else
- dimen.y += 2 * INCH2PS(marginx);
- } else
- PAD(dimen);
- } else
- PAD(dimen);
- }
- d = dimen;
- } else {
- d.x = d.y = 0;
- for (i = 0; i < f->n_flds; i++) {
- d0 = size_reclbl(n, f->fld[i]);
- if (f->LR) {
- d.x += d0.x;
- d.y = fmax(d.y, d0.y);
- } else {
- d.y += d0.y;
- d.x = fmax(d.x, d0.x);
- }
- }
- }
- f->size = d;
- return d;
- }
- static void resize_reclbl(field_t *f, pointf sz, bool nojustify_p) {
- int i, amt;
- double inc;
- pointf d;
- pointf newsz;
- field_t *sf;
- /* adjust field */
- d.x = sz.x - f->size.x;
- d.y = sz.y - f->size.y;
- f->size = sz;
- /* adjust text area */
- if (f->lp && !nojustify_p) {
- f->lp->space.x += d.x;
- f->lp->space.y += d.y;
- }
- /* adjust children */
- if (f->n_flds) {
- if (f->LR)
- inc = d.x / f->n_flds;
- else
- inc = d.y / f->n_flds;
- for (i = 0; i < f->n_flds; i++) {
- sf = f->fld[i];
- amt = (int)((i + 1) * inc) - (int)(i * inc);
- if (f->LR)
- newsz = (pointf){sf->size.x + amt, sz.y};
- else
- newsz = (pointf){sz.x, sf->size.y + amt};
- resize_reclbl(sf, newsz, nojustify_p);
- }
- }
- }
- /* pos_reclbl:
- * Assign position info for each field. Also, set
- * the sides attribute, which indicates which sides of the
- * record are accessible to the field.
- */
- static void pos_reclbl(field_t *f, pointf ul, unsigned char sides) {
- int i, last;
- unsigned char mask;
- f->sides = sides;
- f->b.LL = (pointf){ul.x, ul.y - f->size.y};
- f->b.UR = (pointf){ul.x + f->size.x, ul.y};
- last = f->n_flds - 1;
- for (i = 0; i <= last; i++) {
- if (sides) {
- if (f->LR) {
- if (i == 0) {
- if (i == last)
- mask = TOP | BOTTOM | RIGHT | LEFT;
- else
- mask = TOP | BOTTOM | LEFT;
- } else if (i == last)
- mask = TOP | BOTTOM | RIGHT;
- else
- mask = TOP | BOTTOM;
- } else {
- if (i == 0) {
- if (i == last)
- mask = TOP | BOTTOM | RIGHT | LEFT;
- else
- mask = TOP | RIGHT | LEFT;
- } else if (i == last)
- mask = LEFT | BOTTOM | RIGHT;
- else
- mask = LEFT | RIGHT;
- }
- } else
- mask = 0;
- pos_reclbl(f->fld[i], ul, (unsigned char)(sides & mask));
- if (f->LR)
- ul.x = ul.x + f->fld[i]->size.x;
- else
- ul.y = ul.y - f->fld[i]->size.y;
- }
- }
- #if defined(DEBUG) && DEBUG > 1
- static void indent(int l)
- {
- int i;
- for (i = 0; i < l; i++)
- fputs(" ", stderr);
- }
- static void prbox(boxf b)
- {
- fprintf(stderr, "((%.5g,%.5g),(%.5g,%.5g))\n", b.LL.x, b.LL.y, b.UR.x,
- b.UR.y);
- }
- static void dumpL(field_t * info, int level)
- {
- int i;
- indent(level);
- if (info->n_flds == 0) {
- fprintf(stderr, "Label \"%s\" ", info->lp->text);
- prbox(info->b);
- } else {
- fprintf(stderr, "Tbl ");
- prbox(info->b);
- for (i = 0; i < info->n_flds; i++) {
- dumpL(info->fld[i], level + 1);
- }
- }
- }
- #endif
- /* syntax of labels: foo|bar|baz or foo|(recursive|label)|baz */
- static void record_init(node_t * n)
- {
- field_t *info;
- pointf sz;
- int flip;
- size_t len;
- unsigned char sides = BOTTOM | RIGHT | TOP | LEFT;
- /* Always use rankdir to determine how records are laid out */
- flip = !GD_realflip(agraphof(n));
- reclblp = ND_label(n)->text;
- len = strlen(reclblp);
- /* For some forgotten reason, an empty label is parsed into a space, so
- * we need at least two bytes in textbuf, as well as accounting for the
- * error path involving "\\N" below.
- */
- len = MAX(MAX(len, 1), strlen("\\N"));
- char *textbuf = gv_calloc(len + 1, sizeof(char)); // temp buffer for storing labels
- if (!(info = parse_reclbl(n, flip, true, textbuf))) {
- agerrorf("bad label format %s\n", ND_label(n)->text);
- reclblp = "\\N";
- info = parse_reclbl(n, flip, true, textbuf);
- }
- free(textbuf);
- size_reclbl(n, info);
- sz.x = INCH2PS(ND_width(n));
- sz.y = INCH2PS(ND_height(n));
- if (mapbool(late_string(n, N_fixed, "false"))) {
- if (sz.x < info->size.x || sz.y < info->size.y) {
- /* should check that the record really won't fit, e.g., there may be no text.
- agwarningf("node '%s' size may be too small\n", agnameof(n));
- */
- }
- } else {
- sz.x = fmax(info->size.x, sz.x);
- sz.y = fmax(info->size.y, sz.y);
- }
- resize_reclbl(info, sz, mapbool(late_string(n, N_nojustify, "false")));
- pointf ul = {-sz.x / 2., sz.y / 2.}; /* FIXME - is this still true: suspected to introduce rounding error - see Kluge below */
- pos_reclbl(info, ul, sides);
- ND_width(n) = PS2INCH(info->size.x);
- ND_height(n) = PS2INCH(info->size.y + 1); /* Kluge!! +1 to fix rounding diff between layout and rendering
- otherwise we can get -1 coords in output */
- ND_shape_info(n) = info;
- }
- static void record_free(node_t * n)
- {
- field_t *p = ND_shape_info(n);
- free_field(p);
- }
- static field_t *map_rec_port(field_t * f, char *str)
- {
- field_t *rv;
- int sub;
- if (f->id && streq(f->id, str))
- rv = f;
- else {
- rv = NULL;
- for (sub = 0; sub < f->n_flds; sub++)
- if ((rv = map_rec_port(f->fld[sub], str)))
- break;
- }
- return rv;
- }
- static port record_port(node_t * n, char *portname, char *compass)
- {
- field_t *f;
- field_t *subf;
- port rv;
- unsigned char sides; // bitmap of which sides the port lies along
- if (portname[0] == '\0')
- return Center;
- sides = BOTTOM | RIGHT | TOP | LEFT;
- if (compass == NULL)
- compass = "_";
- f = ND_shape_info(n);
- if ((subf = map_rec_port(f, portname))) {
- if (compassPort(n, &subf->b, &rv, compass, subf->sides, NULL)) {
- agwarningf(
- "node %s, port %s, unrecognized compass point '%s' - ignored\n",
- agnameof(n), portname, compass);
- }
- } else if (compassPort(n, &f->b, &rv, portname, sides, NULL)) {
- unrecognized(n, portname);
- }
- return rv;
- }
- /* record_inside:
- * Note that this does not handle Mrecords correctly. It assumes
- * everything is a rectangle.
- */
- static bool record_inside(inside_t * inside_context, pointf p)
- {
- field_t *fld0;
- boxf *bp = inside_context->s.bp;
- node_t *n = inside_context->s.n;
- boxf bbox;
- /* convert point to node coordinate system */
- p = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n)));
- if (bp == NULL) {
- fld0 = ND_shape_info(n);
- bbox = fld0->b;
- } else
- bbox = *bp;
- // adjust bbox to outline, i.e., the periphery with penwidth taken into account
- const double penwidth = late_int(n, N_penwidth, DEFAULT_NODEPENWIDTH, MIN_NODEPENWIDTH);
- const pointf extension = {penwidth / 2, penwidth / 2};
- bbox.LL = sub_pointf(bbox.LL, extension);
- bbox.UR = add_pointf(bbox.UR, extension);
- return INSIDE(p, bbox);
- }
- /* record_path:
- * Generate box path from port to border.
- * See poly_path for constraints.
- */
- static int record_path(node_t * n, port * prt, int side, boxf rv[],
- int *kptr)
- {
- int i;
- double ls, rs;
- pointf p;
- field_t *info;
- if (!prt->defined)
- return 0;
- p = prt->p;
- info = ND_shape_info(n);
- for (i = 0; i < info->n_flds; i++) {
- if (!GD_flip(agraphof(n))) {
- ls = info->fld[i]->b.LL.x;
- rs = info->fld[i]->b.UR.x;
- } else {
- ls = info->fld[i]->b.LL.y;
- rs = info->fld[i]->b.UR.y;
- }
- if (BETWEEN(ls, p.x, rs)) {
- /* FIXME: I don't understand this code */
- if (GD_flip(agraphof(n))) {
- rv[0] = flip_rec_boxf(info->fld[i]->b, ND_coord(n));
- } else {
- rv[0].LL.x = ND_coord(n).x + ls;
- rv[0].LL.y = ND_coord(n).y - (ND_ht(n) / 2);
- rv[0].UR.x = ND_coord(n).x + rs;
- }
- rv[0].UR.y = ND_coord(n).y + (ND_ht(n) / 2);
- *kptr = 1;
- break;
- }
- }
- return side;
- }
- static void gen_fields(GVJ_t * job, node_t * n, field_t * f)
- {
- int i;
- pointf AF[2], coord;
- if (f->lp) {
- f->lp->pos = add_pointf(mid_pointf(f->b.LL, f->b.UR), ND_coord(n));
- emit_label(job, EMIT_NLABEL, f->lp);
- penColor(job, n);
- }
- coord = ND_coord(n);
- for (i = 0; i < f->n_flds; i++) {
- if (i > 0) {
- if (f->LR) {
- AF[0] = f->fld[i]->b.LL;
- AF[1].x = AF[0].x;
- AF[1].y = f->fld[i]->b.UR.y;
- } else {
- AF[1] = f->fld[i]->b.UR;
- AF[0].x = f->fld[i]->b.LL.x;
- AF[0].y = AF[1].y;
- }
- AF[0] = add_pointf(AF[0], coord);
- AF[1] = add_pointf(AF[1], coord);
- gvrender_polyline(job, AF, 2);
- }
- gen_fields(job, n, f->fld[i]);
- }
- }
- static void record_gencode(GVJ_t * job, node_t * n)
- {
- obj_state_t *obj = job->obj;
- boxf BF;
- pointf AF[4];
- field_t *f;
- int doMap = obj->url || obj->explicit_tooltip;
- int filled;
- f = ND_shape_info(n);
- BF = f->b;
- BF.LL.x += ND_coord(n).x;
- BF.LL.y += ND_coord(n).y;
- BF.UR.x += ND_coord(n).x;
- BF.UR.y += ND_coord(n).y;
- if (doMap && !(job->flags & EMIT_CLUSTERS_LAST))
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- graphviz_polygon_style_t style = stylenode(job, n);
- penColor(job, n);
- char *clrs[2] = {0};
- if (style.filled) {
- char* fillcolor = findFill (n);
- double frac;
- if (findStopColor (fillcolor, clrs, &frac)) {
- gvrender_set_fillcolor(job, clrs[0]);
- if (clrs[1])
- gvrender_set_gradient_vals(job,clrs[1],late_int(n,N_gradientangle,0,0), frac);
- else
- gvrender_set_gradient_vals(job,DEFAULT_COLOR,late_int(n,N_gradientangle,0,0), frac);
- if (style.radial)
- filled = RGRADIENT;
- else
- filled = GRADIENT;
- }
- else {
- filled = FILL;
- gvrender_set_fillcolor(job, fillcolor);
- }
- }
- else filled = 0;
- if (streq(ND_shape(n)->name, "Mrecord"))
- style.rounded = true;
- if (SPECIAL_CORNERS(style)) {
- AF[0] = BF.LL;
- AF[2] = BF.UR;
- AF[1].x = AF[2].x;
- AF[1].y = AF[0].y;
- AF[3].x = AF[0].x;
- AF[3].y = AF[2].y;
- round_corners(job, AF, 4, style, filled);
- } else {
- gvrender_box(job, BF, filled);
- }
- gen_fields(job, n, f);
- free (clrs[0]);
- free(clrs[1]);
- if (doMap) {
- if (job->flags & EMIT_CLUSTERS_LAST)
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- gvrender_end_anchor(job);
- }
- }
- static shape_desc **UserShape;
- static size_t N_UserShape;
- shape_desc *find_user_shape(const char *name)
- {
- if (UserShape) {
- for (size_t i = 0; i < N_UserShape; i++) {
- if (streq(UserShape[i]->name, name))
- return UserShape[i];
- }
- }
- return NULL;
- }
- static shape_desc *user_shape(char *name)
- {
- shape_desc *p;
- if ((p = find_user_shape(name)))
- return p;
- size_t i = N_UserShape++;
- UserShape = gv_recalloc(UserShape, N_UserShape - 1, N_UserShape, sizeof(shape_desc *));
- p = UserShape[i] = gv_alloc(sizeof(shape_desc));
- *p = Shapes[0];
- p->name = strdup(name);
- if (Lib == NULL && !streq(name, "custom")) {
- agwarningf("using %s for unknown shape %s\n", Shapes[0].name,
- p->name);
- p->usershape = false;
- } else {
- p->usershape = true;
- }
- return p;
- }
- shape_desc *bind_shape(char *name, node_t * np)
- {
- shape_desc *ptr, *rv = NULL;
- const char *str;
- str = safefile(agget(np, "shapefile"));
- /* If shapefile is defined and not epsf, set shape = custom */
- if (str && !streq(name, "epsf"))
- name = "custom";
- if (!streq(name, "custom")) {
- for (ptr = Shapes; ptr->name; ptr++) {
- if (streq(ptr->name, name)) {
- rv = ptr;
- break;
- }
- }
- }
- if (rv == NULL)
- rv = user_shape(name);
- return rv;
- }
- static bool epsf_inside(inside_t * inside_context, pointf p)
- {
- pointf P;
- double x2;
- node_t *n = inside_context->s.n;
- P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n)));
- x2 = ND_ht(n) / 2;
- return P.y >= -x2 && P.y <= x2 && P.x >= -ND_lw(n) && P.x <= ND_rw(n);
- }
- static void epsf_gencode(GVJ_t * job, node_t * n)
- {
- obj_state_t *obj = job->obj;
- epsf_t *desc;
- int doMap = obj->url || obj->explicit_tooltip;
- desc = ND_shape_info(n);
- if (!desc)
- return;
- if (doMap && !(job->flags & EMIT_CLUSTERS_LAST))
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- if (desc)
- fprintf(job->output_file,
- "%.5g %.5g translate newpath user_shape_%d\n",
- ND_coord(n).x + desc->offset.x,
- ND_coord(n).y + desc->offset.y, desc->macro_id);
- ND_label(n)->pos = ND_coord(n);
- emit_label(job, EMIT_NLABEL, ND_label(n));
- if (doMap) {
- if (job->flags & EMIT_CLUSTERS_LAST)
- gvrender_begin_anchor(job,
- obj->url, obj->tooltip, obj->target,
- obj->id);
- gvrender_end_anchor(job);
- }
- }
- #define alpha (M_PI/10.0)
- #define alpha2 (2*alpha)
- #define alpha3 (3*alpha)
- #define alpha4 (2*alpha2)
- static pointf star_size (pointf sz0)
- {
- pointf sz;
- double r, rx, ry;
- rx = sz0.x/(2*cos(alpha));
- ry = sz0.y/(sin(alpha) + sin(alpha3));
- const double r0 = fmax(rx, ry);
- r = r0 * sin(alpha4) * cos(alpha2) / (cos(alpha) * cos(alpha4));
- sz.x = 2*r*cos(alpha);
- sz.y = r*(1 + sin(alpha3));
- return sz;
- }
- static void star_vertices (pointf* vertices, pointf* bb)
- {
- int i;
- pointf sz = *bb;
- double offset, a, aspect = (1 + sin(alpha3))/(2*cos(alpha));
- double r, r0, theta = alpha;
- /* Scale up width or height to required aspect ratio */
- a = sz.y/sz.x;
- if (a > aspect) {
- sz.x = sz.y/aspect;
- }
- else if (a < aspect) {
- sz.y = sz.x*aspect;
- }
- /* for given sz, get radius */
- r = sz.x/(2*cos(alpha));
- r0 = r * cos(alpha) * cos(alpha4) / (sin(alpha4) * cos(alpha2));
- /* offset is the y shift of circle center from bb center */
- offset = (r*(1 - sin(alpha3)))/2;
- for (i = 0; i < 10; i += 2) {
- vertices[i].x = r*cos(theta);
- vertices[i].y = r*sin(theta) - offset;
- theta += alpha2;
- vertices[i+1].x = r0*cos(theta);
- vertices[i+1].y = r0*sin(theta) - offset;
- theta += alpha2;
- }
- *bb = sz;
- }
- static bool star_inside(inside_t * inside_context, pointf p)
- {
- size_t sides;
- pointf *vertex;
- const pointf O = {0};
- if (!inside_context) {
- return false;
- }
- boxf *bp = inside_context->s.bp;
- node_t *n = inside_context->s.n;
- pointf P, Q, R;
- int outcnt;
- P = ccwrotatepf(p, 90 * GD_rankdir(agraphof(n)));
- /* Quick test if port rectangle is target */
- if (bp) {
- boxf bbox = *bp;
- return INSIDE(P, bbox);
- }
- if (n != inside_context->s.lastn) {
- inside_context->s.last_poly = ND_shape_info(n);
- vertex = inside_context->s.last_poly->vertices;
- sides = inside_context->s.last_poly->sides;
- const double penwidth = late_int(n, N_penwidth, DEFAULT_NODEPENWIDTH, MIN_NODEPENWIDTH);
- if (inside_context->s.last_poly->peripheries >= 1 && penwidth > 0) {
- /* index to outline, i.e., the outer-periphery with penwidth taken into account */
- inside_context->s.outp = (inside_context->s.last_poly->peripheries + 1 - 1)
- * sides;
- } else if (inside_context->s.last_poly->peripheries < 1) {
- inside_context->s.outp = 0;
- } else {
- /* index to outer-periphery */
- inside_context->s.outp = (inside_context->s.last_poly->peripheries - 1)
- * sides;
- }
- inside_context->s.lastn = n;
- } else {
- vertex = inside_context->s.last_poly->vertices;
- sides = inside_context->s.last_poly->sides;
- }
- outcnt = 0;
- for (size_t i = 0; i < sides; i += 2) {
- Q = vertex[i + inside_context->s.outp];
- R = vertex[(i + 4) % sides + inside_context->s.outp];
- if (!(same_side(P, O, Q, R))) {
- outcnt++;
- }
- if (outcnt == 2) {
- return false;
- }
- }
- return true;
- }
- /* cylinder:
- * Code based on PostScript version by Brandon Rhodes.
- * http://rhodesmill.org/brandon/2007/a-database-symbol-for-graphviz/
- */
- static pointf cylinder_size (pointf sz)
- {
- sz.y *= 1.375;
- return sz;
- }
- static void cylinder_vertices (pointf* vertices, pointf* bb)
- {
- double x = bb->x/2;
- double y = bb->y/2;
- double yr = bb->y/11;
- vertices[0].x = x;
- vertices[0].y = y-yr;
- vertices[1].x = x;
- vertices[1].y = y-(1-0.551784)*yr;
- vertices[2].x = 0.551784*x;
- vertices[2].y = y;
- vertices[3].x = 0;
- vertices[3].y = y;
- vertices[4].x = -0.551784*x;
- vertices[4].y = y;
- vertices[5].x = -x;
- vertices[5].y = vertices[1].y;
- vertices[6].x = -x;
- vertices[6].y = y-yr;
- vertices[7] = vertices[6];
- vertices[8].x = -x;
- vertices[8].y = yr-y;
- vertices[9] = vertices[8];
- vertices[10].x = -x;
- vertices[10].y = -vertices[1].y;
- vertices[11].x = vertices[4].x;
- vertices[11].y = -vertices[4].y;
- vertices[12].x = vertices[3].x;
- vertices[12].y = -vertices[3].y;
- vertices[13].x = vertices[2].x;
- vertices[13].y = -vertices[2].y;
- vertices[14].x = vertices[1].x;
- vertices[14].y = -vertices[1].y;
- vertices[15].x = vertices[0].x;
- vertices[15].y = -vertices[0].y;
- vertices[16] = vertices[15];
- vertices[18] = vertices[17] = vertices[0];
- }
- static void cylinder_draw(GVJ_t *job, pointf *AF, size_t sides, int filled) {
- pointf vertices[7];
- double y0 = AF[0].y;
- double y02 = y0+y0;
- vertices[0] = AF[0];
- vertices[1].x = AF[1].x;
- vertices[1].y = y02 - AF[1].y;
- vertices[2].x = AF[2].x;
- vertices[2].y = y02 - AF[2].y;
- vertices[3].x = AF[3].x;
- vertices[3].y = y02 - AF[3].y;
- vertices[4].x = AF[4].x;
- vertices[4].y = y02 - AF[4].y;
- vertices[5].x = AF[5].x;
- vertices[5].y = y02 - AF[5].y;
- vertices[6] = AF[6];
- gvrender_beziercurve(job, AF, sides, filled);
- gvrender_beziercurve(job, vertices, 7, 0);
- }
- static const char *side_port[] = {"s", "e", "n", "w"};
- static pointf cvtPt(pointf p, int rankdir) {
- pointf q = { 0, 0 };
- switch (rankdir) {
- case RANKDIR_TB:
- q = p;
- break;
- case RANKDIR_BT:
- q.x = p.x;
- q.y = -p.y;
- break;
- case RANKDIR_LR:
- q.y = p.x;
- q.x = -p.y;
- break;
- case RANKDIR_RL:
- q.y = p.x;
- q.x = p.y;
- break;
- default:
- UNREACHABLE();
- }
- return q;
- }
- /* closestSide:
- * Resolve unspecified compass-point port to best available port.
- * At present, this finds the available side closest to the center
- * of the other port.
- *
- * This could be improved:
- * - if other is unspecified, do them together
- * - if dot, bias towards bottom of one to top of another, if possible
- * - if line segment from port centers uses available sides, use these
- * or center. (This latter may require spline routing to cooperate.)
- */
- static const char *closestSide(node_t *n, node_t *other, port *oldport) {
- boxf b;
- int rkd = GD_rankdir(agraphof(n)->root);
- pointf p = {0};
- const pointf pt = cvtPt(ND_coord(n), rkd);
- const pointf opt = cvtPt(ND_coord(other), rkd);
- int sides = oldport->side;
- const char *rv = NULL;
- if (sides == 0 || sides == (TOP | BOTTOM | LEFT | RIGHT))
- return rv; /* use center */
- if (oldport->bp) {
- b = *oldport->bp;
- } else {
- if (GD_flip(agraphof(n))) {
- b.UR.x = ND_ht(n) / 2;
- b.LL.x = -b.UR.x;
- b.UR.y = ND_lw(n);
- b.LL.y = -b.UR.y;
- } else {
- b.UR.y = ND_ht(n) / 2;
- b.LL.y = -b.UR.y;
- b.UR.x = ND_lw(n);
- b.LL.x = -b.UR.x;
- }
- }
- double mind = 0;
- for (int i = 0; i < 4; i++) {
- if ((sides & (1 << i)) == 0)
- continue;
- switch (i) {
- case BOTTOM_IX:
- p.y = b.LL.y;
- p.x = (b.LL.x + b.UR.x) / 2;
- break;
- case RIGHT_IX:
- p.x = b.UR.x;
- p.y = (b.LL.y + b.UR.y) / 2;
- break;
- case TOP_IX:
- p.y = b.UR.y;
- p.x = (b.LL.x + b.UR.x) / 2;
- break;
- case LEFT_IX:
- p.x = b.LL.x;
- p.y = (b.LL.y + b.UR.y) / 2;
- break;
- default:
- UNREACHABLE();
- }
- p.x += pt.x;
- p.y += pt.y;
- const double d = DIST2(p, opt);
- if (!rv || d < mind) {
- mind = d;
- rv = side_port[i];
- }
- }
- return rv;
- }
- port resolvePort(node_t * n, node_t * other, port * oldport)
- {
- port rv;
- const char *compass = closestSide(n, other, oldport);
- /* transfer name pointer; all other necessary fields will be regenerated */
- rv.name = oldport->name;
- compassPort(n, oldport->bp, &rv, compass, oldport->side, NULL);
- return rv;
- }
- void resolvePorts(edge_t * e)
- {
- if (ED_tail_port(e).dyna)
- ED_tail_port(e) =
- resolvePort(agtail(e), aghead(e), &ED_tail_port(e));
- if (ED_head_port(e).dyna)
- ED_head_port(e) =
- resolvePort(aghead(e), agtail(e), &ED_head_port(e));
- }
|