diff --git a/components/fpspreadsheet/examples/visual/fpsctrls/main.lfm b/components/fpspreadsheet/examples/visual/fpsctrls/main.lfm index 6893e01a0..afef3be9b 100644 --- a/components/fpspreadsheet/examples/visual/fpsctrls/main.lfm +++ b/components/fpspreadsheet/examples/visual/fpsctrls/main.lfm @@ -27,6 +27,7 @@ object MainForm: TMainForm Height = 493 Top = 23 Width = 690 + AutoCalcRowHeights = False FrozenCols = 0 FrozenRows = 0 ReadFormulas = True diff --git a/components/fpspreadsheet/fpshtml.pas b/components/fpspreadsheet/fpshtml.pas index 8ea9c8004..6b2f383ec 100644 --- a/components/fpspreadsheet/fpshtml.pas +++ b/components/fpspreadsheet/fpshtml.pas @@ -1393,8 +1393,13 @@ var begin h := FWorksheet.ReadDefaultRowHeight(suPoints); row := FWorksheet.FindRow(ARowIndex); - if row <> nil then - h := FWorkbook.ConvertUnits(row^.Height, FWorkbook.Units, suPoints); + if row <> nil then begin + h := abs(FWorkbook.ConvertUnits(row^.Height, FWorkbook.Units, suPoints)); + if row^.RowHeightType = rhtDefault then begin + Result := ''; + exit; + end; + end; Result := Format(' height="%.1fpt"', [h], FPointSeparatorSettings); end; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 96903edda..ac6b2a246 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -359,8 +359,8 @@ type TRowStyleData = class public Name: String; - RowHeight: Double; // in workbook units - AutoRowHeight: Boolean; + RowHeight: Double; // in workbook units + RowHeightType: TsRowHeightType; end; { PageLayout items stored in PageLayoutList } @@ -3241,7 +3241,7 @@ var rowStyleIndex: Integer; rowStyle: TRowStyleData; rowHeight: Double; - autoRowHeight: Boolean; + rowHeightType: TsRowHeightType; col: Integer; cellNode: TDOMNode; nodeName: String; @@ -3260,9 +3260,11 @@ var begin rowStyle := TRowStyleData(FRowStyleList[rowStyleIndex]); rowHeight := rowStyle.RowHeight; // in Workbook units (see ReadRowStyles) - autoRowHeight := rowStyle.AutoRowHeight; - end else - autoRowHeight := true; + rowHeightType := rowStyle.RowHeightType; + end else begin + rowHeight := FWorksheet.DefaultRowHeight; + rowHeightTYpe := rhtDefault; + end; col := 0; @@ -3362,12 +3364,11 @@ var // Transfer non-default row heights to sheet's rows // This first "if" is a workaround for a bug of LO/OO whichs extends imported // xlsx files with blank rows up to their specification limit. - // React some rows earlier because the added row range is sometimes split + // Process some rows earlier because the added row range is sometimes split // into two parts. if row + rowsRepeated < LongInt(FLimitations.MaxRowCount) - 10 then - if not autoRowHeight then - for i:=1 to rowsRepeated do - FWorksheet.WriteRowHeight(row + i - 1, rowHeight, FWorkbook.Units); + for i:=1 to rowsRepeated do + FWorksheet.WriteRowHeight(row + i - 1, rowHeight, FWorkbook.Units, rowHeightType); row := row + rowsRepeated; end; @@ -3412,14 +3413,14 @@ var styleName, nodename: String; styleChildNode: TDOMNode; rowHeight: Double; - auto: Boolean; s: String; rowStyle: TRowStyleData; + rowHeightType: TsRowHeightType; begin styleName := GetAttrValue(AStyleNode, 'style:name'); styleChildNode := AStyleNode.FirstChild; - rowHeight := -1; - auto := false; + rowHeight := 0; + rowHeightType := rhtCustom; while Assigned(styleChildNode) do begin @@ -3432,7 +3433,7 @@ begin // convert to workbook units s := GetAttrValue(styleChildNode, 'style:use-optimal-row-height'); if s = 'true' then - auto := true; + rowHeightType := rhtAuto; end; styleChildNode := styleChildNode.NextSibling; end; @@ -3440,7 +3441,7 @@ begin rowStyle := TRowStyleData.Create; rowStyle.Name := styleName; rowStyle.RowHeight := rowHeight; - rowStyle.AutoRowHeight := auto; + rowStyle.RowHeightType := rowHeightType; FRowStyleList.Add(rowStyle); end; @@ -4331,7 +4332,7 @@ begin rowStyle := TRowStyleData.Create; rowStyle.Name := 'ro1'; rowStyle.RowHeight := FWorkbook.ConvertUnits(15, suPoints, FWorkbook.Units); - rowStyle.AutoRowHeight := true; + rowStyle.RowHeightType := rhtAuto; FRowStyleList.Add(rowStyle); for i:=0 to Workbook.GetWorksheetCount-1 do @@ -4346,9 +4347,9 @@ begin // Look for this height in the current RowStyleList found := false; for j:=0 to FRowStyleList.Count-1 do - if SameValue(TRowStyleData(FRowStyleList[j]).RowHeight, h, ROWHEIGHT_EPS) and - (not TRowStyleData(FRowStyleList[j]).AutoRowHeight) then - begin + if SameValue(TRowStyleData(FRowStyleList[j]).RowHeight, h, ROWHEIGHT_EPS) + and (TRowStyleData(FRowStyleList[j]).RowHeightType = row^.RowHeightType) + then begin found := true; break; end; @@ -4358,22 +4359,12 @@ begin rowStyle := TRowStyleData.Create; rowStyle.Name := Format('ro%d', [FRowStyleList.Count+1]); rowStyle.RowHeight := h; - rowStyle.AutoRowHeight := false; + rowStyle.RowHeightType := row^.RowHeightType; FRowStyleList.Add(rowStyle); end; end; end; end; - (* - { fpspreadsheet's row heights are measured as line count of the default font. - Using the default font size (which is in points) we convert the line count - to points and then to millimeters as needed by ods. } - multiplier := Workbook.GetDefaultFontSize;; - for i:=0 to FRowStyleList.Count-1 do - begin - h := (TRowStyleData(FRowStyleList[i]).RowHeight + ROW_HEIGHT_CORRECTION) * multiplier; - TRowStyleData(FRowStyleList[i]).RowHeight := PtsToMM(h); - end; *) end; { Is called before zipping the individual file parts. Rewinds the streams. } @@ -5297,8 +5288,9 @@ begin for k := 0 to FRowStyleList.Count-1 do begin rowStyleData := TRowStyleData(FRowStyleList[k]); // Compare row heights, but be aware of rounding errors - if SameValue(rowStyleData.RowHeight, h, ROWHEIGHT_EPS) then - begin + if SameValue(rowStyleData.RowHeight, h, ROWHEIGHT_EPS) and + (rowstyleData.RowHeightType = row^.RowHeightType) + then begin styleName := rowStyleData.Name; break; end; @@ -5443,14 +5435,16 @@ begin AppendToStream(AStream, Format( '', [rowStyle.Name])); - // Column width + // Row height AppendToStream(AStream, Format( ''); + AppendToStream(AStream, Format( + 'style:use-optimal-row-height="%s" ', [FALSE_TRUE[rowstyle.RowHeightType <> rhtCustom]])); + // row height < 0 means: automatic row height + AppendToStream(AStream, + 'fo:break-before="auto"/>'); // End AppendToStream(AStream, diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index ca52dbc10..df4be8ec7 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -41,26 +41,15 @@ type TsWorksheet = class; TsWorkbook = class; - {** - Type: TRow -- record containing information about a spreadsheet row - - Members: - - Row -- The index of the row (beginning with 0) - - Height -- The height of the row (expressed as line count of the default font) - - Notes: - - Only rows with heights that cannot be derived from the font height have - a row record. - } - {@@ The record TRow contains information about a spreadsheet row: @param Row The index of the row (beginning with 0) - @param Height The height of the row (expressed in the units defined in the workbook) - Only rows with heights that cannot be derived from the font height have a - row record. } + @param Height The height of the row (expressed in the units defined by + the workbook) + @param RowHeightType Specifies default, automatic or custom row height } TRow = record Row: Cardinal; Height: Single; + RowHeightType: TsRowHeightType; end; {@@ Pointer to a TRow record } @@ -447,6 +436,7 @@ type function GetRow(ARow: Cardinal): PRow; function GetRowHeight(ARow: Cardinal; AUnits: TsSizeUnits): Single; overload; function GetRowHeight(ARow: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.'; + function GetRowHeightType(ARow: Cardinal): TsRowHeightType; function GetCol(ACol: Cardinal): PCol; function GetColWidth(ACol: Cardinal; AUnits: TsSizeUnits): Single; overload; function GetColWidth(ACol: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.'; @@ -463,8 +453,10 @@ type procedure WriteDefaultColWidth(AValue: Single; AUnits: TsSizeUnits); procedure WriteDefaultRowHeight(AValue: Single; AUnits: TsSizeUnits); procedure WriteRowInfo(ARow: Cardinal; AData: TRow); - procedure WriteRowHeight(ARow: Cardinal; AHeight: Single; AUnits: TsSizeUnits); overload; - procedure WriteRowHeight(ARow: Cardinal; AHeight: Single); overload; deprecated 'Use version with parameter AUnits'; + procedure WriteRowHeight(ARow: Cardinal; AHeight: Single; AUnits: TsSizeUnits; + ARowHeightType: TsRowHeightType = rhtCustom); overload; + procedure WriteRowHeight(ARow: Cardinal; AHeight: Single; + ARowHeightType: TsRowHeightType = rhtCustom); overload; deprecated 'Use version with parameter AUnits'; procedure WriteColInfo(ACol: Cardinal; AData: TCol); procedure WriteColWidth(ACol: Cardinal; AWidth: Single; AUnits: TsSizeUnits); overload; procedure WriteColWidth(ACol: Cardinal; AWidth: Single); overload; deprecated 'Use version with parameter AUnits'; @@ -6275,8 +6267,8 @@ begin Result := 0; for cell in Cells.GetRowEnumerator(ARow) do Result := Max(Result, ReadCellFont(cell).Size); - Result := FWorkbook.ConvertUnits(Result, suPoints, FWorkbook.Units); // FixMe: This is not correct if text is rotated or wrapped + Result := FWorkbook.ConvertUnits(Result, suPoints, FWorkbook.Units); end; function TsWorksheet.CalcRowHeight(ARow: Cardinal): Single; @@ -6461,6 +6453,9 @@ end; @param ARow Index of the row considered @param AUnits Units for the row height. @return Height of the row + Note that the row height value can be negative to indicate that this + is an auto-calculated value (i.e. the value can change for example + when the font size changes). -------------------------------------------------------------------------------} function TsWorksheet.GetRowHeight(ARow: Cardinal; AUnits: TsSizeUnits): Single; var @@ -6484,6 +6479,28 @@ begin Result := GetRowHeight(ARow, suLines); end; +{@@ ---------------------------------------------------------------------------- + Returns the type of rowheight of a specific row. + If there is no row record then rhtDefault is returned. + + @param ARow Index of the row considered + @param AUnits Units for the row height. + @return Height of the row + Note that the row height value can be negative to indicate that this + is an auto-calculated value (i.e. the value can change for example + when the font size changes). +-------------------------------------------------------------------------------} +function TsWorksheet.GetRowHeightType(ARow: Cardinal): TsRowHeightType; +var + lRow: PRow; +begin + lRow := FindRow(ARow); + if lRow = nil then + Result := rhtDefault + else + Result := lRow^.RowHeightType; +end; + {@@ ---------------------------------------------------------------------------- Deletes the column at the index specified. Cells with greader column indexes are moved one column to the left. Merged cell blocks and cell references in @@ -6884,6 +6901,9 @@ end; @param ARow Index of the row record which will be created or modified @param AData Data to be written. Expected to be already in the units defined for the workbook + Note that the row height value can be negative to indicate + that this is an auto-calculated value (i.e. the value can + change for example when the font size changes). -------------------------------------------------------------------------------} procedure TsWorksheet.WriteRowInfo(ARow: Cardinal; AData: TRow); var @@ -6900,9 +6920,11 @@ end; @param ARow Index of the row to be considered @param AHeight Row height to be assigned to the row. @param AUnits Units measuring the row height. + @param ARowHeightType Specifies whether the row height is a default, + automatic or custom row height. -------------------------------------------------------------------------------} procedure TsWorksheet.WriteRowHeight(ARow: Cardinal; AHeight: Single; - AUnits: TsSizeUnits); + AUnits: TsSizeUnits; ARowHeightType: TsRowHeightType = rhtCustom); var AElement: PRow; begin @@ -6910,6 +6932,7 @@ begin exit; AElement := GetRow(ARow); AElement^.Height := FWorkbook.ConvertUnits(AHeight, AUnits, FWorkbook.FUnits); + AElement^.RowHeightType := ARowHeightType; end; {@@ ---------------------------------------------------------------------------- @@ -6919,9 +6942,10 @@ end; Note that this method is deprecated and will be removed. Use the variant in which the units of the new height can be specified. -------------------------------------------------------------------------------} -procedure TsWorksheet.WriteRowHeight(ARow: Cardinal; AHeight: Single); +procedure TsWorksheet.WriteRowHeight(ARow: Cardinal; AHeight: Single; + ARowHeightType: TsRowHeightType = rhtCustom); begin - WriteRowHeight(ARow, AHeight, suLines); + WriteRowHeight(ARow, AHeight, suLines, ARowHeightType); end; {@@ ---------------------------------------------------------------------------- diff --git a/components/fpspreadsheet/fpspreadsheetgrid.pas b/components/fpspreadsheet/fpspreadsheetgrid.pas index 07a2b84ab..3b9d1f3c9 100644 --- a/components/fpspreadsheet/fpspreadsheetgrid.pas +++ b/components/fpspreadsheet/fpspreadsheetgrid.pas @@ -12,11 +12,6 @@ -------------------------------------------------------------------------------} unit fpspreadsheetgrid; -{ Activate this define if the worksheet contains varying row heights and jumps - to specific rows do not meet the requested row. There is a speed penalty in - case of large worksheets. } -{.$DEFINE CALC_ALL_ROWHEIGHTS} - {$mode objfpc}{$H+} {$I fps.inc} @@ -77,6 +72,7 @@ type FDrawingCell: PCell; FTextOverflowing: Boolean; FAutoExpand: TsAutoExpandModes; + FAutoCalcRowHeights: Boolean; FEnhEditMode: Boolean; FSelPen: TsSelPen; FHyperlinkTimer: TTimer; @@ -90,6 +86,7 @@ type function CalcAutoRowHeight(ARow: Integer): Integer; function CalcColWidthFromSheet(AWidth: Single): Integer; function CalcRowHeightFromSheet(AHeight: Single): Integer; + function CalcRowHeightToSheet(AHeight: Integer): Single; procedure ChangedCellHandler(ASender: TObject; ARow, ACol: Cardinal); procedure ChangedFontHandler(ASender: TObject; ARow, ACol: Cardinal); procedure FixNeighborCellBorders(ACell: PCell); @@ -246,14 +243,17 @@ type procedure SetEditText(ACol, ARow: Longint; const AValue: string); override; procedure Setup; procedure Sort(AColSorting: Boolean; AIndex, AIndxFrom, AIndxTo:Integer); override; - procedure TopLeftChanged; override; function TrimToCell(ACell: PCell): String; procedure UpdateColWidths(AStartIndex: Integer = 0); - procedure UpdateRowHeight(ARow: Integer); + procedure UpdateRowHeight(ARow: Integer; EnforceCalcRowHeight: Boolean = false); procedure UpdateRowHeights; {@@ Automatically recalculate formulas whenever a cell value changes. } property AutoCalc: Boolean read FAutoCalc write SetAutoCalc default false; + {@@ Automatically recalculate row heights after loading a file. Gets rid of + possibly incorrect row heights stored by the writing application. But: + slow in case of large files. } + property AutoCalcRowHeights: Boolean read FAutoCalcRowHeights write FAutoCalcRowHeights default true; {@@ Automatically expand grid dimensions } property AutoExpand: TsAutoExpandModes read FAutoExpand write FAutoExpand; {@@ Displays column and row headers in the fixed col/row style of the grid. @@ -502,6 +502,10 @@ type // inherited from TsCustomWorksheetGrid {@@ Automatically recalculates the worksheet if a cell value changes. } property AutoCalc; + {@@ Automatically recalculate row heights after loading a file. Gets rid of + possibly incorrect row heights stored by the writing application. But: + slow in case of large files. } + property AutoCalcRowHeights; {@@ Automatically expand grid dimensions } property AutoExpand default [aeData, aeNavigation]; {@@ Displays column and row headers in the fixed col/row style of the grid. @@ -1012,6 +1016,7 @@ begin FSelPen.JoinStyle := pjsMiter; FSelPen.OnChange := @SelPenChangeHandler; FAutoExpand := [aeData, aeNavigation]; + FAutoCalcRowHeights := true; FHyperlinkTimer := TTimer.Create(self); FHyperlinkTimer.Interval := HYPERLINK_TIMER_INTERVAL; FHyperlinkTimer.OnTimer := @HyperlinkTimerElapsed; @@ -1190,10 +1195,18 @@ function TsCustomWorksheetGrid.CalcRowHeightFromSheet(AHeight: Single): Integer; var h_pts: Single; begin - h_pts := Workbook.ConvertUnits(AHeight, Workbook.Units, suPoints);; + h_pts := Workbook.ConvertUnits(abs(AHeight), Workbook.Units, suPoints);; Result := PtsToPx(h_pts, Screen.PixelsPerInch); // + 4; end; +function TsCustomWorksheetGrid.CalcRowHeightToSheet(AHeight: Integer): Single; +var + h_pts: Single; +begin + h_pts := PxToPts(AHeight, Screen.PixelsPerInch); + Result := Workbook.ConvertUnits(h_pts, suPoints, Workbook.Units); +end; + {@@ ---------------------------------------------------------------------------- Converts the column height given in screen pixels to the units used by the worksheet. @@ -3609,10 +3622,9 @@ begin if GetGridRow(Worksheet.GetLastRowIndex + 1) >= RowCount then RowCount := RowCount + 1; r := GetWorksheetRow(AGridRow); -// r := AGridRow - FHeaderCount; Worksheet.InsertRow(r); - UpdateRowHeight(AGridRow); + UpdateRowHeight(AGridRow, true); // UpdateRowHeights(AGridRow); end; @@ -4007,7 +4019,9 @@ procedure TsCustomWorksheetGrid.ListenerNotification(AChangedItems: TsNotificati AData: Pointer = nil); var grow, gcol: Integer; + srow: Cardinal; cell: PCell; + lRow: PRow; begin Unused(AData); @@ -4079,9 +4093,12 @@ begin // Row height (after font change). if (lniRow in AChangedItems) and (Worksheet <> nil) then begin - grow := GetGridRow({%H-}PtrInt(AData)); + srow := {%H-}PtrInt(AData); // sheet row + grow := GetGridRow(srow); // grid row AutoExpandToRow(grow, aeData); - RowHeights[grow] := CalcAutoRowHeight(grow); + lRow := Worksheet.FindRow(srow); + if (lRow = nil) or (lRow^.RowHeightType <> rhtCustom) then + UpdateRowHeight(grow, true); end; end; @@ -4573,21 +4590,6 @@ begin ); end; -{@@ ---------------------------------------------------------------------------- - Inherited method called whenever to grid is scrolled, i.e. the top/left cell - changes. - Is overridden to calculate the row heights of the currently visible grid --------------------------------------------------------------------------------} -procedure TsCustomWorksheetGrid.TopLeftChanged; -begin - {$IFNDEF CALC_ALL_ROWHEIGHTS} - if FOldTopRow <> TopRow then - UpdateRowHeights; - FOldTopRow := TopRow; - {$ENDIF} - inherited; -end; - {@@ ---------------------------------------------------------------------------- Modifies the text that is show for cells which are too narrow to hold the entire text. The method follows the behavior of Excel and Open/LibreOffice: @@ -4753,48 +4755,74 @@ begin end; end; -procedure TsCustomWorksheetGrid.UpdateRowHeight(ARow: Integer); +{@@ ---------------------------------------------------------------------------- + Updates the height if the specified row in the grid by the value stored in the + Worksheet and multiplied by the current zoom factor. If the stored rowheight + type is rhtAuto (meaning: "row height is auto-calculated") and row height + calculation is enabled (if internal flag FAutoCalcRowHeight is true, or the + parameter EnforceCalcRowHeight is true9, then the current row height is + calculated by iterating over all cells in the row. +-------------------------------------------------------------------------------} +procedure TsCustomWorksheetGrid.UpdateRowHeight(ARow: Integer; + EnforceCalcRowHeight: Boolean = false); var lRow: PRow; - h: Integer; + h: Integer; // Row height, in pixels. Contains zoom factor. begin + h := 0; if Worksheet <> nil then begin lRow := Worksheet.FindRow(ARow - FHeaderCount); - if (lRow <> nil) then - h := round(CalcRowHeightFromSheet(lRow^.Height) * ZoomFactor) - else - h := CalcAutoRowHeight(ARow); // ZoomFactor has already been applied to font heights - end else - h := DefaultRowHeight; // Zoom factor is applied by getter function + if (lRow <> nil) then begin + case lRow^.RowHeightType of + rhtCustom: + h := round(CalcRowHeightFromSheet(lRow^.Height) * ZoomFactor); + rhtAuto, rhtDefault: + if FAutoCalcRowHeights or EnforceCalcRowHeight then begin + // Calculate current grid row height in pixels by iterating over all cells in row + h := CalcAutoRowHeight(ARow); // ZoomFactor already applied to font heights + if h = 0 then begin + h := DefaultRowHeight; // Zoom factor applied by getter function + lRow^.RowHeightType := rhtDefault; + end else + lRow^.RowHeightType := rhtAuto; + // Calculate the unzoomed row height in workbook units and store + // in row record + lRow^.Height := CalcRowHeightToSheet(round(h / ZoomFactor)); + end else + // If autocalc mode is off we just take the row height from the row record + h := round(CalcRowHeightFromSheet(lRow^.Height) * ZoomFactor); + end; // case + end else + // No row record so far. + if FAutoCalcRowHeights or EnforceCalcRowHeight then begin + h := CalcAutoRowHeight(ARow); + if h <> DefaultRowHeight then begin + lRow := Worksheet.GetRow(ARow - FHeaderCount); + lRow^.Height := CalcRowHeightToSheet(round(h / ZoomFactor)); + lRow^.RowHeightType := rhtAuto; + end; + end; + end; + if h = 0 then + h := DefaultRowHeight; // Zoom factor is applied by getter function + + inc(FZoomLock); // We don't want to modify the sheet row heights here. RowHeights[ARow] := h; + dec(FZoomLock); end; {@@ ---------------------------------------------------------------------------- - Updates row heights by using the data from the TRow records or by auto- - calculating the row height from the max of the cell heights - Because there may be many rows only the visible rows are updated. Therefore, - this method is called whenever the grid is scrolled and the coordinates of - the top-left cell changes. + Updates grid row heights by using the data from the TRow records. -------------------------------------------------------------------------------} procedure TsCustomWorksheetGrid.UpdateRowHeights; -const - DELTA = 10; var - r, r1, r2: Integer; + r: Integer; begin if FRowHeightLock > 0 then exit; - {$IFDEF CALC_ALL_ROWHEIGHTS} - r1 := FHeaderCount; - r2 := RowCount-1; - {$ELSE} - r1 := Max(FHeaderCount, TopRow - DELTA); - r2 := Min(RowCount-1, TopRow + VisibleRowCount + DELTA); - {$ENDIF} - - for r:=r1 to r2 do + for r:=FHeaderCount to RowCount-1 do UpdateRowHeight(r); end; diff --git a/components/fpspreadsheet/fpstypes.pas b/components/fpspreadsheet/fpstypes.pas index 3d9a31f24..f15dcca9a 100644 --- a/components/fpspreadsheet/fpstypes.pas +++ b/components/fpspreadsheet/fpstypes.pas @@ -770,6 +770,14 @@ type TsStreamParam = (spClipboard, spWindowsClipboardHTML); TsStreamParams = set of TsStreamParam; + {@@ Types of row heights + rhtDefault - default row height + rhtAuto - automatically determined row height, depends on font size, + text rotation, rich-text parameters, word-wrap + rhtCustom - user-determined row height (dragging the row header borders in + the grid } + TsRowHeightType = (rhtDefault, rhtAuto, rhtCustom); + implementation diff --git a/components/fpspreadsheet/tests/formattests.pas b/components/fpspreadsheet/tests/formattests.pas index ea0d50458..8e66d1381 100644 --- a/components/fpspreadsheet/tests/formattests.pas +++ b/components/fpspreadsheet/tests/formattests.pas @@ -33,7 +33,7 @@ var SollDateTimeFormatStrings: array[0..8] of String; SollColWidths: array[0..1] of Single; - SollRowHeights: Array[0..2] of Single; + SollRowHeights: Array[0..3] of Single; SollBorders: array[0..19] of TsCellBorders; SollBorderLineStyles: array[0..6] of TsLineStyle; SollBorderColors: array[0..5] of TsColor; @@ -156,7 +156,7 @@ type implementation uses - TypInfo, fpsPatches, fpsutils, fpsnumformat, fpspalette, fpscsv; + Math, TypInfo, fpsPatches, fpsutils, fpsnumformat, fpspalette, fpscsv; const FmtNumbersSheet = 'NumbersFormat'; //let's distinguish it from the regular numbers sheet @@ -276,6 +276,7 @@ begin SollRowHeights[0] := 1; // Lines of default font SollRowHeights[1] := 2; SollRowHeights[2] := 4; + SollRowHeights[3] := -2; // an autocalculated row height // Cell borders SollBorders[0] := []; @@ -1181,6 +1182,7 @@ var ActualRowHeight: Single; Row: Integer; TempFile: string; //write xls/xml to this file and read back from it + rht: TsRowHeightType; begin {// Not needed: use workbook.writetofile with overwrite=true if fileexists(TempFile) then @@ -1190,8 +1192,12 @@ begin MyWorkbook := TsWorkbook.Create; try MyWorkSheet:= MyWorkBook.AddWorksheet(RowHeightSheet); - for Row := Low(SollRowHeights) to High(SollRowHeights) do - MyWorksheet.WriteRowHeight(Row, SollRowHeights[Row], suLines); + for Row := Low(SollRowHeights) to High(SollRowHeights) do begin + if SollRowHeights[Row] < 0 then + rht := rhtAuto else + rht := rhtCustom; + MyWorksheet.WriteRowHeight(Row, abs(SollRowHeights[Row]), suLines, rht); + end; TempFile:=NewTempFile; MyWorkBook.WriteToFile(TempFile, AFormat, true); finally @@ -1211,11 +1217,18 @@ begin for Row := Low(SollRowHeights) to High(SollRowHeights) do begin ActualRowHeight := MyWorksheet.GetRowHeight(Row, suLines); + rht := MyWorksheet.GetRowHeightType(Row); // Take care of rounding errors - due to missing details of calculation // they can be quite large... - if abs(ActualRowHeight - SollRowHeights[Row]) > 1e-2 then + if not SameValue(ActualRowHeight, abs(SollRowHeights[Row]), 1e-2) then CheckEquals(SollRowHeights[Row], ActualRowHeight, 'Test saved row height mismatch, row '+RowNotation(MyWorkSheet,Row)); + if (SollRowHeights[Row] < 0) and (rht <> rhtAuto) then + CheckEquals(ord(rht), ord(rhtAuto), + 'Test saved row height type (rhtAuto) mismatch, row'+RowNotation(MyWorksheet,Row)); + if (sollRowHeights[Row] > 0) and (rht <> rhtCustom) then + CheckEquals(ord(rht), ord(rhtCustom), + 'Test saved row height type (rhtCustom) mismatch, row'+RowNotation(MyWorksheet,Row)); end; finally MyWorkbook.Free; diff --git a/components/fpspreadsheet/wikitable.pas b/components/fpspreadsheet/wikitable.pas index 33a196152..3612d0caa 100644 --- a/components/fpspreadsheet/wikitable.pas +++ b/components/fpspreadsheet/wikitable.pas @@ -397,7 +397,6 @@ procedure TsWikiTableWriter.WriteToStrings_WikiMedia(AStrings: TStrings); Result := Format('border-%s:%s', [BORDERNAMES[ABorder], LINESTYLES[ls]]); if clr <> scBlack then Result := Result + ' ' + ColorToHTMLColorStr(clr) + '; '; -// Result := Result + ' ' + FWorkbook.GetPaletteColorAsHTMLStr(clr) + '; '; end; const @@ -497,7 +496,7 @@ begin if j = 0 then begin lRow := FWorksheet.FindRow(i); - if lRow <> nil then + if (lRow <> nil) and (lRow^.RowHeightType <> rhtDefault) then lRowHeightStr := Format(' height="%.0fpt"', [FWorkbook.ConvertUnits(lRow^.Height, FWorkbook.Units, suPoints)]); end; diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index 2131f7ad5..2f293d79d 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -255,6 +255,7 @@ type HorAlign_Border_BkGr: Byte; end; + procedure InternalAddBuiltinNumFormats(AList: TStringList; AFormatSettings: TFormatSettings); var fs: TFormatSettings absolute AFormatSettings; @@ -784,25 +785,27 @@ var rowrec: TRowRecord; lRow: PRow; h: word; + auto: Boolean; + rowheight: Single; + defRowHeight: Single; begin rowRec.RowIndex := 0; // to silence the compiler... AStream.ReadBuffer(rowrec, SizeOf(TRowRecord)); h := WordLEToN(rowrec.Height); - if h and $8000 = 0 then // if this bit were set, rowheight would be default - begin - lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex)); - // Row height is encoded into the 15 remaining bits in units "twips" (1/20 pt) - lRow^.Height := FWorkbook.ConvertUnits( - TwipsToPts(h and $7FFF), suPoints, FWorkbook.Units); - { - // We need it in "lines" units. - lRow^.Height := TwipsToPts(h and $7FFF) / Workbook.GetFont(0).Size; - if lRow^.Height > ROW_HEIGHT_CORRECTION then - lRow^.Height := lRow^.Height - ROW_HEIGHT_CORRECTION - else - lRow^.Height := 0; - } - end; + auto := h and $8000 <> 0; + rowheight := FWorkbook.ConvertUnits(TwipsToPts(h and $7FFF), suPoints, FWorkbook.Units); + defRowHeight := FWorksheet.ReadDefaultRowHeight(FWorkbook.Units); + + // No row record if rowheight in file is the same as the default rowheight + if SameValue(rowheight, defRowHeight, ROWHEIGHT_EPS) then + exit; + + // Otherwise: create a row record + lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex)); + lRow^.Height := rowHeight; + if auto then + lRow^.RowHeightType := rhtAuto else + lRow^.RowHeightType := rhtCustom; end; {@@ ---------------------------------------------------------------------------- @@ -1341,7 +1344,7 @@ begin if (boVirtualMode in Workbook.Options) then WriteVirtualCells(AStream, FWorksheet) else begin - WriteRows(AStream, FWorksheet); + // WriteRows(AStream, FWorksheet); WriteCellsToStream(AStream, FWorksheet.Cells); end; @@ -1967,11 +1970,10 @@ end; procedure TsSpreadBIFF2Writer.WriteRow(AStream: TStream; ASheet: TsWorksheet; ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); -const - EPS = 1E-2; var containsXF: Boolean; rowheight: Word; + auto: Boolean; w: Word; begin if (ARowIndex >= FLimitations.MaxRowCount) or (AFirstColIndex >= FLimitations.MaxColCount) @@ -1995,17 +1997,20 @@ begin { Index to column of the last cell which is described by a cell record, increased by 1 } AStream.WriteWord(WordToLE(Word(ALastColIndex) + 1)); + auto := true; { Row height (in twips, 1/20 point) and info on custom row height } - if (ARow = nil) or SameValue(ARow^.Height, ASheet.ReadDefaultRowHeight(FWorkbook.Units), EPS) then + if (ARow = nil) or (ARow^.RowHeightType = rhtDefault) then rowheight := PtsToTwips(ASheet.ReadDefaultRowHeight(suPoints)) else if (ARow^.Height = 0) then rowheight := 0 - else - rowheight := PtsToTwips(FWorkbook.ConvertUnits( - ARow^.Height, FWorkbook.Units, suPoints)); -// rowheight := PtsToTwips((ARow^.Height + ROW_HEIGHT_CORRECTION) * h); + else begin + rowheight := PtsToTwips(FWorkbook.ConvertUnits(ARow^.Height, FWorkbook.Units, suPoints)); + auto := ARow^.RowHeightType <> rhtCustom; + end; w := rowheight and $7FFF; + if auto then + w := w or $8000; AStream.WriteWord(WordToLE(w)); { not used } diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index b8c0dc58b..aec814539 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -325,6 +325,8 @@ const (2.54* 12.0 , 11.0 *2.54) // 90 - 12x11 ); + ROWHEIGHT_EPS = 1E-2; + type TDateMode=(dm1900,dm1904); //DATEMODE values, 5.28 @@ -2080,17 +2082,30 @@ var rowrec: TRowRecord; lRow: PRow; h: word; + hpts: Single; + hdef: Single; + isNonDefaultHeight: Boolean; + isAutoSizeHeight: Boolean; begin rowrec.RowIndex := 0; // to silence the compiler... AStream.ReadBuffer(rowrec, SizeOf(TRowRecord)); - // If bit 6 is set in the flags row height does not match the font size. - // Only for this case we create a row record for fpspreadsheet - if rowrec.Flags and $00000040 <> 0 then begin + h := WordLEToN(rowrec.Height) and $7FFF; // mask off "custom" bit + hpts := FWorkbook.ConvertUnits(TwipsToPts(h), suPoints, FWorkbook.Units); + hdef := FWorksheet.ReadDefaultRowHeight(FWorkbook.Units); + + isNonDefaultHeight := not SameValue(hpts, hdef, ROWHEIGHT_EPS); + isAutoSizeHeight := WordLEToN(rowrec.Flags) and $00000040 = 0; + // If this bis is set then font size and row height do NOT match, i.e. NO autosize + + // We only create a row record for fpspreadsheet if the row has a + // non-standard height (i.e. different from default row height). + if isNonDefaultHeight then begin lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex)); - // row height is encoded into the 15 lower bits in units "twips" (1/20 pt) - h := WordLEToN(rowrec.Height) and $7FFF; - lRow^.Height := FWorkbook.ConvertUnits(TwipsToPts(h), suPoints, FWorkbook.Units); + if isAutoSizeHeight then + lRow^.RowHeightType := rhtAuto else + lRow^.RowHeightType := rhtCustom; + lRow^.Height := hpts; end; end; @@ -4258,8 +4273,6 @@ end; -------------------------------------------------------------------------------} procedure TsSpreadBIFFWriter.WriteRow(AStream: TStream; ASheet: TsWorksheet; ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); -const - EPS = 1E-2; var w: Word; dw: DWord; @@ -4310,11 +4323,8 @@ begin AStream.WriteWord(WordToLE(Word(ALastColIndex) + 1)); { Row height (in twips, 1/20 point) and info on custom row height } - if (ARow = nil) or SameValue(ARow^.Height, ASheet.ReadDefaultRowHeight(FWorkbook.Units), EPS) then + if (ARow = nil) or (ARow^.RowHeightType = rhtDefault) then rowheight := PtsToTwips(ASheet.ReadDefaultRowHeight(suPoints)) - else - if (ARow^.Height = 0) then - rowheight := 0 else rowheight := PtsToTwips(FWorkbook.ConvertUnits(ARow^.Height, FWorkbook.Units, suPoints)); w := rowheight and $7FFF; @@ -4327,8 +4337,10 @@ begin dw := $00000100; // bit 8 is always 1 if spaceabove then dw := dw or $10000000; if spacebelow then dw := dw or $20000000; - if (ARow <> nil) then - dw := dw or $00000040; // Row height and font height do not match + if (ARow <> nil) and (ARow^.RowHeightType = rhtCustom) then // Custom row height + dw := dw or $00000040; // Row height and font height do not match + + { Write out } AStream.WriteDWord(DWordToLE(dw)); end; diff --git a/components/fpspreadsheet/xlsxml.pas b/components/fpspreadsheet/xlsxml.pas index 9edc86311..b310a430c 100644 --- a/components/fpspreadsheet/xlsxml.pas +++ b/components/fpspreadsheet/xlsxml.pas @@ -710,14 +710,18 @@ begin row := FWorksheet.FindRow(r); // Row height is needed in pts. if Assigned(row) then + begin rowheightStr := Format(' ss:Height="%.2f"', [FWorkbook.ConvertUnits(row^.Height, FWorkbook.Units, suPoints)], FPointSeparatorSettings - ) - else - rowheightStr := ''; + ); + if row^.RowHeightType = rhtCustom then + rowHeightStr := 'ss:AutoFitHeight="0"' + rowHeightStr else + rowHeightStr := 'ss:AutoFitHeight="1"' + rowHeightStr; + end else + rowheightStr := 'ss:AutoFitHeight="1"'; AppendToStream(AStream, ROW_INDENT + Format( - '' + LF, [rowheightStr])); + '' + LF, [rowheightStr])); for c := c1 to c2 do begin cell := AWorksheet.FindCell(r, c); diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index 3b1a0e908..eb8c61305 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -286,6 +286,8 @@ const LAST_PALETTE_INDEX = 63; + ROWHEIGHT_EPS = 1E-2; + type TFillListData = class PatternType: String; @@ -1717,19 +1719,35 @@ end; procedure TsSpreadOOXMLReader.ReadRowHeight(ANode: TDOMNode; AWorksheet: TsWorksheet); var s: String; - ht: Single; + h: Single; r: Cardinal; + rht: TsRowHeightType; begin if ANode = nil then exit; + + { Row height value, in points - if there is no "ht" attribute we assume that + it is the custom row height which does not require a row record. } + s := GetAttrValue(ANode, 'ht'); + if s = '' then + exit; + h := StrToFloat(s, FPointSeparatorSettings); // seems to be in "Points" + + { Row height type } s := GetAttrValue(ANode, 'customHeight'); - if s = '1' then begin - s := GetAttrValue(ANode, 'r'); - r := StrToInt(s) - 1; - s := GetAttrValue(ANode, 'ht'); - ht := StrToFloat(s, FPointSeparatorSettings); // seems to be in "Points" - AWorksheet.WriteRowHeight(r, ht, suPoints); - end; + if s = '1' then + rht := rhtCustom + else if SameValue(h, AWorksheet.ReadDefaultRowHeight(suPoints), ROWHEIGHT_EPS) then + rht := rhtDefault + else + rht := rhtAuto; + + { Row index } + s := GetAttrValue(ANode, 'r'); + r := StrToInt(s) - 1; + + { Write out } + AWorksheet.WriteRowHeight(r, h, suPoints, rht); end; procedure TsSpreadOOXMLReader.ReadSharedStrings(ANode: TDOMNode); @@ -2912,11 +2930,12 @@ begin then begin for r := 0 to r2 do begin row := AWorksheet.FindRow(r); - if row <> nil then - rh := Format(' ht="%.2f" customHeight="1"', + if row <> nil then begin + rh := Format(' ht="%.2f"', [FWorkbook.ConvertUnits(row^.Height, FWorkbook.Units, suPoints)], - FPointSeparatorSettings) - else + FPointSeparatorSettings); + if row^.RowHeightType = rhtCustom then rh := rh + ' customHeight="1"'; + end else rh := ''; AppendToStream(AStream, Format( '', [r+1, AWorksheet.VirtualColCount, rh])); @@ -2971,11 +2990,12 @@ begin for r := r1 to r2 do begin // If the row has a custom height add this value to the specification row := AWorksheet.FindRow(r); - if row <> nil then - rh := Format(' ht="%.2f" customHeight="1"', + if row <> nil then begin + rh := Format(' ht="%.2f"', [FWorkbook.ConvertUnits(row^.Height, FWorkbook.Units, suPoints)], - FPointSeparatorSettings) - else + FPointSeparatorSettings); + if row^.RowHeightType = rhtCustom then rh := rh + ' customHeight="1"'; + end else rh := ''; AppendToStream(AStream, Format( '', [r+1, c1+1, c2+1, rh])); @@ -3071,7 +3091,7 @@ begin actCell := ''; // Selected tab? - tabSel := StrUtils.IfThen(AWorksheet = FWorkbook.ActiveWorksheet, 'tabSelected="1" ', ''); + tabSel := StrUtils.IfThen(AWorksheet = FWorkbook.ActiveWorksheet, ' tabSelected="1"', ''); // SheetView attributes attr := showGridLines + showHeaders + tabSel + zoomScale + bidi;