| 123 | } |
| 124 | |
| 125 | function mergeParagraphProps(target: MergedParagraphStyle, pPr: SafeXmlNode): void { |
| 126 | if (!pPr.exists()) return |
| 127 | |
| 128 | const algn = pPr.attr('algn') |
| 129 | if (algn) target.align = algn |
| 130 | |
| 131 | const marL = pPr.numAttr('marL') |
| 132 | if (marL !== undefined) target.marginLeft = emuToPx(marL) |
| 133 | |
| 134 | const indent = pPr.numAttr('indent') |
| 135 | if (indent !== undefined) target.textIndent = emuToPx(indent) |
| 136 | |
| 137 | // Line spacing |
| 138 | // OOXML spcPct: 100000 = "single spacing" = 1.0× the font's line height. |
| 139 | // IMPORTANT: We must use UNITLESS CSS line-height values (e.g., 1.0, 1.2) |
| 140 | // instead of percentages (e.g., 100%, 120%). CSS percentage line-height is |
| 141 | // computed once against the element's own font-size and inherited as a FIXED |
| 142 | // pixel value — so a parent div with line-height:120% and font-size:16px |
| 143 | // inherits 19.2px to ALL children, even those with font-size:80pt. |
| 144 | // Unitless values are inherited as-is and each child recomputes against its |
| 145 | // own font-size. |
| 146 | const lnSpc = pPr.child('lnSpc') |
| 147 | if (lnSpc.exists()) { |
| 148 | const spcPct = lnSpc.child('spcPct') |
| 149 | if (spcPct.exists()) { |
| 150 | const val = spcPct.numAttr('val') |
| 151 | if (val !== undefined) { |
| 152 | // OOXML 100000 → CSS unitless 1.0; OOXML 120000 → CSS 1.2 |
| 153 | target.lineHeight = `${(val / 100000).toFixed(3)}` |
| 154 | } |
| 155 | } |
| 156 | const spcPts = lnSpc.child('spcPts') |
| 157 | if (spcPts.exists()) { |
| 158 | const val = spcPts.numAttr('val') |
| 159 | if (val !== undefined) { |
| 160 | target.lineHeight = `${val / 100}pt` |
| 161 | target.lineHeightAbsolute = true |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | // Space before |
| 167 | const spcBef = pPr.child('spcBef') |
| 168 | if (spcBef.exists()) { |
| 169 | const spcPts = spcBef.child('spcPts') |
| 170 | if (spcPts.exists()) { |
| 171 | const val = spcPts.numAttr('val') |
| 172 | if (val !== undefined) target.spaceBefore = val / 100 |
| 173 | } |
| 174 | const spcPct = spcBef.child('spcPct') |
| 175 | if (spcPct.exists()) { |
| 176 | const val = spcPct.numAttr('val') |
| 177 | if (val !== undefined) target.spaceBeforePct = val / 100000 // store as ratio |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | // Space after |
| 182 | const spcAft = pPr.child('spcAft') |