Img32.Text.pas 135 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464
  1. unit Img32.Text;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.8 *
  5. * Date : 22 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * Purpose : TrueType fonts for TImage32 (without Windows dependencies) *
  9. * License : http://www.boost.org/LICENSE_1_0.txt *
  10. *******************************************************************************)
  11. interface
  12. {$I Img32.inc}
  13. uses
  14. {$IFDEF MSWINDOWS} Windows, ShlObj, ActiveX, {$ENDIF}
  15. Types, SysUtils, Classes, Math,
  16. {$IFDEF XPLAT_GENERICS} Generics.Collections, Generics.Defaults,{$ENDIF}
  17. Img32, Img32.Draw, Img32.Vector;
  18. type
  19. TFixed = type single;
  20. Int16 = type SmallInt;
  21. TFontFormat = (ffInvalid, ffTrueType, ffCompact);
  22. TFontFamily = (tfUnknown, tfSerif, tfSansSerif, tfMonospace);
  23. TFontReader = class;
  24. {$IFDEF MSWINDOWS}
  25. PArrayOfEnumLogFontEx = ^TArrayOfEnumLogFontEx;
  26. TArrayOfEnumLogFontEx = array of TEnumLogFontEx;
  27. // TFontReaderFamily - a custom (Image32) record
  28. TFontReaderFamily = record
  29. regularFR : TFontReader;
  30. boldFR : TFontReader;
  31. italicFR : TFontReader;
  32. boldItalicFR : TFontReader;
  33. end;
  34. {$ENDIF}
  35. {$IFNDEF Unicode}
  36. UnicodeString = WideString;
  37. {$ENDIF}
  38. TMacStyle = (msBold, msItalic, msUnderline, msOutline,
  39. msShadow, msCondensed, msExtended);
  40. TMacStyles = set of TMacStyle;
  41. TTextAlign = (taLeft, taRight, taCenter, taJustify);
  42. TTextVAlign = (tvaTop, tvaMiddle, tvaBottom);
  43. // nb: Avoid "packed" records as these cause problems with Android
  44. TFontHeaderTable = record
  45. sfntVersion : Cardinal; // $10000 or 'OTTO'
  46. numTables : WORD;
  47. searchRange : WORD;
  48. entrySelector : WORD;
  49. rangeShift : WORD;
  50. end;
  51. TFontTable = record
  52. tag : Cardinal;
  53. checkSum : Cardinal;
  54. offset : Cardinal;
  55. length : Cardinal;
  56. end;
  57. TFontTable_Cmap = record
  58. version : WORD;
  59. numTables : WORD;
  60. end;
  61. TCmapTblRec = record
  62. platformID : WORD; // Unicode = 0; Windows = 3 (obsolete);
  63. encodingID : WORD;
  64. offset : Cardinal;
  65. end;
  66. TCmapFormat0 = record
  67. format : WORD; // 0
  68. length : WORD;
  69. language : WORD;
  70. end;
  71. TCmapFormat4 = record
  72. format : WORD; // 4
  73. length : WORD;
  74. language : WORD;
  75. segCountX2 : WORD;
  76. searchRange : WORD;
  77. entrySelector : WORD;
  78. rangeShift : WORD;
  79. //endCodes : array of WORD; // last = $FFFF
  80. //reserved : WORD; // 0
  81. //startCodes : array of WORD;
  82. end;
  83. TFormat4Rec = record
  84. startCode : WORD;
  85. endCode : WORD;
  86. idDelta : WORD;
  87. rangeOffset : WORD;
  88. end;
  89. TCmapFormat6 = record
  90. format : WORD; // 6
  91. length : WORD;
  92. language : WORD;
  93. firstCode : WORD;
  94. entryCount : WORD;
  95. end;
  96. TCmapFormat12 = record
  97. format : WORD; // 12
  98. reserved : WORD; // 0
  99. length : DWORD;
  100. language : DWORD;
  101. nGroups : DWORD;
  102. //array[nGroups] of TFormat12Group;
  103. end;
  104. TFormat12Rec = record
  105. startCode : WORD;
  106. endCode : WORD;
  107. idDelta : WORD;
  108. rangeOffset : WORD;
  109. end;
  110. TFormat12Group = record
  111. startCharCode : DWORD;
  112. endCharCode : DWORD;
  113. startGlyphCode: DWORD;
  114. end;
  115. TFontTable_Kern = record
  116. version : WORD;
  117. numTables : WORD;
  118. end;
  119. TKernSubTbl = record
  120. version : WORD;
  121. length : WORD;
  122. coverage : WORD;
  123. end;
  124. TFormat0KernHdr = record
  125. nPairs : WORD;
  126. searchRange : WORD;
  127. entrySelector : WORD;
  128. rangeShift : WORD;
  129. end;
  130. TFormat0KernRec = record
  131. left : WORD;
  132. right : WORD;
  133. value : int16;
  134. end;
  135. TArrayOfKernRecs = array of TFormat0KernRec;
  136. TFontTable_Name = record
  137. format : WORD;
  138. count : WORD;
  139. stringOffset : WORD;
  140. //nameRecords[]
  141. end;
  142. TNameRec = record
  143. platformID : WORD;
  144. encodingID : WORD;
  145. languageID : WORD;
  146. nameID : WORD;
  147. length : WORD;
  148. offset : WORD;
  149. end;
  150. TFontTable_Head = record
  151. majorVersion : WORD;
  152. minorVersion : WORD;
  153. fontRevision : TFixed;
  154. checkSumAdjust : Cardinal;
  155. magicNumber : Cardinal; // $5F0F3CF5
  156. flags : WORD;
  157. unitsPerEm : WORD;
  158. dateCreated : UInt64;
  159. dateModified : UInt64;
  160. xMin : Int16;
  161. yMin : Int16;
  162. xMax : Int16;
  163. yMax : Int16;
  164. macStyle : WORD; // see TMacStyles
  165. lowestRecPPEM : WORD;
  166. fontDirHint : Int16; // left to right, right to left
  167. indexToLocFmt : Int16;
  168. glyphDataFmt : Int16;
  169. end;
  170. TFontTable_Maxp = record
  171. version : TFixed;
  172. numGlyphs : WORD;
  173. maxPoints : WORD;
  174. maxContours : WORD;
  175. end;
  176. TFontTable_Glyf = record
  177. numContours : Int16;
  178. xMin : Int16;
  179. yMin : Int16;
  180. xMax : Int16;
  181. yMax : Int16;
  182. end;
  183. TFontTable_Hhea = record
  184. version : TFixed;
  185. ascent : Int16;
  186. descent : Int16;
  187. lineGap : Int16;
  188. advWidthMax : WORD;
  189. minLSB : Int16;
  190. minRSB : Int16;
  191. xMaxExtent : Int16;
  192. caretSlopeRise : Int16;
  193. caretSlopeRun : Int16;
  194. caretOffset : Int16;
  195. reserved : UInt64;
  196. metricDataFmt : Int16;
  197. numLongHorMets : WORD;
  198. end;
  199. TFontTable_Hmtx = record
  200. advanceWidth : WORD;
  201. leftSideBearing : Int16;
  202. end;
  203. TFontTable_Post = record
  204. majorVersion : WORD;
  205. minorVersion : WORD;
  206. italicAngle : TFixed;
  207. underlinePos : Int16;
  208. underlineWidth : Int16;
  209. isFixedPitch : Cardinal;
  210. //minMemType42 : Cardinal;
  211. //maxMemType42 : Cardinal;
  212. //minMemType1 : Cardinal;
  213. //maxMemType1 : Cardinal;
  214. end;
  215. ArrayOfUtf8String = array of Utf8String;
  216. // TFontInfo: a custom summary record
  217. TFontInfo = record
  218. fontFormat : TFontFormat;
  219. family : TFontFamily;
  220. familyNames : ArrayOfUtf8String;
  221. faceName : Utf8String;
  222. fullFaceName : Utf8String;
  223. style : Utf8String;
  224. copyright : Utf8String;
  225. manufacturer : Utf8String;
  226. dateCreated : TDatetime;
  227. dateModified : TDatetime;
  228. macStyles : TMacStyles;
  229. glyphCount : integer;
  230. unitsPerEm : integer;
  231. xMin : integer;
  232. yMin : integer;
  233. xMax : integer;
  234. yMax : integer;
  235. ascent : integer;
  236. descent : integer;
  237. lineGap : integer;
  238. advWidthMax : integer;
  239. minLSB : integer;
  240. minRSB : integer;
  241. xMaxExtent : integer;
  242. end;
  243. TKern = record
  244. rightGlyphIdx : integer;
  245. kernValue : integer;
  246. end;
  247. TArrayOfTKern = array of TKern;
  248. ///////////////////////////////////////////
  249. // the following point structures are only
  250. // used internally by the TFontReader class
  251. TPointEx = record
  252. pt: TPointD;
  253. flag: byte;
  254. end;
  255. TPathEx = array of TPointEx;
  256. TPathsEx = array of TPathEx;
  257. ///////////////////////////////////////////
  258. PGlyphInfo = ^TGlyphInfo;
  259. // TGlyphInfo: another custom record
  260. TGlyphInfo = record
  261. codepoint : integer;
  262. glyphIdx : WORD;
  263. unitsPerEm : integer;
  264. glyf : TFontTable_Glyf;
  265. hmtx : TFontTable_Hmtx;
  266. kernList : TArrayOfTKern;
  267. paths : TPathsD;
  268. end;
  269. TFontTableArray = array of TFontTable;
  270. TArrayOfWord = array of WORD;
  271. TArrayOfCardinal = array of Cardinal;
  272. TArrayOfCmapTblRec = array of TCmapTblRec;
  273. TTableName = (tblName, tblHead, tblHhea,
  274. tblCmap, tblMaxp, tblLoca, tblGlyf,
  275. tblHmtx, tblKern, tblPost);
  276. {$IFDEF ZEROBASEDSTR}
  277. {$ZEROBASEDSTRINGS OFF}
  278. {$ENDIF}
  279. TLoadFontResult = (lfrSuccess, lfrDuplicate, lfrInvalid);
  280. TFontManager = class
  281. private
  282. fMaxFonts: integer;
  283. {$IFDEF XPLAT_GENERICS}
  284. fFontList: TList<TFontReader>;
  285. {$ELSE}
  286. fFontList: TList;
  287. {$ENDIF}
  288. procedure SetMaxFonts(value: integer);
  289. procedure SortFontListOnLastUse;
  290. procedure DeleteOldestFont;
  291. function ValidateFontLoad(var fr: TFontReader): TLoadFontResult;
  292. function FindDuplicate(fr: TFontReader): integer;
  293. public
  294. constructor Create;
  295. destructor Destroy; override;
  296. procedure Clear;
  297. {$IFDEF MSWINDOWS}
  298. // LoadFontReaderFamily: call will fail if the fonts have already been
  299. // loaded, or if the font family hasn't been installed in the PC.
  300. function LoadFontReaderFamily(const fontFamily: string): TLoadFontResult; overload;
  301. function LoadFontReaderFamily(const fontFamily: string;
  302. out fontReaderFamily: TFontReaderFamily): TLoadFontResult; overload;
  303. function LoadFontReader(const fontName: string): TFontReader;
  304. {$ENDIF}
  305. function LoadFromStream(stream: TStream): TFontReader;
  306. function LoadFromResource(const resName: string; resType: PChar): TFontReader;
  307. function LoadFromFile(const filename: string): TFontReader;
  308. function GetBestMatchFont(const fontInfo: TFontInfo): TFontReader; overload;
  309. function GetBestMatchFont(const styles: TMacStyles): TFontReader; overload;
  310. // FindReaderContainingGlyph: returns a TFontReader object containing the
  311. // specified glyph, otherwise nil. If a fontfamily is spedified, then the
  312. // search is limited to within that font family. If a TFontReader is found
  313. // then the out 'glyphIdx' parameter contains the index to the glyph
  314. // matching the supplied codepoint.
  315. function FindReaderContainingGlyph(codepoint: Cardinal;
  316. fntFamily: TFontFamily; out glyphIdx: WORD): TFontReader;
  317. function Delete(fontReader: TFontReader): Boolean;
  318. property MaxFonts: integer read fMaxFonts write SetMaxFonts;
  319. end;
  320. TFontReader = class(TInterfacedObj, INotifySender)
  321. private
  322. fFontManager : TFontManager;
  323. fDestroying : Boolean;
  324. fUpdateCount : integer;
  325. fRecipientList : TRecipients;
  326. fLastUsedTime : TDateTime;
  327. fStream : TMemoryStream;
  328. fFontWeight : integer;
  329. fFontInfo : TFontInfo;
  330. fTables : TFontTableArray;
  331. fTblIdxes : array[TTableName] of integer;
  332. fTbl_name : TFontTable_Name;
  333. fTbl_head : TFontTable_Head;
  334. fTbl_hhea : TFontTable_Hhea;
  335. fTbl_cmap : TFontTable_Cmap;
  336. fTbl_maxp : TFontTable_Maxp;
  337. fTbl_post : TFontTable_Post;
  338. fTbl_loca2 : TArrayOfWord;
  339. fTbl_loca4 : TArrayOfCardinal;
  340. fKernTable : TArrayOfKernRecs;
  341. fFormat0CodeMap : array of byte;
  342. fFormat4CodeMap : array of TFormat4Rec;
  343. fFormat12CodeMap : array of TFormat12Group;
  344. fFormat4Offset : integer;
  345. function GetTables: Boolean;
  346. function GetTable_name: Boolean;
  347. function GetTable_cmap: Boolean;
  348. function GetTable_maxp: Boolean;
  349. function GetTable_head: Boolean;
  350. function GetTable_loca: Boolean;
  351. function IsValidFontTable(const tbl : TFontTable): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
  352. function GetTable_hhea: Boolean;
  353. procedure GetTable_kern;
  354. procedure GetTable_post;
  355. procedure GetFontFamily;
  356. function GetGlyphPaths(glyphIdx: WORD;
  357. var tbl_hmtx: TFontTable_Hmtx; out tbl_glyf: TFontTable_Glyf): TPathsEx;
  358. function GetGlyphIdxUsingCmap(codePoint: Cardinal): WORD;
  359. function GetSimpleGlyph(tbl_glyf: TFontTable_Glyf): TPathsEx;
  360. function GetCompositeGlyph(var tbl_glyf: TFontTable_Glyf;
  361. var tbl_hmtx: TFontTable_Hmtx): TPathsEx;
  362. function ConvertSplinesToBeziers(const pathsEx: TPathsEx): TPathsEx;
  363. procedure GetPathCoords(var paths: TPathsEx);
  364. function GetGlyphHorzMetrics(glyphIdx: WORD): TFontTable_Hmtx;
  365. function GetFontInfo: TFontInfo;
  366. function GetGlyphKernList(glyphIdx: WORD): TArrayOfTKern;
  367. function GetGlyphInfoInternal(glyphIdx: WORD): TGlyphInfo;
  368. function GetWeight: integer;
  369. procedure BeginUpdate;
  370. procedure EndUpdate;
  371. procedure NotifyRecipients(notifyFlag: TImg32Notification);
  372. protected
  373. property LastUsedTime: TDatetime read fLastUsedTime write fLastUsedTime;
  374. property PostTable: TFontTable_Post read fTbl_post;
  375. public
  376. constructor Create; overload;
  377. constructor CreateFromResource(const resName: string; resType: PChar);
  378. {$IFDEF MSWINDOWS}
  379. constructor Create(const fontname: string); overload;
  380. {$ENDIF}
  381. destructor Destroy; override;
  382. procedure Clear;
  383. procedure AddRecipient(recipient: INotifyRecipient);
  384. procedure DeleteRecipient(recipient: INotifyRecipient);
  385. function IsValidFontFormat: Boolean;
  386. function HasGlyph(codepoint: Cardinal): Boolean;
  387. function LoadFromStream(stream: TStream): Boolean;
  388. function LoadFromResource(const resName: string; resType: PChar): Boolean;
  389. function LoadFromFile(const filename: string): Boolean;
  390. {$IFDEF MSWINDOWS}
  391. function Load(const FontName: string): Boolean; overload;
  392. function Load(const logFont: TLogFont): Boolean; overload;
  393. function LoadUsingFontHdl(hdl: HFont): Boolean;
  394. {$ENDIF}
  395. function GetGlyphInfo(codepoint: Cardinal;
  396. out nextX: integer; out glyphInfo: TGlyphInfo): Boolean;
  397. property FontFamily: TFontFamily read fFontInfo.family;
  398. property FontInfo: TFontInfo read GetFontInfo;
  399. property Weight: integer read GetWeight; // range 100-900
  400. end;
  401. TPageTextMetrics = record
  402. bounds : TRect;
  403. lineCount : integer;
  404. lineHeight : double;
  405. topLinePxOffset : integer;
  406. nextChuckIdx : integer;
  407. startOfLineIdx : TArrayOfInteger;
  408. justifyDeltas : TArrayOfDouble;
  409. lineWidths : TArrayOfDouble;
  410. end;
  411. TFontCache = class;
  412. TChunkedText = class;
  413. TTextChunk = class
  414. public
  415. owner : TChunkedText;
  416. index : integer;
  417. text : UnicodeString;
  418. left : double;
  419. top : double;
  420. width : double;
  421. height : double;
  422. backColor : TColor32;
  423. fontColor : TColor32;
  424. ascent : double;
  425. userData : Pointer;
  426. glyphOffsets : TArrayOfDouble;
  427. arrayOfPaths : TArrayOfPathsD;
  428. constructor Create(owner: TChunkedText; const chunk: UnicodeString;
  429. index: integer; fontCache: TFontCache; fontColor: TColor32;
  430. backColor: TColor32 = clNone32);
  431. end;
  432. TDrawChunkEvent = procedure(chunk: TTextChunk; const chunkRec: TRectD) of object;
  433. // TChunkedText: A font formatted list of text 'chunks' (usually space
  434. // seperated words) that will greatly speed up displaying word-wrapped text.
  435. TChunkedText = class
  436. private
  437. fSpaceWidth : double;
  438. fLastFont : TFontCache;
  439. {$IFDEF XPLAT_GENERICS}
  440. fList : TList<TTextChunk>;
  441. {$ELSE}
  442. fList : TList;
  443. {$ENDIF}
  444. fDrawChunkEvent: TDrawChunkEvent;
  445. function GetChunk(index: integer): TTextChunk;
  446. function GetText: UnicodeString;
  447. function GetCount: integer;
  448. protected
  449. function GetGlyphsOrDrawInternal(image: TImage32; const rec: TRect;
  450. textAlign: TTextAlign; textAlignV: TTextVAlign; startChunk: integer;
  451. lineHeight: double; out paths: TPathsD): TPageTextMetrics;
  452. public
  453. constructor Create; overload;
  454. constructor Create(const text: string; font: TFontCache;
  455. fontColor: TColor32 = clBlack32; backColor: TColor32 = clNone32); overload;
  456. destructor Destroy; override;
  457. procedure Clear;
  458. procedure DeleteChunk(Index: Integer);
  459. procedure DeleteChunkRange(startIdx, endIdx: Integer);
  460. procedure AddNewline(font: TFontCache);
  461. procedure AddSpace(font: TFontCache); overload;
  462. function GetPageMetrics(const rec: TRect; lineHeight: double;
  463. startingChunkIdx: integer): TPageTextMetrics;
  464. function GetChunkAndGlyphOffsetAtPt(const ptm: TPageTextMetrics;
  465. const pt: TPoint; out glyphIdx, chunkChrOff: integer): Boolean;
  466. function InsertTextChunk(font: TFontCache; index: integer;
  467. const chunk: UnicodeString; fontColor: TColor32 = clBlack32;
  468. backColor: TColor32 = clNone32): TTextChunk;
  469. function AddTextChunk(font: TFontCache; const chunk: UnicodeString;
  470. fontColor: TColor32 = clBlack32;
  471. backColor: TColor32 = clNone32): TTextChunk;
  472. procedure SetText(const text: UnicodeString; font: TFontCache;
  473. fontColor: TColor32 = clBlack32; backColor: TColor32 = clNone32);
  474. // DrawText: see Examples/FMX2, Examples/Text & Examples/Experimental apps.
  475. function DrawText(image: TImage32; const rec: TRect;
  476. textAlign: TTextAlign; textAlignV: TTextVAlign;
  477. startChunk: integer; lineHeight: double = 0.0): TPageTextMetrics;
  478. function GetTextGlyphs(const rec: TRect;
  479. textAlign: TTextAlign; textAlignV: TTextVAlign; startChunk: integer;
  480. lineHeight: double = 0.0): TPathsD;
  481. procedure ApplyNewFont(font: TFontCache);
  482. property Count: integer read GetCount;
  483. property Chunk[index: integer]: TTextChunk read GetChunk; default;
  484. property Text: UnicodeString read GetText;
  485. property OnDrawChunk: TDrawChunkEvent
  486. read fDrawChunkEvent write fDrawChunkEvent;
  487. end;
  488. // TFontCache: speeds up text rendering by parsing font files only once
  489. // for each accessed character. It can also scale glyphs to a specified
  490. // font height and invert glyphs too (which is necessary on Windows PCs).
  491. TFontCache = class(TInterfacedObj, INotifySender, INotifyRecipient)
  492. private
  493. {$IFDEF XPLAT_GENERICS}
  494. fGlyphInfoList : TList<PGlyphInfo>;
  495. {$ELSE}
  496. fGlyphInfoList : TList;
  497. {$ENDIF}
  498. fFontReader : TFontReader;
  499. fRecipientList : TRecipients;
  500. fSorted : Boolean;
  501. fScale : double;
  502. fUseKerning : Boolean;
  503. fFontHeight : double;
  504. fFlipVert : Boolean;
  505. fUnderlined : Boolean;
  506. fStrikeOut : Boolean;
  507. procedure NotifyRecipients(notifyFlag: TImg32Notification);
  508. function FoundInList(charOrdinal: Cardinal): Boolean;
  509. function AddGlyph(codepoint: Cardinal): PGlyphInfo;
  510. procedure VerticalFlip(var paths: TPathsD);
  511. procedure SetFlipVert(value: Boolean);
  512. procedure SetFontHeight(newHeight: double);
  513. procedure SetFontReader(newFontReader: TFontReader);
  514. procedure UpdateScale;
  515. procedure Sort;
  516. procedure GetMissingGlyphs(const ordinals: TArrayOfCardinal);
  517. function IsValidFont: Boolean;
  518. function GetAscent: double;
  519. function GetDescent: double;
  520. function GetGap: double;
  521. function GetLineHeight: double;
  522. function GetYyHeight: double;
  523. function GetTextOutlineInternal(x, y: double; const text: UnicodeString;
  524. underlineIdx: integer; out glyphs: TArrayOfPathsD;
  525. out offsets: TArrayOfDouble; out nextX: double): Boolean; overload;
  526. procedure UpdateFontReaderLastUsedTime;
  527. public
  528. constructor Create(fontReader: TFontReader = nil; fontHeight: double = 10); overload;
  529. destructor Destroy; override;
  530. procedure Clear;
  531. // TFontCache is both an INotifySender and an INotifyRecipient.
  532. // It receives notifications from a TFontReader object and it sends
  533. // notificiations to any number of TFontCache object users
  534. procedure ReceiveNotification(Sender: TObject; notify: TImg32Notification);
  535. procedure AddRecipient(recipient: INotifyRecipient);
  536. procedure DeleteRecipient(recipient: INotifyRecipient);
  537. function GetGlyphInfo(codepoint: Cardinal): PGlyphInfo;
  538. function GetTextOutline(x, y: double; const text: UnicodeString): TPathsD; overload;
  539. function GetTextOutline(const rec: TRectD; const text: UnicodeString;
  540. ta: TTextAlign; tav: TTextVAlign; underlineIdx: integer = 0): TPathsD; overload;
  541. function GetTextOutline(x, y: double; const text: UnicodeString;
  542. out nextX: double; underlineIdx: integer = 0): TPathsD; overload;
  543. // GetUnderlineOutline - another way to underline text. 'y' indicates the
  544. // text baseline, and 'dy' is the offset from that baseline.
  545. // if dy = InvalidD then the default offset is used (& based on linewidth).
  546. function GetUnderlineOutline(leftX, rightX, y: double; dy: double = invalidD;
  547. wavy: Boolean = false; strokeWidth: double = 0): TPathD;
  548. function GetVerticalTextOutline(x, y: double;
  549. const text: UnicodeString; lineHeight: double = 0.0): TPathsD;
  550. function GetAngledTextGlyphs(x, y: double; const text: UnicodeString;
  551. angleRadians: double; const rotatePt: TPointD;
  552. out nextPt: TPointD): TPathsD;
  553. // GetGlyphOffsets - there isn't always a one-to-one relationship between
  554. // text characters and glyphs since text can on occasions contain
  555. // "surrogate paired" characters (eg emoji characters).
  556. function GetGlyphOffsets(const text: UnicodeString;
  557. interCharSpace: double = 0): TArrayOfDouble;
  558. // As per the comment above, there isn't always a one-to-one relationship
  559. // between text characters and their codepoints (2 byte chars vs 4 bytes)
  560. function GetTextCodePoints(const text: UnicodeString): TArrayOfCardinal;
  561. function GetTextWidth(const text: UnicodeString): double;
  562. function CountCharsThatFit(const text: UnicodeString; maxWidth: double): integer;
  563. function GetSpaceWidth: double;
  564. property Ascent : double read GetAscent;
  565. property Descent : double read GetDescent;
  566. property LineGap : double read GetGap;
  567. property FontHeight : double read fFontHeight write SetFontHeight;
  568. property FontReader : TFontReader read fFontReader write SetFontReader;
  569. property InvertY : boolean read fFlipVert write SetFlipVert;
  570. property Kerning : boolean read fUseKerning write fUseKerning;
  571. property LineHeight : double read GetLineHeight;
  572. property YyHeight : double read GetYyHeight;
  573. property Scale : double read fScale;
  574. property Underlined : Boolean read fUnderlined write fUnderlined;
  575. property StrikeOut : Boolean read fStrikeOut write fStrikeOut;
  576. end;
  577. function DrawText(image: TImage32; x, y: double;
  578. const text: UnicodeString; font: TFontCache;
  579. textColor: TColor32 = clBlack32): double; overload;
  580. procedure DrawText(image: TImage32; const rec: TRectD;
  581. const text: UnicodeString; font: TFontCache;
  582. textColor: TColor32 = clBlack32; align: TTextAlign = taCenter;
  583. valign: TTextVAlign = tvaMiddle); overload;
  584. function DrawText(image: TImage32; x, y: double;
  585. const text: UnicodeString; font: TFontCache;
  586. renderer: TCustomRenderer): double; overload;
  587. function DrawAngledText(image: TImage32;
  588. x, y: double; angleRadians: double;
  589. const text: UnicodeString; font: TFontCache;
  590. textColor: TColor32 = clBlack32): TPointD;
  591. procedure DrawVerticalText(image: TImage32;
  592. x, y: double; const text: UnicodeString; font: TFontCache;
  593. lineHeight: double = 0.0; textColor: TColor32 = clBlack32);
  594. function GetTextOutlineOnPath(const text: UnicodeString;
  595. const path: TPathD; font: TFontCache; textAlign: TTextAlign;
  596. x, y: double; charSpacing: double;
  597. out charsThatFit: integer; out outX: double): TPathsD; overload;
  598. function GetTextOutlineOnPath(const text: UnicodeString;
  599. const path: TPathD; font: TFontCache; textAlign: TTextAlign;
  600. perpendicOffset: integer = 0; charSpacing: double = 0): TPathsD; overload;
  601. function GetTextOutlineOnPath(const text: UnicodeString;
  602. const path: TPathD; font: TFontCache; textAlign: TTextAlign;
  603. perpendicOffset: integer; charSpacing: double;
  604. out charsThatFit: integer): TPathsD; overload;
  605. function GetTextOutlineOnPath(const text: UnicodeString;
  606. const path: TPathD; font: TFontCache; x, y: integer;
  607. charSpacing: double; out outX: double): TPathsD; overload;
  608. {$IFDEF MSWINDOWS}
  609. procedure FontHeightToFontSize(var logFontHeight: integer);
  610. procedure FontSizeToFontHeight(var logFontHeight: integer);
  611. function GetFontPixelHeight(logFontHeight: integer): double;
  612. function GetFontFolder: string;
  613. function GetInstalledTtfFilenames: TArrayOfString;
  614. // GetLogFonts: using DEFAULT_CHARSET will get logfonts
  615. // for ALL charsets that match the specified faceName.
  616. function GetLogFonts(const faceName: string;
  617. charSet: byte = DEFAULT_CHARSET): TArrayOfEnumLogFontEx;
  618. // GetLogFontFromEnumThatMatchesStyles:
  619. // will return false when no style match is found
  620. function GetLogFontFromEnumThatMatchesStyles(LogFonts: TArrayOfEnumLogFontEx;
  621. styles: TMacStyles; out logFont: TLogFont): Boolean;
  622. {$ENDIF}
  623. function FontManager: TFontManager;
  624. implementation
  625. uses
  626. Img32.Transform;
  627. resourcestring
  628. rsChunkedTextRangeError =
  629. 'TChunkedText: range error.';
  630. rsFontCacheError =
  631. 'TFontCache error: notification received from the wrong TFontReader';
  632. rsChunkedTextFontError =
  633. 'TChunkedText: invalid font error.';
  634. var
  635. aFontManager: TFontManager;
  636. const
  637. lineFrac = 0.05;
  638. SPACE = ' ';
  639. //------------------------------------------------------------------------------
  640. // Miscellaneous functions
  641. //------------------------------------------------------------------------------
  642. // GetMeaningfulDateTime: returns UTC date & time
  643. procedure GetMeaningfulDateTime(const secsSince1904: Uint64;
  644. out yy,mo,dd, hh,mi,ss: cardinal);
  645. const
  646. dayInYrAtMthStart: array[boolean, 0..12] of cardinal =
  647. ((0,31,59,90,120,151,181,212,243,273,304,334,365), // non-leap year
  648. (0,31,60,91,121,152,182,213,244,274,305,335,366)); // leap year
  649. var
  650. isLeapYr: Boolean;
  651. const
  652. maxValidYear = 2100;
  653. secsPerHour = 3600;
  654. secsPerDay = 86400;
  655. secsPerNormYr = 31536000;
  656. secsPerLeapYr = secsPerNormYr + secsPerDay;
  657. secsPer4Years = secsPerNormYr * 3 + secsPerLeapYr; // 126230400;
  658. begin
  659. // Leap years are divisble by 4, except for centuries which are not
  660. // leap years unless they are divisble by 400. (Hence 2000 was a leap year,
  661. // but 1900 was not. But 1904 was a leap year because it's divisble by 4.)
  662. // Validate at http://www.mathcats.com/explore/elapsedtime.html
  663. ss := (secsSince1904 div secsPer4Years); // count '4years' since 1904
  664. // manage invalid dates
  665. if (secsSince1904 = 0) or
  666. (ss > (maxValidYear-1904) div 4) then
  667. begin
  668. yy := 1904; mo := 1; dd := 1;
  669. hh := 0; mi := 0; ss := 0;
  670. Exit;
  671. end;
  672. yy := 1904 + (ss * 4);
  673. ss := secsSince1904 mod secsPer4Years; // secs since last leap yr
  674. isLeapYr := ss < secsPerLeapYr;
  675. if not isLeapYr then
  676. begin
  677. dec(ss, secsPerLeapYr);
  678. yy := yy + (ss div secsPerNormYr) + 1;
  679. ss := ss mod secsPerNormYr; // remaining secs in final year
  680. end;
  681. dd := 1 + ss div secsPerDay; // day number in final year
  682. mo := 1; // 1, because mo is base 1
  683. while dayInYrAtMthStart[isLeapYr, mo] < dd do inc(mo);
  684. // remaining secs in month
  685. ss := ss - (dayInYrAtMthStart[isLeapYr, mo -1] * secsPerDay);
  686. dd := 1 + (ss div secsPerDay); // because dd is base 1 too
  687. ss := ss mod secsPerDay;
  688. hh := ss div secsPerHour;
  689. ss := ss mod secsPerHour;
  690. mi := ss div 60;
  691. ss := ss mod 60;
  692. end;
  693. //------------------------------------------------------------------------------
  694. function MergeArrayOfPaths(const pa: TArrayOfPathsD): TPathsD;
  695. var
  696. i, j: integer;
  697. resultCount: integer;
  698. begin
  699. Result := nil;
  700. // Preallocate the Result-Array
  701. resultCount := 0;
  702. for i := 0 to High(pa) do
  703. inc(resultCount, Length(pa[i]));
  704. SetLength(Result, resultCount);
  705. resultCount := 0;
  706. for i := 0 to High(pa) do
  707. begin
  708. for j := 0 to High(pa[i]) do
  709. begin
  710. Result[resultCount] := pa[i][j];
  711. inc(resultCount);
  712. end;
  713. end;
  714. end;
  715. //------------------------------------------------------------------------------
  716. // MergeArrayOfPathsEx - merges AND translates/offsets paths
  717. function MergeArrayOfPathsEx(const pa: TArrayOfPathsD; dx, dy: double): TPathsD;
  718. var
  719. i, j: integer;
  720. resultCount: integer;
  721. begin
  722. Result := nil;
  723. // Preallocate the Result-Array
  724. resultCount := 0;
  725. for i := 0 to High(pa) do
  726. inc(resultCount, Length(pa[i]));
  727. SetLength(Result, resultCount);
  728. resultCount := 0;
  729. for i := 0 to High(pa) do
  730. begin
  731. for j := 0 to High(pa[i]) do
  732. begin
  733. Result[resultCount] := TranslatePath(pa[i][j], dx, dy);
  734. inc(resultCount);
  735. end;
  736. end;
  737. end;
  738. //------------------------------------------------------------------------------
  739. function WordSwap(val: WORD): WORD;
  740. {$IFDEF ASM_X86}
  741. asm
  742. rol ax,8;
  743. end;
  744. {$ELSE}
  745. var
  746. v: array[0..1] of byte absolute val;
  747. r: array[0..1] of byte absolute result;
  748. begin
  749. r[0] := v[1];
  750. r[1] := v[0];
  751. end;
  752. {$ENDIF}
  753. //------------------------------------------------------------------------------
  754. function Int16Swap(val: Int16): Int16;
  755. {$IFDEF ASM_X86}
  756. asm
  757. rol ax,8;
  758. end;
  759. {$ELSE}
  760. var
  761. v: array[0..1] of byte absolute val;
  762. r: array[0..1] of byte absolute result;
  763. begin
  764. r[0] := v[1];
  765. r[1] := v[0];
  766. end;
  767. {$ENDIF}
  768. //------------------------------------------------------------------------------
  769. function Int32Swap(val: integer): integer;
  770. {$IFDEF ASM_X86}
  771. asm
  772. bswap eax
  773. end;
  774. {$ELSE}
  775. var
  776. i: integer;
  777. v: array[0..3] of byte absolute val;
  778. r: array[0..3] of byte absolute Result; // warning: do not inline
  779. begin
  780. for i := 0 to 3 do r[3-i] := v[i];
  781. end;
  782. {$ENDIF}
  783. //------------------------------------------------------------------------------
  784. function UInt64Swap(val: UInt64): UInt64;
  785. {$IFDEF ASM_X86}
  786. asm
  787. MOV EDX, val.Int64Rec.Lo
  788. BSWAP EDX
  789. MOV EAX, val.Int64Rec.Hi
  790. BSWAP EAX
  791. end;
  792. {$ELSE}
  793. var
  794. i: integer;
  795. v: array[0..7] of byte absolute val;
  796. r: array[0..7] of byte absolute Result;
  797. begin
  798. for i := 0 to 7 do r[7-i] := v[i];
  799. end;
  800. {$ENDIF}
  801. //------------------------------------------------------------------------------
  802. procedure GetByte(stream: TStream; out value: byte);
  803. {$IFDEF INLINE} inline; {$ENDIF}
  804. begin
  805. stream.Read(value, 1);
  806. end;
  807. //------------------------------------------------------------------------------
  808. procedure GetShortInt(stream: TStream; out value: ShortInt);
  809. {$IFDEF INLINE} inline; {$ENDIF}
  810. begin
  811. stream.Read(value, 1);
  812. end;
  813. //------------------------------------------------------------------------------
  814. function GetWord(stream: TStream; out value: WORD): Boolean;
  815. {$IFDEF INLINE} inline; {$ENDIF}
  816. begin
  817. result := stream.Position + SizeOf(value) < stream.Size;
  818. if not Result then Exit;
  819. stream.Read(value, SizeOf(value));
  820. value := WordSwap(value);
  821. end;
  822. //------------------------------------------------------------------------------
  823. function GetInt16(stream: TStream; out value: Int16): Boolean;
  824. {$IFDEF INLINE} inline; {$ENDIF}
  825. begin
  826. result := stream.Position + SizeOf(value) < stream.Size;
  827. if not Result then Exit;
  828. stream.Read(value, SizeOf(value));
  829. value := Int16Swap(value);
  830. end;
  831. //------------------------------------------------------------------------------
  832. function GetCardinal(stream: TStream; out value: Cardinal): Boolean;
  833. {$IFDEF INLINE} inline; {$ENDIF}
  834. begin
  835. result := stream.Position + SizeOf(value) < stream.Size;
  836. if not Result then Exit;
  837. stream.Read(value, SizeOf(value));
  838. value := Cardinal(Int32Swap(Integer(value)));
  839. end;
  840. //------------------------------------------------------------------------------
  841. function GetInt(stream: TStream; out value: integer): Boolean;
  842. {$IFDEF INLINE} inline; {$ENDIF}
  843. begin
  844. result := stream.Position + SizeOf(value) < stream.Size;
  845. if not Result then Exit;
  846. stream.Read(value, SizeOf(value));
  847. value := Int32Swap(value);
  848. end;
  849. //------------------------------------------------------------------------------
  850. function GetUInt64(stream: TStream; out value: UInt64): Boolean;
  851. {$IFDEF INLINE} inline; {$ENDIF}
  852. begin
  853. result := stream.Position + SizeOf(value) < stream.Size;
  854. if not Result then Exit;
  855. stream.Read(value, SizeOf(value));
  856. value := UInt64Swap(value);
  857. end;
  858. //------------------------------------------------------------------------------
  859. function Get2Dot14(stream: TStream; out value: single): Boolean;
  860. var
  861. val: Int16;
  862. begin
  863. result := GetInt16(stream, val);
  864. if result then value := val * 6.103515625e-5; // 16384;
  865. end;
  866. //------------------------------------------------------------------------------
  867. function GetFixed(stream: TStream; out value: TFixed): Boolean;
  868. var
  869. val: integer;
  870. begin
  871. result := GetInt(stream, val);
  872. value := val * 1.52587890625e-5; // 1/35536
  873. end;
  874. //------------------------------------------------------------------------------
  875. function GetWideString(stream: TStream; len: integer): Utf8String;
  876. var
  877. i: integer;
  878. w: WORD;
  879. s: UnicodeString;
  880. begin
  881. len := len div 2;
  882. setLength(s, len);
  883. for i := 1 to len do
  884. begin
  885. GetWord(stream, w);
  886. if w = 0 then
  887. begin
  888. SetLength(s, i -1);
  889. break;
  890. end;
  891. s[i] := WideChar(w);
  892. end;
  893. Result := Utf8String(s);
  894. end;
  895. //------------------------------------------------------------------------------
  896. function GetUtf8String(stream: TStream; len: integer): Utf8String;
  897. var
  898. i: integer;
  899. begin
  900. setLength(Result, len+1);
  901. Result[len+1] := #0;
  902. stream.Read(Result[1], len);
  903. for i := 1 to length(Result) do
  904. if Result[i] = #0 then
  905. begin
  906. SetLength(Result, i -1);
  907. break;
  908. end;
  909. end;
  910. //------------------------------------------------------------------------------
  911. function SameText(const text1, text2: Utf8String): Boolean; overload;
  912. var
  913. len: integer;
  914. begin
  915. len := Length(text1);
  916. Result := (Length(text2) = len) and
  917. ((len = 0) or CompareMem(@text1[1], @text2[1], len));
  918. end;
  919. //------------------------------------------------------------------------------
  920. // TTrueTypeReader
  921. //------------------------------------------------------------------------------
  922. constructor TFontReader.Create;
  923. begin
  924. fStream := TMemoryStream.Create;
  925. end;
  926. //------------------------------------------------------------------------------
  927. constructor TFontReader.CreateFromResource(const resName: string; resType: PChar);
  928. begin
  929. Create;
  930. LoadFromResource(resName, resType);
  931. end;
  932. //------------------------------------------------------------------------------
  933. {$IFDEF MSWINDOWS}
  934. constructor TFontReader.Create(const fontname: string);
  935. begin
  936. Create;
  937. Load(fontname);
  938. end;
  939. //------------------------------------------------------------------------------
  940. {$ENDIF}
  941. destructor TFontReader.Destroy;
  942. begin
  943. Clear;
  944. NotifyRecipients(inDestroy);
  945. fStream.Free;
  946. if Assigned(fFontManager) then
  947. begin
  948. fDestroying := true;
  949. fFontManager.Delete(self);
  950. end;
  951. inherited;
  952. end;
  953. //------------------------------------------------------------------------------
  954. procedure TFontReader.Clear;
  955. begin
  956. fTables := nil;
  957. fFormat4CodeMap := nil;
  958. fFormat12CodeMap := nil;
  959. fKernTable := nil;
  960. FillChar(fTbl_post, SizeOf(fTbl_post), 0);
  961. fFontInfo.fontFormat := ffInvalid;
  962. fFontInfo.family := tfUnknown;
  963. fFontWeight := 0;
  964. fStream.Clear;
  965. NotifyRecipients(inStateChange);
  966. end;
  967. //------------------------------------------------------------------------------
  968. procedure TFontReader.BeginUpdate;
  969. begin
  970. inc(fUpdateCount);
  971. end;
  972. //------------------------------------------------------------------------------
  973. procedure TFontReader.EndUpdate;
  974. begin
  975. dec(fUpdateCount);
  976. if fUpdateCount = 0 then NotifyRecipients(inStateChange);
  977. end;
  978. //------------------------------------------------------------------------------
  979. procedure TFontReader.NotifyRecipients(notifyFlag: TImg32Notification);
  980. var
  981. i: integer;
  982. begin
  983. if fUpdateCount > 0 then Exit;
  984. for i := High(fRecipientList) downto 0 do
  985. try
  986. // try .. except block because when TFontReader is destroyed in a
  987. // finalization section, it's possible for recipients to have been
  988. // destroyed without calling their destructors.
  989. fRecipientList[i].ReceiveNotification(self, notifyFlag);
  990. except
  991. end;
  992. end;
  993. //------------------------------------------------------------------------------
  994. procedure TFontReader.AddRecipient(recipient: INotifyRecipient);
  995. var
  996. len: integer;
  997. begin
  998. len := Length(fRecipientList);
  999. SetLength(fRecipientList, len+1);
  1000. fRecipientList[len] := Recipient;
  1001. end;
  1002. //------------------------------------------------------------------------------
  1003. procedure TFontReader.DeleteRecipient(recipient: INotifyRecipient);
  1004. var
  1005. i, highI: integer;
  1006. begin
  1007. highI := High(fRecipientList);
  1008. i := highI;
  1009. while (i >= 0) and (fRecipientList[i] <> Recipient) do dec(i);
  1010. if i < 0 then Exit;
  1011. if i < highI then
  1012. Move(fRecipientList[i+1], fRecipientList[i],
  1013. (highI - i) * SizeOf(INotifyRecipient));
  1014. SetLength(fRecipientList, highI);
  1015. end;
  1016. //------------------------------------------------------------------------------
  1017. function TFontReader.IsValidFontFormat: Boolean;
  1018. begin
  1019. result := fFontInfo.fontFormat = ffTrueType;
  1020. end;
  1021. //------------------------------------------------------------------------------
  1022. function TFontReader.LoadFromStream(stream: TStream): Boolean;
  1023. begin
  1024. BeginUpdate;
  1025. try
  1026. Clear;
  1027. fStream.CopyFrom(stream, 0);
  1028. fStream.Position := 0;
  1029. result := GetTables;
  1030. if not result then Clear;
  1031. finally
  1032. EndUpdate;
  1033. end;
  1034. end;
  1035. //------------------------------------------------------------------------------
  1036. function TFontReader.LoadFromResource(const resName: string; resType: PChar): Boolean;
  1037. var
  1038. rs: TResourceStream;
  1039. begin
  1040. BeginUpdate;
  1041. rs := CreateResourceStream(resName, resType);
  1042. try
  1043. Result := assigned(rs) and LoadFromStream(rs);
  1044. finally
  1045. rs.free;
  1046. EndUpdate;
  1047. end;
  1048. end;
  1049. //------------------------------------------------------------------------------
  1050. function TFontReader.LoadFromFile(const filename: string): Boolean;
  1051. var
  1052. fs: TFileStream;
  1053. begin
  1054. try
  1055. fs := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
  1056. try
  1057. Result := LoadFromStream(fs);
  1058. finally
  1059. fs.free;
  1060. end;
  1061. except
  1062. Result := False;
  1063. end;
  1064. end;
  1065. //------------------------------------------------------------------------------
  1066. {$IFDEF MSWINDOWS}
  1067. function GetFontMemStreamFromFontHdl(hdl: HFont;
  1068. memStream: TMemoryStream): Boolean;
  1069. var
  1070. memDc: HDC;
  1071. cnt: DWORD;
  1072. begin
  1073. result := false;
  1074. if not Assigned(memStream) or (hdl = 0) then Exit;
  1075. memDc := CreateCompatibleDC(0);
  1076. try
  1077. if SelectObject(memDc, hdl) = 0 then Exit;
  1078. // get the required size of the font data (file)
  1079. cnt := Windows.GetFontData(memDc, 0, 0, nil, 0);
  1080. result := cnt <> $FFFFFFFF;
  1081. if not Result then Exit;
  1082. // copy the font data into the memory stream
  1083. memStream.SetSize(cnt);
  1084. Windows.GetFontData(memDc, 0, 0, memStream.Memory, cnt);
  1085. finally
  1086. DeleteDC(memDc);
  1087. end;
  1088. end;
  1089. //------------------------------------------------------------------------------
  1090. function TFontReader.LoadUsingFontHdl(hdl: HFont): Boolean;
  1091. var
  1092. ms: TMemoryStream;
  1093. begin
  1094. ms := TMemoryStream.Create;
  1095. try
  1096. Result := GetFontMemStreamFromFontHdl(hdl, ms) and
  1097. LoadFromStream(ms);
  1098. finally
  1099. ms.Free;
  1100. end;
  1101. end;
  1102. //------------------------------------------------------------------------------
  1103. function TFontReader.Load(const FontName: string): Boolean;
  1104. var
  1105. lf: TLogFont;
  1106. begin
  1107. Result := false;
  1108. if fontname = '' then Exit;
  1109. FillChar(lf, sizeof(TLogFont), 0);
  1110. lf.lfCharSet := DEFAULT_CHARSET;
  1111. Move(fontname[1], lf.lfFaceName[0], Length(fontname) * SizeOf(Char));
  1112. Result := Load(lf);
  1113. end;
  1114. //------------------------------------------------------------------------------
  1115. function TFontReader.Load(const logFont: TLogFont): Boolean;
  1116. var
  1117. hdl: HFont;
  1118. begin
  1119. Result := false;
  1120. hdl := CreateFontIndirect({$IFDEF FPC}@{$ENDIF}logfont);
  1121. if hdl > 0 then
  1122. try
  1123. Result := LoadUsingFontHdl(hdl);
  1124. finally
  1125. DeleteObject(hdl);
  1126. end;
  1127. end;
  1128. //------------------------------------------------------------------------------
  1129. {$ENDIF}
  1130. function GetHeaderTable(stream: TStream;
  1131. out headerTable: TFontHeaderTable): Boolean;
  1132. begin
  1133. result := stream.Position < stream.Size - SizeOf(TFontHeaderTable);
  1134. if not result then Exit;
  1135. GetCardinal(stream, headerTable.sfntVersion);
  1136. GetWord(stream, headerTable.numTables);
  1137. GetWord(stream, headerTable.searchRange);
  1138. GetWord(stream, headerTable.entrySelector);
  1139. GetWord(stream, headerTable.rangeShift);
  1140. end;
  1141. //------------------------------------------------------------------------------
  1142. function TFontReader.IsValidFontTable(const tbl : TFontTable): Boolean;
  1143. begin
  1144. Result := (fStream.Size >= tbl.offset + tbl.length);
  1145. end;
  1146. //------------------------------------------------------------------------------
  1147. function TFontReader.GetTables: Boolean;
  1148. var
  1149. i, tblCount: integer;
  1150. tbl: TTableName;
  1151. headerTable: TFontHeaderTable;
  1152. begin
  1153. result := false;
  1154. if not GetHeaderTable(fStream, headerTable) then Exit;
  1155. tblCount := headerTable.numTables;
  1156. result := fStream.Position < fStream.Size - SizeOf(TFontTable) * tblCount;
  1157. if not result then Exit;
  1158. for tbl := low(TTableName) to High(TTableName) do fTblIdxes[tbl] := -1;
  1159. SetLength(fTables, tblCount);
  1160. for i := 0 to tblCount -1 do
  1161. begin
  1162. GetCardinal(fStream, fTables[i].tag);
  1163. GetCardinal(fStream, fTables[i].checkSum);
  1164. GetCardinal(fStream, fTables[i].offset);
  1165. GetCardinal(fStream, fTables[i].length);
  1166. case
  1167. fTables[i].tag of
  1168. $6E616D65: fTblIdxes[tblName] := i;
  1169. $68656164: fTblIdxes[tblHead] := i;
  1170. $676C7966: fTblIdxes[tblGlyf] := i;
  1171. $6C6F6361: fTblIdxes[tblLoca] := i;
  1172. $6D617870: fTblIdxes[tblMaxp] := i;
  1173. $636D6170: fTblIdxes[tblCmap] := i;
  1174. $68686561: fTblIdxes[tblHhea] := i;
  1175. $686D7478: fTblIdxes[tblHmtx] := i;
  1176. $6B65726E: fTblIdxes[tblKern] := i;
  1177. $706F7374: fTblIdxes[tblPost] := i;
  1178. end;
  1179. end;
  1180. if fTblIdxes[tblName] < 0 then fFontInfo.fontFormat := ffInvalid
  1181. else if fTblIdxes[tblGlyf] < 0 then fFontInfo.fontFormat := ffCompact
  1182. else fFontInfo.fontFormat := ffTrueType;
  1183. result := (fFontInfo.fontFormat = ffTrueType) and
  1184. (fTblIdxes[tblName] >= 0) and GetTable_name and
  1185. (fTblIdxes[tblHead] >= 0) and GetTable_head and
  1186. (fTblIdxes[tblHhea] >= 0) and GetTable_hhea and
  1187. (fTblIdxes[tblMaxp] >= 0) and GetTable_maxp and
  1188. (fTblIdxes[tblLoca] >= 0) and GetTable_loca and // loca must follow maxp
  1189. (fTblIdxes[tblCmap] >= 0) and GetTable_cmap and
  1190. (fTblIdxes[tblHmtx] >= 0) and IsValidFontTable(fTables[fTblIdxes[tblHmtx]]);
  1191. if not Result then Exit;
  1192. if (fTblIdxes[tblKern] >= 0) then GetTable_kern;
  1193. if (fTblIdxes[tblPost] >= 0) then GetTable_post;
  1194. GetFontFamily;
  1195. end;
  1196. //------------------------------------------------------------------------------
  1197. function TFontReader.GetTable_cmap: Boolean;
  1198. var
  1199. i,j : integer;
  1200. segCount : integer;
  1201. format : WORD;
  1202. reserved : WORD;
  1203. format4Rec : TCmapFormat4;
  1204. format12Rec : TCmapFormat12;
  1205. cmapTbl : TFontTable;
  1206. cmapTblRecs : array of TCmapTblRec;
  1207. label
  1208. format4Error;
  1209. begin
  1210. Result := false;
  1211. cmapTbl := fTables[fTblIdxes[tblCmap]];
  1212. if (fStream.Size < cmapTbl.offset + cmapTbl.length) then Exit;
  1213. fStream.Position := cmapTbl.offset;
  1214. GetWord(fStream, fTbl_cmap.version);
  1215. GetWord(fStream, fTbl_cmap.numTables);
  1216. // only use the unicode table (0: always first)
  1217. SetLength(cmapTblRecs, fTbl_cmap.numTables);
  1218. for i := 0 to fTbl_cmap.numTables -1 do
  1219. begin
  1220. GetWord(fStream, cmapTblRecs[i].platformID);
  1221. GetWord(fStream, cmapTblRecs[i].encodingID);
  1222. GetCardinal(fStream, cmapTblRecs[i].offset);
  1223. end;
  1224. for i := 0 to fTbl_cmap.numTables -1 do
  1225. begin
  1226. with cmapTblRecs[i] do
  1227. if (platformID = 0) or (platformID = 3) then
  1228. fStream.Position := cmapTbl.offset + offset
  1229. else
  1230. Continue;
  1231. GetWord(fStream, format);
  1232. case format of
  1233. 0:
  1234. begin
  1235. if Assigned(fFormat0CodeMap) then Continue;
  1236. GetWord(fStream, format4Rec.length);
  1237. GetWord(fStream, format4Rec.language);
  1238. SetLength(fFormat0CodeMap, 256);
  1239. for j := 0 to 255 do
  1240. GetByte(fStream, fFormat0CodeMap[j]);
  1241. fFontInfo.glyphCount := 255;
  1242. end;
  1243. 4: // USC-2
  1244. begin
  1245. if Assigned(fFormat4CodeMap) then Continue;
  1246. GetWord(fStream, format4Rec.length);
  1247. GetWord(fStream, format4Rec.language);
  1248. fFontInfo.glyphCount := 0;
  1249. GetWord(fStream, format4Rec.segCountX2);
  1250. segCount := format4Rec.segCountX2 shr 1;
  1251. GetWord(fStream, format4Rec.searchRange);
  1252. GetWord(fStream, format4Rec.entrySelector);
  1253. GetWord(fStream, format4Rec.rangeShift);
  1254. SetLength(fFormat4CodeMap, segCount);
  1255. for j := 0 to segCount -1 do
  1256. GetWord(fStream, fFormat4CodeMap[j].endCode);
  1257. if fFormat4CodeMap[segCount-1].endCode <> $FFFF then
  1258. GoTo format4Error;
  1259. GetWord(fStream, reserved);
  1260. if reserved <> 0 then
  1261. GoTo format4Error;
  1262. for j := 0 to segCount -1 do
  1263. GetWord(fStream, fFormat4CodeMap[j].startCode);
  1264. if fFormat4CodeMap[segCount-1].startCode <> $FFFF then
  1265. GoTo format4Error;
  1266. for j := 0 to segCount -1 do
  1267. GetWord(fStream, fFormat4CodeMap[j].idDelta);
  1268. fFormat4Offset := fStream.Position;
  1269. for j := 0 to segCount -1 do
  1270. GetWord(fStream, fFormat4CodeMap[j].rangeOffset);
  1271. if Assigned(fFormat12CodeMap) then Break
  1272. else Continue;
  1273. format4Error:
  1274. fFormat4CodeMap := nil;
  1275. end;
  1276. 12: // USC-4
  1277. begin
  1278. if Assigned(fFormat12CodeMap) then Continue;
  1279. GetWord(fStream, reserved);
  1280. GetCardinal(fStream, format12Rec.length);
  1281. GetCardinal(fStream, format12Rec.language);
  1282. GetCardinal(fStream, format12Rec.nGroups);
  1283. SetLength(fFormat12CodeMap, format12Rec.nGroups);
  1284. for j := 0 to format12Rec.nGroups -1 do
  1285. with fFormat12CodeMap[j] do
  1286. begin
  1287. GetCardinal(fStream, startCharCode);
  1288. GetCardinal(fStream, endCharCode);
  1289. GetCardinal(fStream, startGlyphCode);
  1290. end;
  1291. if Assigned(fFormat4CodeMap) then Break;
  1292. end;
  1293. end;
  1294. end;
  1295. Result := Assigned(fFormat4CodeMap) or Assigned(fFormat12CodeMap);
  1296. end;
  1297. //------------------------------------------------------------------------------
  1298. function TFontReader.GetGlyphIdxUsingCmap(codePoint: Cardinal): WORD;
  1299. var
  1300. i: integer;
  1301. w: WORD;
  1302. begin
  1303. result := 0; // default to the 'missing' glyph
  1304. if (codePoint < 256) and Assigned(fFormat0CodeMap) then
  1305. Result := fFormat0CodeMap[codePoint]
  1306. else if Assigned(fFormat12CodeMap) then
  1307. begin
  1308. for i := 0 to High(fFormat12CodeMap) do
  1309. with fFormat12CodeMap[i] do
  1310. if codePoint <= endCharCode then
  1311. begin
  1312. if codePoint < startCharCode then Break;
  1313. result := (startGlyphCode + WORD(codePoint - startCharCode));
  1314. Break;
  1315. end;
  1316. end
  1317. else if (codePoint < $FFFF) and Assigned(fFormat4CodeMap) then
  1318. begin
  1319. for i := 0 to High(fFormat4CodeMap) do
  1320. with fFormat4CodeMap[i] do
  1321. if codePoint <= endCode then
  1322. begin
  1323. if codePoint < startCode then Break;
  1324. if rangeOffset > 0 then
  1325. begin
  1326. fStream.Position := fFormat4Offset +
  1327. rangeOffset + 2 * (i + WORD(codePoint - startCode));
  1328. GetWord(fStream, w);
  1329. if w < fTbl_maxp.numGlyphs then Result := w;
  1330. end else
  1331. result := (idDelta + codePoint) and $FFFF;
  1332. Break;
  1333. end;
  1334. end;
  1335. end;
  1336. //------------------------------------------------------------------------------
  1337. function TFontReader.GetTable_maxp: Boolean;
  1338. var
  1339. maxpTbl: TFontTable;
  1340. begin
  1341. maxpTbl := fTables[fTblIdxes[tblMaxp]];
  1342. Result := (fStream.Size >= maxpTbl.offset + maxpTbl.length) and
  1343. (maxpTbl.length >= SizeOf(TFixed) + SizeOf(WORD));
  1344. if not Result then Exit;
  1345. fStream.Position := maxpTbl.offset;
  1346. GetFixed(fStream, fTbl_maxp.version);
  1347. GetWord(fStream, fTbl_maxp.numGlyphs);
  1348. if fTbl_maxp.version >= 1 then
  1349. begin
  1350. GetWord(fStream, fTbl_maxp.maxPoints);
  1351. GetWord(fStream, fTbl_maxp.maxContours);
  1352. fFontInfo.glyphCount := fTbl_maxp.numGlyphs;
  1353. end else
  1354. Result := false;
  1355. end;
  1356. //------------------------------------------------------------------------------
  1357. function TFontReader.GetTable_loca: Boolean;
  1358. var
  1359. i: integer;
  1360. locaTbl: TFontTable;
  1361. begin
  1362. locaTbl := fTables[fTblIdxes[tblLoca]];
  1363. Result := fStream.Size >= locaTbl.offset + locaTbl.length;
  1364. if not Result then Exit;
  1365. fStream.Position := locaTbl.offset;
  1366. if fTbl_head.indexToLocFmt = 0 then
  1367. begin
  1368. SetLength(fTbl_loca2, fTbl_maxp.numGlyphs +1);
  1369. for i := 0 to fTbl_maxp.numGlyphs do
  1370. GetWord(fStream, fTbl_loca2[i]);
  1371. end else
  1372. begin
  1373. SetLength(fTbl_loca4, fTbl_maxp.numGlyphs +1);
  1374. for i := 0 to fTbl_maxp.numGlyphs do
  1375. GetCardinal(fStream, fTbl_loca4[i]);
  1376. end;
  1377. end;
  1378. //------------------------------------------------------------------------------
  1379. function IsUnicode(platformID: WORD): Boolean;
  1380. begin
  1381. Result := (platformID = 0) or (platformID = 3);
  1382. end;
  1383. //------------------------------------------------------------------------------
  1384. function GetNameRecString(stream: TStream;
  1385. const nameRec: TNameRec; offset: cardinal): Utf8String;
  1386. var
  1387. sPos, len: integer;
  1388. begin
  1389. sPos := stream.Position;
  1390. stream.Position := offset + nameRec.offset;
  1391. if IsUnicode(nameRec.platformID) then
  1392. Result := GetWideString(stream, nameRec.length) else
  1393. Result := GetUtf8String(stream, nameRec.length);
  1394. len := Length(Result);
  1395. if (len > 0) and (Result[len] = #0) then SetLength(Result, len -1);
  1396. stream.Position := sPos;
  1397. end;
  1398. //------------------------------------------------------------------------------
  1399. function TFontReader.GetTable_name: Boolean;
  1400. var
  1401. i: integer;
  1402. offset: cardinal;
  1403. nameRec: TNameRec;
  1404. nameTbl: TFontTable;
  1405. begin
  1406. fFontInfo.faceName := '';
  1407. fFontInfo.fullFaceName := '';
  1408. fFontInfo.style := '';
  1409. nameTbl := fTables[fTblIdxes[tblName]];
  1410. Result := IsValidFontTable(nameTbl) and
  1411. (nameTbl.length >= SizeOf(TFontTable_Name));
  1412. if not Result then Exit;
  1413. fStream.Position := nameTbl.offset;
  1414. GetWord(fStream, fTbl_name.format);
  1415. GetWord(fStream, fTbl_name.count);
  1416. GetWord(fStream, fTbl_name.stringOffset);
  1417. offset := nameTbl.offset + fTbl_name.stringOffset;
  1418. for i := 1 to fTbl_name.count do
  1419. begin
  1420. GetWord(fStream, nameRec.platformID);
  1421. GetWord(fStream, nameRec.encodingID);
  1422. GetWord(fStream, nameRec.languageID);
  1423. GetWord(fStream, nameRec.nameID);
  1424. GetWord(fStream, nameRec.length);
  1425. GetWord(fStream, nameRec.offset);
  1426. case nameRec.nameID of
  1427. 0: fFontInfo.copyright := GetNameRecString(fStream, nameRec, offset);
  1428. 1: fFontInfo.faceName := GetNameRecString(fStream, nameRec, offset);
  1429. 2: fFontInfo.style := GetNameRecString(fStream, nameRec, offset);
  1430. 3: continue;
  1431. 4: fFontInfo.fullFaceName := GetNameRecString(fStream, nameRec, offset);
  1432. 5..7: continue;
  1433. 8: fFontInfo.manufacturer := GetNameRecString(fStream, nameRec, offset);
  1434. end;
  1435. end;
  1436. end;
  1437. //------------------------------------------------------------------------------
  1438. function TFontReader.GetTable_head: Boolean;
  1439. var
  1440. headTbl: TFontTable;
  1441. yy,mo,dd,hh,mi,ss: cardinal;
  1442. begin
  1443. headTbl := fTables[fTblIdxes[tblHead]];
  1444. Result := IsValidFontTable(headTbl) and (headTbl.length >= 54);
  1445. if not Result then Exit;
  1446. fStream.Position := headTbl.offset;
  1447. GetWord(fStream, fTbl_head.majorVersion);
  1448. GetWord(fStream, fTbl_head.minorVersion);
  1449. GetFixed(fStream, fTbl_head.fontRevision);
  1450. GetCardinal(fStream, fTbl_head.checkSumAdjust);
  1451. GetCardinal(fStream, fTbl_head.magicNumber);
  1452. GetWord(fStream, fTbl_head.flags);
  1453. GetWord(fStream, fTbl_head.unitsPerEm);
  1454. GetUInt64(fStream, fTbl_head.dateCreated);
  1455. GetMeaningfulDateTime(fTbl_head.dateCreated, yy,mo,dd,hh,mi,ss);
  1456. fFontInfo.dateCreated := EncodeDate(yy,mo,dd) + EncodeTime(hh,mi,ss, 0);
  1457. GetUInt64(fStream, fTbl_head.dateModified);
  1458. GetMeaningfulDateTime(fTbl_head.dateModified, yy,mo,dd,hh,mi,ss);
  1459. fFontInfo.dateModified := EncodeDate(yy,mo,dd) + EncodeTime(hh,mi,ss, 0);
  1460. GetInt16(fStream, fTbl_head.xMin);
  1461. GetInt16(fStream, fTbl_head.yMin);
  1462. GetInt16(fStream, fTbl_head.xMax);
  1463. GetInt16(fStream, fTbl_head.yMax);
  1464. GetWord(fStream, fTbl_head.macStyle);
  1465. fFontInfo.macStyles := TMacStyles(Byte(fTbl_head.macStyle));
  1466. GetWord(fStream, fTbl_head.lowestRecPPEM);
  1467. GetInt16(fStream, fTbl_head.fontDirHint);
  1468. GetInt16(fStream, fTbl_head.indexToLocFmt);
  1469. GetInt16(fStream, fTbl_head.glyphDataFmt);
  1470. result := fTbl_head.magicNumber = $5F0F3CF5
  1471. end;
  1472. //------------------------------------------------------------------------------
  1473. function TFontReader.GetTable_hhea: Boolean;
  1474. var
  1475. hheaTbl: TFontTable;
  1476. begin
  1477. hheaTbl := fTables[fTblIdxes[tblHhea]];
  1478. Result := IsValidFontTable(hheaTbl) and (hheaTbl.length >= 36);
  1479. if not Result then Exit;
  1480. fStream.Position := hheaTbl.offset;
  1481. GetFixed(fStream, fTbl_hhea.version);
  1482. GetInt16(fStream, fTbl_hhea.ascent);
  1483. GetInt16(fStream, fTbl_hhea.descent);
  1484. GetInt16(fStream, fTbl_hhea.lineGap);
  1485. GetWord(fStream, fTbl_hhea.advWidthMax);
  1486. GetInt16(fStream, fTbl_hhea.minLSB);
  1487. GetInt16(fStream, fTbl_hhea.minRSB);
  1488. GetInt16(fStream, fTbl_hhea.xMaxExtent);
  1489. GetInt16(fStream, fTbl_hhea.caretSlopeRise);
  1490. GetInt16(fStream, fTbl_hhea.caretSlopeRun);
  1491. GetInt16(fStream, fTbl_hhea.caretOffset);
  1492. GetUInt64(fStream, fTbl_hhea.reserved);
  1493. GetInt16(fStream, fTbl_hhea.metricDataFmt);
  1494. GetWord(fStream, fTbl_hhea.numLongHorMets);
  1495. end;
  1496. //------------------------------------------------------------------------------
  1497. function TFontReader.GetGlyphHorzMetrics(glyphIdx: WORD): TFontTable_Hmtx;
  1498. var
  1499. tbl : TFontTable;
  1500. begin
  1501. tbl := fTables[fTblIdxes[tblHmtx]];
  1502. if glyphIdx < fTbl_hhea.numLongHorMets then
  1503. begin
  1504. fStream.Position := Integer(tbl.offset) + glyphIdx * 4;
  1505. GetWord(fStream, Result.advanceWidth);
  1506. GetInt16(fStream, Result.leftSideBearing);
  1507. end else
  1508. begin
  1509. fStream.Position := Integer(tbl.offset) +
  1510. Integer(fTbl_hhea.numLongHorMets -1) * 4;
  1511. GetWord(fStream, Result.advanceWidth);
  1512. fStream.Position := Integer(tbl.offset +
  1513. fTbl_hhea.numLongHorMets * 4) +
  1514. 2 * (glyphIdx - Integer(fTbl_hhea.numLongHorMets));
  1515. GetInt16(fStream, Result.leftSideBearing);
  1516. end;
  1517. end;
  1518. //------------------------------------------------------------------------------
  1519. procedure TFontReader.GetTable_kern;
  1520. var
  1521. i : integer;
  1522. tbl : TFontTable;
  1523. tbl_kern : TFontTable_Kern;
  1524. kernSub : TKernSubTbl;
  1525. format0KernHdr : TFormat0KernHdr;
  1526. begin
  1527. if fTblIdxes[tblKern] < 0 then Exit;
  1528. tbl := fTables[fTblIdxes[tblKern]];
  1529. if not IsValidFontTable(tbl) then Exit;
  1530. fStream.Position := Integer(tbl.offset);
  1531. GetWord(fStream, tbl_kern.version);
  1532. GetWord(fStream, tbl_kern.numTables);
  1533. if tbl_kern.numTables = 0 then Exit;
  1534. // assume there's only one kern table
  1535. GetWord(fStream, kernSub.version);
  1536. GetWord(fStream, kernSub.length);
  1537. GetWord(fStream, kernSub.coverage);
  1538. // we're currently only interested in Format0 horizontal kerning
  1539. if kernSub.coverage <> 1 then Exit;
  1540. GetWord(fStream, format0KernHdr.nPairs);
  1541. GetWord(fStream, format0KernHdr.searchRange);
  1542. GetWord(fStream, format0KernHdr.entrySelector);
  1543. GetWord(fStream, format0KernHdr.rangeShift);
  1544. SetLength(fKernTable, format0KernHdr.nPairs);
  1545. for i := 0 to format0KernHdr.nPairs -1 do
  1546. begin
  1547. GetWord(fStream, fKernTable[i].left);
  1548. GetWord(fStream, fKernTable[i].right);
  1549. GetInt16(fStream, fKernTable[i].value);
  1550. end;
  1551. end;
  1552. //------------------------------------------------------------------------------
  1553. procedure TFontReader.GetTable_post;
  1554. var
  1555. tbl: TFontTable;
  1556. begin
  1557. if fTblIdxes[tblPost] < 0 then Exit;
  1558. tbl := fTables[fTblIdxes[tblPost]];
  1559. if not IsValidFontTable(tbl) then Exit;
  1560. fStream.Position := Integer(tbl.offset);
  1561. GetWord(fStream, fTbl_post.majorVersion);
  1562. GetWord(fStream, fTbl_post.minorVersion);
  1563. GetFixed(fStream, fTbl_post.italicAngle);
  1564. GetInt16(fStream, fTbl_post.underlinePos);
  1565. GetInt16(fStream, fTbl_post.underlineWidth);
  1566. GetCardinal(fStream, fTbl_post.isFixedPitch);
  1567. end;
  1568. //------------------------------------------------------------------------------
  1569. function FindKernInTable(glyphIdx: WORD; const kernTable: TArrayOfKernRecs): integer;
  1570. var
  1571. i,l,r: integer;
  1572. begin
  1573. l := 0;
  1574. r := High(kernTable);
  1575. while l <= r do
  1576. begin
  1577. Result := (l + r) shr 1;
  1578. i := kernTable[Result].left - glyphIdx;
  1579. if i < 0 then
  1580. begin
  1581. l := Result +1
  1582. end else
  1583. begin
  1584. if i = 0 then
  1585. begin
  1586. // found a match! Now find the very first one ...
  1587. while (Result > 0) and
  1588. (kernTable[Result-1].left = glyphIdx) do dec(Result);
  1589. Exit;
  1590. end;
  1591. r := Result -1;
  1592. end;
  1593. end;
  1594. Result := -1;
  1595. end;
  1596. //------------------------------------------------------------------------------
  1597. function TFontReader.GetGlyphKernList(glyphIdx: WORD): TArrayOfTKern;
  1598. var
  1599. i,j,len: integer;
  1600. begin
  1601. result := nil;
  1602. i := FindKernInTable(glyphIdx, fKernTable);
  1603. if i < 0 then Exit;
  1604. len := Length(fKernTable);
  1605. j := i +1;
  1606. while (j < len) and (fKernTable[j].left = glyphIdx) do inc(j);
  1607. SetLength(Result, j - i);
  1608. for j := 0 to High(Result) do
  1609. with fKernTable[i+j] do
  1610. begin
  1611. Result[j].rightGlyphIdx := right;
  1612. Result[j].kernValue := value;
  1613. end;
  1614. end;
  1615. //------------------------------------------------------------------------------
  1616. function TFontReader.GetGlyphPaths(glyphIdx: WORD;
  1617. var tbl_hmtx: TFontTable_Hmtx; out tbl_glyf: TFontTable_Glyf): TPathsEx;
  1618. var
  1619. offset: cardinal;
  1620. glyfTbl: TFontTable;
  1621. begin
  1622. result := nil;
  1623. if fTbl_head.indexToLocFmt = 0 then
  1624. begin
  1625. offset := fTbl_loca2[glyphIdx] *2;
  1626. if offset = fTbl_loca2[glyphIdx+1] *2 then Exit; // no contours
  1627. end else
  1628. begin
  1629. offset := fTbl_loca4[glyphIdx];
  1630. if offset = fTbl_loca4[glyphIdx+1] then Exit; // no contours
  1631. end;
  1632. glyfTbl := fTables[fTblIdxes[tblGlyf]];
  1633. if offset >= glyfTbl.length then Exit;
  1634. inc(offset, glyfTbl.offset);
  1635. fStream.Position := offset;
  1636. GetInt16(fStream, tbl_glyf.numContours);
  1637. GetInt16(fStream, tbl_glyf.xMin);
  1638. GetInt16(fStream, tbl_glyf.yMin);
  1639. GetInt16(fStream, tbl_glyf.xMax);
  1640. GetInt16(fStream, tbl_glyf.yMax);
  1641. if tbl_glyf.numContours < 0 then
  1642. result := GetCompositeGlyph(tbl_glyf, tbl_hmtx) else
  1643. result := GetSimpleGlyph(tbl_glyf);
  1644. end;
  1645. //------------------------------------------------------------------------------
  1646. const
  1647. // glyf flags - simple
  1648. ON_CURVE = $1;
  1649. X_SHORT_VECTOR = $2;
  1650. Y_SHORT_VECTOR = $4;
  1651. REPEAT_FLAG = $8;
  1652. X_DELTA = $10;
  1653. Y_DELTA = $20;
  1654. //------------------------------------------------------------------------------
  1655. function TFontReader.GetSimpleGlyph(tbl_glyf: TFontTable_Glyf): TPathsEx;
  1656. var
  1657. i,j, len: integer;
  1658. instructLen: WORD;
  1659. flag, repeats: byte;
  1660. contourEnds: TArrayOfWord;
  1661. begin
  1662. SetLength(contourEnds, tbl_glyf.numContours);
  1663. for i := 0 to High(contourEnds) do
  1664. GetWord(fStream, contourEnds[i]);
  1665. // hints are currently ignored
  1666. GetWord(fStream, instructLen);
  1667. fStream.Position := fStream.Position + instructLen;
  1668. setLength(result, tbl_glyf.numContours);
  1669. repeats := 0;
  1670. flag := 0; // help the compiler with "flag isn't initialized"
  1671. for i := 0 to High(result) do
  1672. begin
  1673. if i = 0 then len := contourEnds[0] +1
  1674. else len := contourEnds[i] - contourEnds[i-1];
  1675. setLength(result[i], len);
  1676. for j := 0 to len -1 do
  1677. begin
  1678. if repeats = 0 then
  1679. begin
  1680. GetByte(fStream, flag);
  1681. if flag and REPEAT_FLAG = REPEAT_FLAG then
  1682. GetByte(fStream, repeats);
  1683. end else
  1684. dec(repeats);
  1685. result[i][j].flag := flag;
  1686. end;
  1687. end;
  1688. if tbl_glyf.numContours > 0 then
  1689. GetPathCoords(result);
  1690. end;
  1691. //------------------------------------------------------------------------------
  1692. procedure TFontReader.GetPathCoords(var paths: TPathsEx);
  1693. var
  1694. i,j: integer;
  1695. xi,yi: Int16;
  1696. flag, xb,yb: byte;
  1697. pt: TPoint;
  1698. begin
  1699. // get X coords
  1700. pt := Point(0,0);
  1701. xi := 0;
  1702. for i := 0 to high(paths) do
  1703. begin
  1704. for j := 0 to high(paths[i]) do
  1705. begin
  1706. flag := paths[i][j].flag;
  1707. if flag and X_SHORT_VECTOR = X_SHORT_VECTOR then
  1708. begin
  1709. GetByte(fStream, xb);
  1710. if (flag and X_DELTA) = 0 then
  1711. dec(pt.X, xb) else
  1712. inc(pt.X, xb);
  1713. end else
  1714. begin
  1715. if flag and X_DELTA = 0 then
  1716. begin
  1717. if GetInt16(fStream, xi) then
  1718. pt.X := pt.X + xi;
  1719. end;
  1720. end;
  1721. paths[i][j].pt.X := pt.X;
  1722. end;
  1723. end;
  1724. // get Y coords
  1725. yi := 0;
  1726. for i := 0 to high(paths) do
  1727. begin
  1728. for j := 0 to high(paths[i]) do
  1729. begin
  1730. flag := paths[i][j].flag;
  1731. if flag and Y_SHORT_VECTOR = Y_SHORT_VECTOR then
  1732. begin
  1733. GetByte(fStream, yb);
  1734. if (flag and Y_DELTA) = 0 then
  1735. dec(pt.Y, yb) else
  1736. inc(pt.Y, yb);
  1737. end else
  1738. begin
  1739. if flag and Y_DELTA = 0 then
  1740. begin
  1741. if GetInt16(fStream, yi) then
  1742. pt.Y := pt.Y + yi;
  1743. end;
  1744. end;
  1745. paths[i][j].pt.Y := pt.Y;
  1746. end;
  1747. end;
  1748. end;
  1749. //------------------------------------------------------------------------------
  1750. function OnCurve(flag: byte): Boolean;
  1751. begin
  1752. result := flag and ON_CURVE <> 0;
  1753. end;
  1754. //------------------------------------------------------------------------------
  1755. function MidPoint(const pt1, pt2: TPointEx): TPointEx;
  1756. begin
  1757. Result.pt.X := (pt1.pt.X + pt2.pt.X) / 2;
  1758. Result.pt.Y := (pt1.pt.Y + pt2.pt.Y) / 2;
  1759. Result.flag := ON_CURVE;
  1760. end;
  1761. //------------------------------------------------------------------------------
  1762. function TFontReader.ConvertSplinesToBeziers(const pathsEx: TPathsEx): TPathsEx;
  1763. var
  1764. i,j,k: integer;
  1765. pt: TPointEx;
  1766. prevOnCurve: Boolean;
  1767. begin
  1768. SetLength(Result, Length(pathsEx));
  1769. for i := 0 to High(pathsEx) do
  1770. begin
  1771. SetLength(Result[i], Length(pathsEx[i]) *2);
  1772. Result[i][0] := pathsEx[i][0]; k := 1;
  1773. prevOnCurve := true;
  1774. for j := 1 to High(pathsEx[i]) do
  1775. begin
  1776. if OnCurve(pathsEx[i][j].flag) then
  1777. begin
  1778. prevOnCurve := true;
  1779. end
  1780. else if not prevOnCurve then
  1781. begin
  1782. pt := MidPoint(pathsEx[i][j-1], pathsEx[i][j]);
  1783. Result[i][k] := pt; inc(k);
  1784. end else
  1785. prevOnCurve := false;
  1786. Result[i][k] := pathsEx[i][j]; inc(k);
  1787. end;
  1788. SetLength(Result[i], k);
  1789. end;
  1790. end;
  1791. //------------------------------------------------------------------------------
  1792. procedure AppendPathsEx(var paths: TPathsEx; const extra: TPathsEx);
  1793. var
  1794. i, len1, len2: integer;
  1795. begin
  1796. len2 := length(extra);
  1797. len1 := length(paths);
  1798. setLength(paths, len1 + len2);
  1799. for i := 0 to len2 -1 do
  1800. paths[len1+i] := Copy(extra[i], 0, length(extra[i]));
  1801. end;
  1802. //------------------------------------------------------------------------------
  1803. procedure AffineTransform(const a,b,c,d,e,f: double; var pathsEx: TPathsEx);
  1804. var
  1805. i,j: integer;
  1806. mat: TMatrixD;
  1807. begin
  1808. // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
  1809. if ((a = 0) and (b = 0)) or ((c = 0) and (d = 0)) then
  1810. begin
  1811. if (e = 0) and (f = 0) then Exit;
  1812. for i := 0 to High(pathsEx) do
  1813. for j := 0 to High(pathsEx[i]) do
  1814. with pathsEx[i][j].pt do
  1815. begin
  1816. X := X + e;
  1817. y := Y + f;
  1818. end;
  1819. end else
  1820. begin
  1821. mat[0,0] := a;
  1822. mat[0,1] := b;
  1823. mat[1,0] := c;
  1824. mat[1,1] := d;
  1825. mat[2][0] := e;
  1826. mat[2][1] := f;
  1827. for i := 0 to High(pathsEx) do
  1828. for j := 0 to High(pathsEx[i]) do
  1829. MatrixApply(mat, pathsEx[i][j].pt);
  1830. end;
  1831. end;
  1832. //------------------------------------------------------------------------------
  1833. function TFontReader.GetCompositeGlyph(var tbl_glyf: TFontTable_Glyf;
  1834. var tbl_hmtx: TFontTable_Hmtx): TPathsEx;
  1835. var
  1836. streamPos: integer;
  1837. flag, glyphIndex: WORD;
  1838. arg1_i8, arg2_i8: ShortInt;
  1839. arg1_i16, arg2_i16: Int16;
  1840. tmp_single: single;
  1841. a,b,c,d,e,f: double;
  1842. componentPaths: TPathsEx;
  1843. component_tbl_glyf: TFontTable_Glyf;
  1844. component_tbl_hmtx: TFontTable_Hmtx;
  1845. const
  1846. ARG_1_AND_2_ARE_WORDS = $1;
  1847. ARGS_ARE_XY_VALUES = $2;
  1848. ROUND_XY_TO_GRID = $4;
  1849. WE_HAVE_A_SCALE = $8;
  1850. MORE_COMPONENTS = $20;
  1851. WE_HAVE_AN_X_AND_Y_SCALE = $40;
  1852. WE_HAVE_A_TWO_BY_TWO = $80;
  1853. WE_HAVE_INSTRUCTIONS = $100;
  1854. USE_MY_METRICS = $200;
  1855. begin
  1856. result := nil;
  1857. flag := MORE_COMPONENTS;
  1858. while (flag and MORE_COMPONENTS <> 0) do
  1859. begin
  1860. glyphIndex := 0;
  1861. a := 0; b := 0; c := 0; d := 0; e := 0; f := 0;
  1862. GetWord(fStream, flag);
  1863. GetWord(fStream, glyphIndex);
  1864. if (flag and ARG_1_AND_2_ARE_WORDS <> 0) then
  1865. begin
  1866. GetInt16(fStream, arg1_i16);
  1867. GetInt16(fStream, arg2_i16);
  1868. if (flag and ARGS_ARE_XY_VALUES <> 0) then
  1869. begin
  1870. e := arg1_i16;
  1871. f := arg2_i16;
  1872. end;
  1873. end else
  1874. begin
  1875. GetShortInt(fStream, arg1_i8);
  1876. GetShortInt(fStream, arg2_i8);
  1877. if (flag and ARGS_ARE_XY_VALUES <> 0) then
  1878. begin
  1879. e := arg1_i8;
  1880. f := arg2_i8;
  1881. end;
  1882. end;
  1883. if (flag and WE_HAVE_A_SCALE <> 0) then
  1884. begin
  1885. Get2Dot14(fStream, tmp_single);
  1886. a := tmp_single; d := tmp_single;
  1887. end
  1888. else if (flag and WE_HAVE_AN_X_AND_Y_SCALE <> 0) then
  1889. begin
  1890. Get2Dot14(fStream, tmp_single); a := tmp_single;
  1891. Get2Dot14(fStream, tmp_single); d := tmp_single;
  1892. end
  1893. else if (flag and WE_HAVE_A_TWO_BY_TWO <> 0) then
  1894. begin
  1895. Get2Dot14(fStream, tmp_single); a := tmp_single;
  1896. Get2Dot14(fStream, tmp_single); b := tmp_single;
  1897. Get2Dot14(fStream, tmp_single); c := tmp_single;
  1898. Get2Dot14(fStream, tmp_single); d := tmp_single;
  1899. end;
  1900. component_tbl_hmtx := tbl_hmtx;
  1901. // GetGlyphPaths() will change the stream position, so save it.
  1902. streamPos := fStream.Position;
  1903. componentPaths := GetGlyphPaths(glyphIndex, component_tbl_hmtx, component_tbl_glyf);
  1904. // return to saved stream position
  1905. fStream.Position := streamPos;
  1906. if (flag and ARGS_ARE_XY_VALUES <> 0) then
  1907. AffineTransform(a,b,c,d,e,f, componentPaths); // (#131)
  1908. if (flag and USE_MY_METRICS <> 0) then
  1909. tbl_hmtx := component_tbl_hmtx; // (#24)
  1910. if component_tbl_glyf.numContours > 0 then
  1911. begin
  1912. if tbl_glyf.numContours < 0 then tbl_glyf.numContours := 0;
  1913. inc(tbl_glyf.numContours, component_tbl_glyf.numContours);
  1914. tbl_glyf.xMin := Min(tbl_glyf.xMin, component_tbl_glyf.xMin);
  1915. tbl_glyf.xMax := Max(tbl_glyf.xMax, component_tbl_glyf.xMax);
  1916. tbl_glyf.yMin := Min(tbl_glyf.yMin, component_tbl_glyf.yMin);
  1917. tbl_glyf.yMax := Max(tbl_glyf.yMax, component_tbl_glyf.yMax);
  1918. end;
  1919. AppendPathsEx(result, componentPaths);
  1920. end;
  1921. end;
  1922. //------------------------------------------------------------------------------
  1923. function TFontReader.HasGlyph(codepoint: Cardinal): Boolean;
  1924. begin
  1925. Result := GetGlyphIdxUsingCmap(codepoint) > 0;
  1926. end;
  1927. //------------------------------------------------------------------------------
  1928. function FlattenPathExBeziers(pathsEx: TPathsEx): TPathsD;
  1929. var
  1930. i,j : integer;
  1931. pt2: TPointEx;
  1932. bez: TPathD;
  1933. begin
  1934. setLength(Result, length(pathsEx));
  1935. for i := 0 to High(pathsEx) do
  1936. begin
  1937. SetLength(Result[i],1);
  1938. Result[i][0] := pathsEx[i][0].pt;
  1939. for j := 1 to High(pathsEx[i]) do
  1940. begin
  1941. if OnCurve(pathsEx[i][j].flag) then
  1942. begin
  1943. AppendPoint(Result[i], pathsEx[i][j].pt);
  1944. end else
  1945. begin
  1946. if j = High(pathsEx[i]) then
  1947. pt2 := pathsEx[i][0] else
  1948. pt2 := pathsEx[i][j+1];
  1949. bez := FlattenQBezier(pathsEx[i][j-1].pt, pathsEx[i][j].pt, pt2.pt);
  1950. ConcatPaths(Result[i], bez);
  1951. end;
  1952. end;
  1953. end;
  1954. end;
  1955. //------------------------------------------------------------------------------
  1956. function TFontReader.GetGlyphInfo(codepoint: Cardinal;
  1957. out nextX: integer; out glyphInfo: TGlyphInfo): Boolean;
  1958. var
  1959. glyphIdx: WORD;
  1960. begin
  1961. Result := IsValidFontFormat;
  1962. if not Result then Exit;
  1963. glyphIdx := GetGlyphIdxUsingCmap(codepoint);
  1964. glyphInfo := GetGlyphInfoInternal(glyphIdx);
  1965. glyphInfo.hmtx := GetGlyphHorzMetrics(glyphIdx);
  1966. nextX := glyphInfo.hmtx.advanceWidth;
  1967. glyphInfo.codepoint := codepoint;
  1968. end;
  1969. //------------------------------------------------------------------------------
  1970. function TFontReader.GetFontInfo: TFontInfo;
  1971. begin
  1972. if not IsValidFontFormat then
  1973. begin
  1974. FillChar(Result, SizeOf(Result), 0);
  1975. Exit;
  1976. end;
  1977. result := fFontInfo;
  1978. if result.unitsPerEm > 0 then Exit;
  1979. // and updated the record with everything except the strings
  1980. result.unitsPerEm := fTbl_head.unitsPerEm;
  1981. result.xMin := fTbl_head.xMin;
  1982. result.xMax := fTbl_head.xMax;
  1983. result.yMin := fTbl_head.yMin;
  1984. result.yMax := fTbl_head.yMax;
  1985. // note: the following three fields "represent the design
  1986. // intentions of the font's creator rather than any computed value"
  1987. // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6hhea.html
  1988. result.ascent := fTbl_hhea.ascent;
  1989. result.descent := abs(fTbl_hhea.descent);
  1990. result.lineGap := fTbl_hhea.lineGap;
  1991. result.advWidthMax := fTbl_hhea.advWidthMax;
  1992. result.minLSB := fTbl_hhea.minLSB;
  1993. result.minRSB := fTbl_hhea.minRSB;
  1994. result.xMaxExtent := fTbl_hhea.xMaxExtent;
  1995. end;
  1996. //------------------------------------------------------------------------------
  1997. function TFontReader.GetGlyphInfoInternal(glyphIdx: WORD): TGlyphInfo;
  1998. var
  1999. pathsEx: TPathsEx;
  2000. begin
  2001. FillChar(result, sizeOf(Result), 0);
  2002. if not IsValidFontFormat then Exit;
  2003. result.glyphIdx := glyphIdx;
  2004. result.unitsPerEm := fTbl_head.unitsPerEm;
  2005. // get raw splines
  2006. pathsEx := GetGlyphPaths(glyphIdx, result.hmtx, result.glyf);
  2007. if Assigned(pathsEx) then
  2008. begin
  2009. pathsEx := ConvertSplinesToBeziers(pathsEx);
  2010. result.paths := FlattenPathExBeziers(PathsEx);
  2011. end;
  2012. Result.kernList := GetGlyphKernList(glyphIdx);
  2013. end;
  2014. //------------------------------------------------------------------------------
  2015. function TFontReader.GetWeight: integer;
  2016. var
  2017. i, dummy: integer;
  2018. accum: Cardinal;
  2019. gm: TGlyphInfo;
  2020. rec: TRectD;
  2021. img: TImage32;
  2022. p: PARGB;
  2023. const
  2024. imgSize = 16;
  2025. k = 5; // an empirical constant
  2026. begin
  2027. // get an empirical weight based on the character 'G'
  2028. result := 0;
  2029. if not IsValidFontFormat then Exit;
  2030. if fFontWeight > 0 then
  2031. begin
  2032. Result := fFontWeight;
  2033. Exit;
  2034. end;
  2035. GetGlyphInfo(Ord('G'),dummy, gm);
  2036. rec := GetBoundsD(gm.paths);
  2037. gm.paths := Img32.Vector.TranslatePath(gm.paths, -rec.Left, -rec.Top);
  2038. gm.paths := Img32.Vector.ScalePath(gm.paths, imgSize/rec.Width, imgSize/rec.Height);
  2039. img := TImage32.Create(imgSize,imgSize);
  2040. try
  2041. DrawPolygon(img, gm.paths, frEvenOdd, clBlack32);
  2042. accum := 0;
  2043. p := PARGB(img.PixelBase);
  2044. for i := 0 to imgSize * imgSize do
  2045. begin
  2046. inc(accum, p.A);
  2047. inc(p);
  2048. end;
  2049. finally
  2050. img.Free;
  2051. end;
  2052. fFontWeight := Max(100, Min(900,
  2053. Round(k * accum / (imgSize * imgSize * 100)) * 100));
  2054. Result := fFontWeight;
  2055. end;
  2056. //------------------------------------------------------------------------------
  2057. procedure TFontReader.GetFontFamily;
  2058. var
  2059. giT, giI, giM: integer;
  2060. gmT: TGlyphInfo;
  2061. hmtxI, hmtxM: TFontTable_Hmtx;
  2062. begin
  2063. fFontInfo.family := tfUnknown;
  2064. if (fTbl_post.majorVersion > 0) and
  2065. (fTbl_post.isFixedPitch <> 0) then
  2066. begin
  2067. fFontInfo.family := tfMonospace;
  2068. Exit;
  2069. end;
  2070. // use glyph metrics for 'T', 'i' & 'm' to determine the font family
  2071. // if the widths of 'i' & 'm' are equal, then assume a monospace font
  2072. // else if the number of vertices used to draw 'T' is greater than 10
  2073. // then assume a serif font otherwise assume a sans serif font.
  2074. giT := GetGlyphIdxUsingCmap(Ord('T'));
  2075. giI := GetGlyphIdxUsingCmap(Ord('i'));
  2076. giM := GetGlyphIdxUsingCmap(Ord('m'));
  2077. if (giT = 0) or (giI = 0) or (giM = 0) then Exit;
  2078. hmtxI := GetGlyphHorzMetrics(giI);
  2079. hmtxM := GetGlyphHorzMetrics(giM);
  2080. if hmtxI.advanceWidth = hmtxM.advanceWidth then
  2081. begin
  2082. fFontInfo.family := tfMonospace;
  2083. Exit;
  2084. end;
  2085. gmT := GetGlyphInfoInternal(giT);
  2086. if Assigned(gmT.paths) and (Length(gmT.paths[0]) > 10) then
  2087. fFontInfo.family := tfSerif else
  2088. fFontInfo.family := tfSansSerif;
  2089. end;
  2090. //------------------------------------------------------------------------------
  2091. // TFontCache
  2092. //------------------------------------------------------------------------------
  2093. constructor TFontCache.Create(fontReader: TFontReader; fontHeight: double);
  2094. begin
  2095. {$IFDEF XPLAT_GENERICS}
  2096. fGlyphInfoList := TList<PGlyphInfo>.Create;
  2097. {$ELSE}
  2098. fGlyphInfoList := TList.Create;
  2099. {$ENDIF}
  2100. fSorted := false;
  2101. fUseKerning := true;
  2102. fFlipVert := true;
  2103. fFontHeight := fontHeight;
  2104. SetFontReader(fontReader);
  2105. end;
  2106. //------------------------------------------------------------------------------
  2107. destructor TFontCache.Destroy;
  2108. begin
  2109. SetFontReader(nil);
  2110. Clear;
  2111. NotifyRecipients(inDestroy);
  2112. fGlyphInfoList.Free;
  2113. inherited;
  2114. end;
  2115. //------------------------------------------------------------------------------
  2116. procedure TFontCache.ReceiveNotification(Sender: TObject; notify: TImg32Notification);
  2117. begin
  2118. if Sender <> fFontReader then
  2119. raise Exception.Create(rsFontCacheError);
  2120. if notify = inStateChange then
  2121. begin
  2122. Clear;
  2123. UpdateScale;
  2124. end else
  2125. SetFontReader(nil);
  2126. end;
  2127. //------------------------------------------------------------------------------
  2128. procedure TFontCache.NotifyRecipients(notifyFlag: TImg32Notification);
  2129. var
  2130. i: integer;
  2131. begin
  2132. for i := High(fRecipientList) downto 0 do
  2133. try
  2134. // try .. except block because when TFontCache is destroyed in a
  2135. // finalization section, it's possible for recipients to have been
  2136. // destroyed without calling their destructors.
  2137. fRecipientList[i].ReceiveNotification(self, notifyFlag);
  2138. except
  2139. end;
  2140. end;
  2141. //------------------------------------------------------------------------------
  2142. procedure TFontCache.AddRecipient(recipient: INotifyRecipient);
  2143. var
  2144. len: integer;
  2145. begin
  2146. len := Length(fRecipientList);
  2147. SetLength(fRecipientList, len+1);
  2148. fRecipientList[len] := Recipient;
  2149. end;
  2150. //------------------------------------------------------------------------------
  2151. procedure TFontCache.DeleteRecipient(recipient: INotifyRecipient);
  2152. var
  2153. i, highI: integer;
  2154. begin
  2155. highI := High(fRecipientList);
  2156. i := highI;
  2157. while (i >= 0) and (fRecipientList[i] <> Recipient) do dec(i);
  2158. if i < 0 then Exit;
  2159. if i < highI then
  2160. Move(fRecipientList[i+i], fRecipientList[i],
  2161. (highI - i) * SizeOf(INotifyRecipient));
  2162. SetLength(fRecipientList, highI);
  2163. end;
  2164. //------------------------------------------------------------------------------
  2165. procedure TFontCache.Clear;
  2166. var
  2167. i: integer;
  2168. begin
  2169. for i := 0 to fGlyphInfoList.Count -1 do
  2170. Dispose(PGlyphInfo(fGlyphInfoList[i]));
  2171. fGlyphInfoList.Clear;
  2172. fSorted := false;
  2173. end;
  2174. //------------------------------------------------------------------------------
  2175. {$IFDEF XPLAT_GENERICS}
  2176. function FindInSortedList(charOrdinal: Cardinal; glyphList: TList<PGlyphInfo>): integer;
  2177. {$ELSE}
  2178. function FindInSortedList(charOrdinal: Cardinal; glyphList: TList): integer;
  2179. {$ENDIF}
  2180. var
  2181. i,l,r: integer;
  2182. begin
  2183. // binary search the sorted list ...
  2184. l := 0;
  2185. r := glyphList.Count -1;
  2186. while l <= r do
  2187. begin
  2188. Result := (l + r) shr 1;
  2189. i := integer(PGlyphInfo(glyphList[Result]).codepoint) - integer(charOrdinal);
  2190. if i < 0 then
  2191. begin
  2192. l := Result +1
  2193. end else
  2194. begin
  2195. if i = 0 then Exit;
  2196. r := Result -1;
  2197. end;
  2198. end;
  2199. Result := -1;
  2200. end;
  2201. //------------------------------------------------------------------------------
  2202. function TFontCache.FoundInList(charOrdinal: Cardinal): Boolean;
  2203. begin
  2204. if not fSorted then Sort;
  2205. result := FindInSortedList(charOrdinal, fGlyphInfoList) >= 0;
  2206. end;
  2207. //------------------------------------------------------------------------------
  2208. procedure TFontCache.GetMissingGlyphs(const ordinals: TArrayOfCardinal);
  2209. var
  2210. i, len: integer;
  2211. begin
  2212. if not IsValidFont then Exit;
  2213. len := Length(ordinals);
  2214. for i := 0 to len -1 do
  2215. begin
  2216. if ordinals[i] < 32 then continue
  2217. else if not FoundInList(ordinals[i]) then AddGlyph(ordinals[i]);
  2218. end;
  2219. end;
  2220. //------------------------------------------------------------------------------
  2221. function TFontCache.IsValidFont: Boolean;
  2222. begin
  2223. Result := assigned(fFontReader) and fFontReader.IsValidFontFormat;
  2224. end;
  2225. //------------------------------------------------------------------------------
  2226. function TFontCache.GetAscent: double;
  2227. begin
  2228. if not IsValidFont then Result := 0
  2229. else with fFontReader.FontInfo do
  2230. Result := Max(ascent, yMax) * fScale;
  2231. end;
  2232. //------------------------------------------------------------------------------
  2233. function TFontCache.GetDescent: double;
  2234. begin
  2235. if not IsValidFont then Result := 0
  2236. else with fFontReader.FontInfo do
  2237. Result := Max(descent, -yMin) * fScale;
  2238. end;
  2239. //------------------------------------------------------------------------------
  2240. function TFontCache.GetGap: double;
  2241. begin
  2242. if not IsValidFont then Result := 0
  2243. else Result := fFontReader.FontInfo.lineGap * fScale;
  2244. end;
  2245. //------------------------------------------------------------------------------
  2246. function TFontCache.GetLineHeight: double;
  2247. begin
  2248. if not IsValidFont then Result := 0
  2249. else Result := Ascent + Descent + LineGap;
  2250. end;
  2251. //------------------------------------------------------------------------------
  2252. function TFontCache.GetYyHeight: double;
  2253. var
  2254. minY, maxY: double;
  2255. begin
  2256. // nb: non-inverted Y coordinates.
  2257. maxY := GetGlyphInfo(ord('Y')).glyf.yMax;
  2258. minY := GetGlyphInfo(ord('y')).glyf.yMin;
  2259. Result := (maxY - minY) * fScale;
  2260. end;
  2261. //------------------------------------------------------------------------------
  2262. procedure TFontCache.VerticalFlip(var paths: TPathsD);
  2263. var
  2264. i,j: integer;
  2265. begin
  2266. for i := 0 to High(paths) do
  2267. for j := 0 to High(paths[i]) do
  2268. with paths[i][j] do Y := -Y;
  2269. end;
  2270. //------------------------------------------------------------------------------
  2271. function FindInKernList(glyphIdx: WORD; const kernList: TArrayOfTKern): integer;
  2272. var
  2273. i,l,r: integer;
  2274. begin
  2275. l := 0;
  2276. r := High(kernList);
  2277. while l <= r do
  2278. begin
  2279. Result := (l + r) shr 1;
  2280. i := kernList[Result].rightGlyphIdx - glyphIdx;
  2281. if i < 0 then
  2282. begin
  2283. l := Result +1
  2284. end else
  2285. begin
  2286. if i = 0 then Exit; // found!
  2287. r := Result -1;
  2288. end;
  2289. end;
  2290. Result := -1;
  2291. end;
  2292. //------------------------------------------------------------------------------
  2293. function TFontCache.GetGlyphInfo(codepoint: Cardinal): PGlyphInfo;
  2294. var
  2295. listIdx: integer;
  2296. begin
  2297. Result := nil;
  2298. if not IsValidFont then Exit;
  2299. if not fSorted then Sort;
  2300. listIdx := FindInSortedList(codepoint, fGlyphInfoList);
  2301. if listIdx < 0 then
  2302. Result := AddGlyph(codepoint) else
  2303. Result := PGlyphInfo(fGlyphInfoList[listIdx]);
  2304. end;
  2305. //------------------------------------------------------------------------------
  2306. function IsSurrogate(c: WideChar): Boolean;
  2307. {$IFDEF INLINE} inline; {$ENDIF}
  2308. begin
  2309. Result := (c >= #$D800) and (c <= #$DFFF);
  2310. end;
  2311. //------------------------------------------------------------------------------
  2312. function ConvertSurrogatePair(hiSurrogate, loSurrogate: Cardinal): Cardinal;
  2313. {$IFDEF INLINE} inline; {$ENDIF}
  2314. begin
  2315. Result := ((hiSurrogate - $D800) shl 10) + (loSurrogate - $DC00) + $10000;
  2316. end;
  2317. //------------------------------------------------------------------------------
  2318. function TFontCache.GetTextCodePoints(const text: UnicodeString): TArrayOfCardinal;
  2319. var
  2320. i,j, len: integer;
  2321. inSurrogate: Boolean;
  2322. begin
  2323. len := Length(text);
  2324. setLength(Result, len);
  2325. inSurrogate := false;
  2326. j := 0;
  2327. for i := 1 to len do
  2328. begin
  2329. if inSurrogate then
  2330. begin
  2331. Result[j] := ConvertSurrogatePair(Ord(text[i -1]), Ord(text[i]));
  2332. inSurrogate := false;
  2333. end
  2334. else if IsSurrogate(text[i]) then
  2335. begin
  2336. inSurrogate := true;
  2337. Continue;
  2338. end
  2339. else
  2340. Result[j] := Ord(WideChar(text[i]));
  2341. inc(j);
  2342. end;
  2343. setLength(Result, j);
  2344. end;
  2345. //------------------------------------------------------------------------------
  2346. function TFontCache.GetGlyphOffsets(const text: UnicodeString;
  2347. interCharSpace: double): TArrayOfDouble;
  2348. var
  2349. i,j, len: integer;
  2350. codePoints: TArrayOfCardinal;
  2351. glyphInfo: PGlyphInfo;
  2352. thisX: double;
  2353. prevGlyphKernList: TArrayOfTKern;
  2354. begin
  2355. codePoints := GetTextCodePoints(text);
  2356. len := Length(codePoints);
  2357. SetLength(Result, len +1);
  2358. Result[0] := 0;
  2359. if len = 0 then Exit;
  2360. GetMissingGlyphs(codePoints);
  2361. thisX := 0;
  2362. prevGlyphKernList := nil;
  2363. for i := 0 to High(codePoints) do
  2364. begin
  2365. glyphInfo := GetGlyphInfo(codePoints[i]);
  2366. if not assigned(glyphInfo) then Break;
  2367. if fUseKerning and assigned(prevGlyphKernList) then
  2368. begin
  2369. j := FindInKernList(glyphInfo.glyphIdx, prevGlyphKernList);
  2370. if (j >= 0) then
  2371. thisX := thisX + prevGlyphKernList[j].kernValue*fScale;
  2372. end;
  2373. Result[i] := thisX;
  2374. thisX := thisX + glyphInfo.hmtx.advanceWidth*fScale +interCharSpace;
  2375. prevGlyphKernList := glyphInfo.kernList;
  2376. end;
  2377. Result[len] := thisX - interCharSpace;
  2378. end;
  2379. //------------------------------------------------------------------------------
  2380. function TFontCache.GetTextWidth(const text: UnicodeString): double;
  2381. var
  2382. offsets: TArrayOfDouble;
  2383. begin
  2384. Result := 0;
  2385. if not IsValidFont then Exit;
  2386. offsets := GetGlyphOffsets(text);
  2387. Result := offsets[high(offsets)];
  2388. end;
  2389. //------------------------------------------------------------------------------
  2390. function TFontCache.CountCharsThatFit(const text: UnicodeString;
  2391. maxWidth: double): integer;
  2392. var
  2393. offsets: TArrayOfDouble;
  2394. begin
  2395. Result := 0;
  2396. if not IsValidFont then Exit;
  2397. offsets := GetGlyphOffsets(text);
  2398. Result := Length(offsets);
  2399. while offsets[Result -1] > maxWidth do
  2400. Dec(Result);
  2401. end;
  2402. //------------------------------------------------------------------------------
  2403. function TFontCache.GetSpaceWidth: double;
  2404. begin
  2405. Result := GetGlyphInfo(32).hmtx.advanceWidth * fScale;
  2406. end;
  2407. //------------------------------------------------------------------------------
  2408. function TFontCache.GetTextOutline(x, y: double; const text: UnicodeString): TPathsD;
  2409. var
  2410. dummy: double;
  2411. begin
  2412. Result := GetTextOutline(x, y, text, dummy);
  2413. end;
  2414. //------------------------------------------------------------------------------
  2415. function TFontCache.GetTextOutline(x, y: double; const text: UnicodeString;
  2416. out nextX: double; underlineIdx: integer): TPathsD;
  2417. var
  2418. arrayOfGlyphs: TArrayOfPathsD;
  2419. dummy: TArrayOfDouble;
  2420. begin
  2421. Result := nil;
  2422. if not GetTextOutlineInternal(x, y,
  2423. text, underlineIdx, arrayOfGlyphs, dummy, nextX) then Exit;
  2424. Result := MergeArrayOfPaths(arrayOfGlyphs);
  2425. end;
  2426. //------------------------------------------------------------------------------
  2427. function TFontCache.GetTextOutline(const rec: TRectD; const text: UnicodeString;
  2428. ta: TTextAlign; tav: TTextVAlign; underlineIdx: integer): TPathsD;
  2429. var
  2430. dummy2, dx, dy: double;
  2431. arrayOfGlyphs: TArrayOfPathsD;
  2432. dummy1: TArrayOfDouble;
  2433. rec2: TRectD;
  2434. begin
  2435. Result := nil;
  2436. if not GetTextOutlineInternal(0, 0, text, underlineIdx,
  2437. arrayOfGlyphs, dummy1, dummy2) or (arrayOfGlyphs = nil) then Exit;
  2438. rec2 := GetBoundsD(arrayOfGlyphs);
  2439. case ta of
  2440. taRight: dx := rec.Right - rec2.Width;
  2441. taCenter: dx := rec.Left + (rec.Width - rec2.Width)/ 2;
  2442. else dx := rec.Left;
  2443. end;
  2444. case tav of
  2445. tvaMiddle: dy := rec.Top - rec2.Top + (rec.Height - rec2.Height)/ 2;
  2446. tvaBottom: dy := rec.Bottom - Descent;
  2447. else dy := rec.Top + Ascent;
  2448. end;
  2449. Result := MergeArrayOfPathsEx(arrayOfGlyphs, dx, dy);
  2450. end;
  2451. //------------------------------------------------------------------------------
  2452. function TFontCache.GetUnderlineOutline(leftX, rightX, y: double;
  2453. dy: double; wavy: Boolean; strokeWidth: double): TPathD;
  2454. var
  2455. i, cnt: integer;
  2456. dx: double;
  2457. wavyPath: TPathD;
  2458. begin
  2459. if strokeWidth <= 0 then
  2460. strokeWidth := LineHeight * lineFrac;
  2461. if dy = InvalidD then
  2462. y := y + 1.5 * (1 + strokeWidth) else
  2463. y := y + dy;
  2464. if wavy then
  2465. begin
  2466. Result := nil;
  2467. cnt := Ceil((rightX - leftX) / (strokeWidth *4));
  2468. if cnt < 2 then Exit;
  2469. dx := (rightX - leftX)/ cnt;
  2470. SetLength(wavyPath, cnt +2);
  2471. wavyPath[0] := PointD(leftX, y + strokeWidth/2);
  2472. wavyPath[1] := PointD(leftX + dx/2, y-(strokeWidth *2));
  2473. for i := 1 to cnt do
  2474. wavyPath[i+1] := PointD(leftX + dx * i, y + strokeWidth/2);
  2475. Result := FlattenQSpline(wavyPath);
  2476. wavyPath := ReversePath(Result);
  2477. wavyPath := TranslatePath(wavyPath, 0, strokeWidth *1.5);
  2478. ConcatPaths(Result, wavyPath);
  2479. end else
  2480. Result := Rectangle(leftX, y, rightX, y + strokeWidth);
  2481. end;
  2482. //------------------------------------------------------------------------------
  2483. function TFontCache.GetVerticalTextOutline(x, y: double;
  2484. const text: UnicodeString; lineHeight: double): TPathsD;
  2485. var
  2486. i, cnt, xxMax: integer;
  2487. glyphInfo: PGlyphInfo;
  2488. dx: double;
  2489. codePoints: TArrayOfCardinal;
  2490. glyphInfos: array of PGlyphInfo;
  2491. begin
  2492. Result := nil;
  2493. if not IsValidFont then Exit;
  2494. codePoints := GetTextCodePoints(text);
  2495. xxMax := 0;
  2496. cnt := Length(codePoints);
  2497. SetLength(glyphInfos, cnt);
  2498. for i := 0 to cnt -1 do
  2499. begin
  2500. glyphInfos[i] := GetGlyphInfo(codePoints[i]);
  2501. if not assigned(glyphInfos[i]) then Exit;
  2502. with glyphInfos[i].glyf do
  2503. if xMax > xxMax then
  2504. xxMax := xMax;
  2505. end;
  2506. if lineHeight = 0.0 then
  2507. lineHeight := self.LineHeight;
  2508. for i := 0 to cnt -1 do
  2509. begin
  2510. glyphInfo := glyphInfos[i];
  2511. with glyphInfo.glyf do
  2512. dx := (xxMax - xMax) * 0.5 * scale;
  2513. AppendPath(Result, TranslatePath(glyphInfo.paths, x + dx, y));
  2514. y := y + lineHeight;
  2515. end;
  2516. UpdateFontReaderLastUsedTime;
  2517. end;
  2518. //------------------------------------------------------------------------------
  2519. function TFontCache.GetTextOutlineInternal(x, y: double;
  2520. const text: UnicodeString; underlineIdx: integer; out glyphs: TArrayOfPathsD;
  2521. out offsets: TArrayOfDouble; out nextX: double): Boolean;
  2522. var
  2523. i,j, len : integer;
  2524. dx,y2,w : double;
  2525. codepoints : TArrayOfCardinal;
  2526. glyphInfo : PGlyphInfo;
  2527. currGlyph : TPathsD;
  2528. prevGlyphKernList: TArrayOfTKern;
  2529. begin
  2530. Result := true;
  2531. codePoints := GetTextCodePoints(text);
  2532. len := Length(codepoints);
  2533. GetMissingGlyphs(codepoints);
  2534. SetLength(offsets, len);
  2535. nextX := x;
  2536. prevGlyphKernList := nil;
  2537. for i := 0 to len -1 do
  2538. begin
  2539. offsets[i] := nextX;
  2540. glyphInfo := GetGlyphInfo(codepoints[i]);
  2541. if not assigned(glyphInfo) then Break;
  2542. if fUseKerning and assigned(prevGlyphKernList) then
  2543. begin
  2544. j := FindInKernList(glyphInfo.glyphIdx, prevGlyphKernList);
  2545. if (j >= 0) then
  2546. nextX := nextX + prevGlyphKernList[j].kernValue * fScale;
  2547. end;
  2548. currGlyph := TranslatePath(glyphInfo.paths, nextX, y);
  2549. dx := glyphInfo.hmtx.advanceWidth * fScale;
  2550. AppendPath(glyphs, currGlyph);
  2551. if not fUnderlined and (underlineIdx -1 = i) then
  2552. begin
  2553. w := LineHeight * lineFrac;
  2554. y2 := y + 1.5 * (1 + w);
  2555. SetLength(currGlyph, 1);
  2556. currGlyph[0] := Rectangle(nextX, y2, nextX +dx, y2 + w);
  2557. AppendPath(glyphs, currGlyph);
  2558. end;
  2559. nextX := nextX + dx;
  2560. prevGlyphKernList := glyphInfo.kernList;
  2561. end;
  2562. if fUnderlined then
  2563. begin
  2564. w := LineHeight * lineFrac;
  2565. y2 := y + 1.5 * (1 + w);
  2566. SetLength(currGlyph, 1);
  2567. currGlyph[0] := Rectangle(x, y2, nextX, y2 + w);
  2568. AppendPath(glyphs, currGlyph);
  2569. end;
  2570. if fStrikeOut then
  2571. begin
  2572. w := LineHeight * lineFrac;
  2573. y2 := y - LineHeight * 0.22;
  2574. SetLength(currGlyph, 1);
  2575. currGlyph[0] := Rectangle(x, y2, nextX, y2 + w);
  2576. AppendPath(glyphs, currGlyph);
  2577. end;
  2578. UpdateFontReaderLastUsedTime;
  2579. end;
  2580. //------------------------------------------------------------------------------
  2581. function TFontCache.GetAngledTextGlyphs(x, y: double;
  2582. const text: UnicodeString; angleRadians: double;
  2583. const rotatePt: TPointD; out nextPt: TPointD): TPathsD;
  2584. begin
  2585. nextPt.Y := y;
  2586. Result := GetTextOutline(x,y, text, nextPt.X);
  2587. if not Assigned(Result) then Exit;
  2588. Result := RotatePath(Result, rotatePt, angleRadians);
  2589. RotatePoint(nextPt, PointD(x,y), angleRadians);
  2590. UpdateFontReaderLastUsedTime;
  2591. end;
  2592. //------------------------------------------------------------------------------
  2593. procedure TFontCache.UpdateFontReaderLastUsedTime;
  2594. begin
  2595. if Assigned(fFontReader) then
  2596. fFontReader.LastUsedTime := now;
  2597. end;
  2598. //------------------------------------------------------------------------------
  2599. procedure TFontCache.SetFontReader(newFontReader: TFontReader);
  2600. begin
  2601. if newFontReader = fFontReader then Exit;
  2602. if Assigned(fFontReader) then
  2603. begin
  2604. fFontReader.DeleteRecipient(self as INotifyRecipient);
  2605. Clear;
  2606. end;
  2607. fFontReader := newFontReader;
  2608. if Assigned(fFontReader) then
  2609. fFontReader.AddRecipient(self as INotifyRecipient);
  2610. UpdateScale;
  2611. end;
  2612. //------------------------------------------------------------------------------
  2613. procedure TFontCache.UpdateScale;
  2614. begin
  2615. if IsValidFont and (fFontHeight > 0) then
  2616. fScale := fFontHeight / fFontReader.FontInfo.unitsPerEm else
  2617. fScale := 1;
  2618. NotifyRecipients(inStateChange);
  2619. end;
  2620. //------------------------------------------------------------------------------
  2621. procedure TFontCache.SetFontHeight(newHeight: double);
  2622. begin
  2623. newHeight := abs(newHeight); // manage point - pixel conversions externally
  2624. if fFontHeight = newHeight then Exit;
  2625. fFontHeight := newHeight;
  2626. Clear;
  2627. UpdateScale;
  2628. end;
  2629. //------------------------------------------------------------------------------
  2630. procedure FlipVert(var paths: TPathsD);
  2631. var
  2632. i,j: integer;
  2633. begin
  2634. for i := 0 to High(paths) do
  2635. for j := 0 to High(paths[i]) do
  2636. paths[i][j].Y := -paths[i][j].Y;
  2637. end;
  2638. //------------------------------------------------------------------------------
  2639. procedure TFontCache.SetFlipVert(value: Boolean);
  2640. var
  2641. i: integer;
  2642. glyphInfo: PGlyphInfo;
  2643. begin
  2644. if fFlipVert = value then Exit;
  2645. for i := 0 to fGlyphInfoList.Count -1 do
  2646. begin
  2647. glyphInfo := PGlyphInfo(fGlyphInfoList[i]);
  2648. FlipVert(glyphInfo.paths);
  2649. end;
  2650. fFlipVert := value;
  2651. end;
  2652. //------------------------------------------------------------------------------
  2653. function GlyphSorter(glyph1, glyph2: pointer): integer;
  2654. begin
  2655. Result := PGlyphInfo(glyph1).codepoint - PGlyphInfo(glyph2).codepoint;
  2656. end;
  2657. //------------------------------------------------------------------------------
  2658. procedure TFontCache.Sort;
  2659. begin
  2660. {$IFDEF XPLAT_GENERICS}
  2661. fGlyphInfoList.Sort(TComparer<PGlyphInfo>.Construct(
  2662. function (const glyph1, glyph2: PGlyphInfo): integer
  2663. begin
  2664. Result := glyph1.codepoint - glyph2.codepoint;
  2665. end));
  2666. {$ELSE}
  2667. fGlyphInfoList.Sort(GlyphSorter);
  2668. {$ENDIF}
  2669. fSorted := true;
  2670. end;
  2671. //------------------------------------------------------------------------------
  2672. function TFontCache.AddGlyph(codepoint: Cardinal): PGlyphInfo;
  2673. var
  2674. dummy: integer;
  2675. altFontReader: TFontReader;
  2676. glyphIdx: WORD;
  2677. scale: double;
  2678. const
  2679. minLength = 0.1;
  2680. begin
  2681. New(Result);
  2682. Result.codepoint := codepoint;
  2683. if not fFontReader.GetGlyphInfo(codepoint, dummy, Result^) or
  2684. (Result.glyphIdx = 0) then
  2685. begin
  2686. // to get here the unicode char is not supported by fFontReader
  2687. altFontReader :=
  2688. aFontManager.FindReaderContainingGlyph(codepoint, tfUnknown, glyphIdx);
  2689. if Assigned(altFontReader) then
  2690. begin
  2691. altFontReader.GetGlyphInfo(codepoint, dummy, Result^);
  2692. altFontReader.LastUsedTime := now;
  2693. scale := fFontReader.FontInfo.unitsPerEm / altFontReader.FontInfo.unitsPerEm;
  2694. if scale <> 1.0 then
  2695. Result.paths := ScalePath(Result.paths, scale);
  2696. end;
  2697. end;
  2698. fGlyphInfoList.Add(Result);
  2699. if fFontHeight > 0 then
  2700. begin
  2701. Result.paths := ScalePath(Result.paths, fScale);
  2702. // text rendering is about twice as fast when excess detail is removed
  2703. Result.paths := StripNearDuplicates(Result.paths, minLength, true);
  2704. end;
  2705. if fFlipVert then VerticalFlip(Result.paths);
  2706. fSorted := false;
  2707. end;
  2708. //------------------------------------------------------------------------------
  2709. //------------------------------------------------------------------------------
  2710. function AppendSlash(const foldername: string): string;
  2711. begin
  2712. Result := foldername;
  2713. if (Result = '') or (Result[Length(Result)] = '\') then Exit;
  2714. Result := Result + '\';
  2715. end;
  2716. //------------------------------------------------------------------------------
  2717. {$IFDEF MSWINDOWS}
  2718. procedure FontHeightToFontSize(var logFontHeight: integer);
  2719. const
  2720. _72Div96 = 72/96;
  2721. begin
  2722. if logFontHeight < 0 then
  2723. logFontHeight := -Round(logFontHeight * _72Div96 / dpiAware1);
  2724. end;
  2725. //------------------------------------------------------------------------------
  2726. procedure FontSizeToFontHeight(var logFontHeight: integer);
  2727. const
  2728. _96Div72 = 96/72;
  2729. begin
  2730. if logFontHeight > 0 then
  2731. logFontHeight := -Round(DpiAware(logFontHeight * _96Div72));
  2732. end;
  2733. //------------------------------------------------------------------------------
  2734. function GetFontPixelHeight(logFontHeight: integer): double;
  2735. const
  2736. _96Div72 = 96/72;
  2737. begin
  2738. if logFontHeight > 0 then
  2739. Result := DPIAware(logFontHeight * _96Div72) else
  2740. Result := DPIAware(-logFontHeight);
  2741. end;
  2742. //------------------------------------------------------------------------------
  2743. function GetFontFolder: string;
  2744. var
  2745. pidl: PItemIDList;
  2746. path: array[0..MAX_PATH] of char;
  2747. begin
  2748. SHGetSpecialFolderLocation(0, CSIDL_FONTS, pidl);
  2749. SHGetPathFromIDList(pidl, path);
  2750. CoTaskMemFree(pidl);
  2751. result := path;
  2752. end;
  2753. //------------------------------------------------------------------------------
  2754. function GetInstalledTtfFilenames: TArrayOfString;
  2755. var
  2756. cnt, buffLen: integer;
  2757. fontFolder: string;
  2758. sr: TSearchRec;
  2759. res: integer;
  2760. begin
  2761. cnt := 0; buffLen := 1024;
  2762. SetLength(Result, buffLen);
  2763. fontFolder := AppendSlash(GetFontFolder);
  2764. res := FindFirst(fontFolder + '*.ttf', faAnyFile, sr);
  2765. while res = 0 do
  2766. begin
  2767. if cnt = buffLen then
  2768. begin
  2769. inc(buffLen, 128);
  2770. SetLength(Result, buffLen);
  2771. end;
  2772. Result[cnt] := fontFolder + sr.Name;
  2773. inc(cnt);
  2774. res := FindNext(sr);
  2775. end;
  2776. FindClose(sr);
  2777. SetLength(Result, cnt);
  2778. end;
  2779. //------------------------------------------------------------------------------
  2780. function EnumFontProc(LogFont: PEnumLogFontEx; TextMetric: PNewTextMetric;
  2781. FontType: DWORD; userDefined: LPARAM): Integer; stdcall;
  2782. var
  2783. len: integer;
  2784. alf: PArrayOfEnumLogFontEx absolute userDefined;
  2785. begin
  2786. if (FontType = TRUETYPE_FONTTYPE) then
  2787. begin
  2788. len := Length(alf^);
  2789. SetLength(alf^, len +1);
  2790. Move(LogFont^, alf^[len], SizeOf(TEnumLogFontEx));
  2791. end;
  2792. Result := 1;
  2793. end;
  2794. //------------------------------------------------------------------------------
  2795. function GetLogFonts(const faceName: string; charSet: byte): TArrayOfEnumLogFontEx;
  2796. var
  2797. lf: TLogFont;
  2798. dc: HDC;
  2799. begin
  2800. Result := nil;
  2801. if faceName = '' then Exit;
  2802. FillChar(lf, sizeof(lf), 0);
  2803. lf.lfCharSet := charSet;
  2804. Move(faceName[1], lf.lfFaceName[0], Length(faceName) * SizeOf(Char));
  2805. dc := CreateCompatibleDC(0);
  2806. try
  2807. EnumFontFamiliesEx(dc, lf, @EnumFontProc, LParam(@Result), 0);
  2808. finally
  2809. DeleteDC(dc);
  2810. end;
  2811. end;
  2812. //------------------------------------------------------------------------------
  2813. function GetLogFontFromEnumThatMatchesStyles(LogFonts: TArrayOfEnumLogFontEx;
  2814. styles: TMacStyles; out logFont: TLogFont): Boolean;
  2815. var
  2816. i: integer;
  2817. styles2: TMacStyles;
  2818. begin
  2819. Result := False;
  2820. if not Assigned(LogFonts) then Exit;
  2821. for i := 0 to High(LogFonts) do
  2822. begin
  2823. styles2 := [];
  2824. if LogFonts[i].elfLogFont.lfWeight > 500 then Include(styles2, msBold);
  2825. if LogFonts[i].elfLogFont.lfItalic <> 0 then Include(styles2, msItalic);
  2826. if styles <> styles2 then Continue;
  2827. logFont := LogFonts[i].elfLogFont;
  2828. Result := true;
  2829. Exit;
  2830. end;
  2831. end;
  2832. //------------------------------------------------------------------------------
  2833. {$ENDIF}
  2834. //------------------------------------------------------------------------------
  2835. //------------------------------------------------------------------------------
  2836. function DrawText(image: TImage32; x, y: double; const text: UnicodeString;
  2837. font: TFontCache; textColor: TColor32 = clBlack32): double;
  2838. var
  2839. glyphs: TPathsD;
  2840. begin
  2841. Result := 0;
  2842. if (text = '') or not assigned(font) or not font.IsValidFont then Exit;
  2843. glyphs := font.GetTextOutline(x,y, text, Result);
  2844. DrawPolygon(image, glyphs, frNonZero, textColor);
  2845. end;
  2846. //------------------------------------------------------------------------------
  2847. function DrawText(image: TImage32; x, y: double; const text: UnicodeString;
  2848. font: TFontCache; renderer: TCustomRenderer): double;
  2849. var
  2850. glyphs: TPathsD;
  2851. begin
  2852. Result := 0;
  2853. if (text = '') or not assigned(font) or
  2854. not font.IsValidFont then Exit;
  2855. glyphs := font.GetTextOutline(x,y, text, Result);
  2856. DrawPolygon(image, glyphs, frNonZero, renderer);
  2857. end;
  2858. //------------------------------------------------------------------------------
  2859. procedure DrawText(image: TImage32; const rec: TRectD;
  2860. const text: UnicodeString; font: TFontCache;
  2861. textColor: TColor32 = clBlack32; align: TTextAlign = taCenter;
  2862. valign: TTextVAlign = tvaMiddle);
  2863. var
  2864. glyphs: TPathsD;
  2865. dx,dy: double;
  2866. rec2: TRectD;
  2867. chunkedText: TChunkedText;
  2868. begin
  2869. if (text = '') or not assigned(font) or not font.IsValidFont then Exit;
  2870. if align = taJustify then
  2871. begin
  2872. chunkedText := TChunkedText.Create(text, font, textColor);
  2873. try
  2874. chunkedText.DrawText( image, Rect(rec), taJustify, valign, 0);
  2875. finally
  2876. chunkedText.Free;
  2877. end;
  2878. Exit;
  2879. end;
  2880. glyphs := font.GetTextOutline(0,0, text);
  2881. rec2 := GetBoundsD(glyphs);
  2882. case align of
  2883. taRight: dx := rec.Right - rec2.Right;
  2884. taCenter: dx := (rec.Left + rec.Right - rec2.Right) * 0.5;
  2885. else dx := rec.Left;
  2886. end;
  2887. case valign of
  2888. tvaMiddle: dy := (rec.Top + rec.Bottom - rec2.Top) * 0.5;
  2889. tvaBottom: dy := rec.Bottom - rec2.Bottom;
  2890. else dy := rec.Top + font.Ascent;
  2891. end;
  2892. glyphs := TranslatePath(glyphs, dx, dy);
  2893. DrawPolygon(image, glyphs, frNonZero, textColor);
  2894. end;
  2895. //------------------------------------------------------------------------------
  2896. function DrawAngledText(image: TImage32;
  2897. x, y: double; angleRadians: double;
  2898. const text: UnicodeString; font: TFontCache;
  2899. textColor: TColor32 = clBlack32): TPointD;
  2900. var
  2901. glyphs: TPathsD;
  2902. rotatePt: TPointD;
  2903. begin
  2904. rotatePt := PointD(x,y);
  2905. if not assigned(font) or not font.IsValidFont then
  2906. begin
  2907. Result := NullPointD;
  2908. Exit;
  2909. end;
  2910. glyphs := font.GetAngledTextGlyphs(x, y,
  2911. text, angleRadians, rotatePt, Result);
  2912. DrawPolygon(image, glyphs, frNonZero, textColor);
  2913. end;
  2914. //------------------------------------------------------------------------------
  2915. procedure DrawVerticalText(image: TImage32; x, y: double;
  2916. const text: UnicodeString; font: TFontCache;
  2917. lineHeight: double; textColor: TColor32);
  2918. var
  2919. glyphs: TPathsD;
  2920. cr: TCustomRenderer;
  2921. begin
  2922. if not assigned(font) or not font.IsValidFont or (text = '') then Exit;
  2923. glyphs := font.GetVerticalTextOutline(x,y, text, lineHeight);
  2924. if image.AntiAliased then
  2925. cr := TColorRenderer.Create(textColor) else
  2926. cr := TAliasedColorRenderer.Create(textColor);
  2927. try
  2928. DrawPolygon(image, glyphs, frNonZero, cr);
  2929. finally
  2930. cr.Free;
  2931. end;
  2932. end;
  2933. //------------------------------------------------------------------------------
  2934. function FindLastSpace(const text: string; StartAt: integer): integer;
  2935. begin
  2936. Result := StartAt;
  2937. while (Result > 0) and (text[Result] <> SPACE) do Dec(Result);
  2938. end;
  2939. //------------------------------------------------------------------------------
  2940. function GetTextOutlineOnPath(const text: UnicodeString;
  2941. const path: TPathD; font: TFontCache; textAlign: TTextAlign;
  2942. x, y: double; charSpacing: double;
  2943. out charsThatFit: integer; out outX: double): TPathsD;
  2944. var
  2945. pathLen, pathLenMin1: integer;
  2946. cummDists: TArrayOfDouble; // cummulative distances
  2947. i, currentPathIdx: integer;
  2948. textWidth, glyphCenterX, glyphCenterOnPath, dist, dx: double;
  2949. glyph: PGlyphInfo;
  2950. CharOffsets: TArrayOfDouble;
  2951. unitVector: TPointD;
  2952. tmpPaths: TPathsD;
  2953. begin
  2954. Result := nil;
  2955. pathLen := Length(path);
  2956. pathLenMin1 := pathLen -1;
  2957. charsThatFit := Length(text);
  2958. if (pathLen < 2) or (charsThatFit = 0) then Exit;
  2959. CharOffsets := font.GetGlyphOffsets(text, charSpacing);
  2960. textWidth := CharOffsets[charsThatFit];
  2961. setLength(cummDists, pathLen +1);
  2962. cummDists[0] := 0;
  2963. dist := 0;
  2964. for i:= 1 to pathLen -1 do
  2965. begin
  2966. dist := dist + Distance(path[i-1], path[i]);
  2967. cummDists[i] := dist;
  2968. end;
  2969. // truncate text that doesn't fit ...
  2970. if textWidth > dist then
  2971. begin
  2972. Dec(charsThatFit);
  2973. while CharOffsets[charsThatFit] > dist do Dec(charsThatFit);
  2974. // if possible, break text at a SPACE char
  2975. i := FindLastSpace(text, charsThatFit);
  2976. if i > 0 then charsThatFit := i;
  2977. end;
  2978. case textAlign of
  2979. taCenter: x := (dist - textWidth) * 0.5;
  2980. taRight : x := dist - textWidth;
  2981. // else use user defined starting x
  2982. end;
  2983. Result := nil;
  2984. currentPathIdx := 0;
  2985. for i := 1 to charsThatFit do
  2986. begin
  2987. glyph := font.GetGlyphInfo(Ord(text[i]));
  2988. with glyph^ do
  2989. glyphCenterX := (glyf.xMax - glyf.xMin) * font.Scale * 0.5;
  2990. glyphCenterOnPath := x + glyphCenterX;
  2991. while (currentPathIdx < pathLenMin1) and
  2992. (cummDists[currentPathIdx +1] < glyphCenterOnPath) do
  2993. inc(currentPathIdx);
  2994. if currentPathIdx = pathLenMin1 then
  2995. begin
  2996. charsThatFit := i; // nb 1 base vs 0 base :)
  2997. Break;
  2998. end;
  2999. x := x + glyph.hmtx.advanceWidth * font.Scale + charSpacing;
  3000. unitVector := GetUnitVector(path[currentPathIdx], path[currentPathIdx +1]);
  3001. tmpPaths := RotatePath(glyph.paths,
  3002. PointD(glyphCenterX, -y), GetAngle(NullPointD, unitVector));
  3003. dx := glyphCenterOnPath - cummDists[currentPathIdx];
  3004. tmpPaths := TranslatePath(tmpPaths,
  3005. path[currentPathIdx].X + unitVector.X * dx - glyphCenterX,
  3006. path[currentPathIdx].Y + unitVector.Y * dx + y);
  3007. AppendPath(Result, tmpPaths);
  3008. end;
  3009. outX := x;
  3010. end;
  3011. //------------------------------------------------------------------------------
  3012. function GetTextOutlineOnPath(const text: UnicodeString;
  3013. const path: TPathD; font: TFontCache; textAlign: TTextAlign;
  3014. perpendicOffset: integer; charSpacing: double;
  3015. out charsThatFit: integer): TPathsD;
  3016. var
  3017. dummy: double;
  3018. begin
  3019. Result := GetTextOutlineOnPath(text, path, font, textAlign,
  3020. 0, perpendicOffset, charSpacing, charsThatFit, dummy);
  3021. end;
  3022. //------------------------------------------------------------------------------
  3023. function GetTextOutlineOnPath(const text: UnicodeString;
  3024. const path: TPathD; font: TFontCache; textAlign: TTextAlign;
  3025. perpendicOffset: integer = 0; charSpacing: double = 0): TPathsD;
  3026. var
  3027. dummy: integer;
  3028. begin
  3029. Result := GetTextOutlineOnPath(text, path, font, textAlign,
  3030. perpendicOffset, charSpacing, dummy);
  3031. end;
  3032. //------------------------------------------------------------------------------
  3033. function GetTextOutlineOnPath(const text: UnicodeString;
  3034. const path: TPathD; font: TFontCache; x, y: integer;
  3035. charSpacing: double; out outX: double): TPathsD;
  3036. var
  3037. dummy: integer;
  3038. begin
  3039. Result := GetTextOutlineOnPath(text, path, font, taLeft,
  3040. x, y, charSpacing, dummy, outX);
  3041. end;
  3042. //------------------------------------------------------------------------------
  3043. // TTextChunk class
  3044. //------------------------------------------------------------------------------
  3045. constructor TTextChunk.Create(owner: TChunkedText; const chunk: UnicodeString;
  3046. index: integer; fontCache: TFontCache; fontColor, backColor: TColor32);
  3047. var
  3048. i, listCnt: integer;
  3049. begin
  3050. Self.owner := owner;
  3051. listCnt := owner.fList.Count;
  3052. if index < 0 then index := 0
  3053. else if index > listCnt then index := listCnt;
  3054. self.index := index;
  3055. self.text := chunk;
  3056. self.fontColor := fontColor;
  3057. self.backColor := backColor;
  3058. if Assigned(fontCache) then
  3059. begin
  3060. fontCache.GetTextOutlineInternal(0,0,
  3061. chunk, 0, self.arrayOfPaths, self.glyphOffsets, self.width);
  3062. self.height := fontCache.LineHeight;
  3063. self.ascent := fontCache.Ascent;
  3064. end else
  3065. begin
  3066. self.arrayOfPaths := nil;
  3067. SetLength(self.glyphOffsets, 1);
  3068. self.glyphOffsets[0] := 0;
  3069. self.width := 0;
  3070. self.height := 0;
  3071. self.ascent := 0;
  3072. end;
  3073. owner.fList.Insert(index, self);
  3074. // reindex any trailing chunks
  3075. if index < listCnt then
  3076. for i := index +1 to listCnt do
  3077. TTextChunk(owner.fList[i]).index := i;
  3078. end;
  3079. //------------------------------------------------------------------------------
  3080. // TChunkedText
  3081. //------------------------------------------------------------------------------
  3082. constructor TChunkedText.Create;
  3083. begin
  3084. inherited;
  3085. {$IFDEF XPLAT_GENERICS}
  3086. fList := TList<TTextChunk>.Create;
  3087. {$ELSE}
  3088. fList := TList.Create;
  3089. {$ENDIF}
  3090. end;
  3091. //------------------------------------------------------------------------------
  3092. constructor TChunkedText.Create(const text: string; font: TFontCache;
  3093. fontColor: TColor32; backColor: TColor32);
  3094. begin
  3095. Create;
  3096. SetText(text, font, fontColor, backColor);
  3097. end;
  3098. //------------------------------------------------------------------------------
  3099. destructor TChunkedText.Destroy;
  3100. begin
  3101. Clear;
  3102. fList.Free;
  3103. inherited;
  3104. end;
  3105. //------------------------------------------------------------------------------
  3106. function TChunkedText.GetChunk(index: integer): TTextChunk;
  3107. begin
  3108. if (index < 0) or (index >= fList.Count) then
  3109. raise Exception.Create(rsChunkedTextRangeError);
  3110. Result := TTextChunk(fList.Items[index]);
  3111. end;
  3112. //------------------------------------------------------------------------------
  3113. function TChunkedText.GetText: UnicodeString;
  3114. var
  3115. i: integer;
  3116. begin
  3117. Result := '';
  3118. for i := 0 to Count -1 do
  3119. Result := Result + TTextChunk(fList.Items[i]).text;
  3120. end;
  3121. //------------------------------------------------------------------------------
  3122. procedure TChunkedText.AddNewline(font: TFontCache);
  3123. var
  3124. nlChunk: TTextChunk;
  3125. begin
  3126. if not Assigned(font) or not font.IsValidFont then
  3127. raise Exception.Create(rsChunkedTextFontError);
  3128. if (fLastFont = font) then
  3129. begin
  3130. // this is much faster as it bypasses font.GetTextOutlineInternal
  3131. nlChunk := InsertTextChunk(nil, MaxInt, #10, clNone32);
  3132. nlChunk.height := fLastFont.LineHeight;
  3133. nlChunk.ascent := fLastFont.Ascent;
  3134. end else
  3135. begin
  3136. nlChunk := InsertTextChunk(font, MaxInt, SPACE, clNone32);
  3137. nlChunk.text := #10;
  3138. fSpaceWidth := nlChunk.width;
  3139. fLastFont := font;
  3140. end;
  3141. end;
  3142. //------------------------------------------------------------------------------
  3143. procedure TChunkedText.AddSpace(font: TFontCache);
  3144. var
  3145. spaceChunk: TTextChunk;
  3146. begin
  3147. if not Assigned(font) or not font.IsValidFont then
  3148. raise Exception.Create(rsChunkedTextFontError);
  3149. if (fLastFont = font) then
  3150. begin
  3151. // this is much faster as it bypasses font.GetTextOutlineInternal
  3152. spaceChunk := InsertTextChunk(nil, MaxInt, SPACE, clNone32);
  3153. spaceChunk.width := fSpaceWidth;
  3154. spaceChunk.height := fLastFont.LineHeight;
  3155. spaceChunk.ascent := fLastFont.Ascent;
  3156. end else
  3157. begin
  3158. spaceChunk := InsertTextChunk(font, MaxInt, SPACE, clNone32);
  3159. fLastFont := font;
  3160. fSpaceWidth := spaceChunk.width;
  3161. end;
  3162. end;
  3163. //------------------------------------------------------------------------------
  3164. function TChunkedText.AddTextChunk(font: TFontCache; const chunk: UnicodeString;
  3165. fontColor: TColor32; backColor: TColor32): TTextChunk;
  3166. begin
  3167. Result := InsertTextChunk(font, MaxInt, chunk, fontColor, backColor);
  3168. end;
  3169. //------------------------------------------------------------------------------
  3170. function TChunkedText.InsertTextChunk(font: TFontCache; index: integer;
  3171. const chunk: UnicodeString; fontColor: TColor32;
  3172. backColor: TColor32): TTextChunk;
  3173. begin
  3174. Result := TTextChunk.Create(self, chunk, index, font, fontColor, backColor);
  3175. end;
  3176. //------------------------------------------------------------------------------
  3177. function TChunkedText.GetCount: integer;
  3178. begin
  3179. Result := fList.Count;
  3180. end;
  3181. //------------------------------------------------------------------------------
  3182. procedure TChunkedText.Clear;
  3183. var
  3184. i: integer;
  3185. begin
  3186. for i := 0 to fList.Count -1 do
  3187. TTextChunk(fList.Items[i]).Free;
  3188. fList.Clear;
  3189. end;
  3190. //------------------------------------------------------------------------------
  3191. procedure TChunkedText.DeleteChunk(Index: Integer);
  3192. var
  3193. i: integer;
  3194. begin
  3195. if (index < 0) or (index >= fList.Count) then
  3196. raise Exception.Create(rsChunkedTextRangeError);
  3197. TTextChunk(fList.Items[index]).Free;
  3198. fList.Delete(index);
  3199. // reindex
  3200. for i := Index to fList.Count -1 do
  3201. dec(TTextChunk(fList.Items[i]).index);
  3202. end;
  3203. //------------------------------------------------------------------------------
  3204. procedure TChunkedText.DeleteChunkRange(startIdx, endIdx: Integer);
  3205. var
  3206. i, cnt: Integer;
  3207. begin
  3208. cnt := endIdx - startIdx +1;
  3209. if (startIdx < 0) or (endIdx >= fList.Count) or (cnt <= 0) then
  3210. raise Exception.Create(rsChunkedTextRangeError);
  3211. for i := startIdx to endIdx do
  3212. TTextChunk(fList.Items[i]).Free;
  3213. // reindex
  3214. for i := startIdx to fList.Count -1 do
  3215. dec(TTextChunk(fList.Items[i]).index, cnt);
  3216. end;
  3217. //------------------------------------------------------------------------------
  3218. procedure TChunkedText.SetText(const text: UnicodeString;
  3219. font: TFontCache; fontColor: TColor32; backColor: TColor32);
  3220. var
  3221. len: integer;
  3222. p, p2, pEnd: PWideChar;
  3223. s: UnicodeString;
  3224. begin
  3225. if not Assigned(font) then Exit;
  3226. Clear;
  3227. p := PWideChar(text);
  3228. pEnd := p;
  3229. Inc(pEnd, Length(text));
  3230. while p < pEnd do
  3231. begin
  3232. if (p^ <= SPACE) then
  3233. begin
  3234. if (p^ = SPACE) then AddSpace(font)
  3235. else if (p^ = #10) then AddNewline(font);
  3236. inc(p);
  3237. end else
  3238. begin
  3239. p2 := p;
  3240. inc(p);
  3241. while (p < pEnd) and (p^ > SPACE) do inc(p);
  3242. len := p - p2;
  3243. SetLength(s, len);
  3244. Move(p2^, s[1], len * SizeOf(Char));
  3245. AddTextChunk(font, s, fontColor, backColor);
  3246. end;
  3247. end;
  3248. end;
  3249. //------------------------------------------------------------------------------
  3250. function TChunkedText.GetPageMetrics(const rec: TRect; lineHeight: double;
  3251. startingChunkIdx: integer): TPageTextMetrics;
  3252. var
  3253. pageWidth, pageHeight : integer;
  3254. lh, priorSplitWidth : double;
  3255. currentX : double;
  3256. arrayCnt, arrayCap : integer;
  3257. chunkIdxAtStartOfLine : integer;
  3258. currentChunkIdx : integer;
  3259. linesFinished : Boolean;
  3260. procedure SetResultLength(len: integer);
  3261. begin
  3262. SetLength(Result.startOfLineIdx, len);
  3263. SetLength(Result.justifyDeltas, len);
  3264. SetLength(Result.lineWidths, len);
  3265. end;
  3266. procedure CheckArrayCap;
  3267. begin
  3268. if arrayCnt < arrayCap then Exit;
  3269. inc(arrayCap, 16);
  3270. SetResultLength(arrayCap);
  3271. end;
  3272. function IsRoomForCurrentLine: Boolean;
  3273. begin
  3274. Result := (arrayCnt + 1) * lh <= pageHeight;
  3275. end;
  3276. function CheckLineHeight(currentChunk: TTextChunk): Boolean;
  3277. begin
  3278. // unless a user-defined lineHeight has been assigned (lineHeight > 0),
  3279. // get the largest lineHeight of all displayed chunks and use that
  3280. // lineHeight for *every* line that's being displayed ...
  3281. if (lineHeight = 0) and (currentChunk.height > lh) then
  3282. begin
  3283. // first make sure that this chunk will fit
  3284. Result := (arrayCnt + 1) * currentChunk.height <= pageHeight;
  3285. if Result then lh := currentChunk.height;
  3286. end else
  3287. Result := IsRoomForCurrentLine;
  3288. end;
  3289. procedure AddLine;
  3290. var
  3291. i, spcCnt, ChunkIdxAtEndOfLine: integer;
  3292. x: double;
  3293. chnk: TTextChunk;
  3294. begin
  3295. CheckArrayCap;
  3296. ChunkIdxAtEndOfLine := currentChunkIdx -1;
  3297. // ignore spaces at the end of lines
  3298. while (ChunkIdxAtEndOfLine > chunkIdxAtStartOfLine) and
  3299. (Chunk[ChunkIdxAtEndOfLine].text = SPACE) do
  3300. Dec(ChunkIdxAtEndOfLine);
  3301. x := -priorSplitWidth; spcCnt := 0;
  3302. for i := chunkIdxAtStartOfLine to ChunkIdxAtEndOfLine do
  3303. begin
  3304. chnk := Chunk[i];
  3305. if chnk.text = SPACE then inc(spcCnt);
  3306. x := x + chnk.width;
  3307. end;
  3308. Result.lineWidths[arrayCnt] := x;
  3309. Result.lineHeight := lh;
  3310. Result.startOfLineIdx[arrayCnt] := chunkIdxAtStartOfLine;
  3311. if spcCnt = 0 then
  3312. Result.justifyDeltas[arrayCnt] := 0 else
  3313. Result.justifyDeltas[arrayCnt] := (pageWidth - x)/spcCnt;
  3314. inc(arrayCnt);
  3315. chunkIdxAtStartOfLine := currentChunkIdx;
  3316. currentX := 0;
  3317. priorSplitWidth := 0;
  3318. end;
  3319. procedure AddSplitChunkLines(glyphOffset: integer);
  3320. var
  3321. highI: integer;
  3322. residualWidth: double;
  3323. chnk: TTextChunk;
  3324. begin
  3325. chnk := Chunk[chunkIdxAtStartOfLine];
  3326. priorSplitWidth := chnk.glyphOffsets[glyphOffset];
  3327. highI := High(chnk.glyphOffsets);
  3328. residualWidth := chnk.width - priorSplitWidth;
  3329. while (highI >= glyphOffset) and (residualWidth > pageWidth) do
  3330. begin
  3331. residualWidth := chnk.glyphOffsets[highI] - priorSplitWidth;
  3332. Dec(highI);
  3333. end;
  3334. if highI < glyphOffset then
  3335. begin
  3336. // oops, even a single character won't fit !!
  3337. linesFinished := true;
  3338. currentChunkIdx := chunkIdxAtStartOfLine;
  3339. end
  3340. else if not IsRoomForCurrentLine then
  3341. begin
  3342. linesFinished := true;
  3343. currentChunkIdx := chunkIdxAtStartOfLine;
  3344. end
  3345. else
  3346. begin
  3347. CheckArrayCap;
  3348. Result.lineWidths[arrayCnt] := residualWidth;
  3349. Result.lineHeight := lh;
  3350. Result.startOfLineIdx[arrayCnt] := chunkIdxAtStartOfLine;
  3351. Result.justifyDeltas[arrayCnt] := 0;
  3352. if (highI = High(chnk.glyphOffsets)) then
  3353. begin
  3354. currentX := residualWidth;
  3355. inc(currentChunkIdx);
  3356. end else
  3357. begin
  3358. inc(arrayCnt);
  3359. AddSplitChunkLines(highI +1); // note recursion
  3360. end;
  3361. end;
  3362. end;
  3363. var
  3364. chnk: TTextChunk;
  3365. begin
  3366. FillChar(Result, SizeOf(Result), 0);
  3367. arrayCnt := 0; arrayCap := 0;
  3368. if (startingChunkIdx < 0) then startingChunkIdx := 0;
  3369. if (Count = 0) or (startingChunkIdx >= Count) then Exit;
  3370. lh := lineHeight;
  3371. RectWidthHeight(rec, pageWidth, pageHeight);
  3372. currentChunkIdx := startingChunkIdx;
  3373. chunkIdxAtStartOfLine := startingChunkIdx;
  3374. currentX := 0;
  3375. priorSplitWidth := 0;
  3376. linesFinished := false;
  3377. while (currentChunkIdx < Count) do
  3378. begin
  3379. chnk := Chunk[currentChunkIdx];
  3380. if not CheckLineHeight(chnk) then break;
  3381. if (chnk.text = #10) then
  3382. begin
  3383. AddLine;
  3384. if arrayCnt > 0 then
  3385. Result.justifyDeltas[arrayCnt-1] := 0;
  3386. inc(currentChunkIdx);
  3387. chunkIdxAtStartOfLine := currentChunkIdx;
  3388. end
  3389. else if (currentX + chnk.width > pageWidth) then
  3390. begin
  3391. if (currentChunkIdx = chunkIdxAtStartOfLine) then
  3392. begin
  3393. // a single chunk is too wide for 'pageWidth'
  3394. AddSplitChunkLines(0);
  3395. if linesFinished or (currentChunkIdx = Count) then Break;
  3396. end else
  3397. begin
  3398. AddLine;
  3399. // don't allow spaces to wrap to the front of the following line
  3400. while (currentChunkIdx < Count) and
  3401. (self.chunk[currentChunkIdx].text = SPACE) do
  3402. inc(currentChunkIdx);
  3403. chunkIdxAtStartOfLine := currentChunkIdx;
  3404. end;
  3405. end else
  3406. begin
  3407. currentX := currentX + chnk.width;
  3408. inc(currentChunkIdx);
  3409. end;
  3410. end;
  3411. if not linesFinished and
  3412. (currentChunkIdx > chunkIdxAtStartOfLine) then AddLine;
  3413. Result.lineCount := arrayCnt;
  3414. SetResultLength(arrayCnt);
  3415. Result.nextChuckIdx := currentChunkIdx;
  3416. if (arrayCnt > 0) and (Result.nextChuckIdx = Count) then
  3417. Result.justifyDeltas[arrayCnt-1] := 0;
  3418. end;
  3419. //------------------------------------------------------------------------------
  3420. function TChunkedText.GetChunkAndGlyphOffsetAtPt(const ptm: TPageTextMetrics;
  3421. const pt: TPoint; out glyphIdx, chunkChrOff: integer): Boolean;
  3422. var
  3423. x,y, maxY, maxIdx: integer;
  3424. x2 : Double;
  3425. chnk: TTextChunk;
  3426. begin
  3427. Result := false;
  3428. x := pt.X - ptm.bounds.Left;
  3429. y := Trunc((pt.Y - ptm.bounds.Top - ptm.topLinePxOffset) / ptm.lineHeight);
  3430. maxY := ptm.lineCount -1;
  3431. if (x < 0) or (x > ptm.bounds.right - ptm.bounds.Left) or
  3432. (y < 0) or (y > maxY) then Exit;
  3433. if y = maxY then
  3434. maxIdx := ptm.nextChuckIdx -1 else
  3435. maxIdx := ptm.startOfLineIdx[y +1] -1;
  3436. glyphIdx := ptm.startOfLineIdx[y];
  3437. chunkChrOff := 0;
  3438. x2 := x;
  3439. // get chunkIdx within line 'y' ...
  3440. while (glyphIdx < maxIdx) do
  3441. begin
  3442. if Chunk[glyphIdx].text = space then
  3443. begin
  3444. if x2 < Chunk[glyphIdx].width + ptm.justifyDeltas[y] then Break;
  3445. x2 := x2 - Chunk[glyphIdx].width - ptm.justifyDeltas[y];
  3446. end else
  3447. begin
  3448. if x2 < Chunk[glyphIdx].width then Break;
  3449. x2 := x2 - Chunk[glyphIdx].width;
  3450. end;
  3451. inc(glyphIdx);
  3452. end;
  3453. // get chunkChrOffset within Chunk[chunkIdx] ...
  3454. chnk := Chunk[glyphIdx];
  3455. while x2 >= chnk.glyphOffsets[chunkChrOff +1] do Inc(chunkChrOff);
  3456. Result := true;
  3457. end;
  3458. //------------------------------------------------------------------------------
  3459. function TChunkedText.GetGlyphsOrDrawInternal(image: TImage32; const rec: TRect;
  3460. textAlign: TTextAlign; textAlignV: TTextVAlign; startChunk: integer;
  3461. lineHeight: double; out paths: TPathsD): TPageTextMetrics;
  3462. var
  3463. i,j, highJ,k, recWidth, recHeight: integer;
  3464. a,b, chrIdx, lastLine: integer;
  3465. x,y, totalHeight, lineWidth, spcDx: double;
  3466. consumedWidth: double;
  3467. pp: TPathsD;
  3468. top: double;
  3469. chnk: TTextChunk;
  3470. begin
  3471. paths := nil;
  3472. FillChar(Result, SizeOf(Result), 0);
  3473. Result.nextChuckIdx := startChunk;
  3474. if Count = 0 then Exit;
  3475. RectWidthHeight(rec, recWidth, recHeight);
  3476. // LINE HEIGHTS ...............
  3477. // Getting lineheights based on a given font's ascent and descent values
  3478. // works well only when a single font is used. Unfortunately, when using
  3479. // multiple fonts, line spacing becomes uneven and looks ugly.
  3480. // An alternative approach is to measure the highest and lowest bounds of all
  3481. // the glyphs in a line, and use these and a fixed inter line space
  3482. // to derive variable line heights. But this approach also has problems,
  3483. // especially when lines contain no glyphs, or when they only contain glyphs
  3484. // with minimal heights (----------). So this too can look ugly.
  3485. // A third approach, is to get the maximum of every lines' height and use
  3486. // that value for every line. But this approach tends to produce undesirably
  3487. // large line heights.
  3488. // A fourth approach is to use the height of the very first text chunk.
  3489. // And a final approach ia simply to use a user defined line height
  3490. if lineHeight = 0 then
  3491. lineHeight := Chunk[0].height;
  3492. Result := GetPageMetrics(rec, lineHeight, startChunk);
  3493. if (Result.lineCount = 0) or (lineHeight > recHeight) then Exit;
  3494. // only return glyphs for visible lines
  3495. totalHeight := lineHeight * Result.lineCount;
  3496. i := Result.startOfLineIdx[0];
  3497. top := rec.Top + Chunk[i].ascent;
  3498. case textAlignV of
  3499. tvaMiddle: y := top + (RecHeight - totalHeight) /2 -1;
  3500. tvaBottom: y := rec.bottom - totalHeight + Chunk[i].ascent;
  3501. else y := top;
  3502. end;
  3503. Result.bounds := rec;
  3504. Result.topLinePxOffset := Round(y - top);
  3505. chrIdx := 0;
  3506. lastLine := Result.lineCount -1;
  3507. for i := 0 to lastLine do
  3508. begin
  3509. a := Result.startOfLineIdx[i];
  3510. if i = lastLine then
  3511. begin
  3512. if (chunk[a].width - chunk[a].glyphOffsets[chrIdx] > recWidth) then
  3513. b := a -1 // flag getting glyphs for a partial chunk
  3514. else if Result.nextChuckIdx = 0 then
  3515. b := Count -1
  3516. else
  3517. b := Result.nextChuckIdx -1;
  3518. end else
  3519. b := Result.startOfLineIdx[i+1] -1;
  3520. if textAlign = taJustify then
  3521. spcDx := Result.justifyDeltas[i] else
  3522. spcDx := 0;
  3523. lineWidth := Result.lineWidths[i];
  3524. if (b < a) then
  3525. begin
  3526. // chunk[a] width exceeds recWidth
  3527. chnk := chunk[a];
  3528. consumedWidth := chnk.glyphOffsets[chrIdx];
  3529. highJ := High(chnk.glyphOffsets);
  3530. j := chrIdx;
  3531. while (j < highJ) and
  3532. (chnk.glyphOffsets[j+1] -consumedWidth < lineWidth) do inc(j);
  3533. pp := nil;
  3534. for k := chrIdx to j do
  3535. AppendPath(pp, chnk.arrayOfPaths[k]);
  3536. pp := TranslatePath(pp, rec.Left - consumedWidth, y);
  3537. chnk.left := rec.Left;
  3538. chnk.top := y - chnk.ascent;
  3539. if Assigned(image) then
  3540. begin
  3541. if Assigned(fDrawChunkEvent) then
  3542. fDrawChunkEvent(chnk, RectD(rec.Left, chnk.top,
  3543. rec.Left + consumedWidth, chnk.top + chnk.height));
  3544. DrawPolygon(image, pp, frNonZero, chnk.fontColor);
  3545. end else
  3546. AppendPath(paths, pp);
  3547. y := y + lineHeight;
  3548. chrIdx := j +1;
  3549. Continue;
  3550. end
  3551. else if chrIdx > 0 then
  3552. begin
  3553. // finish the partially processed chunk before continuing to next one
  3554. chnk := chunk[a];
  3555. highJ := High(chnk.glyphOffsets);
  3556. consumedWidth := chnk.glyphOffsets[chrIdx];
  3557. j := chrIdx;
  3558. while (j < highJ) and
  3559. (chnk.glyphOffsets[j+1] -consumedWidth < lineWidth) do inc(j);
  3560. pp := nil;
  3561. for k := chrIdx to j do
  3562. AppendPath(pp, chnk.arrayOfPaths[k]);
  3563. pp := TranslatePath(pp, rec.Left - consumedWidth, y);
  3564. if Assigned(image) then
  3565. DrawPolygon(image, pp, frNonZero, chnk.fontColor) else
  3566. AppendPath(paths, pp);
  3567. if (j = chrIdx) and (j < highJ) then
  3568. break // oops, even a character is too wide for 'rec' !
  3569. else if j < HighJ then
  3570. begin
  3571. chrIdx := j;
  3572. Continue;
  3573. end else
  3574. begin
  3575. chrIdx := 0;
  3576. x := rec.Left + chnk.width - consumedWidth;
  3577. inc(a);
  3578. end;
  3579. end else
  3580. begin
  3581. case textAlign of
  3582. taRight : x := rec.Left + (recWidth - lineWidth);
  3583. taCenter : x := rec.Left + (recWidth - lineWidth) / 2;
  3584. else x := rec.Left;
  3585. end;
  3586. end;
  3587. // ignore trailing spaces
  3588. while (b >= a) do
  3589. if Chunk[b].text <= SPACE then
  3590. dec(b) else
  3591. break;
  3592. for j := a to b do
  3593. begin
  3594. chnk := GetChunk(j);
  3595. chnk.left := x;
  3596. chnk.top := y - chnk.ascent;
  3597. if chnk.text > SPACE then
  3598. begin
  3599. pp := MergeArrayOfPathsEx(chnk.arrayOfPaths, x, y);
  3600. if Assigned(image) then
  3601. begin
  3602. if (GetAlpha(chnk.backColor) > 0) then
  3603. image.FillRect(Img32.Vector.Rect(RectD(x, chnk.top,
  3604. x + chnk.width, chnk.top + chnk.height)), chnk.backColor);
  3605. if Assigned(fDrawChunkEvent) then
  3606. fDrawChunkEvent(chnk, RectD(x, chnk.top,
  3607. x + chnk.width, chnk.top + chnk.height));
  3608. DrawPolygon(image, pp, frNonZero, chnk.fontColor);
  3609. end else
  3610. AppendPath(paths, pp);
  3611. x := x + chnk.width;
  3612. end else
  3613. begin
  3614. if (GetAlpha(chnk.backColor) > 0) then
  3615. image.FillRect(Img32.Vector.Rect(RectD(x, chnk.top,
  3616. x + chnk.width + spcDx, chnk.top + chnk.height)),
  3617. chnk.backColor);
  3618. if Assigned(image) and Assigned(fDrawChunkEvent) then
  3619. fDrawChunkEvent(chnk, RectD(x, chnk.top,
  3620. x + chnk.width + spcDx, chnk.top + chnk.height));
  3621. x := x + chnk.width + spcDx;
  3622. end;
  3623. end;
  3624. y := y + lineHeight;
  3625. end;
  3626. end;
  3627. //------------------------------------------------------------------------------
  3628. function TChunkedText.DrawText(image: TImage32; const rec: TRect;
  3629. textAlign: TTextAlign; textAlignV: TTextVAlign;
  3630. startChunk: integer; lineHeight: double): TPageTextMetrics;
  3631. var
  3632. dummy: TPathsD;
  3633. begin
  3634. Result := GetGlyphsOrDrawInternal(image,
  3635. rec, textAlign, textAlignV, startChunk, lineHeight, dummy);
  3636. end;
  3637. //------------------------------------------------------------------------------
  3638. function TChunkedText.GetTextGlyphs(const rec: TRect;
  3639. textAlign: TTextAlign; textAlignV: TTextVAlign; startChunk: integer;
  3640. lineHeight: double = 0.0): TPathsD;
  3641. begin
  3642. GetGlyphsOrDrawInternal(nil, rec, textAlign, textAlignV,
  3643. startChunk, lineHeight, Result);
  3644. end;
  3645. //------------------------------------------------------------------------------
  3646. procedure TChunkedText.ApplyNewFont(font: TFontCache);
  3647. var
  3648. i: integer;
  3649. begin
  3650. if not Assigned(font) then Exit;
  3651. for i := 0 to Count -1 do
  3652. with Chunk[i] do
  3653. begin
  3654. font.GetTextOutlineInternal(0,0,
  3655. text, 0, arrayOfPaths, glyphOffsets, width);
  3656. height := font.LineHeight;
  3657. ascent := font.Ascent;
  3658. end;
  3659. end;
  3660. //------------------------------------------------------------------------------
  3661. // TFontManager
  3662. //------------------------------------------------------------------------------
  3663. constructor TFontManager.Create;
  3664. begin
  3665. fMaxFonts := 32;
  3666. {$IFDEF XPLAT_GENERICS}
  3667. fFontList := TList<TFontReader>.Create;
  3668. {$ELSE}
  3669. fFontList:= TList.Create;
  3670. {$ENDIF}
  3671. end;
  3672. //------------------------------------------------------------------------------
  3673. destructor TFontManager.Destroy;
  3674. begin
  3675. Clear;
  3676. fFontList.Free;
  3677. inherited;
  3678. end;
  3679. //------------------------------------------------------------------------------
  3680. procedure TFontManager.Clear;
  3681. var
  3682. i: integer;
  3683. begin
  3684. for i := 0 to fFontList.Count -1 do
  3685. with TFontReader(fFontList[i]) do
  3686. begin
  3687. fFontManager := nil;
  3688. Free;
  3689. end;
  3690. fFontList.Clear;
  3691. end;
  3692. //------------------------------------------------------------------------------
  3693. function TFontManager.FindDuplicate(fr: TFontReader): integer;
  3694. var
  3695. fi, fi2: TFontInfo;
  3696. begin
  3697. fi := fr.FontInfo;
  3698. for Result := 0 to fFontList.Count -1 do
  3699. begin
  3700. fi2 := TFontReader(fFontList[Result]).FontInfo;
  3701. if SameText(fi.fullFaceName, fi2.fullFaceName) and
  3702. (fi.macStyles = fi2.macStyles) then Exit;
  3703. end;
  3704. Result := -1;
  3705. end;
  3706. //------------------------------------------------------------------------------
  3707. {$IFDEF MSWINDOWS}
  3708. function TFontManager.LoadFontReaderFamily(const fontFamily: string): TLoadFontResult;
  3709. var
  3710. frf: TFontReaderFamily;
  3711. begin
  3712. Result := LoadFontReaderFamily(fontFamily, frf);
  3713. end;
  3714. //------------------------------------------------------------------------------
  3715. function TFontManager.LoadFontReaderFamily(const fontFamily: string;
  3716. out fontReaderFamily: TFontReaderFamily): TLoadFontResult;
  3717. var
  3718. arrayEnumLogFont: TArrayOfEnumLogFontEx;
  3719. lf: TLogFont;
  3720. fontInfo: TFontInfo;
  3721. function FontInfoNamesAndSytlesMatch(const fontInfo1, fontInfo2: TFontInfo): Boolean;
  3722. begin
  3723. Result := (fontInfo1.faceName = fontInfo2.faceName) and
  3724. (fontInfo1.macStyles = fontInfo2.macStyles);
  3725. end;
  3726. begin
  3727. Result := lfrInvalid;
  3728. fontReaderFamily.regularFR := nil;
  3729. fontReaderFamily.boldFR := nil;
  3730. fontReaderFamily.italicFR := nil;
  3731. fontReaderFamily.boldItalicFR := nil;
  3732. if (fontFamily = '') or (Length(fontFamily) > LF_FACESIZE) then Exit;
  3733. arrayEnumLogFont := GetLogFonts(fontFamily, DEFAULT_CHARSET); //ANSI_CHARSET);
  3734. FillChar(lf, SizeOf(TLogFont), 0);
  3735. Move(fontFamily[1], lf.lfFaceName[0], Length(fontFamily) * SizeOf(Char));
  3736. if not GetLogFontFromEnumThatMatchesStyles(arrayEnumLogFont, [], lf) then Exit;
  3737. // make room for 4 new fontreaders
  3738. while fFontList.Count > fMaxFonts - 4 do DeleteOldestFont;
  3739. fontReaderFamily.regularFR := TFontReader.Create;
  3740. fontReaderFamily.regularFR.Load(lf);
  3741. Result := ValidateFontLoad(fontReaderFamily.regularFR);
  3742. case Result of
  3743. lfrInvalid: Exit;
  3744. lfrDuplicate:
  3745. begin
  3746. fontInfo := fontReaderFamily.regularFR.FontInfo;
  3747. fontInfo.macStyles := [msBold];
  3748. fontReaderFamily.boldFR := GetBestMatchFont(fontInfo);
  3749. if not FontInfoNamesAndSytlesMatch(FontInfo,
  3750. fontReaderFamily.boldFR.FontInfo) then
  3751. fontReaderFamily.boldFR := nil;
  3752. fontInfo.macStyles := [msItalic];
  3753. fontReaderFamily.italicFR := GetBestMatchFont(fontInfo);
  3754. if not FontInfoNamesAndSytlesMatch(FontInfo,
  3755. fontReaderFamily.italicFR.FontInfo) then
  3756. fontReaderFamily.italicFR := nil;
  3757. fontInfo.macStyles := [msBold, msItalic];
  3758. fontReaderFamily.boldItalicFR := GetBestMatchFont(fontInfo);
  3759. if not FontInfoNamesAndSytlesMatch(FontInfo,
  3760. fontReaderFamily.boldItalicFR.FontInfo) then
  3761. fontReaderFamily.boldItalicFR := nil;
  3762. end;
  3763. else
  3764. begin
  3765. if GetLogFontFromEnumThatMatchesStyles(arrayEnumLogFont, [msBold], lf) then
  3766. begin
  3767. fontReaderFamily.boldFR := TFontReader.Create;
  3768. fontReaderFamily.boldFR.Load(lf);
  3769. ValidateFontLoad(fontReaderFamily.boldFR);
  3770. end;
  3771. if GetLogFontFromEnumThatMatchesStyles(arrayEnumLogFont, [msItalic], lf) then
  3772. begin
  3773. fontReaderFamily.italicFR := TFontReader.Create;
  3774. fontReaderFamily.italicFR.Load(lf);
  3775. ValidateFontLoad(fontReaderFamily.italicFR);
  3776. end;
  3777. if GetLogFontFromEnumThatMatchesStyles(arrayEnumLogFont, [msBold, msItalic], lf) then
  3778. begin
  3779. fontReaderFamily.boldItalicFR := TFontReader.Create;
  3780. fontReaderFamily.boldItalicFR.Load(lf);
  3781. ValidateFontLoad(fontReaderFamily.boldItalicFR);
  3782. end;
  3783. end;
  3784. end;
  3785. end;
  3786. //------------------------------------------------------------------------------
  3787. function TFontManager.LoadFontReader(const fontName: string): TFontReader;
  3788. begin
  3789. Result := nil;
  3790. if (fontName = '') or (Length(fontName) > LF_FACESIZE) then Exit;
  3791. if fFontList.Count >= fMaxFonts then DeleteOldestFont;
  3792. Result := TFontReader.Create(fontName);
  3793. ValidateFontLoad(Result);
  3794. end;
  3795. //------------------------------------------------------------------------------
  3796. {$ENDIF}
  3797. function TFontManager.LoadFromStream(stream: TStream): TFontReader;
  3798. begin
  3799. if fFontList.Count >= fMaxFonts then DeleteOldestFont;
  3800. Result := TFontReader.Create;
  3801. try
  3802. if not Result.LoadFromStream(stream) then FreeAndNil(Result)
  3803. else ValidateFontLoad(Result);
  3804. except
  3805. FreeAndNil(Result);
  3806. end;
  3807. end;
  3808. //------------------------------------------------------------------------------
  3809. function TFontManager.LoadFromResource(const resName: string; resType: PChar): TFontReader;
  3810. begin
  3811. if fFontList.Count >= fMaxFonts then DeleteOldestFont;
  3812. Result := TFontReader.Create;
  3813. try
  3814. if not Result.LoadFromResource(resName, resType) then FreeAndNil(Result)
  3815. else ValidateFontLoad(Result);
  3816. except
  3817. FreeAndNil(Result);
  3818. end;
  3819. end;
  3820. //------------------------------------------------------------------------------
  3821. function TFontManager.LoadFromFile(const filename: string): TFontReader;
  3822. begin
  3823. if fFontList.Count >= fMaxFonts then DeleteOldestFont;
  3824. Result := TFontReader.Create;
  3825. try
  3826. if not Result.LoadFromFile(filename) then FreeAndNil(Result)
  3827. else ValidateFontLoad(Result);
  3828. except
  3829. FreeAndNil(Result);
  3830. end;
  3831. end;
  3832. //------------------------------------------------------------------------------
  3833. function TFontManager.ValidateFontLoad(var fr: TFontReader): TLoadFontResult;
  3834. var
  3835. dupIdx: integer;
  3836. begin
  3837. if not fr.IsValidFontFormat then
  3838. begin
  3839. FreeAndNil(fr);
  3840. result := lfrInvalid;
  3841. Exit;
  3842. end;
  3843. dupIdx := FindDuplicate(fr);
  3844. if dupIdx >= 0 then
  3845. begin
  3846. FreeAndNil(fr);
  3847. result := lfrDuplicate;
  3848. fr := fFontList[dupIdx];
  3849. end else
  3850. begin
  3851. Result := lfrSuccess;
  3852. fFontList.Add(fr);
  3853. fr.fFontManager := self;
  3854. end;
  3855. end;
  3856. //------------------------------------------------------------------------------
  3857. function TFontManager.Delete(fontReader: TFontReader): Boolean;
  3858. var
  3859. i: integer;
  3860. begin
  3861. for i := 0 to fFontList.Count -1 do
  3862. if TFontReader(fFontList[i]) = fontReader then
  3863. begin
  3864. // make sure the FontReader object isn't destroying itself externally
  3865. if not fontReader.fDestroying then fontReader.Free;
  3866. fFontList.Delete(i);
  3867. Result := true;
  3868. Exit;
  3869. end;
  3870. Result := false;
  3871. end;
  3872. //------------------------------------------------------------------------------
  3873. function StylesToInt(macstyles: TMacStyles): integer;
  3874. {$IFDEF INLINE} inline; {$ENDIF}
  3875. begin
  3876. if msBold in macStyles then
  3877. Result := 1 else Result := 0;
  3878. if msItalic in macStyles then inc(Result, 2);
  3879. end;
  3880. //------------------------------------------------------------------------------
  3881. function FontFamilyToInt(family: TFontFamily): integer;
  3882. {$IFDEF INLINE} inline; {$ENDIF}
  3883. begin
  3884. Result := Ord(family) +1;
  3885. end;
  3886. //------------------------------------------------------------------------------
  3887. function TFontManager.GetBestMatchFont(const fontInfo: TFontInfo): TFontReader;
  3888. function GetStyleDiff(const macstyles1, macstyles2: TMacStyles): integer;
  3889. {$IFDEF INLINE} inline; {$ENDIF}
  3890. begin
  3891. // top priority
  3892. Result := (((Byte(macstyles1) xor $FF) or
  3893. (Byte(macstyles2) xor $FF)) and $3) * 256;
  3894. end;
  3895. function GetFontFamilyDiff(const family1, family2: TFontFamily): integer;
  3896. {$IFDEF INLINE} inline; {$ENDIF}
  3897. begin
  3898. // second priority
  3899. if family1 = tfUnknown then
  3900. Result := 0 else
  3901. Result := Abs(FontFamilyToInt(family1) - FontFamilyToInt(family2)) * 8;
  3902. end;
  3903. function GetShortNameDiff(const name1, name2: Utf8String): integer;
  3904. {$IFDEF INLINE} inline; {$ENDIF}
  3905. begin
  3906. // third priority (shl 3)
  3907. if name1 = '' then
  3908. Result := 0 else
  3909. if SameText(name1, name2) then Result := 0 else Result := 4;
  3910. end;
  3911. function GetFullNameDiff(const fiToMatch: TFontInfo;
  3912. const candidateName: Utf8String): integer;
  3913. var
  3914. i: integer;
  3915. begin
  3916. // lowest priority
  3917. Result := 0;
  3918. if Assigned(fiToMatch.familyNames) then
  3919. begin
  3920. for i := 0 to High(fiToMatch.familyNames) do
  3921. if SameText(fiToMatch.familyNames[i], candidateName) then Exit;
  3922. end
  3923. else if SameText(fiToMatch.faceName, candidateName) then Exit;
  3924. Result := 2;
  3925. end;
  3926. function CompareFontInfos(const fiToMatch, fiCandidate: TFontInfo): integer;
  3927. begin
  3928. Result :=
  3929. GetStyleDiff(fiToMatch.macStyles, fiCandidate.macStyles) +
  3930. GetFontFamilyDiff(fiToMatch.family, fiCandidate.family) +
  3931. GetShortNameDiff(fiToMatch.faceName, fiCandidate.faceName) +
  3932. GetFullNameDiff(fiToMatch, fiCandidate.fullFaceName);
  3933. end;
  3934. var
  3935. i, bestDiff, currDiff: integer;
  3936. fr: TFontReader;
  3937. begin
  3938. Result := nil;
  3939. bestDiff := MaxInt;
  3940. for i := 0 to fFontList.Count -1 do
  3941. begin
  3942. fr := TFontReader(fFontList[i]);
  3943. currDiff := CompareFontInfos(fontInfo, fr.fFontInfo);
  3944. if (currDiff < bestDiff) then
  3945. begin
  3946. Result := fr;
  3947. if currDiff = 0 then Break; // can't do better :)
  3948. bestDiff := currDiff;
  3949. end;
  3950. end;
  3951. end;
  3952. //------------------------------------------------------------------------------
  3953. function TFontManager.GetBestMatchFont(const styles: TMacStyles): TFontReader;
  3954. var
  3955. i, bestDiff, currDiff: integer;
  3956. fr: TFontReader;
  3957. begin
  3958. Result := nil;
  3959. bestDiff := MaxInt;
  3960. for i := 0 to fFontList.Count -1 do
  3961. begin
  3962. fr := TFontReader(fFontList[i]);
  3963. currDiff := (((Byte(styles) xor $FF) or (Byte(fr.fFontInfo.macStyles) xor $FF)) and $3);
  3964. if (currDiff < bestDiff) then
  3965. begin
  3966. Result := fr;
  3967. if currDiff = 0 then Break; // can't do any better :)
  3968. bestDiff := currDiff;
  3969. end;
  3970. end;
  3971. end;
  3972. //------------------------------------------------------------------------------
  3973. function TFontManager.FindReaderContainingGlyph(codepoint: Cardinal;
  3974. fntFamily: TFontFamily; out glyphIdx: WORD): TFontReader;
  3975. var
  3976. i: integer;
  3977. reader: TFontReader;
  3978. begin
  3979. result := nil;
  3980. for i := 0 to fFontList.Count -1 do
  3981. begin
  3982. reader := TFontReader(fFontList[i]);
  3983. glyphIdx := reader.GetGlyphIdxUsingCmap(codepoint);
  3984. // if a font family is specified, then only return true
  3985. // when finding the glyph within that font family
  3986. if (glyphIdx > 0) and ((fntFamily = tfUnknown) or
  3987. (reader.FontFamily = tfUnknown) or (fntFamily = reader.FontFamily)) then
  3988. begin
  3989. Result := reader;
  3990. Exit;
  3991. end;
  3992. end;
  3993. glyphIdx := 0;
  3994. end;
  3995. //------------------------------------------------------------------------------
  3996. procedure TFontManager.SetMaxFonts(value: integer);
  3997. begin
  3998. if value < 0 then value := 0;
  3999. if value <= 0 then Clear
  4000. else while value > fFontList.Count do
  4001. Delete(TFontReader(fFontList[0]));
  4002. fMaxFonts := value;
  4003. end;
  4004. //------------------------------------------------------------------------------
  4005. function FontSorterProc(fontreader1, fontreader2: Pointer): integer;
  4006. var
  4007. fr1: TFontReader absolute fontreader1;
  4008. fr2: TFontReader absolute fontreader2;
  4009. begin
  4010. if fr1.fLastUsedTime > fr2.fLastUsedTime then Result := -1
  4011. else if fr1.fLastUsedTime < fr2.fLastUsedTime then Result := 1
  4012. else Result := 0;
  4013. end;
  4014. //------------------------------------------------------------------------------
  4015. procedure TFontManager.SortFontListOnLastUse;
  4016. begin
  4017. {$IFDEF XPLAT_GENERICS}
  4018. fFontList.Sort(TComparer<TFontReader>.Construct(
  4019. function (const fr1, fr2: TFontReader): integer
  4020. begin
  4021. if fr1.fLastUsedTime > fr2.fLastUsedTime then Result := -1
  4022. else if fr1.fLastUsedTime < fr2.fLastUsedTime then Result := 1
  4023. else Result := 0;
  4024. end));
  4025. {$ELSE}
  4026. fFontList.Sort(FontSorterProc);
  4027. {$ENDIF}
  4028. end;
  4029. //------------------------------------------------------------------------------
  4030. procedure TFontManager.DeleteOldestFont;
  4031. var
  4032. cnt: integer;
  4033. begin
  4034. cnt := fFontList.Count;
  4035. if cnt = 0 then Exit;
  4036. SortFontListOnLastUse;
  4037. TFontReader(fFontList[cnt -1]).Free;
  4038. end;
  4039. //------------------------------------------------------------------------------
  4040. //------------------------------------------------------------------------------
  4041. //------------------------------------------------------------------------------
  4042. function FontManager: TFontManager;
  4043. begin
  4044. result := aFontManager;
  4045. end;
  4046. //------------------------------------------------------------------------------
  4047. initialization
  4048. aFontManager := TFontManager.Create;
  4049. finalization
  4050. aFontManager.Free;
  4051. end.