| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163 |
- unit Img32.SVG.Core;
- (*******************************************************************************
- * Author : Angus Johnson *
- * Version : 4.7 *
- * Date : 12 January 2025 *
- * Website : http://www.angusj.com *
- * Copyright : Angus Johnson 2019-2025 *
- * *
- * Purpose : Essential structures and functions to read SVG files *
- * *
- * License : Use, modification & distribution is subject to *
- * Boost Software License Ver 1 *
- * http://www.boost.org/LICENSE_1_0.txt *
- *******************************************************************************)
- interface
- {$I Img32.inc}
- uses
- SysUtils, Classes, Types, Math, StrUtils,
- {$IFDEF XPLAT_GENERICS} Generics.Collections, Generics.Defaults,{$ENDIF}
- Img32, Img32.Vector, Img32.Text, Img32.Transform;
- {$IFDEF ZEROBASEDSTR}
- {$ZEROBASEDSTRINGS OFF}
- {$ENDIF}
- type
- TSvgEncoding = (eUnknown, eUtf8, eUnicodeLE, eUnicodeBE);
- TUnitType = (utUnknown, utNumber, utPercent, utEm, utEx, utPixel,
- utCm, utMm, utInch, utPt, utPica, utDegree, utRadian);
- //////////////////////////////////////////////////////////////////////
- // TValue - Structure to store numerics with measurement units.
- // See https://www.w3.org/TR/SVG/types.html#InterfaceSVGLength
- // and https://www.w3.org/TR/SVG/types.html#InterfaceSVGAngle
- //////////////////////////////////////////////////////////////////////
- //Unfortunately unit-less values can exhibit ambiguity, especially when their
- //values are small (eg < 1.0). These values can be either absolute values or
- //relative values (ie relative to the supplied dimension size).
- //The 'assumeRelValBelow' parameter (see below) attempts to address this
- //ambiguity, such that unit-less values will be assumed to be 'relative' when
- //'rawVal' is less than the supplied 'assumeRelValBelow' value.
- TValue = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
- rawVal : double;
- unitType : TUnitType;
- procedure Init;
- procedure SetValue(val: double; unitTyp: TUnitType = utNumber);
- function GetValue(relSize: double; assumeRelValBelow: Double): double;
- function GetValueXY(const relSize: TRectD; assumeRelValBelow: Double): double;
- function IsValid: Boolean;
- function IsRelativeValue(assumeRelValBelow: double): Boolean;
- {$IFDEF INLINE} inline; {$ENDIF}
- function HasFontUnits: Boolean;
- function HasAngleUnits: Boolean;
- end;
- TValuePt = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
- X, Y : TValue;
- procedure Init;
- function GetPoint(const relSize: double; assumeRelValBelow: Double): TPointD; overload;
- function GetPoint(const relSize: TRectD; assumeRelValBelow: Double): TPointD; overload;
- function IsValid: Boolean;
- end;
- TValueRecWH = {$IFDEF RECORD_METHODS} record {$ELSE} object {$ENDIF}
- left : TValue;
- top : TValue;
- width : TValue;
- height : TValue;
- procedure Init;
- function GetRectD(const relSize: TRectD; assumeRelValBelow: Double): TRectD; overload;
- function GetRectD(relSize: double; assumeRelValBelow: Double): TRectD; overload;
- function GetRectD(relSizeX, relSizeY: double; assumeRelValBelow: Double): TRectD; overload;
- function GetRectWH(const relSize: TRectD; assumeRelValBelow: Double): TRectWH;
- function IsValid: Boolean;
- function IsEmpty: Boolean;
- end;
- {$IFNDEF UNICODE}
- UTF8Char = Char;
- PUTF8Char = PChar;
- {$ELSE}
- {$IF COMPILERVERSION < 31}
- UTF8Char = AnsiChar;
- PUTF8Char = PAnsiChar;
- {$IFEND}
- {$ENDIF}
- TSvgItalicSyle = (sfsUndefined, sfsNone, sfsItalic);
- TFontDecoration = (fdUndefined, fdNone, fdUnderline, fdStrikeThrough);
- TSvgTextAlign = (staUndefined, staLeft, staCenter, staRight, staJustify);
- TSpacesInText = (sitUndefined, sitIgnore, sitPreserve);
- UTF8Strings = array of UTF8String;
- TSVGFontInfo = record
- family : TFontFamily;
- familyNames : UTF8Strings;
- size : double;
- spacing : double;
- spacesInText : TSpacesInText;
- textLength : double;
- italic : TSvgItalicSyle;
- weight : Integer;
- align : TSvgTextAlign;
- decoration : TFontDecoration;
- baseShift : TValue;
- end;
- //////////////////////////////////////////////////////////////////////
- // TClassStylesList: Map that stores CSS selectors with their styles
- //////////////////////////////////////////////////////////////////////
- PClassStyleListItem = ^TClassStyleListItem;
- TClassStyleListItem = record //used internally by TClassStylesList
- Hash : Cardinal;
- Next : Integer;
- Name : UTF8String;
- Style : UTF8String;
- end;
- TClassStylesList = class
- private
- FNameHash: Cardinal;
- FItems: array of TClassStyleListItem;
- FBuckets: TArrayOfInteger;
- FCount: Integer;
- FMod: Cardinal;
- procedure Grow(NewCapacity: Integer = -1);
- function FindItemIndex(const Name: UTF8String): Integer;
- public
- procedure Preallocate(AdditionalItemCount: Integer);
- procedure AddAppendStyle(const Name, Style: UTF8String);
- function GetStyle(const Name: UTF8String): UTF8String;
- procedure Clear;
- end;
- //////////////////////////////////////////////////////////////////////
- // TSvgParser and associated classes - a simple parser for SVG xml
- //////////////////////////////////////////////////////////////////////
- PSvgAttrib = ^TSvgAttrib; //element attribute
- TSvgAttrib = record
- hash : Cardinal; //hashed name
- name : UTF8String;
- value : UTF8String;
- end;
- TSvgParser = class;
- TXmlEl = class //base element class
- private
- {$IFDEF XPLAT_GENERICS}
- attribs : TList <PSvgAttrib>;
- {$ELSE}
- attribs : TList;
- {$ENDIF}
- function GetAttrib(index: integer): PSvgAttrib;
- function GetAttribCount: integer;
- public
- {$IFDEF XPLAT_GENERICS}
- childs : TList<TXmlEl>;
- {$ELSE}
- childs : TList;
- {$ENDIF}
- name : UTF8String;
- owner : TSvgParser;
- hash : Cardinal;
- text : UTF8String;
- selfClosed : Boolean;
- constructor Create(owner: TSvgParser); virtual;
- destructor Destroy; override;
- procedure Clear; virtual;
- function ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean; virtual;
- function ParseContent(var c: PUTF8Char; endC: PUTF8Char): Boolean; virtual;
- class function ParseAttribName(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char; {$IFDEF CLASS_STATIC}static;{$ENDIF}
- class function ParseAttribValue(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char; {$IFDEF CLASS_STATIC}static;{$ENDIF}
- class function ParseAttribNameAndValue(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char; {$IFDEF CLASS_STATIC}static;{$ENDIF}
- function ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean; virtual;
- procedure ParseStyleAttribute(const style: UTF8String);
- property Attrib[index: integer]: PSvgAttrib read GetAttrib;
- property AttribCount: integer read GetAttribCount;
- end;
- TDocTypeEl = class(TXmlEl)
- private
- function SkipWord(c, endC: PUTF8Char): PUTF8Char;
- function ParseEntities(var c, endC: PUTF8Char): Boolean;
- public
- function ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean; override;
- end;
- TSvgXmlEl = class(TXmlEl)
- public
- constructor Create(owner: TSvgParser); override;
- procedure Clear; override;
- function ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean; override;
- end;
- TSvgParser = class
- private
- svgStream : TMemoryStream;
- procedure ParseUtf8Stream;
- public
- classStyles : TClassStylesList;
- xmlHeader : TXmlEl;
- docType : TDocTypeEl;
- svgTree : TSvgXmlEl;
- constructor Create;
- destructor Destroy; override;
- procedure Clear;
- function FindEntity(hash: Cardinal): PSvgAttrib;
- function LoadFromFile(const filename: string): Boolean;
- function LoadFromStream(stream: TStream): Boolean;
- function LoadFromString(const str: string): Boolean;
- end;
- //////////////////////////////////////////////////////////////////////
- // Miscellaneous SVG functions
- //////////////////////////////////////////////////////////////////////
- //general parsing functions //////////////////////////////////////////
- function ParseNextWord(var c: PUTF8Char; endC: PUTF8Char;
- out word: UTF8String): Boolean;
- function ParseNextWordHash(var c: PUTF8Char; endC: PUTF8Char;
- out hash: cardinal): Boolean; overload;
- function ParseNextWordHash(c, endC: PUTF8Char): cardinal; overload;
- function ParseNextWordExHash(var c: PUTF8Char; endC: PUTF8Char;
- out hash: cardinal): Boolean;
- function ParseNextNum(var c: PUTF8Char; endC: PUTF8Char;
- skipComma: Boolean; out val: double): Boolean;
- function ParseNextNumEx(var c: PUTF8Char; endC: PUTF8Char; skipComma: Boolean;
- out val: double; out unitType: TUnitType): Boolean;
- function GetHash(c: PUTF8Char; len: nativeint): cardinal; overload;
- function GetHash(const name: UTF8String): cardinal; overload; {$IFDEF INLINE} inline; {$ENDIF}
- function GetHashCaseSensitive(name: PUTF8Char; nameLen: integer): cardinal;
- function ExtractRef(const href: UTF8String): UTF8String;
- function IsNumPending(var c: PUTF8Char;
- endC: PUTF8Char; ignoreComma: Boolean): Boolean;
- function UTF8StringToColor32(const value: UTF8String; var color: TColor32): Boolean;
- function ScaleDashArray(const dblArray: TArrayOfDouble; scale: double): TArrayOfDouble;
- function Match(c: PUTF8Char; const compare: UTF8String): Boolean; overload;
- function Match(const compare1, compare2: UTF8String): Boolean; overload;
- function PosEx(const subStr: utf8String; const text: Utf8String; startIdx: integer = 1): integer;
- procedure ToUTF8String(c, endC: PUTF8Char; var S: UTF8String;
- spacesInText: TSpacesInText = sitUndefined);
- function TrimMultiSpacesUtf8(const text: Utf8String): Utf8String;
- function TrimMultiSpacesUnicode(const text: UnicodeString): UnicodeString;
- function ConvertNewlines(const s: UTF8String): UTF8String; overload;
- function ConvertNewlines(const s: UnicodeString): UnicodeString; overload;
- function StripNewlines(const s: UTF8String): UTF8String; overload;
- function StripNewlines(const s: UnicodeString): UnicodeString; overload;
- procedure ToAsciiLowerUTF8String(c, endC: PUTF8Char; var S: UTF8String);
- procedure ToTrimmedUTF8String(c, endC: PUTF8Char; var S: UTF8String);
- function IsSameUTF8String(const S1, S2: UTF8String): Boolean;
- //special parsing functions //////////////////////////////////////////
- procedure ParseStyleElementContent(const value: UTF8String; stylesList: TClassStylesList);
- function ParseTransform(const transform: UTF8String): TMatrixD;
- procedure GetSvgFontInfo(const value: UTF8String; var fontInfo: TSVGFontInfo);
- function HtmlDecode(const html: UTF8String): UTF8String;
- function GetXmlEncoding(memory: Pointer; len: integer): TSvgEncoding;
- function ClampRange(val, min, max: double): double;
- function SkipBlanks(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- function SkipBlanksEx(c: PUTF8Char; endC: PUTF8Char): PUTF8Char; {$IFDEF INLINE} inline; {$ENDIF}
- function SkipBlanksAndComma(c, endC: PUTF8Char): PUTF8Char; {$IFDEF INLINE} inline; {$ENDIF}
- function GetCommaSeparatedArray(const str: UTF8String): UTF8Strings;
- function TrimQuotes(const str: UTF8String): UTF8String;
- procedure ConvertUnicodeToUtf8(memStream: TMemoryStream);
- function GetScale(src, dst: double): double;
- function GetScaleForBestFit(srcW, srcH, dstW, dstH: double): double;
- function Base64Decode(const str: PAnsiChar; len: integer; memStream: TMemoryStream): Boolean;
- type
- TSetOfUTF8Char = set of UTF8Char;
- function CharInSet(chr: UTF8Char; const chrs: TSetOfUTF8Char): Boolean;
- function DecodeUtf8ToUnicode(const utf8: UTF8String): UnicodeString;
- const
- clInvalid = $00010001;
- clCurrent = $00010002;
- sqrt2 = 1.4142135623731;
- quote = '''';
- dquote = '"';
- space = #32;
- comma = ',';
- SvgDecimalSeparator = '.'; //do not localize
- {$I Img32.SVG.HashConsts.inc}
- var
- LowerCaseTable : array[#0..#$FF] of UTF8Char;
- implementation
- //------------------------------------------------------------------------------
- // Color Constant HashMap
- //------------------------------------------------------------------------------
- type
- PColorConst = ^TColorConst;
- TColorConst = record
- ColorName : UTF8String;
- ColorValue: TColor32;
- end;
- PPColorConstMapItem = ^PColorConstMapItem;
- PColorConstMapItem = ^TColorConstMapItem;
- TColorConstMapItem = record
- Hash: Cardinal;
- Next: PColorConstMapItem;
- Data: PColorConst;
- end;
- PColorConstMapItemArray = ^TColorConstMapItemArray;
- TColorConstMapItemArray = array[0..MaxInt div SizeOf(TColorConstMapItem) - 1] of TColorConstMapItem;
- TColorConstList = class(TObject)
- private
- FItems: array of TColorConstMapItem;
- FBuckets: array of PColorConstMapItem;
- FCount: Integer;
- FMod: Cardinal;
- public
- constructor Create(Colors: PColorConst; Count: Integer);
- function GetColorValue(const ColorName: UTF8String; var Color: TColor32): Boolean;
- end;
- var
- ColorConstList : TColorConstList;
- const
- buffSize = 8;
- //include hashed html entity constants
- {$I Img32.SVG.HtmlHashConsts.inc}
- //------------------------------------------------------------------------------
- // Base64 (MIME) Encode & Decode and other encoding functions ...
- //------------------------------------------------------------------------------
- type
- PFourChars = ^TFourChars;
- TFourChars = record
- c1: ansichar;
- c2: ansichar;
- c3: ansichar;
- c4: ansichar;
- end;
- function Chr64ToVal(c: ansiChar): integer; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- case c of
- '+': result := 62;
- '/': result := 63;
- '0'..'9': result := ord(c) + 4;
- 'A'..'Z': result := ord(c) -65;
- 'a'..'z': result := ord(c) -71;
- else Raise Exception.Create('Corrupted MIME encoded text');
- end;
- end;
- //------------------------------------------------------------------------------
- function FrstChr(c: PFourChars): ansichar; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- result := ansichar(Chr64ToVal(c.c1) shl 2 or Chr64ToVal(c.c2) shr 4);
- end;
- //------------------------------------------------------------------------------
- function ScndChr(c: PFourChars): ansichar; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- result := ansichar(Chr64ToVal(c.c2) shl 4 or Chr64ToVal(c.c3) shr 2);
- end;
- //------------------------------------------------------------------------------
- function ThrdChr(c: PFourChars): ansichar; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- result := ansichar( Chr64ToVal(c.c3) shl 6 or Chr64ToVal(c.c4) );
- end;
- //------------------------------------------------------------------------------
- function Base64Decode(const str: PAnsiChar; len: integer; memStream: TMemoryStream): Boolean;
- var
- i, j, extra: integer;
- Chars4: PFourChars;
- dst: PAnsiChar;
- begin
- result := false;
- if (len = 0) or (len mod 4 > 0) or not Assigned(memStream) then exit;
- if str[len-2] = '=' then extra := 2
- else if str[len-1] = '=' then extra := 1
- else extra := 0;
- memStream.SetSize(LongInt((len div 4 * 3) - extra));
- dst := memStream.Memory;
- Chars4 := @str[0];
- i := 0;
- try
- for j := 1 to (len div 4) -1 do
- begin
- dst[i] := FrstChr(Chars4);
- dst[i+1] := ScndChr(Chars4);
- dst[i+2] := ThrdChr(Chars4);
- inc(pbyte(Chars4),4);
- inc(i,3);
- end;
- dst[i] := FrstChr(Chars4);
- if extra < 2 then dst[i+1] := ScndChr(Chars4);
- if extra < 1 then dst[i+2] := ThrdChr(Chars4);
- except
- Exit;
- end;
- Result := true;
- end;
- //------------------------------------------------------------------------------
- // Miscellaneous functions ...
- //------------------------------------------------------------------------------
- function NewSvgAttrib(): PSvgAttrib; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- // New(Result) uses RTTI to initialize the UTF8String fields to nil.
- // By allocating zero'ed memory we can achieve that much faster.
- Result := AllocMem(SizeOf(TSvgAttrib));
- end;
- //------------------------------------------------------------------------------
- procedure DisposeSvgAttrib(attrib: PSvgAttrib); {$IFDEF INLINE} inline; {$ENDIF}
- begin
- // Dispose(Result) uses RTTI to set the UTF8String fields to nil.
- // By clearing them outself we can achieve that much faster.
- attrib.name := '';
- attrib.value := '';
- FreeMem(attrib);
- end;
- //------------------------------------------------------------------------------
- function GetScale(src, dst: double): double;
- begin
- Result := dst / src;
- if (SameValue(Result, 1, 0.00001)) then Result := 1;
- end;
- //------------------------------------------------------------------------------
- function GetScaleForBestFit(srcW, srcH, dstW, dstH: double): double;
- var
- sx,sy: double;
- begin
- sx := dstW / srcW;
- sy := dstH / srcH;
- if sy < sx then sx := sy;
- if (SameValue(sx, 1, 0.00001)) then
- Result := 1 else
- Result := sx;
- end;
- //------------------------------------------------------------------------------
- function ClampRange(val, min, max: double): double;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- if val <= min then Result := min
- else if val >= max then Result := max
- else Result := val;
- end;
- //------------------------------------------------------------------------------
- function IsSameAsciiUTF8String(const S1, S2: UTF8String): Boolean;
- var
- Len: Integer;
- I: Integer;
- Ch1, Ch2: UTF8Char;
- begin
- Len := Length(S1);
- Result := Len = Length(S2);
- if Result then
- begin
- Result := False;
- I := 1;
- while True do
- begin
- if I > Len then
- Break;
- Ch1 := S1[I];
- Ch2 := S2[I];
- if Ch1 = Ch2 then
- begin
- Inc(I);
- Continue;
- end;
- case Ch1 of
- 'A'..'Z', 'a'..'z':
- ch1 := UTF8Char(Ord(ch1) xor $20); // toggle upper/lower
- end;
- if Ch1 <> Ch2 then
- Exit;
- Inc(I);
- end;
- Result := True;
- end;
- end;
- //------------------------------------------------------------------------------
- function IsSameUTF8StringSlow(const S1, S2: UTF8String): Boolean;
- begin
- Result := AnsiSameText(string(S1), string(S2));
- end;
- //------------------------------------------------------------------------------
- function IsSameUTF8String(const S1, S2: UTF8String): Boolean;
- var
- Len: Integer;
- I: Integer;
- Ch1, Ch2: UTF8Char;
- begin
- Len := Length(S1);
- Result := Len = Length(S2);
- if Result then
- begin
- Result := False;
- I := 1;
- Ch1 := #0;
- Ch2 := #0;
- while True do
- begin
- if I > Len then
- Break;
- Ch1 := S1[I];
- Ch2 := S2[I];
- if Ch1 = Ch2 then
- begin
- Inc(I);
- Continue;
- end;
- case Ch1 of
- 'A'..'Z', 'a'..'z':
- ch1 := UTF8Char(Ord(ch1) xor $20); // toggle upper/lower
- end;
- if Ch1 <> Ch2 then
- Break;
- Inc(I);
- end;
- if Ch1 = Ch2 then
- Result := True
- else if (Ord(Ch1) or Ord(Ch2)) and $80 <> 0 then // we found non-matching, non-ASCII characters
- Result := IsSameUTF8StringSlow(S1, S2);
- end;
- end;
- //------------------------------------------------------------------------------
- function CharInSet(chr: UTF8Char; const chrs: TSetOfUTF8Char): Boolean;
- begin
- Result := chr in chrs;
- end;
- //------------------------------------------------------------------------------
- function Match(c: PUTF8Char; const compare: UTF8String): Boolean;
- var
- i: integer;
- begin
- Result := false;
- for i := 1 to Length(compare) do
- begin
- if LowerCaseTable[c[i - 1]] <> compare[i] then Exit;
- end;
- Result := true;
- end;
- //------------------------------------------------------------------------------
- function Match(const compare1, compare2: UTF8String): Boolean;
- var
- i, len: integer;
- c1, c2: PUTF8Char;
- begin
- Result := false;
- len := Length(compare1);
- if len <> Length(compare2) then Exit;
- c1 := @compare1[1]; c2 := @compare2[1];
- for i := 1 to len do
- begin
- if LowerCaseTable[c1[i - 1]] <> LowerCaseTable[c2[i - 1]] then Exit;
- end;
- Result := true;
- end;
- //------------------------------------------------------------------------------
- function Split(const str: UTF8String): UTF8Strings;
- var
- i,j,k, spcCnt, len: integer;
- begin
- spcCnt := 0;
- i := 1;
- len := Length(str);
- while (len > 0) and (str[len] <= space) do dec(len);
- while (i <= len) and (str[i] <= space) do inc(i);
- for j := i + 1 to len do
- if (str[j] <= space) and (str[j -1] > space) then inc(spcCnt);
- SetLength(Result, spcCnt +1);
- for k := 0 to spcCnt do
- begin
- j := i;
- while (j <= len) and (str[j] > space) do inc(j);
- SetLength(Result[k], j -i);
- if j > i then
- Move(str[i], Result[k][1], j -i);
- while (j <= len) and (str[j] <= space) do inc(j);
- i := j;
- end;
- end;
- //------------------------------------------------------------------------------
- function TrimQuotes(const str: UTF8String): UTF8String;
- var
- i, len: integer;
- savedQuote: UTF8Char;
- begin
- len := Length(str);
- i := 1;
- while (i < len) and (str[i] <= space) do inc(i);
- if (i < len) and (str[i] in [quote, dquote]) then
- begin
- savedQuote := str[i];
- inc(i);
- while (len > i) and (str[len] <= space) do dec(len);
- if (len = i) or (str[len] <> savedQuote) then
- Result := str else // oops!
- Result := Copy(str, i, len - i);
- end
- else
- Result := str
- end;
- //------------------------------------------------------------------------------
- function GetCommaSeparatedArray(const str: UTF8String): UTF8Strings;
- var
- i,j,k, cnt, len: integer;
- begin
- // precondition: commas CANNOT be embedded
- len := Length(str);
- cnt := 1;
- for i := 1 to len do
- if (str[i] = comma) then inc(cnt);
- SetLength(Result, cnt);
- j := 0;
- k := 1;
- for i := 1 to len do
- begin
- if (str[i] <> comma) then Continue;
- Result[j] := TrimQuotes(Copy(str, k, i-k));
- inc(j);
- k := i + 1;
- end;
- if len >= k then
- Result[j] := TrimQuotes(Copy(str, k, len-k +1));
- end;
- //------------------------------------------------------------------------------
- function GetXmlEncoding(memory: Pointer; len: integer): TSvgEncoding;
- var
- p, p1: PUTF8Char;
- begin
- Result := eUnknown;
- if (len < 4) or not Assigned(memory) then Exit;
- p := PUTF8Char(memory);
- p1 := (p + 1);
- case p^ of
- #$EF: if (p1^ = #$BB) then
- if ((p +2)^ = #$BF) then
- Result := eUtf8 else
- Exit;
- #$FF: if (p1^ = #$FE) or (p1^ = #0) then
- Result := eUnicodeLE;
- #$FE: if (p1^ = #$FF) then
- Result := eUnicodeBE;
- end;
- end;
- //------------------------------------------------------------------------------
- function SkipBlanks(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- var
- cc: PUTF8Char;
- begin
- cc := c;
- if (cc < endC) and (cc^ <= space) then
- begin
- inc(cc);
- while (cc < endC) and (cc^ <= space) do inc(cc);
- c := cc;
- end;
- Result := (cc < endC);
- end;
- //------------------------------------------------------------------------------
- function SkipBlanksEx(c: PUTF8Char; endC: PUTF8Char): PUTF8Char;
- begin
- while (c < endC) and (c^ <= space) do inc(c);
- Result := c;
- end;
- //------------------------------------------------------------------------------
- function SkipBlanksAndComma(c, endC: PUTF8Char): PUTF8Char;
- begin
- Result := SkipBlanksEx(c, endC);
- if (Result >= endC) or (Result^ <> ',') then Exit;
- Result := SkipBlanksEx(Result + 1, endC);
- end;
- //------------------------------------------------------------------------------
- function SkipStyleBlanks(c, endC: PUTF8Char): PUTF8Char;
- var
- inComment: Boolean;
- ch: UTF8Char;
- begin
- //style content may include multi-line comment blocks
- inComment := false;
- while (c < endC) do
- begin
- ch := c^;
- if inComment then
- begin
- if (ch = '*') and ((c +1)^ = '/') then
- begin
- inComment := false;
- inc(c);
- end;
- end
- else if (ch > space) then
- begin
- inComment := (ch = '/') and ((c +1)^ = '*');
- if not inComment then break;
- inc(c);
- end;
- inc(c);
- end;
- Result := c;
- end;
- //------------------------------------------------------------------------------
- function IsDigit(c: UTF8Char): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- case c of
- '0'..'9': Result := True;
- else Result := False;
- end;
- end;
- //------------------------------------------------------------------------------
- function IsQuoteChar(c: UTF8Char): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := (c = quote) or (c = dquote);
- end;
- //------------------------------------------------------------------------------
- function IsAlpha(c: UTF8Char): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- case c of
- 'A'..'Z', 'a'..'z': Result := True;
- else Result := False;
- end;
- end;
- //------------------------------------------------------------------------------
- function ParseStyleNameLen(c, endC: PUTF8Char): PUTF8Char;
- var
- c2: PUTF8Char;
- begin
- Result := c;
- //nb: style names may start with a hyphen
- c2 := Result;
- if (c2^ = '-') then inc(c2);
- if not IsAlpha(c2^) then Exit;
- Result := c2 + 1;
- while Result < endC do
- begin
- case Result^ of
- '0'..'9', 'A'..'Z', 'a'..'z', '-': inc(Result);
- else break;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function ParseNextWord(var c: PUTF8Char; endC: PUTF8Char; out word: UTF8String): Boolean;
- var
- c2, cc: PUTF8Char;
- begin
- cc := SkipBlanksAndComma(c, endC);
- if cc >= endC then
- begin
- c := cc;
- Result := False;
- Exit;
- end;
- c2 := cc;
- while cc < endC do
- begin
- case cc^ of
- 'A'..'Z', 'a'..'z': inc(cc);
- else break;
- end;
- end;
- c := cc;
- ToUTF8String(c2, cc, word);
- Result := True;
- end;
- //------------------------------------------------------------------------------
- function ParseNextWordHash(var c: PUTF8Char; endC: PUTF8Char; out hash: cardinal): Boolean;
- var
- c2, cc: PUTF8Char;
- begin
- cc := SkipBlanksAndComma(c, endC);
- if cc >= endC then
- begin
- c := cc;
- hash := 0;
- Result := False;
- Exit;
- end;
- c2 := cc;
- while cc < endC do
- begin
- case cc^ of
- 'A'..'Z', 'a'..'z': inc(cc);
- else break;
- end;
- end;
- c := cc;
- hash := GetHash(c2, cc - c2);
- Result := True;
- end;
- //------------------------------------------------------------------------------
- function ParseNextWordHash(c, endC: PUTF8Char): cardinal;
- var
- c2: PUTF8Char;
- begin
- c := SkipBlanksAndComma(c, endC);
- if c >= endC then
- begin
- Result := 0;
- Exit;
- end;
- c2 := c;
- while c < endC do
- begin
- case c^ of
- 'A'..'Z', 'a'..'z': inc(c);
- else break;
- end;
- end;
- Result := GetHash(c2, c - c2);
- end;
- //------------------------------------------------------------------------------
- function ParseNextWordExHash(var c: PUTF8Char; endC: PUTF8Char;
- out hash: cardinal): Boolean;
- var
- c2, cc: PUTF8Char;
- begin
- cc := SkipBlanksAndComma(c, endC);
- if cc >= endC then
- begin
- c := cc;
- hash := 0;
- Result := False;
- Exit;
- end;
- if cc^ = quote then
- begin
- inc(c);
- c2 := cc;
- while (cc < endC) and (cc^ <> quote) do inc(cc);
- hash := GetHash(c2, cc - c2);
- inc(cc);
- end else
- begin
- if not IsAlpha(cc^) then
- begin
- hash := 0;
- Result := False;
- Exit;
- end;
- c2 := cc;
- inc(cc);
- while cc < endC do
- case cc^ of
- 'A'..'Z', 'a'..'z', '-', '_': inc(cc);
- else break;
- end;
- hash := GetHash(c2, cc - c2);
- end;
- c := cc;
- Result := True;
- end;
- //------------------------------------------------------------------------------
- function ParseNameLength(c: PUTF8Char; endC: PUTF8Char): PUTF8Char;
- begin
- inc(c);
- while c < endC do
- begin
- case c^ of
- '0'..'9', 'A'..'Z', 'a'..'z', '_', ':', '-': inc(c);
- else break;
- end;
- end;
- Result := c;
- end;
- //------------------------------------------------------------------------------
- {$PUSH}{$Q-}{$R-}
- function GetHash(c: PUTF8Char; len: nativeint): cardinal;
- var
- i: integer;
- begin
- //https://en.wikipedia.org/wiki/Jenkins_hash_function
- Result := 0;
- if c = nil then Exit;
- for i := 1 to len do
- begin
- Result := (Result + Ord(LowerCaseTable[c^]));
- Result := Result + (Result shl 10);
- Result := Result xor (Result shr 6);
- inc(c);
- end;
- Result := Result + (Result shl 3);
- Result := Result xor (Result shr 11);
- Result := Result + (Result shl 15);
- end;
- {$POP}
- //------------------------------------------------------------------------------
- function GetHash(const name: UTF8String): cardinal;
- begin
- // skip function call by directly casting it to Pointer
- Result := GetHash(PUTF8Char(Pointer(name)), Length(name));
- end;
- //------------------------------------------------------------------------------
- {$PUSH}{$Q-}{$R-}
- function GetHashCaseSensitive(name: PUTF8Char; nameLen: integer): cardinal;
- var
- i: integer;
- begin
- Result := 0;
- for i := 1 to nameLen do
- begin
- Result := (Result + Ord(name^));
- Result := Result + (Result shl 10);
- Result := Result xor (Result shr 6);
- inc(name);
- end;
- Result := Result + (Result shl 3);
- Result := Result xor (Result shr 11);
- Result := Result + (Result shl 15);
- end;
- {$POP}
- //------------------------------------------------------------------------------
- function ParseNextWordHashed(var c: PUTF8Char; endC: PUTF8Char): cardinal;
- var
- c2: PUTF8Char;
- len: integer;
- begin
- c2 := c;
- c := ParseNameLength(c2, endC);
- len := c - c2;
- if len <= 0 then Result := 0
- else Result := GetHash(c2, len);
- end;
- //------------------------------------------------------------------------------
- function ParseExpDigits(c, endC: PUTF8Char; out val: Integer): PUTF8Char; {$IFDEF INLINE} inline; {$ENDIF}
- var
- v32: Cardinal;
- Digit: Integer;
- begin
- Result := c;
- v32 := 0;
- while Result < endC do
- begin
- Digit := Integer(Ord(Result^)) - Ord('0');
- if Cardinal(Digit) >= 10 then break;
- {$IFDEF FPC} // Something Delphi can optimize but FPC can't (yet?)
- v32 := (v32 shl 3) + (v32 shl 1) + Cardinal(Digit); // Delphi's code is even better than this
- {$ELSE}
- v32 := v32 * 10 + Cardinal(Digit);
- {$ENDIF FPC}
- inc(Result);
- end;
- val := v32;
- end;
- //------------------------------------------------------------------------------
- function ParseDigitsToDouble(c, endC: PUTF8Char; out val: double): PUTF8Char;
- var
- v32: Cardinal;
- v64: Int64;
- Digit: Integer;
- blockEndC: PUTF8Char;
- begin
- // skip leading zeros
- while (c < endC) and (c^ = '0') do inc(c);
- // Use Int32 first as it is fast for 64bit and 32bit CPUs
- Result := c;
- v32 := 0;
- blockEndC := c + 9; // log10(2^31) = 9.33
- if blockEndC > endC then
- blockEndC := endC;
- while Result < blockEndC do
- begin
- Digit := Integer(Ord(Result^)) - Ord('0');
- if Cardinal(Digit) >= 10 then break;
- {$IFDEF FPC} // Something Delphi can optimize but FPC can't (yet?)
- v32 := (v32 shl 3) + (v32 shl 1) + Cardinal(Digit);
- {$ELSE}
- v32 := v32 * 10 + Cardinal(Digit);
- {$ENDIF FPC}
- inc(Result);
- end;
- if (Result < endC) and (Result >= blockEndC) then
- begin
- v64 := v32;
- blockEndC := c + 18; // log10(2^63) = 18.96
- if blockEndC > endC then
- blockEndC := endC;
- while Result < blockEndC do
- begin
- Digit := Integer(Ord(Result^)) - Ord('0');
- if Cardinal(Digit) >= 10 then break;
- {$IF (SizeOf(Pointer) = 4) or defined(FPC)} // neither Delphi 32bit nor FPC can optimize this
- v64 := (v64 shl 3) + (v64 shl 1) + Cardinal(Digit);
- {$ELSE}
- v64 := v64 * 10 + Cardinal(Digit);
- {$IFEND}
- inc(Result);
- end;
- val := v64;
- // Use Double for the remaining digits and loose precision (we are beyong 16 digits anyway)
- if (Result < endC) and (Result >= blockEndC) then
- begin
- while Result < endC do
- begin
- Digit := Integer(Ord(Result^)) - Ord('0');
- if Cardinal(Digit) >= 10 then break;
- val := val * 10 + Digit;
- inc(Result);
- end;
- end;
- end
- else
- val := v32;
- end;
- //------------------------------------------------------------------------------
- function ParseNextNumEx(var c: PUTF8Char; endC: PUTF8Char; skipComma: Boolean;
- out val: double; out unitType: TUnitType): Boolean;
- const
- Power10: array[0..18] of Double = (
- 1E0, 1E1, 1E2, 1E3, 1E4, 1E5, 1E6, 1E7, 1E8, 1E9,
- 1E10, 1E11, 1E12, 1E13, 1E14, 1E15, 1E16, 1E17, 1E18
- );
- Power10Reciprocal: array[0..18] of Double = (
- 1/1E0, 1/1E1, 1/1E2, 1/1E3, 1/1E4, 1/1E5, 1/1E6, 1/1E7, 1/1E8, 1/1E9,
- 1/1E10, 1/1E11, 1/1E12, 1/1E13, 1/1E14, 1/1E15, 1/1E16, 1/1E17, 1/1E18
- );
- var
- exp: integer;
- isNeg, expIsNeg: Boolean;
- start, decStart, cc: PUTF8Char;
- decimals: Double;
- begin
- Result := false;
- unitType := utNumber;
- cc := c;
- //skip white space +/- single comma
- if skipComma then
- begin
- while (cc < endC) and (cc^ <= space) do inc(cc);
- if (cc^ = ',') then inc(cc);
- end;
- while (cc < endC) and (cc^ <= space) do inc(cc);
- if (cc = endC) then
- begin
- c := cc;
- Exit;
- end;
- exp := Invalid; expIsNeg := false;
- isNeg := cc^ = '-';
- if isNeg then inc(cc);
- start := cc;
- // Use fast parsing
- cc := ParseDigitsToDouble(cc, endC, val);
- if cc < endC then
- begin
- // Decimals
- if Ord(cc^) = Ord(SvgDecimalSeparator) then
- begin
- inc(cc);
- decStart := cc;
- cc := ParseDigitsToDouble(cc, endC, decimals);
- if cc > decStart then
- begin
- if cc - decStart <= 18 then
- val := val + (decimals * Power10Reciprocal[(cc - decStart)])
- else
- val := val + (decimals * Power(10, -(cc - decStart)))
- end;
- end;
- // Exponent
- if (cc < endC) and ((cc^ = 'e') or (cc^ = 'E')) then
- begin
- case (cc+1)^ of
- '-', '0'..'9':
- begin
- inc(cc);
- if cc^ = '-' then
- begin
- expIsNeg := true;
- inc(cc);
- end;
- cc := ParseExpDigits(cc, endC, exp);
- end;
- end;
- end;
- end;
- Result := cc > start;
- if not Result then
- begin
- c := cc;
- Exit;
- end;
- if isNeg then val := -val;
- if IsValid(exp) then
- begin
- if exp <= 18 then
- begin
- if expIsNeg then
- val := val * Power10Reciprocal[exp] else
- val := val * Power10[exp];
- end
- else
- begin
- if expIsNeg then
- val := val * Power(10, -exp) else
- val := val * Power(10, exp);
- end;
- end;
- //https://oreillymedia.github.io/Using_SVG/guide/units.html
- case cc^ of
- '%':
- begin
- inc(cc);
- unitType := utPercent;
- end;
- 'c': //convert cm to pixels
- if ((cc+1)^ = 'm') then
- begin
- inc(cc, 2);
- unitType := utCm;
- end;
- 'd': //ignore deg
- if ((cc+1)^ = 'e') and ((cc+2)^ = 'g') then
- begin
- inc(cc, 3);
- unitType := utDegree;
- end;
- 'e': //convert cm to pixels
- if ((cc+1)^ = 'm') then
- begin
- inc(cc, 2);
- unitType := utEm;
- end
- else if ((cc+1)^ = 'x') then
- begin
- inc(cc, 2);
- unitType := utEx;
- end;
- 'i': //convert inchs to pixels
- if ((cc+1)^ = 'n') then
- begin
- inc(cc, 2);
- unitType := utInch;
- end;
- 'm': //convert mm to pixels
- if ((cc+1)^ = 'm') then
- begin
- inc(cc, 2);
- unitType := utMm;
- end;
- 'p':
- case (cc+1)^ of
- 'c':
- begin
- inc(cc, 2);
- unitType := utPica;
- end;
- 't':
- begin
- inc(cc, 2);
- unitType := utPt;
- end;
- 'x':
- begin
- inc(cc, 2);
- unitType := utPixel;
- end;
- end;
- 'r': //convert radian angles to degrees
- if Match(cc, 'rad') then
- begin
- inc(cc, 3);
- unitType := utRadian;
- end;
- end;
- c := cc;
- end;
- //------------------------------------------------------------------------------
- function ParseNextNum(var c: PUTF8Char; endC: PUTF8Char;
- skipComma: Boolean; out val: double): Boolean;
- var
- tmp: TValue;
- begin
- tmp.Init;
- Result := ParseNextNumEx(c, endC, skipComma, tmp.rawVal, tmp.unitType);
- val := tmp.GetValue(1, 1);
- end;
- //------------------------------------------------------------------------------
- function ExtractRef(const href: UTF8String): UTF8String; {$IFDEF INLINE} inline; {$ENDIF}
- var
- c, c2, endC: PUTF8Char;
- begin
- c := PUTF8Char(href);
- endC := c + Length(href);
- if Match(c, 'url(') then
- begin
- inc(c, 4);
- dec(endC); // avoid trailing ')'
- end;
- if c^ = '#' then inc(c);
- c2 := c;
- while (c < endC) and (c^ <> ')') do inc(c);
- ToUTF8String(c2, c, Result);
- end;
- //------------------------------------------------------------------------------
- function ParseNextChar(var c: PUTF8Char; endC: PUTF8Char): UTF8Char;
- var
- cc: PUTF8Char;
- begin
- cc := SkipBlanksEx(c, endC);
- if cc >= endC then
- Result := #0
- else
- begin
- Result := cc^;
- c := cc + 1;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure ToTrimmedUTF8String(c, endC: PUTF8Char; var S: UTF8String);
- var
- len: integer;
- begin
- // trim left
- while (c < endC) and (c^ <= space) do Inc(c);
- // trim right
- while (endC > c) and (endC[-1] <= space) do Dec(endC);
- len := endC - c;
- SetLength(S, len);
- if len = 0 then Exit;
- Move(c^, PUTF8Char(S)^, len * SizeOf(UTF8Char));
- end;
- //------------------------------------------------------------------------------
- function PosEx(const subStr: UTF8String; const text: Utf8String; startIdx: integer): integer;
- var
- i, maxI, len, subStrLen: integer;
- begin
- len := Length(Text);
- subStrLen := Length(subStr);
- maxI := len - subStrLen +1;
- for i := Max(1, startIdx) to maxI do
- begin
- if (text[i] <> subStr[1]) or
- not CompareMem(@text[i], @subStr[1], subStrLen) then Continue;
- Result := i;
- Exit;
- end;
- Result := 0;
- end;
- //------------------------------------------------------------------------------
- function ReversePosEx(utf8: utf8Char;
- const text: Utf8String; startIdx: integer): integer; overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := Max(0, Min(Length(text), startidx));
- while (Result > 0) and (text[Result] <> utf8) do Dec(Result);
- end;
- //------------------------------------------------------------------------------
- function TrimMultiSpacesUtf8(const text: Utf8String): Utf8String;
- var
- i, len: integer;
- begin
- Result := text;
- len := Length(Result);
- for i := 1 to len do
- if Result[i] < #32 then Result[i] := #32;
- i := ReversePosEx(space, Result, len);
- while i > 1 do
- begin
- Dec(i);
- while (i > 0) and (Result[i] = space) do
- begin
- Delete(Result, i, 1);
- Dec(i);
- end;
- i := ReversePosEx(space, Result, i);
- end;
- end;
- //------------------------------------------------------------------------------
- function ReversePosEx(c: WideChar;
- const text: UnicodeString; startIdx: integer): integer; overload;
- {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := Max(0, Min(Length(text), startidx));
- while (Result > 0) and (text[Result] <> c) do Dec(Result);
- end;
- //------------------------------------------------------------------------------
- function TrimMultiSpacesUnicode(const text: UnicodeString): UnicodeString;
- var
- i, len: integer;
- begin
- Result := text;
- len := Length(Result);
- for i := 1 to len do
- if Result[i] < #32 then Result[i] := #32;
- i := ReversePosEx(space, Result, len);
- while i > 1 do
- begin
- Dec(i);
- while (i > 0) and (Result[i] = space) do
- begin
- Delete(Result, i, 1);
- Dec(i);
- end;
- i := ReversePosEx(space, Result, i);
- end;
- end;
- //------------------------------------------------------------------------------
- function StripNewlines(const s: UTF8String): UTF8String;
- var
- i: integer;
- begin
- Result := s;
- i := Length(Result);
- while i > 0 do
- begin
- if Result[i] < space then Delete(Result, i, 1);
- Dec(i);
- end;
- end;
- //------------------------------------------------------------------------------
- function StripNewlines(const s: UnicodeString): UnicodeString;
- var
- i: integer;
- begin
- Result := s;
- i := Length(Result);
- while i > 0 do
- begin
- if Result[i] < space then Delete(Result, i, 1);
- Dec(i);
- end;
- end;
- //------------------------------------------------------------------------------
- function ConvertNewlines(const s: UTF8String): UTF8String; overload;
- var
- i: integer;
- begin
- Result := s;
- i := Length(Result);
- while i > 0 do
- begin
- if Result[i] < space then
- begin
- if Result[i] = #10 then
- Result[i] := space else
- Delete(Result, i, 1);
- end;
- Dec(i);
- end;
- end;
- //------------------------------------------------------------------------------
- function ConvertNewlines(const s: UnicodeString): UnicodeString; overload;
- var
- i: integer;
- begin
- Result := s;
- i := Length(Result);
- while i > 0 do
- begin
- if Result[i] < space then
- begin
- if Result[i] = #10 then
- Result[i] := space else
- Delete(Result, i, 1);
- end;
- Dec(i);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure ToUTF8String(c, endC: PUTF8Char;
- var S: UTF8String; spacesInText: TSpacesInText);
- var
- len: integer;
- begin
- len := endC - c;
- SetLength(S, len);
- if len = 0 then Exit;
- Move(c^, PUTF8Char(S)^, len * SizeOf(UTF8Char));
- if spacesInText <> sitPreserve then
- S := TrimMultiSpacesUtf8(S);
- S := ConvertNewlines(S);
- end;
- //------------------------------------------------------------------------------
- procedure ToAsciiLowerUTF8String(c, endC: PUTF8Char; var S: UTF8String);
- // Reads a UTF8String and converts all upper case 'A'..'Z' to lower case 'a'..'z'
- var
- len: integer;
- p: PUTF8Char;
- ch: UTF8Char;
- begin
- len := endC - c;
- SetLength(S, len);
- if len = 0 then Exit;
- // Use a pointer arithmetic trick to run forward by using a negative index
- p := PUTF8Char(S) + len;
- len := -len;
- while len < 0 do
- begin
- ch := endC[len];
- case ch of
- 'A'..'Z':
- ch := UTF8Char(Byte(ch) or $20);
- end;
- p[len] := ch;
- inc(len);
- end;
- end;
- //------------------------------------------------------------------------------
- function IsKnownEntity(owner: TSvgParser;
- var c: PUTF8Char; endC: PUTF8Char; out entity: PSvgAttrib): boolean;
- var
- c2, c3: PUTF8Char;
- begin
- inc(c); //skip ampersand.
- c2 := c; c3 := c;
- c3 := ParseNameLength(c3, endC);
- entity := owner.FindEntity(GetHash(c2, c3 - c2));
- Result := (c3^ = ';') and Assigned(entity);
- //nb: increments 'c' only if the entity is found.
- if Result then c := c3 +1 else dec(c);
- end;
- //------------------------------------------------------------------------------
- function ParseQuotedString(var c: PUTF8Char; endC: PUTF8Char;
- out quotStr: UTF8String): Boolean;
- var
- quote: UTF8Char;
- c2: PUTF8Char;
- begin
- quote := c^;
- inc(c);
- c2 := c;
- while (c < endC) and (c^ <> quote) do inc(c);
- Result := (c < endC);
- if not Result then Exit;
- ToUTF8String(c2, c, quotStr);
- inc(c);
- end;
- //------------------------------------------------------------------------------
- function IsNumPending(var c: PUTF8Char;
- endC: PUTF8Char; ignoreComma: Boolean): Boolean;
- var
- c2: PUTF8Char;
- begin
- Result := false;
- //skip white space +/- single comma
- if ignoreComma then
- begin
- while (c < endC) and (c^ <= space) do inc(c);
- if (c^ = ',') then inc(c);
- end;
- while (c < endC) and (c^ <= ' ') do inc(c);
- if (c = endC) then Exit;
- c2 := c;
- if (c2^ = '-') then inc(c2);
- if (c2^ = SvgDecimalSeparator) then inc(c2);
- Result := (c2 < endC) and IsDigit(c2^);
- end;
- //------------------------------------------------------------------------------
- function ParseTransform(const transform: UTF8String): TMatrixD;
- var
- i: integer;
- c, endC: PUTF8Char;
- c2: UTF8Char;
- word: UTF8String;
- values: array[0..5] of double;
- mat: TMatrixD;
- begin
- c := PUTF8Char(transform);
- endC := c + Length(transform);
- Result := IdentityMatrix; //in case of invalid or referenced value
- while ParseNextWord(c, endC, word) do
- begin
- if Length(word) < 5 then Exit;
- if ParseNextChar(c, endC) <> '(' then Exit; //syntax check
- //reset values variables
- for i := 0 to High(values) do values[i] := InvalidD;
- //and since every transform function requires at least one value
- if not ParseNextNum(c, endC, false, values[0]) then Break;
- //now get additional variables
- i := 1;
- while (i < 6) and IsNumPending(c, endC, true) and
- ParseNextNum(c, endC, true, values[i]) do inc(i);
- if ParseNextChar(c, endC) <> ')' then Exit; //syntax check
- mat := IdentityMatrix;
- //scal(e), matr(i)x, tran(s)late, rota(t)e, skew(X), skew(Y)
- case LowerCaseTable[word[5]] of
- 'e' : //scalE
- if not IsValid(values[1]) then
- MatrixScale(mat, values[0]) else
- MatrixScale(mat, values[0], values[1]);
- 'i' : //matrIx
- if IsValid(values[5]) then
- begin
- mat[0,0] := values[0];
- mat[0,1] := values[1];
- mat[1,0] := values[2];
- mat[1,1] := values[3];
- mat[2,0] := values[4];
- mat[2,1] := values[5];
- end;
- 's' : //tranSlateX, tranSlateY & tranSlate
- if Length(word) =10 then
- begin
- c2 := LowerCaseTable[word[10]];
- if c2 = 'x' then
- MatrixTranslate(mat, values[0], 0)
- else if c2 = 'y' then
- MatrixTranslate(mat, 0, values[0]);
- end
- else if IsValid(values[1]) then
- MatrixTranslate(mat, values[0], values[1])
- else
- MatrixTranslate(mat, values[0], 0);
- 't' : //rotaTe
- if IsValid(values[2]) then
- MatrixRotate(mat, PointD(values[1],values[2]), DegToRad(values[0]))
- else
- MatrixRotate(mat, NullPointD, DegToRad(values[0]));
- 'x' : //skewX
- begin
- MatrixSkew(mat, DegToRad(values[0]), 0);
- end;
- 'y' : //skewY
- begin
- MatrixSkew(mat, 0, DegToRad(values[0]));
- end;
- end;
- MatrixMultiply2(mat, Result);
- end;
- end;
- //------------------------------------------------------------------------------
- procedure GetSvgFontInfo(const value: UTF8String; var fontInfo: TSVGFontInfo);
- var
- c, endC: PUTF8Char;
- hash: Cardinal;
- begin
- c := PUTF8Char(value);
- endC := c + Length(value);
- while (c < endC) and SkipBlanks(c, endC) do
- begin
- if c = ';' then
- break
- else if IsNumPending(c, endC, true) then
- ParseNextNum(c, endC, true, fontInfo.size)
- else
- begin
- hash := ParseNextWordHashed(c, endC);
- case hash of
- hSans_045_Serif : fontInfo.family := tfSansSerif;
- hSerif : fontInfo.family := tfSerif;
- hMonospace : fontInfo.family := tfMonospace;
- hBold : fontInfo.weight := 600;
- hItalic : fontInfo.italic := sfsItalic;
- hNormal :
- begin
- fontInfo.weight := 400;
- fontInfo.italic := sfsNone;
- end;
- hStart : fontInfo.align := staLeft;
- hMiddle : fontInfo.align := staCenter;
- hEnd : fontInfo.align := staRight;
- hline_045_through : fontInfo.decoration := fdStrikeThrough;
- hUnderline : fontInfo.decoration := fdUnderline;
- end;
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- function HtmlDecode(const html: UTF8String): UTF8String;
- var
- val, len: integer;
- c,ce,endC: PUTF8Char;
- ch: UTF8Char;
- begin
- len := Length(html);
- SetLength(Result, len*3);
- c := PUTF8Char(html);
- endC := c + len;
- ce := c;
- len := 1;
- while (ce < endC) and (ce^ <> '&') do
- inc(ce);
- while (ce < endC) do
- begin
- if ce > c then
- begin
- Move(c^, Result[len], ce - c);
- inc(len, ce - c);
- end;
- c := ce; inc(ce);
- while (ce < endC) and (ce^ <> ';') do inc(ce);
- if ce = endC then break;
- val := -1; //assume error
- if (c +1)^ = '#' then
- begin
- val := 0;
- //decode unicode value
- if (c +2)^ = 'x' then
- begin
- inc(c, 3);
- while c < ce do
- begin
- ch := c^;
- case ch of
- 'a'..'f':
- val := val * 16 + Ord(ch) - 87;
- 'A'..'F':
- val := val * 16 + Ord(ch) - 55;
- '0'..'9':
- val := val * 16 + Ord(ch) - 48;
- else
- val := -1;
- break;
- end;
- inc(c);
- end;
- end else
- begin
- inc(c, 2);
- while c < ce do
- begin
- val := val * 10 + Ord(c^) - 48;
- inc(c);
- end;
- end;
- end else
- begin
- //decode html entity ...
- case GetHashCaseSensitive(c, ce - c) of
- {$I Img32.SVG.HtmlValues.inc}
- end;
- end;
- //convert unicode value to utf8 chars
- //this saves the overhead of multiple UTF8String<-->string conversions.
- case val of
- 0 .. $7F:
- begin
- result[len] := UTF8Char(val);
- inc(len);
- end;
- $80 .. $7FF:
- begin
- Result[len] := UTF8Char($C0 or (val shr 6));
- Result[len+1] := UTF8Char($80 or (val and $3f));
- inc(len, 2);
- end;
- $800 .. $7FFF:
- begin
- Result[len] := UTF8Char($E0 or (val shr 12));
- Result[len+1] := UTF8Char($80 or ((val shr 6) and $3f));
- Result[len+2] := UTF8Char($80 or (val and $3f));
- inc(len, 3);
- end;
- $10000 .. $10FFFF:
- begin
- Result[len] := UTF8Char($F0 or (val shr 18));
- Result[len+1] := UTF8Char($80 or ((val shr 12) and $3f));
- Result[len+2] := UTF8Char($80 or ((val shr 6) and $3f));
- Result[len+3] := UTF8Char($80 or (val and $3f));
- inc(len, 4);
- end;
- else
- begin
- //ie: error
- Move(c^, Result[len], ce- c +1);
- inc(len, ce - c +1);
- end;
- end;
- inc(ce);
- c := ce;
- while (ce < endC) and (ce^ <> '&') do inc(ce);
- end;
- if (c < endC) and (ce > c) then
- begin
- Move(c^, Result[len], (ce - c));
- inc(len, ce - c);
- end;
- setLength(Result, len -1);
- end;
- //------------------------------------------------------------------------------
- function HexByteToInt(h: UTF8Char): Cardinal; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- case h of
- '0'..'9': Result := Ord(h) - Ord('0');
- 'A'..'F': Result := 10 + Ord(h) - Ord('A');
- 'a'..'f': Result := 10 + Ord(h) - Ord('a');
- else Result := 0;
- end;
- end;
- //------------------------------------------------------------------------------
- function IsFraction(val: double): Boolean; {$IFDEF INLINE} inline; {$ENDIF}
- begin
- Result := (val <> 0) and (Abs(val) < 1);
- end;
- //------------------------------------------------------------------------------
- function UTF8StringToColor32(const value: UTF8String; var color: TColor32): Boolean;
- var
- i, len : integer;
- j : Cardinal;
- clr : TColor32;
- alpha : Byte;
- vals : array[0..3] of double;
- mus : array[0..3] of TUnitType;
- c, endC : PUTF8Char;
- begin
- Result := false;
- len := Length(value);
- if len < 3 then Exit;
- c := PUTF8Char(value);
- if (color = clInvalid) or (color = clCurrent) or (color = clNone32) then
- alpha := 255 else
- alpha := GetAlpha(color);
- if Match(c, 'rgb') then
- begin
- endC := c + len;
- inc(c, 3);
- if (c^ = 'a') then inc(c);
- if (ParseNextChar(c, endC) <> '(') or
- not ParseNextNumEx(c, endC, false, vals[0], mus[0]) or
- not ParseNextNumEx(c, endC, true, vals[1], mus[1]) or
- not ParseNextNumEx(c, endC, true, vals[2], mus[2]) then Exit;
- for i := 0 to 2 do
- if mus[i] = utPercent then
- vals[i] := vals[i] * 255 / 100;
- if (c < endC) and (c^ <> ')') and ParseNextNumEx(c, endC, true, vals[3], mus[3]) then
- alpha := 255 else //stops further alpha adjustment
- vals[3] := 255;
- if ParseNextChar(c, endC) <> ')' then Exit;
- for i := 0 to 3 do if IsFraction(vals[i]) then
- vals[i] := vals[i] * 255;
- color := ClampByte(Integer(Round(vals[3]))) shl 24 +
- ClampByte(Integer(Round(vals[0]))) shl 16 +
- ClampByte(Integer(Round(vals[1]))) shl 8 +
- ClampByte(Integer(Round(vals[2])));
- end
- else if (c^ = '#') then //#RRGGBB or #RGB
- begin
- if (len = 9) then
- begin
- clr := $0;
- alpha := $0;
- for i := 1 to 6 do
- begin
- inc(c);
- clr := clr shl 4 + HexByteToInt(c^);
- end;
- for i := 1 to 2 do
- begin
- inc(c);
- alpha := alpha shl 4 + HexByteToInt(c^);
- end;
- clr := clr or alpha shl 24;
- end
- else if (len = 7) then
- begin
- clr := $0;
- for i := 1 to 6 do
- begin
- inc(c);
- clr := clr shl 4 + HexByteToInt(c^);
- end;
- clr := clr or $FF000000;
- end
- else if (len = 5) then
- begin
- clr := $0;
- for i := 1 to 3 do
- begin
- inc(c);
- j := HexByteToInt(c^);
- clr := clr shl 4 + j;
- clr := clr shl 4 + j;
- end;
- inc(c);
- alpha := HexByteToInt(c^);
- alpha := alpha + alpha shl 4;
- clr := clr or alpha shl 24;
- end
- else if (len = 4) then
- begin
- clr := $0;
- for i := 1 to 3 do
- begin
- inc(c);
- j := HexByteToInt(c^);
- clr := clr shl 4 + j;
- clr := clr shl 4 + j;
- end;
- clr := clr or $FF000000;
- end
- else
- Exit;
- color := clr;
- end else //color name lookup
- begin
- if not ColorConstList.GetColorValue(value, color) then
- Exit;
- end;
- //and in case the opacity has been set before the color
- if (alpha < 255) then
- color := (color and $FFFFFF) or alpha shl 24;
- {$IF DEFINED(ANDROID)}
- color := SwapRedBlue(color);
- {$IFEND}
- Result := true;
- end;
- //------------------------------------------------------------------------------
- function ScaleDashArray(const dblArray: TArrayOfDouble; scale: double): TArrayOfDouble;
- var
- i, len: integer;
- begin
- len := Length(dblArray);
- SetLength(Result, len);
- if len = 0 then Exit;
- for i := 0 to len -1 do
- Result[i] := dblArray[i] * scale;
- if Odd(len) then
- begin
- SetLength(Result, len *2);
- Move(Result[0], Result[len], len * SizeOf(double));
- end;
- end;
- //------------------------------------------------------------------------------
- function PeekNextChar(var c: PUTF8Char; endC: PUTF8Char): UTF8Char;
- begin
- if not SkipBlanks(c, endC) then
- Result := #0 else
- Result := c^;
- end;
- //------------------------------------------------------------------------------
- procedure ParseStyleElementContent(const value: UTF8String;
- stylesList: TClassStylesList);
- var
- len, cap: integer;
- names: array of UTF8String;
- procedure AddName(const name: UTF8String);
- begin
- if len = cap then
- begin
- cap := cap + buffSize;
- SetLength(names, cap);
- end;
- names[len] := name;
- inc(len);
- end;
- var
- i: integer;
- aclassName: UTF8String;
- aStyle: UTF8String;
- c, c2, endC: PUTF8Char;
- begin
- //https://oreillymedia.github.io/Using_SVG/guide/style.html
- stylesList.Clear;
- if value = '' then Exit;
- len := 0; cap := 0;
- c := @value[1];
- endC := c + Length(value);
- c := SkipBlanksEx(c, endC);
- if c >= endC then Exit;
-
- if Match(c, '<![cdata[') then inc(c, 9);
- while True do
- begin
- c := SkipStyleBlanks(c, endC);
- if c >= endC then Break;
- case c^ of
- SvgDecimalSeparator, '#', 'A'..'Z', 'a'..'z': ;
- else break;
- end;
- //get one or more class names for each pending style
- c2 := c;
- c := ParseNameLength(c, endC);
- ToAsciiLowerUTF8String(c2, c, aclassName);
- AddName(aclassName);
- c := SkipStyleBlanks(c, endC);
- if (c < endC) and (c^ = ',') then
- begin
- inc(c);
- Continue;
- end;
- if len = 0 then break;
- //now get the style
- if (c >= endC) or (c^ <> '{') then Break;
- inc(c);
- c2 := c;
- while (c < endC) and (c^ <> '}') do inc(c);
- if (c = endC) then break;
- ToTrimmedUTF8String(c2, c, aStyle);
- if aStyle <> '' then
- begin
- stylesList.Preallocate(len);
- //finally, for each class name add (or append) this style
- for i := 0 to len - 1 do
- stylesList.AddAppendStyle(names[i], aStyle);
- end;
- // Reset the used names array length, so we can reuse it to reduce the amount of SetLength calls
- len := 0;
- inc(c);
- end;
- end;
- //------------------------------------------------------------------------------
- // TXmlEl classes
- //------------------------------------------------------------------------------
- constructor TXmlEl.Create(owner: TSvgParser);
- begin
- {$IFDEF XPLAT_GENERICS}
- attribs := TList<PSvgAttrib>.Create;
- childs := TList<TXmlEl>.Create;
- {$ELSE}
- attribs := TList.Create;
- childs := TList.Create;
- {$ENDIF}
- selfClosed := true;
- Self.owner := owner;
- end;
- //------------------------------------------------------------------------------
- destructor TXmlEl.Destroy;
- begin
- Clear;
- attribs.Free;
- childs.Free;
- inherited;
- end;
- //------------------------------------------------------------------------------
- procedure TXmlEl.Clear;
- var
- i: integer;
- begin
- for i := 0 to attribs.Count -1 do
- DisposeSvgAttrib(PSvgAttrib(attribs.List[i]));
- attribs.Clear;
- for i := 0 to childs.Count -1 do
- TXmlEl(childs[i]).free;
- childs.Clear;
- end;
- //------------------------------------------------------------------------------
- function TXmlEl.ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- var
- style: UTF8String;
- c2: PUTF8Char;
- begin
- c2 := SkipBlanksEx(c, endC);
- c := ParseNameLength(c2, endC);
- ToAsciiLowerUTF8String(c2, c, name);
- //load the class's style (ie undotted style) if found.
- style := owner.classStyles.GetStyle(name);
- if style <> '' then ParseStyleAttribute(style);
- Result := ParseAttributes(c, endC);
- end;
- //------------------------------------------------------------------------------
- class function TXmlEl.ParseAttribName(c: PUTF8Char;
- endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char;
- begin
- Result := SkipBlanksEx(c, endC);
- if Result >= endC then Exit;
- c := Result;
- Result := ParseNameLength(Result, endC);
- ToUTF8String(c, Result, attrib.Name);
- attrib.hash := GetHash(attrib.Name);
- end;
- //------------------------------------------------------------------------------
- class function TXmlEl.ParseAttribValue(c, endC: PUTF8Char;
- attrib: PSvgAttrib): PUTF8Char;
- // Parse: [Whitespaces] "=" [Whitespaces] ("'" | "\"") <string> ("'" | "\"")
- var
- quoteChar: UTF8Char;
- c2: PUTF8Char;
- begin
- Result := endC;
- // ParseNextChar:
- c := SkipBlanksEx(c, endC);
- if (c >= endC) or (c^ <> '=') then Exit;
- inc(c); // '=' parsed
- // ParseQuoteChar:
- c := SkipBlanksEx(c, endC);
- if c >= endC then Exit;
- quoteChar := c^;
- if not (quoteChar in [quote, dquote]) then Exit;
- inc(c); // quote parsed
- //trim leading and trailing spaces in the actual value
- c := SkipBlanksEx(c, endC);
- // find value end
- Result := c;
- while (Result < endC) and (Result^ <> quoteChar) do inc(Result);
- c2 := Result;
- while (c2 > c) and ((c2 -1)^ <= space) do dec(c2);
- ToUTF8String(c, c2, attrib.value, sitPreserve);
- inc(Result); //skip end quote
- end;
- //------------------------------------------------------------------------------
- class function TXmlEl.ParseAttribNameAndValue(c, endC: PUTF8Char; attrib: PSvgAttrib): PUTF8Char;
- begin
- Result := ParseAttribName(c, endC, attrib);
- if (Result < endC) then
- Result := ParseAttribValue(Result, endC, attrib);
- end;
- //------------------------------------------------------------------------------
- function TXmlEl.ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- var
- i: integer;
- attrib, styleAttrib, classAttrib, idAttrib: PSvgAttrib;
- classes: UTF8Strings;
- ansi: UTF8String;
- begin
- Result := false;
- styleAttrib := nil; classAttrib := nil; idAttrib := nil;
- while SkipBlanks(c, endC) do
- begin
- case c^ of
- '/', '?':
- begin
- inc(c);
- if (c^ <> '>') then Exit; //error
- selfClosed := true;
- inc(c);
- Result := true;
- break;
- end;
- '>':
- begin
- inc(c);
- Result := true;
- break;
- end;
- 'x':
- if Match(c, 'xml:') then
- begin
- inc(c, 4); //ignore xml: prefixes
- end;
- end;
- attrib := NewSvgAttrib();
- c := ParseAttribNameAndValue(c, endC, attrib);
- if c >= endC then
- begin
- DisposeSvgAttrib(attrib);
- Exit;
- end;
- attribs.Add(attrib);
- case attrib.hash of
- hId : idAttrib := attrib;
- hClass : classAttrib := attrib;
- hStyle : styleAttrib := attrib;
- end;
- end;
- if assigned(classAttrib) then
- with classAttrib^ do
- begin
- //get the 'dotted' classname(s)
- classes := Split(value);
- for i := 0 to High(classes) do
- begin
- ansi := SvgDecimalSeparator + classes[i];
- //get the style definition
- ansi := owner.classStyles.GetStyle(ansi);
- if ansi <> '' then ParseStyleAttribute(ansi);
- end;
- end;
- if assigned(styleAttrib) then
- ParseStyleAttribute(styleAttrib.value);
- if assigned(idAttrib) then
- begin
- //get the 'hashed' classname
- ansi := '#' + idAttrib.value;
- //get the style definition
- ansi := owner.classStyles.GetStyle(ansi);
- if ansi <> '' then ParseStyleAttribute(ansi);
- end;
-
- end;
- //------------------------------------------------------------------------------
- procedure TXmlEl.ParseStyleAttribute(const style: UTF8String);
- var
- styleName, styleVal: UTF8String;
- c, c2, endC: PUTF8Char;
- attrib: PSvgAttrib;
- begin
- //there are 4 ways to load styles (in ascending precedence) -
- //1. a class element style (called during element contruction)
- //2. a non-element class style (called via a class attribute)
- //3. an inline style (called via a style attribute)
- //4. an id specific class style
- c := PUTF8Char(style);
- endC := c + Length(style);
- while True do
- begin
- c := SkipStyleBlanks(c, endC);
- if c >= endC then Break;
- c2 := c;
- c := ParseStyleNameLen(c, endC);
- ToUTF8String(c2, c, styleName);
- if styleName = '' then Break;
- // ParseNextChar
- c := SkipStyleBlanks(c, endC);
- if (c >= endC) or (c^ <> ':') then Break; //syntax check
- inc(c);
- c := SkipBlanksEx(c, endC);
- if c >= endC then Break;
- c2 := c;
- inc(c);
- while (c < endC) and (c^ <> ';') do inc(c);
- ToTrimmedUTF8String(c2, c, styleVal);
- inc(c);
- attrib := NewSvgAttrib();
- attrib.name := styleName;
- attrib.value := styleVal;
- attrib.hash := GetHash(attrib.name);
- attribs.Add(attrib);
- end;
- end;
- //------------------------------------------------------------------------------
- function TXmlEl.GetAttribCount: integer;
- begin
- Result := attribs.Count;
- end;
- //------------------------------------------------------------------------------
- function TXmlEl.GetAttrib(index: integer): PSvgAttrib;
- begin
- Result := PSvgAttrib(attribs[index]);
- end;
- //------------------------------------------------------------------------------
- function IsTextAreaTbreak(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- const
- // https://www.w3.org/TR/SVGTiny12/text.html#tbreakElement
- tbreak: PUTF8Char = '<tbreak/>';
- begin
- Result := (c + 9 < endC) and CompareMem(c, tbreak, 9);
- if Result then inc(c, 8);
- end;
- //------------------------------------------------------------------------------
- function TXmlEl.ParseContent(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- var
- child : TSvgXmlEl;
- entity : PSvgAttrib;
- c2, cc : PUTF8Char;
- tmpC, tmpEndC : PUTF8Char;
- begin
- Result := false;
- // note: don't trim spaces at the start of text content.
- // Text space trimming will be done later IF and when required.
- while (hash = hText) or (hash = hTSpan) or
- (hash = hTextArea) or SkipBlanks(c, endC) do
- begin
- if (c^ = '<') then
- begin
- inc(c);
- case c^ of
- '!':
- begin
- cc := c;
- if Match(cc, '!--') then //start comment
- begin
- inc(cc, 3);
- while (cc < endC) and ((cc^ <> '-') or
- not Match(cc, '-->')) do inc(cc); //end comment
- inc(cc, 3);
- end else
- begin
- //it's very likely <![CDATA[
- c2 := cc - 1;
- if Match(cc, '![cdata[') then
- begin
- while (cc < endC) and ((cc^ <> ']') or not Match(cc, ']]>')) do
- inc(cc);
- ToUTF8String(c2, cc, text);
- inc(cc, 3);
- if (hash = hStyle) then
- ParseStyleElementContent(text, owner.classStyles);
- end else
- begin
- while (cc < endC) and (cc^ <> '<') do inc(cc);
- ToUTF8String(c2, cc, text);
- end;
- end;
- c := cc;
- end;
- '/', '?':
- begin
- //element closing tag
- cc := c;
- inc(cc);
- if Match(cc, name) then
- begin
- inc(cc, Length(name));
- //very rarely there's a space before '>'
- cc := SkipBlanksEx(cc, endC);
- Result := cc^ = '>';
- inc(cc);
- end;
- c := cc;
- Exit;
- end;
- else
- begin
- //starting a new element
- child := TSvgXmlEl.Create(owner);
- childs.Add(child);
- if not child.ParseHeader(c, endC) then break;
- if not child.selfClosed then
- child.ParseContent(c, endC);
- end;
- end;
- end
- else if c^ = '>' then
- begin
- break; //oops! something's wrong
- end
- else if (c^ = '&') and IsKnownEntity(owner, c, endC, entity) then
- begin
- tmpC := PUTF8Char(entity.value);
- tmpEndC := tmpC + Length(entity.value);
- ParseContent(tmpC, tmpEndC);
- end
- else if (hash = hTSpan) or (hash = hText) or (hash = hTextPath) then
- begin
- // assume this is text content, and because text can also be mixed
- // with any number of nested <tspan> elements, always put text
- // content inside a pseudo 'self closed' <tspan> element
- cc := c;
- while (cc < endC) and (cc^ <> '<') do inc(cc);
- child := TSvgXmlEl.Create(owner);
- child.name := 'tspan';
- child.hash := GetHash('tspan');
- child.selfClosed := true; ////////////////////// :)))
- childs.Add(child);
- ToUTF8String(c, cc, child.text, sitPreserve);
- c := cc;
- end
- else if (hash = hTextArea) then
- begin
- // also assume this is text content, but don't create
- // pseudo <tspan> elements inside <textarea> elements
- cc := c;
- while (cc < endC) and
- ((cc^ <> '<') or IsTextAreaTbreak(cc, endC)) do inc(cc);
- ToUTF8String(c, cc, text, sitPreserve);
- c := cc;
- end else
- begin
- cc := c;
- while (cc < endC) and (cc^ <> '<') do inc(cc);
- ToUTF8String(c, cc, text);
- c := cc;
- //if <style> element then load styles into owner.classStyles
- if (hash = hStyle) then
- ParseStyleElementContent(text, owner.classStyles);
- end;
- end;
- end;
- //------------------------------------------------------------------------------
- // TDocTypeEl
- //------------------------------------------------------------------------------
- function TDocTypeEl.SkipWord(c, endC: PUTF8Char): PUTF8Char;
- begin
- while (c < endC) and (c^ > space) do inc(c);
- inc(c);
- Result := c;
- end;
- //------------------------------------------------------------------------------
- function TDocTypeEl.ParseEntities(var c, endC: PUTF8Char): Boolean;
- var
- attrib: PSvgAttrib;
- begin
- attrib := nil;
- inc(c); //skip opening '['
- while (c < endC) and SkipBlanks(c, endC) do
- begin
- if (c^ = ']') then break
- else if not Match(c, '<!entity') then
- begin
- while c^ > space do inc(c); //skip word.
- Continue;
- end;
- inc(c, 8);
- attrib := NewSvgAttrib();
- c := ParseAttribName(c, endC, attrib);
- if c >= endC then break;
- SkipBlanks(c, endC);
- if not IsQuoteChar(c^) then break;
- if not ParseQuotedString(c, endC, attrib.value) then break;
- attribs.Add(attrib);
- attrib := nil;
- SkipBlanks(c, endC);
- if c^ <> '>' then break;
- inc(c); //skip entity's trailing '>'
- end;
- if Assigned(attrib) then DisposeSvgAttrib(attrib);
- Result := (c < endC) and (c^ = ']');
- inc(c);
- end;
- //------------------------------------------------------------------------------
- function TDocTypeEl.ParseAttributes(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- var
- dummy : UTF8String;
- begin
- while SkipBlanks(c, endC) do
- begin
- //we're currently only interested in ENTITY declarations
- case c^ of
- '[': ParseEntities(c, endC);
- '"', '''': ParseQuotedString(c, endC, dummy);
- '>': break;
- else c := SkipWord(c, endC);
- end;
- end;
- Result := (c < endC) and (c^ = '>');
- inc(c);
- end;
- //------------------------------------------------------------------------------
- // TSvgTreeEl
- //------------------------------------------------------------------------------
- constructor TSvgXmlEl.Create(owner: TSvgParser);
- begin
- inherited Create(owner);
- selfClosed := false;
- end;
- //------------------------------------------------------------------------------
- procedure TSvgXmlEl.Clear;
- var
- i: integer;
- begin
- for i := 0 to childs.Count -1 do
- TSvgXmlEl(childs[i]).free;
- childs.Clear;
- inherited;
- end;
- //------------------------------------------------------------------------------
- function TSvgXmlEl.ParseHeader(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- begin
- Result := inherited ParseHeader(c, endC);
- if Result then hash := GetHash(name);
- end;
- //------------------------------------------------------------------------------
- //function TSvgTreeEl.ParseContent(var c: PUTF8Char; endC: PUTF8Char): Boolean;
- constructor TSvgParser.Create;
- begin
- classStyles := TClassStylesList.Create;
- svgStream := TMemoryStream.Create;
- xmlHeader := TXmlEl.Create(Self);
- docType := TDocTypeEl.Create(Self);
- svgTree := nil;
- end;
- //------------------------------------------------------------------------------
- destructor TSvgParser.Destroy;
- begin
- Clear;
- svgStream.Free;
- xmlHeader.Free;
- docType.Free;
- classStyles.Free;
- end;
- //------------------------------------------------------------------------------
- procedure TSvgParser.Clear;
- begin
- classStyles.Clear;
- svgStream.Clear;
- xmlHeader.Clear;
- docType.Clear;
- FreeAndNil(svgTree);
- end;
- //------------------------------------------------------------------------------
- function TSvgParser.FindEntity(hash: Cardinal): PSvgAttrib;
- var
- i: integer;
- begin
- //there are usually so few, that there seems little point sorting etc.
- for i := 0 to docType.attribs.Count -1 do
- begin
- Result := PSvgAttrib(docType.attribs.List[i]);
- if Result.hash = hash then Exit;
- end;
- Result := nil;
- end;
- //------------------------------------------------------------------------------
- function TSvgParser.LoadFromFile(const filename: string): Boolean;
- var
- fs: TFileStream;
- begin
- Result := false;
- if not FileExists(filename) then Exit;
- fs := TFileStream.Create(filename, fmOpenRead or fmShareDenyNone);
- try
- Result := LoadFromStream(fs);
- finally
- fs.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure ConvertUnicodeToUtf8(memStream: TMemoryStream);
- var
- i, len: LongInt;
- encoding: TSvgEncoding;
- s: UnicodeString;
- wc: PWord;
- utf8: UTF8String;
- begin
- memStream.Position := 0;
- encoding := GetXmlEncoding(memStream.Memory, memStream.Size);
- case encoding of
- eUnicodeLE, eUnicodeBE: ;
- else Exit;
- end;
- SetLength(s, memStream.Size div 2);
- Move(memStream.Memory^, s[1], memStream.Size);
- if encoding = eUnicodeBE then
- begin
- wc := @s[1];
- for i := 1 to Length(s) do
- begin
- wc^ := Swap(wc^);
- inc(wc);
- end;
- end;
- utf8 := UTF8Encode(s);
- len := Length(utf8);
- memStream.SetSize(len);
- Move(utf8[1], memStream.Memory^, len);
- end;
- //------------------------------------------------------------------------------
- function TSvgParser.LoadFromStream(stream: TStream): Boolean;
- begin
- Clear;
- Result := true;
- try
- svgStream.LoadFromStream(stream);
- // very few SVG files are unicode encoded, almost all are Utf8
- // so it's more efficient to parse them all as Utf8 encoded files
- ConvertUnicodeToUtf8(svgStream);
- ParseUtf8Stream;
- except
- Result := false;
- end;
- end;
- //------------------------------------------------------------------------------
- function TSvgParser.LoadFromString(const str: string): Boolean;
- var
- ss: TStringStream;
- begin
- {$IFDEF UNICODE}
- ss := TStringStream.Create(str, TEncoding.UTF8);
- {$ELSE}
- ss := TStringStream.Create(UTF8Encode(str));
- {$ENDIF}
- try
- Result := LoadFromStream(ss);
- finally
- ss.Free;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TSvgParser.ParseUtf8Stream;
- var
- c, endC: PUTF8Char;
- begin
- c := svgStream.Memory;
- endC := c + svgStream.Size;
- SkipBlanks(c, endC);
- if Match(c, '<?xml') then
- begin
- inc(c, 2); //todo: accommodate space after '<' eg using sMatchEl function
- if not xmlHeader.ParseHeader(c, endC) then Exit;
- SkipBlanks(c, endC);
- end;
- if Match(c, '<!doctype') then
- begin
- inc(c, 2);
- if not docType.ParseHeader(c, endC) then Exit;
- end;
- while SkipBlanks(c, endC) do
- begin
- if (c^ = '<') and Match(c, '<svg') then
- begin
- inc(c);
- svgTree := TSvgXmlEl.Create(self);
- if svgTree.ParseHeader(c, endC) and
- not svgTree.selfClosed then
- svgTree.ParseContent(c, endC);
- break;
- end;
- inc(c);
- end;
- end;
- //------------------------------------------------------------------------------
- // Miscellaneous functions
- //------------------------------------------------------------------------------
- function DecodeUtf8ToUnicode(const utf8: UTF8String): UnicodeString;
- var
- i,j, len: Integer;
- c, cp: Cardinal;
- codePoints: TArrayOfCardinal;
- begin
- Result := '';
- if utf8 = '' then Exit;
- len := Length(utf8);
- // first decode utf8String to codepoints
- SetLength(codePoints, len);
- i := 1;
- j := 0;
- while i <= len do
- begin
- c := Ord(utf8[i]);
- if c and $80 = 0 then // c < 128
- begin
- codePoints[j] := c;
- inc(i); inc(j);
- end
- else if c and $E0 = $C0 then
- begin
- if i = len then break;
- codePoints[j] := (c and $1F) shl 6 + (Ord(utf8[i+1]) and $3F);
- inc(i, 2); inc(j);
- end
- else if c and $F0 = $E0 then
- begin
- if i > len - 2 then break;
- codePoints[j] := (c and $F) shl 12 +
- ((Ord(utf8[i+1]) and $3F) shl 6) + ((Ord(utf8[i+2]) and $3F));
- inc(i, 3); inc(j);
- end else
- begin
- if (i > len - 3) or (c shr 3 <> $1E) then break;
- codePoints[j] := (c and $7) shl 18 + ((Ord(utf8[i+1]) and $3F) shl 12) +
- ((Ord(utf8[i+2]) and $3F) shl 6) + (Ord(utf8[i+3]) and $3F);
- inc(i, 4); inc(j);
- end;
- end;
- len := j; // there are now 'j' valid codepoints
- j := 0;
- // make room in the result for surrogate paired chars, and
- // convert codepoints into the result (a Utf16 string)
- SetLength(Result, len *2);
- for i := 0 to len -1 do
- begin
- inc(j);
- cp := codePoints[i];
- if (cp < $D7FF) or (cp = $E000) or (cp = $FFFF) then
- begin
- Result[j] := WideChar(cp);
- end else if (cp > $FFFF) and (cp < $110000) then
- begin
- Dec(cp, $10000);
- Result[j] := WideChar($D800 + (cp shr 10));
- inc(j);
- Result[j] := WideChar($DC00 + (cp and $3FF));
- end;
- end;
- SetLength(Result, j);
- end;
- //------------------------------------------------------------------------------
- // TValue
- //------------------------------------------------------------------------------
- function ConvertValue(const value: TValue; scale: double): double;
- const
- mm = 96 / 25.4;
- cm = 96 / 2.54;
- rad = 180 / PI;
- pt = 4 / 3;
- begin
- //https://oreillymedia.github.io/Using_SVG/guide/units.html
- //todo: still lots of units to support (eg times for animation)
- with value do
- {if not IsValid or (rawVal = 0) then // already checked by TValue.GetValue, the only function calling this code
- Result := 0
- else}
- case value.unitType of
- utNumber:
- Result := rawVal;
- utPercent:
- Result := rawVal * 0.01 * scale;
- utRadian:
- Result := rawVal * rad;
- utInch:
- Result := rawVal * 96;
- utCm:
- Result := rawVal * cm;
- utMm:
- Result := rawVal * mm;
- utEm:
- if scale <= 0 then
- Result := rawVal * 16 else
- Result := rawVal * scale;
- utEx:
- if scale <= 0 then
- Result := rawVal * 8 else
- Result := rawVal * scale * 0.5;
- utPica:
- Result := rawVal * 16;
- utPt:
- Result := rawVal * pt;
- else
- Result := rawVal;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TValue.Init;
- begin
- rawVal := InvalidD;
- unitType := utNumber;
- end;
- //------------------------------------------------------------------------------
- procedure TValue.SetValue(val: double; unitTyp: TUnitType);
- begin
- rawVal := val;
- unitType := unitTyp;
- end;
- //------------------------------------------------------------------------------
- function TValue.GetValue(relSize: double; assumeRelValBelow: Double): double;
- begin
- if not IsValid or (rawVal = 0) then
- Result := 0
- else if IsRelativeValue(assumeRelValBelow) then
- Result := rawVal * relSize
- else
- Result := ConvertValue(self, relSize);
- end;
- //------------------------------------------------------------------------------
- function TValue.GetValueXY(const relSize: TRectD; assumeRelValBelow: Double): double;
- begin
- //https://www.w3.org/TR/SVG11/coords.html#Units
- Result := GetValue(Hypot(relSize.Width, relSize.Height)/sqrt2, assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValue.IsRelativeValue(assumeRelValBelow: double): Boolean;
- begin
- Result := (unitType = utNumber) and (Abs(rawVal) <= assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValue.IsValid: Boolean;
- begin
- Result := (unitType <> utUnknown) and Img32.Vector.IsValid(rawVal);
- end;
- //------------------------------------------------------------------------------
- function TValue.HasFontUnits: Boolean;
- begin
- case unitType of
- utEm, utEx: Result := true;
- else Result := False;
- end;
- end;
- //------------------------------------------------------------------------------
- function TValue.HasAngleUnits: Boolean;
- begin
- case unitType of
- utDegree, utRadian: Result := true;
- else Result := False;
- end;
- end;
- //------------------------------------------------------------------------------
- // TValuePt
- //------------------------------------------------------------------------------
- procedure TValuePt.Init;
- begin
- X.Init;
- Y.Init;
- end;
- //------------------------------------------------------------------------------
- function TValuePt.GetPoint(const relSize: double; assumeRelValBelow: Double): TPointD;
- begin
- Result.X := X.GetValue(relSize, assumeRelValBelow);
- Result.Y := Y.GetValue(relSize, assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValuePt.GetPoint(const relSize: TRectD; assumeRelValBelow: Double): TPointD;
- begin
- Result.X := X.GetValue(relSize.Width, assumeRelValBelow);
- Result.Y := Y.GetValue(relSize.Height, assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValuePt.IsValid: Boolean;
- begin
- Result := X.IsValid and Y.IsValid;
- end;
- //------------------------------------------------------------------------------
- // TValueRec
- //------------------------------------------------------------------------------
- procedure TValueRecWH.Init;
- begin
- left.Init;
- top.Init;
- width.Init;
- height.Init;
- end;
- //------------------------------------------------------------------------------
- function TValueRecWH.GetRectD(const relSize: TRectD; assumeRelValBelow: Double): TRectD;
- begin
- with GetRectWH(relSize, assumeRelValBelow) do
- begin
- Result.Left :=Left;
- Result.Top := Top;
- Result.Right := Left + Width;
- Result.Bottom := Top + Height;
- end;
- end;
- //------------------------------------------------------------------------------
- function TValueRecWH.GetRectD(relSize: double; assumeRelValBelow: Double): TRectD;
- begin
- if not left.IsValid then
- Result.Left := 0 else
- Result.Left := left.GetValue(relSize, assumeRelValBelow);
- if not top.IsValid then
- Result.Top := 0 else
- Result.Top := top.GetValue(relSize, assumeRelValBelow);
- Result.Right := Result.Left + width.GetValue(relSize, assumeRelValBelow);
- Result.Bottom := Result.Top + height.GetValue(relSize, assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValueRecWH.GetRectD(relSizeX, relSizeY: double; assumeRelValBelow: Double): TRectD;
- begin
- if not left.IsValid then
- Result.Left := 0 else
- Result.Left := left.GetValue(relSizeX, assumeRelValBelow);
- if not top.IsValid then
- Result.Top := 0 else
- Result.Top := top.GetValue(relSizeY, assumeRelValBelow);
- Result.Right := Result.Left + width.GetValue(relSizeX, assumeRelValBelow);
- Result.Bottom := Result.Top + height.GetValue(relSizeY, assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValueRecWH.GetRectWH(const relSize: TRectD; assumeRelValBelow: Double): TRectWH;
- begin
- if not left.IsValid then
- Result.Left := 0 else
- Result.Left := left.GetValue(relSize.Width, assumeRelValBelow);
- if not top.IsValid then
- Result.Top := 0 else
- Result.Top := top.GetValue(relSize.Height, assumeRelValBelow);
- Result.Width := width.GetValue(relSize.Width, assumeRelValBelow);
- Result.Height := height.GetValue(relSize.Height, assumeRelValBelow);
- end;
- //------------------------------------------------------------------------------
- function TValueRecWH.IsValid: Boolean;
- begin
- Result := width.IsValid and height.IsValid;
- end;
- //------------------------------------------------------------------------------
- function TValueRecWH.IsEmpty: Boolean;
- begin
- Result := (width.rawVal <= 0) or (height.rawVal <= 0);
- end;
- //------------------------------------------------------------------------------
- // TClassStylesList
- //------------------------------------------------------------------------------
- procedure TClassStylesList.Grow(NewCapacity: Integer);
- var
- Len, I: Integer;
- Index: Integer;
- begin
- Len := Length(FItems);
- if NewCapacity < 0 then
- begin
- if Len < 5 then
- Len := 5
- else
- Len := Len * 2;
- end
- else if NewCapacity <= Len then
- Exit
- else
- Len := NewCapacity;
- SetLength(FItems, Len);
- FMod := Cardinal(Len);
- if not Odd(FMod) then
- Inc(FMod);
- SetLengthUninit(FBuckets, FMod);
- FillChar(FBuckets[0], FMod * SizeOf(FBuckets[0]), $FF);
- // Rehash
- for I := 0 to FCount - 1 do
- begin
- Index := (FItems[I].Hash and $7FFFFFFF) mod FMod;
- FItems[I].Next := FBuckets[Index];
- FBuckets[Index] := I;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TClassStylesList.Preallocate(AdditionalItemCount: Integer);
- begin
- if AdditionalItemCount > 2 then
- Grow(Length(FItems) + AdditionalItemCount);
- end;
- //------------------------------------------------------------------------------
- function TClassStylesList.FindItemIndex(const Name: UTF8String): Integer;
- begin
- Result := -1;
- FNameHash := GetHash(Name);
- if FMod <> 0 then
- begin
- Result := FBuckets[(FNameHash and $7FFFFFFF) mod FMod];
- while (Result <> -1) and
- ((FItems[Result].Hash <> FNameHash) or
- not IsSameUTF8String(FItems[Result].Name, Name)) do
- Result := FItems[Result].Next;
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TClassStylesList.AddAppendStyle(const Name, Style: UTF8String);
- var
- Index: Integer;
- Item: PClassStyleListItem;
- Bucket: PInteger;
- begin
- Index := FindItemIndex(Name);
- if Index <> -1 then
- begin
- Item := @FItems[Index];
- if (Item.Style <> '') and (Item.Style[Length(Item.Style)] <> ';') then
- Item.Style := Item.Style + ';' + Style
- else
- Item.Style := Item.Style + Style;
- end
- else
- begin
- if FCount = Length(FItems) then
- Grow;
- Index := FCount;
- Inc(FCount);
- Bucket := @FBuckets[(FNameHash and $7FFFFFFF) mod FMod];
- Item := @FItems[Index];
- Item.Next := Bucket^;
- Item.Hash := FNameHash;
- Item.Name := Name;
- Item.Style := style;
- Bucket^ := Index;
- end;
- end;
- //------------------------------------------------------------------------------
- function TClassStylesList.GetStyle(const Name: UTF8String): UTF8String;
- var
- Index: Integer;
- begin
- if FCount = 0 then
- Result := ''
- else
- begin
- Index := FindItemIndex(Name);
- if Index <> -1 then
- Result := FItems[Index].Style
- else
- Result := '';
- end;
- end;
- //------------------------------------------------------------------------------
- procedure TClassStylesList.Clear;
- begin
- FCount := 0;
- FMod := 0;
- FItems := nil;
- FBuckets := nil;
- end;
- //------------------------------------------------------------------------------
- // TColorConstList
- //------------------------------------------------------------------------------
- constructor TColorConstList.Create(Colors: PColorConst; Count: Integer);
- var
- I: Integer;
- Bucket: PPColorConstMapItem;
- Item: PColorConstMapItem;
- begin
- inherited Create;
- FCount := Count;
- SetLength(FItems, FCount);
- FMod := FCount * 2 + 1; // gives us 3 color constants as max. bucket depth
- SetLength(FBuckets, FMod);
- // Initialize FItems[] and fill the buckets
- for I := 0 to Count - 1 do
- begin
- Item := @FItems[I];
- Item.Data := Colors; // link the constant to the ColorConstMapItem
- Inc(Colors);
- Item.Hash := GetHash(Item.Data.ColorName); // case-insensitive
- Bucket := @FBuckets[(Cardinal(Item.Hash) and $7FFFFFFF) mod FMod];
- Item.Next := Bucket^;
- Bucket^ := Item;
- end;
- end;
- //------------------------------------------------------------------------------
- function TColorConstList.GetColorValue(const ColorName: UTF8String; var Color: TColor32): Boolean;
- var
- Hash: Cardinal;
- Item: PColorConstMapItem;
- begin
- Hash := GetHash(ColorName);
- Item := FBuckets[(Cardinal(Hash) and $7FFFFFFF) mod FMod];
- while (Item <> nil) and
- not IsSameAsciiUTF8String(Item.Data.ColorName, ColorName) do
- Item := Item.Next;
- if Item <> nil then
- begin
- Color := Item.Data.ColorValue;
- Result := True;
- end
- else
- Result := False;
- end;
- //------------------------------------------------------------------------------
- // initialization procedures
- //------------------------------------------------------------------------------
- procedure MakeLowerCaseTable;
- var
- i: UTF8Char;
- begin
- for i:= #0 to #$40 do LowerCaseTable[i]:= i;
- for i:= #$41 to #$5A do LowerCaseTable[i]:= UTF8Char(Ord(i) + $20);
- for i:= #$5B to #$FF do LowerCaseTable[i]:= i;
- end;
- //------------------------------------------------------------------------------
- procedure MakeColorConstList;
- {$I Img32.SVG.HtmlColorConsts.inc}
- begin
- ColorConstList := TColorConstList.Create(@ColorConsts, Length(ColorConsts));
- end;
- //------------------------------------------------------------------------------
- procedure CleanupColorConstList;
- begin
- FreeAndNil(ColorConstList);
- end;
- //------------------------------------------------------------------------------
- //------------------------------------------------------------------------------
- initialization
- MakeLowerCaseTable;
- MakeColorConstList;
- finalization
- CleanupColorConstList;
- end.
|