blob: 77ef362d68d1fb355a054e0c3e5006af178470d7 [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 "WordDrawing.h"
#include "WordLenses.h"
#include "WordObjects.h"
#include "WordSection.h"
#include "CSSLength.h"
#include "CSSProperties.h"
#include "DFHTML.h"
#include "DFFilesystem.h"
#include "OPC.h"
#include "DFDOM.h"
#include "DFString.h"
#include "DFPlatform.h"
#include "DFCommon.h"
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static int internalPut(WordPutData *put, DFNode *abstract, DFNode *concrete, int isNew);
static void populateDrawingElement(WordConverter *converter, DFNode *root, double widthPts,
double heightPts, const char *drawingId, const char *rId);
typedef struct {
unsigned int widthPx;
unsigned int heightPx;
} PixelSize;
const static PixelSize PixelSizeZero = { 0, 0 };
typedef struct {
unsigned long long widthEmu;
unsigned long long heightEmu;
} EMUSize;
typedef struct {
double widthPts;
double heightPts;
} PointsSize;
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// ImageInfo //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct {
char *rId;
double widthPts;
double heightPts;
const char *progId; // for embedded objects in VML
} ImageInfo;
static ImageInfo *ImageInfoNew(const char *rId, double widthPts, double heightPts)
{
ImageInfo *info = (ImageInfo *)xcalloc(1,sizeof(ImageInfo));
info->rId = DFStrDup(rId);
info->widthPts = widthPts;
info->heightPts = heightPts;
return info;
}
static void ImageInfoFree(ImageInfo *info)
{
free(info->rId);
free(info);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// DrawingInfo //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static DFNode *DrawingInfoTop(DFNode *element)
{
assert(element->tag == WORD_DRAWING);
DFNode *top = DFChildWithTag(element,DML_WP_INLINE);
if (top == NULL)
top = DFChildWithTag(element,DML_WP_ANCHOR);
return top;
}
static DFNode *DrawingInfoBlip(DFNode *element)
{
DFNode *top = DrawingInfoTop(element);
DFNode *graphic = DFChildWithTag(top,DML_MAIN_GRAPHIC);
DFNode *graphicData = DFChildWithTag(graphic,DML_MAIN_GRAPHICDATA);
DFNode *pic = DFChildWithTag(graphicData,DML_PICTURE_PIC);
DFNode *blipFill = DFChildWithTag(pic,DML_PICTURE_BLIPFILL);
DFNode *blip = DFChildWithTag(blipFill,DML_MAIN_BLIP);
return blip;
}
static const char *DrawingInfoRId(DFNode *element)
{
DFNode *blip = DrawingInfoBlip(element);
return DFGetAttribute(blip,OREL_EMBED);
}
static EMUSize DrawingInfoSize(DFNode *element)
{
EMUSize result = { 0, 0 };
DFNode *top = DrawingInfoTop(element);
DFNode *extent = DFChildWithTag(top,DML_WP_EXTENT);
const char *cx = DFGetAttribute(extent,NULL_CX);
const char *cy = DFGetAttribute(extent,NULL_CY);
if ((cx != NULL) && (cy != NULL)) {
result.widthEmu = atoll(cx);
result.heightEmu = atoll(cy);
}
return result;
}
static const char *DrawingInfoDrawingId(DFNode *element)
{
DFNode *top = DrawingInfoTop(element);
DFNode *docPr = DFChildWithTag(top,DML_WP_DOCPR);
return DFGetAttribute(docPr,NULL_ID);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// WordDrawing //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
WordDrawing *WordDrawingNew(const char *drawingId)
{
WordDrawing *drawing = (WordDrawing *)xcalloc(1,sizeof(WordDrawing));
drawing->retainCount = 1;
drawing->drawingId = xstrdup(drawingId);
return drawing;
}
WordDrawing *WordDrawingRetain(WordDrawing *drawing)
{
if (drawing != NULL)
drawing->retainCount++;
return drawing;
}
void WordDrawingRelease(WordDrawing *drawing)
{
if ((drawing == NULL) || (--drawing->retainCount > 0))
return;
free(drawing->drawingId);
free(drawing);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// WordDrawingLens //
// //
////////////////////////////////////////////////////////////////////////////////////////////////////
static DFNode *createAbstractPlaceholder(WordGetData *get, const char *placeholderText, DFNode *concrete)
{
DFNode *span = WordConverterCreateAbstract(get,HTML_SPAN,concrete);
DFSetAttribute(span,HTML_CLASS,DFPlaceholderClass);
DFNode *text = DFCreateTextNode(get->conv->html,placeholderText);
DFAppendChild(span,text);
return span;
}
static DFNode *imageWithFilename(WordGetData *get, const char *filename, double widthPts, DFNode *concrete)
{
char *lastComponent = DFPathBaseName(filename);
char *relImagePath = DFAppendPathComponent("images",lastComponent);
DFBuffer *content = NULL;
DFError *error = NULL;
DFNode *imageNode = NULL;
content = DFBufferReadFromStorage(get->conv->package->opc->storage,filename,&error);
if (content == NULL) {
WordConverterWarning(get->conv,"Copy %s to %s: %s",filename,relImagePath,DFErrorMessage(&error));
DFErrorRelease(error);
goto end;
}
if (!DFBufferWriteToStorage(content,get->conv->abstractStorage,relImagePath,&error)) {
WordConverterWarning(get->conv,"Copy %s to %s: %s",filename,relImagePath,DFErrorMessage(&error));
DFErrorRelease(error);
goto end;
}
imageNode = WordConverterCreateAbstract(get,HTML_IMG,concrete);
DFFormatAttribute(imageNode,HTML_SRC,"images/%s",lastComponent);
double contentWidthPts = WordSectionContentWidthPts(get->conv->mainSection);
if (contentWidthPts > 0) {
double widthPct = widthPts/contentWidthPts*100.0;
CSSProperties *properties = CSSPropertiesNew();
char buf[100];
CSSPut(properties,"width",DFFormatDoublePct(buf,100,widthPct));
char *propertiesText = CSSPropertiesCopyDescription(properties);
DFSetAttribute(imageNode,HTML_STYLE,propertiesText);
free(propertiesText);
CSSPropertiesRelease(properties);
}
goto end;
end:
free(lastComponent);
free(relImagePath);
DFBufferRelease(content);
if (imageNode != NULL)
return imageNode;
else
return createAbstractPlaceholder(get,"[Error reading image]",concrete);
}
static ImageInfo *getImageInfoDrawing(DFNode *concrete)
{
EMUSize size = DrawingInfoSize(concrete);
if ((size.widthEmu <= 0) || (size.heightEmu <= 0) || (DrawingInfoRId(concrete) == NULL))
return NULL;
double widthPts = size.widthEmu/(double)EMUS_PER_POINT;
double heightPts = size.heightEmu/(double)EMUS_PER_POINT;
return ImageInfoNew(DrawingInfoRId(concrete),widthPts,heightPts);
}
static ImageInfo *getImageInfoObject(DFNode *concrete)
{
DFNode *shape = DFChildWithTag(concrete,VML_SHAPE);
DFNode *imageData = DFChildWithTag(shape,VML_IMAGEDATA);
const char *cssText = DFGetAttribute(shape,NULL_STYLE);
const char *rId = DFGetAttribute(imageData,OREL_ID);
if ((shape == NULL) || (imageData == NULL) || (cssText == NULL) || (rId == NULL))
return NULL;;
CSSProperties *imgProperties = CSSPropertiesNewWithString(cssText);
CSSLength width = CSSLengthFromString(CSSGet(imgProperties,"width"));
CSSLength height = CSSLengthFromString(CSSGet(imgProperties,"height"));
CSSPropertiesRelease(imgProperties);
if (!CSSLengthIsValid(width) || !CSSLengthIsAbsolute(width))
return NULL;
if (!CSSLengthIsValid(height) || !CSSLengthIsAbsolute(height))
return NULL;
double widthPts = convertBetweenUnits(width.value,width.units,UnitsPt);
double heightPts = convertBetweenUnits(height.value,height.units,UnitsPt);
ImageInfo *info = ImageInfoNew(rId,widthPts,heightPts);
DFNode *oleObject = DFChildWithTag(concrete,MSOFFICE_OLEOBJECT);
info->progId = DFGetAttribute(oleObject,NULL_PROGID);
return info;
}
static ImageInfo *getImageInfo(DFNode *concrete)
{
switch (concrete->tag) {
case WORD_DRAWING:
return getImageInfoDrawing(concrete);
case WORD_OBJECT:
case WORD_PICT:
return getImageInfoObject(concrete);
default:
return NULL;
}
}
DFNode *WordDrawingGet(WordGetData *get, DFNode *concrete)
{
ImageInfo *info = getImageInfo(concrete);
if (info == NULL)
return createAbstractPlaceholder(get,"[Unsupported object]",concrete);
int validFileType = 0;
const char *filename = WordPackageTargetForDocumentRel(get->conv->package,info->rId);
if (filename != NULL) {
char *origExtension = DFPathExtension(filename);
char *lowerExtension = DFLowerCase(origExtension);
if (DFHashTableLookup(get->conv->supportedContentTypes,lowerExtension) != NULL)
validFileType = 1;
free(origExtension);
free(lowerExtension);
}
DFNode *result = NULL;
if (!validFileType) {
if (info->progId != NULL) {
char *message = DFFormatString("[Unsupported object: %s]",info->progId);
DFNode *placeholder = createAbstractPlaceholder(get,message,concrete);
free(message);
result = placeholder;
}
else {
result = createAbstractPlaceholder(get,"[Unsupported object]",concrete);
}
}
if (result == NULL)
result = imageWithFilename(get,filename,info->widthPts,concrete);
ImageInfoFree(info);
return result;
}
int WordDrawingIsVisible(WordPutData *put, DFNode *concrete)
{
return 1;
}
static char *genImageFilename(DFStorage *storage, const char *mediaRelDir, const char *extension, DFError **error)
{
const char **paths = DFStorageList(storage,error);
if (paths == NULL)
return NULL;;
DFHashTable *existingPaths = DFHashTableNew((DFCopyFunction)xstrdup,free);
for (int i = 0; paths[i]; i++) {
const char *path = paths[i];
char *lowerPath = DFLowerCase(path);
char *noExtension = DFPathWithoutExtension(lowerPath);
DFHashTableAdd(existingPaths,noExtension,noExtension);
free(lowerPath);
free(noExtension);
}
int num = 1;
char *candidateName = NULL;
char *candidatePath = NULL;
do {
free(candidateName);
free(candidatePath);
candidateName = DFFormatString("image%d",num);
candidatePath = DFAppendPathComponent(mediaRelDir,candidateName);
num++;
} while (DFHashTableLookup(existingPaths,candidatePath) != NULL);
char *result = DFFormatString("%s.%s",candidateName,extension);
free(candidateName);
free(candidatePath);
DFHashTableRelease(existingPaths);
free(paths);
return result;
}
static OPCRelationship *addImageRelationship(WordConverter *converter, const char *unescapedSrc, DFError **error)
{
DFStorage *storage = converter->package->opc->storage;
const char *mediaDir = "word/media";
char *ext = DFPathExtension(unescapedSrc);
char *filename = genImageFilename(storage,mediaDir,ext,error);
free(ext);
if (filename == NULL)
return NULL;
char *destPath = DFAppendPathComponent(mediaDir,filename);
OPCRelationship *result = NULL;
DFBuffer *content = DFBufferReadFromStorage(converter->abstractStorage,unescapedSrc,error);
if ((content != NULL) && DFBufferWriteToStorage(content,storage,destPath,error)) {
OPCRelationshipSet *rels = converter->package->documentPart->relationships;
char *relPath = DFFormatString("/word/media/%s",filename);
result = OPCRelationshipSetAddType(rels,WORDREL_IMAGE,relPath,0);
free(relPath);
}
DFBufferRelease(content);
free(filename);
free(destPath);
return result;
}
static int getImageFile(WordConverter *converter, const char *unescapedSrc, PixelSize *size, DFError **error)
{
size->widthPx = 0;
size->heightPx = 0;
char *ext = DFPathExtension(unescapedSrc);
int ok = 0;
DFBuffer *imageData = DFBufferReadFromStorage(converter->abstractStorage,unescapedSrc,error);
if (imageData == NULL)
goto end;
char *errmsg = NULL;
ok = DFGetImageDimensions(imageData->data,imageData->len,ext,&size->widthPx,&size->heightPx,&errmsg);
if (!ok) {
DFErrorFormat(error,"%s",errmsg);
free(errmsg);
}
end:
DFBufferRelease(imageData);
free(ext);
return ok;
}
static PointsSize pointsSizeFromHTMLImg(WordConverter *converter, DFNode *img, PixelSize fileSize)
{
double aspectRatio;
if (fileSize.heightPx > 0.0)
aspectRatio = fileSize.widthPx/(double)fileSize.heightPx;
else
aspectRatio = 1.0;
CSSSize htmlSize = HTML_getImageDimensions(img);
double widthPts = WordSectionContentWidthPts(converter->mainSection);
if (CSSLengthIsValid(htmlSize.width)) {
if (CSSLengthIsPercentage(htmlSize.width))
widthPts = (htmlSize.width.value/100.0)*WordSectionContentWidthPts(converter->mainSection);
else if (CSSLengthIsAbsolute(htmlSize.width))
widthPts = convertBetweenUnits(htmlSize.width.value,htmlSize.width.units,UnitsPt);
}
double heightPts = widthPts/aspectRatio;
if ((widthPts <= 0) || (heightPts <= 0)) {
widthPts = WordSectionContentWidthPts(converter->mainSection);
heightPts = widthPts/aspectRatio;
}
PointsSize result;
result.widthPts = widthPts;
result.heightPts = heightPts;
return result;
}
static int checkContentType(WordConverter *converter, const char *htmlSrc)
{
char *extension = DFPathExtension(htmlSrc);
const char *contentType = DFHashTableLookup(converter->supportedContentTypes,extension);
if (contentType == NULL) {
WordConverterWarning(converter,"Unsupported image type: %s",extension);
free(extension);
return 0;
}
else {
OPCContentTypesSetDefault(converter->package->opc->contentTypes,extension,contentType);
free(extension);
return 1;
}
}
DFNode *WordDrawingCreate(WordPutData *put, DFNode *abstract)
{
DFNode *concrete = DFCreateElement(put->contentDoc,WORD_DRAWING);
if (internalPut(put,abstract,concrete,1))
return concrete;
else
return NULL;
}
static int internalPut2(WordPutData *put, DFNode *abstract, DFNode *concrete, int isNew, const char *htmlFilename);
static int internalPut(WordPutData *put, DFNode *abstract, DFNode *concrete, int isNew)
{
if (abstract->tag != HTML_IMG)
return 0;
switch (concrete->tag) {
case WORD_DRAWING:
case WORD_PICT:
case WORD_OBJECT:
break;
default:
return 0;
}
const char *htmlSrc = DFGetAttribute(abstract,HTML_SRC);
if ((htmlSrc == NULL) || !checkContentType(put->conv,htmlSrc))
return 0;
char *htmlFilename = DFRemovePercentEncoding(htmlSrc);
int r = internalPut2(put,abstract,concrete,isNew,htmlFilename);
free(htmlFilename);
return r;
}
static int internalPut2(WordPutData *put, DFNode *abstract, DFNode *concrete, int isNew, const char *htmlFilename)
{
int imageChanged = 0;
int sizeChanged = 0;
if (!DFStorageExists(put->conv->abstractStorage,htmlFilename)) {
WordConverterWarning(put->conv,"HTML image %s does not exist",htmlFilename);
return 0;
}
PixelSize htmlFileSize = PixelSizeZero;
DFError *error = NULL;
if (!getImageFile(put->conv,htmlFilename,&htmlFileSize,&error)) {
WordConverterWarning(put->conv,"Could not get aspect ratio of image: %s\n",DFErrorMessage(&error));
DFErrorRelease(error);
return 0;
}
PointsSize htmlSize = pointsSizeFromHTMLImg(put->conv,abstract,htmlFileSize);
OPCRelationship *rel = NULL;
const char *drawingId = NULL;
if (isNew) {
imageChanged = 1;
sizeChanged = 1;
}
else {
ImageInfo *wordInfo = getImageInfo(concrete);
if (wordInfo == NULL)
return 0;
rel = OPCRelationshipSetLookupById(put->conv->package->documentPart->relationships,wordInfo->rId);
if ((wordInfo != NULL) && (wordInfo->widthPts > 0) && (wordInfo->heightPts > 0) && (rel != NULL)) {
const char *wordSrc = rel->target;
DFStorage *storage = put->conv->package->opc->storage;
if (!DFStorageExists(storage,wordSrc)) {
WordConverterWarning(put->conv,"Word image %s does not exist",wordSrc);
ImageInfoFree(wordInfo);
return 0;
}
DFBuffer *content1 = DFBufferReadFromStorage(put->conv->abstractStorage,htmlFilename,NULL);
DFBuffer *content2 = DFBufferReadFromStorage(storage,wordSrc,NULL);
int contentsEqual = ((content1 != NULL) && (content2 != NULL) &&
(content1->len == content2->len) &&
!memcmp(content1->data,content2->data,content1->len));
DFBufferRelease(content1);
DFBufferRelease(content2);
if (!contentsEqual) {
rel->needsRemoveCheck = 1;
imageChanged = 1;
}
if (fabs(wordInfo->widthPts - htmlSize.widthPts) >= 0.1)
sizeChanged = 1;
}
ImageInfoFree(wordInfo);
if (concrete->tag == WORD_DRAWING)
drawingId = DrawingInfoDrawingId(concrete);
}
if (imageChanged || sizeChanged) {
if (imageChanged || (rel == NULL)) { // FIXME: is the rel == NULL needed?
DFError *error = NULL;
rel = addImageRelationship(put->conv,htmlFilename,&error);
if (rel == NULL) {
WordConverterWarning(put->conv,"%s",DFErrorMessage(&error));
return 0;
}
}
if (drawingId == NULL)
drawingId = WordObjectsAddDrawing(put->conv->objects)->drawingId;
populateDrawingElement(put->conv,concrete,htmlSize.widthPts,htmlSize.heightPts,
drawingId,rel->rId);
}
return 1;
}
void WordDrawingPut(WordPutData *put, DFNode *abstract, DFNode *concrete)
{
internalPut(put,abstract,concrete,0);
}
static void populateDrawingElement(WordConverter *converter, DFNode *root, double widthPts,
double heightPts, const char *drawingId, const char *rId)
{
EMUSize size;
size.widthEmu = (unsigned long long)round(widthPts*EMUS_PER_POINT);
size.heightEmu = (unsigned long long)round(heightPts*EMUS_PER_POINT);
root->tag = WORD_DRAWING;
DFRemoveAllAttributes(root);
while (root->first != NULL)
DFRemoveNode(root->first);
char *docName = DFFormatString("Picture %s",drawingId);
const char *imgName = "image";
const char *imgId = "0";
DFNode *inlin = DFCreateChildElement(root,DML_WP_INLINE);
DFNode *extent = DFCreateChildElement(inlin,DML_WP_EXTENT); // a_CT_PositiveSize2D
DFFormatAttribute(extent,NULL_CX,"%llu",size.widthEmu);
DFFormatAttribute(extent,NULL_CY,"%llu",size.heightEmu);
DFNode *docPr = DFCreateChildElement(inlin,DML_WP_DOCPR); // a_CT_NonVisualDrawingProps
DFSetAttribute(docPr,NULL_ID,drawingId);
DFSetAttribute(docPr,NULL_NAME,docName);
DFNode *cNvGraphicFramePr = DFCreateChildElement(inlin,DML_WP_CNVGRAPHICFRAMEPR);
DFNode *graphicFrameLocks = DFCreateChildElement(cNvGraphicFramePr,DML_MAIN_GRAPHICFRAMELOCKS);
DFSetAttribute(graphicFrameLocks,NULL_NOCHANGEASPECT,"1");
DFNode *graphic = DFCreateChildElement(inlin,DML_MAIN_GRAPHIC);
DFNode *graphicData = DFCreateChildElement(graphic,DML_MAIN_GRAPHICDATA);
DFSetAttribute(graphicData,NULL_URI,"http://schemas.openxmlformats.org/drawingml/2006/picture");
DFNode *pic = DFCreateChildElement(graphicData,DML_PICTURE_PIC);
DFNode *nvPicPr = DFCreateChildElement(pic,DML_PICTURE_NVPICPR);
DFNode *blipFill = DFCreateChildElement(pic,DML_PICTURE_BLIPFILL); // a_CT_BlipFillProperties
DFNode *spPr = DFCreateChildElement(pic,DML_PICTURE_SPPR); // a_CT_ShapeProperties
// Graphic - Non-visual properties
DFNode *cNvPr = DFCreateChildElement(nvPicPr,DML_PICTURE_CNVPR);
DFNode *cNvPicPr = DFCreateChildElement(nvPicPr,DML_PICTURE_CNVPICPR);
DFSetAttribute(cNvPr,NULL_ID,imgId);
DFSetAttribute(cNvPr,NULL_NAME,imgName);
// Blip
DFNode *blip = DFCreateChildElement(blipFill,DML_MAIN_BLIP);
DFSetAttribute(blip,OREL_EMBED,rId);
DFNode *stretch = DFCreateChildElement(blipFill,DML_MAIN_STRETCH);
DFNode *fillRect = DFCreateChildElement(stretch,DML_MAIN_FILLRECT);
// Shape properties
DFNode *xfrm = DFCreateChildElement(spPr,DML_MAIN_XFRM);
DFNode *off = DFCreateChildElement(xfrm,DML_MAIN_OFF);
DFSetAttribute(off,NULL_X,"0");
DFSetAttribute(off,NULL_Y,"0");
DFNode *ext = DFCreateChildElement(xfrm,DML_MAIN_EXT);
DFFormatAttribute(ext,NULL_CX,"%llu",size.widthEmu);
DFFormatAttribute(ext,NULL_CY,"%llu",size.heightEmu);
DFNode *prstGeom = DFCreateChildElement(spPr,DML_MAIN_PRSTGEOM);
DFSetAttribute(prstGeom,NULL_PRST,"rect");
(void)cNvPicPr;
(void)fillRect;
free(docName);
}
void WordDrawingRemove(WordPutData *put, DFNode *concrete)
{
// FIXME: This only causes the relationship to be deleted - the image file itself is not removed
if (concrete->tag == WORD_DRAWING) {
EMUSize wordEMUSize = DrawingInfoSize(concrete);
const char *wordRId = DrawingInfoRId(concrete);
const char *drawingId = DrawingInfoDrawingId(concrete);
OPCRelationship *rel = NULL;
if (wordRId != NULL)
rel = OPCRelationshipSetLookupById(put->conv->package->documentPart->relationships,wordRId);
if ((wordEMUSize.widthEmu > 0) && (wordEMUSize.heightEmu > 0) && (rel != NULL) && (drawingId != NULL)) {
rel->needsRemoveCheck = 1;
}
}
else if (concrete->tag == WORD_OBJECT) {
}
}
WordLens WordDrawingLens = {
.isVisible = WordDrawingIsVisible,
.get = WordDrawingGet,
.put = WordDrawingPut,
.create = WordDrawingCreate,
.remove = WordDrawingRemove,
};