Skip to content

Commit 6b466b3

Browse files
authored
Merge pull request #108 from opengisch/QF-1844-Adding-check-when-the-source-is-relative-to-project-home
Ensure the layer SVG or raster symbols are within the project directory or embedd otherwise
2 parents a9cf274 + 14ce113 commit 6b466b3

File tree

2 files changed

+57
-18
lines changed

2 files changed

+57
-18
lines changed

libqfieldsync/offline_converter.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from .utils.file_utils import (
5555
copy_attachments,
5656
copy_multifile,
57-
embed_layer_symbols_on_project,
57+
set_relative_embed_layer_symbols_on_project,
5858
)
5959
from .utils.logger import logger
6060
from .utils.qgis import make_temp_qgis_file, open_project
@@ -306,9 +306,6 @@ def _convert(self, project: QgsProject) -> None:
306306
elif layer_action == SyncAction.REMOVE:
307307
project.removeMapLayer(layer)
308308

309-
# Change symbol path to embedded marker
310-
embed_layer_symbols_on_project(layer)
311-
312309
self.remove_empty_groups_from_layer_tree_group(project.layerTreeRoot())
313310

314311
export_project_filename = self._export_filename
@@ -379,6 +376,11 @@ def _convert(self, project: QgsProject) -> None:
379376
# Disable project options that could create problems on a portable
380377
# project with offline layers
381378
self.post_process_offline_layers()
379+
# Change SVG and Raster symbols path to relative or embedded
380+
for layer in QgsProject.instance().mapLayers().values():
381+
set_relative_embed_layer_symbols_on_project(
382+
layer, self.original_filename.parent, self._export_filename.parent
383+
)
382384

383385
self._check_canceled()
384386

libqfieldsync/utils/file_utils.py

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@
3131
from typing import List, Optional, Tuple, Union
3232

3333
from qgis.core import (
34+
Qgis,
3435
QgsCategorizedSymbolRenderer,
3536
QgsRasterMarkerSymbolLayer,
3637
QgsRuleBasedRenderer,
3738
QgsSingleSymbolRenderer,
3839
QgsSvgMarkerSymbolLayer,
3940
QgsSymbol,
4041
QgsVectorLayer,
41-
QgsWkbTypes,
4242
)
4343
from qgis.PyQt.QtCore import QCoreApplication
4444

@@ -229,11 +229,15 @@ def is_valid_filepath(path: str) -> bool:
229229
return True
230230

231231

232-
def update_symbols_to_embedded(symbol: QgsSymbol) -> None:
232+
def update_symbols_to_relative_embedded(
233+
symbol: QgsSymbol, home_path: Path, destination_path: Path
234+
) -> None:
233235
"""
234-
Update SVG or Raster symbols layer to embed it in the QGIS project.
236+
Update SVG or Raster symbols layer to relative path or embed it in the QGIS project.
235237
Args:
236238
symbol: The QGIS symbol (from a renderer).
239+
home_path: The root of QGIS Project home path.
240+
destination_path: The target directory where the exported project will be saved.
237241
"""
238242
if symbol is None:
239243
return
@@ -255,23 +259,47 @@ def update_symbols_to_embedded(symbol: QgsSymbol) -> None:
255259
if not source_path.is_file():
256260
continue
257261

258-
with open(source_path, "rb") as file:
259-
file_data = file.read()
260-
encoded_data = base64.b64encode(file_data).decode()
262+
if source_path.is_relative_to(home_path):
263+
relative_path = source_path.relative_to(home_path)
264+
destination_path_file = destination_path.joinpath(relative_path)
265+
266+
if destination_path_file.exists():
267+
symbol_layer.setPath(str(relative_path))
268+
else:
269+
encoded_data = base64.b64encode(source_path.read_bytes()).decode()
270+
symbol_layer.setPath(f"base64:{encoded_data}")
271+
272+
else:
273+
encoded_data = base64.b64encode(source_path.read_bytes()).decode()
261274
symbol_layer.setPath(f"base64:{encoded_data}")
262275

263276

264-
def embed_layer_symbols_on_project(layer: QgsVectorLayer) -> None:
277+
def set_relative_embed_layer_symbols_on_project(
278+
layer: QgsVectorLayer, project_home: Path, export_project_path: Path
279+
) -> None:
265280
"""
266-
Update the paths of symbols to embedded symbols in the QGIS project.
281+
Update the layer style SVG or Raster symbol paths to relative or embedded them in the QGIS project file.
282+
283+
First try to ensure the paths are within to the QGIS project path.
284+
If the resulting path is impossible, then embed the symbols in the QGIS project.
267285
268286
Args:
269287
layer: The QgsVectorLayer to update. The layer is a point layer.
288+
project_home: The root of QGIS Project home path.
289+
export_project_path: The target directory for the exported offline QGIS project.
270290
"""
271291

292+
if Qgis.QGIS_VERSION_INT >= 33000:
293+
point_geometry = Qgis.GeometryType.Point
294+
else:
295+
from qgis.core import QgsWkbTypes
296+
297+
point_geometry = QgsWkbTypes.GeometryType.PointGeometry
298+
272299
if (
273-
not isinstance(layer, QgsVectorLayer)
274-
or layer.geometryType() != QgsWkbTypes.PointGeometry
300+
not layer.isValid()
301+
or not isinstance(layer, QgsVectorLayer)
302+
or layer.geometryType() != point_geometry
275303
):
276304
return
277305

@@ -283,7 +311,9 @@ def embed_layer_symbols_on_project(layer: QgsVectorLayer) -> None:
283311
if isinstance(renderer, QgsSingleSymbolRenderer):
284312
symbol = renderer.symbol()
285313
if symbol:
286-
update_symbols_to_embedded(symbol=symbol)
314+
update_symbols_to_relative_embedded(
315+
symbol, project_home, export_project_path
316+
)
287317

288318
elif isinstance(renderer, QgsRuleBasedRenderer):
289319
for rule in renderer.rootRule().children():
@@ -293,16 +323,23 @@ def embed_layer_symbols_on_project(layer: QgsVectorLayer) -> None:
293323
continue
294324

295325
for symbol in symbols:
296-
update_symbols_to_embedded(symbol=symbol)
326+
update_symbols_to_relative_embedded(
327+
symbol, project_home, export_project_path
328+
)
297329

298330
elif isinstance(renderer, QgsCategorizedSymbolRenderer):
299331
categories = renderer.categories()
300332
if categories:
301333
for index in range(len(categories)):
302-
# Get a fresh category. The renderer doesn't update in-place modifications.
334+
# Get a fresh category.
335+
# The renderer doesn't update in-place modifications on categorized.
303336
category = renderer.categories()[index]
304337
symbol = category.symbol().clone()
305-
update_symbols_to_embedded(symbol=symbol)
338+
339+
update_symbols_to_relative_embedded(
340+
symbol, project_home, export_project_path
341+
)
342+
306343
renderer.updateCategorySymbol(index, symbol)
307344

308345
layer.setRenderer(renderer)

0 commit comments

Comments
 (0)