mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-29 09:30:37 +02:00
fpvectorial: Starts rewriting svg text support to support nested spans
git-svn-id: trunk@44383 -
This commit is contained in:
parent
32f60ff178
commit
2d16167f02
@ -148,8 +148,10 @@ type
|
||||
spbfBrushColor, spbfBrushStyle, spbfBrushGradient,
|
||||
spbfFontColor, spbfFontSize, spbfFontName, spbfFontBold, spbfFontItalic,
|
||||
spbfFontUnderline, spbfFontStrikeThrough, spbfAlignment,
|
||||
//
|
||||
sseMarginTop, sseMarginBottom, sseMarginLeft, sseMarginRight
|
||||
// Page style
|
||||
sseMarginTop, sseMarginBottom, sseMarginLeft, sseMarginRight,
|
||||
// Positioning
|
||||
ssePosition
|
||||
);
|
||||
|
||||
TvSetStyleElements = set of TvSetStyleElement;
|
||||
@ -177,9 +179,11 @@ type
|
||||
Pen: TvPen;
|
||||
Brush: TvBrush;
|
||||
Font: TvFont;
|
||||
//
|
||||
// Page style
|
||||
MarginTop, MarginBottom, MarginLeft, MarginRight: Double; // in mm
|
||||
SuppressSpacingBetweenSameParagraphs : Boolean;
|
||||
// Positioning
|
||||
X, Y: Double;
|
||||
//
|
||||
SetElements: TvSetStyleElements;
|
||||
//
|
||||
@ -190,6 +194,7 @@ type
|
||||
procedure CopyFrom(AFrom: TvStyle);
|
||||
procedure ApplyOver(AFrom: TvStyle);
|
||||
function CreateStyleCombinedWithParent: TvStyle;
|
||||
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual;
|
||||
end;
|
||||
|
||||
TvListStyleKind = (vlskBullet, vlskNumeric);
|
||||
@ -392,7 +397,7 @@ type
|
||||
function AdjustColorToBackground(AColor: TFPColor; ARenderInfo: TvRenderInfo): TFPColor;
|
||||
function GetNormalizedPos(APage: TvVectorialPage; ANewMin, ANewMax: Double): T3DPoint;
|
||||
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual;
|
||||
function GenerateDebugStrForFPColor(AColor: TFPColor): string;
|
||||
class function GenerateDebugStrForFPColor(AColor: TFPColor): string;
|
||||
end;
|
||||
|
||||
TvEntityClass = class of TvEntity;
|
||||
@ -1740,6 +1745,41 @@ begin
|
||||
if Parent <> nil then Result.ApplyOver(Parent);
|
||||
end;
|
||||
|
||||
function TvStyle.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
|
||||
APageItem: Pointer): Pointer;
|
||||
var
|
||||
lStr, lParentName: string;
|
||||
begin
|
||||
if Parent <> nil then lParentName := Parent.Name
|
||||
else lParentName := '<No Parent>';
|
||||
|
||||
lStr := Format('[%s] Name=%s Parent=%s',
|
||||
[Self.ClassName, Name, lParentName]);
|
||||
|
||||
if spbfPenColor in SetElements then
|
||||
begin
|
||||
lStr := lStr + Format('Pen.Color==%s', [TvEntity.GenerateDebugStrForFPColor(Pen.Color)]);
|
||||
end;
|
||||
{ // Pen, Brush and Font
|
||||
spbfPenColor, spbfPenStyle, spbfPenWidth,
|
||||
spbfBrushColor, spbfBrushStyle, spbfBrushGradient,
|
||||
spbfFontColor, spbfFontSize, spbfFontName, spbfFontBold, spbfFontItalic,
|
||||
spbfFontUnderline, spbfFontStrikeThrough, spbfAlignment,
|
||||
// Page style
|
||||
sseMarginTop, sseMarginBottom, sseMarginLeft, sseMarginRight,
|
||||
// Positioning
|
||||
ssePosition
|
||||
);
|
||||
{ ,
|
||||
Font.Size, Font.Name, Font.Orientation,
|
||||
BoolToStr(Font.Bold),
|
||||
BoolToStr(Font.Italic),
|
||||
BoolToStr(Font.Underline),
|
||||
BoolToStr(Font.StrikeThrough),
|
||||
GetEnumName(TypeInfo(TvTextAnchor), integer(TextAnchor))}
|
||||
Result := ADestRoutine(lStr, APageItem);
|
||||
end;
|
||||
|
||||
{ TvTableRow }
|
||||
|
||||
constructor TvTableRow.create(APage: TvPage);
|
||||
@ -2273,7 +2313,7 @@ begin
|
||||
Result := ADestRoutine(lStr, APageItem);
|
||||
end;
|
||||
|
||||
function TvEntity.GenerateDebugStrForFPColor(AColor: TFPColor): string;
|
||||
class function TvEntity.GenerateDebugStrForFPColor(AColor: TFPColor): string;
|
||||
begin
|
||||
Result := IntToHex(AColor.Red div $100, 2) + IntToHex(AColor.Green div $100, 2) + IntToHex(AColor.Blue div $100, 2) + IntToHex(AColor.Alpha div $100, 2);
|
||||
end;
|
||||
@ -3238,6 +3278,11 @@ begin
|
||||
GetEnumName(TypeInfo(TvTextAnchor), integer(TextAnchor))
|
||||
]);
|
||||
Result := ADestRoutine(lStr, APageItem);
|
||||
// Add the style as a sub-item
|
||||
if Style <> nil then
|
||||
begin
|
||||
Style.GenerateDebugTree(ADestRoutine, Result);
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TvCircle }
|
||||
@ -4959,7 +5004,8 @@ var
|
||||
lStr: string;
|
||||
lCurEntity: TvEntity;
|
||||
begin
|
||||
lStr := Format('[%s] Name="%s"' + FExtraDebugStr, [Self.ClassName, Self.Name]);
|
||||
lStr := Format('[%s] Name="%s" X=%f Y=%f' + FExtraDebugStr,
|
||||
[Self.ClassName, Self.Name, X, Y]);
|
||||
|
||||
// Add styles
|
||||
// Pen
|
||||
|
@ -14,7 +14,7 @@ unit svgvectorialreader;
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, math,
|
||||
Classes, SysUtils, math, contnrs,
|
||||
fpimage, fpcanvas, laz2_xmlread, laz2_dom, fgl,
|
||||
// image data formats
|
||||
fpreadpng,
|
||||
@ -48,6 +48,13 @@ type
|
||||
|
||||
TSVGTokenList = specialize TFPGList<TSVGToken>;
|
||||
|
||||
{ TSVGObjectStack }
|
||||
|
||||
TSVGObjectStack = class(TObjectStack)
|
||||
public
|
||||
function GetList: TList;
|
||||
end;
|
||||
|
||||
{ TSVGPathTokenizer }
|
||||
|
||||
TSVGPathTokenizer = class
|
||||
@ -138,6 +145,13 @@ const
|
||||
FLOAT_MILIMETERS_PER_PIXEL = 5*0.2822; // DPI 90 = 1 / 90 inches per pixel => Actually I changed the value by this factor! Because otherwise it looks ugly!
|
||||
FLOAT_PIXELS_PER_MILIMETER = 1 / FLOAT_MILIMETERS_PER_PIXEL; // DPI 90 = 1 / 90 inches per pixel
|
||||
|
||||
{ TSVGObjectStack }
|
||||
|
||||
function TSVGObjectStack.GetList: TList;
|
||||
begin
|
||||
Result := List;
|
||||
end;
|
||||
|
||||
{ TSVGPathTokenizer }
|
||||
|
||||
constructor TSVGPathTokenizer.Create;
|
||||
@ -2283,23 +2297,119 @@ begin
|
||||
Result := lRect;
|
||||
end;
|
||||
|
||||
{
|
||||
<text x="0" y="15" fill="red" transform="rotate(30 20,40)">I love SVG</text>
|
||||
|
||||
Example with nested tspan:
|
||||
|
||||
<text class="TextShape">
|
||||
<tspan class="TextParagraph" font-family="Times New Roman, serif" font-size="917px" font-weight="400">
|
||||
<tspan class="TextPosition" x="3650" y="11251">
|
||||
<tspan fill="rgb(0,0,0)" stroke="none">This </tspan>
|
||||
<tspan fill="rgb(197,0,11)" stroke="none">size</tspan>
|
||||
<tspan fill="rgb(0,0,0)" stroke="none"> is bigger.</tspan>
|
||||
</tspan>
|
||||
</tspan>
|
||||
</text>
|
||||
}
|
||||
function TvSVGVectorialReader.ReadTextFromNode(ANode: TDOMNode;
|
||||
AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
|
||||
var
|
||||
lTextStr: string = '';
|
||||
lx, ly: double;
|
||||
lText: TvText;
|
||||
lParagraph: TvParagraph;
|
||||
lTextSpanStack: TSVGObjectStack;
|
||||
lCurStyle: TvStyle;
|
||||
i: Integer;
|
||||
lNodeName, lNodeValue: DOMString;
|
||||
lCurNode: TDOMNode;
|
||||
|
||||
procedure ApplyStackStylesToText(ADest: TvText);
|
||||
var
|
||||
j: Integer;
|
||||
begin
|
||||
for j := 0 to lTextSpanStack.GetList().Count-1 do
|
||||
begin
|
||||
lCurStyle := TvStyle(lTextSpanStack.GetList().Items[j]);
|
||||
if ADest.Style = nil then ADest.Style := TvStyle.Create;
|
||||
ADest.Style.ApplyOver(lCurStyle);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure ReadTextSpans(ACurNode: TDOMNode);
|
||||
var
|
||||
j: Integer;
|
||||
lCurNode: TDOMNode;
|
||||
lCurObject: TObject;
|
||||
begin
|
||||
lCurNode := ACurNode.FirstChild;
|
||||
while lCurNode <> nil do
|
||||
begin
|
||||
lNodeName := LowerCase(lCurNode.NodeName);
|
||||
lNodeValue := LowerCase(lCurNode.NodeValue);
|
||||
|
||||
if lNodeName = 'tspan' then
|
||||
begin
|
||||
lCurStyle := TvStyle.Create;
|
||||
lTextSpanStack.Push(lCurStyle);
|
||||
|
||||
// read the attributes
|
||||
for j := 0 to lCurNode.Attributes.Length - 1 do
|
||||
begin
|
||||
lNodeName := lCurNode.Attributes.Item[j].NodeName;
|
||||
lNodeValue := lCurNode.Attributes.Item[j].NodeValue;
|
||||
if lNodeName = 'x' then
|
||||
begin
|
||||
Include(lCurStyle.SetElements, ssePosition);
|
||||
lCurStyle.X := StringWithUnitToFloat(lNodeValue)
|
||||
end
|
||||
else if lNodeName = 'y' then
|
||||
begin
|
||||
Include(lCurStyle.SetElements, ssePosition);
|
||||
lCurStyle.Y := StringWithUnitToFloat(lNodeValue)
|
||||
end
|
||||
//else if lNodeName = 'id' then
|
||||
// lText.Name := lNodeValue
|
||||
//else if lNodeName = 'style' then
|
||||
// ReadSVGStyle(lNodeValue, lParagraph)
|
||||
else if IsAttributeFromStyle(lNodeName) then
|
||||
begin
|
||||
//ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
|
||||
//ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Recursion
|
||||
ReadTextSpans(lCurNode);
|
||||
|
||||
// Get rid of the current style
|
||||
lCurObject := lTextSpanStack.Pop();
|
||||
if lCurObject <> nil then lCurObject.Free;
|
||||
end
|
||||
else
|
||||
begin
|
||||
lText := lParagraph.AddText(lNodeValue);
|
||||
|
||||
// Apply the layer style
|
||||
ApplyStackStylesToText(lText);
|
||||
end;
|
||||
|
||||
|
||||
lCurNode := lCurNode.NextSibling;
|
||||
end;
|
||||
end;
|
||||
|
||||
begin
|
||||
lx := 0.0;
|
||||
ly := 0.0;
|
||||
|
||||
lText := TvText.Create(nil);
|
||||
// The text contents are inside as a child text, not as a attribute
|
||||
// ex: <text x="0" y="15" fill="red" transform="rotate(30 20,40)">I love SVG</text>
|
||||
// For simple text, but much more complex structures appear if there are
|
||||
// text spans
|
||||
|
||||
// Apply the layer style
|
||||
ApplyLayerStyles(lText);
|
||||
lParagraph := TvParagraph.Create(AData);
|
||||
lTextSpanStack := TSVGObjectStack.Create;
|
||||
|
||||
// read the attributes
|
||||
for i := 0 to ANode.Attributes.Length - 1 do
|
||||
@ -2313,28 +2423,21 @@ begin
|
||||
else if lNodeName = 'id' then
|
||||
lText.Name := lNodeValue
|
||||
else if lNodeName = 'style' then
|
||||
ReadSVGStyle(lNodeValue, lText)
|
||||
ReadSVGStyle(lNodeValue, lParagraph)
|
||||
else if IsAttributeFromStyle(lNodeName) then
|
||||
begin
|
||||
ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
|
||||
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
|
||||
ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lParagraph);
|
||||
ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lParagraph);
|
||||
end;
|
||||
end;
|
||||
|
||||
// The text contents are inside as a child text, not as a attribute
|
||||
// ex: <text x="0" y="15" fill="red" transform="rotate(30 20,40)">I love SVG</text>
|
||||
if Anode.FirstChild <> nil then
|
||||
lTextStr := Anode.FirstChild.NodeValue;
|
||||
// Add the first line
|
||||
lText.Value.Add(lTextStr);
|
||||
|
||||
// Recover the position if there was a transformation matrix
|
||||
lx := lx + lText.X;
|
||||
ly := ly + lText.Y;
|
||||
lx := lx + lParagraph.X;
|
||||
ly := ly + lParagraph.Y;
|
||||
|
||||
// Set the coordinates
|
||||
ConvertSVGCoordinatesToFPVCoordinates(
|
||||
AData, lx, ly, lText.X, lText.Y);
|
||||
AData, lx, ly, lParagraph.X, lParagraph.Y);
|
||||
|
||||
// Now add other lines, which appear as <tspan ...>another line</tspan>
|
||||
// Example:
|
||||
@ -2343,18 +2446,11 @@ begin
|
||||
// <tspan x="10" y="70">Second line</tspan>
|
||||
// </text>
|
||||
// These other lines can be positioned, so they need to appear as independent TvText elements
|
||||
{ lCurNode := Anode.FirstChild;
|
||||
while lCurNode <> nil do
|
||||
begin
|
||||
lNodeName := LowerCase(lCurNode.NodeName);
|
||||
if lNodeName <> 'tspan' then Continue;
|
||||
ReadTextFromNode(lCurNode, AData, ADoc);
|
||||
|
||||
lCurNode := lCurNode.NextSibling;
|
||||
end;}
|
||||
ReadTextSpans(ANode);
|
||||
|
||||
// Finalization
|
||||
Result := lText;
|
||||
lTextSpanStack.Free;
|
||||
Result := lParagraph;
|
||||
end;
|
||||
|
||||
// <use xlink:href="#svgbar" transform="rotate(45)"/>
|
||||
|
Loading…
Reference in New Issue
Block a user