diff --git a/components/fpspreadsheet/fpsclasses.pas b/components/fpspreadsheet/fpsclasses.pas index 47b819a62..7c20de884 100644 --- a/components/fpspreadsheet/fpsclasses.pas +++ b/components/fpspreadsheet/fpsclasses.pas @@ -1175,6 +1175,7 @@ begin P^.NumberFormatIndex := AItem.NumberFormatIndex; P^.NumberFormat := AItem.NumberFormat; P^.NumberFormatStr := AItem.NumberFormatStr; + P^.BiDiMode := AItem.BiDiMode; Result := inherited Add(P); end; end; @@ -1293,6 +1294,9 @@ begin if (P^.NumberFormatStr <> AItem.NumberFormatStr) then continue; end; + if (uffBiDi in AItem.UsedFormattingFields) then + if (P^.BiDiMode <> AItem.BiDiMode) then continue; + // If we arrive here then the format records match. exit; end; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index b226c667e..21a54854b 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -170,6 +170,7 @@ type procedure WriteVirtualCells(AStream: TStream; ASheet: TsWorksheet); function WriteBackgroundColorStyleXMLAsString(const AFormat: TsCellFormat): String; + function WriteBiDiModeStyleXMLAsString(const AFormat: TsCellFormat): String; function WriteBorderStyleXMLAsString(const AFormat: TsCellFormat): String; function WriteCommentXMLAsString(AComment: String): String; function WriteDefaultFontXMLAsString: String; @@ -3555,6 +3556,14 @@ begin fmt.HorAlignment := haCenter; if fmt.HorAlignment <> haDefault then Include(fmt.UsedFormattingFields, uffHorAlign); + // BiDi mode + s := GetAttrValue(styleChildNode, 'style:writing-mode'); + if s = 'lr-tb' then + fmt.BiDiMode := bdRTL + else if s = 'rl-tb' then + fmt.BiDiMode := bdRTL; + if fmt.BiDiMode <> bdDefault then + Include(fmt.UsedFormattingFields, uffBiDi); end; styleChildNode := styleChildNode.NextSibling; end; @@ -4282,7 +4291,7 @@ begin ''); // style:paragraph-properties - s := WriteHorAlignmentStyleXMLAsString(fmt); + s := WriteHorAlignmentStyleXMLAsString(fmt) + WriteBiDiModeStyleXMLAsString(fmt); if s <> '' then AppendToStream(AStream, ''); @@ -4981,6 +4990,18 @@ begin Result := Format('fo:background-color="%s" ', [ColorToHTMLColorStr(TsColor(mix))]); end; +function TsSpreadOpenDocWriter.WriteBiDiModeStyleXMLAsString( + const AFormat: TsCellFormat): String; +begin + Result := ''; + if not (uffBiDi in AFormat.UsedFormattingFields) then + exit; + case AFormat.BiDiMode of + bdLTR : Result := 'style:writing-mode="lr-tb" '; + bdRTL : Result := 'style:writing-mode="rl-tb" '; + end; +end; + {@@ ---------------------------------------------------------------------------- Creates an XML string for inclusion of borders and border styles into the written file from the border settings in the given format record. diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 9dd608cb3..bc26ef170 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -205,6 +205,7 @@ type function ReadTextRotation(ACell: PCell): TsTextRotation; function ReadVertAlignment(ACell: PCell): TsVertAlignment; function ReadWordwrap(ACell: PCell): boolean; + function ReadBiDiMode(ACell: PCell): TsBiDiMode; { Writing of values } function WriteBlank(ARow, ACol: Cardinal): PCell; overload; @@ -369,6 +370,9 @@ type function WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean): PCell; overload; procedure WriteWordwrap(ACell: PCell; AValue: boolean); overload; + function WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; overload; + procedure WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); overload; + { Formulas } function BuildRPNFormula(ACell: PCell; ADestCell: PCell = nil): TsRPNFormula; procedure CalcFormula(ACell: PCell); @@ -3075,6 +3079,22 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Returns the BiDi mode of the cell (right-to-left or left-to-right) +-------------------------------------------------------------------------------} +function TsWorksheet.ReadBiDiMode(ACell: PCell): TsBiDiMode; +var + fmt: PsCellFormat; +begin + Result := bdDefault; + if (ACell <> nil) then + begin + fmt := Workbook.GetPointerToCellFormat(ACell^.FormatIndex); + if (uffBiDi in fmt^.UsedFormattingFields) then + Result := fmt^.BiDiMode; + end; +end; + { Merged cells } @@ -5741,6 +5761,28 @@ begin ChangedCell(ACell^.Row, ACell^.Col); end; +function TsWorksheet.WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; +begin + Result := GetCell(ARow, ACol); + WriteBiDiMode(Result, AVAlue); +end; + +procedure TsWorksheet.WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); +var + fmt: TsCellFormat; +begin + if ACell = nil then + exit; + fmt := Workbook.GetCellFormat(ACell^.FormatIndex); + fmt.BiDiMode := AValue; + if AValue <> bdDefault then + Include(fmt.UsedFormattingFields, uffBiDi) + else + Exclude(fmt.UsedFormattingFields, uffBiDi); + ACell^.FormatIndex := Workbook.AddCellFormat(fmt); + ChangedCell(ACell^.Row, ACell^.Col); +end; + function TsWorksheet.GetFormatSettings: TFormatSettings; begin Result := FWorkbook.FormatSettings; @@ -7525,6 +7567,8 @@ begin else s := s + '+' + GetEnumName(TypeInfo(TsCellBorder), ord(cb)); Result := Format('%s; %s', [Result, s]); end; + if (uffBiDi in fmt^.UsedFormattingFields) then + Result := Format('%s; %s', [Result, GetEnumName(TypeInfo(TsBiDiMode), ord(fmt^.BiDiMode))]); if Result <> '' then Delete(Result, 1, 2); end; diff --git a/components/fpspreadsheet/fpspreadsheetctrls.pas b/components/fpspreadsheet/fpspreadsheetctrls.pas index 96c185ee3..c2263cad3 100644 --- a/components/fpspreadsheet/fpspreadsheetctrls.pas +++ b/components/fpspreadsheet/fpspreadsheetctrls.pas @@ -3015,6 +3015,12 @@ begin AStrings.Add('NumberFormatStr=' + numFmt.NumFormatStr); end; + if (ACell = nil) or not (uffBiDi in fmt.UsedFormattingFields) then + AStrings.Add('BiDi=(bdDefault)') + else + AStrings.Add(Format('BiDiMode=%s', [ + GetEnumName(TypeInfo(TsBiDiMode), ord(fmt.BiDiMode))])); + if (Worksheet = nil) or not Worksheet.IsMerged(ACell) then AStrings.Add('Merged range=(none)') else diff --git a/components/fpspreadsheet/fpspreadsheetgrid.pas b/components/fpspreadsheet/fpspreadsheetgrid.pas index 47608ef66..df619488e 100644 --- a/components/fpspreadsheet/fpspreadsheetgrid.pas +++ b/components/fpspreadsheet/fpspreadsheetgrid.pas @@ -82,6 +82,7 @@ type // Setter/Getter function GetBackgroundColor(ACol, ARow: Integer): TsColor; function GetBackgroundColors(ALeft, ATop, ARight, ABottom: Integer): TsColor; + function GetCellBiDiMode(ACol, ARow: Integer): TsBiDiMode; function GetCellBorder(ACol, ARow: Integer): TsCellBorders; function GetCellBorders(ALeft, ATop, ARight, ABottom: Integer): TsCellBorders; function GetCellBorderStyle(ACol, ARow: Integer; ABorder: TsCellBorder): TsCellBorderStyle; @@ -122,6 +123,7 @@ type procedure SetAutoCalc(AValue: Boolean); procedure SetBackgroundColor(ACol, ARow: Integer; AValue: TsColor); procedure SetBackgroundColors(ALeft, ATop, ARight, ABottom: Integer; AValue: TsColor); + procedure SetCellBiDiMode(ACol, ARow: Integer; AValue: TsBiDiMode); procedure SetCellBorder(ACol, ARow: Integer; AValue: TsCellBorders); procedure SetCellBorders(ALeft, ATop, ARight, ABottom: Integer; AValue: TsCellBorders); procedure SetCellBorderStyle(ACol, ARow: Integer; ABorder: TsCellBorder; @@ -209,7 +211,8 @@ type procedure InternalDrawTextInCell(AText: String; ARect: TRect; ACellHorAlign: TsHorAlignment; ACellVertAlign: TsVertAlignment; ATextRot: TsTextRotation; ATextWrap: Boolean; AFontIndex: Integer; - AOverrideTextColor: TColor; ARichTextParams: TsRichTextParams); + AOverrideTextColor: TColor; ARichTextParams: TsRichTextParams; + AIsRightToLeft: Boolean); procedure KeyDown(var Key : Word; Shift : TShiftState); override; procedure Loaded; override; procedure MouseDown(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override; @@ -327,6 +330,9 @@ type Expressed as index into the workbook's color palette. } property BackgroundColors[ALeft, ATop, ARight, ABottom: Integer]: TsColor read GetBackgroundColors write SetBackgroundColors; + {@@ Override system or sheet right-to-left mode for cell } + property CellBiDiMode[ACol,ARow: Integer]: TsBiDiMode + read GetCellBiDiMode write SetCellBiDiMode; {@@ Set of flags indicating at which cell border a border line is drawn. } property CellBorder[ACol, ARow: Integer]: TsCellBorders read GetCellBorder write SetCellBorder; @@ -990,10 +996,12 @@ var w, maxw: Integer; txt: String; cell: PCell; + RTL: Boolean; begin if Worksheet = nil then exit; + RTL := IsRightToLeft; maxw := -1; for cell in Worksheet.Cells.GetColEnumerator(GetWorkSheetCol(ACol)) do begin @@ -1001,9 +1009,13 @@ begin txt := GetCellText(ACol, gRow); if txt = '' then Continue; + case Worksheet.ReadBiDiMode(cell) of + bdRTL: RTL := true; + bdLTR: RTL := false; + end; w := RichTextWidth(Canvas, Workbook, Rect(0, 0, MaxInt, MaxInt), txt, cell^.RichTextParams, Worksheet.ReadCellFontIndex(cell), - Worksheet.ReadTextRotation(cell), false, IsRightToLeft); + Worksheet.ReadTextRotation(cell), false, RTL); if w > maxw then maxw := w; end; if maxw > -1 then @@ -2174,8 +2186,8 @@ begin temp_rct := rct; // for i := gc1 to gc2 do begin for i:= gc2 downto gc1 do begin - // Starting from last col will insure drawing grid lines below text - // when text is overflow in RTL and have no problem in LTR + // Starting from last col will ensure drawing grid lines below text + // when text is overflowing in RTL, no problem in LTR // (Modification by "shobits1" - ok) ColRowToOffset(true, true, i, temp_rct.Left, temp_rct.Right); if HorizontalIntersect(temp_rct, clipArea) and (i <> gc) then @@ -2349,6 +2361,7 @@ var numfmt: TsNumFormatParams; numFmtColor: TColor; sidx: Integer; // number format section index + RTL: Boolean; begin if (Worksheet = nil) then exit; @@ -2379,6 +2392,12 @@ begin fmt := Workbook.GetPointerToCellFormat(lCell^.FormatIndex); wrapped := (uffWordWrap in fmt^.UsedFormattingFields) or (fmt^.TextRotation = rtStacked); + RTL := IsRightToLeft; + if (uffBiDi in fmt^.UsedFormattingFields) then + case Worksheet.ReadBiDiMode(lCell) of + bdRTL : RTL := true; + bdLTR : RTL := false; + end; // Text rotation if (uffTextRotation in fmt^.UsedFormattingFields) @@ -2400,7 +2419,7 @@ begin begin if (lCell^.ContentType in [cctNumber, cctDateTime]) then begin - if IsRightToLeft then + if RTL then horAlign := haLeft else horAlign := haRight; @@ -2408,7 +2427,7 @@ begin if (lCell^.ContentType in [cctBool]) then horAlign := haCenter else begin - if IsRightToLeft then + if RTL then horAlign := haRight else horAlign := haLeft; @@ -2441,7 +2460,7 @@ begin InflateRect(ARect, -constCellPadding, -constCellPadding); InternalDrawTextInCell(txt, ARect, horAlign, vertAlign, txtRot, wrapped, - fntIndex, numfmtColor, lCell^.RichTextParams); + fntIndex, numfmtColor, lCell^.RichTextParams, RTL); end; {@@ ---------------------------------------------------------------------------- @@ -2698,6 +2717,16 @@ begin end; end; +function TsCustomWorksheetGrid.GetCellBiDiMode(ACol,ARow: Integer): TsBiDiMode; +var + cell: PCell; +begin + cell := Worksheet.FindCell(GetWorksheetRow(ARow), GetWorksheetCol(ACol)); + if cell <> nil then + Result := Worksheet.ReadBiDiMode(cell) else + Result := bdDefault; +end; + {@@ ---------------------------------------------------------------------------- Returns the cell borders which are drawn around a given cell. If the cell is part of a merged block then the borders of the merge base @@ -2935,6 +2964,7 @@ var fmt: PsCellFormat; fntIndex: Integer; txtRot: TsTextRotation; + RTL: Boolean; begin Result := 0; if ShowHeaders and ((ACol = 0) or (ARow = 0)) then @@ -2972,6 +3002,13 @@ begin txtRot := fmt^.TextRotation else txtRot := trHorizontal; wrapped := (uffWordWrap in fmt^.UsedFormattingFields); + RTL := IsRightToLeft; + if (uffBiDi in fmt^.UsedFormattingFields) then + case fmt^.BiDiMode of + bdRTL: RTL := true; + bdLTR: RTL := false; + end; + case txtRot of trHorizontal: ; rt90DegreeClockwiseRotation, @@ -2982,47 +3019,8 @@ begin end; Result := RichTextHeight(Canvas, Workbook, cellR, s, lCell^.RichTextParams, - fntIndex, txtRot, wrapped, IsRightToLeft) + fntIndex, txtRot, wrapped, RTL) + 2 * constCellPadding; - - (* - wrapped := (uffWordWrap in fmt^.UsedFormattingFields) - or (fmt^.TextRotation = rtStacked); - // *** multi-line text *** - if wrapped then - begin - // horizontal - if ( (uffTextRotation in fmt^.UsedFormattingFields) and - (fmt^.TextRotation in [trHorizontal, rtStacked])) - or not (uffTextRotation in fmt^.UsedFormattingFields) - then - begin - cellR := CellRect(ACol, ARow); - InflateRect(cellR, -constCellPadding, -constCellPadding); - txtR := Bounds(cellR.Left, cellR.Top, cellR.Right-cellR.Left, cellR.Bottom-cellR.Top); - flags := DT_WORDBREAK and not DT_SINGLELINE; - LCLIntf.DrawText(Canvas.Handle, PChar(s), Length(s), txtR, - DT_CALCRECT or flags); - Result := txtR.Bottom - txtR.Top + 2*constCellPadding; - end; - // rotated wrapped text: - // do not consider this because wrapping affects cell height. - end else - // *** single-line text *** - begin - // not rotated - if ( not (uffTextRotation in fmt^.UsedFormattingFields) or - (fmt^.TextRotation = trHorizontal) ) - then - Result := Canvas.TextHeight(s) + 2*constCellPadding - else - // rotated by +/- 90° - if (uffTextRotation in fmt^.UsedFormattingFields) and - (fmt^.TextRotation in [rt90DegreeClockwiseRotation, rt90DegreeCounterClockwiseRotation]) - then - Result := Canvas.TextWidth(s) + 2*constCellPadding; - end; - *) end; end; @@ -3517,6 +3515,7 @@ end; @param AFontIndex Font index to be used for drawing non-rich-text. @param ARichTextParams an array of character and font index combinations for rich-text formatting of text in cell + @param AIsRightToLeft if true cell must be drawn in right-to-left mode. @Note The reason to separate AJustification from ACellHorAlign and ACelVertAlign is the output of nfAccounting formatted numbers where the numbers are always @@ -3526,7 +3525,8 @@ end; procedure TsCustomWorksheetGrid.InternalDrawTextInCell(AText: String; ARect: TRect; ACellHorAlign: TsHorAlignment; ACellVertAlign: TsVertAlignment; ATextRot: TsTextRotation; ATextWrap: Boolean; AFontIndex: Integer; - AOverrideTextColor: TColor; ARichTextParams: TsRichTextParams); + AOverrideTextColor: TColor; ARichTextParams: TsRichTextParams; + AIsRightToLeft: Boolean); begin // Since - due to the rich-text mode - characters are drawn individually their // background occasionally overpaints the prev characters (italic). To avoid @@ -3536,7 +3536,7 @@ begin // Work horse for text drawing, both standard text and rich-text DrawRichText(Canvas, Workbook, ARect, AText, ARichTextParams, AFontIndex, ATextWrap, ACellHorAlign, ACellVertAlign, ATextRot, AOverrideTextColor, - IsRightToLeft + AIsRightToLeft ); end; (* @@ -3730,6 +3730,7 @@ begin end; end; *) + {@@ ---------------------------------------------------------------------------- Standard key handling method inherited from TCustomGrid. Is overridden to catch some keys for special processing. @@ -5104,6 +5105,13 @@ begin end; end; +procedure TsCustomWorksheetGrid.SetCellBiDiMode(ACol, ARow: Integer; + AValue: TsBiDiMode); +begin + if Assigned(Worksheet) then + Worksheet.WriteBiDiMode(GetWorksheetRow(ARow), GetWorksheetCol(ACol), AValue); +end; + procedure TsCustomWorksheetGrid.SetCellBorder(ACol, ARow: Integer; AValue: TsCellBorders); var diff --git a/components/fpspreadsheet/fpstypes.pas b/components/fpspreadsheet/fpstypes.pas index 44bbc53f1..0015cd559 100644 --- a/components/fpspreadsheet/fpstypes.pas +++ b/components/fpspreadsheet/fpstypes.pas @@ -209,8 +209,8 @@ type ); {@@ List of possible formatting fields } - TsUsedFormattingField = (uffTextRotation, uffFont, {uffBold, }uffBorder, - uffBackground, uffNumberFormat, uffWordWrap, uffHorAlign, uffVertAlign + TsUsedFormattingField = (uffTextRotation, uffFont, uffBorder, uffBackground, + uffNumberFormat, uffWordWrap, uffHorAlign, uffVertAlign, uffBiDi ); { NOTE: "uffBackgroundColor" of older versions replaced by "uffBackground" } @@ -596,6 +596,9 @@ type Keys: TsSortKeys; end; + {@@ Switch a cell from left-to-right to right-to-left orientation } + TsBiDiMode = (bdDefault, bdLTR, bdRTL); + {@@ Record containing all details for cell formatting } TsCellFormat = record Name: String; @@ -609,6 +612,7 @@ type BorderStyles: TsCelLBorderStyles; Background: TsFillPattern; NumberFormatIndex: Integer; + BiDiMode: TsBiDiMode; // next two are deprecated... NumberFormat: TsNumberFormat; NumberFormatStr: String; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index c690d7789..61378aed1 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -361,6 +361,9 @@ const {%H-}MASK_HLINK_TARGETFRAME = $00000080; {%H-}MASK_HLINK_UNCPATH = $00000100; + { RIGHT-TO-LEFT FLAG } + MASK_XF_BIDI = $C0; + SHAPEID_BASE = 1024; @@ -1515,6 +1518,11 @@ begin if (rec.Align_TextBreak and MASK_XF_TEXTWRAP) <> 0 then Include(fmt.UsedFormattingFields, uffWordwrap); + // BiDi mode + b := (rec.Indent_Shrink_TextDir and MASK_XF_BIDI) shr 6; + if b in [0..2] then fmt.BiDiMode := TsBiDiMode(b); + if b > 0 then Include(fmt.UsedFormattingFields, uffBiDi); + // TextRotation case rec.TextRotation of XF_ROTATION_HORIZONTAL : fmt.TextRotation := trHorizontal; @@ -3338,6 +3346,12 @@ begin Bit 5: MergeCell Bits 6-7: Reading direction } rec.Indent_Shrink_TextDir := 0; + if (AFormatRecord <> nil) and (uffBiDi in AFormatRecord^.UsedFormattingFields) then + begin + b := ord(AFormatRecord^.BiDiMode); + if b > 0 then + rec.Indent_Shrink_TextDir := rec.Indent_Shrink_TextDir or (b shl 6); + end; { Used attributes } rec.UsedAttrib := diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index 64fc68b94..5521cf709 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -838,6 +838,10 @@ begin if s1 = 'bottom' then fmt.VertAlignment := vaBottom; + s1 := GetAttrValue(childNode, 'readingOrder'); + if (s1 = '1') or (s1 = '2') then + fmt.BiDiMode := TsBiDiMode(StrToInt(s1)); + s1 := GetAttrValue(childNode, 'wrapText'); if (s1 <> '0') then Include(fmt.UsedFormattingFields, uffWordWrap); @@ -867,6 +871,8 @@ begin Include(fmt.UsedFormattingFields, uffVertAlign); if fmt.TextRotation <> trHorizontal then Include(fmt.UsedFormattingFields, uffTextRotation); + if fmt.BiDiMode <> bdDefault then + Include(fmt.UsedFormattingFields, uffBiDi); FCellFormatList.Add(fmt); end; node := node.NextSibling; @@ -3040,9 +3046,14 @@ begin vaBottom: sAlign := sAlign + 'vertical="bottom" '; end; + { Word wrap } if (uffWordWrap in fmt^.UsedFormattingFields) then sAlign := sAlign + 'wrapText="1" '; + { BiDi mode } + if (uffBiDi in fmt^.UsedFormattingFields) and (fmt^.BiDiMode <> bdDefault) then + sAlign := sAlign + Format('readingOrder="%d" ', [Ord(fmt^.BiDiMode)]); + { Fill } if (uffBackground in fmt^.UsedFormattingFields) then begin