1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023 |
- /*
- * 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
- 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")
- }
- }
|