1/*
2 * Copyright 2008-2009, 2023, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Fran��ois Revol <revol@free.fr>
7 *		Zardshard
8 */
9
10#include "StyledTextImporter.h"
11
12#include <new>
13#include <stdint.h>
14#include <stdio.h>
15
16#include <Alert.h>
17#include <Archivable.h>
18#include <ByteOrder.h>
19#include <Catalog.h>
20#include <DataIO.h>
21#include <File.h>
22#include <List.h>
23#include <Locale.h>
24#include <Message.h>
25#include <NodeInfo.h>
26#include <Shape.h>
27#include <String.h>
28#include <TextView.h>
29
30#include "Defines.h"
31#include "Icon.h"
32#include "PathSourceShape.h"
33#include "Style.h"
34#include "VectorPath.h"
35
36
37#undef B_TRANSLATION_CONTEXT
38#define B_TRANSLATION_CONTEXT "Icon-O-Matic-StyledTextImport"
39//#define CALLED() printf("%s()\n", __FUNCTION__);
40#define CALLED() do {} while (0)
41
42
43using std::nothrow;
44
45class ShapeIterator : public BShapeIterator {
46 public:
47	ShapeIterator(Icon *icon, PathSourceShape *to, BPoint offset, const char *name);
48	~ShapeIterator() {};
49
50	virtual	status_t	IterateMoveTo(BPoint *point);
51	virtual	status_t	IterateLineTo(int32 lineCount, BPoint *linePts);
52	virtual	status_t	IterateBezierTo(int32 bezierCount, BPoint *bezierPts);
53	virtual	status_t	IterateClose();
54
55 private:
56	VectorPath				*CurrentPath();
57	void					NextPath();
58
59	Icon *fIcon;
60	PathSourceShape *fShape;
61	VectorPath *fPath;
62	BPoint fOffset;
63	const char *fName;
64	BPoint fLastPoint;
65	bool fHasLastPoint;
66};
67
68ShapeIterator::ShapeIterator(Icon *icon, PathSourceShape *to, BPoint offset,
69	const char *name)
70{
71	CALLED();
72	fIcon = icon;
73	fShape = to;
74	fPath = NULL;
75	fOffset = offset;
76	fName = name;
77	fLastPoint = offset;
78	fHasLastPoint = false;
79}
80
81status_t
82ShapeIterator::IterateMoveTo(BPoint *point)
83{
84	CALLED();
85	if (fPath)
86		NextPath();
87	if (!CurrentPath())
88		return B_ERROR;
89	//fPath->AddPoint(fOffset + *point);
90	fLastPoint = fOffset + *point;
91	fHasLastPoint = true;
92	return B_OK;
93}
94
95status_t
96ShapeIterator::IterateLineTo(int32 lineCount, BPoint *linePts)
97{
98	CALLED();
99	if (!CurrentPath())
100		return B_ERROR;
101	while (lineCount--) {
102		fPath->AddPoint(fOffset + *linePts);
103		fLastPoint = fOffset + *linePts;
104		fHasLastPoint = true;
105		linePts++;
106	}
107	return B_OK;
108}
109
110status_t
111ShapeIterator::IterateBezierTo(int32 bezierCount, BPoint *bezierPts)
112{
113	CALLED();
114	if (!CurrentPath())
115		return B_ERROR;
116
117	BPoint firstPoint(fLastPoint);
118	fLastPoint = fOffset + bezierPts[bezierCount * 3 - 1];
119
120	// first point
121	if (firstPoint == fLastPoint) {
122		// combine the first and the last point
123		fPath->AddPoint(firstPoint,
124			fOffset + bezierPts[bezierCount * 3 - 2], fOffset + bezierPts[0], false);
125			// Mark the points as disconnected for now. CleanUp will change this if necessary.
126		fPath->SetClosed(true);
127	} else {
128		fPath->AddPoint(firstPoint, firstPoint, fOffset + bezierPts[0], false);
129	}
130
131	// middle points
132	for (int i = 1; i + 2 < bezierCount * 3; i += 3) {
133		fPath->AddPoint(fOffset + bezierPts[i + 1],
134			fOffset + bezierPts[i + 0], fOffset + bezierPts[i + 2], false);
135	}
136
137	// last point
138	if (firstPoint != fLastPoint) {
139		fPath->AddPoint(fLastPoint,
140			fOffset + bezierPts[bezierCount * 3 - 2], fLastPoint, false);
141	}
142
143	fHasLastPoint = true;
144	return B_OK;
145}
146
147status_t
148ShapeIterator::IterateClose()
149{
150	CALLED();
151	if (!CurrentPath())
152		return B_ERROR;
153	fPath->SetClosed(true);
154	NextPath();
155	fHasLastPoint = false;
156	return B_OK;
157}
158
159VectorPath *
160ShapeIterator::CurrentPath()
161{
162	CALLED();
163	if (fPath)
164		return fPath;
165	fPath = new (nothrow) VectorPath();
166	fPath->SetName(fName);
167	return fPath;
168}
169
170void
171ShapeIterator::NextPath()
172{
173	CALLED();
174	if (fPath) {
175		fPath->CleanUp();
176		fIcon->Paths()->AddItem(fPath);
177		fShape->Paths()->AddItem(fPath);
178	}
179	fPath = NULL;
180}
181
182
183// #pragma mark -
184
185// constructor
186StyledTextImporter::StyledTextImporter()
187	: Importer(),
188	  fStyleMap(NULL),
189	  fStyleCount(0)
190{
191	CALLED();
192}
193
194// destructor
195StyledTextImporter::~StyledTextImporter()
196{
197	CALLED();
198}
199
200// Import
201status_t
202StyledTextImporter::Import(Icon* icon, BMessage* clipping)
203{
204	CALLED();
205	const char *text;
206	ssize_t textLength;
207
208	if (clipping == NULL)
209		return ENOENT;
210	if (clipping->FindData("text/plain",
211		B_MIME_TYPE, (const void **)&text, &textLength) == B_OK) {
212		text_run_array *runs = NULL;
213		ssize_t runsLength;
214		if (clipping->FindData("application/x-vnd.Be-text_run_array",
215			B_MIME_TYPE, (const void **)&runs, &runsLength) < B_OK)
216			runs = NULL;
217		BString str(text, textLength);
218		return _Import(icon, str.String(), runs);
219	}
220	return ENOENT;
221}
222
223// Import
224status_t
225StyledTextImporter::Import(Icon* icon, const entry_ref* ref)
226{
227	CALLED();
228	status_t err;
229	BFile file(ref, B_READ_ONLY);
230	err = file.InitCheck();
231	if (err < B_OK)
232		return err;
233
234	BNodeInfo info(&file);
235	char mime[B_MIME_TYPE_LENGTH];
236	err = info.GetType(mime);
237	if (err < B_OK)
238		return err;
239
240	if (strncmp(mime, "text/plain", B_MIME_TYPE_LENGTH))
241		return EINVAL;
242
243	off_t size;
244	err = file.GetSize(&size);
245	if (err < B_OK)
246		return err;
247	if (size > 1 * 1024 * 1024) // Don't load files that big
248		return E2BIG;
249
250	BMallocIO mio;
251	mio.SetSize((size_t)size + 1);
252	memset((void *)mio.Buffer(), 0, (size_t)size + 1);
253
254	// TODO: read runs from attribute
255
256	return _Import(icon, (const char *)mio.Buffer(), NULL);
257}
258
259
260// _Import
261status_t
262StyledTextImporter::_Import(Icon* icon, const char *text, text_run_array *runs)
263{
264	CALLED();
265	status_t ret = Init(icon);
266	if (ret < B_OK) {
267		printf("StyledTextImporter::Import() - Init() error: %s\n",
268			strerror(ret));
269		return ret;
270	}
271
272	BString str(text);
273	if (str.Length() > 50) {
274		BAlert* alert = new BAlert(B_TRANSLATE("Text too long"),
275			B_TRANSLATE("The text you are trying to import is quite long, "
276				"are you sure?"),
277			B_TRANSLATE("Import text"), B_TRANSLATE("Cancel"), NULL,
278			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
279		if (alert->Go())
280			return B_CANCELED;
281	}
282
283	// import run colors as styles
284	if (runs) {
285		delete[] fStyleMap;
286		fStyleMap = new struct style_map[runs->count];
287		for (int32 i = 0; runs && i < runs->count; i++) {
288			_AddStyle(icon, &runs->runs[i]);
289		}
290	}
291
292	int32 currentRun = 0;
293	text_run *run = NULL;
294	if (runs)
295		run = &runs->runs[0];
296	int32 len = str.Length();
297	int32 chars = str.CountChars();
298	BPoint origin(0, 0);
299	BPoint offset(origin);
300
301	for (int32 i = 0, c = 0; i < len && c < chars; c++) {
302		// make sure we are still on the (good) run
303		while (run && currentRun < runs->count - 1 &&
304			i >= runs->runs[currentRun + 1].offset) {
305			run = &runs->runs[++currentRun];
306			//printf("switching to run %d\n", currentRun);
307		}
308
309		int charLen;
310		for (charLen = 1; str.ByteAt(i + charLen) & 0x80; charLen++);
311
312		BShape glyph;
313		BShape *glyphs[1] = { &glyph };
314		BFont font(be_plain_font);
315		if (run)
316			font = run->font;
317
318		// first char
319		if (offset == BPoint(0,0)) {
320			font_height height;
321			font.GetHeight(&height);
322			origin.y += height.ascent;
323			offset = origin;
324		}
325		// LF
326		if (str[i] == '\n') {
327			// XXX: should take the MAX() for the line
328			// XXX: should use descent + leading from previous line
329			font_height height;
330			font.GetHeight(&height);
331			origin.y += height.ascent + height.descent + height.leading;
332			offset = origin;
333			i++;
334			continue;
335		}
336
337		float charWidth;
338		charWidth = font.StringWidth(str.String() + i, charLen);
339		//printf("StringWidth( %d) = %f\n", charLen, charWidth);
340		BString glyphName(str.String() + i, charLen);
341		glyphName.Prepend("Glyph (");
342		glyphName.Append(")");
343
344		font.GetGlyphShapes((str.String() + i), 1, glyphs);
345		if (glyph.Bounds().IsValid()) {
346			//offset.x += glyph.Bounds().Width();
347			offset.x += charWidth;
348			PathSourceShape* shape = new (nothrow) PathSourceShape(NULL);
349			if (shape == NULL)
350				return B_NO_MEMORY;
351			shape->SetName(glyphName.String());
352			if (!icon->Shapes()->AddItem(shape)) {
353				delete shape;
354				return B_NO_MEMORY;
355			}
356			for (int j = 0; run && j < fStyleCount; j++) {
357				if (fStyleMap[j].run == run) {
358					shape->SetStyle(fStyleMap[j].style);
359					break;
360				}
361			}
362			ShapeIterator iterator(icon, shape, offset, glyphName.String());
363			if (iterator.Iterate(&glyph) < B_OK)
364				return B_ERROR;
365
366		}
367
368		// skip the rest of UTF-8 char bytes
369		for (i++; i < len && str[i] & 0x80; i++);
370	}
371
372	delete[] fStyleMap;
373	fStyleMap = NULL;
374
375	return B_OK;
376}
377
378// #pragma mark -
379
380// _AddStyle
381status_t
382StyledTextImporter::_AddStyle(Icon *icon, text_run *run)
383{
384	CALLED();
385	if (!run)
386		return EINVAL;
387	rgb_color color = run->color;
388	Style* style = new(std::nothrow) Style(color);
389	if (style == NULL)
390		return B_NO_MEMORY;
391	char name[30];
392	sprintf(name, B_TRANSLATE_COMMENT("Color (#%02x%02x%02x)",
393		"Style name after dropping a color"),
394		color.red, color.green, color.blue);
395	style->SetName(name);
396
397	bool found = false;
398	for (int i = 0; i < fStyleCount; i++) {
399		if (*style == *(fStyleMap[i].style)) {
400			delete style;
401			style = fStyleMap[i].style;
402			found = true;
403			break;
404		}
405	}
406
407	if (!found && !icon->Styles()->AddItem(style)) {
408		delete style;
409		return B_NO_MEMORY;
410	}
411
412	fStyleMap[fStyleCount].run = run;
413	fStyleMap[fStyleCount].style = style;
414	fStyleCount++;
415
416	return B_OK;
417}
418
419// _AddPaths
420status_t
421StyledTextImporter::_AddPaths(Icon *icon, BShape *shape)
422{
423	CALLED();
424	return B_ERROR;
425}
426
427// _AddShape
428status_t
429StyledTextImporter::_AddShape(Icon *icon, BShape *shape, text_run *run)
430{
431	CALLED();
432	return B_ERROR;
433}
434