001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.file.types; 029 030import com.alkacon.simapi.Simapi; 031 032import org.opencms.configuration.CmsConfigurationException; 033import org.opencms.db.CmsSecurityManager; 034import org.opencms.file.CmsFile; 035import org.opencms.file.CmsObject; 036import org.opencms.file.CmsProperty; 037import org.opencms.file.CmsPropertyDefinition; 038import org.opencms.file.CmsResource; 039import org.opencms.file.CmsResourceFilter; 040import org.opencms.file.CmsVfsException; 041import org.opencms.file.wrapper.CmsWrappedResource; 042import org.opencms.loader.CmsDumpLoader; 043import org.opencms.loader.CmsImageLoader; 044import org.opencms.loader.CmsImageScaler; 045import org.opencms.main.CmsException; 046import org.opencms.main.CmsLog; 047import org.opencms.main.OpenCms; 048import org.opencms.report.I_CmsReport; 049import org.opencms.security.CmsPermissionSet; 050import org.opencms.security.CmsSecurityException; 051import org.opencms.util.CmsStringUtil; 052import org.opencms.xml.CmsXmlEntityResolver; 053 054import java.awt.image.BufferedImage; 055import java.io.ByteArrayInputStream; 056import java.io.IOException; 057import java.io.InputStream; 058import java.util.ArrayList; 059import java.util.HashMap; 060import java.util.List; 061import java.util.Map; 062import java.util.Objects; 063 064import org.apache.commons.logging.Log; 065 066import org.dom4j.Document; 067import org.dom4j.Element; 068import org.dom4j.io.SAXReader; 069 070import com.drew.imaging.ImageMetadataReader; 071import com.drew.metadata.Directory; 072import com.drew.metadata.Metadata; 073import com.drew.metadata.exif.ExifDirectoryBase; 074import com.drew.metadata.exif.ExifIFD0Directory; 075 076/** 077 * Resource type descriptor for the type "image".<p> 078 * 079 * @since 6.0.0 080 */ 081public class CmsResourceTypeImage extends A_CmsResourceType { 082 083 /** 084 * A data container for image size and scale operations.<p> 085 */ 086 protected static class CmsImageAdjuster { 087 088 /** Value for EXIF Orientation tag that says that the image is oriented as-is (requires no rotation/mirroring). */ 089 public static final int DEFAULT_ORIENTATION = 1; 090 091 /** The image byte content. */ 092 private byte[] m_content; 093 094 /** The (optional) image scaler that contains the image downscale settings. */ 095 private CmsImageScaler m_imageDownScaler; 096 097 /** The image properties. */ 098 private List<CmsProperty> m_properties; 099 100 /** The image root path. */ 101 private String m_rootPath; 102 103 /** 104 * Creates a new image data container.<p> 105 * 106 * @param content the image byte content 107 * @param rootPath the image root path 108 * @param properties the image properties 109 * @param downScaler the (optional) image scaler that contains the image downscale settings 110 */ 111 public CmsImageAdjuster( 112 byte[] content, 113 String rootPath, 114 List<CmsProperty> properties, 115 CmsImageScaler downScaler) { 116 117 m_content = content; 118 m_rootPath = rootPath; 119 m_properties = properties; 120 m_imageDownScaler = downScaler; 121 } 122 123 /** 124 * Calculates the image size and adjusts the image dimensions (if required) accoring to the configured 125 * image downscale settings.<p> 126 * 127 * The image dimensions are always calculated from the given image. The internal list of properties is updated 128 * with a value for <code>{@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE}</code> that 129 * contains the calculated image dimensions.<p> 130 */ 131 public void adjust() { 132 133 int orientation = DEFAULT_ORIENTATION; 134 BufferedImage orientedCopy = null; 135 if (Simapi.TYPE_JPEG.equals(Simapi.getImageType(m_rootPath))) { 136 137 // For JPEGs, detect if non-standard orientation is set, and if so, preprocess 138 // image so that it's image data matches the orientation. Later, we create a new JPEG 139 // with this oriented image data and no EXIF orientation to be saved in the VFS. 140 141 try (InputStream stream = new ByteArrayInputStream(getContent())) { 142 Metadata meta = ImageMetadataReader.readMetadata(stream); 143 Directory ifd0 = meta.getFirstDirectoryOfType(ExifIFD0Directory.class); 144 if ((ifd0 != null) && ifd0.containsTag(ExifDirectoryBase.TAG_ORIENTATION)) { 145 orientation = ifd0.getInt(ExifDirectoryBase.TAG_ORIENTATION); 146 } 147 } catch (Exception e) { 148 LOG.info(e.getLocalizedMessage(), e); 149 } 150 151 if (orientation != DEFAULT_ORIENTATION) { 152 try { 153 BufferedImage image = Simapi.read(getContent()); 154 orientedCopy = createOrientedCopy(image, orientation); 155 } catch (IOException e) { 156 LOG.error(e.getLocalizedMessage(), e); 157 } 158 } 159 } 160 CmsImageScaler scaler = null; 161 if (orientedCopy != null) { 162 scaler = new CmsImageScaler(orientedCopy.getWidth(), orientedCopy.getHeight()); 163 } else { 164 scaler = new CmsImageScaler(getContent(), getRootPath()); 165 } 166 if (!scaler.isValid()) { 167 // error calculating image dimensions - this image can't be scaled or resized 168 return; 169 } 170 171 // check if the image is to big and needs to be rescaled 172 if (scaler.isDownScaleRequired(m_imageDownScaler)) { 173 // image is to big, perform rescale operation 174 CmsImageScaler downScaler = scaler.getDownScaler(m_imageDownScaler); 175 // perform the rescale using the adjusted size 176 m_content = downScaler.scaleImage(m_content, orientedCopy, m_rootPath); 177 // image size has been changed, adjust the scaler for later setting of properties 178 scaler.setHeight(downScaler.getHeight()); 179 scaler.setWidth(downScaler.getWidth()); 180 } else if (orientedCopy != null) { 181 Simapi simapi = new Simapi(); 182 try { 183 m_content = simapi.getBytes(orientedCopy, Simapi.getImageType(m_rootPath)); 184 } catch (Exception e) { 185 LOG.error(e.getLocalizedMessage(), e); 186 } 187 } 188 189 CmsProperty p = new CmsProperty(CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, null, scaler.toString()); 190 // create the new property list if required (don't modify the original List) 191 List<CmsProperty> result = new ArrayList<CmsProperty>(); 192 if ((m_properties != null) && (m_properties.size() > 0)) { 193 result.addAll(m_properties); 194 result.remove(p); 195 } 196 // add the updated property 197 result.add(p); 198 // store the changed properties 199 m_properties = result; 200 } 201 202 /** 203 * Returns the image content.<p> 204 * 205 * @return the image content 206 */ 207 public byte[] getContent() { 208 209 return m_content; 210 } 211 212 /** 213 * Returns the image properties.<p> 214 * 215 * @return the image properties 216 */ 217 public List<CmsProperty> getProperties() { 218 219 return m_properties; 220 } 221 222 /** 223 * Returns the image VFS root path.<p> 224 * 225 * @return the image VFS root path 226 */ 227 public String getRootPath() { 228 229 return m_rootPath; 230 } 231 232 /** 233 * Applies the given EXIF orientation to an image. 234 * 235 * <p>Given a JPEG with the image data 'image' and the orientation 'exifOrientation', this results in an image that, 236 * when written to a JPEG with an EXIF orientation of 1, will look the same as the original image when viewed in an image viewer 237 * that supports EXIF orientation. 238 * 239 * <p>This returns a transformed copy and does not modify the original image. 240 * 241 * @param image the original image 242 * @param exifOrientation the orientation to apply 243 * @return the transformed image 244 */ 245 private BufferedImage createOrientedCopy(BufferedImage image, int exifOrientation) { 246 247 BufferedImage target; 248 boolean flipDimensions = exifOrientation > 4; 249 int w = image.getWidth(); 250 int h = image.getHeight(); 251 target = new BufferedImage(flipDimensions ? h : w, flipDimensions ? w : h, image.getType()); 252 // for each pixel, copy it to different coordinates (tx, ty) in the target depending on orientation 253 for (int x = 0; x < w; x++) { 254 for (int y = 0; y < h; y++) { 255 int tx, ty; 256 switch (exifOrientation) { 257 case 1: 258 tx = x; 259 ty = y; 260 break; 261 case 2: 262 tx = w - 1 - x; 263 ty = y; 264 break; 265 case 3: 266 tx = w - 1 - x; 267 ty = h - 1 - y; 268 break; 269 case 4: 270 tx = x; 271 ty = h - 1 - y; 272 break; 273 case 5: 274 tx = y; 275 ty = x; 276 break; 277 case 6: 278 tx = h - 1 - y; 279 ty = x; 280 break; 281 case 7: 282 tx = h - 1 - y; 283 ty = w - 1 - x; 284 break; 285 case 8: 286 tx = y; 287 ty = w - 1 - x; 288 break; 289 default: 290 tx = x; 291 ty = y; 292 break; 293 } 294 target.setRGB(tx, ty, image.getRGB(x, y)); 295 } 296 } 297 return target; 298 } 299 } 300 301 /** 302 * Helper class for parsing SVG sizes.<p> 303 * 304 * Note: This is *not* intended as a general purpose tool, it is only used for parsing SVG sizes 305 * as part of the image.size property determination. 306 */ 307 private static class SvgSize { 308 309 /** The numeric value of the size. */ 310 private double m_size; 311 312 /** The unit of the size. */ 313 private String m_unit; 314 315 /** 316 * Parses the SVG size.<p> 317 * 318 * @param s the string containing the size 319 * 320 * @return the parsed size 321 */ 322 public static SvgSize parse(String s) { 323 324 if (CmsStringUtil.isEmptyOrWhitespaceOnly(s)) { 325 return null; 326 } 327 s = s.trim(); 328 double length = -1; 329 int unitPos; 330 String unit = ""; 331 // find longest prefix of s that can be parsed as a number, use the remaining part as the unit 332 for (unitPos = s.length(); unitPos >= 0; unitPos--) { 333 String prefix = s.substring(0, unitPos); 334 unit = s.substring(unitPos); 335 try { 336 length = Double.parseDouble(prefix); 337 break; 338 } catch (NumberFormatException e) { 339 // ignore 340 } 341 } 342 if (length < 0) { 343 LOG.warn("Invalid string for SVG size: " + s); 344 return null; 345 } 346 SvgSize result = new SvgSize(); 347 result.m_size = length; 348 result.m_unit = unit; 349 return result; 350 } 351 352 /** 353 * Gets the numeric value of the size.<p> 354 * 355 * @return the size 356 */ 357 public double getSize() { 358 359 return m_size; 360 } 361 362 /** 363 * Gets the unit of the size.<p> 364 * 365 * @return the unit 366 */ 367 public String getUnit() { 368 369 return m_unit; 370 371 } 372 373 /** 374 * @see java.lang.Object#toString() 375 */ 376 @Override 377 public String toString() { 378 379 return m_size + m_unit; 380 } 381 } 382 383 /** The log object for this class. */ 384 public static final Log LOG = CmsLog.getLog(CmsResourceTypeImage.class); 385 386 /** 387 * The value for the {@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE} property if resources in 388 * a folder should never be downscaled.<p> 389 */ 390 public static final String PROPERTY_VALUE_UNLIMITED = "unlimited"; 391 392 /** The default image preview provider. */ 393 private static final String GALLERY_PREVIEW_PROVIDER = "org.opencms.ade.galleries.preview.CmsImagePreviewProvider"; 394 395 /** The image scaler for the image downscale operation (if configured). */ 396 private static CmsImageScaler m_downScaler; 397 398 /** Indicates that the static configuration of the resource type has been frozen. */ 399 private static boolean m_staticFrozen; 400 401 /** The static resource loader id of this resource type. */ 402 private static int m_staticLoaderId; 403 404 /** The static type id of this resource type. */ 405 private static int m_staticTypeId; 406 407 /** The type id of this resource type. */ 408 private static final int RESOURCE_TYPE_ID = 3; 409 410 /** The name of this resource type. */ 411 private static final String RESOURCE_TYPE_NAME = "image"; 412 413 /** The serial version id. */ 414 private static final long serialVersionUID = -8708850913653288684L; 415 416 /** 417 * Default constructor, used to initialize member variables.<p> 418 */ 419 public CmsResourceTypeImage() { 420 421 super(); 422 m_typeId = RESOURCE_TYPE_ID; 423 m_typeName = RESOURCE_TYPE_NAME; 424 } 425 426 /** 427 * Returns the image downscaler to use when writing an image resource to the given root path.<p> 428 * 429 * If <code>null</code> is returned, image downscaling must not be used for the resource with the given path. 430 * This may be the case if image downscaling is not configured at all, or if image downscaling has been disabled 431 * for the parent folder by setting the folders property {@link CmsPropertyDefinition#PROPERTY_IMAGE_SIZE} 432 * to the value {@link #PROPERTY_VALUE_UNLIMITED}.<p> 433 * 434 * @param cms the current OpenCms user context 435 * @param rootPath the root path of the resource to write 436 * 437 * @return the downscaler to use, or <code>null</code> if no downscaling is required for the resource 438 */ 439 public static CmsImageScaler getDownScaler(CmsObject cms, String rootPath) { 440 441 if (m_downScaler == null) { 442 // downscaling is not configured at all 443 return null; 444 } 445 // try to read the image.size property from the parent folder 446 String parentFolder = CmsResource.getParentFolder(rootPath); 447 parentFolder = cms.getRequestContext().removeSiteRoot(parentFolder); 448 try { 449 CmsProperty fileSizeProperty = cms.readPropertyObject( 450 parentFolder, 451 CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, 452 true); 453 if (!fileSizeProperty.isNullProperty()) { 454 // image.size property has been set 455 String value = fileSizeProperty.getValue().trim(); 456 if (CmsStringUtil.isNotEmpty(value)) { 457 if (PROPERTY_VALUE_UNLIMITED.equals(value)) { 458 // in this case no downscaling must be done 459 return null; 460 } else { 461 CmsImageScaler scaler = new CmsImageScaler(value); 462 if (scaler.isValid()) { 463 // special folder based scaler settings have been set 464 return scaler; 465 } 466 } 467 } 468 } 469 } catch (CmsException e) { 470 // ignore, continue with given downScaler 471 } 472 return (CmsImageScaler)m_downScaler.clone(); 473 } 474 475 /** 476 * Returns the static type id of this (default) resource type.<p> 477 * 478 * @return the static type id of this (default) resource type 479 */ 480 public static int getStaticTypeId() { 481 482 return m_staticTypeId; 483 } 484 485 /** 486 * Returns the static type name of this (default) resource type.<p> 487 * 488 * @return the static type name of this (default) resource type 489 */ 490 public static String getStaticTypeName() { 491 492 return RESOURCE_TYPE_NAME; 493 } 494 495 /** 496 * @see org.opencms.file.types.I_CmsResourceType#createResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, java.lang.String, byte[], java.util.List) 497 */ 498 @Override 499 public CmsResource createResource( 500 CmsObject cms, 501 CmsSecurityManager securityManager, 502 String resourcename, 503 byte[] content, 504 List<CmsProperty> properties) 505 throws CmsException { 506 507 if (resourcename.toLowerCase().endsWith(".svg")) { 508 List<CmsProperty> prop2 = tryAddImageSizeFromSvg(content, properties); 509 properties = prop2; 510 } else if (CmsImageLoader.isEnabled()) { 511 String rootPath = cms.getRequestContext().addSiteRoot(resourcename); 512 // get the downscaler to use 513 CmsImageScaler downScaler = getDownScaler(cms, rootPath); 514 // create a new image scale adjuster 515 CmsImageAdjuster adjuster = new CmsImageAdjuster(content, rootPath, properties, downScaler); 516 // update the image scale adjuster - this will calculate the image dimensions and (optionally) downscale the size 517 adjuster.adjust(); 518 // continue with the updated content and properties 519 content = adjuster.getContent(); 520 properties = adjuster.getProperties(); 521 } 522 return super.createResource(cms, securityManager, resourcename, content, properties); 523 } 524 525 /** 526 * @see org.opencms.file.types.I_CmsResourceType#getGalleryPreviewProvider() 527 */ 528 @Override 529 public String getGalleryPreviewProvider() { 530 531 if (m_galleryPreviewProvider == null) { 532 m_galleryPreviewProvider = getConfiguration().getString( 533 CONFIGURATION_GALLERY_PREVIEW_PROVIDER, 534 GALLERY_PREVIEW_PROVIDER); 535 } 536 return m_galleryPreviewProvider; 537 } 538 539 /** 540 * @see org.opencms.file.types.I_CmsResourceType#getLoaderId() 541 */ 542 @Override 543 public int getLoaderId() { 544 545 return m_staticLoaderId; 546 } 547 548 /** 549 * @see org.opencms.file.types.A_CmsResourceType#importResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.report.I_CmsReport, java.lang.String, org.opencms.file.CmsResource, byte[], java.util.List) 550 */ 551 @Override 552 public CmsResource importResource( 553 CmsObject cms, 554 CmsSecurityManager securityManager, 555 I_CmsReport report, 556 String resourcename, 557 CmsResource resource, 558 byte[] content, 559 List<CmsProperty> properties) 560 throws CmsException { 561 562 // keep original parameter, for debugging 563 @SuppressWarnings("unused") 564 CmsResource originalResource = resource; 565 566 if (resourcename.toLowerCase().endsWith(".svg")) { 567 properties = tryAddImageSizeFromSvg(content, properties); 568 } else if (CmsImageLoader.isEnabled()) { 569 // siblings have null content in import 570 if (content != null) { 571 try { 572 // get the downscaler to use 573 CmsImageScaler downScaler = getDownScaler(cms, resource.getRootPath()); 574 // create a new image scale adjuster 575 CmsImageAdjuster adjuster = new CmsImageAdjuster( 576 content, 577 resource.getRootPath(), 578 properties, 579 downScaler); 580 // update the image scale adjuster - this will calculate the image dimensions and (optionally) adjust the size 581 adjuster.adjust(); 582 // continue with the updated content and properties 583 content = adjuster.getContent(); 584 properties = adjuster.getProperties(); 585 } catch (Throwable e) { 586 LOG.error(e.getLocalizedMessage(), e); 587 if (report != null) { 588 report.println(e); 589 report.addWarning(e); 590 } 591 CmsWrappedResource wrappedRes = new CmsWrappedResource(resource); 592 wrappedRes.setTypeId( 593 OpenCms.getResourceManager().getResourceType( 594 CmsResourceTypeBinary.getStaticTypeId()).getTypeId()); 595 resource = wrappedRes.getResource(); 596 } 597 } 598 } 599 return super.importResource(cms, securityManager, report, resourcename, resource, content, properties); 600 } 601 602 /** 603 * @see org.opencms.file.types.A_CmsResourceType#initConfiguration(java.lang.String, java.lang.String, String) 604 */ 605 @Override 606 public void initConfiguration(String name, String id, String className) throws CmsConfigurationException { 607 608 if ((OpenCms.getRunLevel() > OpenCms.RUNLEVEL_2_INITIALIZING) && m_staticFrozen) { 609 // configuration already frozen 610 throw new CmsConfigurationException( 611 Messages.get().container( 612 Messages.ERR_CONFIG_FROZEN_3, 613 this.getClass().getName(), 614 getStaticTypeName(), 615 new Integer(getStaticTypeId()))); 616 } 617 618 if (!RESOURCE_TYPE_NAME.equals(name)) { 619 // default resource type MUST have default name 620 throw new CmsConfigurationException( 621 Messages.get().container( 622 Messages.ERR_INVALID_RESTYPE_CONFIG_NAME_3, 623 this.getClass().getName(), 624 RESOURCE_TYPE_NAME, 625 name)); 626 } 627 628 // freeze the configuration 629 m_staticFrozen = true; 630 631 super.initConfiguration(RESOURCE_TYPE_NAME, id, className); 632 // set static members with values from the configuration 633 m_staticTypeId = m_typeId; 634 635 if (CmsImageLoader.isEnabled()) { 636 // the image loader is enabled, image operations are supported 637 m_staticLoaderId = CmsImageLoader.RESOURCE_LOADER_ID_IMAGE_LOADER; 638 // set the maximum size scaler 639 String downScaleParams = CmsImageLoader.getDownScaleParams(); 640 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(downScaleParams)) { 641 m_downScaler = new CmsImageScaler(downScaleParams); 642 if (!m_downScaler.isValid()) { 643 // ignore invalid parameters 644 m_downScaler = null; 645 } 646 } 647 } else { 648 // no image operations are supported, use dump loader 649 m_staticLoaderId = CmsDumpLoader.RESOURCE_LOADER_ID; 650 // disable maximum image size operation 651 m_downScaler = null; 652 } 653 } 654 655 /** 656 * @see org.opencms.file.types.I_CmsResourceType#replaceResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, int, byte[], java.util.List) 657 */ 658 @Override 659 public void replaceResource( 660 CmsObject cms, 661 CmsSecurityManager securityManager, 662 CmsResource resource, 663 int type, 664 byte[] content, 665 List<CmsProperty> properties) 666 throws CmsException { 667 668 if (resource.getRootPath().toLowerCase().endsWith(".svg")) { 669 List<CmsProperty> newProperties = tryAddImageSizeFromSvg(content, properties); 670 if (properties != newProperties) { // yes, we actually do want to compare object identity here 671 writePropertyObjects(cms, securityManager, resource, newProperties); 672 } 673 } else if (CmsImageLoader.isEnabled()) { 674 // check if the user has write access and if resource is locked 675 // done here so that no image operations are performed in case no write access is granted 676 securityManager.checkPermissions( 677 cms.getRequestContext(), 678 resource, 679 CmsPermissionSet.ACCESS_WRITE, 680 true, 681 CmsResourceFilter.ALL); 682 683 // get the downscaler to use 684 CmsImageScaler downScaler = getDownScaler(cms, resource.getRootPath()); 685 // create a new image scale adjuster 686 CmsImageAdjuster adjuster = new CmsImageAdjuster(content, resource.getRootPath(), properties, downScaler); 687 // update the image scale adjuster - this will calculate the image dimensions and (optionally) adjust the size 688 adjuster.adjust(); 689 // continue with the updated content 690 content = adjuster.getContent(); 691 if (adjuster.getProperties() != null) { 692 // write properties 693 writePropertyObjects(cms, securityManager, resource, adjuster.getProperties()); 694 } 695 } 696 super.replaceResource(cms, securityManager, resource, type, content, properties); 697 } 698 699 /** 700 * @see org.opencms.file.types.I_CmsResourceType#writeFile(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsFile) 701 */ 702 @Override 703 public CmsFile writeFile(CmsObject cms, CmsSecurityManager securityManager, CmsFile resource) 704 throws CmsException, CmsVfsException, CmsSecurityException { 705 706 if (CmsImageLoader.isEnabled()) { 707 // check if the user has write access and if resource is locked 708 // done here so that no image operations are performed in case no write access is granted 709 securityManager.checkPermissions( 710 cms.getRequestContext(), 711 resource, 712 CmsPermissionSet.ACCESS_WRITE, 713 true, 714 CmsResourceFilter.ALL); 715 716 // get the downscaler to use 717 CmsImageScaler downScaler = getDownScaler(cms, resource.getRootPath()); 718 // create a new image scale adjuster 719 CmsImageAdjuster adjuster = new CmsImageAdjuster( 720 resource.getContents(), 721 resource.getRootPath(), 722 null, 723 downScaler); 724 // update the image scale adjuster - this will calculate the image dimensions and (optionally) adjust the size 725 adjuster.adjust(); 726 // continue with the updated content 727 resource.setContents(adjuster.getContent()); 728 if (adjuster.getProperties() != null) { 729 // write properties 730 writePropertyObjects(cms, securityManager, resource, adjuster.getProperties()); 731 } 732 } 733 return super.writeFile(cms, securityManager, resource); 734 } 735 736 /** 737 * Tries to use the viewbox from the SVG data to determine the image size and add it to the list of properties.<p> 738 * 739 * @param content the content bytes of an SVG file 740 * @param properties the original properties (this object will not be modified) 741 * 742 * @return the amended properties 743 */ 744 protected List<CmsProperty> tryAddImageSizeFromSvg(byte[] content, List<CmsProperty> properties) { 745 746 if ((content == null) || (content.length == 0)) { 747 return properties; 748 } 749 List<CmsProperty> newProps = properties; 750 try { 751 double w = -1, h = -1; 752 SAXReader reader = new SAXReader(); 753 reader.setEntityResolver(new CmsXmlEntityResolver(null)); 754 Document doc = reader.read(new ByteArrayInputStream(content)); 755 Element node = (Element)(doc.selectSingleNode("/svg")); 756 if (node != null) { 757 String widthStr = node.attributeValue("width"); 758 String heightStr = node.attributeValue("height"); 759 SvgSize width = SvgSize.parse(widthStr); 760 SvgSize height = SvgSize.parse(heightStr); 761 if ((width != null) && (height != null) && Objects.equals(width.getUnit(), height.getUnit())) { 762 // If width and height are given and have the same units, just interpret them as pixels, otherwise use viewbox 763 w = width.getSize(); 764 h = height.getSize(); 765 } else { 766 String viewboxStr = node.attributeValue("viewBox"); 767 if (viewboxStr != null) { 768 viewboxStr = viewboxStr.replace(",", " "); 769 String[] viewboxParts = viewboxStr.trim().split(" +"); 770 if (viewboxParts.length == 4) { 771 w = Double.parseDouble(viewboxParts[2]); 772 h = Double.parseDouble(viewboxParts[3]); 773 } 774 } 775 } 776 if ((w > 0) && (h > 0)) { 777 String propValue = "w:" + (int)Math.round(w) + ",h:" + (int)Math.round(h); 778 Map<String, CmsProperty> propsMap = properties == null 779 ? new HashMap<>() 780 : CmsProperty.toObjectMap(properties); 781 propsMap.put( 782 CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, 783 new CmsProperty(CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, null, propValue)); 784 newProps = new ArrayList<>(propsMap.values()); 785 } 786 787 } 788 } catch (Exception e) { 789 LOG.error("Error while trying to determine size of SVG: " + e.getLocalizedMessage(), e); 790 } 791 return newProps; 792 } 793 794}