123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022 |
- /*
- * Copyright (c) 2013-2014 Kurt Jung (Gmail: kurt.w.jung)
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
- package gofpdf
- // Version: 1.7
- // Date: 2011-06-18
- // Author: Olivier PLATHEY
- // Port to Go: Kurt Jung, 2013-07-15
- import (
- "bytes"
- "encoding/binary"
- "encoding/json"
- "fmt"
- "image"
- "image/color"
- "image/gif"
- "image/jpeg"
- "image/png"
- "io"
- "io/ioutil"
- "math"
- "os"
- "path"
- "sort"
- "strconv"
- "strings"
- "time"
- )
- var gl struct {
- catalogSort bool
- noCompress bool // Initial zero value indicates compression
- creationDate time.Time
- modDate time.Time
- }
- type fmtBuffer struct {
- bytes.Buffer
- }
- func (b *fmtBuffer) printf(fmtStr string, args ...interface{}) {
- b.Buffer.WriteString(fmt.Sprintf(fmtStr, args...))
- }
- func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) (f *Fpdf) {
- f = new(Fpdf)
- if orientationStr == "" {
- orientationStr = "p"
- } else {
- orientationStr = strings.ToLower(orientationStr)
- }
- if unitStr == "" {
- unitStr = "mm"
- }
- if sizeStr == "" {
- sizeStr = "A4"
- }
- if fontDirStr == "" {
- fontDirStr = "."
- }
- f.page = 0
- f.n = 2
- f.pages = make([]*bytes.Buffer, 0, 8)
- f.pages = append(f.pages, bytes.NewBufferString("")) // pages[0] is unused (1-based)
- f.pageSizes = make(map[int]SizeType)
- f.pageBoxes = make(map[int]map[string]PageBox)
- f.defPageBoxes = make(map[string]PageBox)
- f.state = 0
- f.fonts = make(map[string]fontDefType)
- f.fontFiles = make(map[string]fontFileType)
- f.diffs = make([]string, 0, 8)
- f.templates = make(map[string]Template)
- f.templateObjects = make(map[string]int)
- f.importedObjs = make(map[string][]byte, 0)
- f.importedObjPos = make(map[string]map[int]string, 0)
- f.importedTplObjs = make(map[string]string)
- f.importedTplIDs = make(map[string]int, 0)
- f.images = make(map[string]*ImageInfoType)
- f.pageLinks = make([][]linkType, 0, 8)
- f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0)) // pageLinks[0] is unused (1-based)
- f.links = make([]intLinkType, 0, 8)
- f.links = append(f.links, intLinkType{}) // links[0] is unused (1-based)
- f.pageAttachments = make([][]annotationAttach, 0, 8)
- f.pageAttachments = append(f.pageAttachments, []annotationAttach{}) //
- f.aliasMap = make(map[string]string)
- f.inHeader = false
- f.inFooter = false
- f.lasth = 0
- f.fontFamily = ""
- f.fontStyle = ""
- f.SetFontSize(12)
- f.underline = false
- f.strikeout = false
- f.setDrawColor(0, 0, 0)
- f.setFillColor(0, 0, 0)
- f.setTextColor(0, 0, 0)
- f.colorFlag = false
- f.ws = 0
- f.fontpath = fontDirStr
- // Core fonts
- f.coreFonts = map[string]bool{
- "courier": true,
- "helvetica": true,
- "times": true,
- "symbol": true,
- "zapfdingbats": true,
- }
- // Scale factor
- switch unitStr {
- case "pt", "point":
- f.k = 1.0
- case "mm":
- f.k = 72.0 / 25.4
- case "cm":
- f.k = 72.0 / 2.54
- case "in", "inch":
- f.k = 72.0
- default:
- f.err = fmt.Errorf("incorrect unit %s", unitStr)
- return
- }
- f.unitStr = unitStr
- // Page sizes
- f.stdPageSizes = make(map[string]SizeType)
- f.stdPageSizes["a3"] = SizeType{841.89, 1190.55}
- f.stdPageSizes["a4"] = SizeType{595.28, 841.89}
- f.stdPageSizes["a5"] = SizeType{420.94, 595.28}
- f.stdPageSizes["a6"] = SizeType{297.64, 420.94}
- f.stdPageSizes["a2"] = SizeType{1190.55, 1683.78}
- f.stdPageSizes["a1"] = SizeType{1683.78, 2383.94}
- f.stdPageSizes["letter"] = SizeType{612, 792}
- f.stdPageSizes["legal"] = SizeType{612, 1008}
- f.stdPageSizes["tabloid"] = SizeType{792, 1224}
- if size.Wd > 0 && size.Ht > 0 {
- f.defPageSize = size
- } else {
- f.defPageSize = f.getpagesizestr(sizeStr)
- if f.err != nil {
- return
- }
- }
- f.curPageSize = f.defPageSize
- // Page orientation
- switch orientationStr {
- case "p", "portrait":
- f.defOrientation = "P"
- f.w = f.defPageSize.Wd
- f.h = f.defPageSize.Ht
- // dbg("Assign h: %8.2f", f.h)
- case "l", "landscape":
- f.defOrientation = "L"
- f.w = f.defPageSize.Ht
- f.h = f.defPageSize.Wd
- default:
- f.err = fmt.Errorf("incorrect orientation: %s", orientationStr)
- return
- }
- f.curOrientation = f.defOrientation
- f.wPt = f.w * f.k
- f.hPt = f.h * f.k
- // Page margins (1 cm)
- margin := 28.35 / f.k
- f.SetMargins(margin, margin, margin)
- // Interior cell margin (1 mm)
- f.cMargin = margin / 10
- // Line width (0.2 mm)
- f.lineWidth = 0.567 / f.k
- // Automatic page break
- f.SetAutoPageBreak(true, 2*margin)
- // Default display mode
- f.SetDisplayMode("default", "default")
- if f.err != nil {
- return
- }
- f.acceptPageBreak = func() bool {
- return f.autoPageBreak
- }
- // Enable compression
- f.SetCompression(!gl.noCompress)
- f.spotColorMap = make(map[string]spotColorType)
- f.blendList = make([]blendModeType, 0, 8)
- f.blendList = append(f.blendList, blendModeType{}) // blendList[0] is unused (1-based)
- f.blendMap = make(map[string]int)
- f.blendMode = "Normal"
- f.alpha = 1
- f.gradientList = make([]gradientType, 0, 8)
- f.gradientList = append(f.gradientList, gradientType{}) // gradientList[0] is unused
- // Set default PDF version number
- f.pdfVersion = "1.3"
- f.SetProducer("FPDF "+cnFpdfVersion, true)
- f.layerInit()
- f.catalogSort = gl.catalogSort
- f.creationDate = gl.creationDate
- f.modDate = gl.modDate
- f.userUnderlineThickness = 1
- return
- }
- // NewCustom returns a pointer to a new Fpdf instance. Its methods are
- // subsequently called to produce a single PDF document. NewCustom() is an
- // alternative to New() that provides additional customization. The PageSize()
- // example demonstrates this method.
- func NewCustom(init *InitType) (f *Fpdf) {
- return fpdfNew(init.OrientationStr, init.UnitStr, init.SizeStr, init.FontDirStr, init.Size)
- }
- // New returns a pointer to a new Fpdf instance. Its methods are subsequently
- // called to produce a single PDF document.
- //
- // orientationStr specifies the default page orientation. For portrait mode,
- // specify "P" or "Portrait". For landscape mode, specify "L" or "Landscape".
- // An empty string will be replaced with "P".
- //
- // unitStr specifies the unit of length used in size parameters for elements
- // other than fonts, which are always measured in points. Specify "pt" for
- // point, "mm" for millimeter, "cm" for centimeter, or "in" for inch. An empty
- // string will be replaced with "mm".
- //
- // sizeStr specifies the page size. Acceptable values are "A3", "A4", "A5",
- // "Letter", "Legal", or "Tabloid". An empty string will be replaced with "A4".
- //
- // fontDirStr specifies the file system location in which font resources will
- // be found. An empty string is replaced with ".". This argument only needs to
- // reference an actual directory if a font other than one of the core
- // fonts is used. The core fonts are "courier", "helvetica" (also called
- // "arial"), "times", and "zapfdingbats" (also called "symbol").
- func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) {
- return fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, SizeType{0, 0})
- }
- // Ok returns true if no processing errors have occurred.
- func (f *Fpdf) Ok() bool {
- return f.err == nil
- }
- // Err returns true if a processing error has occurred.
- func (f *Fpdf) Err() bool {
- return f.err != nil
- }
- // ClearError unsets the internal Fpdf error. This method should be used with
- // care, as an internal error condition usually indicates an unrecoverable
- // problem with the generation of a document. It is intended to deal with cases
- // in which an error is used to select an alternate form of the document.
- func (f *Fpdf) ClearError() {
- f.err = nil
- }
- // SetErrorf sets the internal Fpdf error with formatted text to halt PDF
- // generation; this may facilitate error handling by application. If an error
- // condition is already set, this call is ignored.
- //
- // See the documentation for printing in the standard fmt package for details
- // about fmtStr and args.
- func (f *Fpdf) SetErrorf(fmtStr string, args ...interface{}) {
- if f.err == nil {
- f.err = fmt.Errorf(fmtStr, args...)
- }
- }
- // String satisfies the fmt.Stringer interface and summarizes the Fpdf
- // instance.
- func (f *Fpdf) String() string {
- return "Fpdf " + cnFpdfVersion
- }
- // SetError sets an error to halt PDF generation. This may facilitate error
- // handling by application. See also Ok(), Err() and Error().
- func (f *Fpdf) SetError(err error) {
- if f.err == nil && err != nil {
- f.err = err
- }
- }
- // Error returns the internal Fpdf error; this will be nil if no error has occurred.
- func (f *Fpdf) Error() error {
- return f.err
- }
- // GetPageSize returns the current page's width and height. This is the paper's
- // size. To compute the size of the area being used, subtract the margins (see
- // GetMargins()).
- func (f *Fpdf) GetPageSize() (width, height float64) {
- width = f.w
- height = f.h
- return
- }
- // GetMargins returns the left, top, right, and bottom margins. The first three
- // are set with the SetMargins() method. The bottom margin is set with the
- // SetAutoPageBreak() method.
- func (f *Fpdf) GetMargins() (left, top, right, bottom float64) {
- left = f.lMargin
- top = f.tMargin
- right = f.rMargin
- bottom = f.bMargin
- return
- }
- // SetMargins defines the left, top and right margins. By default, they equal 1
- // cm. Call this method to change them. If the value of the right margin is
- // less than zero, it is set to the same as the left margin.
- func (f *Fpdf) SetMargins(left, top, right float64) {
- f.lMargin = left
- f.tMargin = top
- if right < 0 {
- right = left
- }
- f.rMargin = right
- }
- // SetLeftMargin defines the left margin. The method can be called before
- // creating the first page. If the current abscissa gets out of page, it is
- // brought back to the margin.
- func (f *Fpdf) SetLeftMargin(margin float64) {
- f.lMargin = margin
- if f.page > 0 && f.x < margin {
- f.x = margin
- }
- }
- // GetCellMargin returns the cell margin. This is the amount of space before
- // and after the text within a cell that's left blank, and is in units passed
- // to New(). It defaults to 1mm.
- func (f *Fpdf) GetCellMargin() float64 {
- return f.cMargin
- }
- // SetCellMargin sets the cell margin. This is the amount of space before and
- // after the text within a cell that's left blank, and is in units passed to
- // New().
- func (f *Fpdf) SetCellMargin(margin float64) {
- f.cMargin = margin
- }
- // SetPageBoxRec sets the page box for the current page, and any following
- // pages. Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox,
- // art and artbox box types are case insensitive. See SetPageBox() for a method
- // that specifies the coordinates and extent of the page box individually.
- func (f *Fpdf) SetPageBoxRec(t string, pb PageBox) {
- switch strings.ToLower(t) {
- case "trim":
- fallthrough
- case "trimbox":
- t = "TrimBox"
- case "crop":
- fallthrough
- case "cropbox":
- t = "CropBox"
- case "bleed":
- fallthrough
- case "bleedbox":
- t = "BleedBox"
- case "art":
- fallthrough
- case "artbox":
- t = "ArtBox"
- default:
- f.err = fmt.Errorf("%s is not a valid page box type", t)
- return
- }
- pb.X = pb.X * f.k
- pb.Y = pb.Y * f.k
- pb.Wd = (pb.Wd * f.k) + pb.X
- pb.Ht = (pb.Ht * f.k) + pb.Y
- if f.page > 0 {
- f.pageBoxes[f.page][t] = pb
- }
- // always override. page defaults are supplied in addPage function
- f.defPageBoxes[t] = pb
- }
- // SetPageBox sets the page box for the current page, and any following pages.
- // Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox, art and
- // artbox box types are case insensitive.
- func (f *Fpdf) SetPageBox(t string, x, y, wd, ht float64) {
- f.SetPageBoxRec(t, PageBox{SizeType{Wd: wd, Ht: ht}, PointType{X: x, Y: y}})
- }
- // SetPage sets the current page to that of a valid page in the PDF document.
- // pageNum is one-based. The SetPage() example demonstrates this method.
- func (f *Fpdf) SetPage(pageNum int) {
- if (pageNum > 0) && (pageNum < len(f.pages)) {
- f.page = pageNum
- }
- }
- // PageCount returns the number of pages currently in the document. Since page
- // numbers in gofpdf are one-based, the page count is the same as the page
- // number of the current last page.
- func (f *Fpdf) PageCount() int {
- return len(f.pages) - 1
- }
- // SetFontLocation sets the location in the file system of the font and font
- // definition files.
- func (f *Fpdf) SetFontLocation(fontDirStr string) {
- f.fontpath = fontDirStr
- }
- // SetFontLoader sets a loader used to read font files (.json and .z) from an
- // arbitrary source. If a font loader has been specified, it is used to load
- // the named font resources when AddFont() is called. If this operation fails,
- // an attempt is made to load the resources from the configured font directory
- // (see SetFontLocation()).
- func (f *Fpdf) SetFontLoader(loader FontLoader) {
- f.fontLoader = loader
- }
- // SetHeaderFuncMode sets the function that lets the application render the
- // page header. See SetHeaderFunc() for more details. The value for homeMode
- // should be set to true to have the current position set to the left and top
- // margin after the header function is called.
- func (f *Fpdf) SetHeaderFuncMode(fnc func(), homeMode bool) {
- f.headerFnc = fnc
- f.headerHomeMode = homeMode
- }
- // SetHeaderFunc sets the function that lets the application render the page
- // header. The specified function is automatically called by AddPage() and
- // should not be called directly by the application. The implementation in Fpdf
- // is empty, so you have to provide an appropriate function if you want page
- // headers. fnc will typically be a closure that has access to the Fpdf
- // instance and other document generation variables.
- //
- // A header is a convenient place to put background content that repeats on
- // each page such as a watermark. When this is done, remember to reset the X
- // and Y values so the normal content begins where expected. Including a
- // watermark on each page is demonstrated in the example for TransformRotate.
- //
- // This method is demonstrated in the example for AddPage().
- func (f *Fpdf) SetHeaderFunc(fnc func()) {
- f.headerFnc = fnc
- }
- // SetFooterFunc sets the function that lets the application render the page
- // footer. The specified function is automatically called by AddPage() and
- // Close() and should not be called directly by the application. The
- // implementation in Fpdf is empty, so you have to provide an appropriate
- // function if you want page footers. fnc will typically be a closure that has
- // access to the Fpdf instance and other document generation variables. See
- // SetFooterFuncLpi for a similar function that passes a last page indicator.
- //
- // This method is demonstrated in the example for AddPage().
- func (f *Fpdf) SetFooterFunc(fnc func()) {
- f.footerFnc = fnc
- f.footerFncLpi = nil
- }
- // SetFooterFuncLpi sets the function that lets the application render the page
- // footer. The specified function is automatically called by AddPage() and
- // Close() and should not be called directly by the application. It is passed a
- // boolean that is true if the last page of the document is being rendered. The
- // implementation in Fpdf is empty, so you have to provide an appropriate
- // function if you want page footers. fnc will typically be a closure that has
- // access to the Fpdf instance and other document generation variables.
- func (f *Fpdf) SetFooterFuncLpi(fnc func(lastPage bool)) {
- f.footerFncLpi = fnc
- f.footerFnc = nil
- }
- // SetTopMargin defines the top margin. The method can be called before
- // creating the first page.
- func (f *Fpdf) SetTopMargin(margin float64) {
- f.tMargin = margin
- }
- // SetRightMargin defines the right margin. The method can be called before
- // creating the first page.
- func (f *Fpdf) SetRightMargin(margin float64) {
- f.rMargin = margin
- }
- // GetAutoPageBreak returns true if automatic pages breaks are enabled, false
- // otherwise. This is followed by the triggering limit from the bottom of the
- // page. This value applies only if automatic page breaks are enabled.
- func (f *Fpdf) GetAutoPageBreak() (auto bool, margin float64) {
- auto = f.autoPageBreak
- margin = f.bMargin
- return
- }
- // SetAutoPageBreak enables or disables the automatic page breaking mode. When
- // enabling, the second parameter is the distance from the bottom of the page
- // that defines the triggering limit. By default, the mode is on and the margin
- // is 2 cm.
- func (f *Fpdf) SetAutoPageBreak(auto bool, margin float64) {
- f.autoPageBreak = auto
- f.bMargin = margin
- f.pageBreakTrigger = f.h - margin
- }
- // SetDisplayMode sets advisory display directives for the document viewer.
- // Pages can be displayed entirely on screen, occupy the full width of the
- // window, use real size, be scaled by a specific zooming factor or use viewer
- // default (configured in the Preferences menu of Adobe Reader). The page
- // layout can be specified so that pages are displayed individually or in
- // pairs.
- //
- // zoomStr can be "fullpage" to display the entire page on screen, "fullwidth"
- // to use maximum width of window, "real" to use real size (equivalent to 100%
- // zoom) or "default" to use viewer default mode.
- //
- // layoutStr can be "single" (or "SinglePage") to display one page at once,
- // "continuous" (or "OneColumn") to display pages continuously, "two" (or
- // "TwoColumnLeft") to display two pages on two columns with odd-numbered pages
- // on the left, or "TwoColumnRight" to display two pages on two columns with
- // odd-numbered pages on the right, or "TwoPageLeft" to display pages two at a
- // time with odd-numbered pages on the left, or "TwoPageRight" to display pages
- // two at a time with odd-numbered pages on the right, or "default" to use
- // viewer default mode.
- func (f *Fpdf) SetDisplayMode(zoomStr, layoutStr string) {
- if f.err != nil {
- return
- }
- if layoutStr == "" {
- layoutStr = "default"
- }
- switch zoomStr {
- case "fullpage", "fullwidth", "real", "default":
- f.zoomMode = zoomStr
- default:
- f.err = fmt.Errorf("incorrect zoom display mode: %s", zoomStr)
- return
- }
- switch layoutStr {
- case "single", "continuous", "two", "default", "SinglePage", "OneColumn",
- "TwoColumnLeft", "TwoColumnRight", "TwoPageLeft", "TwoPageRight":
- f.layoutMode = layoutStr
- default:
- f.err = fmt.Errorf("incorrect layout display mode: %s", layoutStr)
- return
- }
- }
- // SetDefaultCompression controls the default setting of the internal
- // compression flag. See SetCompression() for more details. Compression is on
- // by default.
- func SetDefaultCompression(compress bool) {
- gl.noCompress = !compress
- }
- // SetCompression activates or deactivates page compression with zlib. When
- // activated, the internal representation of each page is compressed, which
- // leads to a compression ratio of about 2 for the resulting document.
- // Compression is on by default.
- func (f *Fpdf) SetCompression(compress bool) {
- f.compress = compress
- }
- // SetProducer defines the producer of the document. isUTF8 indicates if the string
- // is encoded in ISO-8859-1 (false) or UTF-8 (true).
- func (f *Fpdf) SetProducer(producerStr string, isUTF8 bool) {
- if isUTF8 {
- producerStr = utf8toutf16(producerStr)
- }
- f.producer = producerStr
- }
- // SetTitle defines the title of the document. isUTF8 indicates if the string
- // is encoded in ISO-8859-1 (false) or UTF-8 (true).
- func (f *Fpdf) SetTitle(titleStr string, isUTF8 bool) {
- if isUTF8 {
- titleStr = utf8toutf16(titleStr)
- }
- f.title = titleStr
- }
- // SetSubject defines the subject of the document. isUTF8 indicates if the
- // string is encoded in ISO-8859-1 (false) or UTF-8 (true).
- func (f *Fpdf) SetSubject(subjectStr string, isUTF8 bool) {
- if isUTF8 {
- subjectStr = utf8toutf16(subjectStr)
- }
- f.subject = subjectStr
- }
- // SetAuthor defines the author of the document. isUTF8 indicates if the string
- // is encoded in ISO-8859-1 (false) or UTF-8 (true).
- func (f *Fpdf) SetAuthor(authorStr string, isUTF8 bool) {
- if isUTF8 {
- authorStr = utf8toutf16(authorStr)
- }
- f.author = authorStr
- }
- // SetKeywords defines the keywords of the document. keywordStr is a
- // space-delimited string, for example "invoice August". isUTF8 indicates if
- // the string is encoded
- func (f *Fpdf) SetKeywords(keywordsStr string, isUTF8 bool) {
- if isUTF8 {
- keywordsStr = utf8toutf16(keywordsStr)
- }
- f.keywords = keywordsStr
- }
- // SetCreator defines the creator of the document. isUTF8 indicates if the
- // string is encoded in ISO-8859-1 (false) or UTF-8 (true).
- func (f *Fpdf) SetCreator(creatorStr string, isUTF8 bool) {
- if isUTF8 {
- creatorStr = utf8toutf16(creatorStr)
- }
- f.creator = creatorStr
- }
- // SetXmpMetadata defines XMP metadata that will be embedded with the document.
- func (f *Fpdf) SetXmpMetadata(xmpStream []byte) {
- f.xmp = xmpStream
- }
- // AliasNbPages defines an alias for the total number of pages. It will be
- // substituted as the document is closed. An empty string is replaced with the
- // string "{nb}".
- //
- // See the example for AddPage() for a demonstration of this method.
- func (f *Fpdf) AliasNbPages(aliasStr string) {
- if aliasStr == "" {
- aliasStr = "{nb}"
- }
- f.aliasNbPagesStr = aliasStr
- }
- // RTL enables right-to-left mode
- func (f *Fpdf) RTL() {
- f.isRTL = true
- }
- // LTR disables right-to-left mode
- func (f *Fpdf) LTR() {
- f.isRTL = false
- }
- // open begins a document
- func (f *Fpdf) open() {
- f.state = 1
- }
- // Close terminates the PDF document. It is not necessary to call this method
- // explicitly because Output(), OutputAndClose() and OutputFileAndClose() do it
- // automatically. If the document contains no page, AddPage() is called to
- // prevent the generation of an invalid document.
- func (f *Fpdf) Close() {
- if f.err == nil {
- if f.clipNest > 0 {
- f.err = fmt.Errorf("clip procedure must be explicitly ended")
- } else if f.transformNest > 0 {
- f.err = fmt.Errorf("transformation procedure must be explicitly ended")
- }
- }
- if f.err != nil {
- return
- }
- if f.state == 3 {
- return
- }
- if f.page == 0 {
- f.AddPage()
- if f.err != nil {
- return
- }
- }
- // Page footer
- f.inFooter = true
- if f.footerFnc != nil {
- f.footerFnc()
- } else if f.footerFncLpi != nil {
- f.footerFncLpi(true)
- }
- f.inFooter = false
- // Close page
- f.endpage()
- // Close document
- f.enddoc()
- return
- }
- // PageSize returns the width and height of the specified page in the units
- // established in New(). These return values are followed by the unit of
- // measure itself. If pageNum is zero or otherwise out of bounds, it returns
- // the default page size, that is, the size of the page that would be added by
- // AddPage().
- func (f *Fpdf) PageSize(pageNum int) (wd, ht float64, unitStr string) {
- sz, ok := f.pageSizes[pageNum]
- if ok {
- sz.Wd, sz.Ht = sz.Wd/f.k, sz.Ht/f.k
- } else {
- sz = f.defPageSize // user units
- }
- return sz.Wd, sz.Ht, f.unitStr
- }
- // AddPageFormat adds a new page with non-default orientation or size. See
- // AddPage() for more details.
- //
- // See New() for a description of orientationStr.
- //
- // size specifies the size of the new page in the units established in New().
- //
- // The PageSize() example demonstrates this method.
- func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
- if f.err != nil {
- return
- }
- if f.page != len(f.pages)-1 {
- f.page = len(f.pages) - 1
- }
- if f.state == 0 {
- f.open()
- }
- familyStr := f.fontFamily
- style := f.fontStyle
- if f.underline {
- style += "U"
- }
- if f.strikeout {
- style += "S"
- }
- fontsize := f.fontSizePt
- lw := f.lineWidth
- dc := f.color.draw
- fc := f.color.fill
- tc := f.color.text
- cf := f.colorFlag
- if f.page > 0 {
- f.inFooter = true
- // Page footer avoid double call on footer.
- if f.footerFnc != nil {
- f.footerFnc()
- } else if f.footerFncLpi != nil {
- f.footerFncLpi(false) // not last page.
- }
- f.inFooter = false
- // Close page
- f.endpage()
- }
- // Start new page
- f.beginpage(orientationStr, size)
- // Set line cap style to current value
- // f.out("2 J")
- f.outf("%d J", f.capStyle)
- // Set line join style to current value
- f.outf("%d j", f.joinStyle)
- // Set line width
- f.lineWidth = lw
- f.outf("%.2f w", lw*f.k)
- // Set dash pattern
- if len(f.dashArray) > 0 {
- f.outputDashPattern()
- }
- // Set font
- if familyStr != "" {
- f.SetFont(familyStr, style, fontsize)
- if f.err != nil {
- return
- }
- }
- // Set colors
- f.color.draw = dc
- if dc.str != "0 G" {
- f.out(dc.str)
- }
- f.color.fill = fc
- if fc.str != "0 g" {
- f.out(fc.str)
- }
- f.color.text = tc
- f.colorFlag = cf
- // Page header
- if f.headerFnc != nil {
- f.inHeader = true
- f.headerFnc()
- f.inHeader = false
- if f.headerHomeMode {
- f.SetHomeXY()
- }
- }
- // Restore line width
- if f.lineWidth != lw {
- f.lineWidth = lw
- f.outf("%.2f w", lw*f.k)
- }
- // Restore font
- if familyStr != "" {
- f.SetFont(familyStr, style, fontsize)
- if f.err != nil {
- return
- }
- }
- // Restore colors
- if f.color.draw.str != dc.str {
- f.color.draw = dc
- f.out(dc.str)
- }
- if f.color.fill.str != fc.str {
- f.color.fill = fc
- f.out(fc.str)
- }
- f.color.text = tc
- f.colorFlag = cf
- return
- }
- // AddPage adds a new page to the document. If a page is already present, the
- // Footer() method is called first to output the footer. Then the page is
- // added, the current position set to the top-left corner according to the left
- // and top margins, and Header() is called to display the header.
- //
- // The font which was set before calling is automatically restored. There is no
- // need to call SetFont() again if you want to continue with the same font. The
- // same is true for colors and line width.
- //
- // The origin of the coordinate system is at the top-left corner and increasing
- // ordinates go downwards.
- //
- // See AddPageFormat() for a version of this method that allows the page size
- // and orientation to be different than the default.
- func (f *Fpdf) AddPage() {
- if f.err != nil {
- return
- }
- // dbg("AddPage")
- f.AddPageFormat(f.defOrientation, f.defPageSize)
- return
- }
- // PageNo returns the current page number.
- //
- // See the example for AddPage() for a demonstration of this method.
- func (f *Fpdf) PageNo() int {
- return f.page
- }
- func colorComp(v int) (int, float64) {
- if v < 0 {
- v = 0
- } else if v > 255 {
- v = 255
- }
- return v, float64(v) / 255.0
- }
- func rgbColorValue(r, g, b int, grayStr, fullStr string) (clr colorType) {
- clr.ir, clr.r = colorComp(r)
- clr.ig, clr.g = colorComp(g)
- clr.ib, clr.b = colorComp(b)
- clr.mode = colorModeRGB
- clr.gray = clr.ir == clr.ig && clr.r == clr.b
- if len(grayStr) > 0 {
- if clr.gray {
- clr.str = sprintf("%.3f %s", clr.r, grayStr)
- } else {
- clr.str = sprintf("%.3f %.3f %.3f %s", clr.r, clr.g, clr.b, fullStr)
- }
- } else {
- clr.str = sprintf("%.3f %.3f %.3f", clr.r, clr.g, clr.b)
- }
- return
- }
- // SetDrawColor defines the color used for all drawing operations (lines,
- // rectangles and cell borders). It is expressed in RGB components (0 - 255).
- // The method can be called before the first page is created. The value is
- // retained from page to page.
- func (f *Fpdf) SetDrawColor(r, g, b int) {
- f.setDrawColor(r, g, b)
- }
- func (f *Fpdf) setDrawColor(r, g, b int) {
- f.color.draw = rgbColorValue(r, g, b, "G", "RG")
- if f.page > 0 {
- f.out(f.color.draw.str)
- }
- }
- // GetDrawColor returns the most recently set draw color as RGB components (0 -
- // 255). This will not be the current value if a draw color of some other type
- // (for example, spot) has been more recently set.
- func (f *Fpdf) GetDrawColor() (int, int, int) {
- return f.color.draw.ir, f.color.draw.ig, f.color.draw.ib
- }
- // SetFillColor defines the color used for all filling operations (filled
- // rectangles and cell backgrounds). It is expressed in RGB components (0
- // -255). The method can be called before the first page is created and the
- // value is retained from page to page.
- func (f *Fpdf) SetFillColor(r, g, b int) {
- f.setFillColor(r, g, b)
- }
- func (f *Fpdf) setFillColor(r, g, b int) {
- f.color.fill = rgbColorValue(r, g, b, "g", "rg")
- f.colorFlag = f.color.fill.str != f.color.text.str
- if f.page > 0 {
- f.out(f.color.fill.str)
- }
- }
- // GetFillColor returns the most recently set fill color as RGB components (0 -
- // 255). This will not be the current value if a fill color of some other type
- // (for example, spot) has been more recently set.
- func (f *Fpdf) GetFillColor() (int, int, int) {
- return f.color.fill.ir, f.color.fill.ig, f.color.fill.ib
- }
- // SetTextColor defines the color used for text. It is expressed in RGB
- // components (0 - 255). The method can be called before the first page is
- // created. The value is retained from page to page.
- func (f *Fpdf) SetTextColor(r, g, b int) {
- f.setTextColor(r, g, b)
- }
- func (f *Fpdf) setTextColor(r, g, b int) {
- f.color.text = rgbColorValue(r, g, b, "g", "rg")
- f.colorFlag = f.color.fill.str != f.color.text.str
- }
- // GetTextColor returns the most recently set text color as RGB components (0 -
- // 255). This will not be the current value if a text color of some other type
- // (for example, spot) has been more recently set.
- func (f *Fpdf) GetTextColor() (int, int, int) {
- return f.color.text.ir, f.color.text.ig, f.color.text.ib
- }
- // GetStringWidth returns the length of a string in user units. A font must be
- // currently selected.
- func (f *Fpdf) GetStringWidth(s string) float64 {
- if f.err != nil {
- return 0
- }
- w := f.GetStringSymbolWidth(s)
- return float64(w) * f.fontSize / 1000
- }
- // GetStringSymbolWidth returns the length of a string in glyf units. A font must be
- // currently selected.
- func (f *Fpdf) GetStringSymbolWidth(s string) int {
- if f.err != nil {
- return 0
- }
- w := 0
- if f.isCurrentUTF8 {
- unicode := []rune(s)
- for _, char := range unicode {
- intChar := int(char)
- if len(f.currentFont.Cw) >= intChar && f.currentFont.Cw[intChar] > 0 {
- if f.currentFont.Cw[intChar] != 65535 {
- w += f.currentFont.Cw[intChar]
- }
- } else if f.currentFont.Desc.MissingWidth != 0 {
- w += f.currentFont.Desc.MissingWidth
- } else {
- w += 500
- }
- }
- } else {
- for _, ch := range []byte(s) {
- if ch == 0 {
- break
- }
- w += f.currentFont.Cw[ch]
- }
- }
- return w
- }
- // SetLineWidth defines the line width. By default, the value equals 0.2 mm.
- // The method can be called before the first page is created. The value is
- // retained from page to page.
- func (f *Fpdf) SetLineWidth(width float64) {
- f.setLineWidth(width)
- }
- func (f *Fpdf) setLineWidth(width float64) {
- f.lineWidth = width
- if f.page > 0 {
- f.outf("%.2f w", width*f.k)
- }
- }
- // GetLineWidth returns the current line thickness.
- func (f *Fpdf) GetLineWidth() float64 {
- return f.lineWidth
- }
- // SetLineCapStyle defines the line cap style. styleStr should be "butt",
- // "round" or "square". A square style projects from the end of the line. The
- // method can be called before the first page is created. The value is
- // retained from page to page.
- func (f *Fpdf) SetLineCapStyle(styleStr string) {
- var capStyle int
- switch styleStr {
- case "round":
- capStyle = 1
- case "square":
- capStyle = 2
- default:
- capStyle = 0
- }
- f.capStyle = capStyle
- if f.page > 0 {
- f.outf("%d J", f.capStyle)
- }
- }
- // SetLineJoinStyle defines the line cap style. styleStr should be "miter",
- // "round" or "bevel". The method can be called before the first page
- // is created. The value is retained from page to page.
- func (f *Fpdf) SetLineJoinStyle(styleStr string) {
- var joinStyle int
- switch styleStr {
- case "round":
- joinStyle = 1
- case "bevel":
- joinStyle = 2
- default:
- joinStyle = 0
- }
- f.joinStyle = joinStyle
- if f.page > 0 {
- f.outf("%d j", f.joinStyle)
- }
- }
- // SetDashPattern sets the dash pattern that is used to draw lines. The
- // dashArray elements are numbers that specify the lengths, in units
- // established in New(), of alternating dashes and gaps. The dash phase
- // specifies the distance into the dash pattern at which to start the dash. The
- // dash pattern is retained from page to page. Call this method with an empty
- // array to restore solid line drawing.
- //
- // The Beziergon() example demonstrates this method.
- func (f *Fpdf) SetDashPattern(dashArray []float64, dashPhase float64) {
- scaled := make([]float64, len(dashArray))
- for i, value := range dashArray {
- scaled[i] = value * f.k
- }
- dashPhase *= f.k
- f.dashArray = scaled
- f.dashPhase = dashPhase
- if f.page > 0 {
- f.outputDashPattern()
- }
- }
- func (f *Fpdf) outputDashPattern() {
- var buf bytes.Buffer
- buf.WriteByte('[')
- for i, value := range f.dashArray {
- if i > 0 {
- buf.WriteByte(' ')
- }
- buf.WriteString(strconv.FormatFloat(value, 'f', 2, 64))
- }
- buf.WriteString("] ")
- buf.WriteString(strconv.FormatFloat(f.dashPhase, 'f', 2, 64))
- buf.WriteString(" d")
- f.outbuf(&buf)
- }
- // Line draws a line between points (x1, y1) and (x2, y2) using the current
- // draw color, line width and cap style.
- func (f *Fpdf) Line(x1, y1, x2, y2 float64) {
- f.outf("%.2f %.2f m %.2f %.2f l S", x1*f.k, (f.h-y1)*f.k, x2*f.k, (f.h-y2)*f.k)
- }
- // fillDrawOp corrects path painting operators
- func fillDrawOp(styleStr string) (opStr string) {
- switch strings.ToUpper(styleStr) {
- case "", "D":
- // Stroke the path.
- opStr = "S"
- case "F":
- // fill the path, using the nonzero winding number rule
- opStr = "f"
- case "F*":
- // fill the path, using the even-odd rule
- opStr = "f*"
- case "FD", "DF":
- // fill and then stroke the path, using the nonzero winding number rule
- opStr = "B"
- case "FD*", "DF*":
- // fill and then stroke the path, using the even-odd rule
- opStr = "B*"
- default:
- opStr = styleStr
- }
- return
- }
- // Rect outputs a rectangle of width w and height h with the upper left corner
- // positioned at point (x, y).
- //
- // It can be drawn (border only), filled (with no border) or both. styleStr can
- // be "F" for filled, "D" for outlined only, or "DF" or "FD" for outlined and
- // filled. An empty string will be replaced with "D". Drawing uses the current
- // draw color and line width centered on the rectangle's perimeter. Filling
- // uses the current fill color.
- func (f *Fpdf) Rect(x, y, w, h float64, styleStr string) {
- f.outf("%.2f %.2f %.2f %.2f re %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, fillDrawOp(styleStr))
- }
- // RoundedRect outputs a rectangle of width w and height h with the upper left
- // corner positioned at point (x, y). It can be drawn (border only), filled
- // (with no border) or both. styleStr can be "F" for filled, "D" for outlined
- // only, or "DF" or "FD" for outlined and filled. An empty string will be
- // replaced with "D". Drawing uses the current draw color and line width
- // centered on the rectangle's perimeter. Filling uses the current fill color.
- // The rounded corners of the rectangle are specified by radius r. corners is a
- // string that includes "1" to round the upper left corner, "2" to round the
- // upper right corner, "3" to round the lower right corner, and "4" to round
- // the lower left corner. The RoundedRect example demonstrates this method.
- func (f *Fpdf) RoundedRect(x, y, w, h, r float64, corners string, stylestr string) {
- // This routine was adapted by Brigham Thompson from a script by Christophe Prugnaud
- var rTL, rTR, rBR, rBL float64 // zero means no rounded corner
- if strings.Contains(corners, "1") {
- rTL = r
- }
- if strings.Contains(corners, "2") {
- rTR = r
- }
- if strings.Contains(corners, "3") {
- rBR = r
- }
- if strings.Contains(corners, "4") {
- rBL = r
- }
- f.RoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL, stylestr)
- }
- // RoundedRectExt behaves the same as RoundedRect() but supports a different
- // radius for each corner. A zero radius means squared corner. See
- // RoundedRect() for more details. This method is demonstrated in the
- // RoundedRect() example.
- func (f *Fpdf) RoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL float64, stylestr string) {
- f.roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL)
- f.out(fillDrawOp(stylestr))
- }
- // Circle draws a circle centered on point (x, y) with radius r.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color and line width centered on the circle's perimeter.
- // Filling uses the current fill color.
- func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
- f.Ellipse(x, y, r, r, 0, styleStr)
- }
- // Ellipse draws an ellipse centered at point (x, y). rx and ry specify its
- // horizontal and vertical radii.
- //
- // degRotate specifies the counter-clockwise angle in degrees that the ellipse
- // will be rotated.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color and line width centered on the ellipse's perimeter.
- // Filling uses the current fill color.
- //
- // The Circle() example demonstrates this method.
- func (f *Fpdf) Ellipse(x, y, rx, ry, degRotate float64, styleStr string) {
- f.arc(x, y, rx, ry, degRotate, 0, 360, styleStr, false)
- }
- // Polygon draws a closed figure defined by a series of vertices specified by
- // points. The x and y fields of the points use the units established in New().
- // The last point in the slice will be implicitly joined to the first to close
- // the polygon.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color and line width centered on the ellipse's perimeter.
- // Filling uses the current fill color.
- func (f *Fpdf) Polygon(points []PointType, styleStr string) {
- if len(points) > 2 {
- for j, pt := range points {
- if j == 0 {
- f.point(pt.X, pt.Y)
- } else {
- f.outf("%.5f %.5f l ", pt.X*f.k, (f.h-pt.Y)*f.k)
- }
- }
- f.outf("%.5f %.5f l ", points[0].X*f.k, (f.h-points[0].Y)*f.k)
- f.DrawPath(styleStr)
- }
- }
- // Beziergon draws a closed figure defined by a series of cubic Bézier curve
- // segments. The first point in the slice defines the starting point of the
- // figure. Each three following points p1, p2, p3 represent a curve segment to
- // the point p3 using p1 and p2 as the Bézier control points.
- //
- // The x and y fields of the points use the units established in New().
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color and line width centered on the ellipse's perimeter.
- // Filling uses the current fill color.
- func (f *Fpdf) Beziergon(points []PointType, styleStr string) {
- // Thanks, Robert Lillack, for contributing this function.
- if len(points) < 4 {
- return
- }
- f.point(points[0].XY())
- points = points[1:]
- for len(points) >= 3 {
- cx0, cy0 := points[0].XY()
- cx1, cy1 := points[1].XY()
- x1, y1 := points[2].XY()
- f.curve(cx0, cy0, cx1, cy1, x1, y1)
- points = points[3:]
- }
- f.DrawPath(styleStr)
- }
- // point outputs current point
- func (f *Fpdf) point(x, y float64) {
- f.outf("%.2f %.2f m", x*f.k, (f.h-y)*f.k)
- }
- // curve outputs a single cubic Bézier curve segment from current point
- func (f *Fpdf) curve(cx0, cy0, cx1, cy1, x, y float64) {
- // Thanks, Robert Lillack, for straightening this out
- f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c", cx0*f.k, (f.h-cy0)*f.k, cx1*f.k,
- (f.h-cy1)*f.k, x*f.k, (f.h-y)*f.k)
- }
- // Curve draws a single-segment quadratic Bézier curve. The curve starts at
- // the point (x0, y0) and ends at the point (x1, y1). The control point (cx,
- // cy) specifies the curvature. At the start point, the curve is tangent to the
- // straight line between the start point and the control point. At the end
- // point, the curve is tangent to the straight line between the end point and
- // the control point.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color, line width, and cap style centered on the curve's
- // path. Filling uses the current fill color.
- //
- // The Circle() example demonstrates this method.
- func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) {
- f.point(x0, y0)
- f.outf("%.5f %.5f %.5f %.5f v %s", cx*f.k, (f.h-cy)*f.k, x1*f.k, (f.h-y1)*f.k,
- fillDrawOp(styleStr))
- }
- // CurveCubic draws a single-segment cubic Bézier curve. This routine performs
- // the same function as CurveBezierCubic() but has a nonstandard argument order.
- // It is retained to preserve backward compatibility.
- func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string) {
- // f.point(x0, y0)
- // f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
- // cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
- f.CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1, styleStr)
- }
- // CurveBezierCubic draws a single-segment cubic Bézier curve. The curve starts at
- // the point (x0, y0) and ends at the point (x1, y1). The control points (cx0,
- // cy0) and (cx1, cy1) specify the curvature. At the start point, the curve is
- // tangent to the straight line between the start point and the control point
- // (cx0, cy0). At the end point, the curve is tangent to the straight line
- // between the end point and the control point (cx1, cy1).
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color, line width, and cap style centered on the curve's
- // path. Filling uses the current fill color.
- //
- // This routine performs the same function as CurveCubic() but uses standard
- // argument order.
- //
- // The Circle() example demonstrates this method.
- func (f *Fpdf) CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string) {
- f.point(x0, y0)
- f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
- cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
- }
- // Arc draws an elliptical arc centered at point (x, y). rx and ry specify its
- // horizontal and vertical radii.
- //
- // degRotate specifies the angle that the arc will be rotated. degStart and
- // degEnd specify the starting and ending angle of the arc. All angles are
- // specified in degrees and measured counter-clockwise from the 3 o'clock
- // position.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color, line width, and cap style centered on the arc's
- // path. Filling uses the current fill color.
- //
- // The Circle() example demonstrates this method.
- func (f *Fpdf) Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) {
- f.arc(x, y, rx, ry, degRotate, degStart, degEnd, styleStr, false)
- }
- // GetAlpha returns the alpha blending channel, which consists of the
- // alpha transparency value and the blend mode. See SetAlpha for more
- // details.
- func (f *Fpdf) GetAlpha() (alpha float64, blendModeStr string) {
- return f.alpha, f.blendMode
- }
- // SetAlpha sets the alpha blending channel. The blending effect applies to
- // text, drawings and images.
- //
- // alpha must be a value between 0.0 (fully transparent) to 1.0 (fully opaque).
- // Values outside of this range result in an error.
- //
- // blendModeStr must be one of "Normal", "Multiply", "Screen", "Overlay",
- // "Darken", "Lighten", "ColorDodge", "ColorBurn","HardLight", "SoftLight",
- // "Difference", "Exclusion", "Hue", "Saturation", "Color", or "Luminosity". An
- // empty string is replaced with "Normal".
- //
- // To reset normal rendering after applying a blending mode, call this method
- // with alpha set to 1.0 and blendModeStr set to "Normal".
- func (f *Fpdf) SetAlpha(alpha float64, blendModeStr string) {
- if f.err != nil {
- return
- }
- var bl blendModeType
- switch blendModeStr {
- case "Normal", "Multiply", "Screen", "Overlay",
- "Darken", "Lighten", "ColorDodge", "ColorBurn", "HardLight", "SoftLight",
- "Difference", "Exclusion", "Hue", "Saturation", "Color", "Luminosity":
- bl.modeStr = blendModeStr
- case "":
- bl.modeStr = "Normal"
- default:
- f.err = fmt.Errorf("unrecognized blend mode \"%s\"", blendModeStr)
- return
- }
- if alpha < 0.0 || alpha > 1.0 {
- f.err = fmt.Errorf("alpha value (0.0 - 1.0) is out of range: %.3f", alpha)
- return
- }
- f.alpha = alpha
- f.blendMode = blendModeStr
- alphaStr := sprintf("%.3f", alpha)
- keyStr := sprintf("%s %s", alphaStr, blendModeStr)
- pos, ok := f.blendMap[keyStr]
- if !ok {
- pos = len(f.blendList) // at least 1
- f.blendList = append(f.blendList, blendModeType{alphaStr, alphaStr, blendModeStr, 0})
- f.blendMap[keyStr] = pos
- }
- f.outf("/GS%d gs", pos)
- }
- func (f *Fpdf) gradientClipStart(x, y, w, h float64) {
- // Save current graphic state and set clipping area
- f.outf("q %.2f %.2f %.2f %.2f re W n", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k)
- // Set up transformation matrix for gradient
- f.outf("%.5f 0 0 %.5f %.5f %.5f cm", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k)
- }
- func (f *Fpdf) gradientClipEnd() {
- // Restore previous graphic state
- f.out("Q")
- }
- func (f *Fpdf) gradient(tp, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
- pos := len(f.gradientList)
- clr1 := rgbColorValue(r1, g1, b1, "", "")
- clr2 := rgbColorValue(r2, g2, b2, "", "")
- f.gradientList = append(f.gradientList, gradientType{tp, clr1.str, clr2.str,
- x1, y1, x2, y2, r, 0})
- f.outf("/Sh%d sh", pos)
- }
- // LinearGradient draws a rectangular area with a blending of one color to
- // another. The rectangle is of width w and height h. Its upper left corner is
- // positioned at point (x, y).
- //
- // Each color is specified with three component values, one each for red, green
- // and blue. The values range from 0 to 255. The first color is specified by
- // (r1, g1, b1) and the second color by (r2, g2, b2).
- //
- // The blending is controlled with a gradient vector that uses normalized
- // coordinates in which the lower left corner is position (0, 0) and the upper
- // right corner is (1, 1). The vector's origin and destination are specified by
- // the points (x1, y1) and (x2, y2). In a linear gradient, blending occurs
- // perpendicularly to the vector. The vector does not necessarily need to be
- // anchored on the rectangle edge. Color 1 is used up to the origin of the
- // vector and color 2 is used beyond the vector's end point. Between the points
- // the colors are gradually blended.
- func (f *Fpdf) LinearGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2 float64) {
- f.gradientClipStart(x, y, w, h)
- f.gradient(2, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, 0)
- f.gradientClipEnd()
- }
- // RadialGradient draws a rectangular area with a blending of one color to
- // another. The rectangle is of width w and height h. Its upper left corner is
- // positioned at point (x, y).
- //
- // Each color is specified with three component values, one each for red, green
- // and blue. The values range from 0 to 255. The first color is specified by
- // (r1, g1, b1) and the second color by (r2, g2, b2).
- //
- // The blending is controlled with a point and a circle, both specified with
- // normalized coordinates in which the lower left corner of the rendered
- // rectangle is position (0, 0) and the upper right corner is (1, 1). Color 1
- // begins at the origin point specified by (x1, y1). Color 2 begins at the
- // circle specified by the center point (x2, y2) and radius r. Colors are
- // gradually blended from the origin to the circle. The origin and the circle's
- // center do not necessarily have to coincide, but the origin must be within
- // the circle to avoid rendering problems.
- //
- // The LinearGradient() example demonstrates this method.
- func (f *Fpdf) RadialGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
- f.gradientClipStart(x, y, w, h)
- f.gradient(3, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, r)
- f.gradientClipEnd()
- }
- // ClipRect begins a rectangular clipping operation. The rectangle is of width
- // w and height h. Its upper left corner is positioned at point (x, y). outline
- // is true to draw a border with the current draw color and line width centered
- // on the rectangle's perimeter. Only the outer half of the border will be
- // shown. After calling this method, all rendering operations (for example,
- // Image(), LinearGradient(), etc) will be clipped by the specified rectangle.
- // Call ClipEnd() to restore unclipped operations.
- //
- // This ClipText() example demonstrates this method.
- func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) {
- f.clipNest++
- f.outf("q %.2f %.2f %.2f %.2f re W %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, strIf(outline, "S", "n"))
- }
- // ClipText begins a clipping operation in which rendering is confined to the
- // character string specified by txtStr. The origin (x, y) is on the left of
- // the first character at the baseline. The current font is used. outline is
- // true to draw a border with the current draw color and line width centered on
- // the perimeters of the text characters. Only the outer half of the border
- // will be shown. After calling this method, all rendering operations (for
- // example, Image(), LinearGradient(), etc) will be clipped. Call ClipEnd() to
- // restore unclipped operations.
- func (f *Fpdf) ClipText(x, y float64, txtStr string, outline bool) {
- f.clipNest++
- f.outf("q BT %.5f %.5f Td %d Tr (%s) Tj ET", x*f.k, (f.h-y)*f.k, intIf(outline, 5, 7), f.escape(txtStr))
- }
- func (f *Fpdf) clipArc(x1, y1, x2, y2, x3, y3 float64) {
- h := f.h
- f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c ", x1*f.k, (h-y1)*f.k,
- x2*f.k, (h-y2)*f.k, x3*f.k, (h-y3)*f.k)
- }
- // ClipRoundedRect begins a rectangular clipping operation. The rectangle is of
- // width w and height h. Its upper left corner is positioned at point (x, y).
- // The rounded corners of the rectangle are specified by radius r. outline is
- // true to draw a border with the current draw color and line width centered on
- // the rectangle's perimeter. Only the outer half of the border will be shown.
- // After calling this method, all rendering operations (for example, Image(),
- // LinearGradient(), etc) will be clipped by the specified rectangle. Call
- // ClipEnd() to restore unclipped operations.
- //
- // This ClipText() example demonstrates this method.
- func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) {
- f.ClipRoundedRectExt(x, y, w, h, r, r, r, r, outline)
- }
- // ClipRoundedRectExt behaves the same as ClipRoundedRect() but supports a
- // different radius for each corner, given by rTL (top-left), rTR (top-right)
- // rBR (bottom-right), rBL (bottom-left). See ClipRoundedRect() for more
- // details. This method is demonstrated in the ClipText() example.
- func (f *Fpdf) ClipRoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL float64, outline bool) {
- f.clipNest++
- f.roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL)
- f.outf(" W %s", strIf(outline, "S", "n"))
- }
- // add a rectangle path with rounded corners.
- // routine shared by RoundedRect() and ClipRoundedRect(), which add the
- // drawing operation
- func (f *Fpdf) roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL float64) {
- k := f.k
- hp := f.h
- myArc := (4.0 / 3.0) * (math.Sqrt2 - 1.0)
- f.outf("q %.5f %.5f m", (x+rTL)*k, (hp-y)*k)
- xc := x + w - rTR
- yc := y + rTR
- f.outf("%.5f %.5f l", xc*k, (hp-y)*k)
- if rTR != 0 {
- f.clipArc(xc+rTR*myArc, yc-rTR, xc+rTR, yc-rTR*myArc, xc+rTR, yc)
- }
- xc = x + w - rBR
- yc = y + h - rBR
- f.outf("%.5f %.5f l", (x+w)*k, (hp-yc)*k)
- if rBR != 0 {
- f.clipArc(xc+rBR, yc+rBR*myArc, xc+rBR*myArc, yc+rBR, xc, yc+rBR)
- }
- xc = x + rBL
- yc = y + h - rBL
- f.outf("%.5f %.5f l", xc*k, (hp-(y+h))*k)
- if rBL != 0 {
- f.clipArc(xc-rBL*myArc, yc+rBL, xc-rBL, yc+rBL*myArc, xc-rBL, yc)
- }
- xc = x + rTL
- yc = y + rTL
- f.outf("%.5f %.5f l", x*k, (hp-yc)*k)
- if rTL != 0 {
- f.clipArc(xc-rTL, yc-rTL*myArc, xc-rTL*myArc, yc-rTL, xc, yc-rTL)
- }
- }
- // ClipEllipse begins an elliptical clipping operation. The ellipse is centered
- // at (x, y). Its horizontal and vertical radii are specified by rx and ry.
- // outline is true to draw a border with the current draw color and line width
- // centered on the ellipse's perimeter. Only the outer half of the border will
- // be shown. After calling this method, all rendering operations (for example,
- // Image(), LinearGradient(), etc) will be clipped by the specified ellipse.
- // Call ClipEnd() to restore unclipped operations.
- //
- // This ClipText() example demonstrates this method.
- func (f *Fpdf) ClipEllipse(x, y, rx, ry float64, outline bool) {
- f.clipNest++
- lx := (4.0 / 3.0) * rx * (math.Sqrt2 - 1)
- ly := (4.0 / 3.0) * ry * (math.Sqrt2 - 1)
- k := f.k
- h := f.h
- f.outf("q %.5f %.5f m %.5f %.5f %.5f %.5f %.5f %.5f c",
- (x+rx)*k, (h-y)*k,
- (x+rx)*k, (h-(y-ly))*k,
- (x+lx)*k, (h-(y-ry))*k,
- x*k, (h-(y-ry))*k)
- f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
- (x-lx)*k, (h-(y-ry))*k,
- (x-rx)*k, (h-(y-ly))*k,
- (x-rx)*k, (h-y)*k)
- f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
- (x-rx)*k, (h-(y+ly))*k,
- (x-lx)*k, (h-(y+ry))*k,
- x*k, (h-(y+ry))*k)
- f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c W %s",
- (x+lx)*k, (h-(y+ry))*k,
- (x+rx)*k, (h-(y+ly))*k,
- (x+rx)*k, (h-y)*k,
- strIf(outline, "S", "n"))
- }
- // ClipCircle begins a circular clipping operation. The circle is centered at
- // (x, y) and has radius r. outline is true to draw a border with the current
- // draw color and line width centered on the circle's perimeter. Only the outer
- // half of the border will be shown. After calling this method, all rendering
- // operations (for example, Image(), LinearGradient(), etc) will be clipped by
- // the specified circle. Call ClipEnd() to restore unclipped operations.
- //
- // The ClipText() example demonstrates this method.
- func (f *Fpdf) ClipCircle(x, y, r float64, outline bool) {
- f.ClipEllipse(x, y, r, r, outline)
- }
- // ClipPolygon begins a clipping operation within a polygon. The figure is
- // defined by a series of vertices specified by points. The x and y fields of
- // the points use the units established in New(). The last point in the slice
- // will be implicitly joined to the first to close the polygon. outline is true
- // to draw a border with the current draw color and line width centered on the
- // polygon's perimeter. Only the outer half of the border will be shown. After
- // calling this method, all rendering operations (for example, Image(),
- // LinearGradient(), etc) will be clipped by the specified polygon. Call
- // ClipEnd() to restore unclipped operations.
- //
- // The ClipText() example demonstrates this method.
- func (f *Fpdf) ClipPolygon(points []PointType, outline bool) {
- f.clipNest++
- var s fmtBuffer
- h := f.h
- k := f.k
- s.printf("q ")
- for j, pt := range points {
- s.printf("%.5f %.5f %s ", pt.X*k, (h-pt.Y)*k, strIf(j == 0, "m", "l"))
- }
- s.printf("h W %s", strIf(outline, "S", "n"))
- f.out(s.String())
- }
- // ClipEnd ends a clipping operation that was started with a call to
- // ClipRect(), ClipRoundedRect(), ClipText(), ClipEllipse(), ClipCircle() or
- // ClipPolygon(). Clipping operations can be nested. The document cannot be
- // successfully output while a clipping operation is active.
- //
- // The ClipText() example demonstrates this method.
- func (f *Fpdf) ClipEnd() {
- if f.err == nil {
- if f.clipNest > 0 {
- f.clipNest--
- f.out("Q")
- } else {
- f.err = fmt.Errorf("error attempting to end clip operation out of sequence")
- }
- }
- }
- // AddFont imports a TrueType, OpenType or Type1 font and makes it available.
- // It is necessary to generate a font definition file first with the makefont
- // utility. It is not necessary to call this function for the core PDF fonts
- // (courier, helvetica, times, zapfdingbats).
- //
- // The JSON definition file (and the font file itself when embedding) must be
- // present in the font directory. If it is not found, the error "Could not
- // include font definition file" is set.
- //
- // family specifies the font family. The name can be chosen arbitrarily. If it
- // is a standard family name, it will override the corresponding font. This
- // string is used to subsequently set the font with the SetFont method.
- //
- // style specifies the font style. Acceptable values are (case insensitive) the
- // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
- // "IB" for bold and italic combined.
- //
- // fileStr specifies the base name with ".json" extension of the font
- // definition file to be added. The file will be loaded from the font directory
- // specified in the call to New() or SetFontLocation().
- func (f *Fpdf) AddFont(familyStr, styleStr, fileStr string) {
- f.addFont(fontFamilyEscape(familyStr), styleStr, fileStr, false)
- }
- // AddUTF8Font imports a TrueType font with utf-8 symbols and makes it available.
- // It is necessary to generate a font definition file first with the makefont
- // utility. It is not necessary to call this function for the core PDF fonts
- // (courier, helvetica, times, zapfdingbats).
- //
- // The JSON definition file (and the font file itself when embedding) must be
- // present in the font directory. If it is not found, the error "Could not
- // include font definition file" is set.
- //
- // family specifies the font family. The name can be chosen arbitrarily. If it
- // is a standard family name, it will override the corresponding font. This
- // string is used to subsequently set the font with the SetFont method.
- //
- // style specifies the font style. Acceptable values are (case insensitive) the
- // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
- // "IB" for bold and italic combined.
- //
- // fileStr specifies the base name with ".json" extension of the font
- // definition file to be added. The file will be loaded from the font directory
- // specified in the call to New() or SetFontLocation().
- func (f *Fpdf) AddUTF8Font(familyStr, styleStr, fileStr string) {
- f.addFont(fontFamilyEscape(familyStr), styleStr, fileStr, true)
- }
- func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool) {
- if fileStr == "" {
- if isUTF8 {
- fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".ttf"
- } else {
- fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json"
- }
- }
- if isUTF8 {
- fontKey := getFontKey(familyStr, styleStr)
- _, ok := f.fonts[fontKey]
- if ok {
- return
- }
- var ttfStat os.FileInfo
- var err error
- fileStr = path.Join(f.fontpath, fileStr)
- ttfStat, err = os.Stat(fileStr)
- if err != nil {
- f.SetError(err)
- return
- }
- originalSize := ttfStat.Size()
- Type := "UTF8"
- var utf8Bytes []byte
- utf8Bytes, err = ioutil.ReadFile(fileStr)
- if err != nil {
- f.SetError(err)
- return
- }
- reader := fileReader{readerPosition: 0, array: utf8Bytes}
- utf8File := newUTF8Font(&reader)
- err = utf8File.parseFile()
- if err != nil {
- f.SetError(err)
- return
- }
- desc := FontDescType{
- Ascent: int(utf8File.Ascent),
- Descent: int(utf8File.Descent),
- CapHeight: utf8File.CapHeight,
- Flags: utf8File.Flags,
- FontBBox: utf8File.Bbox,
- ItalicAngle: utf8File.ItalicAngle,
- StemV: utf8File.StemV,
- MissingWidth: round(utf8File.DefaultWidth),
- }
- var sbarr map[int]int
- if f.aliasNbPagesStr == "" {
- sbarr = makeSubsetRange(57)
- } else {
- sbarr = makeSubsetRange(32)
- }
- def := fontDefType{
- Tp: Type,
- Name: fontKey,
- Desc: desc,
- Up: int(round(utf8File.UnderlinePosition)),
- Ut: round(utf8File.UnderlineThickness),
- Cw: utf8File.CharWidths,
- usedRunes: sbarr,
- File: fileStr,
- utf8File: utf8File,
- }
- def.i, _ = generateFontID(def)
- f.fonts[fontKey] = def
- f.fontFiles[fontKey] = fontFileType{
- length1: originalSize,
- fontType: "UTF8",
- }
- f.fontFiles[fileStr] = fontFileType{
- fontType: "UTF8",
- }
- } else {
- if f.fontLoader != nil {
- reader, err := f.fontLoader.Open(fileStr)
- if err == nil {
- f.AddFontFromReader(familyStr, styleStr, reader)
- if closer, ok := reader.(io.Closer); ok {
- closer.Close()
- }
- return
- }
- }
- fileStr = path.Join(f.fontpath, fileStr)
- file, err := os.Open(fileStr)
- if err != nil {
- f.err = err
- return
- }
- defer file.Close()
- f.AddFontFromReader(familyStr, styleStr, file)
- }
- }
- func makeSubsetRange(end int) map[int]int {
- answer := make(map[int]int)
- for i := 0; i < end; i++ {
- answer[i] = 0
- }
- return answer
- }
- // AddFontFromBytes imports a TrueType, OpenType or Type1 font from static
- // bytes within the executable and makes it available for use in the generated
- // document.
- //
- // family specifies the font family. The name can be chosen arbitrarily. If it
- // is a standard family name, it will override the corresponding font. This
- // string is used to subsequently set the font with the SetFont method.
- //
- // style specifies the font style. Acceptable values are (case insensitive) the
- // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
- // "IB" for bold and italic combined.
- //
- // jsonFileBytes contain all bytes of JSON file.
- //
- // zFileBytes contain all bytes of Z file.
- func (f *Fpdf) AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes []byte) {
- f.addFontFromBytes(fontFamilyEscape(familyStr), styleStr, jsonFileBytes, zFileBytes, nil)
- }
- // AddUTF8FontFromBytes imports a TrueType font with utf-8 symbols from static
- // bytes within the executable and makes it available for use in the generated
- // document.
- //
- // family specifies the font family. The name can be chosen arbitrarily. If it
- // is a standard family name, it will override the corresponding font. This
- // string is used to subsequently set the font with the SetFont method.
- //
- // style specifies the font style. Acceptable values are (case insensitive) the
- // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
- // "IB" for bold and italic combined.
- //
- // jsonFileBytes contain all bytes of JSON file.
- //
- // zFileBytes contain all bytes of Z file.
- func (f *Fpdf) AddUTF8FontFromBytes(familyStr, styleStr string, utf8Bytes []byte) {
- f.addFontFromBytes(fontFamilyEscape(familyStr), styleStr, nil, nil, utf8Bytes)
- }
- func (f *Fpdf) addFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes, utf8Bytes []byte) {
- if f.err != nil {
- return
- }
- // load font key
- var ok bool
- fontkey := getFontKey(familyStr, styleStr)
- _, ok = f.fonts[fontkey]
- if ok {
- return
- }
- if utf8Bytes != nil {
- // if styleStr == "IB" {
- // styleStr = "BI"
- // }
- Type := "UTF8"
- reader := fileReader{readerPosition: 0, array: utf8Bytes}
- utf8File := newUTF8Font(&reader)
- err := utf8File.parseFile()
- if err != nil {
- fmt.Printf("get metrics Error: %e\n", err)
- return
- }
- desc := FontDescType{
- Ascent: int(utf8File.Ascent),
- Descent: int(utf8File.Descent),
- CapHeight: utf8File.CapHeight,
- Flags: utf8File.Flags,
- FontBBox: utf8File.Bbox,
- ItalicAngle: utf8File.ItalicAngle,
- StemV: utf8File.StemV,
- MissingWidth: round(utf8File.DefaultWidth),
- }
- var sbarr map[int]int
- if f.aliasNbPagesStr == "" {
- sbarr = makeSubsetRange(57)
- } else {
- sbarr = makeSubsetRange(32)
- }
- def := fontDefType{
- Tp: Type,
- Name: fontkey,
- Desc: desc,
- Up: int(round(utf8File.UnderlinePosition)),
- Ut: round(utf8File.UnderlineThickness),
- Cw: utf8File.CharWidths,
- utf8File: utf8File,
- usedRunes: sbarr,
- }
- def.i, _ = generateFontID(def)
- f.fonts[fontkey] = def
- } else {
- // load font definitions
- var info fontDefType
- err := json.Unmarshal(jsonFileBytes, &info)
- if err != nil {
- f.err = err
- }
- if f.err != nil {
- return
- }
- if info.i, err = generateFontID(info); err != nil {
- f.err = err
- return
- }
- // search existing encodings
- if len(info.Diff) > 0 {
- n := -1
- for j, str := range f.diffs {
- if str == info.Diff {
- n = j + 1
- break
- }
- }
- if n < 0 {
- f.diffs = append(f.diffs, info.Diff)
- n = len(f.diffs)
- }
- info.DiffN = n
- }
- // embed font
- if len(info.File) > 0 {
- if info.Tp == "TrueType" {
- f.fontFiles[info.File] = fontFileType{
- length1: int64(info.OriginalSize),
- embedded: true,
- content: zFileBytes,
- }
- } else {
- f.fontFiles[info.File] = fontFileType{
- length1: int64(info.Size1),
- length2: int64(info.Size2),
- embedded: true,
- content: zFileBytes,
- }
- }
- }
- f.fonts[fontkey] = info
- }
- }
- // getFontKey is used by AddFontFromReader and GetFontDesc
- func getFontKey(familyStr, styleStr string) string {
- familyStr = strings.ToLower(familyStr)
- styleStr = strings.ToUpper(styleStr)
- if styleStr == "IB" {
- styleStr = "BI"
- }
- return familyStr + styleStr
- }
- // AddFontFromReader imports a TrueType, OpenType or Type1 font and makes it
- // available using a reader that satisifies the io.Reader interface. See
- // AddFont for details about familyStr and styleStr.
- func (f *Fpdf) AddFontFromReader(familyStr, styleStr string, r io.Reader) {
- if f.err != nil {
- return
- }
- // dbg("Adding family [%s], style [%s]", familyStr, styleStr)
- familyStr = fontFamilyEscape(familyStr)
- var ok bool
- fontkey := getFontKey(familyStr, styleStr)
- _, ok = f.fonts[fontkey]
- if ok {
- return
- }
- var info fontDefType
- info = f.loadfont(r)
- if f.err != nil {
- return
- }
- if len(info.Diff) > 0 {
- // Search existing encodings
- n := -1
- for j, str := range f.diffs {
- if str == info.Diff {
- n = j + 1
- break
- }
- }
- if n < 0 {
- f.diffs = append(f.diffs, info.Diff)
- n = len(f.diffs)
- }
- info.DiffN = n
- }
- // dbg("font [%s], type [%s]", info.File, info.Tp)
- if len(info.File) > 0 {
- // Embedded font
- if info.Tp == "TrueType" {
- f.fontFiles[info.File] = fontFileType{length1: int64(info.OriginalSize)}
- } else {
- f.fontFiles[info.File] = fontFileType{length1: int64(info.Size1), length2: int64(info.Size2)}
- }
- }
- f.fonts[fontkey] = info
- return
- }
- // GetFontDesc returns the font descriptor, which can be used for
- // example to find the baseline of a font. If familyStr is empty
- // current font descriptor will be returned.
- // See FontDescType for documentation about the font descriptor.
- // See AddFont for details about familyStr and styleStr.
- func (f *Fpdf) GetFontDesc(familyStr, styleStr string) FontDescType {
- if familyStr == "" {
- return f.currentFont.Desc
- }
- return f.fonts[getFontKey(fontFamilyEscape(familyStr), styleStr)].Desc
- }
- // SetFont sets the font used to print character strings. It is mandatory to
- // call this method at least once before printing text or the resulting
- // document will not be valid.
- //
- // The font can be either a standard one or a font added via the AddFont()
- // method or AddFontFromReader() method. Standard fonts use the Windows
- // encoding cp1252 (Western Europe).
- //
- // The method can be called before the first page is created and the font is
- // kept from page to page. If you just wish to change the current font size, it
- // is simpler to call SetFontSize().
- //
- // Note: the font definition file must be accessible. An error is set if the
- // file cannot be read.
- //
- // familyStr specifies the font family. It can be either a name defined by
- // AddFont(), AddFontFromReader() or one of the standard families (case
- // insensitive): "Courier" for fixed-width, "Helvetica" or "Arial" for sans
- // serif, "Times" for serif, "Symbol" or "ZapfDingbats" for symbolic.
- //
- // styleStr can be "B" (bold), "I" (italic), "U" (underscore), "S" (strike-out)
- // or any combination. The default value (specified with an empty string) is
- // regular. Bold and italic styles do not apply to Symbol and ZapfDingbats.
- //
- // size is the font size measured in points. The default value is the current
- // size. If no size has been specified since the beginning of the document, the
- // value taken is 12.
- func (f *Fpdf) SetFont(familyStr, styleStr string, size float64) {
- // dbg("SetFont x %.2f, lMargin %.2f", f.x, f.lMargin)
- if f.err != nil {
- return
- }
- // dbg("SetFont")
- familyStr = fontFamilyEscape(familyStr)
- var ok bool
- if familyStr == "" {
- familyStr = f.fontFamily
- } else {
- familyStr = strings.ToLower(familyStr)
- }
- styleStr = strings.ToUpper(styleStr)
- f.underline = strings.Contains(styleStr, "U")
- if f.underline {
- styleStr = strings.Replace(styleStr, "U", "", -1)
- }
- f.strikeout = strings.Contains(styleStr, "S")
- if f.strikeout {
- styleStr = strings.Replace(styleStr, "S", "", -1)
- }
- if styleStr == "IB" {
- styleStr = "BI"
- }
- if size == 0.0 {
- size = f.fontSizePt
- }
- // Test if font is already loaded
- fontKey := familyStr + styleStr
- _, ok = f.fonts[fontKey]
- if !ok {
- // Test if one of the core fonts
- if familyStr == "arial" {
- familyStr = "helvetica"
- }
- _, ok = f.coreFonts[familyStr]
- if ok {
- if familyStr == "symbol" {
- familyStr = "zapfdingbats"
- }
- if familyStr == "zapfdingbats" {
- styleStr = ""
- }
- fontKey = familyStr + styleStr
- _, ok = f.fonts[fontKey]
- if !ok {
- rdr := f.coreFontReader(familyStr, styleStr)
- if f.err == nil {
- f.AddFontFromReader(familyStr, styleStr, rdr)
- }
- if f.err != nil {
- return
- }
- }
- } else {
- f.err = fmt.Errorf("undefined font: %s %s", familyStr, styleStr)
- return
- }
- }
- // Select it
- f.fontFamily = familyStr
- f.fontStyle = styleStr
- f.fontSizePt = size
- f.fontSize = size / f.k
- f.currentFont = f.fonts[fontKey]
- if f.currentFont.Tp == "UTF8" {
- f.isCurrentUTF8 = true
- } else {
- f.isCurrentUTF8 = false
- }
- if f.page > 0 {
- f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
- }
- return
- }
- // SetFontStyle sets the style of the current font. See also SetFont()
- func (f *Fpdf) SetFontStyle(styleStr string) {
- f.SetFont(f.fontFamily, styleStr, f.fontSizePt)
- }
- // SetFontSize defines the size of the current font. Size is specified in
- // points (1/ 72 inch). See also SetFontUnitSize().
- func (f *Fpdf) SetFontSize(size float64) {
- f.fontSizePt = size
- f.fontSize = size / f.k
- if f.page > 0 {
- f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
- }
- }
- // SetFontUnitSize defines the size of the current font. Size is specified in
- // the unit of measure specified in New(). See also SetFontSize().
- func (f *Fpdf) SetFontUnitSize(size float64) {
- f.fontSizePt = size * f.k
- f.fontSize = size
- if f.page > 0 {
- f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
- }
- }
- // GetFontSize returns the size of the current font in points followed by the
- // size in the unit of measure specified in New(). The second value can be used
- // as a line height value in drawing operations.
- func (f *Fpdf) GetFontSize() (ptSize, unitSize float64) {
- return f.fontSizePt, f.fontSize
- }
- // AddLink creates a new internal link and returns its identifier. An internal
- // link is a clickable area which directs to another place within the document.
- // The identifier can then be passed to Cell(), Write(), Image() or Link(). The
- // destination is defined with SetLink().
- func (f *Fpdf) AddLink() int {
- f.links = append(f.links, intLinkType{})
- return len(f.links) - 1
- }
- // SetLink defines the page and position a link points to. See AddLink().
- func (f *Fpdf) SetLink(link int, y float64, page int) {
- if y == -1 {
- y = f.y
- }
- if page == -1 {
- page = f.page
- }
- f.links[link] = intLinkType{page, y}
- }
- // newLink adds a new clickable link on current page
- func (f *Fpdf) newLink(x, y, w, h float64, link int, linkStr string) {
- // linkList, ok := f.pageLinks[f.page]
- // if !ok {
- // linkList = make([]linkType, 0, 8)
- // f.pageLinks[f.page] = linkList
- // }
- f.pageLinks[f.page] = append(f.pageLinks[f.page],
- linkType{x * f.k, f.hPt - y*f.k, w * f.k, h * f.k, link, linkStr})
- }
- // Link puts a link on a rectangular area of the page. Text or image links are
- // generally put via Cell(), Write() or Image(), but this method can be useful
- // for instance to define a clickable area inside an image. link is the value
- // returned by AddLink().
- func (f *Fpdf) Link(x, y, w, h float64, link int) {
- f.newLink(x, y, w, h, link, "")
- }
- // LinkString puts a link on a rectangular area of the page. Text or image
- // links are generally put via Cell(), Write() or Image(), but this method can
- // be useful for instance to define a clickable area inside an image. linkStr
- // is the target URL.
- func (f *Fpdf) LinkString(x, y, w, h float64, linkStr string) {
- f.newLink(x, y, w, h, 0, linkStr)
- }
- // Bookmark sets a bookmark that will be displayed in a sidebar outline. txtStr
- // is the title of the bookmark. level specifies the level of the bookmark in
- // the outline; 0 is the top level, 1 is just below, and so on. y specifies the
- // vertical position of the bookmark destination in the current page; -1
- // indicates the current position.
- func (f *Fpdf) Bookmark(txtStr string, level int, y float64) {
- if y == -1 {
- y = f.y
- }
- if f.isCurrentUTF8 {
- txtStr = utf8toutf16(txtStr)
- }
- f.outlines = append(f.outlines, outlineType{text: txtStr, level: level, y: y, p: f.PageNo(), prev: -1, last: -1, next: -1, first: -1})
- }
- // Text prints a character string. The origin (x, y) is on the left of the
- // first character at the baseline. This method permits a string to be placed
- // precisely on the page, but it is usually easier to use Cell(), MultiCell()
- // or Write() which are the standard methods to print text.
- func (f *Fpdf) Text(x, y float64, txtStr string) {
- var txt2 string
- if f.isCurrentUTF8 {
- if f.isRTL {
- txtStr = reverseText(txtStr)
- x -= f.GetStringWidth(txtStr)
- }
- txt2 = f.escape(utf8toutf16(txtStr, false))
- for _, uni := range []rune(txtStr) {
- f.currentFont.usedRunes[int(uni)] = int(uni)
- }
- } else {
- txt2 = f.escape(txtStr)
- }
- s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, txt2)
- if f.underline && txtStr != "" {
- s += " " + f.dounderline(x, y, txtStr)
- }
- if f.strikeout && txtStr != "" {
- s += " " + f.dostrikeout(x, y, txtStr)
- }
- if f.colorFlag {
- s = sprintf("q %s %s Q", f.color.text.str, s)
- }
- f.out(s)
- }
- // SetWordSpacing sets spacing between words of following text. See the
- // WriteAligned() example for a demonstration of its use.
- func (f *Fpdf) SetWordSpacing(space float64) {
- f.out(sprintf("%.5f Tw", space*f.k))
- }
- // SetTextRenderingMode sets the rendering mode of following text.
- // The mode can be as follows:
- // 0: Fill text
- // 1: Stroke text
- // 2: Fill, then stroke text
- // 3: Neither fill nor stroke text (invisible)
- // 4: Fill text and add to path for clipping
- // 5: Stroke text and add to path for clipping
- // 6: Fills then stroke text and add to path for clipping
- // 7: Add text to path for clipping
- // This method is demonstrated in the SetTextRenderingMode example.
- func (f *Fpdf) SetTextRenderingMode(mode int) {
- if mode >= 0 && mode <= 7 {
- f.out(sprintf("%d Tr", mode))
- }
- }
- // SetAcceptPageBreakFunc allows the application to control where page breaks
- // occur.
- //
- // fnc is an application function (typically a closure) that is called by the
- // library whenever a page break condition is met. The break is issued if true
- // is returned. The default implementation returns a value according to the
- // mode selected by SetAutoPageBreak. The function provided should not be
- // called by the application.
- //
- // See the example for SetLeftMargin() to see how this function can be used to
- // manage multiple columns.
- func (f *Fpdf) SetAcceptPageBreakFunc(fnc func() bool) {
- f.acceptPageBreak = fnc
- }
- // CellFormat prints a rectangular cell with optional borders, background color
- // and character string. The upper-left corner of the cell corresponds to the
- // current position. The text can be aligned or centered. After the call, the
- // current position moves to the right or to the next line. It is possible to
- // put a link on the text.
- //
- // An error will be returned if a call to SetFont() has not already taken
- // place before this method is called.
- //
- // If automatic page breaking is enabled and the cell goes beyond the limit, a
- // page break is done before outputting.
- //
- // w and h specify the width and height of the cell. If w is 0, the cell
- // extends up to the right margin. Specifying 0 for h will result in no output,
- // but the current position will be advanced by w.
- //
- // txtStr specifies the text to display.
- //
- // borderStr specifies how the cell border will be drawn. An empty string
- // indicates no border, "1" indicates a full border, and one or more of "L",
- // "T", "R" and "B" indicate the left, top, right and bottom sides of the
- // border.
- //
- // ln indicates where the current position should go after the call. Possible
- // values are 0 (to the right), 1 (to the beginning of the next line), and 2
- // (below). Putting 1 is equivalent to putting 0 and calling Ln() just after.
- //
- // alignStr specifies how the text is to be positioned within the cell.
- // Horizontal alignment is controlled by including "L", "C" or "R" (left,
- // center, right) in alignStr. Vertical alignment is controlled by including
- // "T", "M", "B" or "A" (top, middle, bottom, baseline) in alignStr. The default
- // alignment is left middle.
- //
- // fill is true to paint the cell background or false to leave it transparent.
- //
- // link is the identifier returned by AddLink() or 0 for no internal link.
- //
- // linkStr is a target URL or empty for no external link. A non--zero value for
- // link takes precedence over linkStr.
- func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
- alignStr string, fill bool, link int, linkStr string) {
- // dbg("CellFormat. h = %.2f, borderStr = %s", h, borderStr)
- if f.err != nil {
- return
- }
- if f.currentFont.Name == "" {
- f.err = fmt.Errorf("font has not been set; unable to render text")
- return
- }
- borderStr = strings.ToUpper(borderStr)
- k := f.k
- if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
- // Automatic page break
- x := f.x
- ws := f.ws
- // dbg("auto page break, x %.2f, ws %.2f", x, ws)
- if ws > 0 {
- f.ws = 0
- f.out("0 Tw")
- }
- f.AddPageFormat(f.curOrientation, f.curPageSize)
- if f.err != nil {
- return
- }
- f.x = x
- if ws > 0 {
- f.ws = ws
- f.outf("%.3f Tw", ws*k)
- }
- }
- if w == 0 {
- w = f.w - f.rMargin - f.x
- }
- var s fmtBuffer
- if fill || borderStr == "1" {
- var op string
- if fill {
- if borderStr == "1" {
- op = "B"
- // dbg("border is '1', fill")
- } else {
- op = "f"
- // dbg("border is empty, fill")
- }
- } else {
- // dbg("border is '1', no fill")
- op = "S"
- }
- /// dbg("(CellFormat) f.x %.2f f.k %.2f", f.x, f.k)
- s.printf("%.2f %.2f %.2f %.2f re %s ", f.x*k, (f.h-f.y)*k, w*k, -h*k, op)
- }
- if len(borderStr) > 0 && borderStr != "1" {
- // fmt.Printf("border is '%s', no fill\n", borderStr)
- x := f.x
- y := f.y
- left := x * k
- top := (f.h - y) * k
- right := (x + w) * k
- bottom := (f.h - (y + h)) * k
- if strings.Contains(borderStr, "L") {
- s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, left, bottom)
- }
- if strings.Contains(borderStr, "T") {
- s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, right, top)
- }
- if strings.Contains(borderStr, "R") {
- s.printf("%.2f %.2f m %.2f %.2f l S ", right, top, right, bottom)
- }
- if strings.Contains(borderStr, "B") {
- s.printf("%.2f %.2f m %.2f %.2f l S ", left, bottom, right, bottom)
- }
- }
- if len(txtStr) > 0 {
- var dx, dy float64
- // Horizontal alignment
- switch {
- case strings.Contains(alignStr, "R"):
- dx = w - f.cMargin - f.GetStringWidth(txtStr)
- case strings.Contains(alignStr, "C"):
- dx = (w - f.GetStringWidth(txtStr)) / 2
- default:
- dx = f.cMargin
- }
- // Vertical alignment
- switch {
- case strings.Contains(alignStr, "T"):
- dy = (f.fontSize - h) / 2.0
- case strings.Contains(alignStr, "B"):
- dy = (h - f.fontSize) / 2.0
- case strings.Contains(alignStr, "A"):
- var descent float64
- d := f.currentFont.Desc
- if d.Descent == 0 {
- // not defined (standard font?), use average of 19%
- descent = -0.19 * f.fontSize
- } else {
- descent = float64(d.Descent) * f.fontSize / float64(d.Ascent-d.Descent)
- }
- dy = (h-f.fontSize)/2.0 - descent
- default:
- dy = 0
- }
- if f.colorFlag {
- s.printf("q %s ", f.color.text.str)
- }
- //If multibyte, Tw has no effect - do word spacing using an adjustment before each space
- if (f.ws != 0 || alignStr == "J") && f.isCurrentUTF8 { // && f.ws != 0
- if f.isRTL {
- txtStr = reverseText(txtStr)
- }
- wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
- for _, uni := range []rune(txtStr) {
- f.currentFont.usedRunes[int(uni)] = int(uni)
- }
- space := f.escape(utf8toutf16(" ", false))
- strSize := f.GetStringSymbolWidth(txtStr)
- s.printf("BT 0 Tw %.2f %.2f Td [", (f.x+dx)*k, (f.h-(f.y+.5*h+.3*f.fontSize))*k)
- t := strings.Split(txtStr, " ")
- shift := float64((wmax - strSize)) / float64(len(t)-1)
- numt := len(t)
- for i := 0; i < numt; i++ {
- tx := t[i]
- tx = "(" + f.escape(utf8toutf16(tx, false)) + ")"
- s.printf("%s ", tx)
- if (i + 1) < numt {
- s.printf("%.3f(%s) ", -shift, space)
- }
- }
- s.printf("] TJ ET")
- } else {
- var txt2 string
- if f.isCurrentUTF8 {
- if f.isRTL {
- txtStr = reverseText(txtStr)
- }
- txt2 = f.escape(utf8toutf16(txtStr, false))
- for _, uni := range []rune(txtStr) {
- f.currentFont.usedRunes[int(uni)] = int(uni)
- }
- } else {
- txt2 = strings.Replace(txtStr, "\\", "\\\\", -1)
- txt2 = strings.Replace(txt2, "(", "\\(", -1)
- txt2 = strings.Replace(txt2, ")", "\\)", -1)
- }
- bt := (f.x + dx) * k
- td := (f.h - (f.y + dy + .5*h + .3*f.fontSize)) * k
- s.printf("BT %.2f %.2f Td (%s)Tj ET", bt, td, txt2)
- //BT %.2F %.2F Td (%s) Tj ET',(f.x+dx)*k,(f.h-(f.y+.5*h+.3*f.FontSize))*k,txt2);
- }
- if f.underline {
- s.printf(" %s", f.dounderline(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
- }
- if f.strikeout {
- s.printf(" %s", f.dostrikeout(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
- }
- if f.colorFlag {
- s.printf(" Q")
- }
- if link > 0 || len(linkStr) > 0 {
- f.newLink(f.x+dx, f.y+dy+.5*h-.5*f.fontSize, f.GetStringWidth(txtStr), f.fontSize, link, linkStr)
- }
- }
- str := s.String()
- if len(str) > 0 {
- f.out(str)
- }
- f.lasth = h
- if ln > 0 {
- // Go to next line
- f.y += h
- if ln == 1 {
- f.x = f.lMargin
- }
- } else {
- f.x += w
- }
- return
- }
- // Revert string to use in RTL languages
- func reverseText(text string) string {
- oldText := []rune(text)
- newText := make([]rune, len(oldText))
- length := len(oldText) - 1
- for i, r := range oldText {
- newText[length-i] = r
- }
- return string(newText)
- }
- // Cell is a simpler version of CellFormat with no fill, border, links or
- // special alignment. The Cell_strikeout() example demonstrates this method.
- func (f *Fpdf) Cell(w, h float64, txtStr string) {
- f.CellFormat(w, h, txtStr, "", 0, "L", false, 0, "")
- }
- // Cellf is a simpler printf-style version of CellFormat with no fill, border,
- // links or special alignment. See documentation for the fmt package for
- // details on fmtStr and args.
- func (f *Fpdf) Cellf(w, h float64, fmtStr string, args ...interface{}) {
- f.CellFormat(w, h, sprintf(fmtStr, args...), "", 0, "L", false, 0, "")
- }
- // SplitLines splits text into several lines using the current font. Each line
- // has its length limited to a maximum width given by w. This function can be
- // used to determine the total height of wrapped text for vertical placement
- // purposes.
- //
- // This method is useful for codepage-based fonts only. For UTF-8 encoded text,
- // use SplitText().
- //
- // You can use MultiCell if you want to print a text on several lines in a
- // simple way.
- func (f *Fpdf) SplitLines(txt []byte, w float64) [][]byte {
- // Function contributed by Bruno Michel
- lines := [][]byte{}
- cw := f.currentFont.Cw
- wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
- s := bytes.Replace(txt, []byte("\r"), []byte{}, -1)
- nb := len(s)
- for nb > 0 && s[nb-1] == '\n' {
- nb--
- }
- s = s[0:nb]
- sep := -1
- i := 0
- j := 0
- l := 0
- for i < nb {
- c := s[i]
- l += cw[c]
- if c == ' ' || c == '\t' || c == '\n' {
- sep = i
- }
- if c == '\n' || l > wmax {
- if sep == -1 {
- if i == j {
- i++
- }
- sep = i
- } else {
- i = sep + 1
- }
- lines = append(lines, s[j:sep])
- sep = -1
- j = i
- l = 0
- } else {
- i++
- }
- }
- if i != j {
- lines = append(lines, s[j:i])
- }
- return lines
- }
- // MultiCell supports printing text with line breaks. They can be automatic (as
- // soon as the text reaches the right border of the cell) or explicit (via the
- // \n character). As many cells as necessary are output, one below the other.
- //
- // Text can be aligned, centered or justified. The cell block can be framed and
- // the background painted. See CellFormat() for more details.
- //
- // The current position after calling MultiCell() is the beginning of the next
- // line, equivalent to calling CellFormat with ln equal to 1.
- //
- // w is the width of the cells. A value of zero indicates cells that reach to
- // the right margin.
- //
- // h indicates the line height of each cell in the unit of measure specified in New().
- //
- // Note: this method has a known bug that treats UTF-8 fonts differently than
- // non-UTF-8 fonts. With UTF-8 fonts, all trailing newlines in txtStr are
- // removed. With a non-UTF-8 font, if txtStr has one or more trailing newlines,
- // only the last is removed. In the next major module version, the UTF-8 logic
- // will be changed to match the non-UTF-8 logic. To prepare for that change,
- // applications that use UTF-8 fonts and depend on having all trailing newlines
- // removed should call strings.TrimRight(txtStr, "\r\n") before calling this
- // method.
- func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill bool) {
- if f.err != nil {
- return
- }
- // dbg("MultiCell")
- if alignStr == "" {
- alignStr = "J"
- }
- cw := f.currentFont.Cw
- if w == 0 {
- w = f.w - f.rMargin - f.x
- }
- wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
- s := strings.Replace(txtStr, "\r", "", -1)
- srune := []rune(s)
- // remove extra line breaks
- var nb int
- if f.isCurrentUTF8 {
- nb = len(srune)
- for nb > 0 && srune[nb-1] == '\n' {
- nb--
- }
- srune = srune[0:nb]
- } else {
- nb = len(s)
- bytes2 := []byte(s)
- // for nb > 0 && bytes2[nb-1] == '\n' {
- // Prior to August 2019, if s ended with a newline, this code stripped it.
- // After that date, to be compatible with the UTF-8 code above, *all*
- // trailing newlines were removed. Because this regression caused at least
- // one application to break (see issue #333), the original behavior has been
- // reinstated with a caveat included in the documentation.
- if nb > 0 && bytes2[nb-1] == '\n' {
- nb--
- }
- s = s[0:nb]
- }
- // dbg("[%s]\n", s)
- var b, b2 string
- b = "0"
- if len(borderStr) > 0 {
- if borderStr == "1" {
- borderStr = "LTRB"
- b = "LRT"
- b2 = "LR"
- } else {
- b2 = ""
- if strings.Contains(borderStr, "L") {
- b2 += "L"
- }
- if strings.Contains(borderStr, "R") {
- b2 += "R"
- }
- if strings.Contains(borderStr, "T") {
- b = b2 + "T"
- } else {
- b = b2
- }
- }
- }
- //sep := -1
- i := 0
- j := 0
- l := 0
- //ls := 0
- //ns := 0
- nl := 1
- lines := []string{}
- for i < nb {
- // 这里是为了防止内存溢出,曾出现过前面是汉字后面是非汉字,lines为["汉字汉字汉字","","","".....]无限个""导致内存溢出
- if len(lines) > len(srune) {
- break
- }
- // Get next character
- var c rune
- if f.isCurrentUTF8 {
- c = srune[i]
- } else {
- c = rune(s[i])
- }
- if c == '\n' {
- // Explicit line break
- if f.ws > 0 {
- f.ws = 0
- f.out("0 Tw")
- }
- if f.isCurrentUTF8 {
- newAlignStr := alignStr
- if newAlignStr == "J" {
- if f.isRTL {
- newAlignStr = "R"
- } else {
- newAlignStr = "L"
- }
- }
- f.CellFormat(w, h, string(srune[j:i]), b, 2, newAlignStr, fill, 0, "")
- } else {
- f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
- }
- i++
- lines = append(lines, s[j:i])
- //sep = -1
- j = i
- l = 0
- //ns = 0
- nl++
- if len(borderStr) > 0 && nl == 2 {
- b = b2
- }
- continue
- }
- //if c == ' ' || isChinese(c) {
- // sep = i
- // ls = l
- // ns++
- //}
- if int(c) >= len(cw) {
- f.err = fmt.Errorf("character outside the supported range: %s", string(c))
- return
- }
- if cw[int(c)] == 0 { //Marker width 0 used for missing symbols
- l += f.currentFont.Desc.MissingWidth
- } else if cw[int(c)] != 65535 { //Marker width 65535 used for zero width symbols
- l += cw[int(c)]
- }
- if l > wmax {
- ///////////////
- // 如果第一个字就超宽了,别循环了,会死循环
- if i == 0 {
- break
- }
- // Automatic line break
- //if sep == -1 {
- // if i == j {
- // i++
- // }
- // if f.ws > 0 {
- // f.ws = 0
- // f.out("0 Tw")
- // }
- // if f.isCurrentUTF8 {
- // f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
- // } else {
- // f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
- // }
- //} else {
- // if alignStr == "J" {
- // if ns > 1 {
- // f.ws = float64((wmax-ls)/1000) * f.fontSize / float64(ns-1)
- // } else {
- // f.ws = 0
- // }
- // f.outf("%.3f Tw", f.ws*f.k)
- // }
- // if f.isCurrentUTF8 {
- // f.CellFormat(w, h, string(srune[j:sep]), b, 2, alignStr, fill, 0, "")
- // } else {
- // f.CellFormat(w, h, s[j:sep], b, 2, alignStr, fill, 0, "")
- // }
- // //////////
- // // i = sep + 1
- // /////////
- // i = sep
- //}
- lines = append(lines, s[j:i])
- if f.ws > 0 {
- f.ws = 0
- f.out("0 Tw")
- }
- if f.isCurrentUTF8 {
- f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
- } else {
- f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
- }
- //sep = -1
- j = i
- l = 0
- //ns = 0
- nl++
- if len(borderStr) > 0 && nl == 2 {
- b = b2
- }
- } else {
- i++
- }
- }
- // Last chunk
- if f.ws > 0 {
- f.ws = 0
- f.out("0 Tw")
- }
- if len(borderStr) > 0 && strings.Contains(borderStr, "B") {
- b += "B"
- }
- if f.isCurrentUTF8 {
- if alignStr == "J" {
- if f.isRTL {
- alignStr = "R"
- } else {
- alignStr = ""
- }
- }
- f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
- } else {
- f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
- }
- f.x = f.lMargin
- }
- // write outputs text in flowing mode
- func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) {
- // dbg("Write")
- cw := f.currentFont.Cw
- w := f.w - f.rMargin - f.x
- wmax := (w - 2*f.cMargin) * 1000 / f.fontSize
- s := strings.Replace(txtStr, "\r", "", -1)
- var nb int
- if f.isCurrentUTF8 {
- nb = len([]rune(s))
- if nb == 1 && s == " " {
- f.x += f.GetStringWidth(s)
- return
- }
- } else {
- nb = len(s)
- }
- sep := -1
- i := 0
- j := 0
- l := 0.0
- nl := 1
- for i < nb {
- // Get next character
- var c rune
- if f.isCurrentUTF8 {
- c = []rune(s)[i]
- } else {
- c = rune(byte(s[i]))
- }
- if c == '\n' {
- // Explicit line break
- if f.isCurrentUTF8 {
- f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr)
- } else {
- f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr)
- }
- i++
- sep = -1
- j = i
- l = 0.0
- if nl == 1 {
- f.x = f.lMargin
- w = f.w - f.rMargin - f.x
- wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
- }
- nl++
- continue
- }
- if c == ' ' {
- sep = i
- }
- l += float64(cw[int(c)])
- if l > wmax {
- // Automatic line break
- if sep == -1 {
- if f.x > f.lMargin {
- // Move to next line
- f.x = f.lMargin
- f.y += h
- w = f.w - f.rMargin - f.x
- wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
- i++
- nl++
- continue
- }
- if i == j {
- i++
- }
- if f.isCurrentUTF8 {
- f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr)
- } else {
- f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr)
- }
- } else {
- if f.isCurrentUTF8 {
- f.CellFormat(w, h, string([]rune(s)[j:sep]), "", 2, "", false, link, linkStr)
- } else {
- f.CellFormat(w, h, s[j:sep], "", 2, "", false, link, linkStr)
- }
- i = sep + 1
- }
- sep = -1
- j = i
- l = 0.0
- if nl == 1 {
- f.x = f.lMargin
- w = f.w - f.rMargin - f.x
- wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
- }
- nl++
- } else {
- i++
- }
- }
- // Last chunk
- if i != j {
- if f.isCurrentUTF8 {
- f.CellFormat(l/1000*f.fontSize, h, string([]rune(s)[j:]), "", 0, "", false, link, linkStr)
- } else {
- f.CellFormat(l/1000*f.fontSize, h, s[j:], "", 0, "", false, link, linkStr)
- }
- }
- }
- // Write prints text from the current position. When the right margin is
- // reached (or the \n character is met) a line break occurs and text continues
- // from the left margin. Upon method exit, the current position is left just at
- // the end of the text.
- //
- // It is possible to put a link on the text.
- //
- // h indicates the line height in the unit of measure specified in New().
- func (f *Fpdf) Write(h float64, txtStr string) {
- f.write(h, txtStr, 0, "")
- }
- // Writef is like Write but uses printf-style formatting. See the documentation
- // for package fmt for more details on fmtStr and args.
- func (f *Fpdf) Writef(h float64, fmtStr string, args ...interface{}) {
- f.write(h, sprintf(fmtStr, args...), 0, "")
- }
- // WriteLinkString writes text that when clicked launches an external URL. See
- // Write() for argument details.
- func (f *Fpdf) WriteLinkString(h float64, displayStr, targetStr string) {
- f.write(h, displayStr, 0, targetStr)
- }
- // WriteLinkID writes text that when clicked jumps to another location in the
- // PDF. linkID is an identifier returned by AddLink(). See Write() for argument
- // details.
- func (f *Fpdf) WriteLinkID(h float64, displayStr string, linkID int) {
- f.write(h, displayStr, linkID, "")
- }
- // WriteAligned is an implementation of Write that makes it possible to align
- // text.
- //
- // width indicates the width of the box the text will be drawn in. This is in
- // the unit of measure specified in New(). If it is set to 0, the bounding box
- // of the page will be taken (pageWidth - leftMargin - rightMargin).
- //
- // lineHeight indicates the line height in the unit of measure specified in
- // New().
- //
- // alignStr sees to horizontal alignment of the given textStr. The options are
- // "L", "C" and "R" (Left, Center, Right). The default is "L".
- func (f *Fpdf) WriteAligned(width, lineHeight float64, textStr, alignStr string) {
- lMargin, _, rMargin, _ := f.GetMargins()
- pageWidth, _ := f.GetPageSize()
- if width == 0 {
- width = pageWidth - (lMargin + rMargin)
- }
- var lines []string
- if f.isCurrentUTF8 {
- lines = f.SplitText(textStr, width)
- } else {
- for _, line := range f.SplitLines([]byte(textStr), width) {
- lines = append(lines, string(line))
- }
- }
- for _, lineBt := range lines {
- lineStr := string(lineBt)
- lineWidth := f.GetStringWidth(lineStr)
- switch alignStr {
- case "C":
- f.SetLeftMargin(lMargin + ((width - lineWidth) / 2))
- f.Write(lineHeight, lineStr)
- f.SetLeftMargin(lMargin)
- case "R":
- f.SetLeftMargin(lMargin + (width - lineWidth) - 2.01*f.cMargin)
- f.Write(lineHeight, lineStr)
- f.SetLeftMargin(lMargin)
- default:
- f.SetRightMargin(pageWidth - lMargin - width)
- f.Write(lineHeight, lineStr)
- f.SetRightMargin(rMargin)
- }
- }
- }
- // Ln performs a line break. The current abscissa goes back to the left margin
- // and the ordinate increases by the amount passed in parameter. A negative
- // value of h indicates the height of the last printed cell.
- //
- // This method is demonstrated in the example for MultiCell.
- func (f *Fpdf) Ln(h float64) {
- f.x = f.lMargin
- if h < 0 {
- f.y += f.lasth
- } else {
- f.y += h
- }
- }
- // ImageTypeFromMime returns the image type used in various image-related
- // functions (for example, Image()) that is associated with the specified MIME
- // type. For example, "jpg" is returned if mimeStr is "image/jpeg". An error is
- // set if the specified MIME type is not supported.
- func (f *Fpdf) ImageTypeFromMime(mimeStr string) (tp string) {
- switch mimeStr {
- case "image/png":
- tp = "png"
- case "image/jpg":
- tp = "jpg"
- case "image/jpeg":
- tp = "jpg"
- case "image/gif":
- tp = "gif"
- default:
- f.SetErrorf("unsupported image type: %s", mimeStr)
- }
- return
- }
- func (f *Fpdf) imageOut(info *ImageInfoType, x, y, w, h float64, allowNegativeX, flow bool, link int, linkStr string) {
- // Automatic width and height calculation if needed
- if w == 0 && h == 0 {
- // Put image at 96 dpi
- w = -96
- h = -96
- }
- if w == -1 {
- // Set image width to whatever value for dpi we read
- // from the image or that was set manually
- w = -info.dpi
- }
- if h == -1 {
- // Set image height to whatever value for dpi we read
- // from the image or that was set manually
- h = -info.dpi
- }
- if w < 0 {
- w = -info.w * 72.0 / w / f.k
- }
- if h < 0 {
- h = -info.h * 72.0 / h / f.k
- }
- if w == 0 {
- w = h * info.w / info.h
- }
- if h == 0 {
- h = w * info.h / info.w
- }
- // Flowing mode
- if flow {
- if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
- // Automatic page break
- x2 := f.x
- f.AddPageFormat(f.curOrientation, f.curPageSize)
- if f.err != nil {
- return
- }
- f.x = x2
- }
- y = f.y
- f.y += h
- }
- if !allowNegativeX {
- if x < 0 {
- x = f.x
- }
- }
- // dbg("h %.2f", h)
- // q 85.04 0 0 NaN 28.35 NaN cm /I2 Do Q
- f.outf("q %.5f 0 0 %.5f %.5f %.5f cm /I%s Do Q", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k, info.i)
- if link > 0 || len(linkStr) > 0 {
- f.newLink(x, y, w, h, link, linkStr)
- }
- }
- // Image puts a JPEG, PNG or GIF image in the current page.
- //
- // Deprecated in favor of ImageOptions -- see that function for
- // details on the behavior of arguments
- func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string) {
- options := ImageOptions{
- ReadDpi: false,
- ImageType: tp,
- }
- f.ImageOptions(imageNameStr, x, y, w, h, flow, options, link, linkStr)
- }
- // ImageOptions puts a JPEG, PNG or GIF image in the current page. The size it
- // will take on the page can be specified in different ways. If both w and h
- // are 0, the image is rendered at 96 dpi. If either w or h is zero, it will be
- // calculated from the other dimension so that the aspect ratio is maintained.
- // If w and/or h are -1, the dpi for that dimension will be read from the
- // ImageInfoType object. PNG files can contain dpi information, and if present,
- // this information will be populated in the ImageInfoType object and used in
- // Width, Height, and Extent calculations. Otherwise, the SetDpi function can
- // be used to change the dpi from the default of 72.
- //
- // If w and h are any other negative value, their absolute values
- // indicate their dpi extents.
- //
- // Supported JPEG formats are 24 bit, 32 bit and gray scale. Supported PNG
- // formats are 24 bit, indexed color, and 8 bit indexed gray scale. If a GIF
- // image is animated, only the first frame is rendered. Transparency is
- // supported. It is possible to put a link on the image.
- //
- // imageNameStr may be the name of an image as registered with a call to either
- // RegisterImageReader() or RegisterImage(). In the first case, the image is
- // loaded using an io.Reader. This is generally useful when the image is
- // obtained from some other means than as a disk-based file. In the second
- // case, the image is loaded as a file. Alternatively, imageNameStr may
- // directly specify a sufficiently qualified filename.
- //
- // However the image is loaded, if it is used more than once only one copy is
- // embedded in the file.
- //
- // If x is negative, the current abscissa is used.
- //
- // If flow is true, the current y value is advanced after placing the image and
- // a page break may be made if necessary.
- //
- // If link refers to an internal page anchor (that is, it is non-zero; see
- // AddLink()), the image will be a clickable internal link. Otherwise, if
- // linkStr specifies a URL, the image will be a clickable external link.
- func (f *Fpdf) ImageOptions(imageNameStr string, x, y, w, h float64, flow bool, options ImageOptions, link int, linkStr string) {
- if f.err != nil {
- return
- }
- info := f.RegisterImageOptions(imageNameStr, options)
- if f.err != nil {
- return
- }
- f.imageOut(info, x, y, w, h, options.AllowNegativePosition, flow, link, linkStr)
- return
- }
- // RegisterImageReader registers an image, reading it from Reader r, adding it
- // to the PDF file but not adding it to the page.
- //
- // This function is now deprecated in favor of RegisterImageOptionsReader
- func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *ImageInfoType) {
- options := ImageOptions{
- ReadDpi: false,
- ImageType: tp,
- }
- return f.RegisterImageOptionsReader(imgName, options, r)
- }
- // ImageOptions provides a place to hang any options we want to use while
- // parsing an image.
- //
- // ImageType's possible values are (case insensitive):
- // "JPG", "JPEG", "PNG" and "GIF". If empty, the type is inferred from
- // the file extension.
- //
- // ReadDpi defines whether to attempt to automatically read the image
- // dpi information from the image file. Normally, this should be set
- // to true (understanding that not all images will have this info
- // available). However, for backwards compatibility with previous
- // versions of the API, it defaults to false.
- //
- // AllowNegativePosition can be set to true in order to prevent the default
- // coercion of negative x values to the current x position.
- type ImageOptions struct {
- ImageType string
- ReadDpi bool
- AllowNegativePosition bool
- }
- // RegisterImageOptionsReader registers an image, reading it from Reader r, adding it
- // to the PDF file but not adding it to the page. Use Image() with the same
- // name to add the image to the page. Note that tp should be specified in this
- // case.
- //
- // See Image() for restrictions on the image and the options parameters.
- func (f *Fpdf) RegisterImageOptionsReader(imgName string, options ImageOptions, r io.Reader) (info *ImageInfoType) {
- // Thanks, Ivan Daniluk, for generalizing this code to use the Reader interface.
- if f.err != nil {
- return
- }
- info, ok := f.images[imgName]
- if ok {
- return
- }
- // First use of this image, get info
- if options.ImageType == "" {
- f.err = fmt.Errorf("image type should be specified if reading from custom reader")
- return
- }
- options.ImageType = strings.ToLower(options.ImageType)
- if options.ImageType == "jpeg" {
- options.ImageType = "jpg"
- }
- switch options.ImageType {
- case "jpg":
- info = f.parsejpg(r)
- case "png":
- info = f.parsepng(r, options.ReadDpi)
- case "gif":
- info = f.parsegif(r)
- default:
- f.err = fmt.Errorf("unsupported image type: %s", options.ImageType)
- }
- if f.err != nil {
- return
- }
- if info.i, f.err = generateImageID(info); f.err != nil {
- return
- }
- f.images[imgName] = info
- return
- }
- // RegisterImage registers an image, adding it to the PDF file but not adding
- // it to the page. Use Image() with the same filename to add the image to the
- // page. Note that Image() calls this function, so this function is only
- // necessary if you need information about the image before placing it.
- //
- // This function is now deprecated in favor of RegisterImageOptions.
- // See Image() for restrictions on the image and the "tp" parameters.
- func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) {
- options := ImageOptions{
- ReadDpi: false,
- ImageType: tp,
- }
- return f.RegisterImageOptions(fileStr, options)
- }
- // RegisterImageOptions registers an image, adding it to the PDF file but not
- // adding it to the page. Use Image() with the same filename to add the image
- // to the page. Note that Image() calls this function, so this function is only
- // necessary if you need information about the image before placing it. See
- // Image() for restrictions on the image and the "tp" parameters.
- func (f *Fpdf) RegisterImageOptions(fileStr string, options ImageOptions) (info *ImageInfoType) {
- info, ok := f.images[fileStr]
- if ok {
- return
- }
- file, err := os.Open(fileStr)
- if err != nil {
- f.err = err
- return
- }
- defer file.Close()
- // First use of this image, get info
- if options.ImageType == "" {
- pos := strings.LastIndex(fileStr, ".")
- if pos < 0 {
- f.err = fmt.Errorf("image file has no extension and no type was specified: %s", fileStr)
- return
- }
- options.ImageType = fileStr[pos+1:]
- }
- return f.RegisterImageOptionsReader(fileStr, options, file)
- }
- // GetImageInfo returns information about the registered image specified by
- // imageStr. If the image has not been registered, nil is returned. The
- // internal error is not modified by this method.
- func (f *Fpdf) GetImageInfo(imageStr string) (info *ImageInfoType) {
- return f.images[imageStr]
- }
- // ImportObjects imports objects from gofpdi into current document
- func (f *Fpdf) ImportObjects(objs map[string][]byte) {
- for k, v := range objs {
- f.importedObjs[k] = v
- }
- }
- // ImportObjPos imports object hash positions from gofpdi
- func (f *Fpdf) ImportObjPos(objPos map[string]map[int]string) {
- for k, v := range objPos {
- f.importedObjPos[k] = v
- }
- }
- // putImportedTemplates writes the imported template objects to the PDF
- func (f *Fpdf) putImportedTemplates() {
- nOffset := f.n + 1
- // keep track of list of sha1 hashes (to be replaced with integers)
- objsIDHash := make([]string, len(f.importedObjs))
- // actual object data with new id
- objsIDData := make([][]byte, len(f.importedObjs))
- // Populate hash slice and data slice
- i := 0
- for k, v := range f.importedObjs {
- objsIDHash[i] = k
- objsIDData[i] = v
- i++
- }
- // Populate a lookup table to get an object id from a hash
- hashToObjID := make(map[string]int, len(f.importedObjs))
- for i = 0; i < len(objsIDHash); i++ {
- hashToObjID[objsIDHash[i]] = i + nOffset
- }
- // Now, replace hashes inside data with %040d object id
- for i = 0; i < len(objsIDData); i++ {
- // get hash
- hash := objsIDHash[i]
- for pos, h := range f.importedObjPos[hash] {
- // Convert object id into a 40 character string padded with spaces
- objIDPadded := fmt.Sprintf("%40s", fmt.Sprintf("%d", hashToObjID[h]))
- // Convert objIDPadded into []byte
- objIDBytes := []byte(objIDPadded)
- // Replace sha1 hash with object id padded
- for j := pos; j < pos+40; j++ {
- objsIDData[i][j] = objIDBytes[j-pos]
- }
- }
- // Save objsIDHash so that procset dictionary has the correct object ids
- f.importedTplIDs[hash] = i + nOffset
- }
- // Now, put objects
- for i = 0; i < len(objsIDData); i++ {
- f.newobj()
- f.out(string(objsIDData[i]))
- }
- }
- // UseImportedTemplate uses imported template from gofpdi. It draws imported
- // PDF page onto page.
- func (f *Fpdf) UseImportedTemplate(tplName string, scaleX float64, scaleY float64, tX float64, tY float64) {
- f.outf("q 0 J 1 w 0 j 0 G 0 g q %.4F 0 0 %.4F %.4F %.4F cm %s Do Q Q\n", scaleX*f.k, scaleY*f.k, tX*f.k, (tY+f.h)*f.k, tplName)
- }
- // ImportTemplates imports gofpdi template names into importedTplObjs for
- // inclusion in the procset dictionary
- func (f *Fpdf) ImportTemplates(tpls map[string]string) {
- for tplName, tplID := range tpls {
- f.importedTplObjs[tplName] = tplID
- }
- }
- // GetConversionRatio returns the conversion ratio based on the unit given when
- // creating the PDF.
- func (f *Fpdf) GetConversionRatio() float64 {
- return f.k
- }
- // GetXY returns the abscissa and ordinate of the current position.
- //
- // Note: the value returned for the abscissa will be affected by the current
- // cell margin. To account for this, you may need to either add the value
- // returned by GetCellMargin() to it or call SetCellMargin(0) to remove the
- // cell margin.
- func (f *Fpdf) GetXY() (float64, float64) {
- return f.x, f.y
- }
- // GetX returns the abscissa of the current position.
- //
- // Note: the value returned will be affected by the current cell margin. To
- // account for this, you may need to either add the value returned by
- // GetCellMargin() to it or call SetCellMargin(0) to remove the cell margin.
- func (f *Fpdf) GetX() float64 {
- return f.x
- }
- // SetX defines the abscissa of the current position. If the passed value is
- // negative, it is relative to the right of the page.
- func (f *Fpdf) SetX(x float64) {
- if x >= 0 {
- f.x = x
- } else {
- f.x = f.w + x
- }
- }
- // GetY returns the ordinate of the current position.
- func (f *Fpdf) GetY() float64 {
- return f.y
- }
- // SetY moves the current abscissa back to the left margin and sets the
- // ordinate. If the passed value is negative, it is relative to the bottom of
- // the page.
- func (f *Fpdf) SetY(y float64) {
- // dbg("SetY x %.2f, lMargin %.2f", f.x, f.lMargin)
- f.x = f.lMargin
- if y >= 0 {
- f.y = y
- } else {
- f.y = f.h + y
- }
- }
- // SetHomeXY is a convenience method that sets the current position to the left
- // and top margins.
- func (f *Fpdf) SetHomeXY() {
- f.SetY(f.tMargin)
- f.SetX(f.lMargin)
- }
- // SetXY defines the abscissa and ordinate of the current position. If the
- // passed values are negative, they are relative respectively to the right and
- // bottom of the page.
- func (f *Fpdf) SetXY(x, y float64) {
- f.SetY(y)
- f.SetX(x)
- }
- // SetProtection applies certain constraints on the finished PDF document.
- //
- // actionFlag is a bitflag that controls various document operations.
- // CnProtectPrint allows the document to be printed. CnProtectModify allows a
- // document to be modified by a PDF editor. CnProtectCopy allows text and
- // images to be copied into the system clipboard. CnProtectAnnotForms allows
- // annotations and forms to be added by a PDF editor. These values can be
- // combined by or-ing them together, for example,
- // CnProtectCopy|CnProtectModify. This flag is advisory; not all PDF readers
- // implement the constraints that this argument attempts to control.
- //
- // userPassStr specifies the password that will need to be provided to view the
- // contents of the PDF. The permissions specified by actionFlag will apply.
- //
- // ownerPassStr specifies the password that will need to be provided to gain
- // full access to the document regardless of the actionFlag value. An empty
- // string for this argument will be replaced with a random value, effectively
- // prohibiting full access to the document.
- func (f *Fpdf) SetProtection(actionFlag byte, userPassStr, ownerPassStr string) {
- if f.err != nil {
- return
- }
- f.protect.setProtection(actionFlag, userPassStr, ownerPassStr)
- }
- // OutputAndClose sends the PDF document to the writer specified by w. This
- // method will close both f and w, even if an error is detected and no document
- // is produced.
- func (f *Fpdf) OutputAndClose(w io.WriteCloser) error {
- f.Output(w)
- w.Close()
- return f.err
- }
- // OutputFileAndClose creates or truncates the file specified by fileStr and
- // writes the PDF document to it. This method will close f and the newly
- // written file, even if an error is detected and no document is produced.
- //
- // Most examples demonstrate the use of this method.
- func (f *Fpdf) OutputFileAndClose(fileStr string) error {
- if f.err == nil {
- pdfFile, err := os.Create(fileStr)
- if err == nil {
- f.Output(pdfFile)
- pdfFile.Close()
- } else {
- f.err = err
- }
- }
- return f.err
- }
- // Output sends the PDF document to the writer specified by w. No output will
- // take place if an error has occurred in the document generation process. w
- // remains open after this function returns. After returning, f is in a closed
- // state and its methods should not be called.
- func (f *Fpdf) Output(w io.Writer) error {
- if f.err != nil {
- return f.err
- }
- // dbg("Output")
- if f.state < 3 {
- f.Close()
- }
- _, err := f.buffer.WriteTo(w)
- if err != nil {
- f.err = err
- }
- return f.err
- }
- func (f *Fpdf) getpagesizestr(sizeStr string) (size SizeType) {
- if f.err != nil {
- return
- }
- sizeStr = strings.ToLower(sizeStr)
- // dbg("Size [%s]", sizeStr)
- var ok bool
- size, ok = f.stdPageSizes[sizeStr]
- if ok {
- // dbg("found %s", sizeStr)
- size.Wd /= f.k
- size.Ht /= f.k
- } else {
- f.err = fmt.Errorf("unknown page size %s", sizeStr)
- }
- return
- }
- // GetPageSizeStr returns the SizeType for the given sizeStr (that is A4, A3, etc..)
- func (f *Fpdf) GetPageSizeStr(sizeStr string) (size SizeType) {
- return f.getpagesizestr(sizeStr)
- }
- func (f *Fpdf) _getpagesize(size SizeType) SizeType {
- if size.Wd > size.Ht {
- size.Wd, size.Ht = size.Ht, size.Wd
- }
- return size
- }
- func (f *Fpdf) beginpage(orientationStr string, size SizeType) {
- if f.err != nil {
- return
- }
- f.page++
- // add the default page boxes, if any exist, to the page
- f.pageBoxes[f.page] = make(map[string]PageBox)
- for box, pb := range f.defPageBoxes {
- f.pageBoxes[f.page][box] = pb
- }
- f.pages = append(f.pages, bytes.NewBufferString(""))
- f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0))
- f.pageAttachments = append(f.pageAttachments, []annotationAttach{})
- f.state = 2
- f.x = f.lMargin
- f.y = f.tMargin
- f.fontFamily = ""
- // Check page size and orientation
- if orientationStr == "" {
- orientationStr = f.defOrientation
- } else {
- orientationStr = strings.ToUpper(orientationStr[0:1])
- }
- if orientationStr != f.curOrientation || size.Wd != f.curPageSize.Wd || size.Ht != f.curPageSize.Ht {
- // New size or orientation
- if orientationStr == "P" {
- f.w = size.Wd
- f.h = size.Ht
- } else {
- f.w = size.Ht
- f.h = size.Wd
- }
- f.wPt = f.w * f.k
- f.hPt = f.h * f.k
- f.pageBreakTrigger = f.h - f.bMargin
- f.curOrientation = orientationStr
- f.curPageSize = size
- }
- if orientationStr != f.defOrientation || size.Wd != f.defPageSize.Wd || size.Ht != f.defPageSize.Ht {
- f.pageSizes[f.page] = SizeType{f.wPt, f.hPt}
- }
- return
- }
- func (f *Fpdf) endpage() {
- f.EndLayer()
- f.state = 1
- }
- // Load a font definition file from the given Reader
- func (f *Fpdf) loadfont(r io.Reader) (def fontDefType) {
- if f.err != nil {
- return
- }
- // dbg("Loading font [%s]", fontStr)
- var buf bytes.Buffer
- _, err := buf.ReadFrom(r)
- if err != nil {
- f.err = err
- return
- }
- err = json.Unmarshal(buf.Bytes(), &def)
- if err != nil {
- f.err = err
- return
- }
- if def.i, err = generateFontID(def); err != nil {
- f.err = err
- }
- // dump(def)
- return
- }
- // Escape special characters in strings
- func (f *Fpdf) escape(s string) string {
- s = strings.Replace(s, "\\", "\\\\", -1)
- s = strings.Replace(s, "(", "\\(", -1)
- s = strings.Replace(s, ")", "\\)", -1)
- s = strings.Replace(s, "\r", "\\r", -1)
- return s
- }
- // textstring formats a text string
- func (f *Fpdf) textstring(s string) string {
- if f.protect.encrypted {
- b := []byte(s)
- f.protect.rc4(uint32(f.n), &b)
- s = string(b)
- }
- return "(" + f.escape(s) + ")"
- }
- func blankCount(str string) (count int) {
- l := len(str)
- for j := 0; j < l; j++ {
- if byte(' ') == str[j] {
- count++
- }
- }
- return
- }
- // SetUnderlineThickness accepts a multiplier for adjusting the text underline
- // thickness, defaulting to 1. See SetUnderlineThickness example.
- func (f *Fpdf) SetUnderlineThickness(thickness float64) {
- f.userUnderlineThickness = thickness
- }
- // Underline text
- func (f *Fpdf) dounderline(x, y float64, txt string) string {
- up := float64(f.currentFont.Up)
- ut := float64(f.currentFont.Ut) * f.userUnderlineThickness
- w := f.GetStringWidth(txt) + f.ws*float64(blankCount(txt))
- return sprintf("%.2f %.2f %.2f %.2f re f", x*f.k,
- (f.h-(y-up/1000*f.fontSize))*f.k, w*f.k, -ut/1000*f.fontSizePt)
- }
- func (f *Fpdf) dostrikeout(x, y float64, txt string) string {
- up := float64(f.currentFont.Up)
- ut := float64(f.currentFont.Ut)
- w := f.GetStringWidth(txt) + f.ws*float64(blankCount(txt))
- return sprintf("%.2f %.2f %.2f %.2f re f", x*f.k,
- (f.h-(y+4*up/1000*f.fontSize))*f.k, w*f.k, -ut/1000*f.fontSizePt)
- }
- func bufEqual(buf []byte, str string) bool {
- return string(buf[0:len(str)]) == str
- }
- func be16(buf []byte) int {
- return 256*int(buf[0]) + int(buf[1])
- }
- func (f *Fpdf) newImageInfo() *ImageInfoType {
- // default dpi to 72 unless told otherwise
- return &ImageInfoType{scale: f.k, dpi: 72}
- }
- // parsejpg extracts info from io.Reader with JPEG data
- // Thank you, Bruno Michel, for providing this code.
- func (f *Fpdf) parsejpg(r io.Reader) (info *ImageInfoType) {
- info = f.newImageInfo()
- var (
- data bytes.Buffer
- err error
- )
- _, err = data.ReadFrom(r)
- if err != nil {
- f.err = err
- return
- }
- info.data = data.Bytes()
- config, err := jpeg.DecodeConfig(bytes.NewReader(info.data))
- if err != nil {
- f.err = err
- return
- }
- info.w = float64(config.Width)
- info.h = float64(config.Height)
- info.f = "DCTDecode"
- info.bpc = 8
- switch config.ColorModel {
- case color.GrayModel:
- info.cs = "DeviceGray"
- case color.YCbCrModel:
- info.cs = "DeviceRGB"
- case color.CMYKModel:
- info.cs = "DeviceCMYK"
- default:
- f.err = fmt.Errorf("image JPEG buffer has unsupported color space (%v)", config.ColorModel)
- return
- }
- return
- }
- // parsepng extracts info from a PNG data
- func (f *Fpdf) parsepng(r io.Reader, readdpi bool) (info *ImageInfoType) {
- buf, err := bufferFromReader(r)
- if err != nil {
- f.err = err
- return
- }
- return f.parsepngstream(buf, readdpi)
- }
- func (f *Fpdf) readBeInt32(r io.Reader) (val int32) {
- err := binary.Read(r, binary.BigEndian, &val)
- if err != nil && err != io.EOF {
- f.err = err
- }
- return
- }
- func (f *Fpdf) readByte(r io.Reader) (val byte) {
- err := binary.Read(r, binary.BigEndian, &val)
- if err != nil {
- f.err = err
- }
- return
- }
- // parsegif extracts info from a GIF data (via PNG conversion)
- func (f *Fpdf) parsegif(r io.Reader) (info *ImageInfoType) {
- data, err := bufferFromReader(r)
- if err != nil {
- f.err = err
- return
- }
- var img image.Image
- img, err = gif.Decode(data)
- if err != nil {
- f.err = err
- return
- }
- pngBuf := new(bytes.Buffer)
- err = png.Encode(pngBuf, img)
- if err != nil {
- f.err = err
- return
- }
- return f.parsepngstream(pngBuf, false)
- }
- // newobj begins a new object
- func (f *Fpdf) newobj() {
- // dbg("newobj")
- f.n++
- for j := len(f.offsets); j <= f.n; j++ {
- f.offsets = append(f.offsets, 0)
- }
- f.offsets[f.n] = f.buffer.Len()
- f.outf("%d 0 obj", f.n)
- }
- func (f *Fpdf) putstream(b []byte) {
- // dbg("putstream")
- if f.protect.encrypted {
- f.protect.rc4(uint32(f.n), &b)
- }
- f.out("stream")
- f.out(string(b))
- f.out("endstream")
- }
- // out; Add a line to the document
- func (f *Fpdf) out(s string) {
- if f.state == 2 {
- f.pages[f.page].WriteString(s)
- f.pages[f.page].WriteString("\n")
- } else {
- f.buffer.WriteString(s)
- f.buffer.WriteString("\n")
- }
- }
- // outbuf adds a buffered line to the document
- func (f *Fpdf) outbuf(r io.Reader) {
- if f.state == 2 {
- f.pages[f.page].ReadFrom(r)
- f.pages[f.page].WriteString("\n")
- } else {
- f.buffer.ReadFrom(r)
- f.buffer.WriteString("\n")
- }
- }
- // RawWriteStr writes a string directly to the PDF generation buffer. This is a
- // low-level function that is not required for normal PDF construction. An
- // understanding of the PDF specification is needed to use this method
- // correctly.
- func (f *Fpdf) RawWriteStr(str string) {
- f.out(str)
- }
- // RawWriteBuf writes the contents of the specified buffer directly to the PDF
- // generation buffer. This is a low-level function that is not required for
- // normal PDF construction. An understanding of the PDF specification is needed
- // to use this method correctly.
- func (f *Fpdf) RawWriteBuf(r io.Reader) {
- f.outbuf(r)
- }
- // outf adds a formatted line to the document
- func (f *Fpdf) outf(fmtStr string, args ...interface{}) {
- f.out(sprintf(fmtStr, args...))
- }
- // SetDefaultCatalogSort sets the default value of the catalog sort flag that
- // will be used when initializing a new Fpdf instance. See SetCatalogSort() for
- // more details.
- func SetDefaultCatalogSort(flag bool) {
- gl.catalogSort = flag
- }
- // SetCatalogSort sets a flag that will be used, if true, to consistently order
- // the document's internal resource catalogs. This method is typically only
- // used for test purposes to facilitate PDF comparison.
- func (f *Fpdf) SetCatalogSort(flag bool) {
- f.catalogSort = flag
- }
- // SetDefaultCreationDate sets the default value of the document creation date
- // that will be used when initializing a new Fpdf instance. See
- // SetCreationDate() for more details.
- func SetDefaultCreationDate(tm time.Time) {
- gl.creationDate = tm
- }
- // SetDefaultModificationDate sets the default value of the document modification date
- // that will be used when initializing a new Fpdf instance. See
- // SetCreationDate() for more details.
- func SetDefaultModificationDate(tm time.Time) {
- gl.modDate = tm
- }
- // SetCreationDate fixes the document's internal CreationDate value. By
- // default, the time when the document is generated is used for this value.
- // This method is typically only used for testing purposes to facilitate PDF
- // comparison. Specify a zero-value time to revert to the default behavior.
- func (f *Fpdf) SetCreationDate(tm time.Time) {
- f.creationDate = tm
- }
- // SetModificationDate fixes the document's internal ModDate value.
- // See `SetCreationDate` for more details.
- func (f *Fpdf) SetModificationDate(tm time.Time) {
- f.modDate = tm
- }
- // SetJavascript adds Adobe JavaScript to the document.
- func (f *Fpdf) SetJavascript(script string) {
- f.javascript = &script
- }
- // RegisterAlias adds an (alias, replacement) pair to the document so we can
- // replace all occurrences of that alias after writing but before the document
- // is closed. Functions ExampleFpdf_RegisterAlias() and
- // ExampleFpdf_RegisterAlias_utf8() in fpdf_test.go demonstrate this method.
- func (f *Fpdf) RegisterAlias(alias, replacement string) {
- // Note: map[string]string assignments embed literal escape ("\00") sequences
- // into utf16 key and value strings. Consequently, subsequent search/replace
- // operations will fail unexpectedly if utf8toutf16() conversions take place
- // here. Instead, conversions are deferred until the actual search/replace
- // operation takes place when the PDF output is generated.
- f.aliasMap[alias] = replacement
- }
- func (f *Fpdf) replaceAliases() {
- for mode := 0; mode < 2; mode++ {
- for alias, replacement := range f.aliasMap {
- if mode == 1 {
- alias = utf8toutf16(alias, false)
- replacement = utf8toutf16(replacement, false)
- }
- for n := 1; n <= f.page; n++ {
- s := f.pages[n].String()
- if strings.Contains(s, alias) {
- s = strings.Replace(s, alias, replacement, -1)
- f.pages[n].Truncate(0)
- f.pages[n].WriteString(s)
- }
- }
- }
- }
- }
- func (f *Fpdf) putpages() {
- var wPt, hPt float64
- var pageSize SizeType
- var ok bool
- nb := f.page
- if len(f.aliasNbPagesStr) > 0 {
- // Replace number of pages
- f.RegisterAlias(f.aliasNbPagesStr, sprintf("%d", nb))
- }
- f.replaceAliases()
- if f.defOrientation == "P" {
- wPt = f.defPageSize.Wd * f.k
- hPt = f.defPageSize.Ht * f.k
- } else {
- wPt = f.defPageSize.Ht * f.k
- hPt = f.defPageSize.Wd * f.k
- }
- pagesObjectNumbers := make([]int, nb+1) // 1-based
- for n := 1; n <= nb; n++ {
- // Page
- f.newobj()
- pagesObjectNumbers[n] = f.n // save for /Kids
- f.out("<</Type /Page")
- f.out("/Parent 1 0 R")
- pageSize, ok = f.pageSizes[n]
- if ok {
- f.outf("/MediaBox [0 0 %.2f %.2f]", pageSize.Wd, pageSize.Ht)
- }
- for t, pb := range f.pageBoxes[n] {
- f.outf("/%s [%.2f %.2f %.2f %.2f]", t, pb.X, pb.Y, pb.Wd, pb.Ht)
- }
- f.out("/Resources 2 0 R")
- // Links
- if len(f.pageLinks[n])+len(f.pageAttachments[n]) > 0 {
- var annots fmtBuffer
- annots.printf("/Annots [")
- for _, pl := range f.pageLinks[n] {
- annots.printf("<</Type /Annot /Subtype /Link /Rect [%.2f %.2f %.2f %.2f] /Border [0 0 0] ",
- pl.x, pl.y, pl.x+pl.wd, pl.y-pl.ht)
- if pl.link == 0 {
- annots.printf("/A <</S /URI /URI %s>>>>", f.textstring(pl.linkStr))
- } else {
- l := f.links[pl.link]
- var sz SizeType
- var h float64
- sz, ok = f.pageSizes[l.page]
- if ok {
- h = sz.Ht
- } else {
- h = hPt
- }
- // dbg("h [%.2f], l.y [%.2f] f.k [%.2f]\n", h, l.y, f.k)
- annots.printf("/Dest [%d 0 R /XYZ 0 %.2f null]>>", 1+2*l.page, h-l.y*f.k)
- }
- }
- f.putAttachmentAnnotationLinks(&annots, n)
- annots.printf("]")
- f.out(annots.String())
- }
- if f.pdfVersion > "1.3" {
- f.out("/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>")
- }
- f.outf("/Contents %d 0 R>>", f.n+1)
- f.out("endobj")
- // Page content
- f.newobj()
- if f.compress {
- data := sliceCompress(f.pages[n].Bytes())
- f.outf("<</Filter /FlateDecode /Length %d>>", len(data))
- f.putstream(data)
- } else {
- f.outf("<</Length %d>>", f.pages[n].Len())
- f.putstream(f.pages[n].Bytes())
- }
- f.out("endobj")
- }
- // Pages root
- f.offsets[1] = f.buffer.Len()
- f.out("1 0 obj")
- f.out("<</Type /Pages")
- var kids fmtBuffer
- kids.printf("/Kids [")
- for i := 1; i <= nb; i++ {
- kids.printf("%d 0 R ", pagesObjectNumbers[i])
- }
- kids.printf("]")
- f.out(kids.String())
- f.outf("/Count %d", nb)
- f.outf("/MediaBox [0 0 %.2f %.2f]", wPt, hPt)
- f.out(">>")
- f.out("endobj")
- }
- func (f *Fpdf) putfonts() {
- if f.err != nil {
- return
- }
- nf := f.n
- for _, diff := range f.diffs {
- // Encodings
- f.newobj()
- f.outf("<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [%s]>>", diff)
- f.out("endobj")
- }
- {
- var fileList []string
- var info fontFileType
- var file string
- for file = range f.fontFiles {
- fileList = append(fileList, file)
- }
- if f.catalogSort {
- sort.SliceStable(fileList, func(i, j int) bool { return fileList[i] < fileList[j] })
- }
- for _, file = range fileList {
- info = f.fontFiles[file]
- if info.fontType != "UTF8" {
- f.newobj()
- info.n = f.n
- f.fontFiles[file] = info
- var font []byte
- if info.embedded {
- font = info.content
- } else {
- var err error
- font, err = f.loadFontFile(file)
- if err != nil {
- f.err = err
- return
- }
- }
- compressed := file[len(file)-2:] == ".z"
- if !compressed && info.length2 > 0 {
- buf := font[6:info.length1]
- buf = append(buf, font[6+info.length1+6:info.length2]...)
- font = buf
- }
- f.outf("<</Length %d", len(font))
- if compressed {
- f.out("/Filter /FlateDecode")
- }
- f.outf("/Length1 %d", info.length1)
- if info.length2 > 0 {
- f.outf("/Length2 %d /Length3 0", info.length2)
- }
- f.out(">>")
- f.putstream(font)
- f.out("endobj")
- }
- }
- }
- {
- var keyList []string
- var font fontDefType
- var key string
- for key = range f.fonts {
- keyList = append(keyList, key)
- }
- if f.catalogSort {
- sort.SliceStable(keyList, func(i, j int) bool { return keyList[i] < keyList[j] })
- }
- for _, key = range keyList {
- font = f.fonts[key]
- // Font objects
- font.N = f.n + 1
- f.fonts[key] = font
- tp := font.Tp
- name := font.Name
- switch tp {
- case "Core":
- // Core font
- f.newobj()
- f.out("<</Type /Font")
- f.outf("/BaseFont /%s", name)
- f.out("/Subtype /Type1")
- if name != "Symbol" && name != "ZapfDingbats" {
- f.out("/Encoding /WinAnsiEncoding")
- }
- f.out(">>")
- f.out("endobj")
- case "Type1":
- fallthrough
- case "TrueType":
- // Additional Type1 or TrueType/OpenType font
- f.newobj()
- f.out("<</Type /Font")
- f.outf("/BaseFont /%s", name)
- f.outf("/Subtype /%s", tp)
- f.out("/FirstChar 32 /LastChar 255")
- f.outf("/Widths %d 0 R", f.n+1)
- f.outf("/FontDescriptor %d 0 R", f.n+2)
- if font.DiffN > 0 {
- f.outf("/Encoding %d 0 R", nf+font.DiffN)
- } else {
- f.out("/Encoding /WinAnsiEncoding")
- }
- f.out(">>")
- f.out("endobj")
- // Widths
- f.newobj()
- var s fmtBuffer
- s.WriteString("[")
- for j := 32; j < 256; j++ {
- s.printf("%d ", font.Cw[j])
- }
- s.WriteString("]")
- f.out(s.String())
- f.out("endobj")
- // Descriptor
- f.newobj()
- s.Truncate(0)
- s.printf("<</Type /FontDescriptor /FontName /%s ", name)
- s.printf("/Ascent %d ", font.Desc.Ascent)
- s.printf("/Descent %d ", font.Desc.Descent)
- s.printf("/CapHeight %d ", font.Desc.CapHeight)
- s.printf("/Flags %d ", font.Desc.Flags)
- s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
- font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
- s.printf("/ItalicAngle %d ", font.Desc.ItalicAngle)
- s.printf("/StemV %d ", font.Desc.StemV)
- s.printf("/MissingWidth %d ", font.Desc.MissingWidth)
- var suffix string
- if tp != "Type1" {
- suffix = "2"
- }
- s.printf("/FontFile%s %d 0 R>>", suffix, f.fontFiles[font.File].n)
- f.out(s.String())
- f.out("endobj")
- case "UTF8":
- fontName := "utf8" + font.Name
- usedRunes := font.usedRunes
- delete(usedRunes, 0)
- utf8FontStream := font.utf8File.GenerateCutFont(usedRunes)
- utf8FontSize := len(utf8FontStream)
- compressedFontStream := sliceCompress(utf8FontStream)
- CodeSignDictionary := font.utf8File.CodeSymbolDictionary
- delete(CodeSignDictionary, 0)
- f.newobj()
- f.out(fmt.Sprintf("<</Type /Font\n/Subtype /Type0\n/BaseFont /%s\n/Encoding /Identity-H\n/DescendantFonts [%d 0 R]\n/ToUnicode %d 0 R>>\n"+"endobj", fontName, f.n+1, f.n+2))
- f.newobj()
- f.out("<</Type /Font\n/Subtype /CIDFontType2\n/BaseFont /" + fontName + "\n" +
- "/CIDSystemInfo " + strconv.Itoa(f.n+2) + " 0 R\n/FontDescriptor " + strconv.Itoa(f.n+3) + " 0 R")
- if font.Desc.MissingWidth != 0 {
- f.out("/DW " + strconv.Itoa(font.Desc.MissingWidth) + "")
- }
- f.generateCIDFontMap(&font, font.utf8File.LastRune)
- f.out("/CIDToGIDMap " + strconv.Itoa(f.n+4) + " 0 R>>")
- f.out("endobj")
- f.newobj()
- f.out("<</Length " + strconv.Itoa(len(toUnicode)) + ">>")
- f.putstream([]byte(toUnicode))
- f.out("endobj")
- // CIDInfo
- f.newobj()
- f.out("<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0>>")
- f.out("endobj")
- // Font descriptor
- f.newobj()
- var s fmtBuffer
- s.printf("<</Type /FontDescriptor /FontName /%s\n /Ascent %d", fontName, font.Desc.Ascent)
- s.printf(" /Descent %d", font.Desc.Descent)
- s.printf(" /CapHeight %d", font.Desc.CapHeight)
- v := font.Desc.Flags
- v = v | 4
- v = v &^ 32
- s.printf(" /Flags %d", v)
- s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
- font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
- s.printf(" /ItalicAngle %d", font.Desc.ItalicAngle)
- s.printf(" /StemV %d", font.Desc.StemV)
- s.printf(" /MissingWidth %d", font.Desc.MissingWidth)
- s.printf("/FontFile2 %d 0 R", f.n+2)
- s.printf(">>")
- f.out(s.String())
- f.out("endobj")
- // Embed CIDToGIDMap
- cidToGidMap := make([]byte, 256*256*2)
- for cc, glyph := range CodeSignDictionary {
- cidToGidMap[cc*2] = byte(glyph >> 8)
- cidToGidMap[cc*2+1] = byte(glyph & 0xFF)
- }
- cidToGidMap = sliceCompress(cidToGidMap)
- f.newobj()
- f.out("<</Length " + strconv.Itoa(len(cidToGidMap)) + "/Filter /FlateDecode>>")
- f.putstream(cidToGidMap)
- f.out("endobj")
- //Font file
- f.newobj()
- f.out("<</Length " + strconv.Itoa(len(compressedFontStream)))
- f.out("/Filter /FlateDecode")
- f.out("/Length1 " + strconv.Itoa(utf8FontSize))
- f.out(">>")
- f.putstream(compressedFontStream)
- f.out("endobj")
- default:
- f.err = fmt.Errorf("unsupported font type: %s", tp)
- return
- }
- }
- }
- return
- }
- func (f *Fpdf) generateCIDFontMap(font *fontDefType, LastRune int) {
- rangeID := 0
- cidArray := make(map[int]*untypedKeyMap)
- cidArrayKeys := make([]int, 0)
- prevCid := -2
- prevWidth := -1
- interval := false
- startCid := 1
- cwLen := LastRune + 1
- // for each character
- for cid := startCid; cid < cwLen; cid++ {
- if font.Cw[cid] == 0x00 {
- continue
- }
- width := font.Cw[cid]
- if width == 65535 {
- width = 0
- }
- if numb, OK := font.usedRunes[cid]; cid > 255 && (!OK || numb == 0) {
- continue
- }
- if cid == prevCid+1 {
- if width == prevWidth {
- if width == cidArray[rangeID].get(0) {
- cidArray[rangeID].put(nil, width)
- } else {
- cidArray[rangeID].pop()
- rangeID = prevCid
- r := untypedKeyMap{
- valueSet: make([]int, 0),
- keySet: make([]interface{}, 0),
- }
- cidArray[rangeID] = &r
- cidArrayKeys = append(cidArrayKeys, rangeID)
- cidArray[rangeID].put(nil, prevWidth)
- cidArray[rangeID].put(nil, width)
- }
- interval = true
- cidArray[rangeID].put("interval", 1)
- } else {
- if interval {
- // new range
- rangeID = cid
- r := untypedKeyMap{
- valueSet: make([]int, 0),
- keySet: make([]interface{}, 0),
- }
- cidArray[rangeID] = &r
- cidArrayKeys = append(cidArrayKeys, rangeID)
- cidArray[rangeID].put(nil, width)
- } else {
- cidArray[rangeID].put(nil, width)
- }
- interval = false
- }
- } else {
- rangeID = cid
- r := untypedKeyMap{
- valueSet: make([]int, 0),
- keySet: make([]interface{}, 0),
- }
- cidArray[rangeID] = &r
- cidArrayKeys = append(cidArrayKeys, rangeID)
- cidArray[rangeID].put(nil, width)
- interval = false
- }
- prevCid = cid
- prevWidth = width
- }
- previousKey := -1
- nextKey := -1
- isInterval := false
- for g := 0; g < len(cidArrayKeys); {
- key := cidArrayKeys[g]
- ws := *cidArray[key]
- cws := len(ws.keySet)
- if (key == nextKey) && (!isInterval) && (ws.getIndex("interval") < 0 || cws < 4) {
- if cidArray[key].getIndex("interval") >= 0 {
- cidArray[key].delete("interval")
- }
- cidArray[previousKey] = arrayMerge(cidArray[previousKey], cidArray[key])
- cidArrayKeys = remove(cidArrayKeys, key)
- } else {
- g++
- previousKey = key
- }
- nextKey = key + cws
- // ui := ws.getIndex("interval")
- // ui = ui + 1
- if ws.getIndex("interval") >= 0 {
- if cws > 3 {
- isInterval = true
- } else {
- isInterval = false
- }
- cidArray[key].delete("interval")
- nextKey--
- } else {
- isInterval = false
- }
- }
- var w fmtBuffer
- for _, k := range cidArrayKeys {
- ws := cidArray[k]
- if len(arrayCountValues(ws.valueSet)) == 1 {
- w.printf(" %d %d %d", k, k+len(ws.valueSet)-1, ws.get(0))
- } else {
- w.printf(" %d [ %s ]\n", k, implode(" ", ws.valueSet))
- }
- }
- f.out("/W [" + w.String() + " ]")
- }
- func implode(sep string, arr []int) string {
- var s fmtBuffer
- for i := 0; i < len(arr)-1; i++ {
- s.printf("%v", arr[i])
- s.printf(sep)
- }
- if len(arr) > 0 {
- s.printf("%v", arr[len(arr)-1])
- }
- return s.String()
- }
- // arrayCountValues counts the occurrences of each item in the $mp array.
- func arrayCountValues(mp []int) map[int]int {
- answer := make(map[int]int)
- for _, v := range mp {
- answer[v] = answer[v] + 1
- }
- return answer
- }
- func (f *Fpdf) loadFontFile(name string) ([]byte, error) {
- if f.fontLoader != nil {
- reader, err := f.fontLoader.Open(name)
- if err == nil {
- data, err := ioutil.ReadAll(reader)
- if closer, ok := reader.(io.Closer); ok {
- closer.Close()
- }
- return data, err
- }
- }
- return ioutil.ReadFile(path.Join(f.fontpath, name))
- }
- func (f *Fpdf) putimages() {
- var keyList []string
- var key string
- for key = range f.images {
- keyList = append(keyList, key)
- }
- // Sort the keyList []string by the corresponding image's width.
- if f.catalogSort {
- sort.SliceStable(keyList, func(i, j int) bool { return f.images[keyList[i]].w < f.images[keyList[j]].w })
- }
- // Maintain a list of inserted image SHA-1 hashes, with their
- // corresponding object ID number.
- insertedImages := map[string]int{}
- for _, key = range keyList {
- image := f.images[key]
- // Check if this image has already been inserted using it's SHA-1 hash.
- insertedImageObjN, isFound := insertedImages[image.i]
- // If found, skip inserting the image as a new object, and
- // use the object ID from the insertedImages map.
- // If not, insert the image into the PDF and store the object ID.
- if isFound {
- image.n = insertedImageObjN
- } else {
- f.putimage(image)
- insertedImages[image.i] = image.n
- }
- }
- }
- func (f *Fpdf) putimage(info *ImageInfoType) {
- f.newobj()
- info.n = f.n
- f.out("<</Type /XObject")
- f.out("/Subtype /Image")
- f.outf("/Width %d", int(info.w))
- f.outf("/Height %d", int(info.h))
- if info.cs == "Indexed" {
- f.outf("/ColorSpace [/Indexed /DeviceRGB %d %d 0 R]", len(info.pal)/3-1, f.n+1)
- } else {
- f.outf("/ColorSpace /%s", info.cs)
- if info.cs == "DeviceCMYK" {
- f.out("/Decode [1 0 1 0 1 0 1 0]")
- }
- }
- f.outf("/BitsPerComponent %d", info.bpc)
- if len(info.f) > 0 {
- f.outf("/Filter /%s", info.f)
- }
- if len(info.dp) > 0 {
- f.outf("/DecodeParms <<%s>>", info.dp)
- }
- if len(info.trns) > 0 {
- var trns fmtBuffer
- for _, v := range info.trns {
- trns.printf("%d %d ", v, v)
- }
- f.outf("/Mask [%s]", trns.String())
- }
- if info.smask != nil {
- f.outf("/SMask %d 0 R", f.n+1)
- }
- f.outf("/Length %d>>", len(info.data))
- f.putstream(info.data)
- f.out("endobj")
- // Soft mask
- if len(info.smask) > 0 {
- smask := &ImageInfoType{
- w: info.w,
- h: info.h,
- cs: "DeviceGray",
- bpc: 8,
- f: info.f,
- dp: sprintf("/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns %d", int(info.w)),
- data: info.smask,
- scale: f.k,
- }
- f.putimage(smask)
- }
- // Palette
- if info.cs == "Indexed" {
- f.newobj()
- if f.compress {
- pal := sliceCompress(info.pal)
- f.outf("<</Filter /FlateDecode /Length %d>>", len(pal))
- f.putstream(pal)
- } else {
- f.outf("<</Length %d>>", len(info.pal))
- f.putstream(info.pal)
- }
- f.out("endobj")
- }
- }
- func (f *Fpdf) putxobjectdict() {
- {
- var image *ImageInfoType
- var key string
- var keyList []string
- for key = range f.images {
- keyList = append(keyList, key)
- }
- if f.catalogSort {
- sort.SliceStable(keyList, func(i, j int) bool { return f.images[keyList[i]].i < f.images[keyList[j]].i })
- }
- for _, key = range keyList {
- image = f.images[key]
- f.outf("/I%s %d 0 R", image.i, image.n)
- }
- }
- {
- var keyList []string
- var key string
- var tpl Template
- keyList = templateKeyList(f.templates, f.catalogSort)
- for _, key = range keyList {
- tpl = f.templates[key]
- // for _, tpl := range f.templates {
- id := tpl.ID()
- if objID, ok := f.templateObjects[id]; ok {
- f.outf("/TPL%s %d 0 R", id, objID)
- }
- }
- }
- {
- for tplName, objID := range f.importedTplObjs {
- // here replace obj id hash with n
- f.outf("%s %d 0 R", tplName, f.importedTplIDs[objID])
- }
- }
- }
- func (f *Fpdf) putresourcedict() {
- f.out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
- f.out("/Font <<")
- {
- var keyList []string
- var font fontDefType
- var key string
- for key = range f.fonts {
- keyList = append(keyList, key)
- }
- if f.catalogSort {
- sort.SliceStable(keyList, func(i, j int) bool { return f.fonts[keyList[i]].i < f.fonts[keyList[j]].i })
- }
- for _, key = range keyList {
- font = f.fonts[key]
- f.outf("/F%s %d 0 R", font.i, font.N)
- }
- }
- f.out(">>")
- f.out("/XObject <<")
- f.putxobjectdict()
- f.out(">>")
- count := len(f.blendList)
- if count > 1 {
- f.out("/ExtGState <<")
- for j := 1; j < count; j++ {
- f.outf("/GS%d %d 0 R", j, f.blendList[j].objNum)
- }
- f.out(">>")
- }
- count = len(f.gradientList)
- if count > 1 {
- f.out("/Shading <<")
- for j := 1; j < count; j++ {
- f.outf("/Sh%d %d 0 R", j, f.gradientList[j].objNum)
- }
- f.out(">>")
- }
- // Layers
- f.layerPutResourceDict()
- f.spotColorPutResourceDict()
- }
- func (f *Fpdf) putBlendModes() {
- count := len(f.blendList)
- for j := 1; j < count; j++ {
- bl := f.blendList[j]
- f.newobj()
- f.blendList[j].objNum = f.n
- f.outf("<</Type /ExtGState /ca %s /CA %s /BM /%s>>",
- bl.fillStr, bl.strokeStr, bl.modeStr)
- f.out("endobj")
- }
- }
- func (f *Fpdf) putGradients() {
- count := len(f.gradientList)
- for j := 1; j < count; j++ {
- var f1 int
- gr := f.gradientList[j]
- if gr.tp == 2 || gr.tp == 3 {
- f.newobj()
- f.outf("<</FunctionType 2 /Domain [0.0 1.0] /C0 [%s] /C1 [%s] /N 1>>", gr.clr1Str, gr.clr2Str)
- f.out("endobj")
- f1 = f.n
- }
- f.newobj()
- f.outf("<</ShadingType %d /ColorSpace /DeviceRGB", gr.tp)
- if gr.tp == 2 {
- f.outf("/Coords [%.5f %.5f %.5f %.5f] /Function %d 0 R /Extend [true true]>>",
- gr.x1, gr.y1, gr.x2, gr.y2, f1)
- } else if gr.tp == 3 {
- f.outf("/Coords [%.5f %.5f 0 %.5f %.5f %.5f] /Function %d 0 R /Extend [true true]>>",
- gr.x1, gr.y1, gr.x2, gr.y2, gr.r, f1)
- }
- f.out("endobj")
- f.gradientList[j].objNum = f.n
- }
- }
- func (f *Fpdf) putjavascript() {
- if f.javascript == nil {
- return
- }
- f.newobj()
- f.nJs = f.n
- f.out("<<")
- f.outf("/Names [(EmbeddedJS) %d 0 R]", f.n+1)
- f.out(">>")
- f.out("endobj")
- f.newobj()
- f.out("<<")
- f.out("/S /JavaScript")
- f.outf("/JS %s", f.textstring(*f.javascript))
- f.out(">>")
- f.out("endobj")
- }
- func (f *Fpdf) putresources() {
- if f.err != nil {
- return
- }
- f.layerPutLayers()
- f.putBlendModes()
- f.putGradients()
- f.putSpotColors()
- f.putfonts()
- if f.err != nil {
- return
- }
- f.putimages()
- f.putTemplates()
- f.putImportedTemplates() // gofpdi
- // Resource dictionary
- f.offsets[2] = f.buffer.Len()
- f.out("2 0 obj")
- f.out("<<")
- f.putresourcedict()
- f.out(">>")
- f.out("endobj")
- f.putjavascript()
- if f.protect.encrypted {
- f.newobj()
- f.protect.objNum = f.n
- f.out("<<")
- f.out("/Filter /Standard")
- f.out("/V 1")
- f.out("/R 2")
- f.outf("/O (%s)", f.escape(string(f.protect.oValue)))
- f.outf("/U (%s)", f.escape(string(f.protect.uValue)))
- f.outf("/P %d", f.protect.pValue)
- f.out(">>")
- f.out("endobj")
- }
- return
- }
- // returns Now() if tm is zero
- func timeOrNow(tm time.Time) time.Time {
- if tm.IsZero() {
- return time.Now()
- }
- return tm
- }
- func (f *Fpdf) putinfo() {
- if len(f.producer) > 0 {
- f.outf("/Producer %s", f.textstring(f.producer))
- }
- if len(f.title) > 0 {
- f.outf("/Title %s", f.textstring(f.title))
- }
- if len(f.subject) > 0 {
- f.outf("/Subject %s", f.textstring(f.subject))
- }
- if len(f.author) > 0 {
- f.outf("/Author %s", f.textstring(f.author))
- }
- if len(f.keywords) > 0 {
- f.outf("/Keywords %s", f.textstring(f.keywords))
- }
- if len(f.creator) > 0 {
- f.outf("/Creator %s", f.textstring(f.creator))
- }
- creation := timeOrNow(f.creationDate)
- f.outf("/CreationDate %s", f.textstring("D:"+creation.Format("20060102150405")))
- mod := timeOrNow(f.modDate)
- f.outf("/ModDate %s", f.textstring("D:"+mod.Format("20060102150405")))
- }
- func (f *Fpdf) putcatalog() {
- f.out("/Type /Catalog")
- f.out("/Pages 1 0 R")
- switch f.zoomMode {
- case "fullpage":
- f.out("/OpenAction [3 0 R /Fit]")
- case "fullwidth":
- f.out("/OpenAction [3 0 R /FitH null]")
- case "real":
- f.out("/OpenAction [3 0 R /XYZ null null 1]")
- }
- // } else if !is_string($this->zoomMode))
- // $this->out('/OpenAction [3 0 R /XYZ null null '.sprintf('%.2f',$this->zoomMode/100).']');
- switch f.layoutMode {
- case "single", "SinglePage":
- f.out("/PageLayout /SinglePage")
- case "continuous", "OneColumn":
- f.out("/PageLayout /OneColumn")
- case "two", "TwoColumnLeft":
- f.out("/PageLayout /TwoColumnLeft")
- case "TwoColumnRight":
- f.out("/PageLayout /TwoColumnRight")
- case "TwoPageLeft", "TwoPageRight":
- if f.pdfVersion < "1.5" {
- f.pdfVersion = "1.5"
- }
- f.out("/PageLayout /" + f.layoutMode)
- }
- // Bookmarks
- if len(f.outlines) > 0 {
- f.outf("/Outlines %d 0 R", f.outlineRoot)
- f.out("/PageMode /UseOutlines")
- }
- // Layers
- f.layerPutCatalog()
- // Name dictionary :
- // -> Javascript
- // -> Embedded files
- f.out("/Names <<")
- // JavaScript
- if f.javascript != nil {
- f.outf("/JavaScript %d 0 R", f.nJs)
- }
- // Embedded files
- f.outf("/EmbeddedFiles %s", f.getEmbeddedFiles())
- f.out(">>")
- }
- func (f *Fpdf) putheader() {
- if len(f.blendMap) > 0 && f.pdfVersion < "1.4" {
- f.pdfVersion = "1.4"
- }
- f.outf("%%PDF-%s", f.pdfVersion)
- }
- func (f *Fpdf) puttrailer() {
- f.outf("/Size %d", f.n+1)
- f.outf("/Root %d 0 R", f.n)
- f.outf("/Info %d 0 R", f.n-1)
- if f.protect.encrypted {
- f.outf("/Encrypt %d 0 R", f.protect.objNum)
- f.out("/ID [()()]")
- }
- }
- func (f *Fpdf) putxmp() {
- if len(f.xmp) == 0 {
- return
- }
- f.newobj()
- f.outf("<< /Type /Metadata /Subtype /XML /Length %d >>", len(f.xmp))
- f.putstream(f.xmp)
- f.out("endobj")
- }
- func (f *Fpdf) putbookmarks() {
- nb := len(f.outlines)
- if nb > 0 {
- lru := make(map[int]int)
- level := 0
- for i, o := range f.outlines {
- if o.level > 0 {
- parent := lru[o.level-1]
- f.outlines[i].parent = parent
- f.outlines[parent].last = i
- if o.level > level {
- f.outlines[parent].first = i
- }
- } else {
- f.outlines[i].parent = nb
- }
- if o.level <= level && i > 0 {
- prev := lru[o.level]
- f.outlines[prev].next = i
- f.outlines[i].prev = prev
- }
- lru[o.level] = i
- level = o.level
- }
- n := f.n + 1
- for _, o := range f.outlines {
- f.newobj()
- f.outf("<</Title %s", f.textstring(o.text))
- f.outf("/Parent %d 0 R", n+o.parent)
- if o.prev != -1 {
- f.outf("/Prev %d 0 R", n+o.prev)
- }
- if o.next != -1 {
- f.outf("/Next %d 0 R", n+o.next)
- }
- if o.first != -1 {
- f.outf("/First %d 0 R", n+o.first)
- }
- if o.last != -1 {
- f.outf("/Last %d 0 R", n+o.last)
- }
- f.outf("/Dest [%d 0 R /XYZ 0 %.2f null]", 1+2*o.p, (f.h-o.y)*f.k)
- f.out("/Count 0>>")
- f.out("endobj")
- }
- f.newobj()
- f.outlineRoot = f.n
- f.outf("<</Type /Outlines /First %d 0 R", n)
- f.outf("/Last %d 0 R>>", n+lru[0])
- f.out("endobj")
- }
- }
- func (f *Fpdf) enddoc() {
- if f.err != nil {
- return
- }
- f.layerEndDoc()
- f.putheader()
- // Embedded files
- f.putAttachments()
- f.putAnnotationsAttachments()
- f.putpages()
- f.putresources()
- if f.err != nil {
- return
- }
- // Bookmarks
- f.putbookmarks()
- // Metadata
- f.putxmp()
- // Info
- f.newobj()
- f.out("<<")
- f.putinfo()
- f.out(">>")
- f.out("endobj")
- // Catalog
- f.newobj()
- f.out("<<")
- f.putcatalog()
- f.out(">>")
- f.out("endobj")
- // Cross-ref
- o := f.buffer.Len()
- f.out("xref")
- f.outf("0 %d", f.n+1)
- f.out("0000000000 65535 f ")
- for j := 1; j <= f.n; j++ {
- f.outf("%010d 00000 n ", f.offsets[j])
- }
- // Trailer
- f.out("trailer")
- f.out("<<")
- f.puttrailer()
- f.out(">>")
- f.out("startxref")
- f.outf("%d", o)
- f.out("%%EOF")
- f.state = 3
- return
- }
- // Path Drawing
- // MoveTo moves the stylus to (x, y) without drawing the path from the
- // previous point. Paths must start with a MoveTo to set the original
- // stylus location or the result is undefined.
- //
- // Create a "path" by moving a virtual stylus around the page (with
- // MoveTo, LineTo, CurveTo, CurveBezierCubicTo, ArcTo & ClosePath)
- // then draw it or fill it in (with DrawPath). The main advantage of
- // using the path drawing routines rather than multiple Fpdf.Line is
- // that PDF creates nice line joins at the angles, rather than just
- // overlaying the lines.
- func (f *Fpdf) MoveTo(x, y float64) {
- f.point(x, y)
- f.x, f.y = x, y
- }
- // LineTo creates a line from the current stylus location to (x, y), which
- // becomes the new stylus location. Note that this only creates the line in
- // the path; it does not actually draw the line on the page.
- //
- // The MoveTo() example demonstrates this method.
- func (f *Fpdf) LineTo(x, y float64) {
- f.outf("%.2f %.2f l", x*f.k, (f.h-y)*f.k)
- f.x, f.y = x, y
- }
- // CurveTo creates a single-segment quadratic Bézier curve. The curve starts at
- // the current stylus location and ends at the point (x, y). The control point
- // (cx, cy) specifies the curvature. At the start point, the curve is tangent
- // to the straight line between the current stylus location and the control
- // point. At the end point, the curve is tangent to the straight line between
- // the end point and the control point.
- //
- // The MoveTo() example demonstrates this method.
- func (f *Fpdf) CurveTo(cx, cy, x, y float64) {
- f.outf("%.5f %.5f %.5f %.5f v", cx*f.k, (f.h-cy)*f.k, x*f.k, (f.h-y)*f.k)
- f.x, f.y = x, y
- }
- // CurveBezierCubicTo creates a single-segment cubic Bézier curve. The curve
- // starts at the current stylus location and ends at the point (x, y). The
- // control points (cx0, cy0) and (cx1, cy1) specify the curvature. At the
- // current stylus, the curve is tangent to the straight line between the
- // current stylus location and the control point (cx0, cy0). At the end point,
- // the curve is tangent to the straight line between the end point and the
- // control point (cx1, cy1).
- //
- // The MoveTo() example demonstrates this method.
- func (f *Fpdf) CurveBezierCubicTo(cx0, cy0, cx1, cy1, x, y float64) {
- f.curve(cx0, cy0, cx1, cy1, x, y)
- f.x, f.y = x, y
- }
- // ClosePath creates a line from the current location to the last MoveTo point
- // (if not the same) and mark the path as closed so the first and last lines
- // join nicely.
- //
- // The MoveTo() example demonstrates this method.
- func (f *Fpdf) ClosePath() {
- f.outf("h")
- }
- // DrawPath actually draws the path on the page.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D".
- // Path-painting operators as defined in the PDF specification are also
- // allowed: "S" (Stroke the path), "s" (Close and stroke the path),
- // "f" (fill the path, using the nonzero winding number), "f*"
- // (Fill the path, using the even-odd rule), "B" (Fill and then stroke
- // the path, using the nonzero winding number rule), "B*" (Fill and
- // then stroke the path, using the even-odd rule), "b" (Close, fill,
- // and then stroke the path, using the nonzero winding number rule) and
- // "b*" (Close, fill, and then stroke the path, using the even-odd
- // rule).
- // Drawing uses the current draw color, line width, and cap style
- // centered on the
- // path. Filling uses the current fill color.
- //
- // The MoveTo() example demonstrates this method.
- func (f *Fpdf) DrawPath(styleStr string) {
- f.outf(fillDrawOp(styleStr))
- }
- // ArcTo draws an elliptical arc centered at point (x, y). rx and ry specify its
- // horizontal and vertical radii. If the start of the arc is not at
- // the current position, a connecting line will be drawn.
- //
- // degRotate specifies the angle that the arc will be rotated. degStart and
- // degEnd specify the starting and ending angle of the arc. All angles are
- // specified in degrees and measured counter-clockwise from the 3 o'clock
- // position.
- //
- // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
- // outlined and filled. An empty string will be replaced with "D". Drawing uses
- // the current draw color, line width, and cap style centered on the arc's
- // path. Filling uses the current fill color.
- //
- // The MoveTo() example demonstrates this method.
- func (f *Fpdf) ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) {
- f.arc(x, y, rx, ry, degRotate, degStart, degEnd, "", true)
- }
- func (f *Fpdf) arc(x, y, rx, ry, degRotate, degStart, degEnd float64,
- styleStr string, path bool) {
- x *= f.k
- y = (f.h - y) * f.k
- rx *= f.k
- ry *= f.k
- segments := int(degEnd-degStart) / 60
- if segments < 2 {
- segments = 2
- }
- angleStart := degStart * math.Pi / 180
- angleEnd := degEnd * math.Pi / 180
- angleTotal := angleEnd - angleStart
- dt := angleTotal / float64(segments)
- dtm := dt / 3
- if degRotate != 0 {
- a := -degRotate * math.Pi / 180
- f.outf("q %.5f %.5f %.5f %.5f %.5f %.5f cm",
- math.Cos(a), -1*math.Sin(a),
- math.Sin(a), math.Cos(a), x, y)
- x = 0
- y = 0
- }
- t := angleStart
- a0 := x + rx*math.Cos(t)
- b0 := y + ry*math.Sin(t)
- c0 := -rx * math.Sin(t)
- d0 := ry * math.Cos(t)
- sx := a0 / f.k // start point of arc
- sy := f.h - (b0 / f.k)
- if path {
- if f.x != sx || f.y != sy {
- // Draw connecting line to start point
- f.LineTo(sx, sy)
- }
- } else {
- f.point(sx, sy)
- }
- for j := 1; j <= segments; j++ {
- // Draw this bit of the total curve
- t = (float64(j) * dt) + angleStart
- a1 := x + rx*math.Cos(t)
- b1 := y + ry*math.Sin(t)
- c1 := -rx * math.Sin(t)
- d1 := ry * math.Cos(t)
- f.curve((a0+(c0*dtm))/f.k,
- f.h-((b0+(d0*dtm))/f.k),
- (a1-(c1*dtm))/f.k,
- f.h-((b1-(d1*dtm))/f.k),
- a1/f.k,
- f.h-(b1/f.k))
- a0 = a1
- b0 = b1
- c0 = c1
- d0 = d1
- if path {
- f.x = a1 / f.k
- f.y = f.h - (b1 / f.k)
- }
- }
- if !path {
- f.out(fillDrawOp(styleStr))
- }
- if degRotate != 0 {
- f.out("Q")
- }
- }
|