FastReport_2022_VCL/Source/frxHTMLStyleUn.pas

3637 lines
105 KiB
ObjectPascal
Raw Normal View History

2024-01-01 16:13:08 +01:00
{
Version 11.7
Copyright (c) 1995-2008 by L. David Baldwin,
Copyright (c) 2008-2016 by HtmlViewer Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Note that the source modules HTMLGIF1.PAS and DITHERUNIT.PAS
are covered by separate copyright notices located in those modules.
}
{$I frxHTMLCons.inc}
unit frxHTMLStyleUn;
interface
uses
{$ifdef LCL}
LclIntf, LclType, frxHTMLMisc,
{$else}
Windows,
{$endif}
Classes, Graphics, SysUtils, Math, Forms, Contnrs, Variants,
//
frxHTMLBuffer,
frxHTMLFonts,
frxHTMLGlobals,
frxHTMLSymb,
frxHTMLUn2,
frxHTMLStyleTypes;
const
CurColor_Val = 'currentColor';
CurColorStr = 'currentcolor';
varInt = [varInteger, varByte, varSmallInt, varShortInt, varWord, varLongWord, varInt64];
varFloat = [varSingle, varDouble, varCurrency];
varNum = varInt + varFloat;
CrLf = #$D#$A;
//BG, 16.09.2010: CSS2.2: same sizes like html font size:
const
FontConvBase: array[1..7] of Double = (8.0, 10.0, 12.0, 14.0, 18.0, 24.0, 36.0);
PreFontConvBase: array[1..7] of Double = (7.0, 8.0, 10.0, 12.0, 15.0, 20.0, 30.0);
var
FontConv: array[1..7] of Double;
PreFontConv: array[1..7] of Double;
type
// Notice: in ThtPropertyIndex order of rectangle properties required:
//
// Top, Right, Bottom, Left.
//
// Some code relies on this order.
//
ThtPropertyIndex = (
FontFamily, FontSize, FontStyle, FontWeight,
TextAlign, TextDirection, TextDecoration, LetterSpacing, Color,
// the below properties are in MarginArrays
BackgroundColor, BackgroundImage, BackgroundPosition, BackgroundRepeat, BackgroundAttachment,
piMinHeight, piMinWidth, piMaxHeight, piMaxWidth, BoxSizing,
MarginTop, MarginRight, MarginBottom, MarginLeft,
PaddingTop, PaddingRight, PaddingBottom, PaddingLeft,
BorderTopWidth, BorderRightWidth, BorderBottomWidth, BorderLeftWidth,
BorderTopColor, BorderRightColor, BorderBottomColor, BorderLeftColor,
BorderTopStyle, BorderRightStyle, BorderBottomStyle, BorderLeftStyle,
piWidth, piHeight, piTop, piRight, piBottom, piLeft,
BorderSpacingHorz, BorderSpacingVert, //These two are internal
// the above properties are in MarginArrays
Visibility, LineHeight, VerticalAlign, Position, ZIndex,
ListStyleType, ListStyleImage, Float, Clear, TextIndent,
PageBreakBefore, PageBreakAfter, PageBreakInside, TextTransform,
WordWrap, FontVariant, BorderCollapse, OverFlow, piDisplay, piEmptyCells,
piWhiteSpace,
// the below properties are short hands
MarginX, PaddingX, BorderWidthX, BorderX,
BorderTX, BorderRX, BorderBX, BorderLX,
FontX, BackgroundX, ListStyleX, BorderColorX,
BorderStyleX,
// the above properties are short hands
//the following have multiple values sometimes
BorderSpacing
//the above have multiple values sometimes
);
TShortHand = MarginX..BorderSpacing;//BorderStyleX;
ThtPropIndices = FontFamily..piWhiteSpace;
ThtPropertyArray = array [ThtPropIndices] of Variant;
ThtPropIndexSet = Set of ThtPropIndices;
ThtMarginIndices = BackgroundColor..BorderSpacingVert; //piLeft;
ThtVMarginArray = array [ThtMarginIndices] of Variant;
ThtMarginArray = array [ThtMarginIndices] of Integer;
const
PropWords: array [ThtPropertyIndex] of ThtString = (
'font-family', 'font-size', 'font-style', 'font-weight',
'text-align', 'dir', 'text-decoration', 'letter-spacing', 'color',
// these properties are in MarginArrays
'background-color', 'background-image', 'background-position', 'background-repeat', 'background-attachment',
'min-height', 'min-width', 'max-height', 'max-width', 'box-sizing',
'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',
'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',
'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',
'width', 'height', 'top', 'right', 'bottom', 'left',
'thv-border-spacing-horz', 'thv-border-spacing-vert', //These two are for internal use only
'visibility', 'line-height', 'vertical-align', 'position', 'z-index',
'list-style-type', 'list-style-image', 'float', 'clear', 'text-indent',
'page-break-before', 'page-break-after', 'page-break-inside', 'text-transform',
'word-wrap', 'font-variant', 'border-collapse', 'overflow', 'display', 'empty-cells',
'white-space',
// short hand names
'margin', 'padding', 'border-width', 'border',
'border-top', 'border-right', 'border-bottom', 'border-left',
'font', 'background', 'list-style', 'border-color',
'border-style',
//multiple values
'border-spacing'
);
type
TfrxHtStyleList = class;
TfrxHtPropStack = class;
TfrxHtProperties = class
private
FDefPointSize : Double;
PropStack: TfrxHtPropStack; // owner
TheFont: TfrxHtFont;
InLink: Boolean;
DefFontname: ThtString;
procedure AssignCodePage(const CP: Integer);
procedure CalcLinkFontInfo(Styles: TfrxHtStyleList; I: Integer);
procedure GetSingleFontInfo(var Font: TfrxHtFontInfo);
function GetFont: TfrxHtFont;
function GetEmSize: Integer;
function GetExSize: Integer;
public
PropSym: TElemSymb;
PropTag, PropClass, PropID, PropPseudo, PropTitle: ThtString;
PropStyle: TfrxHtProperties;
FontBG: TColor;
FCharSet: TFontCharSet;
FCodePage: Integer;
Props: ThtPropertyArray;
Originals: array[ThtPropIndices] of Boolean;
Important: array[ThtPropIndices] of Boolean;
FIObj: TFontInfoObj;
ID: Integer;
constructor Create; overload; // for use in style list only
constructor Create(APropStack: TfrxHtPropStack); overload; // for use in property stack
constructor CreateCopy(ASource: TfrxHtProperties);
destructor Destroy; override;
function Clone: TfrxHtProperties; virtual;
function BorderStyleNotBlank: Boolean;
function Collapse: Boolean;
function GetBackgroundColor: TColor;
function GetBackgroundImage(var Image: ThtString): Boolean;
function GetBorderStyle(Index: ThtPropIndices; var BorderStyle: ThtBorderStyle): Boolean;
function GetClear(var Clr: ThtClearStyle): Boolean;
function GetDisplay: ThtDisplayStyle; //BG, 15.09.2009
function GetFloat(var Align: ThtAlignmentStyle): Boolean;
function GetFontVariant: ThtString;
function GetLineHeight(NewHeight: Integer): Integer;
function GetListStyleImage: ThtString;
function GetListStyleType: ThtBulletStyle;
function GetOriginalForegroundColor: TColor;
function GetPosition: ThtBoxPositionStyle;
function GetTextIndent(out PC: Boolean): Integer;
function GetTextTransform: ThtTextTransformStyle;
function GetVertAlign(var Align: ThtAlignmentStyle): Boolean;
function GetVisibility: ThtVisibilityStyle;
function GetZIndex: Integer;
function HasBorderStyle: Boolean;
function HasBorderWidth: Boolean;
function IsOverflowHidden: Boolean;
function ShowEmptyCells: Boolean;
procedure AddPropertyByIndex(Index: ThtPropIndices; PropValue: ThtString; IsImportant: Boolean);
procedure AddPropertyByName(const PropName, PropValue: ThtString; IsImportant: Boolean);
procedure SetPropertyDefault(Index: ThtPropIndices; const Value: Variant);
procedure SetPropertyDefaults(Indexes: ThtPropIndexSet; const Value: Variant);
procedure Assign(const Item: Variant; Index: ThtPropIndices);
procedure AssignCharSetAndCodePage(CS: TFontCharset; CP: Integer);
procedure Combine(Styles: TfrxHtStyleList; Sym: TElemSymb; const Tag, AClass, AnID, Pseudo, ATitle: ThtString; AProp: TfrxHtProperties; Attributes: TfrxHtAttributeList; ParentIndexInPropStack: Integer); overload;
procedure Combine(Styles: TfrxHtStyleList; Sym: TElemSymb; const Tag, Pseudo: ThtString; Properties: TfrxHtProperties; Attributes: TfrxHtAttributeList; ParentIndexInPropStack: Integer); overload;
procedure Copy(Source: TfrxHtProperties);
procedure CopyDefault(Source: TfrxHtProperties);
procedure GetBackgroundPos(EmSize, ExSize: Integer; out P: PtPositionRec);
procedure GetFontInfo(AFI: TFontInfoObj);
procedure GetPageBreaks(out Before, After, Intact: Boolean);
function GetBoxSizing(var VBoxSizing : ThtBoxSizing) : Boolean;
function GetBorderSpacingHorz: Integer;
function GetBorderSpacingVert: Integer;
procedure GetVMarginArrayDefBorder(var MArray: ThtVMarginArray; const ADefColor : Variant);
procedure GetVMarginArray(var MArray: ThtVMarginArray);
function HasBorderSpacing: Boolean;
procedure Inherit(Tag: ThtString; Source: TfrxHtProperties);
procedure SetFontBG;
procedure Update(Source: TfrxHtProperties; Styles: TfrxHtStyleList; I: Integer);
//BG, 20.09.2009:
property Display: ThtDisplayStyle read GetDisplay;
property CharSet: TFontCharset read FCharSet write FCharSet;
property CodePage: Integer read FCodePage write AssignCodePage;
property DefPointSize : Double read FDefPointSize write FDefPointSize;
property EmSize: Integer read GetEmSize;
property ExSize: Integer read GetExSize;
property Font: TfrxHtFont read GetFont;
end;
TfrxHtStyleList = class(ThtStringList)
private
SeqNo: Integer;
FDefProp: TfrxHtProperties;
protected
FDefPointSize : Double;
//this must be protected so that the property can be changed in
//a descendant while being read only.
procedure SetLinksActive(Value: Boolean); virtual; abstract;
property LinksActive: Boolean write SetLinksActive;
public
constructor Create; overload;
destructor Destroy; override;
function AddDuplicate(const Tag: ThtString; Prop: TfrxHtProperties): TfrxHtProperties;
function AddObject(const S: ThtString; AObject: TObject): Integer; override;
function GetSeqNo: ThtString;
procedure Clear; override;
procedure AddModifyProp(const Selector, Prop, Value: ThtString; IsImportant: Boolean);
procedure Initialize(const FontName, PreFontName: ThtString; PointSize: Double;
AColor, AHotspot, AVisitedColor, AActiveColor: TColor; LinkUnderline: Boolean;
ACodePage: TBuffCodePage; ACharSet: TFontCharSet; MarginHeight, MarginWidth: Integer);
procedure ModifyLinkColor(Pseudo: ThtString; AColor: TColor);
property DefProp: TfrxHtProperties read FDefProp;
property DefPointSize : Double read FDefPointSize write FDefPointSize;
end;
TfrxHtPropStack = class(TObjectList)
private
function GetProp(Index: Integer): TfrxHtProperties; {$ifdef UseInline} inline; {$endif}
public
function Last: TfrxHtProperties; {$ifdef UseInline} inline; {$endif}
property Items[Index: Integer]: TfrxHtProperties read GetProp; default;
end;
type
ThtPropertyIndexes = set of ThtPropertyIndex;
ThtConvData = record
BaseWidth, BaseHeight: Integer;
EmSize, ExSize: Integer;
BorderWidth: Integer;
AutoCount: Integer;
IsAutoParagraph: ThtPropertyIndexes;
end;
const
IntNull = -12345678;
Auto = -12348765;
AutoParagraph = -12348766;
ParagraphSpace = 14; {default spacing between paragraphs, etc.}
ImageSpace = 3; {extra space for left, right images}
ListIndent = 40;
EastEurope8859_2 = 31; {for 8859-2}
// BG, 25.04.2012: Added:
function IsAuto(const Value: Variant): Boolean; {$ifdef UseInline} inline; {$endif}
//BG, 05.10.2010: added:
function VarIsIntNull(const Value: Variant): Boolean; {$ifdef UseInline} inline; {$endif}
function VarIsAuto(const Value: Variant): Boolean; {$ifdef UseInline} inline; {$endif}
function VMargToMarg(const Value: Variant; Relative: Boolean; Base, EmSize, ExSize, Default: Integer): Integer;
function ConvData(BaseWidth, BaseHeight, EmSize, ExSize, BorderWidth: Integer; AutoCount: Integer = 0): ThtConvData;
procedure ConvMargProp(I: ThtPropIndices; const VM: ThtVMarginArray; var ConvData: ThtConvData; var M: ThtMarginArray);
procedure ConvInlineMargArray(const VM: ThtVMarginArray; BaseWidth, BaseHeight, EmSize, ExSize, BorderWidth: Integer; {BStyle: ThtBorderStyle;} out M: ThtMarginArray);
procedure ConvMargArray(const VM: ThtVMarginArray; BaseWidth, BaseHeight, EmSize, ExSize, BorderWidth: Integer; out AutoCount: Integer; var M: ThtMarginArray);
procedure ConvMargArrayForCellPadding(const VM: ThtVMarginArray; EmSize, ExSize: Integer; var M: ThtMarginArray);
procedure ConvVertMargins(const VM: ThtVMarginArray; var CD: ThtConvData; var M: ThtMarginArray);
function OpacityFromStr(S : ThtString) : Byte;
function SortedColors: ThtStringList;
function TryStrToColor(S: ThtString; NeedPound: Boolean; out Color: TColor): Boolean;
function ColorAndOpacityFromString(S: ThtString; NeedPound: Boolean; out Color: TColor; out VOpacity : Byte): Boolean;
function ReadURL(Item: Variant): ThtString;
function LowerCaseUnquotedStr(const S : THtString) : THtString;
function RemoveQuotes(const S: ThtString): ThtString;
function ReadFontName(S: ThtString): ThtString;
procedure ApplyBoxWidthSettings(var AMarg: ThtMarginArray; var VMinWidth, VMaxWidth: Integer);
procedure ApplyBoxSettings(var AMarg: ThtMarginArray);
//here for inlining
function SkipWhiteSpace(const S: ThtString; I, L: Integer): Integer;
function FontSizeConv(const Str: ThtString; OldSize, DefPointSize: Double): Double;
function LengthConv(const Str: ThtString; Relative: Boolean; Base, EmSize, ExSize, Default: Integer): Integer;
procedure CalcAutoMinMaxConstraints(W, H, MinW, MaxW, MinH, MaxH: Integer; out ResW, ResH: Integer);
implementation
uses
{$ifdef Compiler24_Plus}
System.UITypes,
{$endif}
frxSVGColor;
var
// DefPointSize: Double;
CharsetPerCharset: array [TFontCharset] of record Inited: Boolean; Charset: TFontCharset; end;
//-- BG ---------------------------------------------------------- 17.02.2011 --
function SkipWhiteSpace(const S: ThtString; I, L: Integer): Integer;
{$ifdef UseInline} inline; {$endif}
begin
while I <= L do
begin
case S[I] of
' ',
#10,
#12,
#13:;
else
break;
end;
Inc(I);
end;
Result := I;
end;
//-- BG ---------------------------------------------------------- 17.02.2011 --
function FindChar(const S: ThtString; C: ThtChar; I, L: Integer): Integer;
{$ifdef UseInline} inline; {$endif}
begin
while (I <= L) and (S[I] <> C) do
Inc(I);
Result := I;
end;
{----------------ReadURL}
function ReadURL(Item: Variant): ThtString;
{$ifdef UseInline} inline; {$endif}
{
If Item is a string try to find and parse:
url( ["<url>"|'<url>'|<url>] )
and if successful return the <url>.
ReadURL tolerates
- any substring before url
- any substring after the (optionally quoted) <url>
- nested '(' ')' pairs even in the unquoted <url>
}
var
S: ThtString;
I, J, L, N: Integer;
Q: ThtChar;
begin
Result := '';
if VarIsStr(Item) then
begin
S := Item;
I := Pos('url(', S);
if I > 0 then
begin
L := Length(S);
// optional white spaces
I := SkipWhiteSpace(S, I + 4, L);
// optional quote char
Q := #0;
if I < L then
case S[I] of
'''', '"':
begin
Q := S[I];
Inc(I);
end;
end;
// read url
if Q <> #0 then
// up to quote char
J := FindChar(S, Q, I, L)
else
begin
// unquoted: up to whitespace or ')'
// beyond CSS: tolerate nested '(' ')' pairs as part of the name.
N := 0;
J := I;
while J <= L do
begin
case S[J] of
' ',
#10,
#12,
#13:
if N = 0 then
break;
'(':
Inc(N);
')':
begin
if N = 0 then
break;
Dec(N);
end;
end;
Inc(J);
end;
end;
Result := Copy(S, I, J - I);
// ignore the rest: optional whitespaces and ')'
end;
end;
end;
//-- BG ---------------------------------------------------------- 15.03.2011 --
var
PropertyStrings: ThtStringList;
function StyleProperties: ThtStringList;
{$ifdef UseInline} inline; {$endif}
var
I: ThtPropertyIndex;
begin
// Put the Properties into a sorted StringList for faster access.
if PropertyStrings = nil then
begin
PropertyStrings := ThtStringList.Create;
for I := Low(I) to High(I) do
PropertyStrings.AddObject(PropWords[I], Pointer(I));
PropertyStrings.Sort;
end;
Result := PropertyStrings;
end;
//-- BG ---------------------------------------------------------- 15.03.2011 --
function TryStrToPropIndex(const PropWord: ThtString; var PropIndex: ThtPropIndices): Boolean;
{$ifdef UseInline} inline; {$endif}
var
I: Integer;
P: ThtPropertyIndex;
begin
I := StyleProperties.IndexOf(PropWord);
Result := I >= 0;
if Result then
begin
P := ThtPropertyIndex(StyleProperties.Objects[I]);
Result := P in [Low(ThtPropIndices)..High(ThtPropIndices)];
if Result then
PropIndex := P;
end;
end;
var
Sequence: Integer;
{----------------TfrxHtProperties.Create}
constructor TfrxHtProperties.Create;
var
I: ThtPropIndices;
begin
inherited Create;
ID := Sequence;
Inc(Sequence);
FontBG := clNone;
for I := MarginTop to BorderSpacingVert do
Props[I] := IntNull;
Props[ZIndex] := 0;
end;
//-- BG ---------------------------------------------------------- 12.09.2010 --
constructor TfrxHtProperties.Create(APropStack: TfrxHtPropStack);
begin
Create;
Self.PropStack := APropStack;
end;
//-- BG ---------------------------------------------------------- 21.10.2016 --
function TfrxHtProperties.Clone: TfrxHtProperties;
begin
Result := TfrxHtProperties.CreateCopy(Self);
Result.Props := Props;
Result.Originals := Originals;
Result.Important := Important;
end;
//-- BG ---------------------------------------------------------- 20.01.2013 --
constructor TfrxHtProperties.CreateCopy(ASource: TfrxHtProperties);
begin
FDefPointSize := ASource.DefPointSize;
PropStack := ASource.PropStack ;
InLink := ASource.InLink ;
DefFontname := ASource.DefFontname ;
PropTag := ASource.PropTag ;
PropClass := ASource.PropClass ;
PropID := ASource.PropID ;
PropPseudo := ASource.PropPseudo ;
PropTitle := ASource.PropTitle ;
PropStyle := ASource.PropStyle ;
FontBG := ASource.FontBG ;
FCharSet := ASource.FCharSet ;
FCodePage := ASource.FCodePage ;
Props := ASource.Props ;
Originals := ASource.Originals ;
ID := ASource.ID ;
if ASource.FIObj <> nil then
begin
FIObj := TFontInfoObj.Create;
FIObj.Assign(ASource.FIObj);
end;
if ASource.TheFont <> nil then
begin
TheFont := TfrxHtFont.Create;
TheFont.Assign(ASource.TheFont);
end;
end;
destructor TfrxHtProperties.Destroy;
begin
TheFont.Free;
FIObj.Free;
inherited;
end;
{----------------TfrxHtProperties.Copy}
procedure TfrxHtProperties.Copy(Source: TfrxHtProperties);
var
I: ThtPropIndices;
begin
FDefPointSize := Source.DefPointSize;
for I := Low(I) to High(I) do
Props[I] := Source.Props[I];
end;
{----------------TfrxHtProperties.CopyDefault}
procedure TfrxHtProperties.CopyDefault(Source: TfrxHtProperties);
var
I: ThtPropIndices;
begin
for I := Low(I) to High(I) do
Props[I] := Source.Props[I];
CodePage := Source.CodePage;
DefFontname := Source.DefFontname;
FDefPointSize := Source.DefPointSize;
PropTag := 'default';
end;
procedure TfrxHtProperties.Inherit(Tag: ThtString; Source: TfrxHtProperties);
{copy the properties that are inheritable}
var
I: ThtPropIndices;
Span, HBF: Boolean;
isTable: Boolean;
begin
Span := Source.PropTag = 'span';
HBF := (Source.PropTag = 'thead') or (Source.PropTag = 'tbody') or (Source.PropTag = 'tfoot');
isTable := Tag = 'table';
for I := Low(I) to High(I) do
if Span {and (I <> BorderStyle)} then {Borderstyle already acted on}
Props[I] := Source.Props[I]
else if HBF then
begin
Props[I] := Source.Props[I]; {tr gets them all}
Originals[I] := Source.Originals[I];
end
else
case I of
MarginTop..BorderLeftStyle,
piMinHeight, piMinWidth,
piMaxHeight, piMaxWidth,
piWidth, piHeight,
piTop..piLeft:
Props[I] := IntNull;
WordWrap:
if isTable then
Props[I] := 'normal'
else
Props[I] := Source.Props[I];
BackgroundColor, BackgroundImage, BackgroundPosition,
BackgroundRepeat, BackgroundAttachment,
//BorderColor, BorderStyle,
BorderCollapse,
PageBreakBefore, PageBreakAfter, PageBreakInside,
Clear, Float, Position, OverFlow, piDisplay:
; {do nothing}
else
Props[I] := Source.Props[I];
end;
DefPointSize := Source.DefPointSize;
DefFontname := Source.DefFontname;
FontBG := Source.FontBG;
CodePage := Source.CodePage;
PropTitle := Source.PropTitle;
InLink := Source.InLink;
if InLink then
begin
if not Assigned(FIObj) then
FIObj := TFontInfoObj.Create;
FIObj.Assign(Source.FIObj);
end;
end;
{----------------TfrxHtProperties.Update}
procedure TfrxHtProperties.Update(Source: TfrxHtProperties; Styles: TfrxHtStyleList; I: Integer);
{Change the inherited properties for this item to those of Source}
var
Index: ThtPropIndices;
begin
for Index := Low(Index) to High(Index) do
if not Originals[Index] then
Props[Index] := Source.Props[Index];
FreeAndNil(TheFont); {may no longer be good}
if Assigned(FIObj) then
if Source.Inlink then
FIObj.Assign(Source.FIObj)
else if PropPseudo = 'link' then {an <a href> tag}
CalcLinkFontInfo(Styles, I)
else
begin {an <a href> tag has been removed}
FreeAndNil(FIObj);
Inlink := False;
end;
end;
{----------------TfrxHtProperties.Assign}
procedure TfrxHtProperties.Assign(const Item: Variant; Index: ThtPropIndices);
{Assignment should be made in order of importance as the first one in
predominates}
begin
if not Originals[Index] then
begin
Props[Index] := Item;
Originals[Index] := True;
if InLink then
case Index of
Color:
FIObj.Info.iColor := Item;
FontSize:
FIObj.Info.iSize := Item;
FontFamily:
FIObj.Info.iName := Item;
end;
end;
end;
function TfrxHtProperties.GetBackgroundImage(var Image: ThtString): Boolean;
begin
if (VarIsStr(Props[BackgroundImage])) then
if (Props[BackgroundImage] = 'none') then
begin
Image := '';
Result := True;
end
else
begin
Image := ReadUrl(Props[BackgroundImage]);
Result := Image <> '';
end
else
Result := False;
end;
//procedure TfrxHtProperties.AssignCharSet(CS: TFontCharset);
//begin
// AssignCharSetAndCodePage(CS, CharSetToCodePage(CS));
//end;
//-- BG ---------------------------------------------------------- 30.01.2011 --
function TranslateCharset(CS: TFontCharset): TFontCharset;
{$ifdef UseInline} inline; {$endif}
// extracted from TfrxHtProperties.AssignCharSetAndCodePage()
var
Save: HFONT;
tm: TTextmetric;
DC: HDC;
Font: TFont;
begin
if not CharsetPerCharset[CS].Inited then
begin
{the following makes sure the CharSet is available}
CharsetPerCharset[CS].Inited := True;
CharsetPerCharset[CS].Charset := CS;
Font := TFont.Create;
try
Font.Name := '';
Font.CharSet := CS;
DC := GetDC(0);
try
Save := SelectObject(DC, Font.Handle);
try
GetTextMetrics(DC, tm);
CharsetPerCharset[CS].Charset := tm.tmCharSet;
finally
SelectObject(DC, Save);
end;
finally
ReleaseDC(0, DC);
end;
finally
Font.Free;
end;
end;
Result := CharsetPerCharset[CS].Charset;
end;
//-- BG ---------------------------------------------------------- 25.12.2010 --
procedure TfrxHtProperties.AssignCharSetAndCodePage(CS: TFontCharset; CP: Integer);
begin
if (FCharSet <> CS) or (FCodePage <> CP) then
begin
case CS of
EastEurope8859_2:
FCharSet := TranslateCharset(EASTEUROPE_CHARSET);
DEFAULT_CHARSET:
FCharSet := CS;
else
FCharSet := TranslateCharset(CS);
end;
if Assigned(FIObj) then
FIObj.Info.iCharset := CharSet;
FCodePage := CP;
end;
end;
procedure TfrxHtProperties.AssignCodePage(const CP: Integer);
begin
case CP of
CP_UTF8,
CP_UTF16LE,
CP_UTF16BE:
AssignCharSetAndCodePage(DEFAULT_CHARSET, CP);
else
AssignCharSetAndCodePage(CodePageToCharSet(CP), CP);
end;
end;
{----------------TfrxHtProperties.GetBackgroundPos}
procedure TfrxHtProperties.GetBackgroundPos(EmSize, ExSize: Integer; out P: PtPositionRec);
var
S: array[1..2] of ThtString;
Tmp: ThtString;
I, N, XY: Integer;
PXY: PPositionRec;
begin
//BG, 29.08.2009: thanks to SourceForge user 'bolex': 'not' was missing.
if (not VarIsStr(Props[BackgroundPosition])) then
begin
P.X.PosType := bpDim;
P.X.Value := 0;
P.Y := P.X;
end
else
begin
Tmp := Trim(Props[BackgroundPosition]);
N := Pos(' ', Tmp);
if N > 0 then
begin
S[1] := System.Copy(Tmp, 1, N - 1);
S[2] := Trim(system.Copy(Tmp, N + 1, 255));
N := 2;
end
else
begin
S[1] := Tmp;
N := 1;
end;
I := 1;
XY := 1; {X}
while I <= N do
begin
if XY = 1 then
PXY := @P.X
else
PXY := @P.Y;
PXY.PosType := bpDim;
if S[I] = 'center' then
PXY.PosType := bpCenter
else if Pos('%', S[I]) > 0 then
PXY.PosType := bpPercent
else if S[I] = 'left' then
begin
if XY = 2 then {entered in reverse direction}
P.Y := P.X;
P.X.PosType := bpLeft;
end
else if S[I] = 'right' then
begin
if XY = 2 then
P.Y := P.X;
P.X.PosType := bpRight;
end
else if S[I] = 'top' then
begin
P.Y.PosType := bpTop;
if XY = 1 then
Dec(XY); {read next one into X}
end
else if S[I] = 'bottom' then
begin
P.Y.PosType := bpBottom;
if XY = 1 then
Dec(XY);
end;
if PXY.PosType in [bpDim, bpPercent] then
begin
PXY.Value := LengthConv(S[I], False, 100, EmSize, ExSize, 0);
end;
Inc(I);
Inc(XY);
end;
if N = 1 then
if XY = 2 then
P.Y.PosType := bpCenter
else
P.X.PosType := bpCenter; {single entry but it was a Y}
end;
P.X.RepeatD := True;
P.Y.RepeatD := True;
if (VarIsStr(Props[BackgroundRepeat])) then
begin
Tmp := Trim(Props[BackgroundRepeat]);
if Tmp = 'no-repeat' then
begin
P.X.RepeatD := False;
P.Y.RepeatD := False;
end
else if Tmp = 'repeat-x' then
P.Y.RepeatD := False
else if Tmp = 'repeat-y' then
P.X.RepeatD := False;
end;
P.X.Fixed := False;
if (VarIsStr(Props[BackgroundAttachment])) and
(Trim(Props[BackgroundAttachment]) = 'fixed') then
P.X.Fixed := True;
P.Y.Fixed := P.X.Fixed;
end;
function TfrxHtProperties.GetVertAlign(var Align: ThtAlignmentStyle): Boolean;
{note: 'top' should have a catagory of its own}
var
S: ThtString;
begin
if (VarIsStr(Props[VerticalAlign])) then
begin
Result := True;
S := Props[VerticalAlign];
if (S = 'top') or (S = 'text-top') then
Align := ATop
else if S = 'middle' then
Align := AMiddle
else if S = 'baseline' then
Align := ABaseline
else if (S = 'bottom') then
Align := ABottom
else if (S = 'sub') then
Align := ASub
else if (S = 'super') then
Align := ASuper
else
Result := False;
end
else
Result := False;
end;
function TfrxHtProperties.IsOverflowHidden: Boolean;
begin
Result := (VarIsStr(Props[OverFlow])) and (Props[OverFlow] = 'hidden');
end;
function TfrxHtProperties.GetFloat(var Align: ThtAlignmentStyle): Boolean;
var
S: ThtString;
begin
if (VarIsStr(Props[Float])) then
begin
Result := True;
S := Props[Float];
if S = 'left' then
Align := aLeft
else if S = 'right' then
Align := aRight
else if S = 'none' then
Align := aNone
else
Result := False;
end
else
Result := False;
end;
function TfrxHtProperties.GetClear(var Clr: ThtClearStyle): Boolean;
var
S: ThtString;
begin
if (VarIsStr(Props[Clear])) then
begin
S := Props[Clear];
Result := TryStrToClearStyle(S, Clr);
//Props[Clear] := Unassigned; {allow only one read}
end
else
Result := False;
end;
//-- BG ---------------------------------------------------------- 15.09.2009 --
function TfrxHtProperties.GetDisplay: ThtDisplayStyle;
begin
if VarIsStr(Props[piDisplay]) then
if TryStrToDisplayStyle(Props[piDisplay], Result) then
exit;
Result := pdUnassigned;
end;
//-- BG ---------------------------------------------------------- 21.09.2016 --
function TfrxHtProperties.GetEmSize: Integer;
begin
Result := Font.EmSize;
end;
//-- BG ---------------------------------------------------------- 21.09.2016 --
function TfrxHtProperties.GetExSize: Integer;
begin
Result := Font.ExSize;
end;
//-- BG ---------------------------------------------------------- 16.04.2011 --
function TfrxHtProperties.GetListStyleType: ThtBulletStyle;
begin
if VarIsStr(Props[ListStyleType]) then
if TryStrToBulletStyle(Props[ListStyleType], Result) then
Exit;
Result := lbBlank;
end;
function TfrxHtProperties.GetListStyleImage: ThtString;
begin
Result := ReadURL(Props[ListStyleImage])
end;
function TfrxHtProperties.GetPosition: ThtBoxPositionStyle;
begin
Result := posStatic;
if VarIsStr(Props[Position]) then
begin
if Props[Position] = 'absolute' then
Result := posAbsolute
else if Props[Position] = 'fixed' then
Result := posFixed
else if Props[Position] = 'relative' then
Result := posRelative;
end;
end;
function TfrxHtProperties.GetVisibility: ThtVisibilityStyle;
begin
Result := viVisible;
if VarType(Props[Visibility]) in varInt then
if Props[Visibility] = viHidden then
Result := viHidden;
end;
function TfrxHtProperties.GetZIndex: Integer;
begin
Result := 0;
if VarType(Props[ZIndex]) in VarInt then
Result := Props[ZIndex]
else if VarIsStr(Props[ZIndex]) then
Result := StrToIntDef(Props[ZIndex], 0);
end;
//-- BG ---------------------------------------------------------- 15.10.2010 --
function TfrxHtProperties.HasBorderWidth: Boolean;
begin
Result := not (VarIsIntNull(Props[BorderTopWidth]) or VarIsEmpty(Props[BorderTopWidth]))
or not (VarIsIntNull(Props[BorderRightWidth]) or VarIsEmpty(Props[BorderRightWidth]))
or not (VarIsIntNull(Props[BorderBottomWidth]) or VarIsEmpty(Props[BorderBottomWidth]))
or not (VarIsIntNull(Props[BorderLeftWidth]) or VarIsEmpty(Props[BorderLeftWidth]));
end;
function TfrxHtProperties.Collapse: Boolean;
begin
Result := (VarIsStr(Props[BorderCollapse])) and (Props[BorderCollapse] = 'collapse');
end;
function TfrxHtProperties.GetLineHeight(NewHeight: Integer): Integer;
var
V: Double;
Code: Integer;
begin
if VarIsStr(Props[LineHeight]) then
begin
Val(Props[LineHeight], V, Code);
if Code = 0 then {a numerical entry with no 'em', '%', etc. Use the new font height}
Result := Round(V * NewHeight)
else
{note: 'normal' yields -1 in the next statement}
Result := LengthConv(Props[LineHeight], True, EmSize, EmSize, ExSize, -1);
end
else
Result := -1;
end;
function TfrxHtProperties.GetTextIndent(out PC: Boolean): Integer;
var
I: Integer;
begin
PC := False;
if VarIsStr(Props[TextIndent]) then
begin
I := Pos('%', Props[TextIndent]);
if I > 0 then
begin
PC := True; {return value in percent}
Result := LengthConv(Props[TextIndent], True, 100, 0, 0, 0);
end
else
Result := LengthConv(Props[TextIndent], False, 0, EmSize, EmSize, 0);
end
else
Result := 0;
end;
function TfrxHtProperties.GetTextTransform: ThtTextTransformStyle;
begin
try
if VarType(Props[TextTransform]) in VarInt then
Result := Props[TextTransform]
else
Result := txNone;
except
Result := txNone;
end;
end;
function TfrxHtProperties.GetFontVariant: ThtString;
begin
try
if VarIsStr(Props[FontVariant]) then
Result := Props[FontVariant]
else
Result := 'normal';
except
Result := 'normal';
end;
end;
procedure TfrxHtProperties.GetPageBreaks(out Before, After, Intact: Boolean);
begin
Before := (VarIsStr(Props[PageBreakBefore])) and (Props[PageBreakBefore] = 'always');
After := (VarIsStr(Props[PageBreakAfter])) and (Props[PageBreakAfter] = 'always');
Intact := (VarIsStr(Props[PageBreakInside])) and (Props[PageBreakInside] = 'avoid');
end;
function TfrxHtProperties.GetBackgroundColor: TColor;
begin
if (VarType(Props[BackgroundColor]) in varInt) and Originals[BackgroundColor] then
{Originals to prevent fonts from getting inherited background color}
Result := Props[BackgroundColor]
else
Result := clNone;
end;
function TfrxHtProperties.GetOriginalForegroundColor: TColor;
begin {return a color only if it hasn't been inherited}
if (VarType(Props[Color]) in varInt) and Originals[Color] then
Result := Props[Color]
else
Result := clNone;
end;
//-- BG ---------------------------------------------------------- 12.03.2011 --
function TfrxHtProperties.GetBorderStyle(Index: ThtPropIndices; var BorderStyle: ThtBorderStyle): Boolean;
// Returns True, if there is a valid border style property.
begin
Result := False;
if VarIsStr(Props[Index]) then
Result := TryStrToBorderStyle(Props[Index], BorderStyle)
else if VarType(Props[Index]) in varInt then
if (Props[Index] >= Low(ThtBorderStyle)) and (Props[Index] <= High(ThtBorderStyle)) then
begin
BorderStyle := ThtBorderStyle(Props[Index]);
Result := True;
end;
end;
function TfrxHtProperties.GetBoxSizing(var VBoxSizing: ThtBoxSizing): Boolean;
begin
Result := TryStrToBoxSizing(Props[BoxSizing], VBoxSizing);
end;
function TfrxHtProperties.BorderStyleNotBlank: Boolean;
{was a border of some type (including bssNone) requested?}
var
Dummy: ThtBorderStyle;
begin
Dummy := bssNone;
Result :=
GetBorderStyle(BorderTopStyle, Dummy) or
GetBorderStyle(BorderRightStyle, Dummy) or
GetBorderStyle(BorderBottomStyle, Dummy) or
GetBorderStyle(BorderLeftStyle, Dummy);
end;
//-- BG ---------------------------------------------------------- 12.03.2011 --
function TfrxHtProperties.HasBorderStyle: Boolean;
// Returns True, if at least one border has a style.
var
Dummy: ThtBorderStyle;
begin
Dummy := bssNone;
GetBorderStyle(BorderTopStyle, Dummy);
Result := Dummy <> bssNone;
if Result then
exit;
GetBorderStyle(BorderRightStyle, Dummy);
Result := Dummy <> bssNone;
if Result then
exit;
GetBorderStyle(BorderBottomStyle, Dummy);
Result := Dummy <> bssNone;
if Result then
exit;
GetBorderStyle(BorderLeftStyle, Dummy);
Result := Dummy <> bssNone;
end;
function TfrxHtProperties.GetBorderSpacingHorz: Integer;
var
V: Double;
Code: Integer;
begin
if VarIsStr(Props[BorderSpacingHorz]) then
begin
Val(Props[BorderSpacingHorz], V, Code);
if Code = 0 then {a numerical entry with no 'em', '%', etc. }
Result := Round(V)
else
{note: 'normal' yields -1 in the next statement}
Result := LengthConv(Props[BorderSpacingHorz], True, EmSize, EmSize, ExSize, -1);
end
else
Result := -1;
end;
function TfrxHtProperties.GetBorderSpacingVert: Integer;
var
V: Double;
Code: Integer;
begin
if VarIsStr(Props[BorderSpacingVert]) then
begin
Val(Props[BorderSpacingVert], V, Code);
if Code = 0 then {a numerical entry with no 'em', '%', etc. }
Result := Round(V)
else
{note: 'normal' yields -1 in the next statement}
Result := LengthConv(Props[BorderSpacingVert], True, EmSize, EmSize, ExSize, -1);
end
else
Result := -1;
end;
function TfrxHtProperties.HasBorderSpacing: Boolean;
begin
Result := not (VarIsIntNull(Props[BorderSpacingHorz]) or VarIsEmpty(Props[BorderSpacingHorz]));
end;
procedure TfrxHtProperties.SetFontBG;
{called for font tags like <b>, <small>, etc. Sets the font background color.}
begin
if (VarType(Props[BackgroundColor]) in varInt) and Originals[BackgroundColor] then
FontBG := Props[BackgroundColor];
end;
//-- BG ---------------------------------------------------------- 20.01.2013 --
procedure TfrxHtProperties.SetPropertyDefault(Index: ThtPropIndices; const Value: Variant);
begin
if (Props[Index] = Unassigned) or ((VarType(Props[Index]) in varInt) and (Props[Index] = IntNull)) then
Props[Index] := Value;
end;
//-- BG ---------------------------------------------------------- 20.01.2013 --
procedure TfrxHtProperties.SetPropertyDefaults(Indexes: ThtPropIndexSet; const Value: Variant);
var
Index: ThtPropIndices;
begin
for Index := Low(Index) to High(Index) do
if Index in Indexes then
SetPropertyDefault(Index, Value);
end;
//-- BG ---------------------------------------------------------- 23.11.2009 --
function TfrxHtProperties.ShowEmptyCells: Boolean;
begin
Result := not (VarIsStr(Props[piEmptyCells]) and (Props[piEmptyCells] = 'hide'));
end;
procedure ConvVertMargins(const VM: ThtVMarginArray; var CD: ThtConvData; var M: ThtMarginArray);
begin
ConvMargProp(PaddingTop, VM, CD, M);
ConvMargProp(BorderTopWidth, VM, CD, M);
ConvMargProp(MarginTop, VM, CD, M);
ConvMargProp(piHeight, VM, CD, M);
ConvMargProp(piMinHeight, VM, CD, M);
ConvMargProp(piMaxHeight, VM, CD, M);
ConvMargProp(MarginBottom, VM, CD, M);
ConvMargProp(BorderBottomWidth, VM, CD, M);
ConvMargProp(PaddingBottom, VM, CD, M);
end;
//-- BG ---------------------------------------------------------- 25.04.2012 --
function IsAuto(const Value: Variant): Boolean;
{$ifdef UseInline} inline; {$endif}
begin
Result := Value = Auto;
end;
//-- BG ---------------------------------------------------------- 05.10.2010 --
function VarIsIntNull(const Value: Variant): Boolean;
{$ifdef UseInline} inline; {$endif}
begin
Result := (VarType(Value) in varInt) and (Value = IntNull);
end;
//-- BG ---------------------------------------------------------- 05.10.2010 --
function VarIsAuto(const Value: Variant): Boolean;
{$ifdef UseInline} inline; {$endif}
begin
Result := (VarType(Value) in varInt) and (Value = Auto);
end;
//-- BG ---------------------------------------------------------- 05.10.2010 --
function VMargToMarg(const Value: Variant; Relative: Boolean; Base, EmSize, ExSize, Default: Integer): Integer;
{$ifdef UseInline} inline; {$endif}
begin
if VarIsStr(Value) then
Result := LengthConv(Value, Relative, Base, EmSize, ExSize, Default)
else if (VarType(Value) in varInt) and (Value <> IntNull) then
Result := Value
else
Result := Default;
end;
procedure ApplyBoxWidthSettings(var AMarg: ThtMarginArray; var VMinWidth, VMaxWidth: Integer);
{$ifdef UseInline} inline; {$endif}
begin
{Important!!!
You have to do this with settings. This is only for FindWidth methods}
if AMarg[piMaxWidth] > 0 then
begin
AMarg[piWidth] := Min(AMarg[piMaxWidth], AMarg[piWidth]);
VMaxWidth := Min(AMarg[piMaxWidth], VMaxWidth);
end;
if AMarg[piMinWidth] > 0 then
begin
AMarg[piWidth] := Max(AMarg[piMinWidth], AMarg[piWidth]);
VMinWidth := Max(AMarg[piMinWidth], VMinWidth);
end;
end;
procedure ApplyBoxSettings(var AMarg: ThtMarginArray);
procedure ApplyBorderBoxModel(var AMarg : ThtMarginArray);
begin
if AMarg[piWidth] > -1 then
Dec(AMarg[piWidth], AMarg[BorderLeftWidth] + AMarg[PaddingLeft] + AMarg[PaddingRight] + AMarg[BorderRightWidth]);
if AMarg[piHeight] > -1 then
Dec(AMarg[piHeight], AMarg[BorderTopWidth] + AMarg[PaddingTop] + AMarg[PaddingBottom] + AMarg[BorderBottomWidth]);
end;
begin
{JPM: This test is here to prevent AMarg[piWidth] from being ruined
if it is set to Auto. If it is ruined, AutoCount might be incremented
correctly causing a rendering bug. }
//min max width
if AMarg[piWidth] > 0 then
begin
if AMarg[piMaxWidth] > 0 then
AMarg[piWidth] := Min(AMarg[piWidth], AMarg[piMaxWidth]);
if AMarg[piMinWidth] > 0 then
AMarg[piWidth] := Max(AMarg[piWidth], AMarg[piMinWidth]);
end;
//BG, 09.08.2015: this min/max handling is not as described in CSS 2.1
// and tents to bust the document design.
// //min max height
// if AMarg[piHeight] > 0 then
// begin
// if AMarg[piMaxHeight] > 0 then
// AMarg[piHeight] := Min(AMarg[piHeight], AMarg[piMaxHeight]);
//
// if AMarg[piMinHeight] > 0 then
// AMarg[piHeight] := Max(AMarg[piHeight], AMarg[piMinHeight]);
// end;
case ThtBoxSizing(AMarg[BoxSizing]) of
BorderBox: ApplyBorderBoxModel(AMarg);
end;
end;
{----------------ConvMargArray}
//-- BG ---------------------------------------------------------- 16.05.2014 --
function ConvData(
BaseWidth, BaseHeight: Integer;
EmSize, ExSize: Integer;
BorderWidth: Integer;
AutoCount: Integer = 0): ThtConvData;
begin
Result.BaseWidth := BaseWidth;
Result.BaseHeight := BaseHeight;
Result.EmSize := EmSize;
Result.ExSize := ExSize;
Result.BorderWidth := BorderWidth;
Result.AutoCount := AutoCount;
Result.IsAutoParagraph := [];
end;
//-- BG ---------------------------------------------------------- 16.05.2014 --
procedure ConvMargProp(I: ThtPropIndices; const VM: ThtVMarginArray; var ConvData: ThtConvData; var M: ThtMarginArray);
function Base(I: ThtPropIndices): Integer;
begin
case I of
BorderTopWidth, BorderBottomWidth,
MarginTop, MarginBottom,
piMinHeight, piMaxHeight,
PaddingTop, PaddingBottom,
LineHeight,
piHeight, piTop:
Base := ConvData.BaseHeight
else
Base := ConvData.BaseWidth;
end;
end;
var
LBoxSizing: ThtBoxSizing;
Color: TColor;
begin
with ConvData do
begin
case I of
BackgroundColor, BorderTopColor..BorderLeftColor:
begin
if VarType(VM[I]) <= VarNull then
M[I] := clNone
else if VarIsStr(VM[I]) then
begin
if TryStrToColor(VM[I], false, Color) then
M[I] := Color;
end
else
M[I] := VM[I];
end;
BorderTopWidth..BorderLeftWidth:
begin
if VM[ThtPropIndices(Ord(BorderTopStyle) + (Ord(I) - Ord(BorderTopWidth)))] = bssNone then
M[I] := 0
else
begin
if VarIsStr(VM[I]) then
begin
if VM[I] = 'thin' then
M[I] := 2
else if VM[I] = 'medium' then
M[I] := 4
else if VM[I] = 'thick' then
M[I] := 6
else
M[I] := LengthConv(VM[I], False, Base(I), EmSize, ExSize, BorderWidth); {Auto will be 4}
end
else if (VarType(VM[I]) in varInt) then
begin
if (VM[I] = IntNull) then
M[I] := BorderWidth
else
M[I] := VM[I];
end;
end;
end;
piMinHeight, piMaxHeight,
piHeight:
begin
if VarIsStr(VM[I]) then
begin
M[I] := LengthConv(VM[I], False, Base(I), EmSize, ExSize, 0); {Auto will be 0}
if Pos('%', VM[I]) > 0 then {include border in % heights}
M[I] := M[I] - M[BorderTopWidth] - M[BorderBottomWidth] - M[PaddingTop] - M[PaddingBottom];
end
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := 0
else
M[I] := VM[I];
end
else
M[I] := 0;
end;
PaddingTop..PaddingLeft,BorderSpacingHorz,BorderSpacingVert:
begin
if VarIsStr(VM[I]) then
begin
M[I] := LengthConv(VM[I], False, Base(I), EmSize, ExSize, 0); {Auto will be 0}
end
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := 0
else
M[I] := VM[I];
end
else
M[I] := 0;
end;
piTop..piLeft:
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, Base(I), EmSize, ExSize, Auto) {Auto will be Auto}
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := Auto
else
M[I] := VM[I];
end
else
M[I] := Auto;
end;
BoxSizing:
if TryStrToBoxSizing(VM[I],LBoxSizing) then begin
M[I] := Ord(LBoxSizing);
end else begin
//assume content-box
M[I] := 0;
end;
MarginRight, MarginLeft:
begin
if VarIsStr(VM[I]) then
begin
if VM[I] = 'auto' then
begin
M[I] := Auto;
Inc(AutoCount);
end
else
M[I] := LengthConv(VM[I], False, BaseWidth, EmSize, ExSize, 0);
end
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := 0
else
M[I] := VM[I];
end
else
M[I] := 0;
end;
MarginTop, MarginBottom:
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, BaseHeight, EmSize, ExSize, 0) {Auto will be 0}
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := 0
else if VM[I] = AutoParagraph then
begin
M[I] := ParagraphSpace;
Include(IsAutoParagraph, I);
end
else
M[I] := VM[I];
end
else
M[I] := 0;
end;
piMinWidth,
piMaxWidth:
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, BaseWidth, EmSize, ExSize, Auto)
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := 0
else
M[I] := VM[I];
end
else
M[I] := 0;
end;
piWidth:
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, BaseWidth, EmSize, ExSize, Auto)
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := Auto
else
M[I] := VM[I];
end
else
M[I] := Auto;
if M[I] = Auto then
Inc(AutoCount);
end;
else
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, Base(I), EmSize, ExSize, 0)
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := 0
else
M[I] := VM[I];
end
else
M[I] := 0;
end;
end;
end;
end;
procedure ConvMargArray(const VM: ThtVMarginArray; BaseWidth, BaseHeight, EmSize, ExSize: Integer;
BorderWidth: Integer; out AutoCount: Integer; var M: ThtMarginArray);
{This routine does not do MarginTop and MarginBottom as they are done by ConvVertMargins}
var
I: ThtPropIndices;
CD: ThtConvData;
begin
CD := ConvData(BaseWidth, BaseHeight, EmSize, ExSize, BorderWidth);
for I := Low(VM) to High(VM) do
if not (I in [MarginTop, MarginBottom]) then
ConvMargProp(I, VM, CD, M);
AutoCount := CD.AutoCount; {count of 'auto's in width items}
end;
procedure ConvMargArrayForCellPadding(const VM: ThtVMarginArray; EmSize,
ExSize: Integer; var M: ThtMarginArray);
{Return negative for no entry or percent entry}
var
I: ThtPropIndices;
begin
for I := PaddingTop to PaddingLeft do
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, -100, EmSize, ExSize, 0) {Auto will be 0}
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := -1
else
M[I] := VM[I];
end
else
M[I] := -1;
end;
{----------------ConvInlineMargArray}
procedure ConvInlineMargArray(const VM: ThtVMarginArray; BaseWidth, BaseHeight, EmSize,
ExSize, BorderWidth: Integer; {BStyle: ThtBorderStyle;} out M: ThtMarginArray);
{$ifdef UseInline} inline; {$endif}
{currently for images, form controls. BaseWidth/Height and BStyle currently not supported}
var
I: ThtPropIndices;
begin
for I := Low(VM) to High(VM) do
case I of
piHeight, piWidth:
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, BaseWidth, EmSize, ExSize, Auto) {Auto will be Auto}
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := IntNull
else
M[I] := VM[I];
end
else
M[I] := IntNull;
end;
piMinHeight, piMinWidth, piMaxHeight, piMaxWidth,
MarginLeft, MarginRight, MarginTop, MarginBottom:
begin
if VarIsStr(VM[I]) then
M[I] := LengthConv(VM[I], False, BaseWidth, EmSize, ExSize, 0) {auto is 0}
else if VarType(VM[I]) in varInt then
begin
if VM[I] = IntNull then
M[I] := IntNull
else
M[I] := VM[I];
end
else
M[I] := IntNull;
end;
BorderTopWidth..BorderLeftWidth:
begin
if VM[ThtPropIndices(Ord(BorderTopStyle) + (Ord(I) - Ord(BorderTopWidth)))] = bssNone then
M[I] := 0
else
begin
if VarIsStr(VM[I]) then
begin
if VM[I] = 'thin' then
M[I] := 2
else if VM[I] = 'medium' then
M[I] := 4
else if VM[I] = 'thick' then
M[I] := 6
else
M[I] := LengthConv(VM[I], False, BaseWidth, EmSize, ExSize, BorderWidth); {Auto will be BorderWidth}
end
else if (VarType(VM[I]) in varInt) then
begin
if (VM[I] = IntNull) then
M[I] := 4
else
M[I] := VM[I];
end;
end;
end;
else
; {remaining items unsupported/unused}
end;
end;
{----------------TfrxHtProperties.Combine}
//-- BG ---------------------------------------------------------- 28.02.2016 --
procedure TfrxHtProperties.Combine(Styles: TfrxHtStyleList; Sym: TElemSymb;
const Tag, Pseudo: ThtString; Properties: TfrxHtProperties;
Attributes: TfrxHtAttributeList; ParentIndexInPropStack: Integer);
begin
if Attributes <> nil then
Combine(Styles, Sym, Tag, Attributes.TheClass, Attributes.TheID, PSeudo, Attributes.TheTitle, Properties, Attributes, ParentIndexInPropStack)
else
Combine(Styles, Sym, Tag, '', '', PSeudo, '', Properties, Attributes, ParentIndexInPropStack);
end;
procedure TfrxHtProperties.Combine(Styles: TfrxHtStyleList; Sym: TElemSymb;
const Tag, AClass, AnID, PSeudo, ATitle: ThtString; AProp: TfrxHtProperties; Attributes: TfrxHtAttributeList; ParentIndexInPropStack: Integer);
{When called, this TfrxHtProperties contains the inherited properties. Here we
add the ones relevant to this item. AProp are TfrxHtProperties gleaned from the
Style= attribute. AClass may be a multiple class like class="ab.cd"}
procedure CombineX(Styles: TfrxHtStyleList;
const Tag, AClass, AnID, PSeudo, ATitle: ThtString; AProp: TfrxHtProperties);
{When called, this TfrxHtProperties contains the inherited properties. Here we
add the ones relevant to this item. AProp are TfrxHtProperties gleaned from the
Style= attribute.}
var
OldSize: Double;
NoHoverVisited: Boolean;
//-- BG ------------------------------------------------------ 12.06.2016 --
procedure MergeAttrs(const Attributes: TfrxHtAttributeList);
{Merge Attributes in this TfrxHtProperties.}
var
I: Integer;
A: TfrxHtAttribute;
// T: TElemSymb;
begin
if (Attributes <> nil) and (Attributes.Count > 0) then
begin
for I := 0 to Attributes.Count - 1 do
begin
A := Attributes[I];
case A.Which of
AlignSy:
case Sym of
DivSy,
H1Sy..H6Sy,
PSy,
ColSy,
ColGroupSy,
THeadSy..TFootSy,
TDSy, THSy, TRSy:
if A.Name = 'char' then
Props[TextAlign] := 'right'
else
Props[TextAlign] := A.Name;
end;
atDirSy: Props[TextDirection] := A.Name;
// BackgroundSy: Props[BackgroundImage] := 'url(' + A.Name + ')';
// BGColorSy: Props[BackgroundColor] := A.Name;
BorderSy:
begin
Props[BorderTopWidth] := A.Name;
Props[BorderRightWidth] := A.Name;
Props[BorderBottomWidth] := A.Name;
Props[BorderLeftWidth] := A.Name;
end;
BorderColorSy:
begin
Props[BorderTopColor] := A.Name;
Props[BorderRightColor] := A.Name;
Props[BorderBottomColor] := A.Name;
Props[BorderLeftColor] := A.Name;
end;
// CellPaddingSy:
// begin
// Props[MarginTop] := A.Name;
// Props[MarginRight] := A.Name;
// Props[MarginBottom] := A.Name;
// Props[MarginLeft] := A.Name;
// end;
CellSpacingSy:
begin
Props[BorderSpacingHorz] := A.Name;
Props[BorderSpacingVert] := A.Name;
end;
//ColorSy:;
//HeightSy:;
//HSpaceSy:;
//LeftMarginSy:;
//LinkSy:;
//TopMarginSy:;
//MarginHeightSy:;
//MarginWidthSy:;
//StartSy,
//TypeSy,
VAlignSy: Props[VerticalAlign] := A.Name;
//ValueSy:;
//VLinkSy:;
//VSpaceSy:;
//WidthSy:;
end;
end;
end;
end;
procedure Merge(Source: TfrxHtProperties; Reverse: Boolean = False);
var
Index: ThtPropIndices;
Wt: Integer;
S1: ThtString;
begin
for Index := Low(Index) to High(Index) do
begin
if Reverse then
begin
if (Props[Index] <> Unassigned) and not VarIsIntNull(Props[Index]) then
continue;
end;
if Important[Index] and not Source.Important[Index] then
continue;
if VarIsStr(Source.Props[Index]) and (Source.Props[Index] = 'inherit') then
continue;
if (VarType(Source.Props[Index]) <> varEmpty) and (Vartype(Source.Props[Index]) <> varNull) then
case Index of
MarginTop..BorderSpacingVert:
if VarIsStr(Source.Props[Index]) then // if VarType(Source.Props[Index]) = VarString then
begin
Props[Index] := Source.Props[Index];
Important[Index] := Source.Important[Index];
Originals[Index] := True;
end
else if Source.Props[Index] <> IntNull then
begin
Props[Index] := Source.Props[Index];
Important[Index] := Source.Important[Index];
Originals[Index] := True;
end;
TextDecoration:
begin
Important[Index] := Source.Important[Index];
Originals[Index] := True;
S1 := Props[Index];
if (S1 = 'none') or (Length(S1) = 0) or (Source.Props[Index] = 'none') then
Props[Index] := Source.Props[Index]
else if Pos(Source.Props[Index], S1) = 0 then
Props[Index] := S1 + SpcChar + Source.Props[Index];
if InLink then
begin
with FIObj.Info do
if Props[Index] = 'none' then
iStyle := iStyle - [fsStrikeOut, fsUnderline]
else
begin
if Pos('underline', Props[Index]) > 0 then
Include(iStyle, fsUnderline);
if Pos('line-through', Props[Index]) > 0 then
Include(iStyle, fsStrikeOut);
end;
end;
end;
FontFamily, FontSize, FontStyle, FontWeight, Color, BackgroundColor, LetterSpacing:
begin
Important[Index] := Source.Important[Index];
Originals[Index] := True;
Props[Index] := Source.Props[Index];
if InLink then
with FIObj.Info do
case Index of
FontFamily:
begin
S1 := ReadFontName(Props[Index]);
if S1 <> '' then
iName := S1;
end;
FontSize:
iSize := FontSizeConv(Props[Index], iSize, DefPointSize);
Color:
iColor := Props[Index];
BackgroundColor:
ibgColor := Props[Index];
FontStyle:
if (Props[Index] = 'italic') or (Props[Index] = 'oblique') then
iStyle := iStyle + [fsItalic]
else if Props[Index] = 'normal' then
iStyle := iStyle - [fsItalic];
FontWeight:
if Pos('bold', Props[Index]) > 0 then
iStyle := iStyle + [fsBold]
else if Pos('normal', Props[Index]) > 0 then
iStyle := iStyle - [fsBold]
else
begin
Wt := StrToIntDef(Props[Index], 0);
if Wt >= 600 then
iStyle := iStyle + [fsBold];
end;
LetterSpacing:
iCharExtra := Props[Index];
end;
end
else
begin
Props[Index] := Source.Props[Index];
Important[Index] := Source.Important[Index];
Originals[Index] := True; {it's defined for this item, not inherited}
end;
end;
end;
end;
function CheckForContextual(I: Integer): Boolean;
{process contextual selectors}
var
J, K, N: Integer;
A: array[1..20] of record
Tg, Cl, ID, PS: ThtString;
gt: Boolean;
end;
MustMatchParent: Boolean;
procedure Split(S: ThtString);
var
I, J: Integer;
begin
N := 1; {N is number of selectors in contextual ThtString}
I := Pos(' ', S);
while (I > 0) and (N < 20) do
begin
A[N].Tg := System.Copy(S, 1, I - 1);
Delete(S, 1, I);
S := Trim(S);
Inc(N);
I := Pos(' ', S);
end;
A[N].Tg := S;
if N >= 2 then
while Length(A[2].Tg) > 0 do
begin
case A[2].Tg[1] of
'0'..'9':
Delete(A[2].Tg, 1, 1); {remove the sort digit}
else
break;
end;
end;
for I := 1 to N do
begin
J := Pos('>', A[I].Tg);
if I > 1 then
A[I - 1].gt := J > 0;
if J > 0 then
Delete(A[I].Tg, J, 1);
J := Pos(':', A[I].Tg);
if J > 0 then
begin
A[I].PS := System.Copy(A[I].Tg, J + 1, Length(A[I].Tg));
A[I].Tg := System.Copy(A[I].Tg, 1, J - 1);
end
else
A[I].PS := '';
J := Pos('#', A[I].Tg);
if J > 0 then
begin
A[I].ID := System.Copy(A[I].Tg, J + 1, Length(A[I].Tg));
A[I].Tg := System.Copy(A[I].Tg, 1, J - 1);
end
else
A[I].ID := '';
J := Pos('.', A[I].Tg);
if J > 0 then
begin
A[I].Cl := System.Copy(A[I].Tg, J + 1, Length(A[I].Tg));
A[I].Tg := System.Copy(A[I].Tg, 1, J - 1);
end
else
A[I].Cl := '';
end;
end;
function PartOf(const S1, S2: ThtString): Boolean;
{see if all classes in S1 are present in S2. Classes are separated by '.'}
var
SL1, SL2: ThtStringList;
J, X: Integer;
function FormStringList(S: ThtString): ThtStringList;
{construct a ThtStringList from classes in ThtString S}
var
I: Integer;
begin
Result := ThtStringList.Create;
Result.Sorted := True;
I := Pos('.', S);
while I >= 1 do
begin
Result.Add(System.Copy(S, 1, I - 1));
Delete(S, 1, I);
I := Pos('.', S);
end;
Result.Add(S);
end;
begin {PartOf}
SL1 := FormStringList(S1);
try
SL2 := FormStringList(S2);
try
Result := True; {assume all will be found}
for J := 0 to SL1.Count - 1 do
if not SL2.Find(SL1[J], X) then
begin
Result := False; {one is missing, return False}
Break;
end;
finally
SL2.Free;
end;
finally
SL1.Free;
end;
end;
begin
Result := False;
Split(Styles[I]); //split contextual selectors into parts in array A
if (A[1].Tg <> Tag) and (A[1].Cl <> AClass) and (A[1].PS <> PSeudo) then
Exit
else
Result := True;
if (N > 1) //it's a contextual selector. N is count of selectors
and ((A[1].Tg = Tag) or (A[1].Tg = ''))
and ((A[1].Cl = AClass) or (A[1].Cl = ''))
and ((A[1].ID = AnID) or (A[1].ID = ''))
and ((A[1].PS = PSeudo) or (A[1].PS = '') and (PSeudo = 'link')) then
begin //look thru the stack to see if this contextual selector is appropriate
K := 2; //K is selector index in the sequence
J := ParentIndexInPropStack; //PropStack.Count - 2; // start on stack item below this one
MustMatchParent := A[1].gt;
while (K <= N) and (J >= 1) do
begin
with PropStack[J] do
if ((A[K].Tg = PropTag) or (A[K].Tg = ''))
and ((A[K].Cl = PropClass) or (A[K].Cl = '') or PartOf(A[K].Cl, PropClass))
and ((A[K].ID = PropID) or (A[K].ID = ''))
and ((A[K].PS = PropPseudo) or (A[K].PS = '')) then
begin
if K = N then //all parts of contextual selector match
Merge(Styles.Objects[I] as TfrxHtProperties);
MustMatchParent := A[K].gt;
Inc(K);
end
else if MustMatchParent then
Break; {Didn't match}
Dec(J);
end;
end
end;
procedure MergeItems(const Item: ThtString; Reverse: Boolean = False);
{look up items in the Style list. If found, merge them in this TfrxHtProperties.
Items may be duplicated in which case the last has priority. Items may be
simple tags like 'p', 'blockquote', 'em', etc or they may be more complex
like p.class, em#id, a.class:link, etc}
var
X, Y: Integer;
begin
if Styles.Find(Item, X) then
begin
if Reverse then
begin
// Reverse is used to set unassigned values only.
Y := X;
Inc(X);
while (X < Styles.Count) and (Styles[X] = Item) do
begin //duplicates, last one has highest priority
Inc(X);
end;
// merge in reverse order
while X > Y do
begin
Dec(X);
Merge(Styles.Objects[X] as TfrxHtProperties, Reverse);
end;
end
else
begin
Merge(Styles.Objects[X] as TfrxHtProperties);
Inc(X);
while (X < Styles.Count) and (Styles[X] = Item) do
begin //duplicates, last one has highest priority
Merge(Styles.Objects[X] as TfrxHtProperties);
Inc(X);
end;
end;
end;
end;
//BG, 09.09.2010: extracted from below
procedure MergeContextuals(Style: ThtString);
var
IX: Integer;
begin
Styles.Find(Style, IX); //place to start
try
while (IX < Styles.Count) and (Pos(Style, Styles[IX]) = 1) and CheckForContextual(IX) do
Inc(IX);
except
raise;
end;
end;
begin
if (VarType(Props[FontSize]) in VarNum) and (Props[FontSize] > 0.0) then {should be true}
OldSize := Props[FontSize]
else
OldSize := DefPointSize;
{Some hover and visited items adequately taken care of when link processed}
NoHoverVisited := (Pseudo = '') or ((Pseudo <> 'hover') and (Pseudo <> 'visited'));
// in the following, lowest priority on top, highest towards bottom.
if (Tag = 'a') and ((Pseudo = 'link') or (Pseudo = 'visited')) then
MergeItems('::' + Pseudo); {default Pseudo definition}
if NoHoverVisited then
MergeItems(Tag);
MergeAttrs(Attributes);
if Pseudo <> '' then
MergeItems(':' + Pseudo);
if (AClass <> '') and NoHoverVisited then
begin
MergeItems('.' + AClass);
MergeItems(Tag + '.' + AClass);
end;
if Pseudo <> '' then
begin
MergeItems(Tag + ':' + Pseudo);
if AClass <> '' then
begin
MergeItems('.' + AClass + ':' + Pseudo);
MergeItems(Tag + '.' + AClass + ':' + Pseudo);
end;
end;
if AnID <> '' then
begin
MergeItems('#' + AnID);
MergeItems(Tag + '#' + AnID);
if AClass <> '' then
MergeItems('.' + AClass + '#' + AnID);
if Pseudo <> '' then
begin
MergeItems('#' + AnID + ':' + Pseudo);
MergeItems(Tag + '#' + AnID + ':' + Pseudo);
end;
if AClass <> '' then
begin
MergeItems(Tag + '.' + AClass + '#' + AnID);
if Pseudo <> '' then
begin
MergeItems('.' + AClass + '#' + AnID + ':' + Pseudo);
MergeItems(Tag + '.' + AClass + '#' + AnID + ':' + Pseudo);
end;
end;
end;
{process the entries in Styles to see if they are contextual selectors}
if NoHoverVisited then
MergeContextuals(Tag + ' ');
if Pseudo <> '' then
MergeContextuals(':' + Pseudo + ' ');
if (AClass <> '') and NoHoverVisited then
begin
MergeContextuals('.' + AClass + ' ');
MergeContextuals(Tag + '.' + AClass + ' ');
end;
if Pseudo <> '' then
begin
MergeContextuals(Tag + ':' + Pseudo + ' ');
if AClass <> '' then
begin
MergeContextuals('.' + AClass + ':' + Pseudo + ' ');
MergeContextuals(Tag + '.' + AClass + ':' + Pseudo + ' ');
end;
end;
if AnID <> '' then
begin
MergeContextuals('#' + AnID + ' ');
MergeContextuals(Tag + '#' + AnID + ' ');
if AClass <> '' then
MergeContextuals('.' + AClass + '#' + AnID + ' ');
if Pseudo <> '' then
begin
MergeContextuals('#' + AnID + ':' + Pseudo + ' ');
MergeContextuals(Tag + '#' + AnID + ':' + Pseudo + ' ');
end;
if AClass <> '' then
begin
MergeContextuals(Tag + '.' + AClass + '#' + AnID + ' ');
if Pseudo <> '' then
begin
MergeContextuals('.' + AClass + '#' + AnID + ':' + Pseudo + ' ');
MergeContextuals(Tag + '.' + AClass + '#' + AnID + ':' + Pseudo + ' ');
end;
end;
end;
if AProp <> nil then //the Style= attribute
Merge(AProp);
if (Tag = 'a') and not ((Pseudo = 'hover') or (Pseudo = 'active')) then
// BG, 02.02.2013: github-issue 25: Multiple pseudo elements can apply
// Just assign defaults for what is still unassigned:
MergeItems('::' + Pseudo, True); {default Pseudo definition}
if not (VarType(Props[FontSize]) in varNum) then {if still a ThtString, hasn't been converted}
Props[FontSize] := FontSizeConv(Props[FontSize], OldSize, FDefPointSize);
end;
var
BClass, S: ThtString;
I: Integer;
begin
BClass := Trim(AClass);
I := Pos('.', BClass);
if I <= 0 then
CombineX(Styles, Tag, BClass, AnID, PSeudo, '', AProp) {0 or 1 Class}
else
begin {more than one class}
repeat
S := System.Copy(BClass, 1, I - 1);
CombineX(Styles, Tag, S, AnID, PSeudo, '', nil);
Delete(BClass, 1, I);
BClass := Trim(BClass);
I := Pos('.', BClass);
until I <= 0;
CombineX(Styles, Tag, BClass, AnID, PSeudo, '', AProp);
CombineX(Styles, Tag, AClass, AnID, PSeudo, '', AProp);
end;
PropSym := Sym;
PropTag := Tag;
PropClass := AClass;
PropID := AnID;
PropPseudo := Pseudo;
PropStyle := AProp;
if ATitle <> '' then
PropTitle := ATitle;
if PSeudo = 'link' then
begin
if not Assigned(FIObj) then
FIObj := TFontInfoObj.Create;
CalcLinkFontInfo(Styles, PropStack.Count - 1);
InLink := True;
end;
end;
function TfrxHtProperties.GetFont: TfrxHtFont;
var
Font: TfrxHtFontInfo;
begin {call only if all things valid}
if TheFont = nil then
begin
GetSingleFontInfo(Font);
TheFont := frxAllMyFonts.GetFontLike(Font);
end;
Result := TheFont;
end;
{----------------LowerCaseUnquotedStr}
{Imporant: In many CSS values, a quoted string or anything
in parathesis should be left in it's original case.
Such a substring may be case-sensitive.
Examples include URI filenames and Base64-encoded data.
}
function LowerCaseUnquotedStr(const S : THtString) : THtString;
var
Top: ThtChar;
MoreStack: ThtString;
LCh : ThtChar;
idx, len : Integer;
procedure Push(Ch: ThtChar);
var
I: Integer;
begin
if Top <> EofChar then
begin
I := Length(MoreStack) + 1;
SetLength(MoreStack, I);
MoreStack[I] := Top;
end;
Top := Ch;
end;
procedure Pop;
var
I: Integer;
begin
I := Length(MoreStack);
if I > 0 then
begin
Top := MoreStack[I];
SetLength(MoreStack, I - 1);
end
else
Top := EofChar;
end;
begin
if (Pos('''',S) = 0) and (Pos('(',S)=0) and (Pos('"',S) = 0) then
begin
Result := LowerCase(S);
exit;
end;
len := Length(S);
SetLength(Result,len);
for idx := 1 to Len do begin
LCh := S[idx];
case LCh of
'(' :
begin
if LCh = Top then
Pop
else
Push(LCh);
end;
'''','"' :
begin
Push(LCh);
end;
')' :
begin
if LCh = Top then
Pop;
end;
'A'..'Z' :
begin
if Top = EofChar then
LCh := ThtChar(Word(LCh) or $0020);
end;
end;
Result[idx] := LCh;
end;
end;
{----------------RemoveQuotes}
function RemoveQuotes(const S: ThtString): ThtString;
{$ifdef UseInline} inline; {$endif}
{if ThtString is a quoted ThtString, remove the quotes (either ' or ")}
var
L: Integer;
begin
L := Length(S);
if (L >= 2) and (S[L] = S[1]) and ((S[1] = '''') or (S[1] = '"')) then
Result := Copy(S, 2, Length(S) - 2)
else
Result := S;
end;
{----------------ReadFontName}
function ReadFontName(S: ThtString): ThtString;
const
AMax = 5;
var
S1: ThtString;
Done: Boolean;
function NextFontName: ThtString;
const
Generic1: array[1..AMax] of ThtString = ('serif' , 'monospace', 'sans-serif', 'cursive' , 'helvetica');
Generic2: array[1..AMax] of ThtString = (FontSerif, FontMono , FontSans , FontCursive, FontHelvet );
var
I: Integer;
begin
I := Pos(',', S); {read up to the comma}
if I > 0 then
begin
Result := Trim(System.Copy(S, 1, I - 1));
Delete(S, 1, I);
end
else
begin {last item}
Result := Trim(S);
S := '';
end;
for I := 1 to AMax do
if htCompareText(Result, Generic1[I]) = 0 then
begin
Result := Generic2[I];
break;
end;
Result := RemoveQuotes(Result);
end;
begin
Done := False;
S1 := NextFontName;
while (S1 <> '') and not Done do
begin
Done := Screen.Fonts.IndexOf( htStringToString(S1) ) >= 0;
if Done then
Result := S1
else
S1 := NextFontName;
end;
end;
{----------------TfrxHtProperties.GetSingleFontInfo}
procedure TfrxHtProperties.GetSingleFontInfo(var Font: TfrxHtFontInfo);
var
Wt: Integer;
Style: TFontStyles;
begin {call only if all things valid}
Font.ibgColor := FontBG;
Font.iColor := Props[Color];
Style := [];
if Pos('bold', Props[FontWeight]) > 0 then
Include(Style, fsBold)
else
begin
Wt := StrToIntDef(Props[FontWeight], 0);
if Wt >= 600 then
Include(Style, fsBold);
end;
if (Props[FontStyle] = 'italic') or (Props[FontStyle] = 'oblique') then
Include(Style, fsItalic);
if Pos('underline', Props[TextDecoration]) > 0 then
Include(Style, fsUnderline);
if Pos('line-through', Props[TextDecoration]) > 0 then
Include(Style, fsStrikeOut);
Font.iStyle := Style;
Font.iSize := Props[FontSize];
Font.iCharset := CharSet;
Font.iCharExtra := Props[LetterSpacing];
Font.iName := ReadFontName(Props[FontFamily]);
if Font.iName = '' then
Font.iName := DefFontname;
end;
procedure TfrxHtProperties.CalcLinkFontInfo(Styles: TfrxHtStyleList; I: Integer);
{I is index in PropStack for this item}
procedure InsertNewProp(N: Integer; const Pseudo: ThtString);
begin
PropStack.Insert(N, TfrxHtProperties.Create(PropStack));
PropStack[N].Inherit('', PropStack[N - 1]);
PropStack[N].Combine(Styles, PropSym, PropTag, PropClass, PropID, Pseudo, PropTitle, PropStyle, nil {PropAttr}, N - 1);
end;
begin
PropStack[I].SetFontBG;
GetSingleFontInfo(FIObj.Info);
end;
procedure TfrxHtProperties.GetFontInfo(AFI: TFontInfoObj);
begin
AFI.Assign(FIObj);
end;
procedure TfrxHtProperties.GetVMarginArrayDefBorder(var MArray: ThtVMarginArray; const ADefColor : Variant);
var
I: ThtPropIndices;
BS: ThtBorderStyle;
NewColor : TColor;
LVal : THtString;
begin
for I := Low(MArray) to High(MArray) do
case I of
BorderTopStyle..BorderLeftStyle:
begin
BS := MArray[I];
GetBorderStyle(I, BS);
MArray[I] := BS;
end;
BorderTopColor..BorderLeftColor:
begin
if TryStrToColor(Props[I],False,NewColor) then
MArray[I] := Props[I]
else
begin
LVal := Props[I];
if LVal = CurColor_Val then
// 'currentColor'
MArray[I] := Props[frxHTMLStyleUn.Color]
else
MArray[I] := ADefColor;
end;
end
else
MArray[I] := Props[I];
end;
end;
procedure TfrxHtProperties.GetVMarginArray(var MArray: ThtVMarginArray);
{From: http://www.w3.org/TR/CSS21/box.html#x49
If an element's border color is not specified with a
border property, user agents must use the value of the
element's 'color' property as the computed value for
the border color.
}
begin
GetVMarginArrayDefBorder(MArray,Props[frxHTMLStyleUn.Color]);
end;
procedure TfrxHtProperties.AddPropertyByIndex(Index: ThtPropIndices; PropValue: ThtString; IsImportant: Boolean);
var
NewColor: TColor;
WhiteSpaceStyle : ThtWhiteSpaceStyle;
begin
case Index of
BorderTopColor..BorderLeftColor:
if TryStrToColor(PropValue, False, NewColor) then
Props[Index] := NewColor
else if LowerCase(PropValue) = CurColorStr then
Props[Index] := CurColor_Val;
Color, BackgroundColor:
if TryStrToColor(PropValue, False, NewColor) then
Props[Index] := NewColor
else if Index = Color then
Props[Index] := clBlack
else
Props[Index] := clNone;
Visibility:
if PropValue = 'visible' then
Props[Visibility] := viVisible
else if PropValue = 'hidden' then
Props[Visibility] := viHidden;
TextTransform:
if PropValue = 'uppercase' then
Props[TextTransform] := txUpper
else if PropValue = 'lowercase' then
Props[TextTransform] := txLower
else
Props[TextTransform] := txNone;
WordWrap:
if PropValue = 'break-word' then
Props[WordWrap] := PropValue
else
Props[WordWrap] := 'normal';
piWhiteSpace:
if TryStrToWhiteSpace(PropValue,WhiteSpaceStyle) then
Props[piWhiteSpace] := PropValue;
FontVariant:
if PropValue = 'small-caps' then
Props[FontVariant] := PropValue
else if PropValue = 'normal' then
Props[FontVariant] := 'normal';
else
Props[Index] := PropValue;
end;
Important[Index] := IsImportant;
end;
procedure TfrxHtProperties.AddPropertyByName(const PropName, PropValue: ThtString; IsImportant: Boolean);
var
Index: ThtPropIndices;
begin
if TryStrToPropIndex(PropName, Index) then
AddPropertyByIndex(Index, PropValue, IsImportant);
end;
{ TfrxHtStyleList }
constructor TfrxHtStyleList.Create;
begin
inherited Create;
Sorted := True;
Duplicates := dupAccept;
SeqNo := 10;
end;
destructor TfrxHtStyleList.Destroy;
begin
Clear;
inherited Destroy;
end;
procedure TfrxHtStyleList.Clear;
var
I: Integer;
begin
for I := 0 to Count - 1 do
TfrxHtProperties(Objects[I]).Free;
SeqNo := 10;
inherited;
end;
function TfrxHtStyleList.GetSeqNo: ThtString;
begin
{used to help sort contextual items by entry sequence}
// BG, 23.05.2013: Without fixed width for the number string entries 12 and 111 are sorted literally:
// 'ul 111'
// 'ul 12'.
// With fixed width the sort order is:
// 'ul 00012'
// 'ul 00111'
Result := htString(Format('%.5d', [SeqNo]));
Inc(SeqNo);
end;
procedure FixBordProps(AProp, BodyProp : TfrxHtProperties);
{$ifdef UseInline} inline; {$endif}
var i : ThtPropIndices;
begin
for i := BorderTopColor to BorderLeftColor do
AProp.Props[I] := BodyProp.Props[I];
end;
procedure TfrxHtStyleList.AddModifyProp(const Selector, Prop, Value: ThtString; IsImportant: Boolean);
{strings are all lowercase here}
var
I: Integer;
PropIndex: ThtPropIndices;
Propty: TfrxHtProperties;
NewColor: TColor;
NewProp: Boolean;
begin
if TryStrToPropIndex(Prop, PropIndex) then
begin
I := -1;
if not Find(Selector, I) then
begin
NewProp := True;
Propty := TfrxHtProperties.Create(); {newly created property}
Propty.DefPointSize := FDefPointSize;
end
else
begin
Propty := TfrxHtProperties(Objects[I]); {modify existing property}
NewProp := False;
end;
if PropIndex = Color then
begin
if TryStrToColor(Value, False, NewColor) then
begin
if Selector = ':link' then
begin {changed the defaults to be the same as link}
ModifyLinkColor('hover', NewColor);
ModifyLinkColor('visited', NewColor);
end
else if Selector = ':visited' then
ModifyLinkColor('hover', NewColor);
end;
end;
Propty.AddPropertyByIndex(PropIndex, Value, IsImportant);
if NewProp then
AddObject(Selector, Propty); {it's a newly created property}
if Pos(':hover', Selector) > 0 then
LinksActive := True;
if Selector = 'a' then
begin
AddModifyProp('::link', Prop, Value, IsImportant); {also applies to ::link}
end;
end;
end;
function TfrxHtStyleList.AddObject(const S: ThtString; AObject: TObject): Integer;
begin
Result := inherited AddObject(S, AObject);
TfrxHtProperties(AObject).PropTag := S;
TfrxHtProperties(AObject).FDefPointSize := DefPointSize;
end;
function TfrxHtStyleList.AddDuplicate(const Tag: ThtString; Prop: TfrxHtProperties): TfrxHtProperties;
begin
Result := TfrxHtProperties.Create(Prop.PropStack);
Result.Copy(Prop);
AddObject(Tag, Result);
end;
procedure TfrxHtStyleList.ModifyLinkColor(Pseudo: ThtString; AColor: TColor);
var
I: Integer;
begin
I := -1;
if Find('::' + Pseudo, I) then {the defaults}
with TfrxHtProperties(Objects[I]) do
Props[Color] := AColor;
end;
procedure TfrxHtStyleList.Initialize(const FontName, PreFontName: ThtString; PointSize: Double;
AColor, AHotspot, AVisitedColor, AActiveColor: TColor; LinkUnderline: Boolean;
ACodePage: TBuffCodePage; ACharSet: TFontCharSet; MarginHeight, MarginWidth: Integer);
type
ListTypes = (ul, ol, menu, dir, dl, dd, blockquote);
const
ListStr: array[Low(ListTypes)..High(ListTypes)] of ThtString =
('ul', 'ol', 'menu', 'dir', 'dl', 'dd', 'blockquote');
var
HIndex: Integer;
Properties: TfrxHtProperties;
J: ListTypes;
//F: Double;
begin
Clear;
DefPointSize := PointSize;
Properties := TfrxHtProperties.Create;
Properties.DefFontname := FontName;
Properties.Props[FontFamily] := FontName;
Properties.Props[FontSize] := PointSize;
Properties.Props[FontStyle] := 'none';
Properties.Props[FontWeight] := 'normal';
// Properties.Props[TextAlign] := 'left';
Properties.Props[TextDecoration] := 'none';
Properties.Props[TextTransform] := txNone;
Properties.Props[WordWrap] := 'normal';
Properties.Props[piWhiteSpace] := 'normal';
Properties.Props[FontVariant] := 'normal';
Properties.Props[Color] := AColor;
Properties.Props[MarginTop] := MarginHeight;
Properties.Props[MarginBottom] := MarginHeight;
Properties.Props[MarginLeft] := MarginWidth;
Properties.Props[MarginRight] := MarginWidth;
Properties.Props[Visibility] := viVisible;
Properties.Props[LetterSpacing] := 0;
Properties.Props[BoxSizing] := ContentBox;
Properties.CodePage := ACodePage;
Properties.CharSet := ACharSet;
AddObject('default', Properties);
FDefProp := Properties;
Properties := TfrxHtProperties.Create;
Properties.Props[Color] := AHotSpot or PalRelative;
if LinkUnderline then
Properties.Props[TextDecoration] := 'underline'
else
Properties.Props[TextDecoration] := 'none';
AddObject('::link', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[Color] := AVisitedColor or PalRelative;
AddObject('::visited', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[Color] := AActiveColor or PalRelative;
AddObject('::hover', Properties);
Properties := TfrxHtProperties.Create;
AddObject('null', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontFamily] := PreFontName;
Properties.Props[FontSize] := PointSize * 10.0 / 12.0;
Properties.Props[FontStyle] := 'none';
Properties.Props[FontWeight] := 'normal';
Properties.Props[TextDecoration] := 'none';
Properties.Props[piWhiteSpace] := 'pre';
AddObject('pre', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[MarginTop] := AutoParagraph;
Properties.Props[MarginBottom] := AutoParagraph;
AddObject('p', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[MarginTop] := 0;
AddObject('p 11pre', Properties);
for J := Low(ListTypes) to High(ListTypes) do
begin
Properties := TfrxHtProperties.Create;
case J of
ol, ul, menu, dir:
begin
Properties.Props[ListStyleType] := 'blank';
Properties.Props[MarginTop] := AutoParagraph;
Properties.Props[MarginBottom] := AutoParagraph;
Properties.Props[MarginLeft] := IntNull;
Properties.Props[PaddingLeft] := ListIndent;
end;
dl:
begin
Properties.Props[ListStyleType] := 'none';
Properties.Props[MarginLeft] := 0;
Properties.Props[MarginTop] := 0;
Properties.Props[MarginBottom] := 0;
Properties.Props[MarginLeft] := 0;
end;
blockquote:
begin
Properties.Props[MarginTop] := AutoParagraph;
Properties.Props[MarginBottom] := ParagraphSpace;
Properties.Props[MarginLeft] := ListIndent;
end;
dd:
begin
Properties.Props[MarginTop] := 0;
Properties.Props[MarginBottom] := 0;
Properties.Props[MarginLeft] := ListIndent;
end;
end;
AddObject(ListStr[J], Properties);
end;
Properties := TfrxHtProperties.Create;
Properties.Props[FontFamily] := PrefontName;
Properties.Props[FontSize] := '0.83em'; {10.0 / 12.0;}
AddObject('code', Properties);
AddDuplicate('tt', Properties);
AddDuplicate('kbd', Properties);
AddDuplicate('samp', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontWeight] := 'bold';
AddObject('b', Properties);
AddDuplicate('strong', Properties);
AddDuplicate('th', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[TextAlign] := 'none';
AddObject('table', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontSize] := '0.83em';
Properties.Props[VerticalAlign] := 'super';
AddObject('sup', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontSize] := '0.83em';
Properties.Props[VerticalAlign] := 'sub';
AddObject('sub', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontSize] := '1.17em';
AddObject('big', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontSize] := '0.83em';
AddObject('small', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontStyle] := 'italic';
AddObject('i', Properties);
AddDuplicate('em', Properties);
AddDuplicate('cite', Properties);
AddDuplicate('var', Properties);
AddDuplicate('dfn', Properties);
AddDuplicate('address', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[TextDecoration] := 'underline';
AddObject('u', Properties);
AddDuplicate('ins',Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[TextDecoration] := 'line-through';
AddObject('s', Properties);
AddDuplicate('strike', Properties);
AddDuplicate('del',Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[TextAlign] := 'center';
AddObject('center', Properties);
AddDuplicate('caption', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[FontFamily] := 'Arial Unicode MS, Arial';
Properties.Props[FontSize] := '10pt';
Properties.Props[FontStyle] := 'none';
Properties.Props[FontWeight] := 'normal';
Properties.Props[TextAlign] := 'left';
Properties.Props[TextDecoration] := 'none';
Properties.Props[Color] := AColor;
AddObject('input', Properties);
AddDuplicate('select', Properties);
Properties := AddDuplicate('textarea', Properties);
Properties.Props[FontFamily] := PreFontName;
Properties := TfrxHtProperties.Create;
Properties.Props[MarginLeft] := 0;
Properties.Props[MarginRight] := 0;
Properties.Props[MarginTop] := 10;
Properties.Props[MarginBottom] := 10;
AddObject('hr', Properties);
for HIndex := 1 to 6 do
begin
Properties := TfrxHtProperties.Create;
//F := PointSize / 12.0;
case HIndex of
1: Properties.Props[FontSize] := '2em';
2: Properties.Props[FontSize] := '1.5em';
3: Properties.Props[FontSize] := '1.17em';
else
Properties.Props[FontSize] := '1em';
end;
case HIndex of
4: Properties.Props[MarginTop] := '1.67em';
5: Properties.Props[MarginTop] := '1.5em';
6: Properties.Props[MarginTop] := '1.12em';
else
Properties.Props[MarginTop] := 19;
end;
Properties.Props[MarginBottom] := Properties.Props[MarginTop];
Properties.Props[FontWeight] := 'bolder';
AddObject( htString('h' + IntToStr(HIndex)), Properties);
end;
Properties := TfrxHtProperties.Create;
Properties.Props[FontStyle] := 'none';
Properties.Props[BackgroundColor] := $00FFFF;
Properties.Props[Color] := $000000;
AddObject('mark', Properties);
Properties := TfrxHtProperties.Create;
Properties.Props[ frxHTMLStyleUn.BorderBottomStyle ] := 'dotted';
Properties.Props[ frxHTMLStyleUn.BorderBottomWidth ] := '1px';
AddObject('abbr', Properties);
AddDuplicate('acronym',Properties);
end;
{ TfrxHtPropStack }
function TfrxHtPropStack.GetProp(Index: Integer): TfrxHtProperties;
begin
Result := Get(Index); //TfrxHtProperties(inherited Items[Index]);
end;
function TfrxHtPropStack.Last: TfrxHtProperties;
begin
Result := Get(Count - 1);
end;
const
NumColors = 178;
Colors: array[1..NumColors] of ThtString = (
'none', 'transparent',
'black', 'maroon', 'green', 'olive', 'navy', 'purple', 'teal', 'gray',
'silver', 'red', 'lime', 'yellow', 'blue', 'fuchsia', 'aqua', 'white',
'aliceblue', 'antiquewhite', 'aquamarine', 'azure', 'beige',
'bisque', 'blanchedalmond', 'blueviolet', 'brown', 'burlywood',
'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue',
'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan',
'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta',
'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon',
'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet',
'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick',
'floralwhite', 'forestgreen', 'gainsboro', 'ghostwhite', 'gold',
'goldenrod', 'greenyellow', 'honeydew', 'hotpink', 'indianred',
'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush',
'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan',
'lightgoldenrodyellow', 'lightgreen', 'lightgray', 'lightpink', 'lightsalmon',
'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow',
'limegreen', 'linen', 'magenta', 'mediumaquamarine', 'mediumblue',
'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose',
'moccasin', 'navajowhite', 'oldlace', 'olivedrab', 'orange',
'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise',
'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink',
'plum', 'powderblue', 'rosybrown', 'royalblue', 'saddlebrown',
'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna',
'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen',
'steelblue', 'tan', 'thistle', 'tomato', 'turquoise',
'violet', 'wheat', 'whitesmoke', 'yellowgreen',
'grey', 'darkgrey', 'darkslategrey', 'dimgrey', 'lightgrey', 'lightslategrey', 'slategrey',
'rebeccapurple', //CSS4 tribute to Eric Meyer's daughter, Rebecca, died of cancer on her sixth birthday
'background', 'activecaption', 'inactivecaption', 'menu', 'window',
'windowframe', 'menutext', 'windowtext', 'captiontext', 'activeborder',
'inactiveborder', 'appworkSpace', 'highlight', 'hightlighttext', 'buttonface',
'buttonshadow', 'graytext', 'buttontext', 'inactivecaptiontext', 'buttonhighlight',
'threeddarkshadow', 'threedlightshadow', 'infotext', 'infobackground', 'scrollbar',
'threedface', 'threedhighlight', 'threedshadow');
ColorValues: array[1..NumColors] of TColor = (clNone, clNone,
clBLACK, clMAROON, clGREEN, clOLIVE, clNAVY, clPURPLE, clTEAL, clGRAY,
clSILVER, clRED, clLIME, clYELLOW, clBLUE, clFUCHSIA, clAQUA, clWHITE,
$FFF8F0, $D7EBFA, $D4FF7F, $FFFFF0, $DCF5F5,
$C4E4FF, $CDEBFF, $E22B8A, $2A2AA5, $87B8DE,
$A09E5F, $00FF7F, $1E69D2, $507FFF, $ED9564,
$DCF8FF, $3614DC, $FFFF00, $8B0000, $8B8B00,
$0B86B8, $A9A9A9, $006400, $6BB7BD, $8B008B,
$2F6B55, $008CFF, $CC3299, $00008B, $7A96E9,
$8FBC8F, $8B3D48, $4F4F2F, $D1CE00, $D30094,
$9314FF, $FFBF00, $696969, $FF901E, $2222B2,
$F0FAFF, $228B22, $DCDCDC, $FFF8F8, $00D7FF,
$20A5DA, $2FFFAD, $F0FFF0, $B469FF, $5C5CCD,
$82004B, $F0FFFF, $8CE6F0, $FAE6E6, $F5F0FF,
$00FC7C, $CDFAFF, $E6D8AD, $8080F0, $FFFFE0,
$D2FAFA, $90EE90, $D3D3D3, $C1B6FF, $7AA0FF,
$AAB220, $FACE87, $998877, $DEC4B0, $E0FFFF,
$32CD32, $E6F0FA, $FF00FF, $AACD66, $CD0000,
$D355BA, $DB7093, $71B33C, $EE687B, $9AFA00,
$CCD148, $8515C7, $701919, $FAFFF5, $E1E4FF,
$B5E4FF, $ADDEFF, $E6F5FD, $238E6B, $00A5FF,
$0045FF, $D670DA, $AAE8EE, $98FB98, $EEEEAF,
$9370DB, $D5EFFF, $B9DAFF, $3F85CD, $CBC0FF,
$DDA0DD, $E6E0B0, $8F8FBC, $E16941, $13458B,
$7280FA, $60A4F4, $578B2E, $EEF5FF, $2D52A0,
$EBCE87, $CD5A6A, $908070, $FAFAFF, $7FFF00,
$B48246, $8CB4D2, $D8BFD8, $4763FF, $D0E040,
$EE82EE, $B3DEF5, $F5F5F5, $32CD9A,
clGray, $A9A9A9, $4F4F2F, $696969, $D3D3D3, $998877, $908070,
$663399,
clBackground, clActiveCaption, clInactiveCaption, clMenu, clWindow,
clWindowFrame, clMenuText, clWindowText, clCaptionText, clActiveBorder,
clInactiveBorder, clAppWorkSpace, clHighlight, clHighlightText, clBtnFace,
clBtnShadow, clGrayText, clBtnText, clInactiveCaptionText, clBtnHighlight,
cl3DDkShadow, clBtnHighlight, clInfoText, clInfoBk, clScrollBar,
clBtnFace, cl3DLight, clBtnShadow);
var
ColorStrings: ThtStringList;
function SortedColors: ThtStringList;
var
I: Integer;
begin
// Put the Colors into a sorted StringList for faster access.
if ColorStrings = nil then
begin
ColorStrings := ThtStringList.Create;
for I := 1 to NumColors do
ColorStrings.AddObject(Colors[I], @ColorValues[I]);
ColorStrings.Sort;
end;
Result := ColorStrings;
end;
function OpacityFromStr(S : ThtString) : Byte;
{$ifdef UseInline} inline; {$endif}
var LErr : Integer;
LR : Real;
begin
Val(S,LR,LErr);
if LErr <> 0 then begin
Result := 255;
end else begin
Result := Trunc(255 * LR);
end;
end;
function TryStrToColor(S: ThtString; NeedPound: Boolean; out Color: TColor): Boolean;
{$ifdef UseInline} inline; {$endif}
var LDummy : Byte;
begin
Result := ColorAndOpacityFromString(S,NeedPound,Color,LDummy);
end;
function ColorAndOpacityFromString(S: ThtString; NeedPound: Boolean; out Color: TColor; out VOpacity : Byte): Boolean;
{Translate StyleSheet color ThtString to Color. If NeedPound is true, a '#' sign
is required to preceed a hexidecimal value.}
const
LastS: ThtString = '?&%@';
LastColor: TColor = 0;
var
I, Rd, Bl: Integer;
S1: ThtString;
function FindHSLColor(S: ThtString): Boolean;
type
Colors = (hue, saturation, luminance);
var
I, J: Integer;
var
A: array[hue..luminance] of ThtString;
C: array[hue..luminance] of Integer;
K: Colors;
begin
I := Pos('(', S);
J := Pos(')', S);
if (I > 0) and (J > 0) then
begin
S := Copy(S, 1, J - 1);
S := Trim(Copy(S, I + 1, 255));
for K := hue to saturation do
begin
I := Pos(',', S);
A[K] := Trim(Copy(S, 1, I - 1));
S := Trim(Copy(S, I + 1, 255));
end;
I := Pos(',', S);
if I > 0 then begin
A[luminance] := Trim(Copy(S, 1, I - 1));
S := Trim(Copy(S, I + 1, 255));
VOpacity := OpacityFromStr(S);
end else begin
A[luminance] := S;
VOpacity := 255;
end;
C[hue] := StrToIntDef( htStringToString(A[hue]), 0);
while C[hue] >= 360 do begin
C[hue] := C[hue] - 360;
end;
while C[hue] < 0 do begin
C[hue] := C[hue] + 360;
end;
for K := saturation to luminance do begin
I := Pos('%', A[K]);
if I > 0 then begin
Delete(A[K], I, 1);
end;
C[K] := StrToIntDef( htStringToString(A[K]), 0);
if C[K] > 100 then begin
C[K] := 100;
end;
if C[K] < 0 then begin
C[K] := 0;
end;
end;
Color := HSL2Color(C[hue],C[saturation],C[luminance]);
Result := True;
end
else
Result := False;
end;
function FindRGBColor(S: ThtString): Boolean;
type
Colors = (red, green, blue);
var
A: array[red..blue] of ThtString;
C: array[red..blue] of Integer;
I, J: Integer;
K: Colors;
begin
I := Pos('(', S);
J := Pos(')', S);
if (I > 0) and (J > 0) then
begin
S := copy(S, 1, J - 1);
S := Trim(Copy(S, I + 1, 255));
for K := Red to Green do
begin
I := Pos(',', S);
A[K] := Trim(copy(S, 1, I - 1));
S := Trim(Copy(S, I + 1, 255));
end;
I := Pos(',', S);
if I > 0 then begin
A[blue] := Trim(copy(S, 1, I - 1));
S := Trim(Copy(S, I + 1, 255));
VOpacity := OpacityFromStr(S);
end else begin
A[blue] := S;
VOpacity := 255;
end;
for K := Red to Blue do
begin
I := Pos('%', A[K]);
if I > 0 then
begin
Delete(A[K], I, 1);
try
C[K] := Round( StrToFloat( htStringToString(A[K])) * 2.55);
except
C[K] := 0;
end;
end
else
C[K] := StrToIntDef( htStringToString(A[K]), 0);
C[K] := Max(0, Min(255, C[K]));
end;
Color := (C[Blue] shl 16) or (C[Green] shl 8) or C[Red];
Result := True;
end
else
Result := False;
end;
//BG, 26.08.2009: exceptions are very slow
var
Int: Integer;
Idx : Integer;
//BG, 26.08.2009
begin
//Opacity is not supported with # hexidecimal notation or color names
VOpacity := 255;
if S = '' then
begin
Result := False;
Exit;
end;
S := Lowercase(Trim(S));
if S = LastS then
begin {inquiries often come in pairs, this saves some recomputing}
Color := LastColor;
Result := True;
Exit;
end;
I := Pos('hsl',S);
if I > 0 then begin
Result := FindHSLColor(Copy(S, I + 3, 255));
if Result then
begin
LastS := S1;
LastColor := Color;
end;
exit;
end;
I := Pos('rgb', S);
if (I = 0) and (S[1] <> '#') then
begin
Idx := -1;
if SortedColors.Find(S, Idx) then
begin
Color := PColor(SortedColors.Objects[Idx])^;
Result := True;
LastS := S;
LastColor := Color;
Exit;
end;
end;
S1 := S;
if (I > 0) then
Result := FindRGBColor(Copy(S, I + 3, 255))
else
begin
// try
I := Pos('#', S);
if I > 0 then
while I > 0 do {sometimes multiple ##}
begin
Delete(S, 1, I);
I := Pos('#', S);
end
else if NeedPound then
begin
Result := False;
Exit;
end;
S := Trim(S);
if Length(S) <= 3 then
for I := Length(S) downto 1 do
Insert(S[I], S, I); {Double each character}
Result := TryStrToInt('$' + htStringToString(S), Int);
if Result then
begin
{ok, but bytes are backwards!}
Rd := Int and $FF;
Bl := Int and $FF0000;
Color := (Int and $00FF00) + (Rd shl 16) + (Bl shr 16) or PalRelative;
end;
// except
// Result := False;
// end;
end;
if Result then
begin
LastS := S1;
LastColor := Color;
end;
end;
//BG, 14.07.2010:
function DecodeSize(const Str: ThtString; out V: extended; out U: ThtString): Boolean;
{$ifdef UseInline} inline; {$endif}
var
I, J, L: Integer;
begin
U := '';
Val(Str, V, I);
Result := I <> 1;
if Result then
begin
L := Length(Str);
if I = 0 then
I := L + 1;
J := Pos('e', Str); {'e' would be legal for Val but not for us}
if (J > 0) and (I > J) then
I := J;
if I <= L then
begin
Val(Copy(Str, 1, I - 1), V, J);
U := Trim(Copy(Str, I, L - I + 1)); // text after number, maybe a unit
end;
end;
end;
function IncFontSize(OldSize: Double; Increment: ThtFontSizeIncrement): Double;
{$ifdef UseInline} inline; {$endif}
var
OldIndex, NewIndex: Byte;
D1, D2: Double;
begin
// get nearest old font size index
OldIndex := 4;
D1 := OldSize - FontConv[OldIndex];
repeat
case Sign(D1) of
-1:
begin
Dec(OldIndex);
D2 := OldSize - FontConv[OldIndex];
if D2 >= 0 then
begin
if Abs(D1) < Abs(D2) then
Inc(OldIndex);
break;
end;
D1 := D2;
end;
1:
begin
Inc(OldIndex);
D2 := OldSize - FontConv[OldIndex];
if D2 <= 0 then
begin
if Abs(D1) > Abs(D2) then
Dec(OldIndex);
break;
end;
D1 := D2;
end;
else
break;
end;
until (OldIndex = 1) or (OldIndex = 7);
NewIndex := OldIndex + Increment;
if NewIndex < 1 then
begin
Inc(OldIndex, 1 - NewIndex);
NewIndex := 1;
end
else if NewIndex > 7 then
begin
Dec(OldIndex, NewIndex - 7);
NewIndex := 7;
end;
if OldIndex = NewIndex then
Result := OldSize
else
Result := OldSize * FontConv[NewIndex] / FontConv[OldIndex];
end;
function FontSizeConv(const Str: ThtString; OldSize, DefPointSize : Double): Double;
{$ifdef UseInline} inline; {$endif}
{given a font-size ThtString, return the point size}
var
V, VScreen: extended;
U: ThtString;
begin
if DecodeSize(Str, V, U) then
begin
VScreen := V / Screen.PixelsPerInch;
if (U = 'px') or (U = '') then
Result := VScreen * 72.0
else if U = 'in' then
Result := VScreen * 72.0 * 96.0
else if U = 'cm' then
Result := VScreen * 72.0 * 96.0 / 2.54
else if U = 'mm' then
Result := VScreen * 72.0 * 96.0 / 25.4
else if U = 'pt' then
Result := VScreen * 96.0
else if U = 'pc' then
Result := VScreen * 72.0 * 16
else if U = 'em' then
Result := V * OldSize
else if U = 'ex' then
Result := V * OldSize * 0.5 {1/2 of em}
else if U = '%' then
Result := V * OldSize / 100
else
Result := DefPointSize;
end
else
begin
U := Str;
if U = 'smaller' then
Result := IncFontSize(OldSize, -1) // CSS1: 0.75 * OldSize
else if U = 'larger' then
Result := IncFontSize(OldSize, 1) // CSS1: 1.25 * OldSize
else if U = 'xx-small' then
Result := FontConv[1]
else if U = 'x-small' then
Result := FontConv[1] // same size xx-small (IE and Firefox do it).
else if U = 'small' then
Result := FontConv[2]
else if U = 'medium' then // 'medium' is the user's preferred font size.
Result := FontConv[3]
else if U = 'large' then
Result := FontConv[4]
else if U = 'x-large' then
Result := FontConv[5]
else if U = 'xx-large' then
Result := FontConv[6]
else
Result := DefPointSize;
end;
end;
{----------------LengthConv}
function LengthConv(const Str: ThtString; Relative: Boolean; Base, EmSize, ExSize, Default: Integer): Integer;
{$ifdef UseInline} inline; {$endif}
{given a length ThtString, return the appropriate pixel value. Base is the
base value for percentage. EmSize, ExSize for units relative to the font.
Relative makes a numerical entry relative to Base.
Default returned if no match.}
var
V: Extended;
U: ThtString;
begin
if DecodeSize(Str, V, U) then
begin
{U the units}
if Relative and (U = '') then
V := V * Base
else if (U = 'px') or (U = '') then
V := V
else if U = 'in' then
V := V * 96.0
else if U = 'cm' then
V := V * 96.0 / 2.54
else if U = 'mm' then
V := V * 96.0 / 25.4
else if U = 'pt' then
V := V * 96.0 / 72.0
else if U = 'pc' then
V := V * 16
else if U = '%' then
V := V * Base / 100
else if U = 'em' then
V := V * EmSize
else if U = 'ex' then
V := V * ExSize
else
V := Default;
Result := Trunc(V); // BG, 14.12.2011: issue 104: avoid too wide "50%". Replace Round() with Trunc().
end
else
// anything else but a number, maybe 'auto'
Result := Default;
end;
procedure CalcAutoMinMaxConstraints(W, H, MinW, MaxW, MinH, MaxH: Integer; out ResW, ResH: Integer);
// solving min/max constraint violations as described in CSS 2.1
procedure AdjustMinMax(var Min, Max: Integer);
begin
if Min < 0 then
Min := 0;
if Max < 0 then
Max := MaxInt;
if Max < Min then
Max := Min;
end;
var
FacW, FacH: Double;
begin
AdjustMinMax(MinW, MaxW);
AdjustMinMax(MinH, MaxH);
{
if (w > max-width)
if (h < min-height)
max-width min-height
else if (h > max-height)
if (max-width/w > max-height/h)
max(max-height * w/h, min-width) max-height
else
max-width max(min-height, max-width * h/w)
else
max-width max(min-height, max-width * h/w)
}
if W > MaxW then
begin
if H < MinH then
begin
ResW := MaxW;
ResH := MinH;
Exit;
end;
FacW := MaxW / W;
if H > MaxH then
begin
FacH := MaxH / H;
if FacW > FacH then
begin
ResH := MaxH;
ResW := Max(Round(FacH * W), MinW);
Exit;
end;
end;
ResW := MaxW;
ResH := Max(Round(FacW * H), MinH);
Exit;
end;
{
if (w < min-width)
if (h > max-height)
min-width max-height
else if (h < min-height)
if (min-width/w <= min-height/h)
min(max-width, min-height * w/h) min-height
else
min-width min(min-width * h/w, max-height)
else
min-width min(min-width * h/w, max-height)
}
if W < MinW then
begin
if H > MaxH then
begin
ResW := MinW;
ResH := MaxH;
Exit;
end;
FacW := MinW / W;
if H < MinH then
begin
FacH := MinH / H;
if FacW <= FacH then
begin
ResH := MinH;
ResW := Min(Round(FacH * W), MaxW);
Exit;
end;
end;
ResW := MinW;
ResH := Min(Round(FacW * H), MaxH);
Exit;
end;
{ h > max-height max(max-height * w/h, min-width) max-height }
if H > MaxH then
begin
ResH := MaxH;
ResW := Max((MaxH * W) div H, MinW);
Exit;
end;
{ h < min-height min(min-height * w/h, max-width) min-height }
if H < MinH then
begin
ResH := MinH;
ResW := Min((MinH * W) div H, MaxW);
Exit
end;
ResW := W;
ResH := H;
end;
initialization
finalization
FreeAndNil(ColorStrings);
FreeAndNil(PropertyStrings);
end.