blob: 6f69130b8e24c54082dcbf592df62e4f0ecdb563 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include "DFPlatform.h"
#include "WordLenses.h"
#include "WordStyles.h"
#include "WordSheet.h"
#include "Word.h"
#include "DFTable.h"
#include "DFHTMLTables.h"
#include "CSS.h"
#include "CSSProperties.h"
#include "CSSLength.h"
#include "WordSection.h"
#include "DFString.h"
#include "DFCommon.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
CSSProperties *tableProperties;
CSSProperties *cellProperties;
WordStyle *style;
DFTable *structure;
double totalWidthPts;
} ConcreteInfo;
static ConcreteInfo *ConcreteInfoNew(void)
{
ConcreteInfo *info = (ConcreteInfo *)xcalloc(1,sizeof(ConcreteInfo));
info->tableProperties = CSSPropertiesNew();
info->cellProperties = CSSPropertiesNew();
return info;
}
static void ConcreteInfoFree(ConcreteInfo *info)
{
DFTableRelease(info->structure);
CSSPropertiesRelease(info->tableProperties);
CSSPropertiesRelease(info->cellProperties);
free(info);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// WordTcLens //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static DFNode *WordTcCreateAbstractNode(WordGetData *get, DFNode *concrete)
{
DFNode *td = WordConverterCreateAbstract(get,HTML_TD,concrete);
CSSProperties *properties = CSSPropertiesNew();
DFNode *tcPr = DFChildWithTag(concrete,WORD_TCPR);
if (tcPr != NULL)
WordGetTcPr(tcPr,properties);;
DFHashTable *collapsed = CSSCollapseProperties(properties);
char *styleValue = CSSSerializeProperties(collapsed);
DFHashTableRelease(collapsed);
if (strlen(styleValue) > 0)
DFSetAttribute(td,HTML_STYLE,styleValue);
free(styleValue);
CSSPropertiesRelease(properties);
return td;
}
static DFNode *WordTcGet(WordGetData *get, DFNode *concrete)
{
DFNode *abstract = WordTcCreateAbstractNode(get,concrete);
WordContainerGet(get,&WordBlockLevelLens,abstract,concrete);
return abstract;
}
static void WordTcPut(WordPutData *put, DFNode *abstract, DFNode *concrete)
{
WordContainerPut(put,&WordBlockLevelLens,abstract,concrete);
}
static void WordTcRemove(WordPutData *put, DFNode *concrete)
{
for (DFNode *child = concrete->first; child != NULL; child = child->next)
WordBlockLevelLens.remove(put,child);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// TableStructure //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static const char *Word_getChildVal(DFNode *parent, Tag childName)
{
DFNode *child = DFChildWithTag(parent,childName);
if (child == NULL)
return NULL;
else
return DFGetAttribute(child,WORD_VAL);
}
static const char *Word_getPropertyVal(DFNode *parent, Tag propertiesName, Tag childName)
{
DFNode *properties = DFChildWithTag(parent,propertiesName);
if (properties == NULL)
return NULL;
else
return Word_getChildVal(properties,childName);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// WordTblLens //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static void populateTableStructure(DFTable *structure, DFNode *concrete)
{
// Populate table
int row = 0;
for (DFNode *tblChild = concrete->first; tblChild != NULL; tblChild = tblChild->next) {
if (tblChild->tag != WORD_TR)
continue;
DFTableSetRowElement(structure,tblChild,row);
int col = 0;
for (DFNode *trChild = tblChild->first; trChild != NULL; trChild = trChild->next) {
if (trChild->tag != WORD_TC)
continue;
const char *gridSpan = NULL;
const char *vMerge = NULL;
DFNode *tcPr = DFChildWithTag(trChild,WORD_TCPR);
if (tcPr != NULL) {
DFNode *gridSpanNode = DFChildWithTag(tcPr,WORD_GRIDSPAN);
DFNode *vMergeNode = DFChildWithTag(tcPr,WORD_VMERGE);
if (gridSpanNode != NULL)
gridSpan = DFGetAttribute(gridSpanNode,WORD_VAL);
if (vMergeNode != NULL) {
vMerge = DFGetAttribute(vMergeNode,WORD_VAL);
if (vMerge == NULL)
vMerge = "continue";
}
}
int colSpan = (gridSpan != NULL) ? atoi(gridSpan) : 1;
DFCell *cell;
if ((vMerge != NULL) && !DFStringEquals(vMerge,"restart") && (row > 0)) {
cell = DFCellRetain(DFTableGetCell(structure,row-1,col));
if (cell->rowSpan < row + 1 - cell->row)
cell->rowSpan = row + 1 - cell->row;
}
else {
cell = DFCellNew(trChild,row,col);
cell->colSpan = colSpan;
}
for (int i = 0; i < colSpan; i++)
DFTableSetCell(structure,row,col+i,cell);
col += colSpan;
DFCellRelease(cell);
}
row++;
}
}
static DFTableDimensions tableDimensionsForConcrete(DFNode *concrete)
{
int rows = 0;
int cols = 0;
for (DFNode *tblChild = concrete->first; tblChild != NULL; tblChild = tblChild->next) {
if (tblChild->tag == WORD_TR) {
rows++;
int thisRowCols = 0;
for (DFNode *trChild = tblChild->first; trChild != NULL; trChild = trChild->next) {
if (trChild->tag == WORD_TC) {
const char *gridSpan = Word_getPropertyVal(trChild,WORD_TCPR,WORD_GRIDSPAN);
int colSpan = (gridSpan != NULL) ? atoi(gridSpan) : 1;
thisRowCols += colSpan;
}
}
if (cols < thisRowCols)
cols = thisRowCols;
}
}
return DFTableDimensionsMake(rows,cols);
}
static DFTable *tableStructureForConcrete(DFNode *concrete)
{
DFTableDimensions dim = tableDimensionsForConcrete(concrete);
DFTable *structure = DFTableNew(dim.rows,dim.cols);
populateTableStructure(structure,concrete);
// Compute column widths
unsigned int col = 0;
DFNode *tblGrid = DFChildWithTag(concrete,WORD_TBLGRID);
if (tblGrid != NULL) {
for (DFNode *child = tblGrid->first; child != NULL; child = child->next) {
if ((child->tag != WORD_GRIDCOL) || (col >= structure->cols))
continue;
const char *widthStr = DFGetAttribute(child,WORD_W);
if (widthStr == NULL)
continue;
// Column widths are measured in twips (twentieths of a point)
// FIXME: What if the width is specified as a percentage?
double width = atoi(widthStr)/20.0;
DFTableSetColWidth(structure,col,width);
col++;
}
}
DFTableFixZeroWidthCols(structure);
return structure;
}
// Finds the type of measurement used for cell widths in a table. If the table is empty, or
// the cells use different types of widths, this returns NULL.
static const char *cellWidthTypeForTable(DFNode *tbl)
{
const char *commonType = NULL;
for (DFNode *tr = tbl->first; tr != NULL; tr = tr->next) {
if (tr->tag != WORD_TR)
continue;
for (DFNode *tc = tr->first; tc != NULL; tc = tc->next) {
if (tc->tag != WORD_TC)
continue;
DFNode *tcPr = DFChildWithTag(tc,WORD_TCPR);
DFNode *tcW = DFChildWithTag(tcPr,WORD_TCW);
if (tcW != NULL) {
const char *type = DFGetAttribute(tcW,WORD_TYPE);
if (type == NULL)
type = "dxa";
if (commonType == NULL) // First cell we've encountered
commonType = type;
else if (!DFStringEquals(commonType,type))
return NULL;
}
}
}
return commonType;
}
typedef struct {
double leftPts;
double rightPts;
} CellPadding;
static CellPadding getPadding(CSSSheet *styleSheet, WordStyle *wordStyle, CSSProperties *cellProperties)
{
const char *paddingLeftStr = NULL;
const char *paddingRightStr = NULL;
CSSStyle *style;
if ((wordStyle != NULL) && (wordStyle->selector != NULL)) {
style = CSSSheetLookupSelector(styleSheet,wordStyle->selector,0,0);
}
else {
style = CSSSheetDefaultStyleForFamily(styleSheet,StyleFamilyTable);
}
if (style != NULL) {
paddingLeftStr = CSSGet(CSSStyleCell(style),"padding-left");
paddingRightStr = CSSGet(CSSStyleCell(style),"padding-right");
}
if (CSSGet(cellProperties,"padding-left") != NULL)
paddingLeftStr = CSSGet(cellProperties,"padding-left");
if (CSSGet(cellProperties,"padding-right") != NULL)
paddingRightStr = CSSGet(cellProperties,"padding-right");;
CellPadding padding;
padding.leftPts = 0;
padding.rightPts = 0;
CSSLength paddingLeftLength = CSSLengthFromString(paddingLeftStr);
if (CSSLengthIsValid(paddingLeftLength) && (paddingLeftLength.units == UnitsPt))
padding.leftPts = paddingLeftLength.value;;
CSSLength paddingRightLength = CSSLengthFromString(paddingRightStr);
if (CSSLengthIsValid(paddingRightLength) && (paddingRightLength.units == UnitsPt))
padding.rightPts = paddingRightLength.value;;
return padding;
}
static void calcTotals(WordGetData *get, ConcreteInfo *cinfo)
{
CellPadding padding = getPadding(get->conv->styleSheet,cinfo->style,cinfo->cellProperties);
cinfo->totalWidthPts = cinfo->structure->totalColWidths - padding.leftPts - padding.rightPts;
}
static ConcreteInfo *getConcreteInfo(WordConverter *converter, DFNode *concrete)
{
ConcreteInfo *cinfo = ConcreteInfoNew();
DFNode *tblPr = DFChildWithTag(concrete,WORD_TBLPR);
if (tblPr != NULL) {
const char *styleId = NULL;
WordGetTblPr(tblPr,cinfo->tableProperties,cinfo->cellProperties,converter->mainSection,&styleId);
cinfo->style = WordSheetStyleForTypeId(converter->styles,"table",styleId);
}
cinfo->structure = tableStructureForConcrete(concrete);
return cinfo;
}
static DFNode *WordTblGet(WordGetData *get, DFNode *concrete)
{
if (concrete->tag != WORD_TBL)
return NULL;;
DFNode *table = WordConverterCreateAbstract(get,HTML_TABLE,concrete);
ConcreteInfo *cinfo = getConcreteInfo(get->conv,concrete);
calcTotals(get,cinfo);
const char *cellWidthType = cellWidthTypeForTable(concrete);
int autoWidth = DFStringEquals(cellWidthType,"auto");
if ((CSSGet(cinfo->tableProperties,"width") == NULL) && autoWidth) {
CSSPut(cinfo->tableProperties,"width",NULL);
}
else {
// Determine column widths and table width
if (cinfo->totalWidthPts > 0) {
DFNode *colgroup = HTML_createColgroup(get->conv->html,cinfo->structure);
DFAppendChild(table,colgroup);
double tableWidthPct = 100.0;
if (WordSectionContentWidth(get->conv->mainSection) > 0) {
double contentWidthPts = WordSectionContentWidth(get->conv->mainSection)/20.0;
tableWidthPct = 100.0*cinfo->totalWidthPts/contentWidthPts;
if (CSSGet(cinfo->tableProperties,"width") == NULL) {
char buf[100];
CSSPut(cinfo->tableProperties,"width",DFFormatDoublePct(buf,100,tableWidthPct));
}
}
}
if (CSSGet(cinfo->tableProperties,"width") == NULL)
CSSPut(cinfo->tableProperties,"width","100%");
}
DFHashTable *collapsed = CSSCollapseProperties(cinfo->tableProperties);
char *styleValue = CSSSerializeProperties(collapsed);
DFHashTableRelease(collapsed);
if (strlen(styleValue) > 0)
DFSetAttribute(table,HTML_STYLE,styleValue);
free(styleValue);
if ((cinfo->style != NULL) && (cinfo->style->selector != NULL)) {
char *className = CSSSelectorCopyClassName(cinfo->style->selector);
DFSetAttribute(table,HTML_CLASS,className);
free(className);
}
else {
CSSStyle *defaultStyle = CSSSheetDefaultStyleForFamily(get->conv->styleSheet,StyleFamilyTable);
if (defaultStyle != NULL)
DFSetAttribute(table,HTML_CLASS,defaultStyle->className);
}
// Create rows and cells
int row = 0;
for (DFNode *tblChild = concrete->first; tblChild != NULL; tblChild = tblChild->next) {
if (tblChild->tag != WORD_TR)
continue;
DFNode *tr = WordConverterCreateAbstract(get,HTML_TR,tblChild);
DFAppendChild(table,tr);
unsigned int col = 0;
while (col < cinfo->structure->cols) {
DFCell *cell = DFTableGetCell(cinfo->structure,row,col);
if (cell == NULL) {
DFNode *td = DFCreateElement(get->conv->html,HTML_TD);
DFAppendChild(tr,td);
col++;
continue;
}
if (row == cell->row) {
DFNode *td = WordTcGet(get,cell->element);
DFAppendChild(tr,td);
if (cell->colSpan != 1)
DFFormatAttribute(td,HTML_COLSPAN,"%d",cell->colSpan);
if (cell->rowSpan != 1)
DFFormatAttribute(td,HTML_ROWSPAN,"%d",cell->rowSpan);
}
col += cell->colSpan;
}
row++;
}
ConcreteInfoFree(cinfo);
return table;
}
static DFNode *concreteRowForAbstractRow(WordPutData *put, DFNode *htmlTr)
{
DFNode *wordTr = WordConverterGetConcrete(put,htmlTr);
if (wordTr == NULL) {
wordTr = DFCreateElement(put->contentDoc,WORD_TR);
}
else {
// We're reusing an existing row element, but we first need to clear all the cells in
// it, because we'll be rebuilding the cell list
DFNode *next;
for (DFNode *child = wordTr->first; child != NULL; child = next) {
next = child->next;
if (child->tag == WORD_TC)
DFRemoveNode(child);
}
}
return wordTr;
}
static void updateTrJc(DFNode *wordTr, const char *oldJc, const char *newJc)
{
DFNode *tblPrEx = DFChildWithTag(wordTr,WORD_TBLPREX);
DFNode *trPr = DFChildWithTag(wordTr,WORD_TRPR);
if (tblPrEx != NULL)
DFRemoveNode(tblPrEx);
if (trPr != NULL)
DFRemoveNode(trPr);
if (trPr == NULL)
trPr = DFCreateElement(wordTr->doc,WORD_TRPR);
WordPutTrPr(trPr,oldJc,newJc);
if (trPr->first == NULL)
trPr = NULL;;
DFNode *first = wordTr->first;
if (tblPrEx != NULL)
DFInsertBefore(wordTr,tblPrEx,first);
if (trPr != NULL)
DFInsertBefore(wordTr,trPr,first);
}
static void WordTblPut(WordPutData *put, DFNode *abstract, DFNode *concrete)
{
if ((abstract->tag != HTML_TABLE) || (concrete->tag != WORD_TBL))
return;;
DFTable *abstractStructure = HTML_tableStructure(abstract);
const char *inlineCSSText = DFGetAttribute(abstract,HTML_STYLE);
CSSProperties *tableProperties = CSSPropertiesNewWithString(inlineCSSText);
CSSProperties *cellProperties = CSSPropertiesNew();
const char *className = DFGetAttribute(abstract,HTML_CLASS);
char *selector = CSSMakeSelector("table",className);
WordStyle *style = WordSheetStyleForSelector(put->conv->styles,selector);
CellPadding padding = getPadding(put->conv->styleSheet,style,cellProperties);
DFNode *tblPr = DFChildWithTag(concrete,WORD_TBLPR);
if (tblPr == NULL)
tblPr = DFCreateElement(concrete->doc,WORD_TBLPR);;
DFNode *tblGrid = DFChildWithTag(concrete,WORD_TBLGRID);
if (tblGrid == NULL)
tblGrid = DFCreateElement(concrete->doc,WORD_TBLGRID);
while (concrete->first != NULL)
DFRemoveNode(concrete->first);
const char *oldJc = DFGetChildAttribute(tblPr,WORD_JC,WORD_VAL);
WordPutTblPr(tblPr,tableProperties,NULL,put->conv->mainSection,style != NULL ? style->styleId : NULL);
const char *newJc = DFGetChildAttribute(tblPr,WORD_JC,WORD_VAL);
double tableWidthPct = 100;
if (CSSGet(tableProperties,"width") != NULL) {
CSSLength length = CSSLengthFromString(CSSGet(tableProperties,"width"));
if (CSSLengthIsValid(length) && (length.units == UnitsPct))
tableWidthPct = length.value;
}
double contentWidthPts = WordSectionContentWidthPts(put->conv->mainSection);
double totalWidthPts = (contentWidthPts+padding.leftPts+padding.rightPts)*(tableWidthPct/100.0);
while (tblGrid->first != NULL)
DFRemoveNode(tblGrid->first);
for (unsigned int i = 0; i < abstractStructure->cols; i++) {
DFNode *gridCol = DFCreateChildElement(tblGrid,WORD_GRIDCOL);
double colWidthPct = DFTablePctWidthForCol(abstractStructure,i);
double colWidthPts = totalWidthPts*colWidthPct/100.0;
int colWidthTwips = (int)round(colWidthPts*20);
DFFormatAttribute(gridCol,WORD_W,"%d",colWidthTwips);
}
DFAppendChild(concrete,tblPr);
DFAppendChild(concrete,tblGrid);
for (unsigned int row = 0; row < abstractStructure->rows; row++) {
DFNode *htmlTr = DFTableGetRowElement(abstractStructure,row);
DFNode *wordTr = concreteRowForAbstractRow(put,htmlTr);
updateTrJc(wordTr,oldJc,newJc);
DFAppendChild(concrete,wordTr);
unsigned int col = 0;
while (col < abstractStructure->cols) {
DFCell *cell = DFTableGetCell(abstractStructure,row,col);
assert(cell != NULL);
DFNode *tc = WordConverterGetConcrete(put,cell->element);
if ((tc == NULL) || (row != cell->row))
tc = DFCreateElement(concrete->doc,WORD_TC);
DFAppendChild(wordTr,tc);
if (cell->row == row)
WordTcPut(put,cell->element,tc);;
const char *vMerge = NULL;
if (cell->rowSpan > 1) {
if (row == cell->row)
vMerge = "restart";
else
vMerge = "continue";
}
DFNode *tcPr = DFChildWithTag(tc,WORD_TCPR);
if (tcPr == NULL)
tcPr = DFCreateElement(concrete->doc,WORD_TCPR);
// Make sure tcPr comes first
DFInsertBefore(tc,tcPr,tc->first);
WordPutTcPr2(tcPr,cell->colSpan,vMerge);
const char *inlineCSSText = DFGetAttribute(cell->element,HTML_STYLE);
CSSProperties *innerCellProperties = CSSPropertiesNewWithString(inlineCSSText);
if ((row == cell->row) && (totalWidthPts > 0)) {
double spannedWidthPct = 0;
for (unsigned int c = col; c < col + cell->colSpan; c++)
spannedWidthPct += DFTablePctWidthForCol(abstractStructure,c);
char buf[100];
CSSPut(innerCellProperties,"width",DFFormatDoublePct(buf,100,spannedWidthPct));
}
WordPutTcPr1(tcPr,innerCellProperties);
int haveBlockLevelElement = 0;
for (DFNode *tcChild = tc->first; tcChild != NULL; tcChild = tcChild->next) {
if (WordBlockLevelLens.isVisible(put,tcChild))
haveBlockLevelElement = 1;
}
// Every cell must contain at least one block-level element
if (!haveBlockLevelElement) {
DFNode *p = DFCreateElement(concrete->doc,WORD_P);
DFAppendChild(tc,p);
}
col += cell->colSpan;
CSSPropertiesRelease(innerCellProperties);
}
}
free(selector);
DFTableRelease(abstractStructure);
CSSPropertiesRelease(tableProperties);
CSSPropertiesRelease(cellProperties);
}
static int WordTblIsVisible(WordPutData *put, DFNode *concrete)
{
return (concrete->tag == WORD_TBL);
}
static void WordTblRemove(WordPutData *put, DFNode *concrete)
{
for (DFNode *tr = concrete->first; tr != NULL; tr = tr->next) {
if (tr->tag == WORD_TR) {
for (DFNode *tc = tr->first; tc != NULL; tc = tc->next) {
if (tc->tag == WORD_TC)
WordTcRemove(put,tc);
}
}
}
}
WordLens WordTableLens = {
.isVisible = WordTblIsVisible,
.get = WordTblGet,
.put = WordTblPut,
.create = NULL, // LENS FIXME
.remove = WordTblRemove,
};