1/*
2 * Copyright 2006, 2023, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus <superstippi@gmx.de>
7 *		Zardshard
8 */
9
10#include "FlatIconExporter.h"
11
12#include <new>
13#include <stdio.h>
14
15#include <Archivable.h>
16#include <Catalog.h>
17#include <DataIO.h>
18#include <Locale.h>
19#include <Message.h>
20#include <Node.h>
21
22#include "AffineTransformer.h"
23#include "Container.h"
24#include "ContourTransformer.h"
25#include "FlatIconFormat.h"
26#include "GradientTransformable.h"
27#include "Icon.h"
28#include "LittleEndianBuffer.h"
29#include "PathCommandQueue.h"
30#include "PathSourceShape.h"
31#include "PerspectiveTransformer.h"
32#include "ReferenceImage.h"
33#include "Shape.h"
34#include "StrokeTransformer.h"
35#include "Style.h"
36#include "VectorPath.h"
37
38#undef B_TRANSLATION_CONTEXT
39#define B_TRANSLATION_CONTEXT "Icon-O-Matic-FlatIconExporter"
40
41using std::nothrow;
42
43
44FlatIconExporter::FlatIconExporter()
45#if PRINT_STATISTICS
46	: fStyleSectionSize(0)
47	, fGradientSize(0)
48	, fGradientTransformSize(0)
49	, fPathSectionSize(0)
50	, fShapeSectionSize(0)
51	, fPointCount(0)
52#endif // PRINT_STATISTICS
53{
54}
55
56
57FlatIconExporter::~FlatIconExporter()
58{
59#if PRINT_STATISTICS
60	printf("Statistics\n"
61		   "--style section size: %" B_PRId32 "\n"
62		   "           gradients: %" B_PRId32 "\n"
63		   " gradient transforms: %" B_PRId32 "\n"
64		   "---path section size: %" B_PRId32 "\n"
65		   "--shape section size: %" B_PRId32 "\n"
66		   "---total/different points: %" B_PRId32 "/%" B_PRIdSSIZE "\n",
67		   fStyleSectionSize,
68		   fGradientSize,
69		   fGradientTransformSize,
70		   fPathSectionSize,
71		   fShapeSectionSize,
72		   fPointCount,
73		   fUsedPoints.size());
74#endif // PRINT_STATISTICS
75}
76
77
78status_t
79FlatIconExporter::Export(const Icon* icon, BPositionIO* stream)
80{
81	LittleEndianBuffer buffer;
82
83	// flatten icon
84	status_t ret = _Export(buffer, icon);
85	if (ret < B_OK)
86		return ret;
87
88	// write buffer to stream
89	ssize_t written = stream->Write(buffer.Buffer(), buffer.SizeUsed());
90	if (written != (ssize_t)buffer.SizeUsed()) {
91		if (written < 0)
92			return (status_t)written;
93		return B_ERROR;
94	}
95
96	return B_OK;
97}
98
99
100const char*
101FlatIconExporter::ErrorCodeToString(status_t code)
102{
103	switch (code) {
104		case E_TOO_MANY_PATHS:
105			return B_TRANSLATE("There are too many paths. "
106				"The HVIF format supports a maximum of 255.");
107		case E_PATH_TOO_MANY_POINTS:
108			return B_TRANSLATE("One or more of the paths have too many vertices. "
109				"The HVIF format supports a maximum of 255 vertices per path.");
110		case E_TOO_MANY_SHAPES:
111			return B_TRANSLATE("There are too many shapes. "
112				"The HVIF format supports a maximum of 255.");
113		case E_SHAPE_TOO_MANY_PATHS:
114			return B_TRANSLATE("One or more of the shapes has too many paths. "
115				"The HVIF format supports a maximum of 255 paths per shape.");
116		case E_SHAPE_TOO_MANY_TRANSFORMERS:
117			return B_TRANSLATE("One or more of the shapes have too many transformers. "
118				"The HVIF format supports a maximum of 255 transformers per shape.");
119		case E_TOO_MANY_STYLES:
120			return B_TRANSLATE("There are too many styles. "
121				"The HVIF format supports a maximum of 255.");
122		default:
123			return Exporter::ErrorCodeToString(code);
124	}
125}
126
127
128// #pragma mark -
129
130
131status_t
132FlatIconExporter::Export(const Icon* icon, BNode* node,
133						 const char* attrName)
134{
135	LittleEndianBuffer buffer;
136
137	// flatten icon
138	status_t ret = _Export(buffer, icon);
139	if (ret < B_OK) {
140		printf("failed to export to buffer: %s\n", strerror(ret));
141		return ret;
142	}
143
144#ifndef __HAIKU__
145	// work arround a BFS bug, attributes with the same name but different
146	// type fail to be written
147	node->RemoveAttr(attrName);
148#endif
149
150	// write buffer to attribute
151	ssize_t written = node->WriteAttr(attrName, B_VECTOR_ICON_TYPE, 0,
152									  buffer.Buffer(), buffer.SizeUsed());
153	if (written != (ssize_t)buffer.SizeUsed()) {
154		if (written < 0) {
155			printf("failed to write attribute: %s\n",
156				strerror((status_t)written));
157			return (status_t)written;
158		}
159		printf("failed to write attribute\n");
160		return B_ERROR;
161	}
162
163	return B_OK;
164}
165
166
167// #pragma mark -
168
169
170status_t
171FlatIconExporter::_Export(LittleEndianBuffer& buffer, const Icon* icon)
172{
173	if (!buffer.Write(FLAT_ICON_MAGIC))
174		return B_NO_MEMORY;
175
176#if PRINT_STATISTICS
177	fGradientSize = 0;
178	fGradientTransformSize = 0;
179	fPointCount = 0;
180#endif
181
182	// styles
183	const Container<Style>* styles = icon->Styles();
184	status_t ret = _WriteStyles(buffer, styles);
185	if (ret < B_OK)
186		return ret;
187
188#if PRINT_STATISTICS
189	fStyleSectionSize = buffer.SizeUsed();
190#endif
191
192	// paths
193	const Container<VectorPath>* paths = icon->Paths();
194	ret = _WritePaths(buffer, paths);
195	if (ret < B_OK)
196		return ret;
197
198#if PRINT_STATISTICS
199	fPathSectionSize = buffer.SizeUsed() - fStyleSectionSize;
200#endif
201
202	// shapes
203	ret = _WriteShapes(buffer, styles, paths, icon->Shapes());
204	if (ret < B_OK)
205		return ret;
206
207#if PRINT_STATISTICS
208	fShapeSectionSize = buffer.SizeUsed()
209		- (fStyleSectionSize + fPathSectionSize);
210#endif
211
212	return B_OK;
213}
214
215
216static bool
217_WriteTransformable(LittleEndianBuffer& buffer, const Transformable* transformable)
218{
219	int32 matrixSize = Transformable::matrix_size;
220	double matrix[matrixSize];
221	transformable->StoreTo(matrix);
222	for (int32 i = 0; i < matrixSize; i++) {
223//		if (!buffer.Write((float)matrix[i]))
224//			return false;
225		if (!write_float_24(buffer, (float)matrix[i]))
226			return false;
227	}
228	return true;
229}
230
231
232static bool
233_WriteTranslation(LittleEndianBuffer& buffer, const Transformable* transformable)
234{
235	BPoint t(B_ORIGIN);
236	transformable->Transform(&t);
237	return write_coord(buffer, t.x) && write_coord(buffer, t.y);
238}
239
240
241status_t
242FlatIconExporter::_WriteStyles(LittleEndianBuffer& buffer, const Container<Style>* styles)
243{
244	if (styles->CountItems() > 255)
245		return E_TOO_MANY_STYLES;
246	uint8 styleCount = min_c(255, styles->CountItems());
247	if (!buffer.Write(styleCount))
248		return B_NO_MEMORY;
249
250	for (int32 i = 0; i < styleCount; i++) {
251		Style* style = styles->ItemAtFast(i);
252
253		// style type
254		uint8 styleType;
255
256		const Gradient* gradient = style->Gradient();
257		if (gradient) {
258			styleType = STYLE_TYPE_GRADIENT;
259		} else {
260			rgb_color color = style->Color();
261			if (color.red == color.green && color.red == color.blue) {
262				// gray
263				if (style->Color().alpha == 255)
264					styleType = STYLE_TYPE_SOLID_GRAY_NO_ALPHA;
265				else
266					styleType = STYLE_TYPE_SOLID_GRAY;
267			} else {
268				// color
269				if (style->Color().alpha == 255)
270					styleType = STYLE_TYPE_SOLID_COLOR_NO_ALPHA;
271				else
272					styleType = STYLE_TYPE_SOLID_COLOR;
273			}
274		}
275
276		if (!buffer.Write(styleType))
277			return B_NO_MEMORY;
278
279		if (styleType == STYLE_TYPE_SOLID_COLOR) {
280			// solid color
281			rgb_color color = style->Color();
282			if (!buffer.Write(*(uint32*)&color))
283				return B_NO_MEMORY;
284		} else if (styleType == STYLE_TYPE_SOLID_COLOR_NO_ALPHA) {
285			// solid color without alpha
286			rgb_color color = style->Color();
287			if (!buffer.Write(color.red)
288				|| !buffer.Write(color.green)
289				|| !buffer.Write(color.blue))
290				return B_NO_MEMORY;
291		} else if (styleType == STYLE_TYPE_SOLID_GRAY) {
292			// solid gray
293			rgb_color color = style->Color();
294			if (!buffer.Write(color.red)
295				|| !buffer.Write(color.alpha))
296				return B_NO_MEMORY;
297		} else if (styleType == STYLE_TYPE_SOLID_GRAY_NO_ALPHA) {
298			// solid gray without alpha
299			if (!buffer.Write(style->Color().red))
300				return B_NO_MEMORY;
301		} else if (styleType == STYLE_TYPE_GRADIENT) {
302			// gradient
303			if (!_WriteGradient(buffer, gradient))
304				return B_NO_MEMORY;
305		}
306	}
307
308	return B_OK;
309}
310
311
312bool
313FlatIconExporter::_AnalysePath(VectorPath* path, uint8 pointCount,
314	int32& straightCount, int32& lineCount, int32& curveCount)
315{
316	straightCount = 0;
317	lineCount = 0;
318	curveCount = 0;
319
320	BPoint lastPoint(B_ORIGIN);
321	for (uint32 p = 0; p < pointCount; p++) {
322		BPoint point;
323		BPoint pointIn;
324		BPoint pointOut;
325		if (!path->GetPointsAt(p, point, pointIn, pointOut))
326			return B_ERROR;
327		if (point == pointIn && point == pointOut) {
328			if (point.x == lastPoint.x || point.y == lastPoint.y)
329				straightCount++;
330			else
331				lineCount++;
332		} else
333			curveCount++;
334		lastPoint = point;
335
336#if PRINT_STATISTICS
337		fUsedPoints.insert(point);
338		fUsedPoints.insert(pointIn);
339		fUsedPoints.insert(pointOut);
340		fPointCount += 3;
341#endif
342	}
343
344	return true;
345}
346
347
348
349static bool
350write_path_no_curves(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount)
351{
352//printf("write_path_no_curves()\n");
353	for (uint32 p = 0; p < pointCount; p++) {
354		BPoint point;
355		if (!path->GetPointAt(p, point))
356			return false;
357		if (!write_coord(buffer, point.x) || !write_coord(buffer, point.y))
358			return false;
359	}
360	return true;
361}
362
363
364static bool
365write_path_curves(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount)
366{
367//printf("write_path_curves()\n");
368	for (uint32 p = 0; p < pointCount; p++) {
369		BPoint point;
370		BPoint pointIn;
371		BPoint pointOut;
372		if (!path->GetPointsAt(p, point, pointIn, pointOut))
373			return false;
374		if (!write_coord(buffer, point.x)
375			|| !write_coord(buffer, point.y))
376			return false;
377		if (!write_coord(buffer, pointIn.x)
378			|| !write_coord(buffer, pointIn.y))
379			return false;
380		if (!write_coord(buffer, pointOut.x)
381			|| !write_coord(buffer, pointOut.y))
382			return false;
383	}
384	return true;
385}
386
387
388static bool
389write_path_with_commands(LittleEndianBuffer& buffer, VectorPath* path, uint8 pointCount)
390{
391	PathCommandQueue queue;
392	return queue.Write(buffer, path, pointCount);
393}
394
395
396
397status_t
398FlatIconExporter::_WritePaths(LittleEndianBuffer& buffer, const Container<VectorPath>* paths)
399{
400	if (paths->CountItems() > 255)
401		return E_TOO_MANY_PATHS;
402	uint8 pathCount = min_c(255, paths->CountItems());
403	if (!buffer.Write(pathCount))
404		return B_NO_MEMORY;
405
406	for (uint32 i = 0; i < pathCount; i++) {
407		VectorPath* path = paths->ItemAtFast(i);
408		uint8 pathFlags = 0;
409		if (path->IsClosed())
410			pathFlags |= PATH_FLAG_CLOSED;
411
412		if (path->CountPoints() > 255)
413			return E_PATH_TOO_MANY_POINTS;
414		uint8 pointCount = min_c(255, path->CountPoints());
415
416		// see if writing segments with commands is more efficient
417		// than writing all segments as curves with no commands
418		int32 straightCount;
419		int32 lineCount;
420		int32 curveCount;
421		if (!_AnalysePath(path, pointCount,
422			 	straightCount, lineCount, curveCount))
423			 return B_ERROR;
424
425		int32 commandPathLength
426			= pointCount + straightCount * 2 + lineCount * 4 + curveCount * 12;
427		int32 plainPathLength
428			= pointCount * 12;
429//printf("segments: %d, command length: %ld, plain length: %ld\n",
430//	pointCount, commandPathLength, plainPathLength);
431
432		if (commandPathLength < plainPathLength) {
433			if (curveCount == 0)
434				pathFlags |= PATH_FLAG_NO_CURVES;
435			else
436				pathFlags |= PATH_FLAG_USES_COMMANDS;
437		}
438
439		if (!buffer.Write(pathFlags) || !buffer.Write(pointCount))
440			return B_NO_MEMORY;
441
442		if (pathFlags & PATH_FLAG_NO_CURVES) {
443			if (!write_path_no_curves(buffer, path, pointCount))
444				return B_ERROR;
445		} else if (pathFlags & PATH_FLAG_USES_COMMANDS) {
446			if (!write_path_with_commands(buffer, path, pointCount))
447				return B_ERROR;
448		} else {
449			if (!write_path_curves(buffer, path, pointCount))
450				return B_ERROR;
451		}
452	}
453
454	return B_OK;
455}
456
457
458static bool
459_WriteTransformer(LittleEndianBuffer& buffer, Transformer* t)
460{
461	if (AffineTransformer* affine
462		= dynamic_cast<AffineTransformer*>(t)) {
463		// affine
464		if (!buffer.Write((uint8)TRANSFORMER_TYPE_AFFINE))
465			return false;
466		double matrix[6];
467		affine->store_to(matrix);
468		for (int32 i = 0; i < 6; i++) {
469			if (!write_float_24(buffer, (float)matrix[i]))
470				return false;
471		}
472
473	} else if (ContourTransformer* contour
474		= dynamic_cast<ContourTransformer*>(t)) {
475		// contour
476		if (!buffer.Write((uint8)TRANSFORMER_TYPE_CONTOUR))
477			return false;
478		uint8 width = (uint8)((int8)contour->width() + 128);
479		uint8 lineJoin = (uint8)contour->line_join();
480		uint8 miterLimit = (uint8)contour->miter_limit();
481		if (!buffer.Write(width)
482			|| !buffer.Write(lineJoin)
483			|| !buffer.Write(miterLimit))
484			return false;
485
486	} else if (PerspectiveTransformer* perspective
487		= dynamic_cast<PerspectiveTransformer*>(t)) {
488		// perspective
489		if (!buffer.Write((uint8)TRANSFORMER_TYPE_PERSPECTIVE))
490			return false;
491		double matrix[9];
492		perspective->store_to(matrix);
493		for (int32 i = 0; i < 9; i++) {
494			if (!write_float_24(buffer, (float)matrix[i]))
495				return false;
496		}
497
498	} else if (StrokeTransformer* stroke
499		= dynamic_cast<StrokeTransformer*>(t)) {
500		// stroke
501		if (!buffer.Write((uint8)TRANSFORMER_TYPE_STROKE))
502			return false;
503		uint8 width = (uint8)((int8)stroke->width() + 128);
504		uint8 lineOptions = (uint8)stroke->line_join();
505		lineOptions |= ((uint8)stroke->line_cap()) << 4;
506		uint8 miterLimit = (uint8)stroke->miter_limit();
507
508		if (!buffer.Write(width)
509			|| !buffer.Write(lineOptions)
510			|| !buffer.Write(miterLimit))
511			return false;
512	}
513
514	return true;
515}
516
517
518static bool
519_WritePathSourceShape(LittleEndianBuffer& buffer, PathSourceShape* shape,
520	const Container<Style>* styles, const Container<VectorPath>* paths)
521{
522	// find out which style this shape uses
523	Style* style = shape->Style();
524	if (!style)
525		return false;
526
527	int32 styleIndex = styles->IndexOf(style);
528	if (styleIndex < 0 || styleIndex > 255)
529		return false;
530
531	if (shape->Paths()->CountItems() > 255)
532		return E_SHAPE_TOO_MANY_PATHS;
533	uint8 pathCount = min_c(255, shape->Paths()->CountItems());
534
535	// write shape type and style index
536	if (!buffer.Write((uint8)SHAPE_TYPE_PATH_SOURCE)
537		|| !buffer.Write((uint8)styleIndex)
538		|| !buffer.Write(pathCount))
539		return false;
540
541	// find out which paths this shape uses
542	for (uint32 i = 0; i < pathCount; i++) {
543		VectorPath* path = shape->Paths()->ItemAtFast(i);
544		int32 pathIndex = paths->IndexOf(path);
545		if (pathIndex < 0 || pathIndex > 255)
546			return false;
547
548		if (!buffer.Write((uint8)pathIndex))
549			return false;
550	}
551
552	if (shape->Transformers()->CountItems() > 255)
553		return E_SHAPE_TOO_MANY_TRANSFORMERS;
554	uint8 transformerCount = min_c(255, shape->Transformers()->CountItems());
555
556	// shape flags
557	uint8 shapeFlags = 0;
558	if (!shape->IsIdentity()) {
559		if (shape->IsTranslationOnly())
560			shapeFlags |= SHAPE_FLAG_TRANSLATION;
561		else
562			shapeFlags |= SHAPE_FLAG_TRANSFORM;
563	}
564	if (shape->Hinting())
565		shapeFlags |= SHAPE_FLAG_HINTING;
566	if (shape->MinVisibilityScale() != 0.0
567		|| shape->MaxVisibilityScale() != 4.0)
568		shapeFlags |= SHAPE_FLAG_LOD_SCALE;
569	if (transformerCount > 0)
570		shapeFlags |= SHAPE_FLAG_HAS_TRANSFORMERS;
571
572	if (!buffer.Write((uint8)shapeFlags))
573		return false;
574
575	if (shapeFlags & SHAPE_FLAG_TRANSFORM) {
576		// transformation
577		if (!_WriteTransformable(buffer, shape))
578			return false;
579	} else if (shapeFlags & SHAPE_FLAG_TRANSLATION) {
580		// translation
581		if (!_WriteTranslation(buffer, shape))
582			return false;
583	}
584
585	if (shapeFlags & SHAPE_FLAG_LOD_SCALE) {
586		// min max visibility scale
587		if (!buffer.Write(
588				(uint8)(shape->MinVisibilityScale() * 63.75 + 0.5))
589			|| !buffer.Write(
590				(uint8)(shape->MaxVisibilityScale() * 63.75 + 0.5))) {
591			return false;
592		}
593	}
594
595	if (shapeFlags & SHAPE_FLAG_HAS_TRANSFORMERS) {
596		// transformers
597		if (!buffer.Write(transformerCount))
598			return false;
599
600		for (uint32 i = 0; i < transformerCount; i++) {
601			Transformer* transformer = shape->Transformers()->ItemAtFast(i);
602			if (!_WriteTransformer(buffer, transformer))
603				return false;
604		}
605	}
606
607	return true;
608}
609
610
611status_t
612FlatIconExporter::_WriteShapes(LittleEndianBuffer& buffer, const Container<Style>* styles,
613	const Container<VectorPath>* paths, const Container<Shape>* shapes)
614{
615	uint32 shapeCount = shapes->CountItems();
616
617	// Count the number of exportable shapes
618	uint32 pathShapeCount = 0;
619	for (uint32 i = 0; i < shapeCount; i++) {
620		Shape* shape = shapes->ItemAtFast(i);
621		if (dynamic_cast<PathSourceShape*>(shape) != NULL)
622			pathShapeCount++;
623	}
624
625	// Write number of exportable shapes
626	if (pathShapeCount > 255)
627		return E_TOO_MANY_SHAPES;
628	if (!buffer.Write((uint8) pathShapeCount))
629		return B_NO_MEMORY;
630
631	// Export each shape
632	for (uint32 i = 0; i < shapeCount; i++) {
633		Shape* shape = shapes->ItemAtFast(i);
634
635		PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(shape);
636		if (pathSourceShape != NULL) {
637			if (!_WritePathSourceShape(buffer, pathSourceShape, styles, paths))
638				return B_ERROR;
639		}
640	}
641
642	return B_OK;
643}
644
645
646bool
647FlatIconExporter::_WriteGradient(LittleEndianBuffer& buffer, const Gradient* gradient)
648{
649#if PRINT_STATISTICS
650	size_t currentSize = buffer.SizeUsed();
651#endif
652
653	uint8 gradientType = (uint8)gradient->Type();
654	uint8 gradientFlags = 0;
655	uint8 gradientStopCount = (uint8)gradient->CountColors();
656
657	// figure out flags
658	if (!gradient->IsIdentity())
659		gradientFlags |= GRADIENT_FLAG_TRANSFORM;
660
661	bool alpha = false;
662	bool gray = true;
663	for (int32 i = 0; i < gradientStopCount; i++) {
664		BGradient::ColorStop* step = gradient->ColorAtFast(i);
665		if (step->color.alpha < 255)
666			alpha = true;
667		if (step->color.red != step->color.green
668			|| step->color.red != step->color.blue)
669			gray = false;
670	}
671	if (!alpha)
672		gradientFlags |= GRADIENT_FLAG_NO_ALPHA;
673	if (gray)
674		gradientFlags |= GRADIENT_FLAG_GRAYS;
675
676	if (!buffer.Write(gradientType)
677		|| !buffer.Write(gradientFlags)
678		|| !buffer.Write(gradientStopCount))
679		return false;
680
681	if (gradientFlags & GRADIENT_FLAG_TRANSFORM) {
682		if (!_WriteTransformable(buffer, gradient))
683			return false;
684#if PRINT_STATISTICS
685	fGradientTransformSize += Transformable::matrix_size * sizeof(float);
686#endif
687	}
688
689	for (int32 i = 0; i < gradientStopCount; i++) {
690		BGradient::ColorStop* step = gradient->ColorAtFast(i);
691		uint8 stopOffset = (uint8)(step->offset * 255.0);
692		uint32 color = (uint32&)step->color;
693		if (!buffer.Write(stopOffset))
694			return false;
695		if (alpha) {
696			if (gray) {
697				if (!buffer.Write(step->color.red)
698					|| !buffer.Write(step->color.alpha))
699					return false;
700			} else {
701				if (!buffer.Write(color))
702					return false;
703			}
704		} else {
705			if (gray) {
706				if (!buffer.Write(step->color.red))
707					return false;
708			} else {
709				if (!buffer.Write(step->color.red)
710					|| !buffer.Write(step->color.green)
711					|| !buffer.Write(step->color.blue))
712					return false;
713			}
714		}
715	}
716
717#if PRINT_STATISTICS
718	fGradientSize += buffer.SizeUsed() - currentSize;
719#endif
720
721	return true;
722}
723
724