From c1b78dcb6559bd359d9397edb3c03d2a61b2a411 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Thu, 1 Feb 2024 14:09:55 +0000 Subject: [PATCH] fpspreadsheet: Add xlsx chart writer font support. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@9197 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../source/common/xlsxooxmlchart.pas | 321 ++++++++++++------ 1 file changed, 215 insertions(+), 106 deletions(-) diff --git a/components/fpspreadsheet/source/common/xlsxooxmlchart.pas b/components/fpspreadsheet/source/common/xlsxooxmlchart.pas index c92fa22a4..2bb92ce6b 100644 --- a/components/fpspreadsheet/source/common/xlsxooxmlchart.pas +++ b/components/fpspreadsheet/source/common/xlsxooxmlchart.pas @@ -82,6 +82,7 @@ type FAxisID: array[TsChartAxisAlignment] of DWord; function GetChartFillAndLineXML(AIndent: Integer; AChart: TsChart; AFill: TsChartFill; ALine: TsChartLine): String; function GetChartFillXML(AIndent: Integer; AChart: TsChart; AFill: TsChartFill): String; + function GetChartFontXML(AIndent: Integer; AFont: TsFont; ANodeName: String): String; function GetChartLineXML(AIndent: Integer; AChart: TsChart; ALine: TsChartLine): String; function GetChartRangeXML(AIndent: Integer; ARange: TsChartRange; ARefKind: String): String; function GetChartSeriesMarkerXML(AIndent: Integer; ASeries: TsScatterSeries): String; @@ -96,7 +97,8 @@ type // Writing the main chart xml nodes procedure WriteChartNode(AStream: TStream; AIndent: Integer; AChartIndex: Integer); - procedure WriteChartAxisNode(AStream: TStream; AIndent: Integer; Axis: TsChartAxis; AxisKind: String); + procedure WriteChartAxisNode(AStream: TStream; AIndent: Integer; Axis: TsChartAxis; ANodeName: String); + procedure WriteChartAxisTitle(AStream: TStream; AIndent: Integer; Axis: TsChartAxis); procedure WriteChartLegendNode(AStream: TStream; AIndent: Integer; ALegend: TsChartLegend); procedure WriteChartPlotAreaNode(AStream: TStream; AIndent: Integer; AChart: TsChart); procedure WriteChartTitleNode(AStream: TStream; AIndent: Integer; ATitle: TsChartText); @@ -105,6 +107,9 @@ type procedure WriteBarSeries(AStream: TStream; AIndent: Integer; ASeries: TsBarSeries; ASeriesIndex: Integer); procedure WriteScatterSeries(AStream: TStream; AIndent: Integer; ASeries: TsScatterSeries; ASeriesIndex: Integer); + procedure WriteChartLabels(AStream: TStream; AIndent: Integer; AFont: TsFont); + procedure WriteChartText(AStream: TStream; AIndent: Integer; AText: TsChartText); + public constructor Create(AWriter: TsBasicSpreadWriter); override; destructor Destroy; override; @@ -146,6 +151,10 @@ const PERCENT_MULTIPLIER = 1000; FACTOR_MULTIPLIER = 100000; + DEFAULT_FONT_NAME = 'Liberation Sans'; + + FALSE_TRUE: Array[boolean] of String = ('0', '1'); + {$INCLUDE xlsxooxmlchart_hatch.inc} type @@ -2476,14 +2485,14 @@ end; procedure TsSpreadOOXMLChartWriter.WriteChartNode(AStream: TStream; AIndent: Integer; AChartIndex: Integer); var - indent: String; chart: TsChart; + indent: String; begin indent := DupeString(' ', AIndent); chart := TsWorkbook(Writer.Workbook).GetChartByIndex(AChartIndex); AppendToStream(AStream, - '' + LE + indent + '' + LE ); WriteChartTitleNode(AStream, AIndent + 2, chart.Title); @@ -2491,8 +2500,8 @@ begin WriteChartLegendNode(AStream, AIndent + 2, chart.Legend); AppendToStream(AStream, - ' ' + LE + - '' + LE + indent + ' ' + LE + + indent + '' + LE ); end; @@ -3072,61 +3081,15 @@ end; -------------------------------------------------------------------------------} procedure TsSpreadOOXMLChartWriter.WriteChartSpaceXML(AStream: TStream; AChartIndex: Integer); - - function GetChartAxisXML(AIndent: Integer; AChart: TsChart; - AxisID, OtherAxisID: Integer; NodeName, AxPos: String): String; - var - ind: String; - begin - ind := DupeString(' ', AIndent); - Result := Format( - ind + '<%s>' + LE + // 1 - ind + ' ' + LE + // 2 - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + // 3 - ind + ' ' + LE + - IfThen(AxPos='l', ind + ' ' + LE, '') + - ind + ' ' + LE + // 4 - ind + ' ' + LE + - IfThen(AxPos='l', ind + ' ' + LE, '') + - IfThen(AxPos='b', ind + ' ' + LE, '') + - IfThen(AxPos='b', ind + ' ' + LE, '') + - IfThen(AxPos='b', ind + ' ' + LE, '') + - ind + '', [ // 5 - NodeName, // 1 - AxisID, // 2 - AxPos, // 3 - OtherAxisID, // 4 - NodeName // 5 - ]); - end; - - function GetBarChartXML(Indent: Integer; AChart: TsChart; CatAxID, ValAxID: Integer): String; - var - ind: String; - begin - ind := DupeString(' ', Indent); - Result := Format( - ind + '' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + // categories axis (x) - ind + ' ' + LE + // values axis (y) - ind + '', [ - CatAxID, - ValAxID - ]); - end; - begin AppendToStream(AStream, '' + LE + '' + LE + - ' ' + LE // to do: get correct value + ' ' + LE + // to do: get correct value + ' ' + LE + ); WriteChartNode(AStream, 2, AChartIndex); @@ -3226,6 +3189,44 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Assembles the xml string for a font to be used in a chart + + @param AIndent Number of indentation spaces, for better legibility + @param AFont Font to be processed + @param ANode String for the node in which the result is used. Either '', or '' +-------------------------------------------------------------------------------} +function TsSpreadOOXMLChartWriter.GetChartFontXML(AIndent: Integer; + AFont: TsFont; ANodeName: String): String; +var + indent: String; + fontname: String; + bold: String; + italic: String; + strike: String; + underline: String; +begin + indent := DupeString(' ', AIndent); + + fontName := IfThen(AFont.FontName <> '', AFont.FontName, DEFAULT_FONTNAME); + bold := IfThen(fssBold in AFont.Style, 'b="1" ', 'b="0" '); + italic := IfThen(fssItalic in AFont.Style, 'i="1" ', ''); + strike := IfThen(fssStrikeOut in AFont.Style, 'strike="sngStrike" ', 'strike="noStrike" '); // no support for double-strike... + underline := IfThen(fssUnderline in AFont.Style, 'u="sng" ', ''); // no support for double-underline + + Result := Format( + indent + '<%0:s sz="%d" spc="-1" %s%s%s%s>' + LE + + indent + ' ' + LE + + indent + '', + [ + ANodeName, + round(AFont.Size * 100), + bold, italic, strike, underline, + fontName + ] + ); +end; + function TsSpreadOOXMLChartWriter.GetChartLineXML(AIndent: Integer; AChart: TsChart; ALine: TsChartline): String; var @@ -3345,10 +3346,17 @@ procedure TsSpreadOOXMLChartWriter.WriteBarSeries(AStream: TStream; var indent: String; chart: TsChart; + xRng: TsChartRange; begin indent := DupeString(' ', AIndent); chart := ASeries.Chart; + xRng := ASeries.XRange; + if xRng.IsEmpty then + xRng := ASeries.LabelRange; + if xRng.IsEmpty then + xRng := chart.CategoryLabelRange; + AppendToStream(AStream, Format( indent + '' + LE + indent + ' ' + LE + @@ -3360,7 +3368,7 @@ begin GetChartFillAndLineXML(AIndent + 6, chart, ASeries.Fill, ASeries.Line) + LE + indent + ' ' + LE + indent + ' ' + LE + - GetChartRangeXML(AIndent + 6, ASeries.XRange, 'strRef') + LE + + GetChartRangeXML(AIndent + 6, xRng, 'strRef') + LE + indent + ' ' + LE + indent + ' ' + LE + GetChartRangeXML(AIndent + 6, ASeries.YRange, 'numRef') + LE + @@ -3390,7 +3398,7 @@ end; @param AxisKind 'catAx' when Axis is a category axis, otherwise 'valAx' -------------------------------------------------------------------------------} procedure TsSpreadOOXMLChartWriter.WriteChartAxisNode(AStream: TStream; - AIndent: Integer; Axis: TsChartAxis; AxisKind: String); + AIndent: Integer; Axis: TsChartAxis; ANodeName: String); function GetTickMarkStr(ATicks: TsChartAxisTicks): String; begin @@ -3429,40 +3437,91 @@ var indent: String; axID: DWord; rotAxID: DWord; + crosses: String = 'autoZero'; begin indent := DupeString(' ', AIndent); axID := FAxisID[Axis.Alignment]; rotAxID := FAxisID[Axis.GetRotatedAxis.Alignment]; + if (Axis = Axis.Chart.YAxis) and (Axis.Chart.GetChartType in [ctBar]) then + crosses := 'min'; AppendToStream(AStream, Format( - indent + '' + LE + + indent + '<%s>' + LE + indent + ' ' + LE + indent + ' ' + LE + indent + ' ' + LE + indent + ' ' + LE + indent + ' ' + LE + - indent + ' ' + LE + - GetGridLineStr(AIndent + 2, 'c:majorGridlines', Axis.MajorGridLines) + - GetGridLineStr(AIndent + 2, 'c:minorGridlines', Axis.MinorGridLines) + + indent + ' ' + LE, + [ + ANodeName, // or + axID, // + AX_POS[Axis.Alignment] // + ] + )); + + // Grid lines + AppendToStream(AStream, + GetGridLineStr(AIndent + 2, 'c:majorGridlines', Axis.MajorGridLines) + + GetGridLineStr(AIndent + 2, 'c:minorGridlines', Axis.MinorGridLines) + ); + + // Axis title + WriteChartAxisTitle(AStream, AIndent + 2, Axis); + + // Axis labels + if Axis.ShowLabels then + WriteChartLabels(AStream, AIndent + 2, Axis.LabelFont); + + AppendToStream(AStream, Format( indent + ' ' + LE + indent + ' ' + LE + indent + ' ' + LE + indent + ' ' + LE + indent + ' ' + LE + - indent + ' ' + LE + + indent + ' ' + LE + // indent + ' ' + LE + - indent + '' + LE, - [ AxisKind, // or - axID, // - AX_POS[Axis.Alignment], // + indent + '' + LE, + [ GetTickMarkStr(Axis.MajorTicks), // GetTickMarkStr(Axis.MinorTicks), // - rotAxID // + rotAxID, // + crosses, // + ANodeName // or ] )); end; +procedure TsSpreadOOXMLChartWriter.WriteChartAxisTitle(AStream: TStream; + AIndent: Integer; Axis: TsChartAxis); +var + indent: String; +begin + if not Axis.Title.Visible or (Axis.Title.Caption = '') then + exit; + + indent := DupeString(' ', AIndent); + + AppendToStream(AStream, + indent + '' + LE + ); + + WriteChartText(AStream, AIndent + 4, Axis.Title); + + AppendToStream(AStream, + indent + ' ' + LE + + indent + ' ' + LE + + GetChartFillAndLineXML(AIndent + 6, Axis.Chart, Axis.Title.Background, Axis.Title.Border) + LE + + indent + ' ' + LE + ); + + AppendToStream(AStream, + indent + '' + LE + ); +end; + + {@@ ---------------------------------------------------------------------------- Writes the chart-related entries to the [Content_Types].xml file @@ -3495,6 +3554,33 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Writes a node for chart or axis labels + + @param AStream Stream to be written to + @param AIndent Number of indentation spaced, for better legibility + @param AFont Font to be used by the labels +-------------------------------------------------------------------------------} +procedure TsSpreadOOXMLChartWriter.WriteChartLabels(AStream: TStream; + AIndent: Integer; AFont: TsFont); +var + indent: String; +begin + indent := DupeString(' ', AIndent); + + AppendToStream(AStream, + indent + '' + LE + + indent + ' ' + LE + +// indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + GetChartFontXML(AIndent + 6, AFont, 'a:defRPr') + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + '' + LE + ); +end; + {@@ ---------------------------------------------------------------------------- Writes the node of a chart. @@ -3505,18 +3591,30 @@ end; procedure TsSpreadOOXMLChartWriter.WriteChartLegendNode(AStream: TStream; AIndent: Integer; ALegend: TsChartLegend); var - ind: String; + indent: String; begin if not ALegend.Visible then exit; - ind := DupeString(' ', AIndent); + indent := DupeString(' ', AIndent); AppendToStream(AStream, - ind + '' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + '' + LE + indent + '' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + GetChartFillAndLineXML(AIndent + 4, ALegend.Chart, ALegend.Background, ALegend.Border) + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + GetChartFontXML(AIndent + 8, ALegend.Font, 'a:defRPr') + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + '' + LE ); end; @@ -3586,8 +3684,8 @@ begin indent + '' + LE ); - xAxKind := 'catAx'; - yAxKind := 'valAx'; + xAxKind := 'c:catAx'; + yAxKind := 'c:valAx'; for i := 0 to AChart.Series.Count-1 do begin @@ -3598,7 +3696,7 @@ begin ctScatter: begin WriteScatterSeries(AStream, AIndent + 2, TsScatterSeries(ser), i); - xAxKind := 'valAx'; + xAxKind := 'c:valAx'; end; end; end; @@ -3663,6 +3761,39 @@ begin )); end; +{@@ ---------------------------------------------------------------------------- + Writes a node containing either the chart title or axis title. +-------------------------------------------------------------------------------} +procedure TsSpreadOOXMLChartWriter.WriteChartText(AStream: TStream; + AIndent: Integer; AText: TsChartText); +var + indent: String; + rotStr: String; +begin + if not AText.Visible then + exit; + + str(-AText.RotationAngle * ANGLE_MULTIPLIER:0:0, rotStr); + + indent := DupeString(' ', AIndent); + AppendToStream(AStream, + indent + '' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + GetChartFontXML(AIndent + 8, AText.Font, 'a:defRPr') + LE + + indent + ' ' + LE + + indent + ' ' + LE + + GetChartFontXML(AIndent + 8, AText.Font, 'a:rPr') + LE + + indent + ' ' + AText.Caption + '' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + ' ' + LE + + indent + '' + LE + ); +end; + {@@ ---------------------------------------------------------------------------- Writes the node defining the chart's title @@ -3675,46 +3806,24 @@ procedure TsSpreadOOXMLChartWriter.WriteChartTitleNode(AStream: TStream; var indent: String; begin + if not ATitle.Visible or (ATitle.Caption = '') then + exit; + indent := DupeString(' ', AIndent); - AppendToStream(AStream, - indent + '' + LE + - indent + ' ' + LE - ); -{ - AppendToStream(AStream, - GetChartFillAndLineXML(AIndent + 2, ATitle.Background, ATitle.Border) + indent + '' + LE ); + WriteChartText(AStream, AIndent + 2, ATitle); + AppendToStream(AStream, - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' '+ LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + - ind + ' ' + LE + indent + ' ' + LE + + GetChartFillAndLineXML(AIndent + 2, ATitle.Chart, ATitle.Background, ATitle.Border) + LE ); - } + AppendToStream(AStream, indent + '' + LE ); - end; {$ENDIF}