Img32.SVG.Core.pas 87 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163
  1. unit Img32.SVG.Core;
  2. (*******************************************************************************
  3. * Author : Angus Johnson *
  4. * Version : 4.7 *
  5. * Date : 12 January 2025 *
  6. * Website : http://www.angusj.com *
  7. * Copyright : Angus Johnson 2019-2025 *
  8. * *
  9. * Purpose : Essential structures and functions to read SVG files *
  10. * *
  11. * License : Use, modification & distribution is subject to *
  12. * Boost Software License Ver 1 *
  13. * http://www.boost.org/LICENSE_1_0.txt *
  14. *******************************************************************************)
  15. interface
  16. {$I Img32.inc}
  17. uses
  18. SysUtils, Classes, Types, Math, StrUtils,
  19. {$IFDEF XPLAT_GENERICS} Generics.Collections, Generics.Defaults,{$ENDIF}
  20. Img32, Img32.Vector, Img32.Text, Img32.Transform;
  21. {$IFDEF ZEROBASEDSTR}
  22. {$ZEROBASEDSTRINGS OFF}
  23. {$ENDIF}
  24. type
  25. TSvgEncoding = (eUnknown, eUtf8, eUnicodeLE, eUnicodeBE);
  26. TUnitType = (utUnknown, utNumber, utPercent, utEm, utEx, utPixel,
  27. utCm, utMm, utInch, utPt, utPica, utDegree, utRadian);
  28. //////////////////////////////////////////////////////////////////////
  29. // TValue - Structure to store numerics with measurement units.
  30. // See https://www.w3.org/TR/SVG/types.html#InterfaceSVGLength
  31. // and https://www.w3.org/TR/SVG/types.html#InterfaceSVGAngle
  32. //////////////////////////////////////////////////////////////////////
  33. //Unfortunately unit-less values can exhibit ambiguity, especially when their
  34. //values are small (eg < 1.0). These values can be either absolute values or
  35. //relative values (ie relative to the supplied dimension size).
  36. //The 'assumeRelValBelow' parameter (see below) attempts to address this
  37. //ambiguity, such that unit-less values will be assumed to be 'relative' when
  38. //'rawVal' is less than the supplied 'assumeRelValBelow' value.
  39. TValue = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
  40. rawVal : double;
  41. unitType : TUnitType;
  42. procedure Init;
  43. procedure SetValue(val: double; unitTyp: TUnitType = utNumber);
  44. function GetValue(relSize: double; assumeRelValBelow: Double): double;
  45. function GetValueXY(const relSize: TRectD; assumeRelValBelow: Double): double;
  46. function IsValid: Boolean;
  47. function IsRelativeValue(assumeRelValBelow: double): Boolean;
  48. {$IFDEF INLINE} inline; {$ENDIF}
  49. function HasFontUnits: Boolean;
  50. function HasAngleUnits: Boolean;
  51. end;
  52. TValuePt = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
  53. X, Y : TValue;
  54. procedure Init;
  55. function GetPoint(const relSize: double; assumeRelValBelow: Double): TPointD; overload;
  56. function GetPoint(const relSize: TRectD; assumeRelValBelow: Double): TPointD; overload;
  57. function IsValid: Boolean;
  58. end;
  59. TValueRecWH = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
  60. left : TValue;
  61. top : TValue;
  62. width : TValue;
  63. height : TValue;
  64. procedure Init;
  65. function GetRectD(const relSize: TRectD; assumeRelValBelow: Double): TRectD; overload;
  66. function GetRectD(relSize: double; assumeRelValBelow: Double): TRectD; overload;
  67. function GetRectD(relSizeX, relSizeY: double; assumeRelValBelow: Double): TRectD; overload;
  68. function GetRectWH(const relSize: TRectD; assumeRelValBelow: Double): TRectWH;
  69. function IsValid: Boolean;
  70. function IsEmpty: Boolean;
  71. end;
  72. {$IFNDEF UNICODE}
  73. UTF8Char = Char;
  74. PUTF8Char = PChar;
  75. {$ELSE}
  76. {$IF COMPILERVERSION < 31}
  77. UTF8Char = AnsiChar;
  78. PUTF8Char = PAnsiChar;
  79. {$IFEND}
  80. {$ENDIF}
  81. TSvgItalicSyle = (sfsUndefined, sfsNone, sfsItalic);
  82. TFontDecoration = (fdUndefined, fdNone, fdUnderline, fdStrikeThrough);
  83. TSvgTextAlign = (staUndefined, staLeft, staCenter, staRight, staJustify);
  84. TSpacesInText = (sitUndefined, sitIgnore, sitPreserve);
  85. UTF8Strings = array of UTF8String;
  86. TSVGFontInfo = record
  87. family : TFontFamily;
  88. familyNames : UTF8Strings;
  89. size : double;
  90. spacing : double;
  91. spacesInText : TSpacesInText;
  92. textLength : double;
  93. italic : TSvgItalicSyle;
  94. weight : Integer;
  95. align : TSvgTextAlign;
  96. decoration : TFontDecoration;
  97. baseShift : TValue;
  98. end;
  99. //////////////////////////////////////////////////////////////////////
  100. // TClassStylesList: Map that stores CSS selectors with their styles
  101. //////////////////////////////////////////////////////////////////////
  102. PClassStyleListItem = ^TClassStyleListItem;
  103. TClassStyleListItem = record //used internally by TClassStylesList
  104. Hash : Cardinal;
  105. Next : Integer;
  106. Name : UTF8String;
  107. Style : UTF8String;
  108. end;
  109. TClassStylesList = class
  110. private
  111. FNameHash: Cardinal;
  112. FItems: array of TClassStyleListItem;
  113. FBuckets: TArrayOfInteger;
  114. FCount: Integer;
  115. FMod: Cardinal;
  116. procedure Grow(NewCapacity: Integer = -1);
  117. function FindItemIndex(const Name: UTF8String): Integer;
  118. public
  119. procedure Preallocate(AdditionalItemCount: Integer);
  120. procedure AddAppendStyle(const Name, Style: UTF8String);
  121. function GetStyle(const Name: UTF8String): UTF8String;
  122. procedure Clear;
  123. end;
  124. //////////////////////////////////////////////////////////////////////
  125. // TSvgParser and associated classes - a simple parser for SVG xml
  126. //////////////////////////////////////////////////////////////////////
  127. PSvgAttrib = ^TSvgAttrib; //element attribute
  128. TSvgAttrib = record
  129. hash : Cardinal; //hashed name
  130. name : UTF8String;
  131. value : UTF8String;
  132. end;
  133. TSvgParser = class;
  134. TXmlEl = class //base element class
  135. private
  136. {$IFDEF XPLAT_GENERICS}
  137. attribs : TList <PSvgAttrib>;
  138. {$ELSE}
  139. attribs : TList;
  140. {$ENDIF}
  141. function GetAttrib(index: integer): PSvgAttrib;
  142. function GetAttribCount: integer;
  143. public
  144. {$IFDEF XPLAT_GENERICS}
  145. childs : TList<TXmlEl>;
  146. {$ELSE}
  147. childs : TList;
  148. {$ENDIF}
  149. name : UTF8String;
  150. owner : TSvgParser;
  151. hash : Cardinal;
  152. text : UTF8String;
  153. selfClosed : Boolean;
  154. constructor Create(owner: TSvgParser); virtual;
  155. destructor Destroy; override;
  156. procedure Clear; virtual;
  157. function ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean; virtual;
  158. function ParseContent(var c: PUTF8Char; endC: PUTF8Char): Boolean; virtual;
  159. class function ParseAttribName(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char; {$IFDEF CLASS_STATIC}static;{$ENDIF}
  160. class function ParseAttribValue(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char; {$IFDEF CLASS_STATIC}static;{$ENDIF}
  161. class function ParseAttribNameAndValue(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char; {$IFDEF CLASS_STATIC}static;{$ENDIF}
  162. function ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean; virtual;
  163. procedure ParseStyleAttribute(const style: UTF8String);
  164. property Attrib[index: integer]: PSvgAttrib read GetAttrib;
  165. property AttribCount: integer read GetAttribCount;
  166. end;
  167. TDocTypeEl = class(TXmlEl)
  168. private
  169. function SkipWord(c, endC: PUTF8Char): PUTF8Char;
  170. function ParseEntities(var c, endC: PUTF8Char): Boolean;
  171. public
  172. function ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean; override;
  173. end;
  174. TSvgXmlEl = class(TXmlEl)
  175. public
  176. constructor Create(owner: TSvgParser); override;
  177. procedure Clear; override;
  178. function ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean; override;
  179. end;
  180. TSvgParser = class
  181. private
  182. svgStream : TMemoryStream;
  183. procedure ParseUtf8Stream;
  184. public
  185. classStyles : TClassStylesList;
  186. xmlHeader : TXmlEl;
  187. docType : TDocTypeEl;
  188. svgTree : TSvgXmlEl;
  189. constructor Create;
  190. destructor Destroy; override;
  191. procedure Clear;
  192. function FindEntity(hash: Cardinal): PSvgAttrib;
  193. function LoadFromFile(const filename: string): Boolean;
  194. function LoadFromStream(stream: TStream): Boolean;
  195. function LoadFromString(const str: string): Boolean;
  196. end;
  197. //////////////////////////////////////////////////////////////////////
  198. // Miscellaneous SVG functions
  199. //////////////////////////////////////////////////////////////////////
  200. //general parsing functions //////////////////////////////////////////
  201. function ParseNextWord(var c: PUTF8Char; endC: PUTF8Char;
  202. out word: UTF8String): Boolean;
  203. function ParseNextWordHash(var c: PUTF8Char; endC: PUTF8Char;
  204. out hash: cardinal): Boolean; overload;
  205. function ParseNextWordHash(c, endC: PUTF8Char): cardinal; overload;
  206. function ParseNextWordExHash(var c: PUTF8Char; endC: PUTF8Char;
  207. out hash: cardinal): Boolean;
  208. function ParseNextNum(var c: PUTF8Char; endC: PUTF8Char;
  209. skipComma: Boolean; out val: double): Boolean;
  210. function ParseNextNumEx(var c: PUTF8Char; endC: PUTF8Char; skipComma: Boolean;
  211. out val: double; out unitType: TUnitType): Boolean;
  212. function GetHash(c: PUTF8Char; len: nativeint): cardinal; overload;
  213. function GetHash(const name: UTF8String): cardinal; overload; {$IFDEF INLINE} inline; {$ENDIF}
  214. function GetHashCaseSensitive(name: PUTF8Char; nameLen: integer): cardinal;
  215. function ExtractRef(const href: UTF8String): UTF8String;
  216. function IsNumPending(var c: PUTF8Char;
  217. endC: PUTF8Char; ignoreComma: Boolean): Boolean;
  218. function UTF8StringToColor32(const value: UTF8String; var color: TColor32): Boolean;
  219. function ScaleDashArray(const dblArray: TArrayOfDouble; scale: double): TArrayOfDouble;
  220. function Match(c: PUTF8Char; const compare: UTF8String): Boolean; overload;
  221. function Match(const compare1, compare2: UTF8String): Boolean; overload;
  222. function PosEx(const subStr: utf8String; const text: Utf8String; startIdx: integer = 1): integer;
  223. procedure ToUTF8String(c, endC: PUTF8Char; var S: UTF8String;
  224. spacesInText: TSpacesInText = sitUndefined);
  225. function TrimMultiSpacesUtf8(const text: Utf8String): Utf8String;
  226. function TrimMultiSpacesUnicode(const text: UnicodeString): UnicodeString;
  227. function ConvertNewlines(const s: UTF8String): UTF8String; overload;
  228. function ConvertNewlines(const s: UnicodeString): UnicodeString; overload;
  229. function StripNewlines(const s: UTF8String): UTF8String; overload;
  230. function StripNewlines(const s: UnicodeString): UnicodeString; overload;
  231. procedure ToAsciiLowerUTF8String(c, endC: PUTF8Char; var S: UTF8String);
  232. procedure ToTrimmedUTF8String(c, endC: PUTF8Char; var S: UTF8String);
  233. function IsSameUTF8String(const S1, S2: UTF8String): Boolean;
  234. //special parsing functions //////////////////////////////////////////
  235. procedure ParseStyleElementContent(const value: UTF8String; stylesList: TClassStylesList);
  236. function ParseTransform(const transform: UTF8String): TMatrixD;
  237. procedure GetSvgFontInfo(const value: UTF8String; var fontInfo: TSVGFontInfo);
  238. function HtmlDecode(const html: UTF8String): UTF8String;
  239. function GetXmlEncoding(memory: Pointer; len: integer): TSvgEncoding;
  240. function ClampRange(val, min, max: double): double;
  241. function SkipBlanks(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  242. function SkipBlanksEx(c: PUTF8Char; endC: PUTF8Char): PUTF8Char; {$IFDEF INLINE} inline; {$ENDIF}
  243. function SkipBlanksAndComma(c, endC: PUTF8Char): PUTF8Char; {$IFDEF INLINE} inline; {$ENDIF}
  244. function GetCommaSeparatedArray(const str: UTF8String): UTF8Strings;
  245. function TrimQuotes(const str: UTF8String): UTF8String;
  246. procedure ConvertUnicodeToUtf8(memStream: TMemoryStream);
  247. function GetScale(src, dst: double): double;
  248. function GetScaleForBestFit(srcW, srcH, dstW, dstH: double): double;
  249. function Base64Decode(const str: PAnsiChar; len: integer; memStream: TMemoryStream): Boolean;
  250. type
  251. TSetOfUTF8Char = set of UTF8Char;
  252. function CharInSet(chr: UTF8Char; const chrs: TSetOfUTF8Char): Boolean;
  253. function DecodeUtf8ToUnicode(const utf8: UTF8String): UnicodeString;
  254. const
  255. clInvalid = $00010001;
  256. clCurrent = $00010002;
  257. sqrt2 = 1.4142135623731;
  258. quote = '''';
  259. dquote = '"';
  260. space = #32;
  261. comma = ',';
  262. SvgDecimalSeparator = '.'; //do not localize
  263. {$I Img32.SVG.HashConsts.inc}
  264. var
  265. LowerCaseTable : array[#0..#$FF] of UTF8Char;
  266. implementation
  267. //------------------------------------------------------------------------------
  268. // Color Constant HashMap
  269. //------------------------------------------------------------------------------
  270. type
  271. PColorConst = ^TColorConst;
  272. TColorConst = record
  273. ColorName : UTF8String;
  274. ColorValue: TColor32;
  275. end;
  276. PPColorConstMapItem = ^PColorConstMapItem;
  277. PColorConstMapItem = ^TColorConstMapItem;
  278. TColorConstMapItem = record
  279. Hash: Cardinal;
  280. Next: PColorConstMapItem;
  281. Data: PColorConst;
  282. end;
  283. PColorConstMapItemArray = ^TColorConstMapItemArray;
  284. TColorConstMapItemArray = array[0..MaxInt div SizeOf(TColorConstMapItem) - 1] of TColorConstMapItem;
  285. TColorConstList = class(TObject)
  286. private
  287. FItems: array of TColorConstMapItem;
  288. FBuckets: array of PColorConstMapItem;
  289. FCount: Integer;
  290. FMod: Cardinal;
  291. public
  292. constructor Create(Colors: PColorConst; Count: Integer);
  293. function GetColorValue(const ColorName: UTF8String; var Color: TColor32): Boolean;
  294. end;
  295. var
  296. ColorConstList : TColorConstList;
  297. const
  298. buffSize = 8;
  299. //include hashed html entity constants
  300. {$I Img32.SVG.HtmlHashConsts.inc}
  301. //------------------------------------------------------------------------------
  302. // Base64 (MIME) Encode & Decode and other encoding functions ...
  303. //------------------------------------------------------------------------------
  304. type
  305. PFourChars = ^TFourChars;
  306. TFourChars = record
  307. c1: ansichar;
  308. c2: ansichar;
  309. c3: ansichar;
  310. c4: ansichar;
  311. end;
  312. function Chr64ToVal(c: ansiChar): integer; {$IFDEF INLINE} inline; {$ENDIF}
  313. begin
  314. case c of
  315. '+': result := 62;
  316. '/': result := 63;
  317. '0'..'9': result := ord(c) + 4;
  318. 'A'..'Z': result := ord(c) -65;
  319. 'a'..'z': result := ord(c) -71;
  320. else Raise Exception.Create('Corrupted MIME encoded text');
  321. end;
  322. end;
  323. //------------------------------------------------------------------------------
  324. function FrstChr(c: PFourChars): ansichar; {$IFDEF INLINE} inline; {$ENDIF}
  325. begin
  326. result := ansichar(Chr64ToVal(c.c1) shl 2 or Chr64ToVal(c.c2) shr 4);
  327. end;
  328. //------------------------------------------------------------------------------
  329. function ScndChr(c: PFourChars): ansichar; {$IFDEF INLINE} inline; {$ENDIF}
  330. begin
  331. result := ansichar(Chr64ToVal(c.c2) shl 4 or Chr64ToVal(c.c3) shr 2);
  332. end;
  333. //------------------------------------------------------------------------------
  334. function ThrdChr(c: PFourChars): ansichar; {$IFDEF INLINE} inline; {$ENDIF}
  335. begin
  336. result := ansichar( Chr64ToVal(c.c3) shl 6 or Chr64ToVal(c.c4) );
  337. end;
  338. //------------------------------------------------------------------------------
  339. function Base64Decode(const str: PAnsiChar; len: integer; memStream: TMemoryStream): Boolean;
  340. var
  341. i, j, extra: integer;
  342. Chars4: PFourChars;
  343. dst: PAnsiChar;
  344. begin
  345. result := false;
  346. if (len = 0) or (len mod 4 > 0) or not Assigned(memStream) then exit;
  347. if str[len-2] = '=' then extra := 2
  348. else if str[len-1] = '=' then extra := 1
  349. else extra := 0;
  350. memStream.SetSize(LongInt((len div 4 * 3) - extra));
  351. dst := memStream.Memory;
  352. Chars4 := @str[0];
  353. i := 0;
  354. try
  355. for j := 1 to (len div 4) -1 do
  356. begin
  357. dst[i] := FrstChr(Chars4);
  358. dst[i+1] := ScndChr(Chars4);
  359. dst[i+2] := ThrdChr(Chars4);
  360. inc(pbyte(Chars4),4);
  361. inc(i,3);
  362. end;
  363. dst[i] := FrstChr(Chars4);
  364. if extra < 2 then dst[i+1] := ScndChr(Chars4);
  365. if extra < 1 then dst[i+2] := ThrdChr(Chars4);
  366. except
  367. Exit;
  368. end;
  369. Result := true;
  370. end;
  371. //------------------------------------------------------------------------------
  372. // Miscellaneous functions ...
  373. //------------------------------------------------------------------------------
  374. function NewSvgAttrib(): PSvgAttrib; {$IFDEF INLINE} inline; {$ENDIF}
  375. begin
  376. // New(Result) uses RTTI to initialize the UTF8String fields to nil.
  377. // By allocating zero'ed memory we can achieve that much faster.
  378. Result := AllocMem(SizeOf(TSvgAttrib));
  379. end;
  380. //------------------------------------------------------------------------------
  381. procedure DisposeSvgAttrib(attrib: PSvgAttrib); {$IFDEF INLINE} inline; {$ENDIF}
  382. begin
  383. // Dispose(Result) uses RTTI to set the UTF8String fields to nil.
  384. // By clearing them outself we can achieve that much faster.
  385. attrib.name := '';
  386. attrib.value := '';
  387. FreeMem(attrib);
  388. end;
  389. //------------------------------------------------------------------------------
  390. function GetScale(src, dst: double): double;
  391. begin
  392. Result := dst / src;
  393. if (SameValue(Result, 1, 0.00001)) then Result := 1;
  394. end;
  395. //------------------------------------------------------------------------------
  396. function GetScaleForBestFit(srcW, srcH, dstW, dstH: double): double;
  397. var
  398. sx,sy: double;
  399. begin
  400. sx := dstW / srcW;
  401. sy := dstH / srcH;
  402. if sy < sx then sx := sy;
  403. if (SameValue(sx, 1, 0.00001)) then
  404. Result := 1 else
  405. Result := sx;
  406. end;
  407. //------------------------------------------------------------------------------
  408. function ClampRange(val, min, max: double): double;
  409. {$IFDEF INLINE} inline; {$ENDIF}
  410. begin
  411. if val <= min then Result := min
  412. else if val >= max then Result := max
  413. else Result := val;
  414. end;
  415. //------------------------------------------------------------------------------
  416. function IsSameAsciiUTF8String(const S1, S2: UTF8String): Boolean;
  417. var
  418. Len: Integer;
  419. I: Integer;
  420. Ch1, Ch2: UTF8Char;
  421. begin
  422. Len := Length(S1);
  423. Result := Len = Length(S2);
  424. if Result then
  425. begin
  426. Result := False;
  427. I := 1;
  428. while True do
  429. begin
  430. if I > Len then
  431. Break;
  432. Ch1 := S1[I];
  433. Ch2 := S2[I];
  434. if Ch1 = Ch2 then
  435. begin
  436. Inc(I);
  437. Continue;
  438. end;
  439. case Ch1 of
  440. 'A'..'Z', 'a'..'z':
  441. ch1 := UTF8Char(Ord(ch1) xor $20); // toggle upper/lower
  442. end;
  443. if Ch1 <> Ch2 then
  444. Exit;
  445. Inc(I);
  446. end;
  447. Result := True;
  448. end;
  449. end;
  450. //------------------------------------------------------------------------------
  451. function IsSameUTF8StringSlow(const S1, S2: UTF8String): Boolean;
  452. begin
  453. Result := AnsiSameText(string(S1), string(S2));
  454. end;
  455. //------------------------------------------------------------------------------
  456. function IsSameUTF8String(const S1, S2: UTF8String): Boolean;
  457. var
  458. Len: Integer;
  459. I: Integer;
  460. Ch1, Ch2: UTF8Char;
  461. begin
  462. Len := Length(S1);
  463. Result := Len = Length(S2);
  464. if Result then
  465. begin
  466. Result := False;
  467. I := 1;
  468. Ch1 := #0;
  469. Ch2 := #0;
  470. while True do
  471. begin
  472. if I > Len then
  473. Break;
  474. Ch1 := S1[I];
  475. Ch2 := S2[I];
  476. if Ch1 = Ch2 then
  477. begin
  478. Inc(I);
  479. Continue;
  480. end;
  481. case Ch1 of
  482. 'A'..'Z', 'a'..'z':
  483. ch1 := UTF8Char(Ord(ch1) xor $20); // toggle upper/lower
  484. end;
  485. if Ch1 <> Ch2 then
  486. Break;
  487. Inc(I);
  488. end;
  489. if Ch1 = Ch2 then
  490. Result := True
  491. else if (Ord(Ch1) or Ord(Ch2)) and $80 <> 0 then // we found non-matching, non-ASCII characters
  492. Result := IsSameUTF8StringSlow(S1, S2);
  493. end;
  494. end;
  495. //------------------------------------------------------------------------------
  496. function CharInSet(chr: UTF8Char; const chrs: TSetOfUTF8Char): Boolean;
  497. begin
  498. Result := chr in chrs;
  499. end;
  500. //------------------------------------------------------------------------------
  501. function Match(c: PUTF8Char; const compare: UTF8String): Boolean;
  502. var
  503. i: integer;
  504. begin
  505. Result := false;
  506. for i := 1 to Length(compare) do
  507. begin
  508. if LowerCaseTable[c[i - 1]] <> compare[i] then Exit;
  509. end;
  510. Result := true;
  511. end;
  512. //------------------------------------------------------------------------------
  513. function Match(const compare1, compare2: UTF8String): Boolean;
  514. var
  515. i, len: integer;
  516. c1, c2: PUTF8Char;
  517. begin
  518. Result := false;
  519. len := Length(compare1);
  520. if len <> Length(compare2) then Exit;
  521. c1 := @compare1[1]; c2 := @compare2[1];
  522. for i := 1 to len do
  523. begin
  524. if LowerCaseTable[c1[i - 1]] <> LowerCaseTable[c2[i - 1]] then Exit;
  525. end;
  526. Result := true;
  527. end;
  528. //------------------------------------------------------------------------------
  529. function Split(const str: UTF8String): UTF8Strings;
  530. var
  531. i,j,k, spcCnt, len: integer;
  532. begin
  533. spcCnt := 0;
  534. i := 1;
  535. len := Length(str);
  536. while (len > 0) and (str[len] <= space) do dec(len);
  537. while (i <= len) and (str[i] <= space) do inc(i);
  538. for j := i + 1 to len do
  539. if (str[j] <= space) and (str[j -1] > space) then inc(spcCnt);
  540. SetLength(Result, spcCnt +1);
  541. for k := 0 to spcCnt do
  542. begin
  543. j := i;
  544. while (j <= len) and (str[j] > space) do inc(j);
  545. SetLength(Result[k], j -i);
  546. if j > i then
  547. Move(str[i], Result[k][1], j -i);
  548. while (j <= len) and (str[j] <= space) do inc(j);
  549. i := j;
  550. end;
  551. end;
  552. //------------------------------------------------------------------------------
  553. function TrimQuotes(const str: UTF8String): UTF8String;
  554. var
  555. i, len: integer;
  556. savedQuote: UTF8Char;
  557. begin
  558. len := Length(str);
  559. i := 1;
  560. while (i < len) and (str[i] <= space) do inc(i);
  561. if (i < len) and (str[i] in [quote, dquote]) then
  562. begin
  563. savedQuote := str[i];
  564. inc(i);
  565. while (len > i) and (str[len] <= space) do dec(len);
  566. if (len = i) or (str[len] <> savedQuote) then
  567. Result := str else // oops!
  568. Result := Copy(str, i, len - i);
  569. end
  570. else
  571. Result := str
  572. end;
  573. //------------------------------------------------------------------------------
  574. function GetCommaSeparatedArray(const str: UTF8String): UTF8Strings;
  575. var
  576. i,j,k, cnt, len: integer;
  577. begin
  578. // precondition: commas CANNOT be embedded
  579. len := Length(str);
  580. cnt := 1;
  581. for i := 1 to len do
  582. if (str[i] = comma) then inc(cnt);
  583. SetLength(Result, cnt);
  584. j := 0;
  585. k := 1;
  586. for i := 1 to len do
  587. begin
  588. if (str[i] <> comma) then Continue;
  589. Result[j] := TrimQuotes(Copy(str, k, i-k));
  590. inc(j);
  591. k := i + 1;
  592. end;
  593. if len >= k then
  594. Result[j] := TrimQuotes(Copy(str, k, len-k +1));
  595. end;
  596. //------------------------------------------------------------------------------
  597. function GetXmlEncoding(memory: Pointer; len: integer): TSvgEncoding;
  598. var
  599. p, p1: PUTF8Char;
  600. begin
  601. Result := eUnknown;
  602. if (len < 4) or not Assigned(memory) then Exit;
  603. p := PUTF8Char(memory);
  604. p1 := (p + 1);
  605. case p^ of
  606. #$EF: if (p1^ = #$BB) then
  607. if ((p +2)^ = #$BF) then
  608. Result := eUtf8 else
  609. Exit;
  610. #$FF: if (p1^ = #$FE) or (p1^ = #0) then
  611. Result := eUnicodeLE;
  612. #$FE: if (p1^ = #$FF) then
  613. Result := eUnicodeBE;
  614. end;
  615. end;
  616. //------------------------------------------------------------------------------
  617. function SkipBlanks(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  618. var
  619. cc: PUTF8Char;
  620. begin
  621. cc := c;
  622. if (cc < endC) and (cc^ <= space) then
  623. begin
  624. inc(cc);
  625. while (cc < endC) and (cc^ <= space) do inc(cc);
  626. c := cc;
  627. end;
  628. Result := (cc < endC);
  629. end;
  630. //------------------------------------------------------------------------------
  631. function SkipBlanksEx(c: PUTF8Char; endC: PUTF8Char): PUTF8Char;
  632. begin
  633. while (c < endC) and (c^ <= space) do inc(c);
  634. Result := c;
  635. end;
  636. //------------------------------------------------------------------------------
  637. function SkipBlanksAndComma(c, endC: PUTF8Char): PUTF8Char;
  638. begin
  639. Result := SkipBlanksEx(c, endC);
  640. if (Result >= endC) or (Result^ <> ',') then Exit;
  641. Result := SkipBlanksEx(Result + 1, endC);
  642. end;
  643. //------------------------------------------------------------------------------
  644. function SkipStyleBlanks(c, endC: PUTF8Char): PUTF8Char;
  645. var
  646. inComment: Boolean;
  647. ch: UTF8Char;
  648. begin
  649. //style content may include multi-line comment blocks
  650. inComment := false;
  651. while (c < endC) do
  652. begin
  653. ch := c^;
  654. if inComment then
  655. begin
  656. if (ch = '*') and ((c +1)^ = '/') then
  657. begin
  658. inComment := false;
  659. inc(c);
  660. end;
  661. end
  662. else if (ch > space) then
  663. begin
  664. inComment := (ch = '/') and ((c +1)^ = '*');
  665. if not inComment then break;
  666. inc(c);
  667. end;
  668. inc(c);
  669. end;
  670. Result := c;
  671. end;
  672. //------------------------------------------------------------------------------
  673. function IsDigit(c: UTF8Char): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
  674. begin
  675. case c of
  676. '0'..'9': Result := True;
  677. else Result := False;
  678. end;
  679. end;
  680. //------------------------------------------------------------------------------
  681. function IsQuoteChar(c: UTF8Char): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
  682. begin
  683. Result := (c = quote) or (c = dquote);
  684. end;
  685. //------------------------------------------------------------------------------
  686. function IsAlpha(c: UTF8Char): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
  687. begin
  688. case c of
  689. 'A'..'Z', 'a'..'z': Result := True;
  690. else Result := False;
  691. end;
  692. end;
  693. //------------------------------------------------------------------------------
  694. function ParseStyleNameLen(c, endC: PUTF8Char): PUTF8Char;
  695. var
  696. c2: PUTF8Char;
  697. begin
  698. Result := c;
  699. //nb: style names may start with a hyphen
  700. c2 := Result;
  701. if (c2^ = '-') then inc(c2);
  702. if not IsAlpha(c2^) then Exit;
  703. Result := c2 + 1;
  704. while Result < endC do
  705. begin
  706. case Result^ of
  707. '0'..'9', 'A'..'Z', 'a'..'z', '-': inc(Result);
  708. else break;
  709. end;
  710. end;
  711. end;
  712. //------------------------------------------------------------------------------
  713. function ParseNextWord(var c: PUTF8Char; endC: PUTF8Char; out word: UTF8String): Boolean;
  714. var
  715. c2, cc: PUTF8Char;
  716. begin
  717. cc := SkipBlanksAndComma(c, endC);
  718. if cc >= endC then
  719. begin
  720. c := cc;
  721. Result := False;
  722. Exit;
  723. end;
  724. c2 := cc;
  725. while cc < endC do
  726. begin
  727. case cc^ of
  728. 'A'..'Z', 'a'..'z': inc(cc);
  729. else break;
  730. end;
  731. end;
  732. c := cc;
  733. ToUTF8String(c2, cc, word);
  734. Result := True;
  735. end;
  736. //------------------------------------------------------------------------------
  737. function ParseNextWordHash(var c: PUTF8Char; endC: PUTF8Char; out hash: cardinal): Boolean;
  738. var
  739. c2, cc: PUTF8Char;
  740. begin
  741. cc := SkipBlanksAndComma(c, endC);
  742. if cc >= endC then
  743. begin
  744. c := cc;
  745. hash := 0;
  746. Result := False;
  747. Exit;
  748. end;
  749. c2 := cc;
  750. while cc < endC do
  751. begin
  752. case cc^ of
  753. 'A'..'Z', 'a'..'z': inc(cc);
  754. else break;
  755. end;
  756. end;
  757. c := cc;
  758. hash := GetHash(c2, cc - c2);
  759. Result := True;
  760. end;
  761. //------------------------------------------------------------------------------
  762. function ParseNextWordHash(c, endC: PUTF8Char): cardinal;
  763. var
  764. c2: PUTF8Char;
  765. begin
  766. c := SkipBlanksAndComma(c, endC);
  767. if c >= endC then
  768. begin
  769. Result := 0;
  770. Exit;
  771. end;
  772. c2 := c;
  773. while c < endC do
  774. begin
  775. case c^ of
  776. 'A'..'Z', 'a'..'z': inc(c);
  777. else break;
  778. end;
  779. end;
  780. Result := GetHash(c2, c - c2);
  781. end;
  782. //------------------------------------------------------------------------------
  783. function ParseNextWordExHash(var c: PUTF8Char; endC: PUTF8Char;
  784. out hash: cardinal): Boolean;
  785. var
  786. c2, cc: PUTF8Char;
  787. begin
  788. cc := SkipBlanksAndComma(c, endC);
  789. if cc >= endC then
  790. begin
  791. c := cc;
  792. hash := 0;
  793. Result := False;
  794. Exit;
  795. end;
  796. if cc^ = quote then
  797. begin
  798. inc(c);
  799. c2 := cc;
  800. while (cc < endC) and (cc^ <> quote) do inc(cc);
  801. hash := GetHash(c2, cc - c2);
  802. inc(cc);
  803. end else
  804. begin
  805. if not IsAlpha(cc^) then
  806. begin
  807. hash := 0;
  808. Result := False;
  809. Exit;
  810. end;
  811. c2 := cc;
  812. inc(cc);
  813. while cc < endC do
  814. case cc^ of
  815. 'A'..'Z', 'a'..'z', '-', '_': inc(cc);
  816. else break;
  817. end;
  818. hash := GetHash(c2, cc - c2);
  819. end;
  820. c := cc;
  821. Result := True;
  822. end;
  823. //------------------------------------------------------------------------------
  824. function ParseNameLength(c: PUTF8Char; endC: PUTF8Char): PUTF8Char;
  825. begin
  826. inc(c);
  827. while c < endC do
  828. begin
  829. case c^ of
  830. '0'..'9', 'A'..'Z', 'a'..'z', '_', ':', '-': inc(c);
  831. else break;
  832. end;
  833. end;
  834. Result := c;
  835. end;
  836. //------------------------------------------------------------------------------
  837. {$PUSH}{$Q-}{$R-}
  838. function GetHash(c: PUTF8Char; len: nativeint): cardinal;
  839. var
  840. i: integer;
  841. begin
  842. //https://en.wikipedia.org/wiki/Jenkins_hash_function
  843. Result := 0;
  844. if c = nil then Exit;
  845. for i := 1 to len do
  846. begin
  847. Result := (Result + Ord(LowerCaseTable[c^]));
  848. Result := Result + (Result shl 10);
  849. Result := Result xor (Result shr 6);
  850. inc(c);
  851. end;
  852. Result := Result + (Result shl 3);
  853. Result := Result xor (Result shr 11);
  854. Result := Result + (Result shl 15);
  855. end;
  856. {$POP}
  857. //------------------------------------------------------------------------------
  858. function GetHash(const name: UTF8String): cardinal;
  859. begin
  860. // skip function call by directly casting it to Pointer
  861. Result := GetHash(PUTF8Char(Pointer(name)), Length(name));
  862. end;
  863. //------------------------------------------------------------------------------
  864. {$PUSH}{$Q-}{$R-}
  865. function GetHashCaseSensitive(name: PUTF8Char; nameLen: integer): cardinal;
  866. var
  867. i: integer;
  868. begin
  869. Result := 0;
  870. for i := 1 to nameLen do
  871. begin
  872. Result := (Result + Ord(name^));
  873. Result := Result + (Result shl 10);
  874. Result := Result xor (Result shr 6);
  875. inc(name);
  876. end;
  877. Result := Result + (Result shl 3);
  878. Result := Result xor (Result shr 11);
  879. Result := Result + (Result shl 15);
  880. end;
  881. {$POP}
  882. //------------------------------------------------------------------------------
  883. function ParseNextWordHashed(var c: PUTF8Char; endC: PUTF8Char): cardinal;
  884. var
  885. c2: PUTF8Char;
  886. len: integer;
  887. begin
  888. c2 := c;
  889. c := ParseNameLength(c2, endC);
  890. len := c - c2;
  891. if len <= 0 then Result := 0
  892. else Result := GetHash(c2, len);
  893. end;
  894. //------------------------------------------------------------------------------
  895. function ParseExpDigits(c, endC: PUTF8Char; out val: Integer): PUTF8Char; {$IFDEF INLINE} inline; {$ENDIF}
  896. var
  897. v32: Cardinal;
  898. Digit: Integer;
  899. begin
  900. Result := c;
  901. v32 := 0;
  902. while Result < endC do
  903. begin
  904. Digit := Integer(Ord(Result^)) - Ord('0');
  905. if Cardinal(Digit) >= 10 then break;
  906. {$IFDEF FPC} // Something Delphi can optimize but FPC can't (yet?)
  907. v32 := (v32 shl 3) + (v32 shl 1) + Cardinal(Digit); // Delphi's code is even better than this
  908. {$ELSE}
  909. v32 := v32 * 10 + Cardinal(Digit);
  910. {$ENDIF FPC}
  911. inc(Result);
  912. end;
  913. val := v32;
  914. end;
  915. //------------------------------------------------------------------------------
  916. function ParseDigitsToDouble(c, endC: PUTF8Char; out val: double): PUTF8Char;
  917. var
  918. v32: Cardinal;
  919. v64: Int64;
  920. Digit: Integer;
  921. blockEndC: PUTF8Char;
  922. begin
  923. // skip leading zeros
  924. while (c < endC) and (c^ = '0') do inc(c);
  925. // Use Int32 first as it is fast for 64bit and 32bit CPUs
  926. Result := c;
  927. v32 := 0;
  928. blockEndC := c + 9; // log10(2^31) = 9.33
  929. if blockEndC > endC then
  930. blockEndC := endC;
  931. while Result < blockEndC do
  932. begin
  933. Digit := Integer(Ord(Result^)) - Ord('0');
  934. if Cardinal(Digit) >= 10 then break;
  935. {$IFDEF FPC} // Something Delphi can optimize but FPC can't (yet?)
  936. v32 := (v32 shl 3) + (v32 shl 1) + Cardinal(Digit);
  937. {$ELSE}
  938. v32 := v32 * 10 + Cardinal(Digit);
  939. {$ENDIF FPC}
  940. inc(Result);
  941. end;
  942. if (Result < endC) and (Result >= blockEndC) then
  943. begin
  944. v64 := v32;
  945. blockEndC := c + 18; // log10(2^63) = 18.96
  946. if blockEndC > endC then
  947. blockEndC := endC;
  948. while Result < blockEndC do
  949. begin
  950. Digit := Integer(Ord(Result^)) - Ord('0');
  951. if Cardinal(Digit) >= 10 then break;
  952. {$IF (SizeOf(Pointer) = 4) or defined(FPC)} // neither Delphi 32bit nor FPC can optimize this
  953. v64 := (v64 shl 3) + (v64 shl 1) + Cardinal(Digit);
  954. {$ELSE}
  955. v64 := v64 * 10 + Cardinal(Digit);
  956. {$IFEND}
  957. inc(Result);
  958. end;
  959. val := v64;
  960. // Use Double for the remaining digits and loose precision (we are beyong 16 digits anyway)
  961. if (Result < endC) and (Result >= blockEndC) then
  962. begin
  963. while Result < endC do
  964. begin
  965. Digit := Integer(Ord(Result^)) - Ord('0');
  966. if Cardinal(Digit) >= 10 then break;
  967. val := val * 10 + Digit;
  968. inc(Result);
  969. end;
  970. end;
  971. end
  972. else
  973. val := v32;
  974. end;
  975. //------------------------------------------------------------------------------
  976. function ParseNextNumEx(var c: PUTF8Char; endC: PUTF8Char; skipComma: Boolean;
  977. out val: double; out unitType: TUnitType): Boolean;
  978. const
  979. Power10: array[0..18] of Double = (
  980. 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9,
  981. 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18
  982. );
  983. Power10Reciprocal: array[0..18] of Double = (
  984. 1/1E0, 1/1E1, 1/1E2, 1/1E3, 1/1E4, 1/1E5, 1/1E6, 1/1E7, 1/1E8, 1/1E9,
  985. 1/1E10, 1/1E11, 1/1E12, 1/1E13, 1/1E14, 1/1E15, 1/1E16, 1/1E17, 1/1E18
  986. );
  987. var
  988. exp: integer;
  989. isNeg, expIsNeg: Boolean;
  990. start, decStart, cc: PUTF8Char;
  991. decimals: Double;
  992. begin
  993. Result := false;
  994. unitType := utNumber;
  995. cc := c;
  996. //skip white space +/- single comma
  997. if skipComma then
  998. begin
  999. while (cc < endC) and (cc^ <= space) do inc(cc);
  1000. if (cc^ = ',') then inc(cc);
  1001. end;
  1002. while (cc < endC) and (cc^ <= space) do inc(cc);
  1003. if (cc = endC) then
  1004. begin
  1005. c := cc;
  1006. Exit;
  1007. end;
  1008. exp := Invalid; expIsNeg := false;
  1009. isNeg := cc^ = '-';
  1010. if isNeg then inc(cc);
  1011. start := cc;
  1012. // Use fast parsing
  1013. cc := ParseDigitsToDouble(cc, endC, val);
  1014. if cc < endC then
  1015. begin
  1016. // Decimals
  1017. if Ord(cc^) = Ord(SvgDecimalSeparator) then
  1018. begin
  1019. inc(cc);
  1020. decStart := cc;
  1021. cc := ParseDigitsToDouble(cc, endC, decimals);
  1022. if cc > decStart then
  1023. begin
  1024. if cc - decStart <= 18 then
  1025. val := val + (decimals * Power10Reciprocal[(cc - decStart)])
  1026. else
  1027. val := val + (decimals * Power(10, -(cc - decStart)))
  1028. end;
  1029. end;
  1030. // Exponent
  1031. if (cc < endC) and ((cc^ = 'e') or (cc^ = 'E')) then
  1032. begin
  1033. case (cc+1)^ of
  1034. '-', '0'..'9':
  1035. begin
  1036. inc(cc);
  1037. if cc^ = '-' then
  1038. begin
  1039. expIsNeg := true;
  1040. inc(cc);
  1041. end;
  1042. cc := ParseExpDigits(cc, endC, exp);
  1043. end;
  1044. end;
  1045. end;
  1046. end;
  1047. Result := cc > start;
  1048. if not Result then
  1049. begin
  1050. c := cc;
  1051. Exit;
  1052. end;
  1053. if isNeg then val := -val;
  1054. if IsValid(exp) then
  1055. begin
  1056. if exp <= 18 then
  1057. begin
  1058. if expIsNeg then
  1059. val := val * Power10Reciprocal[exp] else
  1060. val := val * Power10[exp];
  1061. end
  1062. else
  1063. begin
  1064. if expIsNeg then
  1065. val := val * Power(10, -exp) else
  1066. val := val * Power(10, exp);
  1067. end;
  1068. end;
  1069. //https://oreillymedia.github.io/Using_SVG/guide/units.html
  1070. case cc^ of
  1071. '%':
  1072. begin
  1073. inc(cc);
  1074. unitType := utPercent;
  1075. end;
  1076. 'c': //convert cm to pixels
  1077. if ((cc+1)^ = 'm') then
  1078. begin
  1079. inc(cc, 2);
  1080. unitType := utCm;
  1081. end;
  1082. 'd': //ignore deg
  1083. if ((cc+1)^ = 'e') and ((cc+2)^ = 'g') then
  1084. begin
  1085. inc(cc, 3);
  1086. unitType := utDegree;
  1087. end;
  1088. 'e': //convert cm to pixels
  1089. if ((cc+1)^ = 'm') then
  1090. begin
  1091. inc(cc, 2);
  1092. unitType := utEm;
  1093. end
  1094. else if ((cc+1)^ = 'x') then
  1095. begin
  1096. inc(cc, 2);
  1097. unitType := utEx;
  1098. end;
  1099. 'i': //convert inchs to pixels
  1100. if ((cc+1)^ = 'n') then
  1101. begin
  1102. inc(cc, 2);
  1103. unitType := utInch;
  1104. end;
  1105. 'm': //convert mm to pixels
  1106. if ((cc+1)^ = 'm') then
  1107. begin
  1108. inc(cc, 2);
  1109. unitType := utMm;
  1110. end;
  1111. 'p':
  1112. case (cc+1)^ of
  1113. 'c':
  1114. begin
  1115. inc(cc, 2);
  1116. unitType := utPica;
  1117. end;
  1118. 't':
  1119. begin
  1120. inc(cc, 2);
  1121. unitType := utPt;
  1122. end;
  1123. 'x':
  1124. begin
  1125. inc(cc, 2);
  1126. unitType := utPixel;
  1127. end;
  1128. end;
  1129. 'r': //convert radian angles to degrees
  1130. if Match(cc, 'rad') then
  1131. begin
  1132. inc(cc, 3);
  1133. unitType := utRadian;
  1134. end;
  1135. end;
  1136. c := cc;
  1137. end;
  1138. //------------------------------------------------------------------------------
  1139. function ParseNextNum(var c: PUTF8Char; endC: PUTF8Char;
  1140. skipComma: Boolean; out val: double): Boolean;
  1141. var
  1142. tmp: TValue;
  1143. begin
  1144. tmp.Init;
  1145. Result := ParseNextNumEx(c, endC, skipComma, tmp.rawVal, tmp.unitType);
  1146. val := tmp.GetValue(1, 1);
  1147. end;
  1148. //------------------------------------------------------------------------------
  1149. function ExtractRef(const href: UTF8String): UTF8String; {$IFDEF INLINE} inline; {$ENDIF}
  1150. var
  1151. c, c2, endC: PUTF8Char;
  1152. begin
  1153. c := PUTF8Char(href);
  1154. endC := c + Length(href);
  1155. if Match(c, 'url(') then
  1156. begin
  1157. inc(c, 4);
  1158. dec(endC); // avoid trailing ')'
  1159. end;
  1160. if c^ = '#' then inc(c);
  1161. c2 := c;
  1162. while (c < endC) and (c^ <> ')') do inc(c);
  1163. ToUTF8String(c2, c, Result);
  1164. end;
  1165. //------------------------------------------------------------------------------
  1166. function ParseNextChar(var c: PUTF8Char; endC: PUTF8Char): UTF8Char;
  1167. var
  1168. cc: PUTF8Char;
  1169. begin
  1170. cc := SkipBlanksEx(c, endC);
  1171. if cc >= endC then
  1172. Result := #0
  1173. else
  1174. begin
  1175. Result := cc^;
  1176. c := cc + 1;
  1177. end;
  1178. end;
  1179. //------------------------------------------------------------------------------
  1180. procedure ToTrimmedUTF8String(c, endC: PUTF8Char; var S: UTF8String);
  1181. var
  1182. len: integer;
  1183. begin
  1184. // trim left
  1185. while (c < endC) and (c^ <= space) do Inc(c);
  1186. // trim right
  1187. while (endC > c) and (endC[-1] <= space) do Dec(endC);
  1188. len := endC - c;
  1189. SetLength(S, len);
  1190. if len = 0 then Exit;
  1191. Move(c^, PUTF8Char(S)^, len * SizeOf(UTF8Char));
  1192. end;
  1193. //------------------------------------------------------------------------------
  1194. function PosEx(const subStr: UTF8String; const text: Utf8String; startIdx: integer): integer;
  1195. var
  1196. i, maxI, len, subStrLen: integer;
  1197. begin
  1198. len := Length(Text);
  1199. subStrLen := Length(subStr);
  1200. maxI := len - subStrLen +1;
  1201. for i := Max(1, startIdx) to maxI do
  1202. begin
  1203. if (text[i] <> subStr[1]) or
  1204. not CompareMem(@text[i], @subStr[1], subStrLen) then Continue;
  1205. Result := i;
  1206. Exit;
  1207. end;
  1208. Result := 0;
  1209. end;
  1210. //------------------------------------------------------------------------------
  1211. function ReversePosEx(utf8: utf8Char;
  1212. const text: Utf8String; startIdx: integer): integer; overload;
  1213. {$IFDEF INLINE} inline; {$ENDIF}
  1214. begin
  1215. Result := Max(0, Min(Length(text), startidx));
  1216. while (Result > 0) and (text[Result] <> utf8) do Dec(Result);
  1217. end;
  1218. //------------------------------------------------------------------------------
  1219. function TrimMultiSpacesUtf8(const text: Utf8String): Utf8String;
  1220. var
  1221. i, len: integer;
  1222. begin
  1223. Result := text;
  1224. len := Length(Result);
  1225. for i := 1 to len do
  1226. if Result[i] < #32 then Result[i] := #32;
  1227. i := ReversePosEx(space, Result, len);
  1228. while i > 1 do
  1229. begin
  1230. Dec(i);
  1231. while (i > 0) and (Result[i] = space) do
  1232. begin
  1233. Delete(Result, i, 1);
  1234. Dec(i);
  1235. end;
  1236. i := ReversePosEx(space, Result, i);
  1237. end;
  1238. end;
  1239. //------------------------------------------------------------------------------
  1240. function ReversePosEx(c: WideChar;
  1241. const text: UnicodeString; startIdx: integer): integer; overload;
  1242. {$IFDEF INLINE} inline; {$ENDIF}
  1243. begin
  1244. Result := Max(0, Min(Length(text), startidx));
  1245. while (Result > 0) and (text[Result] <> c) do Dec(Result);
  1246. end;
  1247. //------------------------------------------------------------------------------
  1248. function TrimMultiSpacesUnicode(const text: UnicodeString): UnicodeString;
  1249. var
  1250. i, len: integer;
  1251. begin
  1252. Result := text;
  1253. len := Length(Result);
  1254. for i := 1 to len do
  1255. if Result[i] < #32 then Result[i] := #32;
  1256. i := ReversePosEx(space, Result, len);
  1257. while i > 1 do
  1258. begin
  1259. Dec(i);
  1260. while (i > 0) and (Result[i] = space) do
  1261. begin
  1262. Delete(Result, i, 1);
  1263. Dec(i);
  1264. end;
  1265. i := ReversePosEx(space, Result, i);
  1266. end;
  1267. end;
  1268. //------------------------------------------------------------------------------
  1269. function StripNewlines(const s: UTF8String): UTF8String;
  1270. var
  1271. i: integer;
  1272. begin
  1273. Result := s;
  1274. i := Length(Result);
  1275. while i > 0 do
  1276. begin
  1277. if Result[i] < space then Delete(Result, i, 1);
  1278. Dec(i);
  1279. end;
  1280. end;
  1281. //------------------------------------------------------------------------------
  1282. function StripNewlines(const s: UnicodeString): UnicodeString;
  1283. var
  1284. i: integer;
  1285. begin
  1286. Result := s;
  1287. i := Length(Result);
  1288. while i > 0 do
  1289. begin
  1290. if Result[i] < space then Delete(Result, i, 1);
  1291. Dec(i);
  1292. end;
  1293. end;
  1294. //------------------------------------------------------------------------------
  1295. function ConvertNewlines(const s: UTF8String): UTF8String; overload;
  1296. var
  1297. i: integer;
  1298. begin
  1299. Result := s;
  1300. i := Length(Result);
  1301. while i > 0 do
  1302. begin
  1303. if Result[i] < space then
  1304. begin
  1305. if Result[i] = #10 then
  1306. Result[i] := space else
  1307. Delete(Result, i, 1);
  1308. end;
  1309. Dec(i);
  1310. end;
  1311. end;
  1312. //------------------------------------------------------------------------------
  1313. function ConvertNewlines(const s: UnicodeString): UnicodeString; overload;
  1314. var
  1315. i: integer;
  1316. begin
  1317. Result := s;
  1318. i := Length(Result);
  1319. while i > 0 do
  1320. begin
  1321. if Result[i] < space then
  1322. begin
  1323. if Result[i] = #10 then
  1324. Result[i] := space else
  1325. Delete(Result, i, 1);
  1326. end;
  1327. Dec(i);
  1328. end;
  1329. end;
  1330. //------------------------------------------------------------------------------
  1331. procedure ToUTF8String(c, endC: PUTF8Char;
  1332. var S: UTF8String; spacesInText: TSpacesInText);
  1333. var
  1334. len: integer;
  1335. begin
  1336. len := endC - c;
  1337. SetLength(S, len);
  1338. if len = 0 then Exit;
  1339. Move(c^, PUTF8Char(S)^, len * SizeOf(UTF8Char));
  1340. if spacesInText <> sitPreserve then
  1341. S := TrimMultiSpacesUtf8(S);
  1342. S := ConvertNewlines(S);
  1343. end;
  1344. //------------------------------------------------------------------------------
  1345. procedure ToAsciiLowerUTF8String(c, endC: PUTF8Char; var S: UTF8String);
  1346. // Reads a UTF8String and converts all upper case 'A'..'Z' to lower case 'a'..'z'
  1347. var
  1348. len: integer;
  1349. p: PUTF8Char;
  1350. ch: UTF8Char;
  1351. begin
  1352. len := endC - c;
  1353. SetLength(S, len);
  1354. if len = 0 then Exit;
  1355. // Use a pointer arithmetic trick to run forward by using a negative index
  1356. p := PUTF8Char(S) + len;
  1357. len := -len;
  1358. while len < 0 do
  1359. begin
  1360. ch := endC[len];
  1361. case ch of
  1362. 'A'..'Z':
  1363. ch := UTF8Char(Byte(ch) or $20);
  1364. end;
  1365. p[len] := ch;
  1366. inc(len);
  1367. end;
  1368. end;
  1369. //------------------------------------------------------------------------------
  1370. function IsKnownEntity(owner: TSvgParser;
  1371. var c: PUTF8Char; endC: PUTF8Char; out entity: PSvgAttrib): boolean;
  1372. var
  1373. c2, c3: PUTF8Char;
  1374. begin
  1375. inc(c); //skip ampersand.
  1376. c2 := c; c3 := c;
  1377. c3 := ParseNameLength(c3, endC);
  1378. entity := owner.FindEntity(GetHash(c2, c3 - c2));
  1379. Result := (c3^ = ';') and Assigned(entity);
  1380. //nb: increments 'c' only if the entity is found.
  1381. if Result then c := c3 +1 else dec(c);
  1382. end;
  1383. //------------------------------------------------------------------------------
  1384. function ParseQuotedString(var c: PUTF8Char; endC: PUTF8Char;
  1385. out quotStr: UTF8String): Boolean;
  1386. var
  1387. quote: UTF8Char;
  1388. c2: PUTF8Char;
  1389. begin
  1390. quote := c^;
  1391. inc(c);
  1392. c2 := c;
  1393. while (c < endC) and (c^ <> quote) do inc(c);
  1394. Result := (c < endC);
  1395. if not Result then Exit;
  1396. ToUTF8String(c2, c, quotStr);
  1397. inc(c);
  1398. end;
  1399. //------------------------------------------------------------------------------
  1400. function IsNumPending(var c: PUTF8Char;
  1401. endC: PUTF8Char; ignoreComma: Boolean): Boolean;
  1402. var
  1403. c2: PUTF8Char;
  1404. begin
  1405. Result := false;
  1406. //skip white space +/- single comma
  1407. if ignoreComma then
  1408. begin
  1409. while (c < endC) and (c^ <= space) do inc(c);
  1410. if (c^ = ',') then inc(c);
  1411. end;
  1412. while (c < endC) and (c^ <= ' ') do inc(c);
  1413. if (c = endC) then Exit;
  1414. c2 := c;
  1415. if (c2^ = '-') then inc(c2);
  1416. if (c2^ = SvgDecimalSeparator) then inc(c2);
  1417. Result := (c2 < endC) and IsDigit(c2^);
  1418. end;
  1419. //------------------------------------------------------------------------------
  1420. function ParseTransform(const transform: UTF8String): TMatrixD;
  1421. var
  1422. i: integer;
  1423. c, endC: PUTF8Char;
  1424. c2: UTF8Char;
  1425. word: UTF8String;
  1426. values: array[0..5] of double;
  1427. mat: TMatrixD;
  1428. begin
  1429. c := PUTF8Char(transform);
  1430. endC := c + Length(transform);
  1431. Result := IdentityMatrix; //in case of invalid or referenced value
  1432. while ParseNextWord(c, endC, word) do
  1433. begin
  1434. if Length(word) < 5 then Exit;
  1435. if ParseNextChar(c, endC) <> '(' then Exit; //syntax check
  1436. //reset values variables
  1437. for i := 0 to High(values) do values[i] := InvalidD;
  1438. //and since every transform function requires at least one value
  1439. if not ParseNextNum(c, endC, false, values[0]) then Break;
  1440. //now get additional variables
  1441. i := 1;
  1442. while (i < 6) and IsNumPending(c, endC, true) and
  1443. ParseNextNum(c, endC, true, values[i]) do inc(i);
  1444. if ParseNextChar(c, endC) <> ')' then Exit; //syntax check
  1445. mat := IdentityMatrix;
  1446. //scal(e), matr(i)x, tran(s)late, rota(t)e, skew(X), skew(Y)
  1447. case LowerCaseTable[word[5]] of
  1448. 'e' : //scalE
  1449. if not IsValid(values[1]) then
  1450. MatrixScale(mat, values[0]) else
  1451. MatrixScale(mat, values[0], values[1]);
  1452. 'i' : //matrIx
  1453. if IsValid(values[5]) then
  1454. begin
  1455. mat[0,0] := values[0];
  1456. mat[0,1] := values[1];
  1457. mat[1,0] := values[2];
  1458. mat[1,1] := values[3];
  1459. mat[2,0] := values[4];
  1460. mat[2,1] := values[5];
  1461. end;
  1462. 's' : //tranSlateX, tranSlateY & tranSlate
  1463. if Length(word) =10 then
  1464. begin
  1465. c2 := LowerCaseTable[word[10]];
  1466. if c2 = 'x' then
  1467. MatrixTranslate(mat, values[0], 0)
  1468. else if c2 = 'y' then
  1469. MatrixTranslate(mat, 0, values[0]);
  1470. end
  1471. else if IsValid(values[1]) then
  1472. MatrixTranslate(mat, values[0], values[1])
  1473. else
  1474. MatrixTranslate(mat, values[0], 0);
  1475. 't' : //rotaTe
  1476. if IsValid(values[2]) then
  1477. MatrixRotate(mat, PointD(values[1],values[2]), DegToRad(values[0]))
  1478. else
  1479. MatrixRotate(mat, NullPointD, DegToRad(values[0]));
  1480. 'x' : //skewX
  1481. begin
  1482. MatrixSkew(mat, DegToRad(values[0]), 0);
  1483. end;
  1484. 'y' : //skewY
  1485. begin
  1486. MatrixSkew(mat, 0, DegToRad(values[0]));
  1487. end;
  1488. end;
  1489. MatrixMultiply2(mat, Result);
  1490. end;
  1491. end;
  1492. //------------------------------------------------------------------------------
  1493. procedure GetSvgFontInfo(const value: UTF8String; var fontInfo: TSVGFontInfo);
  1494. var
  1495. c, endC: PUTF8Char;
  1496. hash: Cardinal;
  1497. begin
  1498. c := PUTF8Char(value);
  1499. endC := c + Length(value);
  1500. while (c < endC) and SkipBlanks(c, endC) do
  1501. begin
  1502. if c = ';' then
  1503. break
  1504. else if IsNumPending(c, endC, true) then
  1505. ParseNextNum(c, endC, true, fontInfo.size)
  1506. else
  1507. begin
  1508. hash := ParseNextWordHashed(c, endC);
  1509. case hash of
  1510. hSans_045_Serif : fontInfo.family := tfSansSerif;
  1511. hSerif : fontInfo.family := tfSerif;
  1512. hMonospace : fontInfo.family := tfMonospace;
  1513. hBold : fontInfo.weight := 600;
  1514. hItalic : fontInfo.italic := sfsItalic;
  1515. hNormal :
  1516. begin
  1517. fontInfo.weight := 400;
  1518. fontInfo.italic := sfsNone;
  1519. end;
  1520. hStart : fontInfo.align := staLeft;
  1521. hMiddle : fontInfo.align := staCenter;
  1522. hEnd : fontInfo.align := staRight;
  1523. hline_045_through : fontInfo.decoration := fdStrikeThrough;
  1524. hUnderline : fontInfo.decoration := fdUnderline;
  1525. end;
  1526. end;
  1527. end;
  1528. end;
  1529. //------------------------------------------------------------------------------
  1530. function HtmlDecode(const html: UTF8String): UTF8String;
  1531. var
  1532. val, len: integer;
  1533. c,ce,endC: PUTF8Char;
  1534. ch: UTF8Char;
  1535. begin
  1536. len := Length(html);
  1537. SetLength(Result, len*3);
  1538. c := PUTF8Char(html);
  1539. endC := c + len;
  1540. ce := c;
  1541. len := 1;
  1542. while (ce < endC) and (ce^ <> '&') do
  1543. inc(ce);
  1544. while (ce < endC) do
  1545. begin
  1546. if ce > c then
  1547. begin
  1548. Move(c^, Result[len], ce - c);
  1549. inc(len, ce - c);
  1550. end;
  1551. c := ce; inc(ce);
  1552. while (ce < endC) and (ce^ <> ';') do inc(ce);
  1553. if ce = endC then break;
  1554. val := -1; //assume error
  1555. if (c +1)^ = '#' then
  1556. begin
  1557. val := 0;
  1558. //decode unicode value
  1559. if (c +2)^ = 'x' then
  1560. begin
  1561. inc(c, 3);
  1562. while c < ce do
  1563. begin
  1564. ch := c^;
  1565. case ch of
  1566. 'a'..'f':
  1567. val := val * 16 + Ord(ch) - 87;
  1568. 'A'..'F':
  1569. val := val * 16 + Ord(ch) - 55;
  1570. '0'..'9':
  1571. val := val * 16 + Ord(ch) - 48;
  1572. else
  1573. val := -1;
  1574. break;
  1575. end;
  1576. inc(c);
  1577. end;
  1578. end else
  1579. begin
  1580. inc(c, 2);
  1581. while c < ce do
  1582. begin
  1583. val := val * 10 + Ord(c^) - 48;
  1584. inc(c);
  1585. end;
  1586. end;
  1587. end else
  1588. begin
  1589. //decode html entity ...
  1590. case GetHashCaseSensitive(c, ce - c) of
  1591. {$I Img32.SVG.HtmlValues.inc}
  1592. end;
  1593. end;
  1594. //convert unicode value to utf8 chars
  1595. //this saves the overhead of multiple UTF8String<-->string conversions.
  1596. case val of
  1597. 0 .. $7F:
  1598. begin
  1599. result[len] := UTF8Char(val);
  1600. inc(len);
  1601. end;
  1602. $80 .. $7FF:
  1603. begin
  1604. Result[len] := UTF8Char($C0 or (val shr 6));
  1605. Result[len+1] := UTF8Char($80 or (val and $3f));
  1606. inc(len, 2);
  1607. end;
  1608. $800 .. $7FFF:
  1609. begin
  1610. Result[len] := UTF8Char($E0 or (val shr 12));
  1611. Result[len+1] := UTF8Char($80 or ((val shr 6) and $3f));
  1612. Result[len+2] := UTF8Char($80 or (val and $3f));
  1613. inc(len, 3);
  1614. end;
  1615. $10000 .. $10FFFF:
  1616. begin
  1617. Result[len] := UTF8Char($F0 or (val shr 18));
  1618. Result[len+1] := UTF8Char($80 or ((val shr 12) and $3f));
  1619. Result[len+2] := UTF8Char($80 or ((val shr 6) and $3f));
  1620. Result[len+3] := UTF8Char($80 or (val and $3f));
  1621. inc(len, 4);
  1622. end;
  1623. else
  1624. begin
  1625. //ie: error
  1626. Move(c^, Result[len], ce- c +1);
  1627. inc(len, ce - c +1);
  1628. end;
  1629. end;
  1630. inc(ce);
  1631. c := ce;
  1632. while (ce < endC) and (ce^ <> '&') do inc(ce);
  1633. end;
  1634. if (c < endC) and (ce > c) then
  1635. begin
  1636. Move(c^, Result[len], (ce - c));
  1637. inc(len, ce - c);
  1638. end;
  1639. setLength(Result, len -1);
  1640. end;
  1641. //------------------------------------------------------------------------------
  1642. function HexByteToInt(h: UTF8Char): Cardinal; {$IFDEF INLINE} inline; {$ENDIF}
  1643. begin
  1644. case h of
  1645. '0'..'9': Result := Ord(h) - Ord('0');
  1646. 'A'..'F': Result := 10 + Ord(h) - Ord('A');
  1647. 'a'..'f': Result := 10 + Ord(h) - Ord('a');
  1648. else Result := 0;
  1649. end;
  1650. end;
  1651. //------------------------------------------------------------------------------
  1652. function IsFraction(val: double): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
  1653. begin
  1654. Result := (val <> 0) and (Abs(val) < 1);
  1655. end;
  1656. //------------------------------------------------------------------------------
  1657. function UTF8StringToColor32(const value: UTF8String; var color: TColor32): Boolean;
  1658. var
  1659. i, len : integer;
  1660. j : Cardinal;
  1661. clr : TColor32;
  1662. alpha : Byte;
  1663. vals : array[0..3] of double;
  1664. mus : array[0..3] of TUnitType;
  1665. c, endC : PUTF8Char;
  1666. begin
  1667. Result := false;
  1668. len := Length(value);
  1669. if len < 3 then Exit;
  1670. c := PUTF8Char(value);
  1671. if (color = clInvalid) or (color = clCurrent) or (color = clNone32) then
  1672. alpha := 255 else
  1673. alpha := GetAlpha(color);
  1674. if Match(c, 'rgb') then
  1675. begin
  1676. endC := c + len;
  1677. inc(c, 3);
  1678. if (c^ = 'a') then inc(c);
  1679. if (ParseNextChar(c, endC) <> '(') or
  1680. not ParseNextNumEx(c, endC, false, vals[0], mus[0]) or
  1681. not ParseNextNumEx(c, endC, true, vals[1], mus[1]) or
  1682. not ParseNextNumEx(c, endC, true, vals[2], mus[2]) then Exit;
  1683. for i := 0 to 2 do
  1684. if mus[i] = utPercent then
  1685. vals[i] := vals[i] * 255 / 100;
  1686. if (c < endC) and (c^ <> ')') and ParseNextNumEx(c, endC, true, vals[3], mus[3]) then
  1687. alpha := 255 else //stops further alpha adjustment
  1688. vals[3] := 255;
  1689. if ParseNextChar(c, endC) <> ')' then Exit;
  1690. for i := 0 to 3 do if IsFraction(vals[i]) then
  1691. vals[i] := vals[i] * 255;
  1692. color := ClampByte(Integer(Round(vals[3]))) shl 24 +
  1693. ClampByte(Integer(Round(vals[0]))) shl 16 +
  1694. ClampByte(Integer(Round(vals[1]))) shl 8 +
  1695. ClampByte(Integer(Round(vals[2])));
  1696. end
  1697. else if (c^ = '#') then //#RRGGBB or #RGB
  1698. begin
  1699. if (len = 9) then
  1700. begin
  1701. clr := $0;
  1702. alpha := $0;
  1703. for i := 1 to 6 do
  1704. begin
  1705. inc(c);
  1706. clr := clr shl 4 + HexByteToInt(c^);
  1707. end;
  1708. for i := 1 to 2 do
  1709. begin
  1710. inc(c);
  1711. alpha := alpha shl 4 + HexByteToInt(c^);
  1712. end;
  1713. clr := clr or alpha shl 24;
  1714. end
  1715. else if (len = 7) then
  1716. begin
  1717. clr := $0;
  1718. for i := 1 to 6 do
  1719. begin
  1720. inc(c);
  1721. clr := clr shl 4 + HexByteToInt(c^);
  1722. end;
  1723. clr := clr or $FF000000;
  1724. end
  1725. else if (len = 5) then
  1726. begin
  1727. clr := $0;
  1728. for i := 1 to 3 do
  1729. begin
  1730. inc(c);
  1731. j := HexByteToInt(c^);
  1732. clr := clr shl 4 + j;
  1733. clr := clr shl 4 + j;
  1734. end;
  1735. inc(c);
  1736. alpha := HexByteToInt(c^);
  1737. alpha := alpha + alpha shl 4;
  1738. clr := clr or alpha shl 24;
  1739. end
  1740. else if (len = 4) then
  1741. begin
  1742. clr := $0;
  1743. for i := 1 to 3 do
  1744. begin
  1745. inc(c);
  1746. j := HexByteToInt(c^);
  1747. clr := clr shl 4 + j;
  1748. clr := clr shl 4 + j;
  1749. end;
  1750. clr := clr or $FF000000;
  1751. end
  1752. else
  1753. Exit;
  1754. color := clr;
  1755. end else //color name lookup
  1756. begin
  1757. if not ColorConstList.GetColorValue(value, color) then
  1758. Exit;
  1759. end;
  1760. //and in case the opacity has been set before the color
  1761. if (alpha < 255) then
  1762. color := (color and $FFFFFF) or alpha shl 24;
  1763. {$IF DEFINED(ANDROID)}
  1764. color := SwapRedBlue(color);
  1765. {$IFEND}
  1766. Result := true;
  1767. end;
  1768. //------------------------------------------------------------------------------
  1769. function ScaleDashArray(const dblArray: TArrayOfDouble; scale: double): TArrayOfDouble;
  1770. var
  1771. i, len: integer;
  1772. begin
  1773. len := Length(dblArray);
  1774. SetLength(Result, len);
  1775. if len = 0 then Exit;
  1776. for i := 0 to len -1 do
  1777. Result[i] := dblArray[i] * scale;
  1778. if Odd(len) then
  1779. begin
  1780. SetLength(Result, len *2);
  1781. Move(Result[0], Result[len], len * SizeOf(double));
  1782. end;
  1783. end;
  1784. //------------------------------------------------------------------------------
  1785. function PeekNextChar(var c: PUTF8Char; endC: PUTF8Char): UTF8Char;
  1786. begin
  1787. if not SkipBlanks(c, endC) then
  1788. Result := #0 else
  1789. Result := c^;
  1790. end;
  1791. //------------------------------------------------------------------------------
  1792. procedure ParseStyleElementContent(const value: UTF8String;
  1793. stylesList: TClassStylesList);
  1794. var
  1795. len, cap: integer;
  1796. names: array of UTF8String;
  1797. procedure AddName(const name: UTF8String);
  1798. begin
  1799. if len = cap then
  1800. begin
  1801. cap := cap + buffSize;
  1802. SetLength(names, cap);
  1803. end;
  1804. names[len] := name;
  1805. inc(len);
  1806. end;
  1807. var
  1808. i: integer;
  1809. aclassName: UTF8String;
  1810. aStyle: UTF8String;
  1811. c, c2, endC: PUTF8Char;
  1812. begin
  1813. //https://oreillymedia.github.io/Using_SVG/guide/style.html
  1814. stylesList.Clear;
  1815. if value = '' then Exit;
  1816. len := 0; cap := 0;
  1817. c := @value[1];
  1818. endC := c + Length(value);
  1819. c := SkipBlanksEx(c, endC);
  1820. if c >= endC then Exit;
  1821. if Match(c, '<![cdata[') then inc(c, 9);
  1822. while True do
  1823. begin
  1824. c := SkipStyleBlanks(c, endC);
  1825. if c >= endC then Break;
  1826. case c^ of
  1827. SvgDecimalSeparator, '#', 'A'..'Z', 'a'..'z': ;
  1828. else break;
  1829. end;
  1830. //get one or more class names for each pending style
  1831. c2 := c;
  1832. c := ParseNameLength(c, endC);
  1833. ToAsciiLowerUTF8String(c2, c, aclassName);
  1834. AddName(aclassName);
  1835. c := SkipStyleBlanks(c, endC);
  1836. if (c < endC) and (c^ = ',') then
  1837. begin
  1838. inc(c);
  1839. Continue;
  1840. end;
  1841. if len = 0 then break;
  1842. //now get the style
  1843. if (c >= endC) or (c^ <> '{') then Break;
  1844. inc(c);
  1845. c2 := c;
  1846. while (c < endC) and (c^ <> '}') do inc(c);
  1847. if (c = endC) then break;
  1848. ToTrimmedUTF8String(c2, c, aStyle);
  1849. if aStyle <> '' then
  1850. begin
  1851. stylesList.Preallocate(len);
  1852. //finally, for each class name add (or append) this style
  1853. for i := 0 to len - 1 do
  1854. stylesList.AddAppendStyle(names[i], aStyle);
  1855. end;
  1856. // Reset the used names array length, so we can reuse it to reduce the amount of SetLength calls
  1857. len := 0;
  1858. inc(c);
  1859. end;
  1860. end;
  1861. //------------------------------------------------------------------------------
  1862. // TXmlEl classes
  1863. //------------------------------------------------------------------------------
  1864. constructor TXmlEl.Create(owner: TSvgParser);
  1865. begin
  1866. {$IFDEF XPLAT_GENERICS}
  1867. attribs := TList<PSvgAttrib>.Create;
  1868. childs := TList<TXmlEl>.Create;
  1869. {$ELSE}
  1870. attribs := TList.Create;
  1871. childs := TList.Create;
  1872. {$ENDIF}
  1873. selfClosed := true;
  1874. Self.owner := owner;
  1875. end;
  1876. //------------------------------------------------------------------------------
  1877. destructor TXmlEl.Destroy;
  1878. begin
  1879. Clear;
  1880. attribs.Free;
  1881. childs.Free;
  1882. inherited;
  1883. end;
  1884. //------------------------------------------------------------------------------
  1885. procedure TXmlEl.Clear;
  1886. var
  1887. i: integer;
  1888. begin
  1889. for i := 0 to attribs.Count -1 do
  1890. DisposeSvgAttrib(PSvgAttrib(attribs.List[i]));
  1891. attribs.Clear;
  1892. for i := 0 to childs.Count -1 do
  1893. TXmlEl(childs[i]).free;
  1894. childs.Clear;
  1895. end;
  1896. //------------------------------------------------------------------------------
  1897. function TXmlEl.ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  1898. var
  1899. style: UTF8String;
  1900. c2: PUTF8Char;
  1901. begin
  1902. c2 := SkipBlanksEx(c, endC);
  1903. c := ParseNameLength(c2, endC);
  1904. ToAsciiLowerUTF8String(c2, c, name);
  1905. //load the class's style (ie undotted style) if found.
  1906. style := owner.classStyles.GetStyle(name);
  1907. if style <> '' then ParseStyleAttribute(style);
  1908. Result := ParseAttributes(c, endC);
  1909. end;
  1910. //------------------------------------------------------------------------------
  1911. class function TXmlEl.ParseAttribName(c: PUTF8Char;
  1912. endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char;
  1913. begin
  1914. Result := SkipBlanksEx(c, endC);
  1915. if Result >= endC then Exit;
  1916. c := Result;
  1917. Result := ParseNameLength(Result, endC);
  1918. ToUTF8String(c, Result, attrib.Name);
  1919. attrib.hash := GetHash(attrib.Name);
  1920. end;
  1921. //------------------------------------------------------------------------------
  1922. class function TXmlEl.ParseAttribValue(c, endC: PUTF8Char;
  1923. attrib: PSvgAttrib): PUTF8Char;
  1924. // Parse: [Whitespaces] "=" [Whitespaces] ("'" | "\"") <string> ("'" | "\"")
  1925. var
  1926. quoteChar: UTF8Char;
  1927. c2: PUTF8Char;
  1928. begin
  1929. Result := endC;
  1930. // ParseNextChar:
  1931. c := SkipBlanksEx(c, endC);
  1932. if (c >= endC) or (c^ <> '=') then Exit;
  1933. inc(c); // '=' parsed
  1934. // ParseQuoteChar:
  1935. c := SkipBlanksEx(c, endC);
  1936. if c >= endC then Exit;
  1937. quoteChar := c^;
  1938. if not (quoteChar in [quote, dquote]) then Exit;
  1939. inc(c); // quote parsed
  1940. //trim leading and trailing spaces in the actual value
  1941. c := SkipBlanksEx(c, endC);
  1942. // find value end
  1943. Result := c;
  1944. while (Result < endC) and (Result^ <> quoteChar) do inc(Result);
  1945. c2 := Result;
  1946. while (c2 > c) and ((c2 -1)^ <= space) do dec(c2);
  1947. ToUTF8String(c, c2, attrib.value, sitPreserve);
  1948. inc(Result); //skip end quote
  1949. end;
  1950. //------------------------------------------------------------------------------
  1951. class function TXmlEl.ParseAttribNameAndValue(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char;
  1952. begin
  1953. Result := ParseAttribName(c, endC, attrib);
  1954. if (Result < endC) then
  1955. Result := ParseAttribValue(Result, endC, attrib);
  1956. end;
  1957. //------------------------------------------------------------------------------
  1958. function TXmlEl.ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  1959. var
  1960. i: integer;
  1961. attrib, styleAttrib, classAttrib, idAttrib: PSvgAttrib;
  1962. classes: UTF8Strings;
  1963. ansi: UTF8String;
  1964. begin
  1965. Result := false;
  1966. styleAttrib := nil; classAttrib := nil; idAttrib := nil;
  1967. while SkipBlanks(c, endC) do
  1968. begin
  1969. case c^ of
  1970. '/', '?':
  1971. begin
  1972. inc(c);
  1973. if (c^ <> '>') then Exit; //error
  1974. selfClosed := true;
  1975. inc(c);
  1976. Result := true;
  1977. break;
  1978. end;
  1979. '>':
  1980. begin
  1981. inc(c);
  1982. Result := true;
  1983. break;
  1984. end;
  1985. 'x':
  1986. if Match(c, 'xml:') then
  1987. begin
  1988. inc(c, 4); //ignore xml: prefixes
  1989. end;
  1990. end;
  1991. attrib := NewSvgAttrib();
  1992. c := ParseAttribNameAndValue(c, endC, attrib);
  1993. if c >= endC then
  1994. begin
  1995. DisposeSvgAttrib(attrib);
  1996. Exit;
  1997. end;
  1998. attribs.Add(attrib);
  1999. case attrib.hash of
  2000. hId : idAttrib := attrib;
  2001. hClass : classAttrib := attrib;
  2002. hStyle : styleAttrib := attrib;
  2003. end;
  2004. end;
  2005. if assigned(classAttrib) then
  2006. with classAttrib^ do
  2007. begin
  2008. //get the 'dotted' classname(s)
  2009. classes := Split(value);
  2010. for i := 0 to High(classes) do
  2011. begin
  2012. ansi := SvgDecimalSeparator + classes[i];
  2013. //get the style definition
  2014. ansi := owner.classStyles.GetStyle(ansi);
  2015. if ansi <> '' then ParseStyleAttribute(ansi);
  2016. end;
  2017. end;
  2018. if assigned(styleAttrib) then
  2019. ParseStyleAttribute(styleAttrib.value);
  2020. if assigned(idAttrib) then
  2021. begin
  2022. //get the 'hashed' classname
  2023. ansi := '#' + idAttrib.value;
  2024. //get the style definition
  2025. ansi := owner.classStyles.GetStyle(ansi);
  2026. if ansi <> '' then ParseStyleAttribute(ansi);
  2027. end;
  2028. end;
  2029. //------------------------------------------------------------------------------
  2030. procedure TXmlEl.ParseStyleAttribute(const style: UTF8String);
  2031. var
  2032. styleName, styleVal: UTF8String;
  2033. c, c2, endC: PUTF8Char;
  2034. attrib: PSvgAttrib;
  2035. begin
  2036. //there are 4 ways to load styles (in ascending precedence) -
  2037. //1. a class element style (called during element contruction)
  2038. //2. a non-element class style (called via a class attribute)
  2039. //3. an inline style (called via a style attribute)
  2040. //4. an id specific class style
  2041. c := PUTF8Char(style);
  2042. endC := c + Length(style);
  2043. while True do
  2044. begin
  2045. c := SkipStyleBlanks(c, endC);
  2046. if c >= endC then Break;
  2047. c2 := c;
  2048. c := ParseStyleNameLen(c, endC);
  2049. ToUTF8String(c2, c, styleName);
  2050. if styleName = '' then Break;
  2051. // ParseNextChar
  2052. c := SkipStyleBlanks(c, endC);
  2053. if (c >= endC) or (c^ <> ':') then Break; //syntax check
  2054. inc(c);
  2055. c := SkipBlanksEx(c, endC);
  2056. if c >= endC then Break;
  2057. c2 := c;
  2058. inc(c);
  2059. while (c < endC) and (c^ <> ';') do inc(c);
  2060. ToTrimmedUTF8String(c2, c, styleVal);
  2061. inc(c);
  2062. attrib := NewSvgAttrib();
  2063. attrib.name := styleName;
  2064. attrib.value := styleVal;
  2065. attrib.hash := GetHash(attrib.name);
  2066. attribs.Add(attrib);
  2067. end;
  2068. end;
  2069. //------------------------------------------------------------------------------
  2070. function TXmlEl.GetAttribCount: integer;
  2071. begin
  2072. Result := attribs.Count;
  2073. end;
  2074. //------------------------------------------------------------------------------
  2075. function TXmlEl.GetAttrib(index: integer): PSvgAttrib;
  2076. begin
  2077. Result := PSvgAttrib(attribs[index]);
  2078. end;
  2079. //------------------------------------------------------------------------------
  2080. function IsTextAreaTbreak(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  2081. const
  2082. // https://www.w3.org/TR/SVGTiny12/text.html#tbreakElement
  2083. tbreak: PUTF8Char = '<tbreak/>';
  2084. begin
  2085. Result := (c + 9 < endC) and CompareMem(c, tbreak, 9);
  2086. if Result then inc(c, 8);
  2087. end;
  2088. //------------------------------------------------------------------------------
  2089. function TXmlEl.ParseContent(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  2090. var
  2091. child : TSvgXmlEl;
  2092. entity : PSvgAttrib;
  2093. c2, cc : PUTF8Char;
  2094. tmpC, tmpEndC : PUTF8Char;
  2095. begin
  2096. Result := false;
  2097. // note: don't trim spaces at the start of text content.
  2098. // Text space trimming will be done later IF and when required.
  2099. while (hash = hText) or (hash = hTSpan) or
  2100. (hash = hTextArea) or SkipBlanks(c, endC) do
  2101. begin
  2102. if (c^ = '<') then
  2103. begin
  2104. inc(c);
  2105. case c^ of
  2106. '!':
  2107. begin
  2108. cc := c;
  2109. if Match(cc, '!--') then //start comment
  2110. begin
  2111. inc(cc, 3);
  2112. while (cc < endC) and ((cc^ <> '-') or
  2113. not Match(cc, '-->')) do inc(cc); //end comment
  2114. inc(cc, 3);
  2115. end else
  2116. begin
  2117. //it's very likely <![CDATA[
  2118. c2 := cc - 1;
  2119. if Match(cc, '![cdata[') then
  2120. begin
  2121. while (cc < endC) and ((cc^ <> ']') or not Match(cc, ']]>')) do
  2122. inc(cc);
  2123. ToUTF8String(c2, cc, text);
  2124. inc(cc, 3);
  2125. if (hash = hStyle) then
  2126. ParseStyleElementContent(text, owner.classStyles);
  2127. end else
  2128. begin
  2129. while (cc < endC) and (cc^ <> '<') do inc(cc);
  2130. ToUTF8String(c2, cc, text);
  2131. end;
  2132. end;
  2133. c := cc;
  2134. end;
  2135. '/', '?':
  2136. begin
  2137. //element closing tag
  2138. cc := c;
  2139. inc(cc);
  2140. if Match(cc, name) then
  2141. begin
  2142. inc(cc, Length(name));
  2143. //very rarely there's a space before '>'
  2144. cc := SkipBlanksEx(cc, endC);
  2145. Result := cc^ = '>';
  2146. inc(cc);
  2147. end;
  2148. c := cc;
  2149. Exit;
  2150. end;
  2151. else
  2152. begin
  2153. //starting a new element
  2154. child := TSvgXmlEl.Create(owner);
  2155. childs.Add(child);
  2156. if not child.ParseHeader(c, endC) then break;
  2157. if not child.selfClosed then
  2158. child.ParseContent(c, endC);
  2159. end;
  2160. end;
  2161. end
  2162. else if c^ = '>' then
  2163. begin
  2164. break; //oops! something's wrong
  2165. end
  2166. else if (c^ = '&') and IsKnownEntity(owner, c, endC, entity) then
  2167. begin
  2168. tmpC := PUTF8Char(entity.value);
  2169. tmpEndC := tmpC + Length(entity.value);
  2170. ParseContent(tmpC, tmpEndC);
  2171. end
  2172. else if (hash = hTSpan) or (hash = hText) or (hash = hTextPath) then
  2173. begin
  2174. // assume this is text content, and because text can also be mixed
  2175. // with any number of nested <tspan> elements, always put text
  2176. // content inside a pseudo 'self closed' <tspan> element
  2177. cc := c;
  2178. while (cc < endC) and (cc^ <> '<') do inc(cc);
  2179. child := TSvgXmlEl.Create(owner);
  2180. child.name := 'tspan';
  2181. child.hash := GetHash('tspan');
  2182. child.selfClosed := true; ////////////////////// :)))
  2183. childs.Add(child);
  2184. ToUTF8String(c, cc, child.text, sitPreserve);
  2185. c := cc;
  2186. end
  2187. else if (hash = hTextArea) then
  2188. begin
  2189. // also assume this is text content, but don't create
  2190. // pseudo <tspan> elements inside <textarea> elements
  2191. cc := c;
  2192. while (cc < endC) and
  2193. ((cc^ <> '<') or IsTextAreaTbreak(cc, endC)) do inc(cc);
  2194. ToUTF8String(c, cc, text, sitPreserve);
  2195. c := cc;
  2196. end else
  2197. begin
  2198. cc := c;
  2199. while (cc < endC) and (cc^ <> '<') do inc(cc);
  2200. ToUTF8String(c, cc, text);
  2201. c := cc;
  2202. //if <style> element then load styles into owner.classStyles
  2203. if (hash = hStyle) then
  2204. ParseStyleElementContent(text, owner.classStyles);
  2205. end;
  2206. end;
  2207. end;
  2208. //------------------------------------------------------------------------------
  2209. // TDocTypeEl
  2210. //------------------------------------------------------------------------------
  2211. function TDocTypeEl.SkipWord(c, endC: PUTF8Char): PUTF8Char;
  2212. begin
  2213. while (c < endC) and (c^ > space) do inc(c);
  2214. inc(c);
  2215. Result := c;
  2216. end;
  2217. //------------------------------------------------------------------------------
  2218. function TDocTypeEl.ParseEntities(var c, endC: PUTF8Char): Boolean;
  2219. var
  2220. attrib: PSvgAttrib;
  2221. begin
  2222. attrib := nil;
  2223. inc(c); //skip opening '['
  2224. while (c < endC) and SkipBlanks(c, endC) do
  2225. begin
  2226. if (c^ = ']') then break
  2227. else if not Match(c, '<!entity') then
  2228. begin
  2229. while c^ > space do inc(c); //skip word.
  2230. Continue;
  2231. end;
  2232. inc(c, 8);
  2233. attrib := NewSvgAttrib();
  2234. c := ParseAttribName(c, endC, attrib);
  2235. if c >= endC then break;
  2236. SkipBlanks(c, endC);
  2237. if not IsQuoteChar(c^) then break;
  2238. if not ParseQuotedString(c, endC, attrib.value) then break;
  2239. attribs.Add(attrib);
  2240. attrib := nil;
  2241. SkipBlanks(c, endC);
  2242. if c^ <> '>' then break;
  2243. inc(c); //skip entity's trailing '>'
  2244. end;
  2245. if Assigned(attrib) then DisposeSvgAttrib(attrib);
  2246. Result := (c < endC) and (c^ = ']');
  2247. inc(c);
  2248. end;
  2249. //------------------------------------------------------------------------------
  2250. function TDocTypeEl.ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  2251. var
  2252. dummy : UTF8String;
  2253. begin
  2254. while SkipBlanks(c, endC) do
  2255. begin
  2256. //we're currently only interested in ENTITY declarations
  2257. case c^ of
  2258. '[': ParseEntities(c, endC);
  2259. '"', '''': ParseQuotedString(c, endC, dummy);
  2260. '>': break;
  2261. else c := SkipWord(c, endC);
  2262. end;
  2263. end;
  2264. Result := (c < endC) and (c^ = '>');
  2265. inc(c);
  2266. end;
  2267. //------------------------------------------------------------------------------
  2268. // TSvgTreeEl
  2269. //------------------------------------------------------------------------------
  2270. constructor TSvgXmlEl.Create(owner: TSvgParser);
  2271. begin
  2272. inherited Create(owner);
  2273. selfClosed := false;
  2274. end;
  2275. //------------------------------------------------------------------------------
  2276. procedure TSvgXmlEl.Clear;
  2277. var
  2278. i: integer;
  2279. begin
  2280. for i := 0 to childs.Count -1 do
  2281. TSvgXmlEl(childs[i]).free;
  2282. childs.Clear;
  2283. inherited;
  2284. end;
  2285. //------------------------------------------------------------------------------
  2286. function TSvgXmlEl.ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  2287. begin
  2288. Result := inherited ParseHeader(c, endC);
  2289. if Result then hash := GetHash(name);
  2290. end;
  2291. //------------------------------------------------------------------------------
  2292. //function TSvgTreeEl.ParseContent(var c: PUTF8Char; endC: PUTF8Char): Boolean;
  2293. constructor TSvgParser.Create;
  2294. begin
  2295. classStyles := TClassStylesList.Create;
  2296. svgStream := TMemoryStream.Create;
  2297. xmlHeader := TXmlEl.Create(Self);
  2298. docType := TDocTypeEl.Create(Self);
  2299. svgTree := nil;
  2300. end;
  2301. //------------------------------------------------------------------------------
  2302. destructor TSvgParser.Destroy;
  2303. begin
  2304. Clear;
  2305. svgStream.Free;
  2306. xmlHeader.Free;
  2307. docType.Free;
  2308. classStyles.Free;
  2309. end;
  2310. //------------------------------------------------------------------------------
  2311. procedure TSvgParser.Clear;
  2312. begin
  2313. classStyles.Clear;
  2314. svgStream.Clear;
  2315. xmlHeader.Clear;
  2316. docType.Clear;
  2317. FreeAndNil(svgTree);
  2318. end;
  2319. //------------------------------------------------------------------------------
  2320. function TSvgParser.FindEntity(hash: Cardinal): PSvgAttrib;
  2321. var
  2322. i: integer;
  2323. begin
  2324. //there are usually so few, that there seems little point sorting etc.
  2325. for i := 0 to docType.attribs.Count -1 do
  2326. begin
  2327. Result := PSvgAttrib(docType.attribs.List[i]);
  2328. if Result.hash = hash then Exit;
  2329. end;
  2330. Result := nil;
  2331. end;
  2332. //------------------------------------------------------------------------------
  2333. function TSvgParser.LoadFromFile(const filename: string): Boolean;
  2334. var
  2335. fs: TFileStream;
  2336. begin
  2337. Result := false;
  2338. if not FileExists(filename) then Exit;
  2339. fs := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
  2340. try
  2341. Result := LoadFromStream(fs);
  2342. finally
  2343. fs.Free;
  2344. end;
  2345. end;
  2346. //------------------------------------------------------------------------------
  2347. procedure ConvertUnicodeToUtf8(memStream: TMemoryStream);
  2348. var
  2349. i, len: LongInt;
  2350. encoding: TSvgEncoding;
  2351. s: UnicodeString;
  2352. wc: PWord;
  2353. utf8: UTF8String;
  2354. begin
  2355. memStream.Position := 0;
  2356. encoding := GetXmlEncoding(memStream.Memory, memStream.Size);
  2357. case encoding of
  2358. eUnicodeLE, eUnicodeBE: ;
  2359. else Exit;
  2360. end;
  2361. SetLength(s, memStream.Size div 2);
  2362. Move(memStream.Memory^, s[1], memStream.Size);
  2363. if encoding = eUnicodeBE then
  2364. begin
  2365. wc := @s[1];
  2366. for i := 1 to Length(s) do
  2367. begin
  2368. wc^ := Swap(wc^);
  2369. inc(wc);
  2370. end;
  2371. end;
  2372. utf8 := UTF8Encode(s);
  2373. len := Length(utf8);
  2374. memStream.SetSize(len);
  2375. Move(utf8[1], memStream.Memory^, len);
  2376. end;
  2377. //------------------------------------------------------------------------------
  2378. function TSvgParser.LoadFromStream(stream: TStream): Boolean;
  2379. begin
  2380. Clear;
  2381. Result := true;
  2382. try
  2383. svgStream.LoadFromStream(stream);
  2384. // very few SVG files are unicode encoded, almost all are Utf8
  2385. // so it's more efficient to parse them all as Utf8 encoded files
  2386. ConvertUnicodeToUtf8(svgStream);
  2387. ParseUtf8Stream;
  2388. except
  2389. Result := false;
  2390. end;
  2391. end;
  2392. //------------------------------------------------------------------------------
  2393. function TSvgParser.LoadFromString(const str: string): Boolean;
  2394. var
  2395. ss: TStringStream;
  2396. begin
  2397. {$IFDEF UNICODE}
  2398. ss := TStringStream.Create(str, TEncoding.UTF8);
  2399. {$ELSE}
  2400. ss := TStringStream.Create(UTF8Encode(str));
  2401. {$ENDIF}
  2402. try
  2403. Result := LoadFromStream(ss);
  2404. finally
  2405. ss.Free;
  2406. end;
  2407. end;
  2408. //------------------------------------------------------------------------------
  2409. procedure TSvgParser.ParseUtf8Stream;
  2410. var
  2411. c, endC: PUTF8Char;
  2412. begin
  2413. c := svgStream.Memory;
  2414. endC := c + svgStream.Size;
  2415. SkipBlanks(c, endC);
  2416. if Match(c, '<?xml') then
  2417. begin
  2418. inc(c, 2); //todo: accommodate space after '<' eg using sMatchEl function
  2419. if not xmlHeader.ParseHeader(c, endC) then Exit;
  2420. SkipBlanks(c, endC);
  2421. end;
  2422. if Match(c, '<!doctype') then
  2423. begin
  2424. inc(c, 2);
  2425. if not docType.ParseHeader(c, endC) then Exit;
  2426. end;
  2427. while SkipBlanks(c, endC) do
  2428. begin
  2429. if (c^ = '<') and Match(c, '<svg') then
  2430. begin
  2431. inc(c);
  2432. svgTree := TSvgXmlEl.Create(self);
  2433. if svgTree.ParseHeader(c, endC) and
  2434. not svgTree.selfClosed then
  2435. svgTree.ParseContent(c, endC);
  2436. break;
  2437. end;
  2438. inc(c);
  2439. end;
  2440. end;
  2441. //------------------------------------------------------------------------------
  2442. // Miscellaneous functions
  2443. //------------------------------------------------------------------------------
  2444. function DecodeUtf8ToUnicode(const utf8: UTF8String): UnicodeString;
  2445. var
  2446. i,j, len: Integer;
  2447. c, cp: Cardinal;
  2448. codePoints: TArrayOfCardinal;
  2449. begin
  2450. Result := '';
  2451. if utf8 = '' then Exit;
  2452. len := Length(utf8);
  2453. // first decode utf8String to codepoints
  2454. SetLength(codePoints, len);
  2455. i := 1;
  2456. j := 0;
  2457. while i <= len do
  2458. begin
  2459. c := Ord(utf8[i]);
  2460. if c and $80 = 0 then // c < 128
  2461. begin
  2462. codePoints[j] := c;
  2463. inc(i); inc(j);
  2464. end
  2465. else if c and $E0 = $C0 then
  2466. begin
  2467. if i = len then break;
  2468. codePoints[j] := (c and $1F) shl 6 + (Ord(utf8[i+1]) and $3F);
  2469. inc(i, 2); inc(j);
  2470. end
  2471. else if c and $F0 = $E0 then
  2472. begin
  2473. if i > len - 2 then break;
  2474. codePoints[j] := (c and $F) shl 12 +
  2475. ((Ord(utf8[i+1]) and $3F) shl 6) + ((Ord(utf8[i+2]) and $3F));
  2476. inc(i, 3); inc(j);
  2477. end else
  2478. begin
  2479. if (i > len - 3) or (c shr 3 <> $1E) then break;
  2480. codePoints[j] := (c and $7) shl 18 + ((Ord(utf8[i+1]) and $3F) shl 12) +
  2481. ((Ord(utf8[i+2]) and $3F) shl 6) + (Ord(utf8[i+3]) and $3F);
  2482. inc(i, 4); inc(j);
  2483. end;
  2484. end;
  2485. len := j; // there are now 'j' valid codepoints
  2486. j := 0;
  2487. // make room in the result for surrogate paired chars, and
  2488. // convert codepoints into the result (a Utf16 string)
  2489. SetLength(Result, len *2);
  2490. for i := 0 to len -1 do
  2491. begin
  2492. inc(j);
  2493. cp := codePoints[i];
  2494. if (cp < $D7FF) or (cp = $E000) or (cp = $FFFF) then
  2495. begin
  2496. Result[j] := WideChar(cp);
  2497. end else if (cp > $FFFF) and (cp < $110000) then
  2498. begin
  2499. Dec(cp, $10000);
  2500. Result[j] := WideChar($D800 + (cp shr 10));
  2501. inc(j);
  2502. Result[j] := WideChar($DC00 + (cp and $3FF));
  2503. end;
  2504. end;
  2505. SetLength(Result, j);
  2506. end;
  2507. //------------------------------------------------------------------------------
  2508. // TValue
  2509. //------------------------------------------------------------------------------
  2510. function ConvertValue(const value: TValue; scale: double): double;
  2511. const
  2512. mm = 96 / 25.4;
  2513. cm = 96 / 2.54;
  2514. rad = 180 / PI;
  2515. pt = 4 / 3;
  2516. begin
  2517. //https://oreillymedia.github.io/Using_SVG/guide/units.html
  2518. //todo: still lots of units to support (eg times for animation)
  2519. with value do
  2520. {if not IsValid or (rawVal = 0) then // already checked by TValue.GetValue, the only function calling this code
  2521. Result := 0
  2522. else}
  2523. case value.unitType of
  2524. utNumber:
  2525. Result := rawVal;
  2526. utPercent:
  2527. Result := rawVal * 0.01 * scale;
  2528. utRadian:
  2529. Result := rawVal * rad;
  2530. utInch:
  2531. Result := rawVal * 96;
  2532. utCm:
  2533. Result := rawVal * cm;
  2534. utMm:
  2535. Result := rawVal * mm;
  2536. utEm:
  2537. if scale <= 0 then
  2538. Result := rawVal * 16 else
  2539. Result := rawVal * scale;
  2540. utEx:
  2541. if scale <= 0 then
  2542. Result := rawVal * 8 else
  2543. Result := rawVal * scale * 0.5;
  2544. utPica:
  2545. Result := rawVal * 16;
  2546. utPt:
  2547. Result := rawVal * pt;
  2548. else
  2549. Result := rawVal;
  2550. end;
  2551. end;
  2552. //------------------------------------------------------------------------------
  2553. procedure TValue.Init;
  2554. begin
  2555. rawVal := InvalidD;
  2556. unitType := utNumber;
  2557. end;
  2558. //------------------------------------------------------------------------------
  2559. procedure TValue.SetValue(val: double; unitTyp: TUnitType);
  2560. begin
  2561. rawVal := val;
  2562. unitType := unitTyp;
  2563. end;
  2564. //------------------------------------------------------------------------------
  2565. function TValue.GetValue(relSize: double; assumeRelValBelow: Double): double;
  2566. begin
  2567. if not IsValid or (rawVal = 0) then
  2568. Result := 0
  2569. else if IsRelativeValue(assumeRelValBelow) then
  2570. Result := rawVal * relSize
  2571. else
  2572. Result := ConvertValue(self, relSize);
  2573. end;
  2574. //------------------------------------------------------------------------------
  2575. function TValue.GetValueXY(const relSize: TRectD; assumeRelValBelow: Double): double;
  2576. begin
  2577. //https://www.w3.org/TR/SVG11/coords.html#Units
  2578. Result := GetValue(Hypot(relSize.Width, relSize.Height)/sqrt2, assumeRelValBelow);
  2579. end;
  2580. //------------------------------------------------------------------------------
  2581. function TValue.IsRelativeValue(assumeRelValBelow: double): Boolean;
  2582. begin
  2583. Result := (unitType = utNumber) and (Abs(rawVal) <= assumeRelValBelow);
  2584. end;
  2585. //------------------------------------------------------------------------------
  2586. function TValue.IsValid: Boolean;
  2587. begin
  2588. Result := (unitType <> utUnknown) and Img32.Vector.IsValid(rawVal);
  2589. end;
  2590. //------------------------------------------------------------------------------
  2591. function TValue.HasFontUnits: Boolean;
  2592. begin
  2593. case unitType of
  2594. utEm, utEx: Result := true;
  2595. else Result := False;
  2596. end;
  2597. end;
  2598. //------------------------------------------------------------------------------
  2599. function TValue.HasAngleUnits: Boolean;
  2600. begin
  2601. case unitType of
  2602. utDegree, utRadian: Result := true;
  2603. else Result := False;
  2604. end;
  2605. end;
  2606. //------------------------------------------------------------------------------
  2607. // TValuePt
  2608. //------------------------------------------------------------------------------
  2609. procedure TValuePt.Init;
  2610. begin
  2611. X.Init;
  2612. Y.Init;
  2613. end;
  2614. //------------------------------------------------------------------------------
  2615. function TValuePt.GetPoint(const relSize: double; assumeRelValBelow: Double): TPointD;
  2616. begin
  2617. Result.X := X.GetValue(relSize, assumeRelValBelow);
  2618. Result.Y := Y.GetValue(relSize, assumeRelValBelow);
  2619. end;
  2620. //------------------------------------------------------------------------------
  2621. function TValuePt.GetPoint(const relSize: TRectD; assumeRelValBelow: Double): TPointD;
  2622. begin
  2623. Result.X := X.GetValue(relSize.Width, assumeRelValBelow);
  2624. Result.Y := Y.GetValue(relSize.Height, assumeRelValBelow);
  2625. end;
  2626. //------------------------------------------------------------------------------
  2627. function TValuePt.IsValid: Boolean;
  2628. begin
  2629. Result := X.IsValid and Y.IsValid;
  2630. end;
  2631. //------------------------------------------------------------------------------
  2632. // TValueRec
  2633. //------------------------------------------------------------------------------
  2634. procedure TValueRecWH.Init;
  2635. begin
  2636. left.Init;
  2637. top.Init;
  2638. width.Init;
  2639. height.Init;
  2640. end;
  2641. //------------------------------------------------------------------------------
  2642. function TValueRecWH.GetRectD(const relSize: TRectD; assumeRelValBelow: Double): TRectD;
  2643. begin
  2644. with GetRectWH(relSize, assumeRelValBelow) do
  2645. begin
  2646. Result.Left :=Left;
  2647. Result.Top := Top;
  2648. Result.Right := Left + Width;
  2649. Result.Bottom := Top + Height;
  2650. end;
  2651. end;
  2652. //------------------------------------------------------------------------------
  2653. function TValueRecWH.GetRectD(relSize: double; assumeRelValBelow: Double): TRectD;
  2654. begin
  2655. if not left.IsValid then
  2656. Result.Left := 0 else
  2657. Result.Left := left.GetValue(relSize, assumeRelValBelow);
  2658. if not top.IsValid then
  2659. Result.Top := 0 else
  2660. Result.Top := top.GetValue(relSize, assumeRelValBelow);
  2661. Result.Right := Result.Left + width.GetValue(relSize, assumeRelValBelow);
  2662. Result.Bottom := Result.Top + height.GetValue(relSize, assumeRelValBelow);
  2663. end;
  2664. //------------------------------------------------------------------------------
  2665. function TValueRecWH.GetRectD(relSizeX, relSizeY: double; assumeRelValBelow: Double): TRectD;
  2666. begin
  2667. if not left.IsValid then
  2668. Result.Left := 0 else
  2669. Result.Left := left.GetValue(relSizeX, assumeRelValBelow);
  2670. if not top.IsValid then
  2671. Result.Top := 0 else
  2672. Result.Top := top.GetValue(relSizeY, assumeRelValBelow);
  2673. Result.Right := Result.Left + width.GetValue(relSizeX, assumeRelValBelow);
  2674. Result.Bottom := Result.Top + height.GetValue(relSizeY, assumeRelValBelow);
  2675. end;
  2676. //------------------------------------------------------------------------------
  2677. function TValueRecWH.GetRectWH(const relSize: TRectD; assumeRelValBelow: Double): TRectWH;
  2678. begin
  2679. if not left.IsValid then
  2680. Result.Left := 0 else
  2681. Result.Left := left.GetValue(relSize.Width, assumeRelValBelow);
  2682. if not top.IsValid then
  2683. Result.Top := 0 else
  2684. Result.Top := top.GetValue(relSize.Height, assumeRelValBelow);
  2685. Result.Width := width.GetValue(relSize.Width, assumeRelValBelow);
  2686. Result.Height := height.GetValue(relSize.Height, assumeRelValBelow);
  2687. end;
  2688. //------------------------------------------------------------------------------
  2689. function TValueRecWH.IsValid: Boolean;
  2690. begin
  2691. Result := width.IsValid and height.IsValid;
  2692. end;
  2693. //------------------------------------------------------------------------------
  2694. function TValueRecWH.IsEmpty: Boolean;
  2695. begin
  2696. Result := (width.rawVal <= 0) or (height.rawVal <= 0);
  2697. end;
  2698. //------------------------------------------------------------------------------
  2699. // TClassStylesList
  2700. //------------------------------------------------------------------------------
  2701. procedure TClassStylesList.Grow(NewCapacity: Integer);
  2702. var
  2703. Len, I: Integer;
  2704. Index: Integer;
  2705. begin
  2706. Len := Length(FItems);
  2707. if NewCapacity < 0 then
  2708. begin
  2709. if Len < 5 then
  2710. Len := 5
  2711. else
  2712. Len := Len * 2;
  2713. end
  2714. else if NewCapacity <= Len then
  2715. Exit
  2716. else
  2717. Len := NewCapacity;
  2718. SetLength(FItems, Len);
  2719. FMod := Cardinal(Len);
  2720. if not Odd(FMod) then
  2721. Inc(FMod);
  2722. SetLengthUninit(FBuckets, FMod);
  2723. FillChar(FBuckets[0], FMod * SizeOf(FBuckets[0]), $FF);
  2724. // Rehash
  2725. for I := 0 to FCount - 1 do
  2726. begin
  2727. Index := (FItems[I].Hash and $7FFFFFFF) mod FMod;
  2728. FItems[I].Next := FBuckets[Index];
  2729. FBuckets[Index] := I;
  2730. end;
  2731. end;
  2732. //------------------------------------------------------------------------------
  2733. procedure TClassStylesList.Preallocate(AdditionalItemCount: Integer);
  2734. begin
  2735. if AdditionalItemCount > 2 then
  2736. Grow(Length(FItems) + AdditionalItemCount);
  2737. end;
  2738. //------------------------------------------------------------------------------
  2739. function TClassStylesList.FindItemIndex(const Name: UTF8String): Integer;
  2740. begin
  2741. Result := -1;
  2742. FNameHash := GetHash(Name);
  2743. if FMod <> 0 then
  2744. begin
  2745. Result := FBuckets[(FNameHash and $7FFFFFFF) mod FMod];
  2746. while (Result <> -1) and
  2747. ((FItems[Result].Hash <> FNameHash) or
  2748. not IsSameUTF8String(FItems[Result].Name, Name)) do
  2749. Result := FItems[Result].Next;
  2750. end;
  2751. end;
  2752. //------------------------------------------------------------------------------
  2753. procedure TClassStylesList.AddAppendStyle(const Name, Style: UTF8String);
  2754. var
  2755. Index: Integer;
  2756. Item: PClassStyleListItem;
  2757. Bucket: PInteger;
  2758. begin
  2759. Index := FindItemIndex(Name);
  2760. if Index <> -1 then
  2761. begin
  2762. Item := @FItems[Index];
  2763. if (Item.Style <> '') and (Item.Style[Length(Item.Style)] <> ';') then
  2764. Item.Style := Item.Style + ';' + Style
  2765. else
  2766. Item.Style := Item.Style + Style;
  2767. end
  2768. else
  2769. begin
  2770. if FCount = Length(FItems) then
  2771. Grow;
  2772. Index := FCount;
  2773. Inc(FCount);
  2774. Bucket := @FBuckets[(FNameHash and $7FFFFFFF) mod FMod];
  2775. Item := @FItems[Index];
  2776. Item.Next := Bucket^;
  2777. Item.Hash := FNameHash;
  2778. Item.Name := Name;
  2779. Item.Style := style;
  2780. Bucket^ := Index;
  2781. end;
  2782. end;
  2783. //------------------------------------------------------------------------------
  2784. function TClassStylesList.GetStyle(const Name: UTF8String): UTF8String;
  2785. var
  2786. Index: Integer;
  2787. begin
  2788. if FCount = 0 then
  2789. Result := ''
  2790. else
  2791. begin
  2792. Index := FindItemIndex(Name);
  2793. if Index <> -1 then
  2794. Result := FItems[Index].Style
  2795. else
  2796. Result := '';
  2797. end;
  2798. end;
  2799. //------------------------------------------------------------------------------
  2800. procedure TClassStylesList.Clear;
  2801. begin
  2802. FCount := 0;
  2803. FMod := 0;
  2804. FItems := nil;
  2805. FBuckets := nil;
  2806. end;
  2807. //------------------------------------------------------------------------------
  2808. // TColorConstList
  2809. //------------------------------------------------------------------------------
  2810. constructor TColorConstList.Create(Colors: PColorConst; Count: Integer);
  2811. var
  2812. I: Integer;
  2813. Bucket: PPColorConstMapItem;
  2814. Item: PColorConstMapItem;
  2815. begin
  2816. inherited Create;
  2817. FCount := Count;
  2818. SetLength(FItems, FCount);
  2819. FMod := FCount * 2 + 1; // gives us 3 color constants as max. bucket depth
  2820. SetLength(FBuckets, FMod);
  2821. // Initialize FItems[] and fill the buckets
  2822. for I := 0 to Count - 1 do
  2823. begin
  2824. Item := @FItems[I];
  2825. Item.Data := Colors; // link the constant to the ColorConstMapItem
  2826. Inc(Colors);
  2827. Item.Hash := GetHash(Item.Data.ColorName); // case-insensitive
  2828. Bucket := @FBuckets[(Cardinal(Item.Hash) and $7FFFFFFF) mod FMod];
  2829. Item.Next := Bucket^;
  2830. Bucket^ := Item;
  2831. end;
  2832. end;
  2833. //------------------------------------------------------------------------------
  2834. function TColorConstList.GetColorValue(const ColorName: UTF8String; var Color: TColor32): Boolean;
  2835. var
  2836. Hash: Cardinal;
  2837. Item: PColorConstMapItem;
  2838. begin
  2839. Hash := GetHash(ColorName);
  2840. Item := FBuckets[(Cardinal(Hash) and $7FFFFFFF) mod FMod];
  2841. while (Item <> nil) and
  2842. not IsSameAsciiUTF8String(Item.Data.ColorName, ColorName) do
  2843. Item := Item.Next;
  2844. if Item <> nil then
  2845. begin
  2846. Color := Item.Data.ColorValue;
  2847. Result := True;
  2848. end
  2849. else
  2850. Result := False;
  2851. end;
  2852. //------------------------------------------------------------------------------
  2853. // initialization procedures
  2854. //------------------------------------------------------------------------------
  2855. procedure MakeLowerCaseTable;
  2856. var
  2857. i: UTF8Char;
  2858. begin
  2859. for i:= #0 to #$40 do LowerCaseTable[i]:= i;
  2860. for i:= #$41 to #$5A do LowerCaseTable[i]:= UTF8Char(Ord(i) + $20);
  2861. for i:= #$5B to #$FF do LowerCaseTable[i]:= i;
  2862. end;
  2863. //------------------------------------------------------------------------------
  2864. procedure MakeColorConstList;
  2865. {$I Img32.SVG.HtmlColorConsts.inc}
  2866. begin
  2867. ColorConstList := TColorConstList.Create(@ColorConsts, Length(ColorConsts));
  2868. end;
  2869. //------------------------------------------------------------------------------
  2870. procedure CleanupColorConstList;
  2871. begin
  2872. FreeAndNil(ColorConstList);
  2873. end;
  2874. //------------------------------------------------------------------------------
  2875. //------------------------------------------------------------------------------
  2876. initialization
  2877. MakeLowerCaseTable;
  2878. MakeColorConstList;
  2879. finalization
  2880. CleanupColorConstList;
  2881. end.