/* * out2window.c * Copyright (C) 1998-2005 A.J. van Os; Released under GPL * * Description: * Output to a text window */ #include #include #include #include "antiword.h" /* Used for numbering the chapters */ static unsigned int auiHdrCounter[9]; /* * vString2Diagram - put a string into a diagram */ static void vString2Diagram(diagram_type *pDiag, output_type *pAnchor) { output_type *pOutput; long lWidth; USHORT usMaxFontSize; TRACE_MSG("vString2Diagram"); fail(pDiag == NULL); fail(pAnchor == NULL); /* Compute the maximum fontsize in this string */ usMaxFontSize = MIN_FONT_SIZE; for (pOutput = pAnchor; pOutput != NULL; pOutput = pOutput->pNext) { if (pOutput->usFontSize > usMaxFontSize) { usMaxFontSize = pOutput->usFontSize; } } /* Goto the next line */ vMove2NextLine(pDiag, pAnchor->tFontRef, usMaxFontSize); /* Output all substrings */ for (pOutput = pAnchor; pOutput != NULL; pOutput = pOutput->pNext) { lWidth = lMilliPoints2DrawUnits(pOutput->lStringWidth); vSubstring2Diagram(pDiag, pOutput->szStorage, pOutput->tNextFree, lWidth, pOutput->ucFontColor, pOutput->usFontStyle, pOutput->tFontRef, pOutput->usFontSize, usMaxFontSize); } /* Goto the start of the line */ pDiag->lXleft = 0; TRACE_MSG("leaving vString2Diagram"); } /* end of vString2Diagram */ /* * vSetLeftIndentation - set the left indentation of the specified diagram */ void vSetLeftIndentation(diagram_type *pDiag, long lLeftIndentation) { long lX; TRACE_MSG("vSetLeftIndentation"); fail(pDiag == NULL); fail(lLeftIndentation < 0); lX = lMilliPoints2DrawUnits(lLeftIndentation); if (lX > 0) { pDiag->lXleft = lX; } else { pDiag->lXleft = 0; } } /* end of vSetLeftIndentation */ /* * lComputeNetWidth - compute the net string width */ static long lComputeNetWidth(output_type *pAnchor) { output_type *pTmp; long lNetWidth; TRACE_MSG("lComputeNetWidth"); fail(pAnchor == NULL); /* Step 1: Count all but the last sub-string */ lNetWidth = 0; for (pTmp = pAnchor; pTmp->pNext != NULL; pTmp = pTmp->pNext) { fail(pTmp->lStringWidth < 0); lNetWidth += pTmp->lStringWidth; } fail(pTmp == NULL); fail(pTmp->pNext != NULL); /* Step 2: remove the white-space from the end of the string */ while (pTmp->tNextFree != 0 && isspace((int)(UCHAR)pTmp->szStorage[pTmp->tNextFree - 1])) { pTmp->szStorage[pTmp->tNextFree - 1] = '\0'; pTmp->tNextFree--; NO_DBG_DEC(pTmp->lStringWidth); pTmp->lStringWidth = lComputeStringWidth( pTmp->szStorage, pTmp->tNextFree, pTmp->tFontRef, pTmp->usFontSize); NO_DBG_DEC(pTmp->lStringWidth); } /* Step 3: Count the last sub-string */ lNetWidth += pTmp->lStringWidth; return lNetWidth; } /* end of lComputeNetWidth */ /* * iComputeHoles - compute number of holes * (A hole is a number of whitespace characters followed by a * non-whitespace character) */ static int iComputeHoles(output_type *pAnchor) { output_type *pTmp; size_t tIndex; int iCounter; BOOL bWasSpace, bIsSpace; TRACE_MSG("iComputeHoles"); fail(pAnchor == NULL); iCounter = 0; bIsSpace = FALSE; /* Count the holes */ for (pTmp = pAnchor; pTmp != NULL; pTmp = pTmp->pNext) { fail(pTmp->tNextFree != strlen(pTmp->szStorage)); for (tIndex = 0; tIndex <= pTmp->tNextFree; tIndex++) { bWasSpace = bIsSpace; bIsSpace = isspace((int)(UCHAR)pTmp->szStorage[tIndex]); if (bWasSpace && !bIsSpace) { iCounter++; } } } return iCounter; } /* end of iComputeHoles */ /* * vAlign2Window - Align a string and insert it into the text */ void vAlign2Window(diagram_type *pDiag, output_type *pAnchor, long lScreenWidth, UCHAR ucAlignment) { long lNetWidth, lLeftIndentation; TRACE_MSG("vAlign2Window"); fail(pDiag == NULL || pAnchor == NULL); fail(lScreenWidth < lChar2MilliPoints(MIN_SCREEN_WIDTH)); lNetWidth = lComputeNetWidth(pAnchor); if (lScreenWidth > lChar2MilliPoints(MAX_SCREEN_WIDTH) || lNetWidth <= 0) { /* * Screenwidth is "infinite", so no alignment is possible * Don't bother to align an empty line */ vString2Diagram(pDiag, pAnchor); TRACE_MSG("leaving vAlign2Window #1"); return; } switch (ucAlignment) { case ALIGNMENT_CENTER: lLeftIndentation = (lScreenWidth - lNetWidth) / 2; DBG_DEC_C(lLeftIndentation < 0, lLeftIndentation); if (lLeftIndentation > 0) { vSetLeftIndentation(pDiag, lLeftIndentation); } break; case ALIGNMENT_RIGHT: lLeftIndentation = lScreenWidth - lNetWidth; DBG_DEC_C(lLeftIndentation < 0, lLeftIndentation); if (lLeftIndentation > 0) { vSetLeftIndentation(pDiag, lLeftIndentation); } break; case ALIGNMENT_JUSTIFY: case ALIGNMENT_LEFT: default: break; } vString2Diagram(pDiag, pAnchor); TRACE_MSG("leaving vAlign2Window #2"); } /* end of vAlign2Window */ /* * vJustify2Window - Justify a string and insert it into the text */ void vJustify2Window(diagram_type *pDiag, output_type *pAnchor, long lScreenWidth, long lRightIndentation, UCHAR ucAlignment) { output_type *pTmp; char *pcNew, *pcOld, *szStorage; long lNetWidth, lSpaceWidth, lToAdd; int iFillerLen, iHoles; TRACE_MSG("vJustify2Window"); fail(pDiag == NULL || pAnchor == NULL); fail(lScreenWidth < MIN_SCREEN_WIDTH); fail(lRightIndentation > 0); if (ucAlignment != ALIGNMENT_JUSTIFY) { vAlign2Window(pDiag, pAnchor, lScreenWidth, ucAlignment); return; } lNetWidth = lComputeNetWidth(pAnchor); if (lScreenWidth > lChar2MilliPoints(MAX_SCREEN_WIDTH) || lNetWidth <= 0) { /* * Screenwidth is "infinite", so justify is not possible * Don't bother to justify an empty line */ vString2Diagram(pDiag, pAnchor); TRACE_MSG("leaving vJustify2Window #1"); return; } /* Justify */ fail(ucAlignment != ALIGNMENT_JUSTIFY); lSpaceWidth = lComputeStringWidth(" ", 1, pAnchor->tFontRef, pAnchor->usFontSize); lToAdd = lScreenWidth - lNetWidth - lDrawUnits2MilliPoints(pDiag->lXleft) + lRightIndentation; #if defined(DEBUG) if (lToAdd / lSpaceWidth < -1) { DBG_DEC(lSpaceWidth); DBG_DEC(lToAdd); DBG_DEC(lScreenWidth); DBG_DEC(lNetWidth); DBG_DEC(lDrawUnits2MilliPoints(pDiag->lXleft)); DBG_DEC(pDiag->lXleft); DBG_DEC(lRightIndentation); } #endif /* DEBUG */ lToAdd /= lSpaceWidth; DBG_DEC_C(lToAdd < 0, lToAdd); if (lToAdd <= 0) { vString2Diagram(pDiag, pAnchor); TRACE_MSG("leaving vJustify2Window #2"); return; } /* Justify by adding spaces */ iHoles = iComputeHoles(pAnchor); for (pTmp = pAnchor; pTmp != NULL; pTmp = pTmp->pNext) { fail(pTmp->tNextFree != strlen(pTmp->szStorage)); fail(lToAdd < 0); szStorage = xmalloc(pTmp->tNextFree + (size_t)lToAdd + 1); pcNew = szStorage; for (pcOld = pTmp->szStorage; *pcOld != '\0'; pcOld++) { *pcNew++ = *pcOld; if (*pcOld == ' ' && *(pcOld + 1) != ' ' && iHoles > 0) { iFillerLen = (int)(lToAdd / iHoles); lToAdd -= iFillerLen; iHoles--; for (; iFillerLen > 0; iFillerLen--) { *pcNew++ = ' '; } } } *pcNew = '\0'; pTmp->szStorage = xfree(pTmp->szStorage); pTmp->szStorage = szStorage; pTmp->tStorageSize = pTmp->tNextFree + (size_t)lToAdd + 1; pTmp->lStringWidth += (pcNew - szStorage - (long)pTmp->tNextFree) * lSpaceWidth; fail(pcNew < szStorage); pTmp->tNextFree = (size_t)(pcNew - szStorage); fail(pTmp->tNextFree != strlen(pTmp->szStorage)); } DBG_DEC_C(lToAdd != 0, lToAdd); vString2Diagram(pDiag, pAnchor); TRACE_MSG("leaving vJustify2Window #3"); } /* end of vJustify2Window */ /* * vResetStyles - reset the style information variables */ void vResetStyles(void) { TRACE_MSG("vResetStyles"); (void)memset(auiHdrCounter, 0, sizeof(auiHdrCounter)); } /* end of vResetStyles */ /* * tStyle2Window - Add the style characters to the line * * Returns the length of the resulting string */ size_t tStyle2Window(char *szLine, size_t tLineSize, const style_block_type *pStyle, const section_block_type *pSection) { char *pcTxt; size_t tIndex, tStyleIndex; BOOL bNeedPrevLvl; level_type_enum eNumType; UCHAR ucNFC; TRACE_MSG("tStyle2Window"); fail(szLine == NULL || pStyle == NULL || pSection == NULL); if (pStyle->usIstd == 0 || pStyle->usIstd > 9) { szLine[0] = '\0'; return 0; } /* Set the numbers */ tStyleIndex = (size_t)pStyle->usIstd - 1; for (tIndex = 0; tIndex < 9; tIndex++) { if (tIndex == tStyleIndex) { auiHdrCounter[tIndex]++; } else if (tIndex > tStyleIndex) { auiHdrCounter[tIndex] = 0; } else if (auiHdrCounter[tIndex] == 0) { auiHdrCounter[tIndex] = 1; } } eNumType = eGetNumType(pStyle->ucNumLevel); if (eNumType != level_type_outline) { szLine[0] = '\0'; return 0; } /* Print the numbers */ pcTxt = szLine; bNeedPrevLvl = (pSection->usNeedPrevLvl & BIT(tStyleIndex)) != 0; for (tIndex = 0; tIndex <= tStyleIndex; tIndex++) { if (tIndex == tStyleIndex || (bNeedPrevLvl && tIndex < tStyleIndex)) { if (pcTxt - szLine >= tLineSize - 25) { /* Prevent a possible buffer overflow */ DBG_DEC(pcTxt - szLine); DBG_DEC(tLineSize - 25); DBG_FIXME(); szLine[0] = '\0'; return 0; } ucNFC = pSection->aucNFC[tIndex]; switch(ucNFC) { case LIST_ARABIC_NUM: case LIST_NUMBER_TXT: case LIST_ORDINAL_TXT: pcTxt += sprintf(pcTxt, "%u", auiHdrCounter[tIndex]); break; case LIST_UPPER_ROMAN: case LIST_LOWER_ROMAN: pcTxt += tNumber2Roman( auiHdrCounter[tIndex], ucNFC == LIST_UPPER_ROMAN, pcTxt); break; case LIST_UPPER_ALPHA: case LIST_LOWER_ALPHA: pcTxt += tNumber2Alpha( auiHdrCounter[tIndex], ucNFC == LIST_UPPER_ALPHA, pcTxt); break; case LIST_OUTLINE_NUM: pcTxt += sprintf(pcTxt, "%02u", auiHdrCounter[tIndex]); break; default: DBG_DEC(ucNFC); DBG_FIXME(); pcTxt += sprintf(pcTxt, "%u", auiHdrCounter[tIndex]); break; } if (tIndex < tStyleIndex) { *pcTxt++ = '.'; } else if (tIndex == tStyleIndex) { *pcTxt++ = ' '; } } } *pcTxt = '\0'; NO_DBG_MSG_C((int)pStyle->usIstd >= 1 && (int)pStyle->usIstd <= 9 && eNumType != level_type_none && eNumType != level_type_outline, szLine); NO_DBG_MSG_C(szLine[0] != '\0', szLine); fail(pcTxt < szLine); return (size_t)(pcTxt - szLine); } /* end of tStyle2Window */ /* * vRemoveRowEnd - remove the end of table row indicator * * Remove the double TABLE_SEPARATOR characters from the end of the string. * Special: remove the TABLE_SEPARATOR, 0x0a sequence */ static void vRemoveRowEnd(char *szRowTxt) { int iLastIndex; TRACE_MSG("vRemoveRowEnd"); fail(szRowTxt == NULL || szRowTxt[0] == '\0'); iLastIndex = (int)strlen(szRowTxt) - 1; if (szRowTxt[iLastIndex] == TABLE_SEPARATOR || szRowTxt[iLastIndex] == (char)0x0a) { szRowTxt[iLastIndex] = '\0'; iLastIndex--; } else { DBG_HEX(szRowTxt[iLastIndex]); } if (iLastIndex >= 0 && szRowTxt[iLastIndex] == (char)0x0a) { szRowTxt[iLastIndex] = '\0'; iLastIndex--; } if (iLastIndex >= 0 && szRowTxt[iLastIndex] == TABLE_SEPARATOR) { szRowTxt[iLastIndex] = '\0'; return; } DBG_DEC(iLastIndex); DBG_HEX(szRowTxt[iLastIndex]); DBG_MSG(szRowTxt); } /* end of vRemoveRowEnd */ /* * tComputeStringLengthMax - max string length in relation to max column width * * Return the maximum string length */ static size_t tComputeStringLengthMax(const char *szString, size_t tColumnWidthMax) { const char *pcTmp; size_t tLengthMax, tLenPrev, tLen, tWidth; TRACE_MSG("tComputeStringLengthMax"); fail(szString == NULL); fail(tColumnWidthMax == 0); pcTmp = strchr(szString, '\n'); if (pcTmp != NULL) { tLengthMax = (size_t)(pcTmp - szString + 1); } else { tLengthMax = strlen(szString); } if (tLengthMax == 0) { return 0; } tLen = 0; tWidth = 0; for (;;) { tLenPrev = tLen; tLen += tGetCharacterLength(szString + tLen); DBG_DEC_C(tLen > tLengthMax, tLen); DBG_DEC_C(tLen > tLengthMax, tLengthMax); fail(tLen > tLengthMax); tWidth = tCountColumns(szString, tLen); if (tWidth > tColumnWidthMax) { return tLenPrev; } if (tLen >= tLengthMax) { return tLengthMax; } } } /* end of tComputeStringLengthMax */ /* * tGetBreakingPoint - get the number of bytes that fit the column * * Returns the number of bytes that fit the column */ static size_t tGetBreakingPoint(const char *szString, size_t tLen, size_t tWidth, size_t tColumnWidthMax) { int iIndex; TRACE_MSG("tGetBreakingPoint"); fail(szString == NULL); fail(tLen > strlen(szString)); fail(tWidth > tColumnWidthMax); if (tWidth < tColumnWidthMax || (tWidth == tColumnWidthMax && (szString[tLen] == ' ' || szString[tLen] == '\n' || szString[tLen] == '\0'))) { /* The string already fits, do nothing */ return tLen; } /* Search for a breaking point */ for (iIndex = (int)tLen - 1; iIndex >= 0; iIndex--) { if (szString[iIndex] == ' ') { return (size_t)iIndex; } } /* No breaking point found, just fill the column */ return tLen; } /* end of tGetBreakingPoint */ /* * tComputeColumnWidthMax - compute the maximum column width */ static size_t tComputeColumnWidthMax(short sWidth, long lCharWidth, double dFactor) { size_t tColumnWidthMax; TRACE_MSG("tComputeColumnWidthMax"); fail(sWidth < 0); fail(lCharWidth <= 0); fail(dFactor <= 0.0); tColumnWidthMax = (size_t)( (lTwips2MilliPoints(sWidth) * dFactor + lCharWidth / 2.0) / lCharWidth); if (tColumnWidthMax == 0) { /* Minimum column width */ return 1; } if (tColumnWidthMax > 1) { /* Make room for the TABLE_SEPARATOR_CHAR */ tColumnWidthMax--; } NO_DBG_DEC(tColumnWidthMax); return tColumnWidthMax; } /* end of tComputeColumnWidthMax */ /* * vTableRow2Window - put a table row into a diagram */ void vTableRow2Window(diagram_type *pDiag, output_type *pOutput, const row_block_type *pRowInfo, conversion_type eConversionType, int iParagraphBreak) { output_type tRow; char *aszColTxt[TABLE_COLUMN_MAX]; char *szLine, *pcTxt; double dMagnify; long lCharWidthLarge, lCharWidthSmall; size_t tColumnWidthTotal, atColumnWidthMax[TABLE_COLUMN_MAX]; size_t tSize, tColumnWidthMax, tWidth, tLen; int iIndex, iNbrOfColumns, iTmp; BOOL bNotReady; TRACE_MSG("vTableRow2Window"); fail(pDiag == NULL || pOutput == NULL || pRowInfo == NULL); fail(pOutput->szStorage == NULL); fail(pOutput->pNext != NULL); fail(iParagraphBreak < 0); /* Character sizes */ lCharWidthLarge = lComputeStringWidth("W", 1, pOutput->tFontRef, pOutput->usFontSize); NO_DBG_DEC(lCharWidthLarge); lCharWidthSmall = lComputeStringWidth("i", 1, pOutput->tFontRef, pOutput->usFontSize); NO_DBG_DEC(lCharWidthSmall); /* For the time being: use a fixed width font */ fail(lCharWidthLarge != lCharWidthSmall); vRemoveRowEnd(pOutput->szStorage); /* Split the row text into a set of column texts */ aszColTxt[0] = pOutput->szStorage; for (iNbrOfColumns = 1; iNbrOfColumns < TABLE_COLUMN_MAX; iNbrOfColumns++) { aszColTxt[iNbrOfColumns] = strchr(aszColTxt[iNbrOfColumns - 1], TABLE_SEPARATOR); if (aszColTxt[iNbrOfColumns] == NULL) { break; } *aszColTxt[iNbrOfColumns] = '\0'; aszColTxt[iNbrOfColumns]++; NO_DBG_DEC(iNbrOfColumns); NO_DBG_MSG(aszColTxt[iNbrOfColumns]); } /* Work around a bug in Word */ while (iNbrOfColumns > (int)pRowInfo->ucNumberOfColumns && pRowInfo->asColumnWidth[iNbrOfColumns] == 0) { iNbrOfColumns--; } DBG_DEC_C(iNbrOfColumns != (int)pRowInfo->ucNumberOfColumns, iNbrOfColumns); DBG_DEC_C(iNbrOfColumns != (int)pRowInfo->ucNumberOfColumns, pRowInfo->ucNumberOfColumns); if (iNbrOfColumns != (int)pRowInfo->ucNumberOfColumns) { werr(0, "Skipping an unmatched table row"); return; } #if defined(__FULL_TEXT_SEARCH) /* No table formatting: use for full-text search (untested) */ for (iIndex = 0; iIndex < iNbrOfColumns; iIndex++) { fprintf(pDiag->pOutFile, "%s\n" , aszColTxt[iIndex]); } #else if (bAddTableRow(pDiag, aszColTxt, iNbrOfColumns, pRowInfo->asColumnWidth, pRowInfo->ucBorderInfo)) { /* All work has been done */ return; } /* Fill the table with maximum column widths */ if (eConversionType == conversion_text || eConversionType == conversion_fmt_text) { if (iParagraphBreak == 0 || iParagraphBreak >= MAX_SCREEN_WIDTH) { dMagnify = (double)MAX_SCREEN_WIDTH; } else if (iParagraphBreak <= MIN_SCREEN_WIDTH) { dMagnify = (double)MIN_SCREEN_WIDTH; } else { dMagnify = (double)iParagraphBreak; } dMagnify /= (double)DEFAULT_SCREEN_WIDTH; DBG_FLT_C(dMagnify < 0.99 || dMagnify > 1.01, dMagnify); } else { dMagnify = 1.0; } tColumnWidthTotal = 0; for (iIndex = 0; iIndex < iNbrOfColumns; iIndex++) { atColumnWidthMax[iIndex] = tComputeColumnWidthMax( pRowInfo->asColumnWidth[iIndex], lCharWidthLarge, dMagnify); tColumnWidthTotal += atColumnWidthMax[iIndex]; } /* * Get enough space for the row. * Worst case: three bytes per UTF-8 character */ tSize = 3 * (1 + tColumnWidthTotal + (size_t)iNbrOfColumns + 3); szLine = xmalloc(tSize); do { /* Print one line of a table row */ bNotReady = FALSE; pcTxt = szLine; *pcTxt++ = TABLE_SEPARATOR_CHAR; for (iIndex = 0; iIndex < iNbrOfColumns; iIndex++) { tColumnWidthMax = atColumnWidthMax[iIndex]; if (aszColTxt[iIndex] == NULL) { /* Add an empty column */ for (iTmp = 0; iTmp < (int)tColumnWidthMax; iTmp++) { *pcTxt++ = (char)FILLER_CHAR; } *pcTxt++ = TABLE_SEPARATOR_CHAR; *pcTxt = '\0'; continue; } /* Compute the length and width of the column text */ tLen = tComputeStringLengthMax( aszColTxt[iIndex], tColumnWidthMax); NO_DBG_DEC(tLen); while (tLen != 0 && (aszColTxt[iIndex][tLen - 1] == '\n' || aszColTxt[iIndex][tLen - 1] == ' ')) { aszColTxt[iIndex][tLen - 1] = ' '; tLen--; } tWidth = tCountColumns(aszColTxt[iIndex], tLen); fail(tWidth > tColumnWidthMax); tLen = tGetBreakingPoint(aszColTxt[iIndex], tLen, tWidth, tColumnWidthMax); tWidth = tCountColumns(aszColTxt[iIndex], tLen); if (tLen == 0 && *aszColTxt[iIndex] == '\0') { /* No text at all */ aszColTxt[iIndex] = NULL; } else { /* Add the text */ pcTxt += sprintf(pcTxt, "%.*s", (int)tLen, aszColTxt[iIndex]); if (tLen == 0 && *aszColTxt[iIndex] != ' ') { tLen = tGetCharacterLength( aszColTxt[iIndex]); DBG_CHR(*aszColTxt[iIndex]); DBG_FIXME(); fail(tLen == 0); } aszColTxt[iIndex] += tLen; while (*aszColTxt[iIndex] == ' ') { aszColTxt[iIndex]++; } if (*aszColTxt[iIndex] == '\0') { /* This row is now complete */ aszColTxt[iIndex] = NULL; } else { /* This row needs more lines */ bNotReady = TRUE; } } /* Fill up the rest */ for (iTmp = 0; iTmp < (int)tColumnWidthMax - (int)tWidth; iTmp++) { *pcTxt++ = (char)FILLER_CHAR; } /* End of column */ *pcTxt++ = TABLE_SEPARATOR_CHAR; *pcTxt = '\0'; } /* Output the table row line */ *pcTxt = '\0'; tRow = *pOutput; tRow.szStorage = szLine; fail(pcTxt < szLine); tRow.tNextFree = (size_t)(pcTxt - szLine); tRow.lStringWidth = lComputeStringWidth( tRow.szStorage, tRow.tNextFree, tRow.tFontRef, tRow.usFontSize); vString2Diagram(pDiag, &tRow); TRACE_MSG("after vString2Diagram in vTableRow2Window"); } while (bNotReady); /* Clean up before you leave */ szLine = xfree(szLine); TRACE_MSG("leaving vTableRow2Window"); #endif /* __FULL_TEXT_SEARCH */ } /* end of vTableRow2Window */