Skip to content

Commit 9310861

Browse files
committed
Fix issues with vertical text in table cells in DOCX to RTF/HTML
1 parent 0b31e52 commit 9310861

File tree

8 files changed

+145
-83
lines changed

8 files changed

+145
-83
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
- Hidden paragraphs are no longer exported in DOCX to HTML/Markdown/TXT conversion
66
- Hidden runs are no longer exported in DOCX to Markdown/TXT conversion and are hidden in DOCX to HTML
77
- Support for inset, outset, groove and ridge border styles in DOCX to HTML converter
8+
- Support for 3D and dash-dot stripe border styles in DOCX to RTF converter
89
- Try to preserve text fill effect as font color in DOCX to RTF converter
910
- Preserve "leading zeros" format in numbered lists in DOCX to HTML/TXT converter
1011
- Fix paragraph spacing and automatic cell height for default tables in DOCX to HTML converter
11-
- Fix row height in DOCX to HTML converter
12+
- Fix row height not detected in some cases in DOCX to HTML converter
13+
- Fix issues with vertical text in table cells in DOCX to RTF/HTML converter
1214

1315
**Full Changelog**: https://github.com/manfromarce/DocSharp/compare/v0.11.0...v0.12.0
1416

src/DocSharp.Docx/DocxToHtml/DocxToHtmlConverter.Borders.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace DocSharp.Docx;
1212

1313
public partial class DocxToHtmlConverter : DocxToTextWriterBase<HtmlTextWriter>
1414
{
15-
internal void ProcessBorder(BorderType? border, ref List<string> styles, bool isTableCell, bool isLastRow = false, bool isLastColumn = false)
15+
internal void ProcessBorder(BorderType? border, ref List<string> styles, bool isTableCell, bool isLastRow = false, bool isLastColumn = false, bool isVertical = false)
1616
{
1717
if (border == null)
1818
{
@@ -39,16 +39,18 @@ internal void ProcessBorder(BorderType? border, ref List<string> styles, bool is
3939
}
4040
else if (border is BarBorder) // paragraph border between facing pages
4141
{
42-
//cssAttribute = "border-right";
43-
cssAttribute = "border-inline-end";
42+
cssAttribute = isVertical ? "border-right" : "border-inline-end";
43+
// If the cell has vertical orientation, inline-end is considered the bottom border (incorrect)
4444
}
4545
else if (border is StartBorder)
4646
{
47-
cssAttribute = "border-inline-start";
47+
cssAttribute = isVertical ? "border-left" : "border-inline-start";
48+
// If the cell has vertical orientation, inline-start is considered the top border (incorrect)
4849
}
4950
else if (border is EndBorder)
5051
{
51-
cssAttribute = "border-inline-end";
52+
cssAttribute = isVertical ? "border-right" : "border-inline-end";
53+
// If the cell has vertical orientation, inline-end is considered the bottom border (incorrect)
5254
}
5355
else if (border is InsideHorizontalBorder) // for tables
5456
{
@@ -60,7 +62,8 @@ internal void ProcessBorder(BorderType? border, ref List<string> styles, bool is
6062
{
6163
if (isLastColumn)
6264
return;
63-
cssAttribute = "border-inline-end";
65+
cssAttribute = isVertical ? "border-right" : "border-inline-end";
66+
// If the cell has vertical orientation, inline-end is considered the bottom border (incorrect)
6467
}
6568
else if (border is Border) // Used for characters borders (same for top, left, bottom and right)
6669
{

src/DocSharp.Docx/DocxToHtml/DocxToHtmlConverter.Paragraph.cs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,15 @@ internal override void ProcessParagraph(Paragraph paragraph, HtmlTextWriter sb)
3737
var keepLines = OpenXmlHelpers.GetEffectiveProperty<KeepLines>(paragraph);
3838
var keepNext = OpenXmlHelpers.GetEffectiveProperty<KeepNext>(paragraph);
3939
var widowControl = OpenXmlHelpers.GetEffectiveProperty<WidowControl>(paragraph);
40-
var direction = OpenXmlHelpers.GetEffectiveProperty<TextDirection>(paragraph);
4140
// var frameProperties = OpenXmlHelpers.GetEffectiveProperty<FrameProperties>(paragraph); // TODO
4241

4342
// Build CSS style string
4443
var styles = new List<string>();
44+
45+
// var direction = paragraph.GetEffectiveProperty<TextDirection>() ??
46+
// cell.GetEffectiveProperty<TextDirection>();
47+
// // Direction is not applied to regular paragraphs in DOCX but only in table cells and text boxes
48+
4549
if (alignment != null)
4650
{
4751
if (alignment == JustificationValues.Left || alignment == JustificationValues.Start)
@@ -56,6 +60,22 @@ internal override void ProcessParagraph(Paragraph paragraph, HtmlTextWriter sb)
5660
styles.Add("text-align: justify;");
5761
}
5862

63+
if (verticalAlignment?.Val != null)
64+
{
65+
if (verticalAlignment.Val == VerticalTextAlignmentValues.Top || verticalAlignment.Val == VerticalTextAlignmentValues.Auto)
66+
styles.Add("vertical-align: top;");
67+
else if (verticalAlignment.Val == VerticalTextAlignmentValues.Center)
68+
styles.Add("vertical-align: middle;");
69+
else if (verticalAlignment.Val == VerticalTextAlignmentValues.Bottom)
70+
styles.Add("vertical-align: bottom;");
71+
else if (verticalAlignment.Val == VerticalTextAlignmentValues.Baseline)
72+
styles.Add("vertical-align: baseline;");
73+
}
74+
else
75+
{
76+
styles.Add("vertical-align: top;");
77+
}
78+
5979
if (paragraph.GetEffectiveBorder<TopBorder>() is TopBorder topBorder)
6080
ProcessBorder(topBorder, ref styles, false);
6181
if (paragraph.GetEffectiveBorder<BottomBorder>() is BottomBorder bottomBorder)
@@ -127,23 +147,6 @@ internal override void ProcessParagraph(Paragraph paragraph, HtmlTextWriter sb)
127147
ProcessIndentation(indent, ref styles);
128148
}
129149

130-
if (verticalAlignment?.Val != null)
131-
{
132-
if (verticalAlignment.Val == VerticalTextAlignmentValues.Top || verticalAlignment.Val == VerticalTextAlignmentValues.Auto)
133-
styles.Add("vertical-align: top;");
134-
else if (verticalAlignment.Val == VerticalTextAlignmentValues.Center)
135-
styles.Add("vertical-align: middle;");
136-
else if (verticalAlignment.Val == VerticalTextAlignmentValues.Bottom)
137-
styles.Add("vertical-align: bottom;");
138-
else if (verticalAlignment.Val == VerticalTextAlignmentValues.Baseline)
139-
styles.Add("vertical-align: baseline;");
140-
}
141-
142-
if (direction?.Val != null)
143-
{
144-
ProcessTextDirection(direction.Val.Value, ref styles);
145-
}
146-
147150
if (widowControl.ToBool() || keepLines.ToBool())
148151
{
149152
// Avoid breaks inside the paragraph
@@ -160,7 +163,9 @@ internal override void ProcessParagraph(Paragraph paragraph, HtmlTextWriter sb)
160163
// By default text breaks in new lines at the word level.
161164
// If WordWrap is set to off the document allows to break at character level.
162165
styles.Add("word-break: break-all;");
166+
styles.Add("word-wrap: break-word;");
163167
}
168+
164169
if (paragraph.GetEffectiveProperty<SuppressAutoHyphens>().ToBool())
165170
{
166171
styles.Add(@"hyphens: none;");

src/DocSharp.Docx/DocxToHtml/DocxToHtmlConverter.Run.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ internal override void ProcessRun(Run run, HtmlTextWriter sb)
9292
styles.Add($"letter-spacing: {letterSpacing.ToStringInvariant(2)}pt;");
9393
}
9494

95-
// CharacterScale
95+
// Characters scale
9696
if (fontScaling?.Val != null)
9797
{
9898
//double scale = fontScaling.Val / 100.0; // Convert percent to decimal
@@ -103,6 +103,11 @@ internal override void ProcessRun(Run run, HtmlTextWriter sb)
103103
styles.Add($"font-stretch: {fontScaling.Val.Value.ToStringInvariant()}%;");
104104
}
105105

106+
//if (run.GetEffectiveProperty<FitText>() is FitText fitText && fitText.Val != null)
107+
//{
108+
// var fitTextInPoints = fitText.Val.Value / 20m;
109+
//}
110+
106111
// Kern (font-kerning)
107112
if (kerning != null)
108113
{

src/DocSharp.Docx/DocxToHtml/DocxToHtmlConverter.Table.cs

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,40 @@ internal bool ProcessTableCellProperties(TableCell cell, ref List<string> cellSt
168168
}
169169
}
170170

171-
var direction = cell.TableCellProperties?.TextDirection;
171+
if (cell.GetEffectiveProperty<NoWrap>().ToBool() || cell.GetEffectiveProperty<TableCellFitText>().ToBool())
172+
{
173+
cellStyles.Add("white-space: nowrap;");
174+
cellStyles.Add("overflow: hidden;");
175+
cellStyles.Add("text-overflow: ellipsis;");
176+
// TODO: for TableCellFitText adjust letter spacing and font size to fit container
177+
}
178+
else
179+
{
180+
cellStyles.Add("word-wrap: break-word;");
181+
cellStyles.Add("overflow-wrap: break-word;");
182+
cellStyles.Add("word-break: break-all;");
183+
// Otherwise vertical text will *not* wrap in multiple lines if it doesn't fit the row height
184+
}
185+
186+
bool isVertical = false;
187+
var direction = cell.GetEffectiveProperty<TextDirection>();
172188
if (direction?.Val != null)
173189
{
174-
ProcessTextDirection(direction.Val.Value, ref cellStyles);
190+
ProcessTextDirection(direction.Val.Value, ref cellStyles, out isVertical);
191+
}
192+
var verticalAlignment = OpenXmlHelpers.GetEffectiveProperty<TableCellVerticalAlignment>(cell);
193+
if (verticalAlignment?.Val != null)
194+
{
195+
if (verticalAlignment.Val == TableVerticalAlignmentValues.Top)
196+
cellStyles.Add("vertical-align: top;");
197+
else if (verticalAlignment.Val == TableVerticalAlignmentValues.Center)
198+
cellStyles.Add("vertical-align: middle;");
199+
else if (verticalAlignment.Val == TableVerticalAlignmentValues.Bottom)
200+
cellStyles.Add("vertical-align: bottom;");
201+
}
202+
else
203+
{
204+
cellStyles.Add("vertical-align: top;");
175205
}
176206

177207
var margin = OpenXmlHelpers.GetEffectiveProperty<TableCellMargin>(cell);
@@ -208,33 +238,43 @@ internal bool ProcessTableCellProperties(TableCell cell, ref List<string> cellSt
208238
RemoveStyleIfPresent(ref cellStyles, "padding-inline-end");
209239
ProcessTableWidthType(margin?.EndMargin, ref cellStyles, "padding-inline-end");
210240
}
211-
}
241+
}
212242

213-
if (cell.GetEffectiveProperty<TableCellFitText>().ToBool())
243+
var cellWidth = OpenXmlHelpers.GetEffectiveProperty<TableCellWidth>(cell);
244+
ProcessTableWidthType(cellWidth, ref cellStyles, "width");
245+
246+
// For vertical text, it seems row height is not applied if not specified for cells too.
247+
var height = cell.GetFirstAncestor<TableRow>()?.GetEffectiveProperty<TableRowHeight>();
248+
if (height != null &&
249+
height.Val != null &&
250+
(height.HeightType == null || // if HeightType is not specified but a value is present, assume it means "Exact"
251+
height.HeightType.Value == HeightRuleValues.AtLeast ||
252+
height.HeightType.Value == HeightRuleValues.Exact))
253+
// if HeightType is "Auto" instead, the row should automatically resize to fit the content
214254
{
215-
cellStyles.Add("white-space: nowrap;");
216-
cellStyles.Add("overflow: hidden;");
217-
cellStyles.Add("text-overflow: ellipsis;");
218-
// TODO: adjust letter spacing to fit container
219-
}
255+
string property;
256+
if (height.HeightType != null && height.HeightType.Value == HeightRuleValues.AtLeast)
257+
property = "min-height";
258+
else
259+
property = "height";
220260

221-
var cellWidth = OpenXmlHelpers.GetEffectiveProperty<TableCellWidth>(cell);
222-
ProcessTableWidthType(cellWidth, ref cellStyles, "width");
261+
cellStyles.Add($"{property}: {(height.Val.Value / 20m).ToStringInvariant(2)}pt;"); // Convert twips to points
262+
}
223263

224264
BorderType? topBorder = cell.GetEffectiveBorder(Primitives.BorderValue.Top, rowNumber, columnNumber, rowCount, columnCount);
225-
ProcessBorder(topBorder, ref cellStyles, true);
265+
ProcessBorder(topBorder, ref cellStyles, true, isVertical: isVertical);
226266
BorderType? bottomBorder = cell.GetEffectiveBorder(Primitives.BorderValue.Bottom, rowNumber, columnNumber, rowCount, columnCount);
227-
ProcessBorder(bottomBorder, ref cellStyles, true);
267+
ProcessBorder(bottomBorder, ref cellStyles, true, isVertical: isVertical);
228268
BorderType? leftBorder = cell.GetEffectiveBorder(Primitives.BorderValue.Left, rowNumber, columnNumber, rowCount, columnCount);
229-
ProcessBorder(leftBorder, ref cellStyles, true);
269+
ProcessBorder(leftBorder, ref cellStyles, true, isVertical: isVertical);
230270
BorderType? rightBorder = cell.GetEffectiveBorder(Primitives.BorderValue.Right, rowNumber, columnNumber, rowCount, columnCount);
231-
ProcessBorder(rightBorder, ref cellStyles, true);
271+
ProcessBorder(rightBorder, ref cellStyles, true, isVertical: isVertical);
232272
if (leftBorder == null && rightBorder == null) // Left and right should have priority over start and end as they are more specific
233273
{
234274
BorderType? startBorder = cell.GetEffectiveBorder(Primitives.BorderValue.Start, rowNumber, columnNumber, rowCount, columnCount);
235-
ProcessBorder(startBorder, ref cellStyles, true);
275+
ProcessBorder(startBorder, ref cellStyles, true, isVertical: isVertical);
236276
BorderType? endBorder = cell.GetEffectiveBorder(Primitives.BorderValue.End, rowNumber, columnNumber, rowCount, columnCount);
237-
ProcessBorder(endBorder, ref cellStyles, true);
277+
ProcessBorder(endBorder, ref cellStyles, true, isVertical: isVertical);
238278
}
239279

240280
ProcessShading(OpenXmlHelpers.GetEffectiveProperty<Shading>(cell), ref cellStyles);

src/DocSharp.Docx/DocxToHtmlConverter.cs

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -122,41 +122,44 @@ internal override void EnsureSpace(HtmlTextWriter sb)
122122
sb.WriteEndElement("p");
123123
}
124124

125-
private void ProcessTextDirection(TextDirectionValues value, ref List<string> styles)
125+
private void ProcessTextDirection(TextDirectionValues textDirection, ref List<string> styles, out bool isVertical)
126126
{
127127
// Possible CSS properties: direction, unicode-bidi, text-orientation, writing-mode
128-
if (value == TextDirectionValues.LefToRightTopToBottom ||
129-
value == TextDirectionValues.LeftToRightTopToBottom2010)
128+
129+
isVertical = false;
130+
if (textDirection == TextDirectionValues.LefToRightTopToBottom ||
131+
textDirection == TextDirectionValues.LeftToRightTopToBottom2010 ||
132+
textDirection == TextDirectionValues.LefttoRightTopToBottomRotated ||
133+
textDirection == TextDirectionValues.LeftToRightTopToBottomRotated2010)
130134
{
131-
// Horizontal text, left to right (default)
135+
// Horizontal text, left to right
136+
// (there seems to be no difference in DOCX between LefToRightTopToBottom and LefttoRightTopToBottomRotated)
132137
styles.Add("writing-mode: horizontal-tb;");
133-
}
134-
if (value == TextDirectionValues.TopToBottomRightToLeft ||
135-
value == TextDirectionValues.TopToBottomRightToLeft2010)
136-
{
137-
// Horizontal text, right to left
138-
}
139-
if (value == TextDirectionValues.BottomToTopLeftToRight ||
140-
value == TextDirectionValues.BottomToTopLeftToRight2010)
138+
}
139+
if (textDirection == TextDirectionValues.TopToBottomLeftToRightRotated ||
140+
textDirection == TextDirectionValues.TopToBottomLeftToRightRotated2010)
141141
{
142-
// Horizontal text, bottom to top
143-
}
144-
if (value == TextDirectionValues.LefttoRightTopToBottomRotated ||
145-
value == TextDirectionValues.LeftToRightTopToBottomRotated2010 ||
146-
value == TextDirectionValues.TopToBottomLeftToRightRotated ||
147-
value == TextDirectionValues.TopToBottomLeftToRightRotated2010)
148-
{
149-
// Vertical text
142+
// Vertical text (rotated letters), top to bottom, left to right
150143
styles.Add("writing-mode: vertical-lr;");
151-
styles.Add("text-orientation: upright;");
144+
isVertical = true;
152145
}
153-
if (value == TextDirectionValues.TopToBottomRightToLeftRotated ||
154-
value == TextDirectionValues.TopToBottomRightToLeftRotated2010)
146+
if (textDirection == TextDirectionValues.TopToBottomRightToLeft ||
147+
textDirection == TextDirectionValues.TopToBottomRightToLeft2010 ||
148+
textDirection == TextDirectionValues.TopToBottomRightToLeftRotated ||
149+
textDirection == TextDirectionValues.TopToBottomRightToLeftRotated2010)
155150
{
156-
// Vertical text
157-
styles.Add("writing-mode: vertical-rl;");
158-
styles.Add("text-orientation: upright;");
151+
// Vertical text (rotated letters), top to bottom, right to left
152+
// (there seems to be no difference in DOCX between TopToBottomRightToLeft and TopToBottomRightToLeftRotated)
153+
styles.Add("writing-mode: sideways-rl;"); // or vertical-rl
154+
isVertical = true;
159155
}
156+
if (textDirection == TextDirectionValues.BottomToTopLeftToRight ||
157+
textDirection == TextDirectionValues.BottomToTopLeftToRight2010)
158+
{
159+
// Vertical text (rotated letters), bottom to top, left to right
160+
styles.Add("writing-mode: sideways-lr;");
161+
isVertical = true;
162+
}
160163
}
161164

162165
internal override void ProcessContentPart(ContentPart contentPart, HtmlTextWriter writer)

src/DocSharp.Docx/DocxToRtf/DocxToRtfConverter.Table.cs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -646,34 +646,38 @@ internal void ProcessTableCellProperties(TableCell cell, RtfStringWriter sb, ref
646646
if (direction.Val == TextDirectionValues.LefToRightTopToBottom ||
647647
direction.Val == TextDirectionValues.LeftToRightTopToBottom2010)
648648
{
649-
// Horizontal text, left to right (default)
649+
// Horizontal text, left to right, top to bottom (default)
650650
sb.Write(@"\cltxlrtb");
651651
}
652+
if (direction.Val == TextDirectionValues.LefttoRightTopToBottomRotated ||
653+
direction.Val == TextDirectionValues.LeftToRightTopToBottomRotated2010)
654+
{
655+
// Vertical text, left to right, top to bottom (seems the same as the default, maybe depends on the font or context)
656+
sb.Write(@"\cltxlrtbv");
657+
}
652658
if (direction.Val == TextDirectionValues.TopToBottomRightToLeft ||
653659
direction.Val == TextDirectionValues.TopToBottomRightToLeft2010)
654660
{
655-
// Horizontal text, right to left
661+
// Vertical text, top to bottom, right to left
656662
sb.Write(@"\cltxtbrl");
657663
}
664+
if (direction.Val == TextDirectionValues.TopToBottomRightToLeftRotated ||
665+
direction.Val == TextDirectionValues.TopToBottomRightToLeftRotated2010)
666+
{
667+
// Vertical text, bottom to top, right to left (seems the same as the default, maybe depends on the font or context)
668+
sb.Write(@"\cltxtbrlv");
669+
}
658670
if (direction.Val == TextDirectionValues.BottomToTopLeftToRight ||
659671
direction.Val == TextDirectionValues.BottomToTopLeftToRight2010)
660672
{
661-
// Horizontal text, bottom to top
673+
// Vertical text, bottom to top, left to right
662674
sb.Write(@"\cltxbtlr");
663675
}
664-
if (direction.Val == TextDirectionValues.LefttoRightTopToBottomRotated ||
665-
direction.Val == TextDirectionValues.LeftToRightTopToBottomRotated2010 ||
666-
direction.Val == TextDirectionValues.TopToBottomLeftToRightRotated ||
676+
if (direction.Val == TextDirectionValues.TopToBottomLeftToRightRotated ||
667677
direction.Val == TextDirectionValues.TopToBottomLeftToRightRotated2010)
668678
{
669-
// Vertical text
670-
sb.Write(@"\cltxlrtbv");
671-
}
672-
if (direction.Val == TextDirectionValues.TopToBottomRightToLeftRotated ||
673-
direction.Val == TextDirectionValues.TopToBottomRightToLeftRotated2010)
674-
{
675-
// Vertical text
676-
sb.Write(@"\cltxtbrlv");
679+
// Not supported in RTF, fallback to BottomToTopLeftToRight
680+
sb.Write(@"\cltxbtlr");
677681
}
678682
}
679683

13.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)